Completed
Push — master ( 9cffad...7a46cc )
by Andrei
01:25
created

optics   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 359
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 359
rs 8.5454
wmc 49

13 Methods

Rating   Name   Duplication   Size   Complexity  
A get_clusters() 0 14 1
A get_radius() 0 14 1
B __neighbor_indexes() 0 18 7
B process() 0 27 6
B __expand_cluster_order() 0 45 6
A get_cluster_encoding() 0 11 1
B __extract_clusters() 0 19 6
A __allocate_clusters() 0 13 3
B __init__() 0 34 2
B get_ordering() 0 24 5
A __initialize() 0 12 2
D __update_order_seed() 0 32 8
A get_noise() 0 14 1

How to fix   Complexity   

Complex Class

Complex classes like optics often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""!
2
3
@brief Cluster analysis algorithm: OPTICS (Ordering Points To Identify Clustering Structure)
4
@details Based on article description:
5
         - M.Ankerst, M.Breunig, H.Kriegel, J.Sander. OPTICS: Ordering Points To Identify the Clustering Structure. 1999.
6
7
@authors Andrei Novikov ([email protected])
8
@date 2014-2018
9
@copyright GNU Public License
10
11
@cond GNU_PUBLIC_LICENSE
12
    PyClustering is free software: you can redistribute it and/or modify
13
    it under the terms of the GNU General Public License as published by
14
    the Free Software Foundation, either version 3 of the License, or
15
    (at your option) any later version.
16
    
17
    PyClustering is distributed in the hope that it will be useful,
18
    but WITHOUT ANY WARRANTY; without even the implied warranty of
19
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
    GNU General Public License for more details.
21
    
22
    You should have received a copy of the GNU General Public License
23
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
@endcond
25
26
"""
27
28
29
import math;
30
31
import matplotlib.pyplot as plt;
0 ignored issues
show
Configuration introduced by
The import matplotlib.pyplot could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
32
33
from enum import IntEnum;
34
35
from pyclustering.container.kdtree import kdtree;
36
37
from pyclustering.cluster.encoder import type_encoding;
38
39
from pyclustering.utils import get_argument;
40
from pyclustering.utils.color import color as color_list;
41
42
from pyclustering.core.wrapper import ccore_library;
43
44
import pyclustering.core.optics_wrapper as wrapper;
45
46
47
class optics_data_type(IntEnum):
48
    """!
49
    @brief Enumeration of OPTICS input data types that is used for processing: points, adjacency matrix.
50
51
    """
52
53
    ## Input data is represented by points that are contained by array like container, for example, by list.
54
    POINTS = 0;
55
56
    ## Input data is represented by distance matrix between points.
57
    DISTANCE_MATRIX = 1;
58
59
60
class ordering_visualizer:
61
    """!
62
    @brief Cluster ordering diagram visualizer that represents dataset graphically as density-based clustering structure.
63
    @details This OPTICS algorithm is KD-tree optimized.
64
    
65
    @see ordering_analyser
66
    
67
    """
68
    
69
    @staticmethod
70
    def show_ordering_diagram(analyser, amount_clusters = None):
71
        """!
72
        @brief Display cluster-ordering (reachability-plot) diagram.
73
        
74
        @param[in] analyser (ordering_analyser): cluster-ordering analyser whose ordering diagram should be displayed.
75
        @param[in] amount_clusters (uint): if it is not 'None' then it displays connectivity radius line that can used for allocation of specified amount of clusters
76
                    and colorize diagram by corresponding cluster colors.
77
        
78
        Example demonstrates general abilities of 'ordering_visualizer' class:
79
        @code
80
            # Display cluster-ordering diagram with connectivity radius is used for allocation of three clusters.
81
            ordering_visualizer.show_ordering_diagram(analyser, 3);
82
        
83
            # Display cluster-ordering diagram without radius.
84
            ordering_visualizer.show_ordering_diagram(analyser);
85
        @endcode
86
        
