001/*
002 * Copyright (c) 2015-2020, Oracle and/or its affiliates. All rights reserved.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.tribuo.common.tree;
018
019import com.google.protobuf.Any;
020import org.tribuo.Output;
021import org.tribuo.common.tree.protos.SplitNodeProto;
022import org.tribuo.common.tree.protos.TreeNodeProto;
023import org.tribuo.math.la.SparseVector;
024
025import java.util.Objects;
026
027/**
028 * An immutable {@link Node} with a split and two child nodes.
029 */
030public class SplitNode<T extends Output<T>> implements Node<T> {
031    private static final long serialVersionUID = 3L;
032
033    /**
034     * Protobuf serialization version.
035     */
036    public static final int CURRENT_VERSION = 0;
037
038    private final Node<T> greaterThan;
039
040    private final Node<T> lessThanOrEqual;
041
042    private final int splitFeature;
043
044    private final double splitValue;
045
046    private final double impurity;
047
048    /**
049     * Constructs a split node with the specified split value, feature id, impurity and child nodes.
050     * @param splitValue The feature value to split on.
051     * @param featureID The feature id number.
052     * @param impurity The impurity of this node at training time.
053     * @param greaterThan The node to take if the feature value is greater than the split value.
054     * @param lessThanOrEqual The node to take if the feature value is less than or equal to the split value.
055     */
056    public SplitNode(double splitValue, int featureID, double impurity, Node<T> greaterThan, Node<T> lessThanOrEqual) {
057        this.splitValue = splitValue;
058        this.splitFeature = featureID;
059        this.impurity = impurity;
060        this.greaterThan = greaterThan;
061        this.lessThanOrEqual = lessThanOrEqual;
062    }
063
064    /**
065     * Return the appropriate child node. If the splitFeature is not present in
066     * the example it's value is treated as zero.
067     *
068     * @param e The example to inspect.
069     * @return The corresponding child node.
070     */
071    @Override
072    public Node<T> getNextNode(SparseVector e) {
073        double feature = e.get(splitFeature);
074        if (feature > splitValue) {
075            return greaterThan;
076        } else {
077            return lessThanOrEqual;
078        }
079    }
080
081    @Override
082    public boolean isLeaf() {
083        return false;
084    }
085
086    @Override
087    public double getImpurity() {
088        return impurity;
089    }
090
091    @Override
092    public Node<T> copy() {
093        return new SplitNode<>(splitValue,splitFeature,impurity,greaterThan.copy(),lessThanOrEqual.copy());
094    }
095
096    /**
097     * Gets the feature ID that this node uses for splitting.
098     * @return The feature ID.
099     */
100    public int getFeatureID() {
101        return splitFeature;
102    }
103
104    /**
105     * The threshold value.
106     * @return The threshold value.
107     */
108    public double splitValue() {
109        return splitValue;
110    }
111
112    /**
113     * The node used if the value is greater than the splitValue.
114     * @return The greater than node.
115     */
116    public Node<T> getGreaterThan() {
117        return greaterThan;
118    }
119
120    /**
121     * The node used if the value is less than or equal to the splitValue.
122     * @return The less than or equal to node.
123     */
124    public Node<T> getLessThanOrEqual() {
125        return lessThanOrEqual;
126    }
127
128    @Override
129    public String toString() {
130        return "SplitNode(feature="+splitFeature+",value="+splitValue+",impurity="+impurity+",\n\t\tleft="+lessThanOrEqual.toString()+",\n\t\tright="+greaterThan.toString()+")";
131    }
132
133    @Override
134    public boolean equals(Object o) {
135        if (this == o) return true;
136        if (o == null || getClass() != o.getClass()) return false;
137        SplitNode<?> splitNode = (SplitNode<?>) o;
138        return splitFeature == splitNode.splitFeature &&
139                Double.compare(splitNode.splitValue, splitValue) == 0 &&
140                Double.compare(splitNode.impurity, impurity) == 0 &&
141                greaterThan.equals(splitNode.greaterThan) &&
142                lessThanOrEqual.equals(splitNode.lessThanOrEqual);
143    }
144
145    @Override
146    public int hashCode() {
147        return Objects.hash(greaterThan, lessThanOrEqual, splitFeature, splitValue, impurity);
148    }
149
150    TreeNodeProto serialize(int parentIdx, int curIdx, int greaterThanIdx, int lessThanOrEqualIdx) {
151        SplitNodeProto.Builder nodeBuilder = SplitNodeProto.newBuilder();
152        nodeBuilder.setParentIdx(parentIdx);
153        nodeBuilder.setCurIdx(curIdx);
154        nodeBuilder.setGreaterThanIdx(greaterThanIdx);
155        nodeBuilder.setLessThanOrEqualIdx(lessThanOrEqualIdx);
156        nodeBuilder.setSplitFeatureIdx(splitFeature);
157        nodeBuilder.setSplitValue(splitValue);
158        nodeBuilder.setImpurity(impurity);
159
160        TreeNodeProto.Builder builder = TreeNodeProto.newBuilder();
161        builder.setVersion(CURRENT_VERSION);
162        builder.setClassName(LeafNode.class.getName());
163        builder.setSerializedData(Any.pack(nodeBuilder.build()));
164
165        return builder.build();
166    }
167
168    static final class SplitNodeBuilder<T extends Output<T>> extends TreeModel.NodeBuilder implements Node<T> {
169
170        private final int parentIdx;
171        private final int curIdx;
172        private final int greaterThanIdx;
173        private final int lessThanOrEqualIdx;
174        private final int splitFeature;
175        private final double splitValue;
176        private final double impurity;
177
178        private Node<T> greaterThan;
179        private Node<T> lessThanOrEqual;
180
181        SplitNodeBuilder(SplitNodeProto proto) {
182            this.parentIdx = proto.getParentIdx();
183            this.curIdx = proto.getCurIdx();
184            this.greaterThanIdx = proto.getGreaterThanIdx();
185            this.lessThanOrEqualIdx = proto.getLessThanOrEqualIdx();
186            this.splitFeature = proto.getSplitFeatureIdx();
187            this.splitValue = proto.getSplitValue();
188            this.impurity = proto.getImpurity();
189        }
190
191        SplitNodeBuilder(int parentIdx, int curIdx, int greaterThanIdx, int lessThanOrEqualIdx, int splitFeature, double splitValue, double impurity) {
192            this.parentIdx = parentIdx;
193            this.curIdx = curIdx;
194            this.greaterThanIdx = greaterThanIdx;
195            this.lessThanOrEqualIdx = lessThanOrEqualIdx;
196            this.splitFeature = splitFeature;
197            this.splitValue = splitValue;
198            this.impurity = impurity;
199        }
200
201        @Override
202        public boolean isLeaf() {
203            return false;
204        }
205
206        @Override
207        public Node<T> getNextNode(SparseVector example) {
208            return null;
209        }
210
211        @Override
212        public double getImpurity() {
213            return impurity;
214        }
215
216        @Override
217        public SplitNodeBuilder<T> copy() {
218            return new SplitNodeBuilder<>(parentIdx, curIdx, greaterThanIdx, lessThanOrEqualIdx, splitFeature, splitValue, impurity);
219        }
220
221        boolean canBuild() {
222            return greaterThan != null && lessThanOrEqual != null;
223        }
224
225        SplitNode<T> build() {
226            if (!canBuild()) {
227                throw new IllegalStateException("Not ready to build this split node, missing the children pointers");
228            }
229            return new SplitNode<>(splitValue,splitFeature,impurity,greaterThan,lessThanOrEqual);
230        }
231
232        void setGreaterThan(Node<T> greaterThan) {
233            if (this.greaterThan == null) {
234                this.greaterThan = greaterThan;
235            } else {
236                throw new IllegalStateException("Invalid protobuf, multiple nodes mapped to the greaterThanIdx");
237            }
238        }
239
240        void setLessThanOrEqual(Node<T> lessThanOrEqual) {
241            if (this.lessThanOrEqual == null) {
242                this.lessThanOrEqual = lessThanOrEqual;
243            } else {
244                throw new IllegalStateException("Invalid protobuf, multiple nodes mapped to the lessThanOrEqualIdx");
245            }
246        }
247
248        int getGreaterThanIdx() {
249            return greaterThanIdx;
250        }
251
252        int getLessThanOrEqualIdx() {
253            return lessThanOrEqualIdx;
254        }
255
256        int getParentIdx() {
257            return parentIdx;
258        }
259
260        int getCurIdx() {
261            return curIdx;
262        }
263    }
264}
265