1 /*
2  * This file is part of ELKI:
3  * Environment for Developing KDD-Applications Supported by Index-Structures
4  *
5  * Copyright (C) 2018
6  * ELKI Development Team
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Affero General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Affero General Public License for more details.
17  *
18  * You should have received a copy of the GNU Affero General Public License
19  * along with this program. If not, see <http://www.gnu.org/licenses/>.
20  */
21 package de.lmu.ifi.dbs.elki.algorithm.outlier.spatial.neighborhood.weighted;
22 
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.List;
26 
27 import de.lmu.ifi.dbs.elki.algorithm.outlier.spatial.neighborhood.NeighborSetPredicate;
28 import de.lmu.ifi.dbs.elki.data.type.TypeInformation;
29 import de.lmu.ifi.dbs.elki.database.Database;
30 import de.lmu.ifi.dbs.elki.database.ids.DBIDIter;
31 import de.lmu.ifi.dbs.elki.database.ids.DBIDRef;
32 import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil;
33 import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
34 import de.lmu.ifi.dbs.elki.database.ids.DoubleDBIDPair;
35 import de.lmu.ifi.dbs.elki.database.ids.ModifiableDBIDs;
36 import de.lmu.ifi.dbs.elki.database.relation.Relation;
37 import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer;
38 import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
39 import de.lmu.ifi.dbs.elki.utilities.optionhandling.constraints.CommonConstraints;
40 import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
41 import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.IntParameter;
42 import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.ObjectParameter;
43 
44 /**
45  * Neighborhood obtained by computing the k-fold closure of an existing
46  * neighborhood. Objects are weighted linearly by their distance: the object
47  * itself has a weight of 1 and this decreases linearly to 1/(n+1) for the
48  * nth-step neighbors.
49  *
50  * TODO: make actual weighting parameterizable?
51  *
52  * @author Erich Schubert
53  * @since 0.4.0
54  */
55 public class LinearWeightedExtendedNeighborhood implements WeightedNeighborSetPredicate {
56   /**
57    * The data store to use
58    */
59   private NeighborSetPredicate inner;
60 
61   /**
62    * The number of steps to extend to.
63    */
64   private int steps;
65 
66   /**
67    * Constructor.
68    *
69    * @param inner Inner neighborhood
70    * @param steps Number of steps to expand
71    */
LinearWeightedExtendedNeighborhood(NeighborSetPredicate inner, int steps)72   public LinearWeightedExtendedNeighborhood(NeighborSetPredicate inner, int steps) {
73     super();
74     this.inner = inner;
75     this.steps = steps;
76   }
77 
78   /**
79    * Compute the weight from the number of steps needed.
80    *
81    * @param tsteps steps to target
82    * @return weight
83    */
computeWeight(int tsteps)84   private double computeWeight(int tsteps) {
85     return 1.0 - (tsteps / (float) (steps + 1));
86   }
87 
88   @Override
getWeightedNeighbors(DBIDRef reference)89   public Collection<DoubleDBIDPair> getWeightedNeighbors(DBIDRef reference) {
90     ModifiableDBIDs seen = DBIDUtil.newHashSet();
91     List<DoubleDBIDPair> result = new ArrayList<>();
92 
93     // Add starting object
94     result.add(DBIDUtil.newPair(computeWeight(0), reference));
95     seen.add(reference);
96     // Extend.
97     DBIDs cur = DBIDUtil.deref(reference);
98     for(int i = 1; i <= steps; i++) {
99       final double weight = computeWeight(i);
100       // Collect newly discovered IDs
101       ModifiableDBIDs add = DBIDUtil.newHashSet();
102       for(DBIDIter iter = cur.iter(); iter.valid(); iter.advance()) {
103         for(DBIDIter iter2 = inner.getNeighborDBIDs(iter).iter(); iter2.valid(); iter2.advance()) {
104           // Seen before?
105           if(seen.contains(iter2)) {
106             continue;
107           }
108           add.add(iter2);
109           result.add(DBIDUtil.newPair(weight, iter2));
110         }
111       }
112       if(add.size() == 0) {
113         break;
114       }
115       cur = add;
116     }
117     return result;
118   }
119 
120   /**
121    * Factory class.
122    *
123    * @author Erich Schubert
124    *
125    * @stereotype factory
126    * @navhas - produces - LinearWeightedExtendedNeighborhood
127    */
128   public static class Factory<O> implements WeightedNeighborSetPredicate.Factory<O> {
129     /**
130      * Inner neighbor set predicate
131      */
132     private NeighborSetPredicate.Factory<O> inner;
133 
134     /**
135      * Number of steps to do
136      */
137     private int steps;
138 
139     /**
140      * Constructor.
141      *
142      * @param inner Inner neighbor set predicate
143      * @param steps Number of steps to do
144      */
Factory(NeighborSetPredicate.Factory<O> inner, int steps)145     public Factory(NeighborSetPredicate.Factory<O> inner, int steps) {
146       super();
147       this.inner = inner;
148       this.steps = steps;
149     }
150 
151     @Override
instantiate(Database database, Relation<? extends O> relation)152     public LinearWeightedExtendedNeighborhood instantiate(Database database, Relation<? extends O> relation) {
153       return new LinearWeightedExtendedNeighborhood(inner.instantiate(database, relation), steps);
154     }
155 
156     @Override
getInputTypeRestriction()157     public TypeInformation getInputTypeRestriction() {
158       return inner.getInputTypeRestriction();
159     }
160 
161     /**
162      * Parameterization class.
163      *
164      * @author Erich Schubert
165      */
166     public static class Parameterizer<O> extends AbstractParameterizer {
167       /**
168        * Parameter to specify the neighborhood predicate to use.
169        */
170       public static final OptionID NEIGHBORHOOD_ID = new OptionID("extendedneighbors.neighborhood", "The inner neighborhood predicate to use.");
171 
172       /**
173        * Parameter to specify the number of steps allowed
174        */
175       public static final OptionID STEPS_ID = new OptionID("extendedneighbors.steps", "The number of steps allowed in the neighborhood graph.");
176 
177       /**
178        * The number of steps to do.
179        */
180       private int steps;
181 
182       /**
183        * Inner neighbor set predicate
184        */
185       private NeighborSetPredicate.Factory<O> inner;
186 
187       /**
188        * Inner neighborhood parameter.
189        *
190        * @param config Parameterization
191        * @return Inner neighborhood.
192        */
getParameterInnerNeighborhood(Parameterization config)193       protected static <O> NeighborSetPredicate.Factory<O> getParameterInnerNeighborhood(Parameterization config) {
194         final ObjectParameter<NeighborSetPredicate.Factory<O>> param = new ObjectParameter<>(NEIGHBORHOOD_ID, NeighborSetPredicate.Factory.class);
195         if(config.grab(param)) {
196           return param.instantiateClass(config);
197         }
198         return null;
199       }
200 
201       @Override
makeOptions(Parameterization config)202       protected void makeOptions(Parameterization config) {
203         super.makeOptions(config);
204         inner = getParameterInnerNeighborhood(config);
205         steps = getParameterSteps(config);
206       }
207 
208       /**
209        * Get the number of steps to do in the neighborhood graph.
210        *
211        * @param config Parameterization
212        * @return number of steps, default 1
213        */
getParameterSteps(Parameterization config)214       public static int getParameterSteps(Parameterization config) {
215         final IntParameter param = new IntParameter(STEPS_ID) //
216             .addConstraint(CommonConstraints.GREATER_EQUAL_ONE_INT);
217         if(config.grab(param)) {
218           return param.getValue();
219         }
220         return 1;
221       }
222 
223       @Override
makeInstance()224       protected LinearWeightedExtendedNeighborhood.Factory<O> makeInstance() {
225         return new LinearWeightedExtendedNeighborhood.Factory<>(inner, steps);
226       }
227     }
228   }
229 }