87
        """
88
        ordering = analyser.cluster_ordering;
89
        axis = plt.subplot(111);
90
        
91
        if (amount_clusters is not None):
92
            radius, borders = analyser.calculate_connvectivity_radius(amount_clusters);
93
        
94
            # divide into cluster groups to visualize by colors
95
            left_index_border = 0;
96
            current_index_border = 0;
97
            for index_border in range(len(borders)):
98
                right_index_border = borders[index_border];
99
                axis.bar(range(left_index_border, right_index_border), ordering[left_index_border:right_index_border], width = 1.0, color = color_list.TITLES[index_border]);
100
                left_index_border = right_index_border;
101
                current_index_border = index_border;
102
            
103
            axis.bar(range(left_index_border, len(ordering)), ordering[left_index_border:len(ordering)], width = 1.0, color = color_list.TITLES[current_index_border + 1]);
104
            
105
            plt.xlim([0, len(ordering)]);
106
            
107
            plt.axhline(y = radius, linewidth = 2, color = 'black');
108
            plt.text(0, radius + radius * 0.03, " Radius:   " + str(round(radius, 4)) + ";\n Clusters: " + str(amount_clusters), color = 'b', fontsize = 10);
109
            
110
        else:
111
            axis.bar(range(0, len(ordering)), ordering[0:len(ordering)], width = 1.0, color = 'black');
112
            plt.xlim([0, len(ordering)]);
113
        
114
        plt.show();
115
116
117
class ordering_analyser:
118
    """!
119
    @brief Analyser of cluster ordering diagram.
120
    @details Using cluster-ordering it is able to connectivity radius for allocation of specified amount of clusters and
121
              calculate amount of clusters using specified connectivity radius. Cluster-ordering is formed by OPTICS algorithm
122
              during cluster analysis.
123
    
124
    @see optics
125
    
126
    """
127
    
128
    @property
129
    def cluster_ordering(self):
130
        """!
131
        @brief (list) Returns values of dataset cluster ordering.
132
        
133
        """
134
        return self.__ordering;
135
    
136
    
137
    def __init__(self, ordering_diagram):
138
        """!
139
        @brief Analyser of ordering diagram that is based on reachability-distances.
140
        
141
        @see calculate_connvectivity_radius
142
        
143
        """
144
        self.__ordering = ordering_diagram;
145
    
146
    
147
    def __len__(self):
148
        """!
149
        @brief Returns length of clustering-ordering diagram.
150
        
151
        """
152
        return len(self.__ordering);
153
    
154
    
155
    def calculate_connvectivity_radius(self, amount_clusters, maximum_iterations = 100):
156
        """!
157
        @brief Calculates connectivity radius of allocation specified amount of clusters using ordering diagram and marks borders of clusters using indexes of values of ordering diagram.
158
        @details Parameter 'maximum_iterations' is used to protect from hanging when it is impossible to allocate specified number of clusters.
159
        
160
        @param[in] amount_clusters (uint): amount of clusters that should be allocated by calculated connectivity radius.
161
        @param[in] maximum_iterations (uint): maximum number of iteration for searching connectivity radius to allocated specified amount of clusters (by default it is restricted by 100 iterations).
162
        
163
        @return (double, list) Value of connectivity radius and borders of clusters like (radius, borders), radius may be 'None' as well as borders may be '[]'
164
                                if connectivity radius hasn't been found for the specified amount of iterations.
165
        
166
        """
167
        
168
        maximum_distance = max(self.__ordering);
169
        
170
        upper_distance = maximum_distance;
171
        lower_distance = 0.0;
172
        
173
        radius = None;
174
        result = None;
175
        
176
        amount, borders = self.extract_cluster_amount(maximum_distance);
177
        if (amount <= amount_clusters):
178
            for _ in range(maximum_iterations):
179
                radius = (lower_distance + upper_distance) / 2.0;
180
                
181
                amount, borders = self.extract_cluster_amount(radius);
182
                if (amount == amount_clusters):
183
                    result = radius;
184
                    break;
185
                
186
                elif (amount == 0):
187
                    break;
188
                
189
                elif (amount > amount_clusters):
190
                    lower_distance = radius;
191
                
192
                elif (amount < amount_clusters):
193
                    upper_distance = radius;
194
        
195
        return result, borders;
196
    
197
    
198
    def extract_cluster_amount(self, radius):
199
        """!
200
        @brief Obtains amount of clustering that can be allocated by using specified radius for ordering diagram and borders between them.
201
        @details When growth of reachability-distances is detected than it is considered as a start point of cluster, 
202
                 than pick is detected and after that recession is observed until new growth (that means end of the
203
                 current cluster and start of a new one) or end of diagram.
204
        
205
        @param[in] radius (double): connectivity radius that is used for cluster allocation.
206
        
207
        @return (unit, list) Amount of clusters that can be allocated by the connectivity radius on ordering diagram and borders between them using indexes
208
                 from ordering diagram (amount_clusters, border_clusters).
209
        
210
        """
211
        
212
        amount_clusters = 1;
213
        
214
        cluster_start = False;
215
        cluster_pick = False;
216
        total_similarity = True;
217
        previous_cluster_distance = None;
218
        previous_distance = None;
219
        
220
        cluster_borders = [];
221
        
222
        for index_ordering in range(len(self.__ordering)):
223
            distance = self.__ordering[index_ordering];
224
            if (distance >= radius):
225
                if (cluster_start is False):
226
                    cluster_start = True;
227
                    amount_clusters += 1;
228
                    
229
                    if (index_ordering != 0):
230
                        cluster_borders.append(index_ordering);
231
                
232
                else:
233
                    if ((distance < previous_cluster_distance) and (cluster_pick is False)):
234
                        cluster_pick = True;
235
                    
236
                    elif ((distance > previous_cluster_distance) and (cluster_pick is True)):
237
                        cluster_pick = False;
238
                        amount_clusters += 1;
239
                        
240
                        if (index_ordering != 0):
241
                            cluster_borders.append(index_ordering);
242
                
243
                previous_cluster_distance = distance;
244
            
245
            else:
246
                cluster_start = False;
247
                cluster_pick = False;
248
            
249
            if ( (previous_distance is not None) and (distance != previous_distance) ):
250
                total_similarity = False;
251
            
252
            previous_distance = distance;
253
        
254
        if ( (total_similarity is True) and (previous_distance > radius) ):
255
            amount_clusters = 0;
256
257
        return amount_clusters, cluster_borders;
258
259
260
class optics_descriptor:
261
    """!
262
    @brief Object description that used by OPTICS algorithm for cluster analysis.
263
    
264
    """
265
266
    def __init__(self, index, core_distance = None, reachability_distance = None):
267
        """!
268
        @brief Constructor of object description in optics terms.
269
        
270
        @param[in] index (uint): Index of the object in the data set.
271
        @param[in] core_distance (double): Core distance that is minimum distance to specified number of neighbors.
272
        @param[in] reachability_distance (double): Reachability distance to this object.
273
        
274
        """
275
        
276
        ## Reachability distance - the smallest distance to be reachable by core object.
277
        self.index_object = index;
278
        
279
        ## Core distance - the smallest distance to reach specified number of neighbors that is not greater then connectivity radius.
280
        self.core_distance = core_distance;
281
        
282
        ## Index of object from the input data.
283
        self.reachability_distance = reachability_distance;
284
        
285
        ## True is object has been already traversed.
286
        self.processed = False;
287
288
    def __repr__(self):
289
        """!
290
        @brief Returns string representation of the optics descriptor.
291
        
292
        """
293
        
294
        return '(%s, [c: %s, r: %s])' % (self.index_object, self.core_distance, self.reachability_distance);
295
296
297
class optics:
298
    """!
299
    @brief Class represents clustering algorithm OPTICS (Ordering Points To Identify Clustering Structure) with KD-tree optimization (ccore options is supported).
300
    @details OPTICS is a density-based algorithm. Purpose of the algorithm is to provide explicit clusters, but create clustering-ordering representation of the input data. 
301
             Clustering-ordering information contains information about internal structures of data set in terms of density and proper connectivity radius can be obtained
302
             for allocation required amount of clusters using this diagram. In case of usage additional input parameter 'amount of clusters' connectivity radius should be
303
             bigger than real - because it will be calculated by the algorithms if requested amount of clusters is not allocated.
304
             
305
             CCORE option can be used to use the pyclustering core - C/C++ shared library for processing that significantly increases performance.
306
307
    @image html optics_example_clustering.png "Scheme how does OPTICS works. At the beginning only one cluster is allocated, but two is requested. At the second step OPTICS calculates connectivity radius using cluster-ordering and performs final cluster allocation."
308
309
    Example:
310
    @code
311
        # Read sample for clustering from some file
312
        sample = read_sample(path_sample);
313
        
314
        # Create OPTICS algorithm for cluster analysis
315
        optics_instance = optics(sample, 0.5, 6);
316
        
317
        # Run cluster analysis
318
        optics_instance.process();
319
        
320
        # Obtain results of clustering
321
        clusters = optics_instance.get_clusters();
322
        noise = optics_instance.get_noise();
323
        
324
        # Obtain rechability-distances
325
        ordering = ordering_analyser(optics_instance.get_ordering());
326
        
327
        # Visualization of cluster ordering in line with reachability distance.
328
        ordering_visualizer.show_ordering_diagram(ordering);
329
    @endcode
330
    
331
    Amount of clusters that should be allocated can be also specified. In this case connectivity radius should be greater than real, for example:
332
    @code
333
        # Import required packages
334
        from pyclustering.cluster.optics import optics;
335
        from pyclustering.samples.definitions import FCPS_SAMPLES;
336
        from pyclustering.utils import read_sample;
337
        
338
        # Read sample for clustering from some file
339
        sample = read_sample(FCPS_SAMPLES.SAMPLE_LSUN);
340
        
341
        # Run cluster analysis where connvectivity radius is bigger than real
342
        radius = 2.0;
343
        neighbors = 3;
344
        amount_of_clusters = 3;
345
        
346
        optics_instance = optics(sample, radius, neighbors, amount_of_clusters);
347
        
348
        # Obtain results of clustering
349
        clusters = optics_instance.get_clusters();
350
        noise = optics_instance.get_noise();
351
    @endcode
352
    
353
    """
354
    
355
    def __init__(self, sample, eps, minpts, amount_clusters = None, ccore = True, **kwargs):
356
        """!
357
        @brief Constructor of clustering algorithm OPTICS.
358
        
359
        @param[in] sample (list): Input data that is presented as a list of points (objects), where each point is represented by list or tuple.
360
        @param[in] eps (double): Connectivity radius between points, points may be connected if distance between them less than the radius.
361
        @param[in] minpts (uint): Minimum number of shared neighbors that is required for establishing links between points.
362
        @param[in] amount_clusters (uint): Optional parameter where amount of clusters that should be allocated is specified.
363
                    In case of usage 'amount_clusters' connectivity radius can be greater than real, in other words, there is place for mistake
364
                    in connectivity radius usage.
365
        @param[in] ccore (bool): if True than DLL CCORE (C++ solution) will be used for solving the problem.
366
        @param[in] **kwargs: Arbitrary keyword arguments (available arguments: 'data_type').
367
368
        Keyword Args:
369
            data_type (dbscan_data_type): Data type of input sample 'data' that is processed by the algorithm (simple sequence of points or distance matrix).
370
371
        """
372
        
373
        self.__sample_pointer = sample;     # Algorithm parameter - pointer to sample for processing.
374
        self.__eps = eps;                   # Algorithm parameter - connectivity radius between object for establish links between object.
375
        self.__minpts = minpts;             # Algorithm parameter - minimum number of neighbors that is required for establish links between object.
376
        self.__amount_clusters = amount_clusters;
377
        
378
        self.__ordering = None;
379
        self.__clusters = None;
380
        self.__noise = None;
381
382
        self.__data_type = get_argument("data_type", optics_data_type.POINTS, **kwargs);
383
        
384
        self.__kdtree = None;
385
        self.__ccore = ccore;
386
        
387
        if (self.__ccore):
388
            self.__ccore = ccore_library.workable();
389
390
391
    def process(self):
392
        """!
393
        @brief Performs cluster analysis in line with rules of OPTICS algorithm.
394
        
395
        @remark Results of clustering can be obtained using corresponding gets methods.
396
        
397
        @see get_clusters()
398
        @see get_noise()
399
        @see get_ordering()
400
        
401
        """
402
        
403
        if self.__ccore is True:
404
            (self.__clusters, self.__noise, self.__ordering, self.__eps) = wrapper.optics(self.__sample_pointer, self.__eps, self.__minpts, self.__amount_clusters, self.__data_type);
405
        
406
        else:
407
            if self.__data_type == optics_data_type.POINTS:
408
                self.__kdtree = kdtree(self.__sample_pointer, range(len(self.__sample_pointer)));
409
410
            self.__allocate_clusters();
411
            
412
            if ( (self.__amount_clusters is not None) and (self.__amount_clusters != len(self.get_clusters())) ):
413
                analyser = ordering_analyser(self.get_ordering());
414
                radius, _ = analyser.calculate_connvectivity_radius(self.__amount_clusters);
415
                if radius is not None:
416
                    self.__eps = radius;
417
                    self.__allocate_clusters();
418
419
420
    def __initialize(self, sample):
421
        """!
422
        @brief Initializes internal states and resets clustering results in line with input sample.
423
        
424
        """
425
        
426
        self.__processed = [False] * len(sample);
427
        self.__optics_objects = [optics_descriptor(i) for i in range(len(sample))];     # List of OPTICS objects that corresponds to objects from input sample.
428
        self.__ordered_database = [];       # List of OPTICS objects in traverse order. 
429
        
430
        self.__clusters = None;     # Result of clustering (list of clusters where each cluster contains indexes of objects from input data).
431
        self.__noise = None;        # Result of clustering (noise).
432
433
434
    def __allocate_clusters(self):
435
        """!
436
        @brief Performs cluster allocation and builds ordering diagram that is based on reachability-distances.
437
        
438
        """
439
        
440
        self.__initialize(self.__sample_pointer);
441
        
442
        for optic_object in self.__optics_objects:
443
            if optic_object.processed is False:
444
                self.__expand_cluster_order(optic_object);
445
        
446
        self.__extract_clusters();
447
    
448
    
449
    def get_clusters(self):
450
        """!
451
        @brief Returns list of allocated clusters, where each cluster contains indexes of objects and each cluster is represented by list.
452
        
453
        @return (list) List of allocated clusters.
454
        
455
        @see process()
456
        @see get_noise()
457
        @see get_ordering()
458
        @see get_radius()
459
        
460
        """
461
        
462
        return self.__clusters;
463
    
464
    
465
    def get_noise(self):
466
        """!
467
        @brief Returns list of noise that contains indexes of objects that corresponds to input data.
468
        
469
        @return (list) List of allocated noise objects.
470
        
471
        @see process()
472
        @see get_clusters()
473
        @see get_ordering()
474
        @see get_radius()
475
        
476
        """
477
        
478
        return self.__noise;
479
    
480
    
481
    def get_ordering(self):
482
        """!
483
        @brief Returns clustering ordering information about the input data set.
484
        @details Clustering ordering of data-set contains the information about the internal clustering structure in line with connectivity radius.
485
        
486
        @return (ordering_analyser) Analyser of clustering ordering.
487
        
488
        @see process()
489
        @see get_clusters()
490
        @see get_noise()
491
        @see get_radius()
492
        
493
        """
494
        
495
        if (self.__ordering is None):
496
            self.__ordering = [];
497
        
498
            for cluster in self.__clusters:
499
                for index_object in cluster:
500
                    optics_object = self.__optics_objects[index_object];
501
                    if (optics_object.reachability_distance is not None):
502
                        self.__ordering.append(optics_object.reachability_distance);
503
            
504
        return self.__ordering;
505
    
506
    
507
    def get_radius(self):
508
        """!
509
        @brief Returns connectivity radius that is calculated and used for clustering by the algorithm.
510
        @details Connectivity radius may be changed only in case of usage additional parameter of the algorithm - amount of clusters for allocation.
511
        
512
        @return (double) Connectivity radius.
513
        
514
        @see get_ordering()
515
        @see get_clusters()
516
        @see get_noise()
517
        
518
        """
519
        
520
        return self.__eps;
521
    
522
523
    def get_cluster_encoding(self):
524
        """!
525
        @brief Returns clustering result representation type that indicate how clusters are encoded.
526
        
527
        @return (type_encoding) Clustering result representation.
528
        
529
        @see get_clusters()
530
        
531
        """
532
        
533
        return type_encoding.CLUSTER_INDEX_LIST_SEPARATION;
534
535
536
    def __expand_cluster_order(self, optics_object):
537
        """!
538
        @brief Expand cluster order from not processed optic-object that corresponds to object from input data.
539
               Traverse procedure is performed until objects are reachable from core-objects in line with connectivity radius.
540
               Order database is updated during expanding.
541
               
542
        @param[in] optics_object (optics_descriptor): Object that hasn't been processed.
543
        
544
        """
545
        
546
        optics_object.processed = True;
547
        
548
        neighbors_descriptor = self.__neighbor_indexes(optics_object);
549
        optics_object.reachability_distance = None;
550
        
551
        self.__ordered_database.append(optics_object);
552
        
553
        # Check core distance
554
        if len(neighbors_descriptor) >= self.__minpts:
555
            neighbors_descriptor.sort(key = lambda obj: obj[1]);
556
            optics_object.core_distance = neighbors_descriptor[self.__minpts - 1][1];
557
            
558
            # Continue processing
559
            order_seed = list();
560
            self.__update_order_seed(optics_object, neighbors_descriptor, order_seed);
561
            
562
            while len(order_seed) > 0:
563
                optic_descriptor = order_seed[0];
564
                order_seed.remove(optic_descriptor);
565
                
566
                neighbors_descriptor = self.__neighbor_indexes(optic_descriptor);
567
                optic_descriptor.processed = True;
568
                
569
                self.__ordered_database.append(optic_descriptor);
570
                
571
                if len(neighbors_descriptor) >= self.__minpts:
572
                    neighbors_descriptor.sort(key = lambda obj: obj[1]);
573
                    optic_descriptor.core_distance = neighbors_descriptor[self.__minpts - 1][1];
574
                    
575
                    self.__update_order_seed(optic_descriptor, neighbors_descriptor, order_seed);
576
                else:
577
                    optic_descriptor.core_distance = None;
578
                    
579
        else:
580
            optics_object.core_distance = None;
581
582
    
583
    def __extract_clusters(self):
584
        """!
585
        @brief Extract clusters and noise from order database.
586
        
587
        """
588
     
589
        self.__clusters = [];
590
        self.__noise = [];
591
592
        current_cluster = self.__noise;
593
        for optics_object in self.__ordered_database:
594
            if ((optics_object.reachability_distance is None) or (optics_object.reachability_distance > self.__eps)):
595
                if ((optics_object.core_distance is not None) and (optics_object.core_distance <= self.__eps)):
596
                    self.__clusters.append([ optics_object.index_object ]);
597
                    current_cluster = self.__clusters[-1];
598
                else:
599
                    self.__noise.append(optics_object.index_object);
600
            else:
601
                current_cluster.append(optics_object.index_object);
602
603
604
    def __update_order_seed(self, optic_descriptor, neighbors_descriptors, order_seed):
605
        """!
606
        @brief Update sorted list of reachable objects (from core-object) that should be processed using neighbors of core-object.
607
        
608
        @param[in] optic_descriptor (optics_descriptor): Core-object whose neighbors should be analysed.
609
        @param[in] neighbors_descriptors (list): List of neighbors of core-object.
610
        @param[in|out] order_seed (list): List of sorted object in line with reachable distance.
611
        
612
        """
613
        
614
        for neighbor_descriptor in neighbors_descriptors:
615
            index_neighbor = neighbor_descriptor[0];
616
            current_reachable_distance = neighbor_descriptor[1];
617
            
618
            if self.__optics_objects[index_neighbor].processed is not True:
619
                reachable_distance = max(current_reachable_distance, optic_descriptor.core_distance);
620
                if self.__optics_objects[index_neighbor].reachability_distance is None:
621
                    self.__optics_objects[index_neighbor].reachability_distance = reachable_distance;
622
                    
623
                    # insert element in queue O(n) - worst case.
624
                    index_insertion = len(order_seed);
625
                    for index_seed in range(0, len(order_seed)):
626
                        if reachable_distance < order_seed[index_seed].reachability_distance:
627
                            index_insertion = index_seed;
628
                            break;
629
                    
630
                    order_seed.insert(index_insertion, self.__optics_objects[index_neighbor]);
631
632
                else:
633
                    if reachable_distance < self.__optics_objects[index_neighbor].reachability_distance:
634
                        self.__optics_objects[index_neighbor].reachability_distance = reachable_distance;
635
                        order_seed.sort(key = lambda obj: obj.reachability_distance);
636
637
638
    def __neighbor_indexes(self, optic_object):
639
        """!
640
        @brief Return list of indexes of neighbors of specified point for the data.
641
        
642
        @param[in] optic_object (optics_descriptor): Object for which neighbors should be returned in line with connectivity radius.
643
        
644
        @return (list) List of indexes of neighbors in line the connectivity radius.
645
        
646
        """
647
648
        if self.__data_type == optics_data_type.POINTS:
649
            kdnodes = self.__kdtree.find_nearest_dist_nodes(self.__sample_pointer[optic_object.index_object], self.__eps);
650
            return [ [node_tuple[1].payload, math.sqrt(node_tuple[0]) ] for node_tuple in kdnodes if node_tuple[1].payload != optic_object.index_object];
651
652
        else:
653
            distances = self.__sample_pointer[optic_object.index_object];
654
            return [ [ index_neighbor, distances[index_neighbor] ] for index_neighbor in range(len(distances))
655
                     if ( (distances[index_neighbor] <= self.__eps) and (index_neighbor != optic_object.index_object) ) ];