From 524dbd003e908cc7a4a81d6bcc2387e2d398cd48 Mon Sep 17 00:00:00 2001 From: Navneet Verma Date: Mon, 9 Sep 2024 16:27:15 -0700 Subject: [PATCH 01/59] Fixing filtered vector search when quantization is applied (#2076) Signed-off-by: Navneet Verma --- .../knn/index/query/ExactSearcher.java | 86 +++++++++++++------ .../opensearch/knn/index/query/KNNWeight.java | 62 ++++++------- .../query/SegmentLevelQuantizationInfo.java | 46 ++++++++++ .../query/SegmentLevelQuantizationUtil.java | 60 +++++++++++++ .../filtered/FilteredIdsKNNIterator.java | 30 ++++++- .../NestedFilteredIdsKNNIterator.java | 17 +++- .../nativelib/NativeEngineKnnVectorQuery.java | 11 ++- .../knn/index/query/KNNWeightTests.java | 5 +- .../NativeEngineKNNVectorQueryTests.java | 6 +- 9 files changed, 249 insertions(+), 74 deletions(-) create mode 100644 src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationInfo.java create mode 100644 src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationUtil.java diff --git a/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java b/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java index 249c66d03..5b6029766 100644 --- a/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java +++ b/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java @@ -6,6 +6,8 @@ package org.opensearch.knn.index.query; import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Value; import lombok.extern.log4j.Log4j2; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.LeafReaderContext; @@ -42,27 +44,18 @@ public class ExactSearcher { /** * Execute an exact search on a subset of documents of a leaf * - * @param leafReaderContext LeafReaderContext to be searched over - * @param matchedDocs matched documents - * @param knnQuery KNN Query - * @param k number of results to return - * @param isParentHits whether the matchedDocs contains parent ids or child ids. This is relevant in the case of - * filtered nested search where the matchedDocs contain the parent ids and {@link NestedFilteredIdsKNNIterator} - * needs to be used. + * @param leafReaderContext {@link LeafReaderContext} + * @param exactSearcherContext {@link ExactSearcherContext} * @return Map of re-scored results + * @throws IOException exception during execution of exact search */ - public Map searchLeaf( - final LeafReaderContext leafReaderContext, - final BitSet matchedDocs, - final KNNQuery knnQuery, - int k, - boolean isParentHits - ) throws IOException { - KNNIterator iterator = getMatchedKNNIterator(leafReaderContext, matchedDocs, knnQuery, isParentHits); - if (matchedDocs.cardinality() <= k) { + public Map searchLeaf(final LeafReaderContext leafReaderContext, final ExactSearcherContext exactSearcherContext) + throws IOException { + KNNIterator iterator = getMatchedKNNIterator(leafReaderContext, exactSearcherContext); + if (exactSearcherContext.getMatchedDocs().cardinality() <= exactSearcherContext.getK()) { return scoreAllDocs(iterator); } - return searchTopK(iterator, k); + return searchTopK(iterator, exactSearcherContext.getK()); } private Map scoreAllDocs(KNNIterator iterator) throws IOException { @@ -105,17 +98,15 @@ private Map searchTopK(KNNIterator iterator, int k) throws IOExc return docToScore; } - private KNNIterator getMatchedKNNIterator( - final LeafReaderContext leafReaderContext, - final BitSet matchedDocs, - KNNQuery knnQuery, - boolean isParentHits - ) throws IOException { + private KNNIterator getMatchedKNNIterator(LeafReaderContext leafReaderContext, ExactSearcherContext exactSearcherContext) + throws IOException { + final KNNQuery knnQuery = exactSearcherContext.getKnnQuery(); + final BitSet matchedDocs = exactSearcherContext.getMatchedDocs(); final SegmentReader reader = Lucene.segmentReader(leafReaderContext.reader()); final FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(knnQuery.getField()); final SpaceType spaceType = FieldInfoExtractor.getSpaceType(modelDao, fieldInfo); - boolean isNestedRequired = isParentHits && knnQuery.getParentsFilter() != null; + boolean isNestedRequired = exactSearcherContext.isParentHits() && knnQuery.getParentsFilter() != null; if (VectorDataType.BINARY == knnQuery.getVectorDataType() && isNestedRequired) { final KNNVectorValues vectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); @@ -137,6 +128,17 @@ private KNNIterator getMatchedKNNIterator( spaceType ); } + final byte[] quantizedQueryVector; + final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo; + if (exactSearcherContext.isUseQuantizedVectorsForSearch()) { + // Build Segment Level Quantization info. + segmentLevelQuantizationInfo = SegmentLevelQuantizationInfo.build(reader, fieldInfo, knnQuery.getField()); + // Quantize the Query Vector Once. + quantizedQueryVector = SegmentLevelQuantizationUtil.quantizeVector(knnQuery.getQueryVector(), segmentLevelQuantizationInfo); + } else { + segmentLevelQuantizationInfo = null; + quantizedQueryVector = null; + } final KNNVectorValues vectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); if (isNestedRequired) { @@ -145,10 +147,42 @@ private KNNIterator getMatchedKNNIterator( knnQuery.getQueryVector(), (KNNFloatVectorValues) vectorValues, spaceType, - knnQuery.getParentsFilter().getBitSet(leafReaderContext) + knnQuery.getParentsFilter().getBitSet(leafReaderContext), + quantizedQueryVector, + segmentLevelQuantizationInfo ); } - return new FilteredIdsKNNIterator(matchedDocs, knnQuery.getQueryVector(), (KNNFloatVectorValues) vectorValues, spaceType); + return new FilteredIdsKNNIterator( + matchedDocs, + knnQuery.getQueryVector(), + (KNNFloatVectorValues) vectorValues, + spaceType, + quantizedQueryVector, + segmentLevelQuantizationInfo + ); + } + + /** + * Stores the context that is used to do the exact search. This class will help in reducing the explosion of attributes + * for doing exact search. + */ + @Value + @Builder + public static class ExactSearcherContext { + /** + * controls whether we should use Quantized vectors during exact search or not. This is useful because when we do + * re-scoring we need to re-score using full precision vectors and not quantized vectors. + */ + boolean useQuantizedVectorsForSearch; + int k; + BitSet matchedDocs; + KNNQuery knnQuery; + /** + * whether the matchedDocs contains parent ids or child ids. This is relevant in the case of + * filtered nested search where the matchedDocs contain the parent ids and {@link NestedFilteredIdsKNNIterator} + * needs to be used. + */ + boolean isParentHits; } } diff --git a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java index 1769328fe..b1ba9de59 100644 --- a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java +++ b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java @@ -27,7 +27,6 @@ import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; -import org.opensearch.knn.index.codec.KNN990Codec.QuantizationConfigKNNCollector; import org.opensearch.knn.index.memory.NativeMemoryAllocation; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.memory.NativeMemoryEntryContext; @@ -39,8 +38,6 @@ import org.opensearch.knn.indices.ModelUtil; import org.opensearch.knn.jni.JNIService; import org.opensearch.knn.plugin.stats.KNNCounter; -import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; -import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; import java.io.IOException; import java.nio.file.Path; @@ -140,8 +137,17 @@ public Map searchLeaf(LeafReaderContext context, int k) throws I * This improves the recall. */ Map docIdsToScoreMap; + final ExactSearcher.ExactSearcherContext exactSearcherContext = ExactSearcher.ExactSearcherContext.builder() + .k(k) + .isParentHits(true) + .matchedDocs(filterBitSet) + // setting to true, so that if quantization details are present we want to do search on the quantized + // vectors as this flow is used in first pass of search. + .useQuantizedVectorsForSearch(true) + .knnQuery(knnQuery) + .build(); if (filterWeight != null && canDoExactSearch(cardinality)) { - docIdsToScoreMap = exactSearch(context, filterBitSet, true, k); + docIdsToScoreMap = exactSearch(context, exactSearcherContext); } else { docIdsToScoreMap = doANNSearch(context, filterBitSet, cardinality, k); if (docIdsToScoreMap == null) { @@ -155,7 +161,7 @@ public Map searchLeaf(LeafReaderContext context, int k) throws I docIdsToScoreMap.size(), cardinality ); - docIdsToScoreMap = exactSearch(context, filterBitSet, true, k); + docIdsToScoreMap = exactSearch(context, exactSearcherContext); } } if (docIdsToScoreMap.isEmpty()) { @@ -258,10 +264,13 @@ private Map doANNSearch( ); } - QuantizationParams quantizationParams = quantizationService.getQuantizationParams(fieldInfo); - + final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo = SegmentLevelQuantizationInfo.build( + reader, + fieldInfo, + knnQuery.getField() + ); // TODO: Change type of vector once more quantization methods are supported - byte[] quantizedVector = getQuantizedVector(quantizationParams, reader, fieldInfo); + final byte[] quantizedVector = SegmentLevelQuantizationUtil.quantizeVector(knnQuery.getQueryVector(), segmentLevelQuantizationInfo); List engineFiles = getEngineFiles(reader, knnEngine.getExtension()); if (engineFiles.isEmpty()) { @@ -285,7 +294,7 @@ private Map doANNSearch( knnEngine, knnQuery.getIndexName(), // TODO: In the future, more vector data types will be supported with quantization - quantizationParams == null ? vectorDataType : VectorDataType.BINARY + quantizedVector == null ? vectorDataType : VectorDataType.BINARY ), knnQuery.getIndexName(), modelId @@ -310,11 +319,11 @@ private Map doANNSearch( int[] parentIds = getParentIdsArray(context); if (k > 0) { if (knnQuery.getVectorDataType() == VectorDataType.BINARY - || quantizationParams != null && quantizationService.getVectorDataTypeForTransfer(fieldInfo) == VectorDataType.BINARY) { + || quantizedVector != null && quantizationService.getVectorDataTypeForTransfer(fieldInfo) == VectorDataType.BINARY) { results = JNIService.queryBinaryIndex( indexAllocation.getMemoryAddress(), // TODO: In the future, quantizedVector can have other data types than byte - quantizationParams == null ? knnQuery.getByteQueryVector() : quantizedVector, + quantizedVector == null ? knnQuery.getByteQueryVector() : quantizedVector, k, knnQuery.getMethodParameters(), knnEngine, @@ -391,16 +400,14 @@ List getEngineFiles(SegmentReader reader, String extension) throws IOExc /** * Execute exact search for the given matched doc ids and return the results as a map of docId to score. * - * @param leafReaderContext The leaf reader context for the current segment. - * @param matchSet The filterIds to search for. - * @param isParentHits Whether the matchedDocs contains parent ids or child ids. - * @param k The number of results to return. * @return Map of docId to score for the exact search results. * @throws IOException If an error occurs during the search. */ - public Map exactSearch(final LeafReaderContext leafReaderContext, final BitSet matchSet, boolean isParentHits, int k) - throws IOException { - return exactSearcher.searchLeaf(leafReaderContext, matchSet, knnQuery, k, isParentHits); + public Map exactSearch( + final LeafReaderContext leafReaderContext, + final ExactSearcher.ExactSearcherContext exactSearcherContext + ) throws IOException { + return exactSearcher.searchLeaf(leafReaderContext, exactSearcherContext); } @Override @@ -462,23 +469,4 @@ private boolean isExactSearchThresholdSettingSet(int filterThresholdValue) { private boolean canDoExactSearchAfterANNSearch(final int filterIdsCount, final int annResultCount) { return filterWeight != null && filterIdsCount >= knnQuery.getK() && knnQuery.getK() > annResultCount; } - - // TODO: this will eventually return more types than just byte - private byte[] getQuantizedVector(QuantizationParams quantizationParams, SegmentReader reader, FieldInfo fieldInfo) throws IOException { - if (quantizationParams != null) { - QuantizationConfigKNNCollector tempCollector = new QuantizationConfigKNNCollector(); - reader.searchNearestVectors(knnQuery.getField(), new float[0], tempCollector, null); - if (tempCollector.getQuantizationState() == null) { - throw new IllegalStateException(String.format("No quantization state found for field %s", fieldInfo.getName())); - } - QuantizationOutput quantizationOutput = quantizationService.createQuantizationOutput(quantizationParams); - // TODO: In the future, byte array will not be the only output type from this method - return (byte[]) quantizationService.quantize( - tempCollector.getQuantizationState(), - knnQuery.getQueryVector(), - quantizationOutput - ); - } - return null; - } } diff --git a/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationInfo.java b/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationInfo.java new file mode 100644 index 000000000..d25774cdc --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationInfo.java @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.LeafReader; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.io.IOException; + +/** + * This class encapsulate the necessary details to do the quantization of the vectors present in a lucene segment. + */ +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class SegmentLevelQuantizationInfo { + private final QuantizationParams quantizationParams; + private final QuantizationState quantizationState; + + /** + * A builder like function to build the {@link SegmentLevelQuantizationInfo} + * @param leafReader {@link LeafReader} + * @param fieldInfo {@link FieldInfo} + * @param fieldName {@link String} + * @return {@link SegmentLevelQuantizationInfo} + * @throws IOException exception while creating the {@link SegmentLevelQuantizationInfo} object. + */ + public static SegmentLevelQuantizationInfo build(final LeafReader leafReader, final FieldInfo fieldInfo, final String fieldName) + throws IOException { + final QuantizationParams quantizationParams = QuantizationService.getInstance().getQuantizationParams(fieldInfo); + if (quantizationParams == null) { + return null; + } + final QuantizationState quantizationState = SegmentLevelQuantizationUtil.getQuantizationState(leafReader, fieldName); + return new SegmentLevelQuantizationInfo(quantizationParams, quantizationState); + } + +} diff --git a/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationUtil.java b/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationUtil.java new file mode 100644 index 000000000..46db8bb6b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationUtil.java @@ -0,0 +1,60 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import lombok.experimental.UtilityClass; +import org.apache.lucene.index.LeafReader; +import org.opensearch.knn.index.codec.KNN990Codec.QuantizationConfigKNNCollector; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.io.IOException; +import java.util.Locale; + +/** + * A utility class for doing Quantization related operation at a segment level. We can move this utility in {@link SegmentLevelQuantizationInfo} + * but I am keeping it thinking that {@link SegmentLevelQuantizationInfo} free from these utility functions to reduce + * the responsibilities of {@link SegmentLevelQuantizationInfo} class. + */ +@UtilityClass +public class SegmentLevelQuantizationUtil { + + /** + * A simple function to convert a vector to a quantized vector for a segment. + * @param vector array of float + * @return array of byte + */ + @SuppressWarnings("unchecked") + public static byte[] quantizeVector(final float[] vector, final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo) { + if (segmentLevelQuantizationInfo == null) { + return null; + } + final QuantizationService quantizationService = QuantizationService.getInstance(); + // TODO: We are converting the output of Quantize to byte array for now. But this needs to be fixed when + // other types of quantized outputs are returned like float[]. + return (byte[]) quantizationService.quantize( + segmentLevelQuantizationInfo.getQuantizationState(), + vector, + quantizationService.createQuantizationOutput(segmentLevelQuantizationInfo.getQuantizationParams()) + ); + } + + /** + * A utility function to get {@link QuantizationState} for a given segment and field. + * @param leafReader {@link LeafReader} + * @param fieldName {@link String} + * @return {@link QuantizationState} + * @throws IOException exception during reading the {@link QuantizationState} + */ + static QuantizationState getQuantizationState(final LeafReader leafReader, String fieldName) throws IOException { + final QuantizationConfigKNNCollector tempCollector = new QuantizationConfigKNNCollector(); + leafReader.searchNearestVectors(fieldName, new float[0], tempCollector, null); + if (tempCollector.getQuantizationState() == null) { + throw new IllegalStateException(String.format(Locale.ROOT, "No quantization state found for field %s", fieldName)); + } + return tempCollector.getQuantizationState(); + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNIterator.java index a0d7694c9..56d291470 100644 --- a/src/main/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNIterator.java +++ b/src/main/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNIterator.java @@ -9,6 +9,8 @@ import org.apache.lucene.util.BitSet; import org.apache.lucene.util.BitSetIterator; import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.query.SegmentLevelQuantizationInfo; +import org.opensearch.knn.index.query.SegmentLevelQuantizationUtil; import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; import java.io.IOException; @@ -24,16 +26,29 @@ public class FilteredIdsKNNIterator implements KNNIterator { protected final BitSet filterIdsBitSet; protected final BitSetIterator bitSetIterator; protected final float[] queryVector; + private final byte[] quantizedQueryVector; protected final KNNFloatVectorValues knnFloatVectorValues; protected final SpaceType spaceType; protected float currentScore = Float.NEGATIVE_INFINITY; protected int docId; + private final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo; - public FilteredIdsKNNIterator( + FilteredIdsKNNIterator( final BitSet filterIdsBitSet, final float[] queryVector, final KNNFloatVectorValues knnFloatVectorValues, final SpaceType spaceType + ) { + this(filterIdsBitSet, queryVector, knnFloatVectorValues, spaceType, null, null); + } + + public FilteredIdsKNNIterator( + final BitSet filterIdsBitSet, + final float[] queryVector, + final KNNFloatVectorValues knnFloatVectorValues, + final SpaceType spaceType, + final byte[] quantizedQueryVector, + final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo ) { this.filterIdsBitSet = filterIdsBitSet; this.bitSetIterator = new BitSetIterator(filterIdsBitSet, filterIdsBitSet.length()); @@ -41,6 +56,8 @@ public FilteredIdsKNNIterator( this.knnFloatVectorValues = knnFloatVectorValues; this.spaceType = spaceType; this.docId = bitSetIterator.nextDoc(); + this.quantizedQueryVector = quantizedQueryVector; + this.segmentLevelQuantizationInfo = segmentLevelQuantizationInfo; } /** @@ -68,8 +85,13 @@ public float score() { protected float computeScore() throws IOException { final float[] vector = knnFloatVectorValues.getVector(); - // Calculates a similarity score between the two vectors with a specified function. Higher similarity - // scores correspond to closer vectors. - return spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector); + if (segmentLevelQuantizationInfo != null && quantizedQueryVector != null) { + byte[] quantizedVector = SegmentLevelQuantizationUtil.quantizeVector(vector, segmentLevelQuantizationInfo); + return SpaceType.HAMMING.getKnnVectorSimilarityFunction().compare(quantizedQueryVector, quantizedVector); + } else { + // Calculates a similarity score between the two vectors with a specified function. Higher similarity + // scores correspond to closer vectors. + return spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector); + } } } diff --git a/src/main/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNIterator.java index 259b004f8..53ac72882 100644 --- a/src/main/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNIterator.java +++ b/src/main/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNIterator.java @@ -8,6 +8,7 @@ import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.util.BitSet; import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.query.SegmentLevelQuantizationInfo; import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; import java.io.IOException; @@ -19,14 +20,26 @@ public class NestedFilteredIdsKNNIterator extends FilteredIdsKNNIterator { private final BitSet parentBitSet; - public NestedFilteredIdsKNNIterator( + NestedFilteredIdsKNNIterator( final BitSet filterIdsArray, final float[] queryVector, final KNNFloatVectorValues knnFloatVectorValues, final SpaceType spaceType, final BitSet parentBitSet ) { - super(filterIdsArray, queryVector, knnFloatVectorValues, spaceType); + this(filterIdsArray, queryVector, knnFloatVectorValues, spaceType, parentBitSet, null, null); + } + + public NestedFilteredIdsKNNIterator( + final BitSet filterIdsArray, + final float[] queryVector, + final KNNFloatVectorValues knnFloatVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet, + final byte[] quantizedVector, + final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo + ) { + super(filterIdsArray, queryVector, knnFloatVectorValues, spaceType, quantizedVector, segmentLevelQuantizationInfo); this.parentBitSet = parentBitSet; } diff --git a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java index c13d86554..945da850a 100644 --- a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java +++ b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java @@ -20,6 +20,7 @@ import org.apache.lucene.util.BitSet; import org.apache.lucene.util.Bits; import org.opensearch.common.StopWatch; +import org.opensearch.knn.index.query.ExactSearcher; import org.opensearch.knn.index.query.KNNQuery; import org.opensearch.knn.index.query.KNNWeight; import org.opensearch.knn.index.query.ResultUtil; @@ -108,7 +109,15 @@ private List> doRescore( int finalI = i; rescoreTasks.add(() -> { BitSet convertedBitSet = ResultUtil.resultMapToMatchBitSet(perLeafResults.get(finalI)); - return knnWeight.exactSearch(leafReaderContext, convertedBitSet, false, k); + final ExactSearcher.ExactSearcherContext exactSearcherContext = ExactSearcher.ExactSearcherContext.builder() + .matchedDocs(convertedBitSet) + // setting to false because in re-scoring we want to do exact search on full precision vectors + .useQuantizedVectorsForSearch(false) + .k(k) + .isParentHits(false) + .knnQuery(knnQuery) + .build(); + return knnWeight.exactSearch(leafReaderContext, exactSearcherContext); }); } return indexSearcher.getTaskExecutor().invokeAll(rescoreTasks); diff --git a/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java index a2b41804a..810f49c15 100644 --- a/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java +++ b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java @@ -84,6 +84,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.times; @@ -1363,9 +1364,9 @@ private KNNQueryResult[] getFilteredKNNQueryResults() { public void testANNWithQuantizationParams_whenStateNotFound_thenFail() { try (MockedStatic quantizationServiceMockedStatic = Mockito.mockStatic(QuantizationService.class)) { QuantizationService quantizationService = Mockito.mock(QuantizationService.class); + quantizationServiceMockedStatic.when(QuantizationService::getInstance).thenReturn(quantizationService); QuantizationParams quantizationParams = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); Mockito.when(quantizationService.getQuantizationParams(any(FieldInfo.class))).thenReturn(quantizationParams); - quantizationServiceMockedStatic.when(QuantizationService::getInstance).thenReturn(quantizationService); // Given int k = 3; @@ -1413,6 +1414,8 @@ public void testANNWithQuantizationParams_whenStateNotFound_thenFail() { when(reader.getFieldInfos()).thenReturn(fieldInfos); when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); when(fieldInfo.attributes()).thenReturn(attributesMap); + // fieldName, new float[0], tempCollector, null) + doNothing().when(reader).searchNearestVectors(any(), eq(new float[0]), any(), any()); expectThrows(IllegalStateException.class, () -> knnWeight.scorer(leafReaderContext)); } diff --git a/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java index ee53818f1..06350f39c 100644 --- a/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java +++ b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java @@ -36,7 +36,6 @@ import java.util.concurrent.Callable; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -186,8 +185,9 @@ public void testRescore() { when(knnWeight.getQuery()).thenReturn(knnQuery); when(knnWeight.searchLeaf(leaf1, firstPassK)).thenReturn(initialLeaf1Results); when(knnWeight.searchLeaf(leaf2, firstPassK)).thenReturn(initialLeaf2Results); - when(knnWeight.exactSearch(eq(leaf1), any(), anyBoolean(), anyInt())).thenReturn(rescoredLeaf1Results); - when(knnWeight.exactSearch(eq(leaf2), any(), anyBoolean(), anyInt())).thenReturn(rescoredLeaf2Results); + + when(knnWeight.exactSearch(eq(leaf1), any())).thenReturn(rescoredLeaf1Results); + when(knnWeight.exactSearch(eq(leaf2), any())).thenReturn(rescoredLeaf2Results); try (MockedStatic mockedResultUtil = mockStatic(ResultUtil.class)) { mockedResultUtil.when(() -> ResultUtil.reduceToTopK(any(), anyInt())).thenAnswer(InvocationOnMock::callRealMethod); mockedResultUtil.when(() -> ResultUtil.resultMapToTopDocs(eq(rescoredLeaf1Results), anyInt())).thenAnswer(t -> topDocs1); From 270ac6a52f2ff9cc9cfa58dbd65333b21f925048 Mon Sep 17 00:00:00 2001 From: John Mazanec Date: Tue, 10 Sep 2024 18:13:49 -0400 Subject: [PATCH 02/59] Fix mode/comp params so parameter overrides work (#2083) PR adds capability to override parameters when specifying mode and compression. In order to do this, I add functionality for creating a deep copy of KNNMethodContext and MethodComponentContext so that we wouldnt overwrite user provided config. Then, re-arranged some of the parameter resolution logic. Signed-off-by: John Mazanec --- .../index/engine/AbstractMethodResolver.java | 185 ++++++++++ .../opensearch/knn/index/engine/Encoder.java | 12 + .../knn/index/engine/EngineResolver.java | 62 ++++ .../knn/index/engine/KNNEngine.java | 10 + .../knn/index/engine/KNNLibrary.java | 2 +- .../knn/index/engine/KNNMethodContext.java | 55 ++- .../knn/index/engine/MethodComponent.java | 5 +- .../index/engine/MethodComponentContext.java | 23 ++ .../knn/index/engine/MethodResolver.java | 36 ++ .../index/engine/ResolvedMethodContext.java | 23 ++ .../knn/index/engine/SpaceTypeResolver.java | 96 +++++ .../knn/index/engine/faiss/Faiss.java | 25 +- .../index/engine/faiss/FaissFlatEncoder.java | 11 + .../index/engine/faiss/FaissHNSWMethod.java | 26 +- .../engine/faiss/FaissHNSWPQEncoder.java | 12 + .../index/engine/faiss/FaissIVFMethod.java | 27 +- .../index/engine/faiss/FaissIVFPQEncoder.java | 12 + .../engine/faiss/FaissMethodResolver.java | 159 ++++++++ .../index/engine/faiss/FaissSQEncoder.java | 12 + .../index/engine/faiss/QFrameBitEncoder.java | 35 ++ .../knn/index/engine/lucene/Lucene.java | 17 + .../index/engine/lucene/LuceneHNSWMethod.java | 19 +- .../engine/lucene/LuceneMethodResolver.java | 106 ++++++ .../index/engine/lucene/LuceneSQEncoder.java | 12 + .../knn/index/engine/nmslib/Nmslib.java | 16 + .../index/engine/nmslib/NmslibHNSWMethod.java | 4 +- .../engine/nmslib/NmslibMethodResolver.java | 69 ++++ .../knn/index/mapper/CompressionLevel.java | 26 +- .../index/mapper/KNNVectorFieldMapper.java | 123 +++---- .../knn/index/mapper/KNNVectorFieldType.java | 5 +- .../knn/index/mapper/LuceneFieldMapper.java | 21 +- .../knn/index/mapper/MethodFieldMapper.java | 19 +- .../knn/index/mapper/ModeBasedResolver.java | 213 ----------- .../mapper/OriginalMappingParameters.java | 2 + .../opensearch/knn/index/util/IndexUtil.java | 5 +- .../plugin/rest/RestTrainModelHandler.java | 45 +-- .../transport/TrainingModelRequest.java | 19 +- .../java/org/opensearch/knn/KNNTestCase.java | 6 + .../index/engine/AbstractKNNLibraryTests.java | 10 + .../engine/AbstractMethodResolverTests.java | 158 ++++++++ .../knn/index/engine/EngineResolverTests.java | 152 ++++++++ .../knn/index/engine/NativeLibraryTests.java | 10 + .../index/engine/SpaceTypeResolverTests.java | 99 +++++ .../engine/faiss/FaissHNSWPQEncoderTests.java | 16 + .../engine/faiss/FaissIVFPQEncoderTests.java | 16 + .../faiss/FaissMethodResolverTests.java | 246 +++++++++++++ .../engine/faiss/FaissSQEncoderTests.java | 16 + .../engine/faiss/QFrameBitEncoderTests.java | 43 +++ .../lucene/LuceneMethodResolverTests.java | 212 +++++++++++ .../engine/lucene/LuceneSQEncoderTests.java | 16 + .../nmslib/NmslibMethodResolverTests.java | 106 ++++++ .../mapper/KNNVectorFieldMapperTests.java | 343 +++++++++++++++++- .../knn/integ/ModeAndCompressionIT.java | 107 ++++-- .../LibraryInitializedSupplierTests.java | 11 + ...TrainingJobRouterTransportActionTests.java | 6 +- .../transport/TrainingModelRequestTests.java | 72 +--- 56 files changed, 2715 insertions(+), 479 deletions(-) create mode 100644 src/main/java/org/opensearch/knn/index/engine/AbstractMethodResolver.java create mode 100644 src/main/java/org/opensearch/knn/index/engine/EngineResolver.java create mode 100644 src/main/java/org/opensearch/knn/index/engine/MethodResolver.java create mode 100644 src/main/java/org/opensearch/knn/index/engine/ResolvedMethodContext.java create mode 100644 src/main/java/org/opensearch/knn/index/engine/SpaceTypeResolver.java create mode 100644 src/main/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolver.java create mode 100644 src/main/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolver.java create mode 100644 src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolver.java delete mode 100644 src/main/java/org/opensearch/knn/index/mapper/ModeBasedResolver.java create mode 100644 src/test/java/org/opensearch/knn/index/engine/AbstractMethodResolverTests.java create mode 100644 src/test/java/org/opensearch/knn/index/engine/EngineResolverTests.java create mode 100644 src/test/java/org/opensearch/knn/index/engine/SpaceTypeResolverTests.java create mode 100644 src/test/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoderTests.java create mode 100644 src/test/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoderTests.java create mode 100644 src/test/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolverTests.java create mode 100644 src/test/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoderTests.java create mode 100644 src/test/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolverTests.java create mode 100644 src/test/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoderTests.java create mode 100644 src/test/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolverTests.java diff --git a/src/main/java/org/opensearch/knn/index/engine/AbstractMethodResolver.java b/src/main/java/org/opensearch/knn/index/engine/AbstractMethodResolver.java new file mode 100644 index 000000000..8127a041d --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/AbstractMethodResolver.java @@ -0,0 +1,185 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; + +/** + * Abstract {@link MethodResolver} with helpful utilitiy functions that can be shared across different + * implementations + */ +public abstract class AbstractMethodResolver implements MethodResolver { + + /** + * Utility method to get the compression level from the context + * + * @param resolvedKnnMethodContext Resolved method context. Should have an encoder set in the params if available + * @return {@link CompressionLevel} Compression level that is configured with the {@link KNNMethodContext} + */ + protected CompressionLevel resolveCompressionLevelFromMethodContext( + KNNMethodContext resolvedKnnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + Map encoderMap + ) { + // If the context is null, the compression is not configured or the encoder is not defined, return not configured + // because the method context does not contain this info + if (isEncoderSpecified(resolvedKnnMethodContext) == false) { + return CompressionLevel.x1; + } + Encoder encoder = encoderMap.get(getEncoderName(resolvedKnnMethodContext)); + if (encoder == null) { + return CompressionLevel.NOT_CONFIGURED; + } + return encoder.calculateCompressionLevel(getEncoderComponentContext(resolvedKnnMethodContext), knnMethodConfigContext); + } + + protected void resolveMethodParams( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext, + MethodComponent methodComponent + ) { + Map resolvedParams = MethodComponent.getParameterMapWithDefaultsAdded( + methodComponentContext, + methodComponent, + knnMethodConfigContext + ); + methodComponentContext.getParameters().putAll(resolvedParams); + } + + protected KNNMethodContext initResolvedKNNMethodContext( + KNNMethodContext originalMethodContext, + KNNEngine knnEngine, + SpaceType spaceType, + String methodName + ) { + if (originalMethodContext == null) { + return new KNNMethodContext(knnEngine, spaceType, new MethodComponentContext(methodName, new HashMap<>())); + } + return new KNNMethodContext(originalMethodContext); + } + + protected String getEncoderName(KNNMethodContext knnMethodContext) { + if (isEncoderSpecified(knnMethodContext) == false) { + return null; + } + + MethodComponentContext methodComponentContext = getEncoderComponentContext(knnMethodContext); + if (methodComponentContext == null) { + return null; + } + + return methodComponentContext.getName(); + } + + protected MethodComponentContext getEncoderComponentContext(KNNMethodContext knnMethodContext) { + if (isEncoderSpecified(knnMethodContext) == false) { + return null; + } + + return (MethodComponentContext) knnMethodContext.getMethodComponentContext().getParameters().get(METHOD_ENCODER_PARAMETER); + } + + /** + * Determine if the encoder parameter is specified + * + * @param knnMethodContext {@link KNNMethodContext} + * @return true is the encoder is specified in the structure; false otherwise + */ + protected boolean isEncoderSpecified(KNNMethodContext knnMethodContext) { + return knnMethodContext != null + && knnMethodContext.getMethodComponentContext().getParameters() != null + && knnMethodContext.getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER); + } + + protected boolean shouldEncoderBeResolved(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + // The encoder should not be resolved if: + // 1. The encoder is specified + // 2. The compression is x1 + // 3. The compression is not specified and the mode is not disk-based + if (isEncoderSpecified(knnMethodContext)) { + return false; + } + + if (knnMethodConfigContext.getCompressionLevel() == CompressionLevel.x1) { + return false; + } + + if (CompressionLevel.isConfigured(knnMethodConfigContext.getCompressionLevel()) == false + && Mode.ON_DISK != knnMethodConfigContext.getMode()) { + return false; + } + + if (VectorDataType.FLOAT != knnMethodConfigContext.getVectorDataType()) { + return false; + } + + return true; + } + + protected ValidationException validateNotTrainingContext( + boolean shouldRequireTraining, + KNNEngine knnEngine, + ValidationException validationException + ) { + if (shouldRequireTraining) { + validationException = validationException == null ? new ValidationException() : validationException; + validationException.addValidationError( + String.format(Locale.ROOT, "Cannot use \"%s\" engine from training context", knnEngine.getName()) + ); + } + + return validationException; + } + + protected ValidationException validateCompressionSupported( + CompressionLevel compressionLevel, + Set supportedCompressionLevels, + KNNEngine knnEngine, + ValidationException validationException + ) { + if (CompressionLevel.isConfigured(compressionLevel) && supportedCompressionLevels.contains(compressionLevel) == false) { + validationException = validationException == null ? new ValidationException() : validationException; + validationException.addValidationError( + String.format(Locale.ROOT, "\"%s\" does not support \"%s\" compression", knnEngine.getName(), compressionLevel.getName()) + ); + } + return validationException; + } + + protected ValidationException validateCompressionNotx1WhenOnDisk( + KNNMethodConfigContext knnMethodConfigContext, + ValidationException validationException + ) { + if (knnMethodConfigContext.getCompressionLevel() == CompressionLevel.x1 && knnMethodConfigContext.getMode() == Mode.ON_DISK) { + validationException = validationException == null ? new ValidationException() : validationException; + validationException.addValidationError( + String.format(Locale.ROOT, "Cannot specify \"x1\" compression level when using \"%s\" mode", Mode.ON_DISK.getName()) + ); + } + return validationException; + } + + protected void validateCompressionConflicts(CompressionLevel originalCompressionLevel, CompressionLevel resolvedCompressionLevel) { + if (CompressionLevel.isConfigured(originalCompressionLevel) + && CompressionLevel.isConfigured(resolvedCompressionLevel) + && resolvedCompressionLevel != originalCompressionLevel) { + ValidationException validationException = new ValidationException(); + validationException.addValidationError("Cannot specify an encoder that conflicts with the provided compression level"); + throw validationException; + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/Encoder.java b/src/main/java/org/opensearch/knn/index/engine/Encoder.java index 7e22145eb..f15d0afcf 100644 --- a/src/main/java/org/opensearch/knn/index/engine/Encoder.java +++ b/src/main/java/org/opensearch/knn/index/engine/Encoder.java @@ -5,6 +5,8 @@ package org.opensearch.knn.index.engine; +import org.opensearch.knn.index.mapper.CompressionLevel; + /** * Interface representing an encoder. An encoder generally refers to a vector quantizer. */ @@ -24,4 +26,14 @@ default String getName() { * @return Method component associated with the encoder */ MethodComponent getMethodComponent(); + + /** + * Calculate the compression level for the give params. Assume float32 vectors are used. All parameters should + * be resolved in the encoderContext passed in. + * + * @param encoderContext Context for the encoder to extract params from + * @return Compression level this encoder produces. If the encoder does not support this calculation yet, it will + * return {@link CompressionLevel#NOT_CONFIGURED} + */ + CompressionLevel calculateCompressionLevel(MethodComponentContext encoderContext, KNNMethodConfigContext knnMethodConfigContext); } diff --git a/src/main/java/org/opensearch/knn/index/engine/EngineResolver.java b/src/main/java/org/opensearch/knn/index/engine/EngineResolver.java new file mode 100644 index 000000000..daae361e4 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/EngineResolver.java @@ -0,0 +1,62 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +/** + * Figures out what {@link KNNEngine} to use based on configuration details + */ +public final class EngineResolver { + + public static final EngineResolver INSTANCE = new EngineResolver(); + + private EngineResolver() {} + + /** + * Based on the provided {@link Mode} and {@link CompressionLevel}, resolve to a {@link KNNEngine}. + * + * @param knnMethodConfigContext configuration context + * @param knnMethodContext KNNMethodContext + * @param requiresTraining whether config requires training + * @return {@link KNNEngine} + */ + public KNNEngine resolveEngine( + KNNMethodConfigContext knnMethodConfigContext, + KNNMethodContext knnMethodContext, + boolean requiresTraining + ) { + // User configuration gets precedence + if (knnMethodContext != null && knnMethodContext.isEngineConfigured()) { + return knnMethodContext.getKnnEngine(); + } + + // Faiss is the only engine that supports training, so we default to faiss here for now + if (requiresTraining) { + return KNNEngine.FAISS; + } + + Mode mode = knnMethodConfigContext.getMode(); + CompressionLevel compressionLevel = knnMethodConfigContext.getCompressionLevel(); + // If both mode and compression are not specified, we can just default + if (Mode.isConfigured(mode) == false && CompressionLevel.isConfigured(compressionLevel) == false) { + return KNNEngine.DEFAULT; + } + + // For 1x, we need to default to faiss if mode is provided and use nmslib otherwise + if (CompressionLevel.isConfigured(compressionLevel) == false || compressionLevel == CompressionLevel.x1) { + return mode == Mode.ON_DISK ? KNNEngine.FAISS : KNNEngine.DEFAULT; + } + + // Lucene is only engine that supports 4x - so we have to default to it here. + if (compressionLevel == CompressionLevel.x4) { + return KNNEngine.LUCENE; + } + + return KNNEngine.FAISS; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/KNNEngine.java b/src/main/java/org/opensearch/knn/index/engine/KNNEngine.java index 2f3cb3430..80b9f32a6 100644 --- a/src/main/java/org/opensearch/knn/index/engine/KNNEngine.java +++ b/src/main/java/org/opensearch/knn/index/engine/KNNEngine.java @@ -201,4 +201,14 @@ public void setInitialized(Boolean isInitialized) { public List mmapFileExtensions() { return knnLibrary.mmapFileExtensions(); } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + return knnLibrary.resolveMethod(knnMethodContext, knnMethodConfigContext, shouldRequireTraining, spaceType); + } } diff --git a/src/main/java/org/opensearch/knn/index/engine/KNNLibrary.java b/src/main/java/org/opensearch/knn/index/engine/KNNLibrary.java index 14085243f..cf7c4ad82 100644 --- a/src/main/java/org/opensearch/knn/index/engine/KNNLibrary.java +++ b/src/main/java/org/opensearch/knn/index/engine/KNNLibrary.java @@ -14,7 +14,7 @@ /** * KNNLibrary is an interface that helps the plugin communicate with k-NN libraries */ -public interface KNNLibrary { +public interface KNNLibrary extends MethodResolver { /** * Gets the version of the library that is being used. In general, this can be used for ensuring compatibility of diff --git a/src/main/java/org/opensearch/knn/index/engine/KNNMethodContext.java b/src/main/java/org/opensearch/knn/index/engine/KNNMethodContext.java index 8b2f00f74..4a4c2704e 100644 --- a/src/main/java/org/opensearch/knn/index/engine/KNNMethodContext.java +++ b/src/main/java/org/opensearch/knn/index/engine/KNNMethodContext.java @@ -5,6 +5,7 @@ package org.opensearch.knn.index.engine; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NonNull; @@ -34,17 +35,48 @@ * KNNMethodContext will contain the information necessary to produce a library index from an Opensearch mapping. * It will encompass all parameters necessary to build the index. */ -@AllArgsConstructor +@AllArgsConstructor(access = AccessLevel.PACKAGE) @Getter public class KNNMethodContext implements ToXContentFragment, Writeable { @NonNull - private final KNNEngine knnEngine; + private KNNEngine knnEngine; @NonNull @Setter private SpaceType spaceType; @NonNull private final MethodComponentContext methodComponentContext; + // Currently, the KNNEngine member variable cannot be null and defaults during parsing to nmslib. However, in order + // to support disk based engine resolution, this value potentially needs to be updated. Thus, this value is used + // to determine if the variable can be overridden or not based on whether the user explicitly set the value during parsing + private boolean isEngineConfigured; + + /** + * Copy constructor. Useful for creating a deep copy of a {@link KNNMethodContext}. Note that the engine and + * space type should be set. + * + * @param knnMethodContext original {@link KNNMethodContext}. Must NOT be null + */ + public KNNMethodContext(KNNMethodContext knnMethodContext) { + if (knnMethodContext == null) { + throw new IllegalArgumentException("KNNMethodContext cannot be null"); + } + + this.knnEngine = knnMethodContext.knnEngine; + this.spaceType = knnMethodContext.spaceType; + this.isEngineConfigured = true; + this.methodComponentContext = new MethodComponentContext(knnMethodContext.methodComponentContext); + } + + /** + * + * @param knnEngine {@link KNNEngine} + * @param spaceType {@link SpaceType} + * @param methodComponentContext {@link MethodComponentContext} + */ + public KNNMethodContext(KNNEngine knnEngine, SpaceType spaceType, MethodComponentContext methodComponentContext) { + this(knnEngine, spaceType, methodComponentContext, true); + } /** * Constructor from stream. @@ -56,6 +88,21 @@ public KNNMethodContext(StreamInput in) throws IOException { this.knnEngine = KNNEngine.getEngine(in.readString()); this.spaceType = SpaceType.getSpace(in.readString()); this.methodComponentContext = new MethodComponentContext(in); + this.isEngineConfigured = true; + } + + /** + * Set the {@link KNNEngine} if it is not configured (i.e. DEFAULT). This is useful for using different engines + * for different configurations - i.e. dynamic defaults + * + * @param knnEngine KNNEngine to set + */ + public void setKnnEngine(KNNEngine knnEngine) { + if (isEngineConfigured) { + throw new IllegalArgumentException("Cannot configure KNNEngine if it has already been configured"); + } + this.knnEngine = knnEngine; + this.isEngineConfigured = true; } /** @@ -101,6 +148,7 @@ public static KNNMethodContext parse(Object in) { @SuppressWarnings("unchecked") Map methodMap = (Map) in; + boolean isEngineConfigured = false; KNNEngine engine = KNNEngine.DEFAULT; // Get or default SpaceType spaceType = SpaceType.UNDEFINED; // Get or default String name = ""; @@ -123,6 +171,7 @@ public static KNNMethodContext parse(Object in) { throw new MapperParsingException("Invalid " + KNN_ENGINE + ": " + value); } } + isEngineConfigured = true; } else if (METHOD_PARAMETER_SPACE_TYPE.equals(key)) { if (value != null && !(value instanceof String)) { throw new MapperParsingException("\"" + METHOD_PARAMETER_SPACE_TYPE + "\" must be a string"); @@ -173,7 +222,7 @@ public static KNNMethodContext parse(Object in) { MethodComponentContext method = new MethodComponentContext(name, parameters); - return new KNNMethodContext(engine, spaceType, method); + return new KNNMethodContext(engine, spaceType, method, isEngineConfigured); } @Override diff --git a/src/main/java/org/opensearch/knn/index/engine/MethodComponent.java b/src/main/java/org/opensearch/knn/index/engine/MethodComponent.java index 2579063e9..75e18a243 100644 --- a/src/main/java/org/opensearch/knn/index/engine/MethodComponent.java +++ b/src/main/java/org/opensearch/knn/index/engine/MethodComponent.java @@ -342,7 +342,10 @@ public static Map getParameterMapWithDefaultsAdded( IndexHyperParametersUtil.getHNSWEFConstructionValue(indexCreationVersion) ); } else { - parametersWithDefaultsMap.put(parameter.getName(), parameter.getDefaultValue()); + Object value = parameter.getDefaultValue(); + if (value != null) { + parametersWithDefaultsMap.put(parameter.getName(), value); + } } } diff --git a/src/main/java/org/opensearch/knn/index/engine/MethodComponentContext.java b/src/main/java/org/opensearch/knn/index/engine/MethodComponentContext.java index 586cc338f..1f0b345e9 100644 --- a/src/main/java/org/opensearch/knn/index/engine/MethodComponentContext.java +++ b/src/main/java/org/opensearch/knn/index/engine/MethodComponentContext.java @@ -49,6 +49,29 @@ public class MethodComponentContext implements ToXContentFragment, Writeable { private final String name; private final Map parameters; + /** + * Copy constructor. Creates a deep copy of a {@link MethodComponentContext} + * + * @param methodComponentContext to be copied. Must NOT be null + */ + public MethodComponentContext(MethodComponentContext methodComponentContext) { + if (methodComponentContext == null) { + throw new IllegalArgumentException("MethodComponentContext cannot be null"); + } + + this.name = methodComponentContext.name; + this.parameters = new HashMap<>(); + if (methodComponentContext.parameters != null) { + for (Map.Entry entry : methodComponentContext.parameters.entrySet()) { + if (entry.getValue() instanceof MethodComponentContext) { + parameters.put(entry.getKey(), new MethodComponentContext((MethodComponentContext) entry.getValue())); + } else { + parameters.put(entry.getKey(), entry.getValue()); + } + } + } + } + /** * Constructor from stream. * diff --git a/src/main/java/org/opensearch/knn/index/engine/MethodResolver.java b/src/main/java/org/opensearch/knn/index/engine/MethodResolver.java new file mode 100644 index 000000000..4df18ad72 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/MethodResolver.java @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.knn.index.SpaceType; + +/** + * Interface for resolving the {@link ResolvedMethodContext} for an engine and configuration + */ +public interface MethodResolver { + + /** + * Creates a new {@link ResolvedMethodContext} filling parameters based on other configuration details. A validation + * exception will be thrown if the {@link KNNMethodConfigContext} is not compatible with the + * parameters provided by the user. + * + * @param knnMethodContext User provided information regarding the method context. A new context should be + * constructed. This variable will not be modified. + * @param knnMethodConfigContext Configuration details that can be used for resolving the defaults. Should not be null + * @param shouldRequireTraining Should the provided context require training + * @param spaceType Space type for the method. Cannot be null or undefined + * @return {@link ResolvedMethodContext} with dynamic defaults configured. This will include both the resolved + * compression as well as the completely resolve {@link KNNMethodContext}. + * This is guanteed to be a copy of the user provided context. + * @throws org.opensearch.common.ValidationException on invalid configuration and userprovided context. + */ + ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ); +} diff --git a/src/main/java/org/opensearch/knn/index/engine/ResolvedMethodContext.java b/src/main/java/org/opensearch/knn/index/engine/ResolvedMethodContext.java new file mode 100644 index 000000000..1edc0a98e --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/ResolvedMethodContext.java @@ -0,0 +1,23 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.opensearch.knn.index.mapper.CompressionLevel; + +/** + * Small data class for storing info that gets resolved during resolution process + */ +@RequiredArgsConstructor +@Getter +@Builder +public class ResolvedMethodContext { + private final KNNMethodContext knnMethodContext; + @Builder.Default + private final CompressionLevel compressionLevel = CompressionLevel.NOT_CONFIGURED; +} diff --git a/src/main/java/org/opensearch/knn/index/engine/SpaceTypeResolver.java b/src/main/java/org/opensearch/knn/index/engine/SpaceTypeResolver.java new file mode 100644 index 000000000..a12ffbc7b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/SpaceTypeResolver.java @@ -0,0 +1,96 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.apache.logging.log4j.util.Strings; +import org.opensearch.index.mapper.MapperParsingException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; + +import java.util.Locale; + +/** + * Class contains the logic to figure out what {@link SpaceType} to use based on configuration + * details. A user can either provide the {@link SpaceType} via the {@link KNNMethodContext} or through a + * top level parameter. This class will take care of this resolution logic (as well as if neither are configured) and + * ensure there are not any contradictions. + */ +public final class SpaceTypeResolver { + + public static final SpaceTypeResolver INSTANCE = new SpaceTypeResolver(); + + private SpaceTypeResolver() {} + + /** + * Resolves space type from configuration details. It is guaranteed not to return either null or + * {@link SpaceType#UNDEFINED} + * + * @param knnMethodContext Method context + * @param vectorDataType Vectordatatype + * @param topLevelSpaceTypeString Alternative top-level space type + * @return {@link SpaceType} for the method + */ + public SpaceType resolveSpaceType( + final KNNMethodContext knnMethodContext, + final VectorDataType vectorDataType, + final String topLevelSpaceTypeString + ) { + SpaceType methodSpaceType = getSpaceTypeFromMethodContext(knnMethodContext); + SpaceType topLevelSpaceType = getSpaceTypeFromString(topLevelSpaceTypeString); + + if (isSpaceTypeConfigured(methodSpaceType) == false && isSpaceTypeConfigured(topLevelSpaceType) == false) { + return getSpaceTypeFromVectorDataType(vectorDataType); + } + + if (isSpaceTypeConfigured(methodSpaceType) == false) { + return topLevelSpaceType; + } + + if (isSpaceTypeConfigured(topLevelSpaceType) == false) { + return methodSpaceType; + } + + if (methodSpaceType == topLevelSpaceType) { + return topLevelSpaceType; + } + + throw new MapperParsingException( + String.format( + Locale.ROOT, + "Cannot specify conflicting space types: \"[%s]\" \"[%s]\"", + methodSpaceType.getValue(), + topLevelSpaceType.getValue() + ) + ); + } + + private SpaceType getSpaceTypeFromMethodContext(final KNNMethodContext knnMethodContext) { + if (knnMethodContext == null) { + return SpaceType.UNDEFINED; + } + + return knnMethodContext.getSpaceType(); + } + + private SpaceType getSpaceTypeFromVectorDataType(final VectorDataType vectorDataType) { + if (vectorDataType == VectorDataType.BINARY) { + return SpaceType.DEFAULT_BINARY; + } + return SpaceType.DEFAULT; + } + + private SpaceType getSpaceTypeFromString(final String spaceType) { + if (Strings.isEmpty(spaceType)) { + return SpaceType.UNDEFINED; + } + + return SpaceType.getSpace(spaceType); + } + + private boolean isSpaceTypeConfigured(final SpaceType spaceType) { + return spaceType != null && spaceType != SpaceType.UNDEFINED; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/Faiss.java b/src/main/java/org/opensearch/knn/index/engine/faiss/Faiss.java index 329acbdb8..a602619a1 100644 --- a/src/main/java/org/opensearch/knn/index/engine/faiss/Faiss.java +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/Faiss.java @@ -9,7 +9,11 @@ import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.engine.KNNMethod; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodResolver; import org.opensearch.knn.index.engine.NativeLibrary; +import org.opensearch.knn.index.engine.ResolvedMethodContext; import java.util.Map; import java.util.function.Function; @@ -41,12 +45,8 @@ public class Faiss extends NativeLibrary { SpaceType, Function>builder().put(SpaceType.INNER_PRODUCT, score -> score > 1 ? 1 - score : 1 / score - 1).build(); - private final static Map METHODS = ImmutableMap.of( - METHOD_HNSW, - new FaissHNSWMethod(), - METHOD_IVF, - new FaissIVFMethod() - ); + // Package private so that the method resolving logic can access the methods + final static Map METHODS = ImmutableMap.of(METHOD_HNSW, new FaissHNSWMethod(), METHOD_IVF, new FaissIVFMethod()); public final static Faiss INSTANCE = new Faiss( METHODS, @@ -56,6 +56,8 @@ public class Faiss extends NativeLibrary { SCORE_TO_DISTANCE_TRANSFORMATIONS ); + private final MethodResolver methodResolver; + /** * Constructor for Faiss * @@ -73,6 +75,7 @@ private Faiss( ) { super(methods, scoreTranslation, currentVersion, extension); this.scoreTransform = scoreTransform; + this.methodResolver = new FaissMethodResolver(); } @Override @@ -89,4 +92,14 @@ public Float scoreToRadialThreshold(Float score, SpaceType spaceType) { } return spaceType.scoreToDistanceTranslation(score); } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + return methodResolver.resolveMethod(knnMethodContext, knnMethodConfigContext, shouldRequireTraining, spaceType); + } } diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissFlatEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissFlatEncoder.java index bd7598d84..f7d4342fc 100644 --- a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissFlatEncoder.java +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissFlatEncoder.java @@ -9,7 +9,10 @@ import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.CompressionLevel; import java.util.Set; @@ -41,4 +44,12 @@ public class FaissFlatEncoder implements Encoder { public MethodComponent getMethodComponent() { return METHOD_COMPONENT; } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext encoderContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + return CompressionLevel.x1; + } } diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWMethod.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWMethod.java index 41db777e3..c153a9328 100644 --- a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWMethod.java +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWMethod.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -52,12 +53,23 @@ public class FaissHNSWMethod extends AbstractFaissMethod { KNNConstants.ENCODER_FLAT, Collections.emptyMap() ); - private final static List SUPPORTED_ENCODERS = List.of( - new FaissFlatEncoder(), - new FaissSQEncoder(), - new FaissHNSWPQEncoder(), - new QFrameBitEncoder() + + // Package private so that the method resolving logic can access the methods + final static Encoder FLAT_ENCODER = new FaissFlatEncoder(); + final static Encoder SQ_ENCODER = new FaissSQEncoder(); + final static Encoder HNSW_PQ_ENCODER = new FaissHNSWPQEncoder(); + final static Encoder QFRAME_BIT_ENCODER = new QFrameBitEncoder(); + final static Map SUPPORTED_ENCODERS = Map.of( + FLAT_ENCODER.getName(), + FLAT_ENCODER, + SQ_ENCODER.getName(), + SQ_ENCODER, + HNSW_PQ_ENCODER.getName(), + HNSW_PQ_ENCODER, + QFRAME_BIT_ENCODER.getName(), + QFRAME_BIT_ENCODER ); + final static MethodComponent HNSW_COMPONENT = initMethodComponent(); /** * Constructor for FaissHNSWMethod @@ -65,7 +77,7 @@ public class FaissHNSWMethod extends AbstractFaissMethod { * @see AbstractKNNMethod */ public FaissHNSWMethod() { - super(initMethodComponent(), Set.copyOf(SUPPORTED_SPACES), new DefaultHnswSearchContext()); + super(HNSW_COMPONENT, Set.copyOf(SUPPORTED_SPACES), new DefaultHnswSearchContext()); } private static MethodComponent initMethodComponent() { @@ -108,7 +120,7 @@ private static Parameter.MethodComponentContextParameter initEncoderParameter() return new Parameter.MethodComponentContextParameter( METHOD_ENCODER_PARAMETER, DEFAULT_ENCODER_CONTEXT, - SUPPORTED_ENCODERS.stream().collect(Collectors.toMap(Encoder::getName, Encoder::getMethodComponent)) + SUPPORTED_ENCODERS.values().stream().collect(Collectors.toMap(Encoder::getName, Encoder::getMethodComponent)) ); } } diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoder.java index 9bebf5b4d..6750d84ed 100644 --- a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoder.java +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoder.java @@ -9,8 +9,11 @@ import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.engine.Parameter; +import org.opensearch.knn.index.mapper.CompressionLevel; import java.util.Objects; import java.util.Set; @@ -69,4 +72,13 @@ public class FaissHNSWPQEncoder implements Encoder { public MethodComponent getMethodComponent() { return METHOD_COMPONENT; } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + // TODO: For now, not supported out of the box + return CompressionLevel.NOT_CONFIGURED; + } } diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFMethod.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFMethod.java index 70ab4222b..340c1f4d8 100644 --- a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFMethod.java +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFMethod.java @@ -19,6 +19,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -55,20 +56,32 @@ public class FaissIVFMethod extends AbstractFaissMethod { KNNConstants.ENCODER_FLAT, Collections.emptyMap() ); - private final static List SUPPORTED_ENCODERS = List.of( - new FaissFlatEncoder(), - new FaissSQEncoder(), - new FaissIVFPQEncoder(), - new QFrameBitEncoder() + + // Package private so that the method resolving logic can access the methods + final static Encoder FLAT_ENCODER = new FaissFlatEncoder(); + final static Encoder SQ_ENCODER = new FaissSQEncoder(); + final static Encoder IVF_PQ_ENCODER = new FaissIVFPQEncoder(); + final static Encoder QFRAME_BIT_ENCODER = new QFrameBitEncoder(); + final static Map SUPPORTED_ENCODERS = Map.of( + FLAT_ENCODER.getName(), + FLAT_ENCODER, + SQ_ENCODER.getName(), + SQ_ENCODER, + IVF_PQ_ENCODER.getName(), + IVF_PQ_ENCODER, + QFRAME_BIT_ENCODER.getName(), + QFRAME_BIT_ENCODER ); + final static MethodComponent IVF_COMPONENT = initMethodComponent(); + /** * Constructor for FaissIVFMethod * * @see AbstractKNNMethod */ public FaissIVFMethod() { - super(initMethodComponent(), Set.copyOf(SUPPORTED_SPACES), new DefaultIVFSearchContext()); + super(IVF_COMPONENT, Set.copyOf(SUPPORTED_SPACES), new DefaultIVFSearchContext()); } private static MethodComponent initMethodComponent() { @@ -133,7 +146,7 @@ private static Parameter.MethodComponentContextParameter initEncoderParameter() return new Parameter.MethodComponentContextParameter( METHOD_ENCODER_PARAMETER, DEFAULT_ENCODER_CONTEXT, - SUPPORTED_ENCODERS.stream().collect(Collectors.toMap(Encoder::getName, Encoder::getMethodComponent)) + SUPPORTED_ENCODERS.values().stream().collect(Collectors.toMap(Encoder::getName, Encoder::getMethodComponent)) ); } } diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoder.java index bb6623600..8d54548bd 100644 --- a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoder.java +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoder.java @@ -9,8 +9,11 @@ import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.engine.Parameter; +import org.opensearch.knn.index.mapper.CompressionLevel; import java.util.Set; @@ -90,4 +93,13 @@ public class FaissIVFPQEncoder implements Encoder { public MethodComponent getMethodComponent() { return METHOD_COMPONENT; } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + // TODO: For now, not supported out of the box + return CompressionLevel.NOT_CONFIGURED; + } } diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolver.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolver.java new file mode 100644 index 000000000..90e938eb3 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolver.java @@ -0,0 +1,159 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.AbstractMethodResolver; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_FLAT; +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_FP16; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_TYPE; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.index.engine.faiss.FaissHNSWMethod.HNSW_COMPONENT; +import static org.opensearch.knn.index.engine.faiss.FaissIVFMethod.IVF_COMPONENT; + +public class FaissMethodResolver extends AbstractMethodResolver { + + private static final Set SUPPORTED_COMPRESSION_LEVELS = Set.of( + CompressionLevel.x1, + CompressionLevel.x2, + CompressionLevel.x8, + CompressionLevel.x16, + CompressionLevel.x32 + ); + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + // Initial validation to ensure that there are no contradictions in provided parameters + validateConfig(knnMethodConfigContext); + + KNNMethodContext resolvedKNNMethodContext = initResolvedKNNMethodContext( + knnMethodContext, + KNNEngine.FAISS, + spaceType, + shouldRequireTraining ? METHOD_IVF : METHOD_HNSW + ); + MethodComponent method = METHOD_HNSW.equals(resolvedKNNMethodContext.getMethodComponentContext().getName()) == false + ? IVF_COMPONENT + : HNSW_COMPONENT; + Map encoderMap = method == HNSW_COMPONENT ? FaissHNSWMethod.SUPPORTED_ENCODERS : FaissIVFMethod.SUPPORTED_ENCODERS; + + // Fill in parameters for the encoder and then the method. + resolveEncoder(resolvedKNNMethodContext, knnMethodConfigContext, encoderMap); + resolveMethodParams(resolvedKNNMethodContext.getMethodComponentContext(), knnMethodConfigContext, method); + + // From the resolved method context, get the compression level and validate it against the passed in + // configuration + CompressionLevel resolvedCompressionLevel = resolveCompressionLevelFromMethodContext( + resolvedKNNMethodContext, + knnMethodConfigContext, + encoderMap + ); + + // Validate that resolved compression doesnt have any conflicts + validateCompressionConflicts(knnMethodConfigContext.getCompressionLevel(), resolvedCompressionLevel); + return ResolvedMethodContext.builder() + .knnMethodContext(resolvedKNNMethodContext) + .compressionLevel(resolvedCompressionLevel) + .build(); + } + + private void resolveEncoder( + KNNMethodContext resolvedKNNMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + Map encoderMap + ) { + if (shouldEncoderBeResolved(resolvedKNNMethodContext, knnMethodConfigContext) == false) { + return; + } + + CompressionLevel resolvedCompressionLevel = getDefaultCompressionLevel(knnMethodConfigContext); + if (resolvedCompressionLevel == CompressionLevel.x1) { + return; + } + + MethodComponentContext encoderComponentContext = new MethodComponentContext(ENCODER_FLAT, new HashMap<>()); + Encoder encoder = encoderMap.get(ENCODER_FLAT); + if (CompressionLevel.x2 == resolvedCompressionLevel) { + encoderComponentContext = new MethodComponentContext(ENCODER_SQ, new HashMap<>()); + encoder = encoderMap.get(ENCODER_SQ); + encoderComponentContext.getParameters().put(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16); + } + + if (CompressionLevel.x8 == resolvedCompressionLevel) { + encoderComponentContext = new MethodComponentContext(QFrameBitEncoder.NAME, new HashMap<>()); + encoder = encoderMap.get(QFrameBitEncoder.NAME); + encoderComponentContext.getParameters().put(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x8.numBitsForFloat32()); + } + + if (CompressionLevel.x16 == resolvedCompressionLevel) { + encoderComponentContext = new MethodComponentContext(QFrameBitEncoder.NAME, new HashMap<>()); + encoder = encoderMap.get(QFrameBitEncoder.NAME); + encoderComponentContext.getParameters().put(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x16.numBitsForFloat32()); + } + + if (CompressionLevel.x32 == resolvedCompressionLevel) { + encoderComponentContext = new MethodComponentContext(QFrameBitEncoder.NAME, new HashMap<>()); + encoder = encoderMap.get(QFrameBitEncoder.NAME); + encoderComponentContext.getParameters().put(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x32.numBitsForFloat32()); + } + + Map resolvedParams = MethodComponent.getParameterMapWithDefaultsAdded( + encoderComponentContext, + encoder.getMethodComponent(), + knnMethodConfigContext + ); + encoderComponentContext.getParameters().putAll(resolvedParams); + resolvedKNNMethodContext.getMethodComponentContext().getParameters().put(METHOD_ENCODER_PARAMETER, encoderComponentContext); + } + + // Method validates for explicit contradictions in the config + private void validateConfig(KNNMethodConfigContext knnMethodConfigContext) { + CompressionLevel compressionLevel = knnMethodConfigContext.getCompressionLevel(); + ValidationException validationException = validateCompressionSupported( + compressionLevel, + SUPPORTED_COMPRESSION_LEVELS, + KNNEngine.FAISS, + null + ); + validationException = validateCompressionNotx1WhenOnDisk(knnMethodConfigContext, validationException); + if (validationException != null) { + throw validationException; + } + } + + private CompressionLevel getDefaultCompressionLevel(KNNMethodConfigContext knnMethodConfigContext) { + if (CompressionLevel.isConfigured(knnMethodConfigContext.getCompressionLevel())) { + return knnMethodConfigContext.getCompressionLevel(); + } + if (knnMethodConfigContext.getMode() == Mode.ON_DISK) { + return CompressionLevel.x32; + } + return CompressionLevel.x1; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoder.java index 6d57aef2f..cd7e1e5f3 100644 --- a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoder.java +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoder.java @@ -8,8 +8,11 @@ import com.google.common.collect.ImmutableSet; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.engine.Parameter; +import org.opensearch.knn.index.mapper.CompressionLevel; import java.util.Objects; import java.util.Set; @@ -49,4 +52,13 @@ public class FaissSQEncoder implements Encoder { public MethodComponent getMethodComponent() { return METHOD_COMPONENT; } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + // TODO: Hard code for now + return CompressionLevel.x2; + } } diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoder.java index e135fa33f..2292dc3cc 100644 --- a/src/main/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoder.java +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoder.java @@ -6,12 +6,16 @@ package org.opensearch.knn.index.engine.faiss; import com.google.common.collect.ImmutableSet; +import org.opensearch.common.ValidationException; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.Encoder; import org.opensearch.knn.index.engine.KNNLibraryIndexingContextImpl; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.engine.Parameter; import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.mapper.CompressionLevel; import org.opensearch.knn.quantization.enums.ScalarQuantizationType; import java.util.HashMap; @@ -75,4 +79,35 @@ public class QFrameBitEncoder implements Encoder { public MethodComponent getMethodComponent() { return METHOD_COMPONENT; } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + if (methodComponentContext.getParameters().containsKey(BITCOUNT_PARAM) == false) { + return CompressionLevel.NOT_CONFIGURED; + } + + // Map the number of bits passed in, back to the compression level + Object value = methodComponentContext.getParameters().get(BITCOUNT_PARAM); + ValidationException validationException = METHOD_COMPONENT.getParameters() + .get(BITCOUNT_PARAM) + .validate(value, knnMethodConfigContext); + if (validationException != null) { + throw validationException; + } + + Integer bitCount = (Integer) value; + if (bitCount == 1) { + return CompressionLevel.x32; + } + + if (bitCount == 2) { + return CompressionLevel.x16; + } + + // Validation will ensure that only 1 of the supported bit count will be selected. + return CompressionLevel.x8; + } } diff --git a/src/main/java/org/opensearch/knn/index/engine/lucene/Lucene.java b/src/main/java/org/opensearch/knn/index/engine/lucene/Lucene.java index 986380897..db516d309 100644 --- a/src/main/java/org/opensearch/knn/index/engine/lucene/Lucene.java +++ b/src/main/java/org/opensearch/knn/index/engine/lucene/Lucene.java @@ -10,6 +10,10 @@ import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.engine.JVMLibrary; import org.opensearch.knn.index.engine.KNNMethod; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodResolver; +import org.opensearch.knn.index.engine.ResolvedMethodContext; import java.util.List; import java.util.Map; @@ -37,6 +41,8 @@ public class Lucene extends JVMLibrary { public final static Lucene INSTANCE = new Lucene(METHODS, Version.LATEST.toString(), DISTANCE_TRANSLATIONS); + private final MethodResolver methodResolver; + /** * Constructor * @@ -47,6 +53,7 @@ public class Lucene extends JVMLibrary { Lucene(Map methods, String version, Map> distanceTransform) { super(methods, version); this.distanceTransform = distanceTransform; + this.methodResolver = new LuceneMethodResolver(); } @Override @@ -86,4 +93,14 @@ public Float scoreToRadialThreshold(Float score, SpaceType spaceType) { public List mmapFileExtensions() { return List.of("vec", "vex"); } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + return methodResolver.resolveMethod(knnMethodContext, knnMethodConfigContext, shouldRequireTraining, spaceType); + } } diff --git a/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneHNSWMethod.java b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneHNSWMethod.java index 317f67c10..57cc016a6 100644 --- a/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneHNSWMethod.java +++ b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneHNSWMethod.java @@ -6,19 +6,17 @@ package org.opensearch.knn.index.engine.lucene; import com.google.common.collect.ImmutableSet; -import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.AbstractKNNMethod; import org.opensearch.knn.index.engine.Encoder; import org.opensearch.knn.index.engine.MethodComponent; -import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.engine.Parameter; import java.util.Arrays; -import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -41,11 +39,10 @@ public class LuceneHNSWMethod extends AbstractKNNMethod { SpaceType.INNER_PRODUCT ); - private final static MethodComponentContext DEFAULT_ENCODER_CONTEXT = new MethodComponentContext( - KNNConstants.ENCODER_FLAT, - Collections.emptyMap() - ); - private final static List SUPPORTED_ENCODERS = List.of(new LuceneSQEncoder()); + final static Encoder SQ_ENCODER = new LuceneSQEncoder(); + final static Map SUPPORTED_ENCODERS = Map.of(SQ_ENCODER.getName(), SQ_ENCODER); + + final static MethodComponent HNSW_METHOD_COMPONENT = initMethodComponent(); /** * Constructor for LuceneHNSWMethod @@ -53,7 +50,7 @@ public class LuceneHNSWMethod extends AbstractKNNMethod { * @see AbstractKNNMethod */ public LuceneHNSWMethod() { - super(initMethodComponent(), Set.copyOf(SUPPORTED_SPACES), new LuceneHNSWSearchContext()); + super(HNSW_METHOD_COMPONENT, Set.copyOf(SUPPORTED_SPACES), new LuceneHNSWSearchContext()); } private static MethodComponent initMethodComponent() { @@ -78,8 +75,8 @@ private static MethodComponent initMethodComponent() { private static Parameter.MethodComponentContextParameter initEncoderParameter() { return new Parameter.MethodComponentContextParameter( METHOD_ENCODER_PARAMETER, - DEFAULT_ENCODER_CONTEXT, - SUPPORTED_ENCODERS.stream().collect(Collectors.toMap(Encoder::getName, Encoder::getMethodComponent)) + null, + SUPPORTED_ENCODERS.values().stream().collect(Collectors.toMap(Encoder::getName, Encoder::getMethodComponent)) ); } } diff --git a/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolver.java b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolver.java new file mode 100644 index 000000000..6546d9f93 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolver.java @@ -0,0 +1,106 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.lucene; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.AbstractMethodResolver; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.index.engine.lucene.LuceneHNSWMethod.HNSW_METHOD_COMPONENT; +import static org.opensearch.knn.index.engine.lucene.LuceneHNSWMethod.SQ_ENCODER; + +public class LuceneMethodResolver extends AbstractMethodResolver { + + private static final Set SUPPORTED_COMPRESSION_LEVELS = Set.of(CompressionLevel.x1, CompressionLevel.x4); + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + validateConfig(knnMethodConfigContext, shouldRequireTraining); + KNNMethodContext resolvedKNNMethodContext = initResolvedKNNMethodContext( + knnMethodContext, + KNNEngine.LUCENE, + spaceType, + METHOD_HNSW + ); + resolveEncoder(resolvedKNNMethodContext, knnMethodConfigContext); + resolveMethodParams(resolvedKNNMethodContext.getMethodComponentContext(), knnMethodConfigContext, HNSW_METHOD_COMPONENT); + CompressionLevel resolvedCompressionLevel = resolveCompressionLevelFromMethodContext( + resolvedKNNMethodContext, + knnMethodConfigContext, + LuceneHNSWMethod.SUPPORTED_ENCODERS + ); + validateCompressionConflicts(knnMethodConfigContext.getCompressionLevel(), resolvedCompressionLevel); + return ResolvedMethodContext.builder() + .knnMethodContext(resolvedKNNMethodContext) + .compressionLevel(resolvedCompressionLevel) + .build(); + } + + protected void resolveEncoder(KNNMethodContext resolvedKNNMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + if (shouldEncoderBeResolved(resolvedKNNMethodContext, knnMethodConfigContext) == false) { + return; + } + + CompressionLevel resolvedCompressionLevel = getDefaultCompressionLevel(knnMethodConfigContext); + if (resolvedCompressionLevel == CompressionLevel.x1) { + return; + } + + MethodComponentContext methodComponentContext = resolvedKNNMethodContext.getMethodComponentContext(); + MethodComponentContext encoderComponentContext = new MethodComponentContext(SQ_ENCODER.getName(), new HashMap<>()); + Map resolvedParams = MethodComponent.getParameterMapWithDefaultsAdded( + encoderComponentContext, + SQ_ENCODER.getMethodComponent(), + knnMethodConfigContext + ); + encoderComponentContext.getParameters().putAll(resolvedParams); + methodComponentContext.getParameters().put(METHOD_ENCODER_PARAMETER, encoderComponentContext); + } + + // Method validates for explicit contradictions in the config + private void validateConfig(KNNMethodConfigContext knnMethodConfigContext, boolean shouldRequireTraining) { + ValidationException validationException = validateNotTrainingContext(shouldRequireTraining, KNNEngine.LUCENE, null); + validationException = validateCompressionSupported( + knnMethodConfigContext.getCompressionLevel(), + SUPPORTED_COMPRESSION_LEVELS, + KNNEngine.LUCENE, + validationException + ); + validationException = validateCompressionNotx1WhenOnDisk(knnMethodConfigContext, validationException); + if (validationException != null) { + throw validationException; + } + } + + private CompressionLevel getDefaultCompressionLevel(KNNMethodConfigContext knnMethodConfigContext) { + if (CompressionLevel.isConfigured(knnMethodConfigContext.getCompressionLevel())) { + return knnMethodConfigContext.getCompressionLevel(); + } + if (knnMethodConfigContext.getMode() == Mode.ON_DISK) { + return CompressionLevel.x4; + } + return CompressionLevel.x1; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoder.java index 0ec43db41..6bd16ebee 100644 --- a/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoder.java +++ b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoder.java @@ -8,8 +8,11 @@ import com.google.common.collect.ImmutableSet; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.engine.Parameter; +import org.opensearch.knn.index.mapper.CompressionLevel; import java.util.List; import java.util.Set; @@ -49,4 +52,13 @@ public class LuceneSQEncoder implements Encoder { public MethodComponent getMethodComponent() { return METHOD_COMPONENT; } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + // Hard coding to 4x for now, given thats all that is supported. + return CompressionLevel.x4; + } } diff --git a/src/main/java/org/opensearch/knn/index/engine/nmslib/Nmslib.java b/src/main/java/org/opensearch/knn/index/engine/nmslib/Nmslib.java index d35cc5f6c..4d7f7f423 100644 --- a/src/main/java/org/opensearch/knn/index/engine/nmslib/Nmslib.java +++ b/src/main/java/org/opensearch/knn/index/engine/nmslib/Nmslib.java @@ -8,7 +8,11 @@ import com.google.common.collect.ImmutableMap; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.engine.KNNMethod; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodResolver; import org.opensearch.knn.index.engine.NativeLibrary; +import org.opensearch.knn.index.engine.ResolvedMethodContext; import java.util.Collections; import java.util.Map; @@ -27,6 +31,7 @@ public class Nmslib extends NativeLibrary { final static Map METHODS = ImmutableMap.of(METHOD_HNSW, new NmslibHNSWMethod()); public final static Nmslib INSTANCE = new Nmslib(METHODS, Collections.emptyMap(), CURRENT_VERSION, EXTENSION); + private final MethodResolver methodResolver; /** * Constructor for Nmslib @@ -43,6 +48,7 @@ private Nmslib( String extension ) { super(methods, scoreTranslation, currentVersion, extension); + this.methodResolver = new NmslibMethodResolver(); } @Override @@ -53,4 +59,14 @@ public Float distanceToRadialThreshold(Float distance, SpaceType spaceType) { public Float scoreToRadialThreshold(Float score, SpaceType spaceType) { return score; } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + return methodResolver.resolveMethod(knnMethodContext, knnMethodConfigContext, shouldRequireTraining, spaceType); + } } diff --git a/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibHNSWMethod.java b/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibHNSWMethod.java index 779c16cd3..d2440926e 100644 --- a/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibHNSWMethod.java +++ b/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibHNSWMethod.java @@ -38,12 +38,14 @@ public class NmslibHNSWMethod extends AbstractKNNMethod { SpaceType.INNER_PRODUCT ); + final static MethodComponent HNSW_METHOD_COMPONENT = initMethodComponent(); + /** * Constructor. Builds the method with the default parameters and supported spaces. * @see AbstractKNNMethod */ public NmslibHNSWMethod() { - super(initMethodComponent(), Set.copyOf(SUPPORTED_SPACES), new DefaultHnswSearchContext()); + super(HNSW_METHOD_COMPONENT, Set.copyOf(SUPPORTED_SPACES), new DefaultHnswSearchContext()); } private static MethodComponent initMethodComponent() { diff --git a/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolver.java b/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolver.java new file mode 100644 index 000000000..619a00eda --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolver.java @@ -0,0 +1,69 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.nmslib; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.AbstractMethodResolver; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.index.engine.nmslib.NmslibHNSWMethod.HNSW_METHOD_COMPONENT; + +/** + * Method resolution logic for nmslib. Because nmslib does not support quantization, it is in general a validation + * before returning the original request + */ +public class NmslibMethodResolver extends AbstractMethodResolver { + + private static final Set SUPPORTED_COMPRESSION_LEVELS = Set.of(CompressionLevel.x1); + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + validateConfig(knnMethodConfigContext, shouldRequireTraining); + KNNMethodContext resolvedKNNMethodContext = initResolvedKNNMethodContext( + knnMethodContext, + KNNEngine.NMSLIB, + spaceType, + METHOD_HNSW + ); + resolveMethodParams(resolvedKNNMethodContext.getMethodComponentContext(), knnMethodConfigContext, HNSW_METHOD_COMPONENT); + return ResolvedMethodContext.builder().knnMethodContext(resolvedKNNMethodContext).compressionLevel(CompressionLevel.x1).build(); + } + + // Method validates for explicit contradictions in the config + private void validateConfig(KNNMethodConfigContext knnMethodConfigContext, boolean shouldRequireTraining) { + ValidationException validationException = validateNotTrainingContext(shouldRequireTraining, KNNEngine.NMSLIB, null); + CompressionLevel compressionLevel = knnMethodConfigContext.getCompressionLevel(); + validationException = validateCompressionSupported( + compressionLevel, + SUPPORTED_COMPRESSION_LEVELS, + KNNEngine.NMSLIB, + validationException + ); + + if (Mode.ON_DISK == knnMethodConfigContext.getMode()) { + validationException = validationException == null ? new ValidationException() : validationException; + validationException.addValidationError("Nmslib engine does not support disk-based search"); + } + + if (validationException != null) { + throw validationException; + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java index 4b5026598..cc80bb1ed 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java +++ b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java @@ -10,7 +10,9 @@ import org.opensearch.core.common.Strings; import org.opensearch.knn.index.query.rescore.RescoreContext; +import java.util.Collections; import java.util.Locale; +import java.util.Set; /** * Enum representing the compression level for float vectors. Compression in this sense refers to compressing a @@ -19,13 +21,13 @@ */ @AllArgsConstructor public enum CompressionLevel { - NOT_CONFIGURED(-1, "", null), - x1(1, "1x", null), - x2(2, "2x", null), - x4(4, "4x", new RescoreContext(1.0f)), - x8(8, "8x", new RescoreContext(1.5f)), - x16(16, "16x", new RescoreContext(2.0f)), - x32(32, "32x", new RescoreContext(2.0f)); + NOT_CONFIGURED(-1, "", null, Collections.emptySet()), + x1(1, "1x", null, Collections.emptySet()), + x2(2, "2x", null, Collections.emptySet()), + x4(4, "4x", null, Collections.emptySet()), + x8(8, "8x", new RescoreContext(1.5f), Set.of(Mode.ON_DISK)), + x16(16, "16x", new RescoreContext(2.0f), Set.of(Mode.ON_DISK)), + x32(32, "32x", new RescoreContext(2.0f), Set.of(Mode.ON_DISK)); // Internally, an empty string is easier to deal with them null. However, from the mapping, // we do not want users to pass in the empty string and instead want null. So we make the conversion herex @@ -33,6 +35,7 @@ public enum CompressionLevel { NOT_CONFIGURED.getName(), x1.getName(), x2.getName(), + x4.getName(), x8.getName(), x16.getName(), x32.getName() }; @@ -64,8 +67,8 @@ public static CompressionLevel fromName(String name) { private final int compressionLevel; @Getter private final String name; - @Getter private final RescoreContext defaultRescoreContext; + private final Set modesForRescore; /** * Gets the number of bits used to represent a float in order to achieve this compression. For instance, for @@ -90,4 +93,11 @@ public int numBitsForFloat32() { public static boolean isConfigured(CompressionLevel compressionLevel) { return compressionLevel != null && compressionLevel != NOT_CONFIGURED; } + + public RescoreContext getDefaultRescoreContext(Mode mode) { + if (modesForRescore.contains(mode)) { + return defaultRescoreContext; + } + return null; + } } diff --git a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java index f149fa1d2..6e5138a56 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java +++ b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java @@ -39,12 +39,15 @@ import org.opensearch.index.mapper.ParseContext; import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.engine.EngineResolver; import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.KNNMethodContext; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.VectorField; import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.engine.SpaceTypeResolver; import org.opensearch.knn.indices.ModelDao; import static org.opensearch.knn.common.KNNConstants.DEFAULT_VECTOR_DATA_TYPE_FIELD; @@ -174,6 +177,7 @@ public static class Builder extends ParametrizedFieldMapper.Builder { protected ModelDao modelDao; protected Version indexCreatedVersion; @Setter + @Getter private KNNMethodConfigContext knnMethodConfigContext; @Setter @Getter @@ -365,9 +369,20 @@ public Mapper.Builder parse(String name, Map node, ParserCont } else if (builder.modelId.get() != null) { validateFromModel(builder); } else { - validateMode(builder); + // Validate that the mode and compression are not set if data type is not float, as they are not + // supported. + validateModeAndCompressionForDataType(builder); + // If the original knnMethodContext is not null, resolve its space type and engine from the rest of the + // configuration. This is consistent with the existing behavior for space type in 2.16 where we modify the + // parsed value + SpaceType resolvedSpaceType = SpaceTypeResolver.INSTANCE.resolveSpaceType( + builder.originalParameters.getKnnMethodContext(), + builder.vectorDataType.get(), + builder.topLevelSpaceType.get() + ); + setSpaceType(builder.originalParameters.getKnnMethodContext(), resolvedSpaceType); validateSpaceType(builder); - resolveKNNMethodComponents(builder, parserContext); + resolveKNNMethodComponents(builder, parserContext, resolvedSpaceType); validateFromKNNMethod(builder); } @@ -391,20 +406,9 @@ private void validateSpaceType(KNNVectorFieldMapper.Builder builder) { } } - private void validateMode(KNNVectorFieldMapper.Builder builder) { - boolean isKNNMethodContextConfigured = builder.originalParameters.getKnnMethodContext() != null; - boolean isModeConfigured = builder.mode.isConfigured() || builder.compressionLevel.isConfigured(); - if (isModeConfigured && isKNNMethodContextConfigured) { - throw new MapperParsingException( - String.format( - Locale.ROOT, - "Compression and mode can not be specified in a \"method\" mapping configuration for field: %s", - builder.name - ) - ); - } - - if (isModeConfigured && builder.vectorDataType.getValue() != VectorDataType.FLOAT) { + private void validateModeAndCompressionForDataType(KNNVectorFieldMapper.Builder builder) { + boolean isModeOrCompressionConfigured = builder.mode.isConfigured() || builder.compressionLevel.isConfigured(); + if (isModeOrCompressionConfigured && builder.vectorDataType.getValue() != VectorDataType.FLOAT) { throw new MapperParsingException( String.format(Locale.ROOT, "Compression and mode cannot be used for non-float32 data type for field %s", builder.name) ); @@ -468,7 +472,12 @@ private void validateCompressionAndModeNotSet(KNNVectorFieldMapper.Builder build } } - private void resolveKNNMethodComponents(KNNVectorFieldMapper.Builder builder, ParserContext parserContext) { + private void resolveKNNMethodComponents( + KNNVectorFieldMapper.Builder builder, + ParserContext parserContext, + SpaceType resolvedSpaceType + ) { + // Setup the initial configuration that is used to help resolve parameters. builder.setKnnMethodConfigContext( KNNMethodConfigContext.builder() .vectorDataType(builder.originalParameters.getVectorDataType()) @@ -479,36 +488,34 @@ private void resolveKNNMethodComponents(KNNVectorFieldMapper.Builder builder, Pa .build() ); - // Configure method from map or legacy + // If the original parameters are from legacy if (builder.originalParameters.isLegacyMapping()) { + // Then create KNNMethodContext to be used from the legacy index settings builder.originalParameters.setResolvedKnnMethodContext( - createKNNMethodContextFromLegacy( - parserContext.getSettings(), - parserContext.indexVersionCreated(), - SpaceType.getSpace(builder.topLevelSpaceType.get()) - ) + createKNNMethodContextFromLegacy(parserContext.getSettings(), parserContext.indexVersionCreated(), resolvedSpaceType) ); - } else if (Mode.isConfigured(Mode.fromName(builder.mode.get())) - || CompressionLevel.isConfigured(CompressionLevel.fromName(builder.compressionLevel.get()))) { - // we need don't need to resolve the space type, whatever default we are using will be passed down to - // while resolving KNNMethodContext for the mode and compression. and then when we resolve the spaceType - // we will set the correct spaceType. - builder.originalParameters.setResolvedKnnMethodContext( - ModeBasedResolver.INSTANCE.resolveKNNMethodContext( - builder.knnMethodConfigContext.getMode(), - builder.knnMethodConfigContext.getCompressionLevel(), - false, - SpaceType.getSpace(builder.originalParameters.getTopLevelSpaceType()) - ) - ); - } - // this function should now correct the space type for the above resolved context too, if spaceType was - // not provided. - setSpaceType( + } + + // Based on config context, if the user does not set the engine, set it + KNNEngine resolvedKNNEngine = EngineResolver.INSTANCE.resolveEngine( + builder.knnMethodConfigContext, builder.originalParameters.getResolvedKnnMethodContext(), - builder.originalParameters.getVectorDataType(), - builder.topLevelSpaceType.get() + false ); + setEngine(builder.originalParameters.getResolvedKnnMethodContext(), resolvedKNNEngine); + + // Create a copy of the KNNMethodContext and fill in the parameters left blank by configuration context context + ResolvedMethodContext resolvedMethodContext = resolvedKNNEngine.resolveMethod( + builder.originalParameters.getResolvedKnnMethodContext(), + builder.knnMethodConfigContext, + false, + resolvedSpaceType + ); + + // The original parameters stores both the resolveMethodContext as well as the original provided by the + // user. Now that we have resolved, we need to update this in the original parameters. + builder.originalParameters.setResolvedKnnMethodContext(resolvedMethodContext.getKnnMethodContext()); + builder.knnMethodConfigContext.setCompressionLevel(resolvedMethodContext.getCompressionLevel()); } private boolean isKNNDisabled(Settings settings) { @@ -516,32 +523,18 @@ private boolean isKNNDisabled(Settings settings) { return !isSettingPresent || !KNNSettings.IS_KNN_INDEX_SETTING.get(settings); } - private void setSpaceType( - final KNNMethodContext knnMethodContext, - final VectorDataType vectorDataType, - final String topLevelSpaceType - ) { - // Now KNNMethodContext should never be null. Because only case it could be null is flatMapper which is - // already handled + private void setSpaceType(final KNNMethodContext knnMethodContext, final SpaceType spaceType) { if (knnMethodContext == null) { - throw new IllegalArgumentException("KNNMethodContext cannot be null"); + return; } - final SpaceType topLevelSpaceTypeEnum = SpaceType.getSpace(topLevelSpaceType); - // Now set the spaceSpaceType for KNNMethodContext - if (SpaceType.UNDEFINED == knnMethodContext.getSpaceType()) { - // We are handling the case when top level space type is defined but method level spaceType is not - // defined. - if (topLevelSpaceTypeEnum != SpaceType.UNDEFINED) { - knnMethodContext.setSpaceType(topLevelSpaceTypeEnum); - } else { - // If both spaceTypes are undefined then put the default spaceType based on datatype - if (VectorDataType.BINARY == vectorDataType) { - knnMethodContext.setSpaceType(SpaceType.DEFAULT_BINARY); - } else { - knnMethodContext.setSpaceType(SpaceType.DEFAULT); - } - } + knnMethodContext.setSpaceType(spaceType); + } + + private void setEngine(final KNNMethodContext knnMethodContext, KNNEngine knnEngine) { + if (knnMethodContext == null || knnMethodContext.isEngineConfigured()) { + return; } + knnMethodContext.setKnnEngine(knnEngine); } } diff --git a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldType.java b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldType.java index 963688d0c..e684ba4f1 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldType.java +++ b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldType.java @@ -93,9 +93,6 @@ public RescoreContext resolveRescoreContext(RescoreContext userProvidedContext) if (userProvidedContext != null) { return userProvidedContext; } - return ModeBasedResolver.INSTANCE.resolveRescoreContext( - getKnnMappingConfig().getMode(), - getKnnMappingConfig().getCompressionLevel() - ); + return getKnnMappingConfig().getCompressionLevel().getDefaultRescoreContext(getKnnMappingConfig().getMode()); } } diff --git a/src/main/java/org/opensearch/knn/index/mapper/LuceneFieldMapper.java b/src/main/java/org/opensearch/knn/index/mapper/LuceneFieldMapper.java index 3da2745ac..fcf3aa034 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/LuceneFieldMapper.java +++ b/src/main/java/org/opensearch/knn/index/mapper/LuceneFieldMapper.java @@ -63,6 +63,16 @@ public Optional getKnnMethodContext() { public int getDimension() { return knnMethodConfigContext.getDimension(); } + + @Override + public Mode getMode() { + return knnMethodConfigContext.getMode(); + } + + @Override + public CompressionLevel getCompressionLevel() { + return knnMethodConfigContext.getCompressionLevel(); + } } ); @@ -87,24 +97,23 @@ private LuceneFieldMapper( originalMappingParameters ); KNNMappingConfig knnMappingConfig = mappedFieldType.getKnnMappingConfig(); - KNNMethodContext knnMethodContext = knnMappingConfig.getKnnMethodContext() - .orElseThrow(() -> new IllegalArgumentException("KNN method context is missing")); + KNNMethodContext resolvedKnnMethodContext = originalMappingParameters.getResolvedKnnMethodContext(); VectorDataType vectorDataType = mappedFieldType.getVectorDataType(); - final VectorSimilarityFunction vectorSimilarityFunction = knnMethodContext.getSpaceType() + final VectorSimilarityFunction vectorSimilarityFunction = resolvedKnnMethodContext.getSpaceType() .getKnnVectorSimilarityFunction() .getVectorSimilarityFunction(); this.fieldType = vectorDataType.createKnnVectorFieldType(knnMappingConfig.getDimension(), vectorSimilarityFunction); if (this.hasDocValues) { - this.vectorFieldType = buildDocValuesFieldType(knnMethodContext.getKnnEngine()); + this.vectorFieldType = buildDocValuesFieldType(resolvedKnnMethodContext.getKnnEngine()); } else { this.vectorFieldType = null; } - KNNLibraryIndexingContext knnLibraryIndexingContext = knnMethodContext.getKnnEngine() - .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext); + KNNLibraryIndexingContext knnLibraryIndexingContext = resolvedKnnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(resolvedKnnMethodContext, knnMethodConfigContext); this.perDimensionProcessor = knnLibraryIndexingContext.getPerDimensionProcessor(); this.perDimensionValidator = knnLibraryIndexingContext.getPerDimensionValidator(); this.vectorValidator = knnLibraryIndexingContext.getVectorValidator(); diff --git a/src/main/java/org/opensearch/knn/index/mapper/MethodFieldMapper.java b/src/main/java/org/opensearch/knn/index/mapper/MethodFieldMapper.java index 3d11949fe..bf5bc2b51 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/MethodFieldMapper.java +++ b/src/main/java/org/opensearch/knn/index/mapper/MethodFieldMapper.java @@ -74,7 +74,7 @@ public int getDimension() { @Override public Mode getMode() { - return knnMethodConfigContext.getMode(); + return Mode.fromName(originalMappingParameters.getMode()); } @Override @@ -125,19 +125,18 @@ private MethodFieldMapper( originalMappingParameters ); this.useLuceneBasedVectorField = KNNVectorFieldMapperUtil.useLuceneKNNVectorsFormat(indexCreatedVersion); - KNNMappingConfig annConfig = mappedFieldType.getKnnMappingConfig(); - KNNMethodContext knnMethodContext = annConfig.getKnnMethodContext() - .orElseThrow(() -> new IllegalArgumentException("KNN method context cannot be empty")); - KNNEngine knnEngine = knnMethodContext.getKnnEngine(); + KNNMappingConfig knnMappingConfig = mappedFieldType.getKnnMappingConfig(); + KNNMethodContext resolvedKnnMethodContext = originalMappingParameters.getResolvedKnnMethodContext(); + KNNEngine knnEngine = resolvedKnnMethodContext.getKnnEngine(); KNNLibraryIndexingContext knnLibraryIndexingContext = knnEngine.getKNNLibraryIndexingContext( - knnMethodContext, + resolvedKnnMethodContext, knnMethodConfigContext ); QuantizationConfig quantizationConfig = knnLibraryIndexingContext.getQuantizationConfig(); this.fieldType = new FieldType(KNNVectorFieldMapper.Defaults.FIELD_TYPE); - this.fieldType.putAttribute(DIMENSION, String.valueOf(annConfig.getDimension())); - this.fieldType.putAttribute(SPACE_TYPE, knnMethodContext.getSpaceType().getValue()); + this.fieldType.putAttribute(DIMENSION, String.valueOf(knnMappingConfig.getDimension())); + this.fieldType.putAttribute(SPACE_TYPE, resolvedKnnMethodContext.getSpaceType().getValue()); // Conditionally add quantization config if (quantizationConfig != null && quantizationConfig != QuantizationConfig.EMPTY) { this.fieldType.putAttribute(QFRAMEWORK_CONFIG, QuantizationConfigParser.toCsv(quantizationConfig)); @@ -157,8 +156,8 @@ private MethodFieldMapper( if (useLuceneBasedVectorField) { int adjustedDimension = mappedFieldType.vectorDataType == VectorDataType.BINARY - ? annConfig.getDimension() / 8 - : annConfig.getDimension(); + ? knnMappingConfig.getDimension() / 8 + : knnMappingConfig.getDimension(); final VectorEncoding encoding = mappedFieldType.vectorDataType == VectorDataType.FLOAT ? VectorEncoding.FLOAT32 : VectorEncoding.BYTE; diff --git a/src/main/java/org/opensearch/knn/index/mapper/ModeBasedResolver.java b/src/main/java/org/opensearch/knn/index/mapper/ModeBasedResolver.java deleted file mode 100644 index 2a0c8ef46..000000000 --- a/src/main/java/org/opensearch/knn/index/mapper/ModeBasedResolver.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index.mapper; - -import org.opensearch.knn.index.KNNSettings; -import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.engine.KNNEngine; -import org.opensearch.knn.index.engine.KNNMethodContext; -import org.opensearch.knn.index.engine.MethodComponentContext; -import org.opensearch.knn.index.engine.faiss.QFrameBitEncoder; -import org.opensearch.knn.index.query.rescore.RescoreContext; - -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; -import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_CLIP; -import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_FP16; -import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_TYPE; -import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; -import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; -import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST_DEFAULT; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES_DEFAULT; - -/** - * Class contains the logic to make parameter resolutions based on the {@link Mode} and {@link CompressionLevel}. - */ -public final class ModeBasedResolver { - - public static final ModeBasedResolver INSTANCE = new ModeBasedResolver(); - - private static final CompressionLevel DEFAULT_COMPRESSION_FOR_MODE_ON_DISK = CompressionLevel.x32; - private static final CompressionLevel DEFAULT_COMPRESSION_FOR_MODE_IN_MEMORY = CompressionLevel.x1; - public final static Set SUPPORTED_COMPRESSION_LEVELS = Set.of( - CompressionLevel.x1, - CompressionLevel.x2, - CompressionLevel.x8, - CompressionLevel.x16, - CompressionLevel.x32 - ); - - private ModeBasedResolver() {} - - /** - * Based on the provided {@link Mode} and {@link CompressionLevel}, resolve to a {@link KNNMethodContext} - * - * @param mode {@link Mode} - * @param compressionLevel {@link CompressionLevel} - * @param requiresTraining whether config requires trianing - * @return {@link KNNMethodContext} - */ - public KNNMethodContext resolveKNNMethodContext( - Mode mode, - CompressionLevel compressionLevel, - boolean requiresTraining, - SpaceType spaceType - ) { - if (requiresTraining) { - return resolveWithTraining(mode, compressionLevel, spaceType); - } - return resolveWithoutTraining(mode, compressionLevel, spaceType); - } - - private KNNMethodContext resolveWithoutTraining(Mode mode, CompressionLevel compressionLevel, final SpaceType spaceType) { - CompressionLevel resolvedCompressionLevel = resolveCompressionLevel(mode, compressionLevel); - MethodComponentContext encoderContext = resolveEncoder(resolvedCompressionLevel); - - KNNEngine knnEngine = Mode.ON_DISK == mode || encoderContext != null ? KNNEngine.FAISS : KNNEngine.DEFAULT; - - if (encoderContext != null) { - return new KNNMethodContext( - knnEngine, - spaceType, - new MethodComponentContext( - METHOD_HNSW, - Map.of( - METHOD_PARAMETER_M, - KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M, - METHOD_PARAMETER_EF_CONSTRUCTION, - KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION, - METHOD_PARAMETER_EF_SEARCH, - KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, - METHOD_ENCODER_PARAMETER, - encoderContext - ) - ) - ); - } - - if (knnEngine == KNNEngine.FAISS) { - return new KNNMethodContext( - knnEngine, - spaceType, - new MethodComponentContext( - METHOD_HNSW, - Map.of( - METHOD_PARAMETER_M, - KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M, - METHOD_PARAMETER_EF_CONSTRUCTION, - KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION, - METHOD_PARAMETER_EF_SEARCH, - KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH - ) - ) - ); - } - - return new KNNMethodContext( - knnEngine, - spaceType, - new MethodComponentContext( - METHOD_HNSW, - Map.of( - METHOD_PARAMETER_M, - KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M, - METHOD_PARAMETER_EF_CONSTRUCTION, - KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION - ) - ) - ); - } - - private KNNMethodContext resolveWithTraining(Mode mode, CompressionLevel compressionLevel, SpaceType spaceType) { - CompressionLevel resolvedCompressionLevel = resolveCompressionLevel(mode, compressionLevel); - MethodComponentContext encoderContext = resolveEncoder(resolvedCompressionLevel); - if (encoderContext != null) { - return new KNNMethodContext( - KNNEngine.FAISS, - spaceType, - new MethodComponentContext( - METHOD_IVF, - Map.of( - METHOD_PARAMETER_NLIST, - METHOD_PARAMETER_NLIST_DEFAULT, - METHOD_PARAMETER_NPROBES, - METHOD_PARAMETER_NPROBES_DEFAULT, - METHOD_ENCODER_PARAMETER, - encoderContext - ) - ) - ); - } - - return new KNNMethodContext( - KNNEngine.FAISS, - spaceType, - new MethodComponentContext( - METHOD_IVF, - Map.of(METHOD_PARAMETER_NLIST, METHOD_PARAMETER_NLIST_DEFAULT, METHOD_PARAMETER_NPROBES, METHOD_PARAMETER_NPROBES_DEFAULT) - ) - ); - } - - /** - * Resolves the rescore context give the {@link Mode} and {@link CompressionLevel} - * - * @param mode {@link Mode} - * @param compressionLevel {@link CompressionLevel} - * @return {@link RescoreContext} - */ - public RescoreContext resolveRescoreContext(Mode mode, CompressionLevel compressionLevel) { - CompressionLevel resolvedCompressionLevel = resolveCompressionLevel(mode, compressionLevel); - return resolvedCompressionLevel.getDefaultRescoreContext(); - } - - private CompressionLevel resolveCompressionLevel(Mode mode, CompressionLevel compressionLevel) { - if (CompressionLevel.isConfigured(compressionLevel)) { - return compressionLevel; - } - - if (mode == Mode.ON_DISK) { - return DEFAULT_COMPRESSION_FOR_MODE_ON_DISK; - } - - return DEFAULT_COMPRESSION_FOR_MODE_IN_MEMORY; - } - - private MethodComponentContext resolveEncoder(CompressionLevel compressionLevel) { - if (CompressionLevel.isConfigured(compressionLevel) == false) { - throw new IllegalStateException("Compression level needs to be configured"); - } - - if (SUPPORTED_COMPRESSION_LEVELS.contains(compressionLevel) == false) { - throw new IllegalArgumentException( - String.format(Locale.ROOT, "Unsupported compression level: \"[%s]\"", compressionLevel.getName()) - ); - } - - if (compressionLevel == CompressionLevel.x1) { - return null; - } - - if (compressionLevel == CompressionLevel.x2) { - return new MethodComponentContext(ENCODER_SQ, Map.of(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16, FAISS_SQ_CLIP, true)); - } - - return new MethodComponentContext( - QFrameBitEncoder.NAME, - Map.of(QFrameBitEncoder.BITCOUNT_PARAM, compressionLevel.numBitsForFloat32()) - ); - } - -} diff --git a/src/main/java/org/opensearch/knn/index/mapper/OriginalMappingParameters.java b/src/main/java/org/opensearch/knn/index/mapper/OriginalMappingParameters.java index 77bf07a90..340c450ee 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/OriginalMappingParameters.java +++ b/src/main/java/org/opensearch/knn/index/mapper/OriginalMappingParameters.java @@ -37,6 +37,8 @@ public final class OriginalMappingParameters { // (https://github.com/opensearch-project/OpenSearch/blob/2.16.0/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java#L322-L324). // So, what we do is pass in a "resolvedKNNMethodContext" to ensure we track this resolveKnnMethodContext. // A similar approach was taken for https://github.com/opendistro-for-elasticsearch/k-NN/issues/288 + // + // In almost all cases except when dealing with the mapping, the resolved context should be used @Setter private KNNMethodContext resolvedKnnMethodContext; private final String mode; diff --git a/src/main/java/org/opensearch/knn/index/util/IndexUtil.java b/src/main/java/org/opensearch/knn/index/util/IndexUtil.java index daf6aba2b..d9170153e 100644 --- a/src/main/java/org/opensearch/knn/index/util/IndexUtil.java +++ b/src/main/java/org/opensearch/knn/index/util/IndexUtil.java @@ -34,6 +34,7 @@ import java.util.Set; import static org.opensearch.knn.common.KNNConstants.BYTES_PER_KILOBYTES; +import static org.opensearch.knn.common.KNNConstants.ENCODER_FLAT; import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_EF_SEARCH; import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; @@ -172,7 +173,9 @@ public static ValidationException validateKnnField( if (parameters != null && parameters.containsKey(KNNConstants.METHOD_ENCODER_PARAMETER)) { MethodComponentContext encoder = (MethodComponentContext) parameters.get(KNNConstants.METHOD_ENCODER_PARAMETER); - if (encoder != null && VECTOR_DATA_TYPES_NOT_SUPPORTING_ENCODERS.contains(trainRequestVectorDataType)) { + if (encoder != null + && VECTOR_DATA_TYPES_NOT_SUPPORTING_ENCODERS.contains(trainRequestVectorDataType) + && ENCODER_FLAT.equals(encoder.getName()) == false) { exception.addValidationError( String.format( Locale.ROOT, diff --git a/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java b/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java index 0f7e8523b..71f7201de 100644 --- a/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java +++ b/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java @@ -19,6 +19,7 @@ import org.opensearch.knn.index.engine.KNNMethodContext; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.SpaceTypeResolver; import org.opensearch.knn.index.mapper.CompressionLevel; import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.indices.ModelUtil; @@ -132,8 +133,7 @@ private TrainingModelRequest createTransportRequest(RestRequest restRequest) thr } } - // Check that these parameters get set - ensureAtleasOneSet(KNN_METHOD, knnMethodContext, MODE_PARAMETER, mode, COMPRESSION_LEVEL_PARAMETER, compressionLevel); + ensureAtleastOneSet(KNN_METHOD, knnMethodContext, MODE_PARAMETER, mode, COMPRESSION_LEVEL_PARAMETER, compressionLevel); ensureMutualExclusion(KNN_METHOD, knnMethodContext, MODE_PARAMETER, mode); ensureMutualExclusion(KNN_METHOD, knnMethodContext, COMPRESSION_LEVEL_PARAMETER, compressionLevel); @@ -160,11 +160,12 @@ private TrainingModelRequest createTransportRequest(RestRequest restRequest) thr vectorDataType, VectorDataType.FLOAT.getValue() ); - resolveSpaceTypeAndSetInKNNMethodContext(topLevelSpaceType, knnMethodContext); - // if KNNMethodContext was not null then spaceTypes we should fix the space type if it is not set. - if (knnMethodContext == null && topLevelSpaceType == SpaceType.UNDEFINED) { - topLevelSpaceType = SpaceType.DEFAULT; - } + SpaceType resolvedSpaceType = SpaceTypeResolver.INSTANCE.resolveSpaceType( + knnMethodContext, + vectorDataType, + topLevelSpaceType.getValue() + ); + setSpaceType(knnMethodContext, resolvedSpaceType); TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, knnMethodContext, @@ -176,7 +177,7 @@ private TrainingModelRequest createTransportRequest(RestRequest restRequest) thr vectorDataType, Mode.fromName(mode), CompressionLevel.fromName(compressionLevel), - topLevelSpaceType + resolvedSpaceType ); if (maximumVectorCount != DEFAULT_NOT_SET_INT_VALUE) { @@ -217,26 +218,11 @@ private boolean ensureSpaceTypeNotSet(SpaceType spaceType) { return true; } - private void resolveSpaceTypeAndSetInKNNMethodContext(SpaceType topLevelSpaceType, KNNMethodContext knnMethodContext) { - // First check if KNNMethodContext is not null as it can be null - if (knnMethodContext != null) { - // if space type is not provided by user then it will undefined - if (knnMethodContext.getSpaceType() == SpaceType.UNDEFINED) { - // fix the top level spaceType if it is undefined - if (topLevelSpaceType == SpaceType.UNDEFINED) { - topLevelSpaceType = SpaceType.DEFAULT; - } - // set the space type now in KNNMethodContext - knnMethodContext.setSpaceType(topLevelSpaceType); - } else { - // if spaceType is set at 2 places lets ensure that we validate those cases and throw error - if (topLevelSpaceType != SpaceType.UNDEFINED) { - throw new IllegalArgumentException( - "Top Level spaceType and space type in method both are set. Set space type at 1 place." - ); - } - } + private void setSpaceType(KNNMethodContext knnMethodContext, SpaceType resolvedSpaceType) { + if (knnMethodContext == null) { + return; } + knnMethodContext.setSpaceType(resolvedSpaceType); } private void ensureIfSetThenEquals( @@ -263,8 +249,11 @@ private void ensureIfSetThenEquals( } } - private void ensureAtleasOneSet(String fieldNameA, Object valueA, String fieldNameB, Object valueB, String fieldNameC, Object valueC) { + private void ensureAtleastOneSet(String fieldNameA, Object valueA, String fieldNameB, Object valueB, String fieldNameC, Object valueC) { if (valueA == DEFAULT_NOT_SET_OBJECT_VALUE && valueB == DEFAULT_NOT_SET_OBJECT_VALUE && valueC == DEFAULT_NOT_SET_OBJECT_VALUE) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "At least \"[%s]\", \"[%s]\" or \"[%s]\" needs to be set", fieldNameA, fieldNameB, fieldNameC) + ); } } diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelRequest.java index df24baf0e..9906ab490 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelRequest.java @@ -22,10 +22,12 @@ import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.ResolvedMethodContext; import org.opensearch.knn.index.mapper.CompressionLevel; import org.opensearch.knn.index.mapper.Mode; -import org.opensearch.knn.index.mapper.ModeBasedResolver; +import org.opensearch.knn.index.engine.EngineResolver; import org.opensearch.knn.index.util.IndexUtil; import org.opensearch.knn.index.engine.KNNMethodContext; import org.opensearch.knn.index.VectorDataType; @@ -116,6 +118,7 @@ public TrainingModelRequest( this.preferredNodeId = preferredNodeId; this.description = description; this.vectorDataType = vectorDataType; + this.mode = mode; // Set these as defaults initially. If call wants to override them, they can use the setters. this.maximumVectorCount = Integer.MAX_VALUE; // By default, get all vectors in the index @@ -123,10 +126,6 @@ public TrainingModelRequest( // Training data size in kilobytes. By default, this is invalid (it cant have negative kb). It eventually gets // calculated in transit. A user cannot set this value directly. - this.trainingDataSizeInKB = -1; - this.mode = mode; - this.compressionLevel = compressionLevel; - this.knnMethodConfigContext = KNNMethodConfigContext.builder() .vectorDataType(vectorDataType) .dimension(dimension) @@ -135,11 +134,11 @@ public TrainingModelRequest( .mode(mode) .build(); - if (knnMethodContext == null && (Mode.isConfigured(mode) || CompressionLevel.isConfigured(compressionLevel))) { - this.knnMethodContext = ModeBasedResolver.INSTANCE.resolveKNNMethodContext(mode, compressionLevel, true, spaceType); - } else { - this.knnMethodContext = knnMethodContext; - } + KNNEngine knnEngine = EngineResolver.INSTANCE.resolveEngine(knnMethodConfigContext, knnMethodContext, true); + ResolvedMethodContext resolvedMethodContext = knnEngine.resolveMethod(knnMethodContext, knnMethodConfigContext, true, spaceType); + this.knnMethodContext = resolvedMethodContext.getKnnMethodContext(); + this.compressionLevel = resolvedMethodContext.getCompressionLevel(); + this.knnMethodConfigContext.setCompressionLevel(resolvedMethodContext.getCompressionLevel()); } /** diff --git a/src/test/java/org/opensearch/knn/KNNTestCase.java b/src/test/java/org/opensearch/knn/KNNTestCase.java index 6ef7373d2..21b3298be 100644 --- a/src/test/java/org/opensearch/knn/KNNTestCase.java +++ b/src/test/java/org/opensearch/knn/KNNTestCase.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.when; import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; /** * Base class for integration tests for KNN plugin. Contains several methods for testing KNN ES functionality. @@ -106,6 +107,11 @@ public static KNNMethodContext getDefaultKNNMethodContext() { return new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, methodComponentContext); } + public static KNNMethodContext getDefaultKNNMethodContextForModel() { + MethodComponentContext methodComponentContext = new MethodComponentContext(METHOD_IVF, Collections.emptyMap()); + return new KNNMethodContext(KNNEngine.FAISS, SpaceType.DEFAULT, methodComponentContext); + } + public static KNNMethodContext getDefaultByteKNNMethodContext() { MethodComponentContext methodComponentContext = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); return new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, methodComponentContext); diff --git a/src/test/java/org/opensearch/knn/index/engine/AbstractKNNLibraryTests.java b/src/test/java/org/opensearch/knn/index/engine/AbstractKNNLibraryTests.java index ccaeb19a5..95d4b68a5 100644 --- a/src/test/java/org/opensearch/knn/index/engine/AbstractKNNLibraryTests.java +++ b/src/test/java/org/opensearch/knn/index/engine/AbstractKNNLibraryTests.java @@ -168,5 +168,15 @@ public Boolean isInitialized() { public void setInitialized(Boolean isInitialized) { } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + SpaceType spaceType + ) { + return null; + } } } diff --git a/src/test/java/org/opensearch/knn/index/engine/AbstractMethodResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/AbstractMethodResolverTests.java new file mode 100644 index 000000000..f21459246 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/AbstractMethodResolverTests.java @@ -0,0 +1,158 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +public class AbstractMethodResolverTests extends KNNTestCase { + + private final static String ENCODER_NAME = "test"; + private final static CompressionLevel DEFAULT_COMPRESSION = CompressionLevel.x8; + + private final static AbstractMethodResolver TEST_RESOLVER = new AbstractMethodResolver() { + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + SpaceType spaceType + ) { + return null; + } + }; + + private final static Encoder TEST_ENCODER = new Encoder() { + + @Override + public MethodComponent getMethodComponent() { + return MethodComponent.Builder.builder(ENCODER_NAME).build(); + } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext encoderContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + return DEFAULT_COMPRESSION; + } + }; + + private final static Map ENCODER_MAP = Map.of(ENCODER_NAME, TEST_ENCODER); + + public void testResolveCompressionLevelFromMethodContext() { + assertEquals( + CompressionLevel.x1, + TEST_RESOLVER.resolveCompressionLevelFromMethodContext( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, MethodComponentContext.EMPTY), + KNNMethodConfigContext.builder().build(), + ENCODER_MAP + ) + ); + assertEquals( + DEFAULT_COMPRESSION, + TEST_RESOLVER.resolveCompressionLevelFromMethodContext( + new KNNMethodContext( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + new MethodComponentContext( + METHOD_HNSW, + Map.of(METHOD_ENCODER_PARAMETER, new MethodComponentContext(ENCODER_NAME, Map.of())) + ) + ), + KNNMethodConfigContext.builder().build(), + ENCODER_MAP + ) + ); + } + + public void testIsEncoderSpecified() { + assertFalse(TEST_RESOLVER.isEncoderSpecified(null)); + assertFalse( + TEST_RESOLVER.isEncoderSpecified(new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, MethodComponentContext.EMPTY)) + ); + assertFalse( + TEST_RESOLVER.isEncoderSpecified( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, new MethodComponentContext(METHOD_HNSW, Map.of())) + ) + ); + assertTrue( + TEST_RESOLVER.isEncoderSpecified( + new KNNMethodContext( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + new MethodComponentContext(METHOD_HNSW, Map.of(METHOD_ENCODER_PARAMETER, "test")) + ) + ) + ); + } + + public void testShouldEncoderBeResolved() { + assertFalse( + TEST_RESOLVER.shouldEncoderBeResolved( + new KNNMethodContext( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + new MethodComponentContext(METHOD_HNSW, Map.of(METHOD_ENCODER_PARAMETER, "test")) + ), + KNNMethodConfigContext.builder().build() + ) + ); + assertFalse( + TEST_RESOLVER.shouldEncoderBeResolved(null, KNNMethodConfigContext.builder().compressionLevel(CompressionLevel.x1).build()) + ); + assertFalse( + TEST_RESOLVER.shouldEncoderBeResolved( + null, + KNNMethodConfigContext.builder().compressionLevel(CompressionLevel.x1).mode(Mode.ON_DISK).build() + ) + ); + assertFalse( + TEST_RESOLVER.shouldEncoderBeResolved( + null, + KNNMethodConfigContext.builder().compressionLevel(CompressionLevel.NOT_CONFIGURED).mode(Mode.IN_MEMORY).build() + ) + ); + assertFalse( + TEST_RESOLVER.shouldEncoderBeResolved( + null, + KNNMethodConfigContext.builder() + .compressionLevel(CompressionLevel.NOT_CONFIGURED) + .mode(Mode.ON_DISK) + .vectorDataType(VectorDataType.BINARY) + .build() + ) + ); + assertTrue( + TEST_RESOLVER.shouldEncoderBeResolved( + null, + KNNMethodConfigContext.builder() + .compressionLevel(CompressionLevel.NOT_CONFIGURED) + .mode(Mode.ON_DISK) + .vectorDataType(VectorDataType.FLOAT) + .build() + ) + ); + assertTrue( + TEST_RESOLVER.shouldEncoderBeResolved( + null, + KNNMethodConfigContext.builder() + .compressionLevel(CompressionLevel.x32) + .mode(Mode.ON_DISK) + .vectorDataType(VectorDataType.FLOAT) + .build() + ) + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/EngineResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/EngineResolverTests.java new file mode 100644 index 000000000..df195883a --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/EngineResolverTests.java @@ -0,0 +1,152 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +public class EngineResolverTests extends KNNTestCase { + + private static final EngineResolver ENGINE_RESOLVER = EngineResolver.INSTANCE; + + public void testResolveEngine_whenEngineSpecifiedInMethod_thenThatEngine() { + assertEquals( + KNNEngine.LUCENE, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().build(), + new KNNMethodContext(KNNEngine.LUCENE, SpaceType.DEFAULT, MethodComponentContext.EMPTY), + false + ) + ); + } + + public void testResolveEngine_whenRequiresTraining_thenFaiss() { + assertEquals(KNNEngine.FAISS, ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().build(), null, true)); + } + + public void testResolveEngine_whenModeAndCompressionAreFalse_thenDefault() { + assertEquals(KNNEngine.DEFAULT, ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().build(), null, false)); + assertEquals( + KNNEngine.DEFAULT, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().build(), + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY, false), + false + ) + ); + } + + public void testResolveEngine_whenModeSpecifiedAndCompressionIsNotSpecified_thenDefault() { + assertEquals(KNNEngine.DEFAULT, ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().build(), null, false)); + assertEquals( + KNNEngine.DEFAULT, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.IN_MEMORY).build(), + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY, false), + false + ) + ); + } + + public void testResolveEngine_whenCompressionIs1x_thenEngineBasedOnMode() { + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x1).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.DEFAULT, + ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().compressionLevel(CompressionLevel.x1).build(), null, false) + ); + } + + public void testResolveEngine_whenCompressionIs4x_thenEngineIsLucene() { + assertEquals( + KNNEngine.LUCENE, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x4).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.LUCENE, + ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().compressionLevel(CompressionLevel.x4).build(), null, false) + ); + } + + public void testResolveEngine_whenConfiguredForBQ_thenEngineIsFaiss() { + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x2).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.IN_MEMORY).compressionLevel(CompressionLevel.x2).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x8).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.IN_MEMORY).compressionLevel(CompressionLevel.x8).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x16).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.IN_MEMORY).compressionLevel(CompressionLevel.x16).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x32).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.IN_MEMORY).compressionLevel(CompressionLevel.x32).build(), + null, + false + ) + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/NativeLibraryTests.java b/src/test/java/org/opensearch/knn/index/engine/NativeLibraryTests.java index 243e9a3c1..c1fbe4aa5 100644 --- a/src/test/java/org/opensearch/knn/index/engine/NativeLibraryTests.java +++ b/src/test/java/org/opensearch/knn/index/engine/NativeLibraryTests.java @@ -73,5 +73,15 @@ public Float distanceToRadialThreshold(Float distance, SpaceType spaceType) { public Float scoreToRadialThreshold(Float score, SpaceType spaceType) { return 0.0f; } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + SpaceType spaceType + ) { + return null; + } } } diff --git a/src/test/java/org/opensearch/knn/index/engine/SpaceTypeResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/SpaceTypeResolverTests.java new file mode 100644 index 000000000..99fc98c9e --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/SpaceTypeResolverTests.java @@ -0,0 +1,99 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import lombok.SneakyThrows; +import org.opensearch.index.mapper.MapperParsingException; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; + +public class SpaceTypeResolverTests extends KNNTestCase { + + private static final SpaceTypeResolver SPACE_TYPE_RESOLVER = SpaceTypeResolver.INSTANCE; + + public void testResolveSpaceType_whenNoConfigProvided_thenFallbackToVectorDataType() { + assertEquals(SpaceType.DEFAULT, SPACE_TYPE_RESOLVER.resolveSpaceType(null, VectorDataType.FLOAT, "")); + assertEquals(SpaceType.DEFAULT, SPACE_TYPE_RESOLVER.resolveSpaceType(null, VectorDataType.BYTE, "")); + assertEquals( + SpaceType.DEFAULT, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + "" + ) + ); + assertEquals(SpaceType.DEFAULT_BINARY, SPACE_TYPE_RESOLVER.resolveSpaceType(null, VectorDataType.BINARY, "")); + assertEquals( + SpaceType.DEFAULT_BINARY, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY), + VectorDataType.BINARY, + "" + ) + ); + } + + @SneakyThrows + public void testResolveSpaceType_whenMethodSpaceTypeAndTopLevelSpecified_thenThrowIfConflict() { + expectThrows( + MapperParsingException.class, + () -> SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.L2, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + SpaceType.INNER_PRODUCT.getValue() + ) + ); + assertEquals( + SpaceType.DEFAULT, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + SpaceType.DEFAULT.getValue() + ) + ); + assertEquals( + SpaceType.DEFAULT, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + SpaceType.UNDEFINED.getValue() + ) + ); + assertEquals( + SpaceType.DEFAULT, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + SpaceType.DEFAULT.getValue() + ) + ); + assertEquals( + SpaceType.DEFAULT, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + SpaceType.UNDEFINED.getValue() + ) + ); + } + + @SneakyThrows + public void testResolveSpaceType_whenSpaceTypeSpecifiedOnce_thenReturnValue() { + assertEquals( + SpaceType.L1, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.L1, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + "" + ) + ); + assertEquals( + SpaceType.INNER_PRODUCT, + SPACE_TYPE_RESOLVER.resolveSpaceType(null, VectorDataType.FLOAT, SpaceType.INNER_PRODUCT.getValue()) + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoderTests.java new file mode 100644 index 000000000..3f7dd9dcd --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoderTests.java @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.mapper.CompressionLevel; + +public class FaissHNSWPQEncoderTests extends KNNTestCase { + public void testCalculateCompressionLevel() { + FaissHNSWPQEncoder encoder = new FaissHNSWPQEncoder(); + assertEquals(CompressionLevel.NOT_CONFIGURED, encoder.calculateCompressionLevel(null, null)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoderTests.java new file mode 100644 index 000000000..35b7a64ab --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoderTests.java @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.mapper.CompressionLevel; + +public class FaissIVFPQEncoderTests extends KNNTestCase { + public void testCalculateCompressionLevel() { + FaissIVFPQEncoder encoder = new FaissIVFPQEncoder(); + assertEquals(CompressionLevel.NOT_CONFIGURED, encoder.calculateCompressionLevel(null, null)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolverTests.java new file mode 100644 index 000000000..ad466d4bb --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolverTests.java @@ -0,0 +1,246 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.Version; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.MethodResolver; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_FLAT; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +public class FaissMethodResolverTests extends KNNTestCase { + + MethodResolver TEST_RESOLVER = new FaissMethodResolver(); + + public void testResolveMethod_whenValid_thenResolve() { + ResolvedMethodContext resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x1, SpaceType.INNER_PRODUCT, ENCODER_FLAT, false); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x32, SpaceType.INNER_PRODUCT, QFrameBitEncoder.NAME, true); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .compressionLevel(CompressionLevel.x16) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x16, SpaceType.INNER_PRODUCT, QFrameBitEncoder.NAME, true); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .compressionLevel(CompressionLevel.x16) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x16, SpaceType.INNER_PRODUCT, QFrameBitEncoder.NAME, true); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + new KNNMethodContext( + KNNEngine.FAISS, + SpaceType.L2, + new MethodComponentContext( + METHOD_HNSW, + Map.of( + METHOD_ENCODER_PARAMETER, + new MethodComponentContext( + QFrameBitEncoder.NAME, + Map.of(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x8.numBitsForFloat32()) + ) + ) + ) + ), + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x8, SpaceType.L2, QFrameBitEncoder.NAME, true); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + new KNNMethodContext( + KNNEngine.FAISS, + SpaceType.L2, + new MethodComponentContext( + METHOD_HNSW, + Map.of( + METHOD_ENCODER_PARAMETER, + new MethodComponentContext( + QFrameBitEncoder.NAME, + Map.of(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x8.numBitsForFloat32()) + ) + ) + ) + ), + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.L2 + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x8, SpaceType.L2, QFrameBitEncoder.NAME, true); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, new MethodComponentContext(METHOD_HNSW, Map.of())), + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.L2 + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x1, SpaceType.L2, ENCODER_FLAT, false); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, new MethodComponentContext(METHOD_HNSW, Map.of())), + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.BINARY).versionCreated(Version.CURRENT).build(), + false, + SpaceType.L2 + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x1, SpaceType.L2, ENCODER_FLAT, false); + } + + private void validateResolveMethodContext( + ResolvedMethodContext resolvedMethodContext, + CompressionLevel expectedCompression, + SpaceType expectedSpaceType, + String expectedEncoderName, + boolean checkBitsEncoderParam + ) { + assertEquals(expectedCompression, resolvedMethodContext.getCompressionLevel()); + assertEquals(KNNEngine.FAISS, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(expectedSpaceType, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals( + expectedEncoderName, + ((MethodComponentContext) resolvedMethodContext.getKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .get(METHOD_ENCODER_PARAMETER)).getName() + ); + if (checkBitsEncoderParam) { + assertEquals( + expectedCompression.numBitsForFloat32(), + ((MethodComponentContext) resolvedMethodContext.getKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .get(METHOD_ENCODER_PARAMETER)).getParameters().get(QFrameBitEncoder.BITCOUNT_PARAM) + ); + } + + } + + public void testResolveMethod_whenInvalid_thenThrow() { + // Invalid compression + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .compressionLevel(CompressionLevel.x4) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.BINARY) + .compressionLevel(CompressionLevel.x4) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + + // Invalid spec ondisk and compression is 1 + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .compressionLevel(CompressionLevel.x1) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + + // Invalid compression conflict + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + new KNNMethodContext( + KNNEngine.FAISS, + SpaceType.INNER_PRODUCT, + new MethodComponentContext( + METHOD_HNSW, + Map.of( + METHOD_ENCODER_PARAMETER, + new MethodComponentContext( + QFrameBitEncoder.NAME, + Map.of(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x32.numBitsForFloat32()) + ) + ) + ) + ), + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .compressionLevel(CompressionLevel.x8) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.INNER_PRODUCT + ) + + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoderTests.java new file mode 100644 index 000000000..3905158a2 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoderTests.java @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.mapper.CompressionLevel; + +public class FaissSQEncoderTests extends KNNTestCase { + public void testCalculateCompressionLevel() { + FaissSQEncoder encoder = new FaissSQEncoder(); + assertEquals(CompressionLevel.x2, encoder.calculateCompressionLevel(null, null)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoderTests.java index 7457b49aa..e926916af 100644 --- a/src/test/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoderTests.java +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoderTests.java @@ -7,6 +7,7 @@ import com.google.common.collect.ImmutableMap; import org.opensearch.Version; +import org.opensearch.common.ValidationException; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.KNNLibraryIndexingContext; @@ -14,10 +15,16 @@ import org.opensearch.knn.index.engine.MethodComponent; import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.mapper.CompressionLevel; import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import java.util.HashMap; +import java.util.Map; + import static org.opensearch.knn.common.KNNConstants.FAISS_FLAT_DESCRIPTION; import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; import static org.opensearch.knn.index.engine.faiss.QFrameBitEncoder.BITCOUNT_PARAM; public class QFrameBitEncoderTests extends KNNTestCase { @@ -121,4 +128,40 @@ public void testEstimateOverheadInKB() { .estimateOverheadInKB(new MethodComponentContext(QFrameBitEncoder.NAME, ImmutableMap.of(BITCOUNT_PARAM, 4)), 8) ); } + + public void testCalculateCompressionLevel() { + QFrameBitEncoder encoder = new QFrameBitEncoder(); + assertEquals( + CompressionLevel.x32, + encoder.calculateCompressionLevel(generateMethodComponentContext(CompressionLevel.x32.numBitsForFloat32()), null) + ); + assertEquals( + CompressionLevel.x16, + encoder.calculateCompressionLevel(generateMethodComponentContext(CompressionLevel.x16.numBitsForFloat32()), null) + ); + assertEquals( + CompressionLevel.x8, + encoder.calculateCompressionLevel(generateMethodComponentContext(CompressionLevel.x8.numBitsForFloat32()), null) + ); + assertEquals( + CompressionLevel.NOT_CONFIGURED, + encoder.calculateCompressionLevel( + new MethodComponentContext( + METHOD_HNSW, + new HashMap<>(Map.of(METHOD_ENCODER_PARAMETER, new MethodComponentContext(QFrameBitEncoder.NAME, Map.of()))) + ), + null + ) + ); + + expectThrows( + ValidationException.class, + () -> encoder.calculateCompressionLevel(generateMethodComponentContext(CompressionLevel.x4.numBitsForFloat32()), null) + ); + expectThrows(ValidationException.class, () -> encoder.calculateCompressionLevel(generateMethodComponentContext(-1), null)); + } + + private MethodComponentContext generateMethodComponentContext(int bitCount) { + return new MethodComponentContext(QFrameBitEncoder.NAME, Map.of(BITCOUNT_PARAM, bitCount)); + } } diff --git a/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolverTests.java new file mode 100644 index 000000000..833d83135 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolverTests.java @@ -0,0 +1,212 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.lucene; + +import org.opensearch.Version; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.MethodResolver; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +public class LuceneMethodResolverTests extends KNNTestCase { + MethodResolver TEST_RESOLVER = new LuceneMethodResolver(); + + public void testResolveMethod_whenValid_thenResolve() { + ResolvedMethodContext resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x1, resolvedMethodContext.getCompressionLevel()); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .mode(Mode.ON_DISK) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertTrue( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x4, resolvedMethodContext.getCompressionLevel()); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .compressionLevel(CompressionLevel.x4) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertTrue( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x4, resolvedMethodContext.getCompressionLevel()); + + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.LUCENE, + SpaceType.INNER_PRODUCT, + new MethodComponentContext(METHOD_HNSW, Map.of()) + ); + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + knnMethodContext, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .mode(Mode.ON_DISK) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertTrue( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x4, resolvedMethodContext.getCompressionLevel()); + assertNotEquals(knnMethodContext, resolvedMethodContext.getKnnMethodContext()); + + knnMethodContext = new KNNMethodContext( + KNNEngine.LUCENE, + SpaceType.INNER_PRODUCT, + new MethodComponentContext(METHOD_HNSW, Map.of()) + ); + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + knnMethodContext, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .compressionLevel(CompressionLevel.x4) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertTrue( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x4, resolvedMethodContext.getCompressionLevel()); + assertNotEquals(knnMethodContext, resolvedMethodContext.getKnnMethodContext()); + + knnMethodContext = new KNNMethodContext( + KNNEngine.LUCENE, + SpaceType.INNER_PRODUCT, + new MethodComponentContext(METHOD_HNSW, Map.of(METHOD_ENCODER_PARAMETER, new MethodComponentContext(ENCODER_SQ, Map.of()))) + ); + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + knnMethodContext, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertTrue( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x4, resolvedMethodContext.getCompressionLevel()); + assertNotEquals(knnMethodContext, resolvedMethodContext.getKnnMethodContext()); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.BYTE).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertFalse( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x1, resolvedMethodContext.getCompressionLevel()); + } + + public void testResolveMethod_whenInvalid_thenThrow() { + // Invalid training context + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + true, + SpaceType.L2 + ) + ); + + // Invalid compression + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .compressionLevel(CompressionLevel.x32) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + + // Invalid spec ondisk and compression is 1 + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .compressionLevel(CompressionLevel.x1) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoderTests.java new file mode 100644 index 000000000..139f96e8b --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoderTests.java @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.lucene; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.mapper.CompressionLevel; + +public class LuceneSQEncoderTests extends KNNTestCase { + public void testCalculateCompressionLevel() { + LuceneSQEncoder encoder = new LuceneSQEncoder(); + assertEquals(CompressionLevel.x4, encoder.calculateCompressionLevel(null, null)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolverTests.java new file mode 100644 index 000000000..065e0e378 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolverTests.java @@ -0,0 +1,106 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.nmslib; + +import org.opensearch.Version; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.MethodResolver; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +public class NmslibMethodResolverTests extends KNNTestCase { + + MethodResolver TEST_RESOLVER = new NmslibMethodResolver(); + + public void testResolveMethod_whenValid_thenResolve() { + // No configuration passed in + ResolvedMethodContext resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertEquals(KNNEngine.NMSLIB, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x1, resolvedMethodContext.getCompressionLevel()); + + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.NMSLIB, + SpaceType.INNER_PRODUCT, + new MethodComponentContext(METHOD_HNSW, Map.of()) + ); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + knnMethodContext, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertEquals(KNNEngine.NMSLIB, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x1, resolvedMethodContext.getCompressionLevel()); + assertNotEquals(knnMethodContext, resolvedMethodContext.getKnnMethodContext()); + } + + public void testResolveMethod_whenInvalid_thenThrow() { + // Invalid training context + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + true, + SpaceType.L2 + ) + ); + + // Invalid compression + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .compressionLevel(CompressionLevel.x8) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + + // Invalid mode + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java b/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java index 84cbf05dc..98bbf42ca 100644 --- a/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java +++ b/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java @@ -48,6 +48,7 @@ import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.KNNMethodContext; import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.faiss.QFrameBitEncoder; import org.opensearch.knn.indices.ModelDao; import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.knn.indices.ModelState; @@ -93,6 +94,7 @@ @Log4j2 public class KNNVectorFieldMapperTests extends KNNTestCase { + private static final String TEST_INDEX_NAME = "test-index-name"; private static final String TEST_FIELD_NAME = "test-field-name"; private static final int TEST_DIMENSION = 17; @@ -1633,7 +1635,7 @@ public void testTypeParser_whenBinaryWithLegacyKNNEnabled_thenException() throws typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder), buildParserContext(indexName, settings)); }); - assertTrue(ex.getMessage(), ex.getMessage().contains("is not supported with")); + assertTrue(ex.getMessage(), ex.getMessage().contains("does not support space type")); } public void testBuild_whenInvalidCharsInFieldName_thenThrowException() { @@ -1653,6 +1655,345 @@ public void testBuild_whenInvalidCharsInFieldName_thenThrowException() { } } + public void testTypeParser_whenModeAndCompressionAreSet_thenHandle() throws IOException { + int dimension = 16; + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + // Default to nmslib and ensure legacy is in use + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .endObject(); + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + assertNull(builder.getOriginalParameters().getKnnMethodContext()); + assertTrue(builder.getOriginalParameters().isLegacyMapping()); + validateBuilderAfterParsing( + builder, + KNNEngine.NMSLIB, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x1, + CompressionLevel.NOT_CONFIGURED, + Mode.NOT_CONFIGURED, + false + ); + + // If mode is in memory and 1x compression, again use default legacy + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x1.getName()) + .field(MODE_PARAMETER, Mode.IN_MEMORY.getName()) + .endObject(); + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + assertNull(builder.getOriginalParameters().getKnnMethodContext()); + assertFalse(builder.getOriginalParameters().isLegacyMapping()); + validateBuilderAfterParsing( + builder, + KNNEngine.NMSLIB, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x1, + CompressionLevel.x1, + Mode.IN_MEMORY, + false + ); + + // Default on disk is faiss with 32x binary quant + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.FAISS, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x32, + CompressionLevel.NOT_CONFIGURED, + Mode.ON_DISK, + true + ); + + // Ensure 2x does not use binary quantization + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x2.getName()) + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.FAISS, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x2, + CompressionLevel.x2, + Mode.NOT_CONFIGURED, + false + ); + + // For 8x ensure that it does use binary quantization + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x8.getName()) + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.FAISS, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x8, + CompressionLevel.x8, + Mode.ON_DISK, + true + ); + + // For 4x compression on disk, use Lucene + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x4.getName()) + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.LUCENE, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x4, + CompressionLevel.x4, + Mode.ON_DISK, + false + ); + + // For 4x compression in memory, use Lucene + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(MODE_PARAMETER, Mode.IN_MEMORY.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x4.getName()) + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.LUCENE, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x4, + CompressionLevel.x4, + Mode.IN_MEMORY, + false + ); + + // For override, ensure compression is correct + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, KNNEngine.FAISS) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, QFrameBitEncoder.NAME) + .startObject(PARAMETERS) + .field(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x16.numBitsForFloat32()) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.FAISS, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x16, + CompressionLevel.NOT_CONFIGURED, + Mode.NOT_CONFIGURED, + true + ); + + // Override with conflicting compression levels should fail + XContentBuilder invalidXContentBuilder1 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x4.getName()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, KNNEngine.FAISS) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, QFrameBitEncoder.NAME) + .startObject(PARAMETERS) + .field(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x16.numBitsForFloat32()) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + expectThrows( + ValidationException.class, + () -> typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(invalidXContentBuilder1), + buildParserContext(TEST_INDEX_NAME, settings) + ) + ); + + // Invalid if vector data type is binary + XContentBuilder invalidXContentBuilder2 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.BINARY.getValue()) + .field(MODE_PARAMETER, Mode.IN_MEMORY.getName()) + .endObject(); + + expectThrows( + MapperParsingException.class, + () -> typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(invalidXContentBuilder2), + buildParserContext(TEST_INDEX_NAME, settings) + ) + ); + + // Invalid if engine doesnt support the compression + XContentBuilder invalidXContentBuilder3 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x4.getName()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, KNNEngine.FAISS) + .endObject() + .endObject(); + + expectThrows( + ValidationException.class, + () -> typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(invalidXContentBuilder3), + buildParserContext(TEST_INDEX_NAME, settings) + ) + ); + } + + private void validateBuilderAfterParsing( + KNNVectorFieldMapper.Builder builder, + KNNEngine expectedEngine, + SpaceType expectedSpaceType, + VectorDataType expectedVectorDataType, + CompressionLevel expectedResolvedCompressionLevel, + CompressionLevel expectedOriginalCompressionLevel, + Mode expectedMode, + boolean shouldUsesBinaryQFramework + ) { + assertEquals(expectedEngine, builder.getOriginalParameters().getResolvedKnnMethodContext().getKnnEngine()); + assertEquals(expectedSpaceType, builder.getOriginalParameters().getResolvedKnnMethodContext().getSpaceType()); + assertEquals(expectedVectorDataType, builder.getKnnMethodConfigContext().getVectorDataType()); + + assertEquals(expectedResolvedCompressionLevel, builder.getKnnMethodConfigContext().getCompressionLevel()); + assertEquals(expectedOriginalCompressionLevel, CompressionLevel.fromName(builder.getOriginalParameters().getCompressionLevel())); + assertEquals(expectedMode, Mode.fromName(builder.getOriginalParameters().getMode())); + assertEquals(expectedMode, builder.getKnnMethodConfigContext().getMode()); + assertFalse(builder.getOriginalParameters().getResolvedKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + + if (shouldUsesBinaryQFramework) { + assertEquals( + QFrameBitEncoder.NAME, + ((MethodComponentContext) builder.getOriginalParameters() + .getResolvedKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .get(METHOD_ENCODER_PARAMETER)).getName() + ); + assertEquals( + expectedResolvedCompressionLevel.numBitsForFloat32(), + (int) ((MethodComponentContext) builder.getOriginalParameters() + .getResolvedKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .get(METHOD_ENCODER_PARAMETER)).getParameters().get(QFrameBitEncoder.BITCOUNT_PARAM) + ); + } else { + assertTrue( + builder.getOriginalParameters().getResolvedKnnMethodContext().getMethodComponentContext().getParameters().isEmpty() + || builder.getOriginalParameters() + .getResolvedKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .containsKey(METHOD_ENCODER_PARAMETER) == false + || QFrameBitEncoder.NAME.equals( + ((MethodComponentContext) builder.getOriginalParameters() + .getResolvedKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .get(METHOD_ENCODER_PARAMETER)).getName() + ) == false + ); + } + } + private LuceneFieldMapper.CreateLuceneFieldMapperInput.CreateLuceneFieldMapperInputBuilder createLuceneFieldMapperInputBuilder() { return LuceneFieldMapper.CreateLuceneFieldMapperInput.builder() .name(TEST_FIELD_NAME) diff --git a/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java index 8b4c856c4..16c657ac6 100644 --- a/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java +++ b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java @@ -21,7 +21,6 @@ import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.mapper.CompressionLevel; import org.opensearch.knn.index.mapper.Mode; -import org.opensearch.knn.index.mapper.ModeBasedResolver; import org.opensearch.knn.index.query.parser.RescoreParser; import java.util.List; @@ -30,7 +29,6 @@ import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; -import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; @@ -39,7 +37,6 @@ import static org.opensearch.knn.common.KNNConstants.MODEL_DESCRIPTION; import static org.opensearch.knn.common.KNNConstants.MODE_PARAMETER; import static org.opensearch.knn.common.KNNConstants.NAME; -import static org.opensearch.knn.common.KNNConstants.PARAMETERS; import static org.opensearch.knn.common.KNNConstants.TRAIN_FIELD_PARAMETER; import static org.opensearch.knn.common.KNNConstants.TRAIN_INDEX_PARAMETER; import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; @@ -71,29 +68,16 @@ public class ModeAndCompressionIT extends KNNRestTestCase { 1.0f, 2.0f }; + private static final String[] COMPRESSION_LEVELS = new String[] { + CompressionLevel.x2.getName(), + CompressionLevel.x4.getName(), + CompressionLevel.x8.getName(), + CompressionLevel.x16.getName(), + CompressionLevel.x32.getName() }; + @SneakyThrows public void testIndexCreation_whenInvalid_thenFail() { XContentBuilder builder = XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(FIELD_NAME) - .field("type", "knn_vector") - .field("dimension", DIMENSION) - .field(MODE_PARAMETER, "on_disk") - .field(COMPRESSION_LEVEL_PARAMETER, "16x") - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .field(KNN_ENGINE, FAISS_NAME) - .startObject(PARAMETERS) - .endObject() - .endObject() - .endObject() - .endObject() - .endObject(); - String mapping1 = builder.toString(); - expectThrows(ResponseException.class, () -> createKnnIndex(INDEX_NAME, mapping1)); - - builder = XContentFactory.jsonBuilder() .startObject() .startObject("properties") .startObject(FIELD_NAME) @@ -139,7 +123,7 @@ public void testIndexCreation_whenInvalid_thenFail() { @SneakyThrows public void testIndexCreation_whenValid_ThenSucceed() { XContentBuilder builder; - for (CompressionLevel compressionLevel : ModeBasedResolver.SUPPORTED_COMPRESSION_LEVELS) { + for (String compressionLevel : COMPRESSION_LEVELS) { String indexName = INDEX_NAME + compressionLevel; builder = XContentFactory.jsonBuilder() .startObject() @@ -147,16 +131,23 @@ public void testIndexCreation_whenValid_ThenSucceed() { .startObject(FIELD_NAME) .field("type", "knn_vector") .field("dimension", DIMENSION) - .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel) .endObject() .endObject() .endObject(); String mapping = builder.toString(); validateIndex(indexName, mapping); - validateSearch(indexName, METHOD_PARAMETER_EF_SEARCH, KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH); + logger.info("Compression level {}", compressionLevel); + validateSearch( + indexName, + METHOD_PARAMETER_EF_SEARCH, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, + compressionLevel, + Mode.NOT_CONFIGURED.getName() + ); } - for (CompressionLevel compressionLevel : ModeBasedResolver.SUPPORTED_COMPRESSION_LEVELS) { + for (String compressionLevel : COMPRESSION_LEVELS) { for (String mode : Mode.NAMES_ARRAY) { String indexName = INDEX_NAME + compressionLevel + "_" + mode; builder = XContentFactory.jsonBuilder() @@ -166,13 +157,20 @@ public void testIndexCreation_whenValid_ThenSucceed() { .field("type", "knn_vector") .field("dimension", DIMENSION) .field(MODE_PARAMETER, mode) - .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel) .endObject() .endObject() .endObject(); String mapping = builder.toString(); validateIndex(indexName, mapping); - validateSearch(indexName, METHOD_PARAMETER_EF_SEARCH, KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH); + logger.info("Compression level {}", compressionLevel); + validateSearch( + indexName, + METHOD_PARAMETER_EF_SEARCH, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, + compressionLevel, + mode + ); } } @@ -190,7 +188,14 @@ public void testIndexCreation_whenValid_ThenSucceed() { .endObject(); String mapping = builder.toString(); validateIndex(indexName, mapping); - validateSearch(indexName, METHOD_PARAMETER_EF_SEARCH, KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH); + logger.info("Compression level {}", CompressionLevel.NOT_CONFIGURED.getName()); + validateSearch( + indexName, + METHOD_PARAMETER_EF_SEARCH, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, + CompressionLevel.NOT_CONFIGURED.getName(), + mode + ); } } @@ -252,7 +257,7 @@ public void testTraining_whenInvalid_thenFail() { public void testTraining_whenValid_thenSucceed() { setupTrainingIndex(); XContentBuilder builder; - for (CompressionLevel compressionLevel : ModeBasedResolver.SUPPORTED_COMPRESSION_LEVELS) { + for (String compressionLevel : CompressionLevel.NAMES_ARRAY) { String indexName = INDEX_NAME + compressionLevel; String modelId = indexName; builder = XContentFactory.jsonBuilder() @@ -261,7 +266,7 @@ public void testTraining_whenValid_thenSucceed() { .field(TRAIN_FIELD_PARAMETER, TRAINING_FIELD_NAME) .field(KNNConstants.DIMENSION, DIMENSION) .field(MODEL_DESCRIPTION, "") - .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel) .endObject(); validateTraining(modelId, builder); builder = XContentFactory.jsonBuilder() @@ -275,10 +280,16 @@ public void testTraining_whenValid_thenSucceed() { .endObject(); String mapping = builder.toString(); validateIndex(indexName, mapping); - validateSearch(indexName, METHOD_PARAMETER_NPROBES, METHOD_PARAMETER_NLIST_DEFAULT); + validateSearch( + indexName, + METHOD_PARAMETER_NPROBES, + METHOD_PARAMETER_NLIST_DEFAULT, + compressionLevel, + Mode.NOT_CONFIGURED.getName() + ); } - for (CompressionLevel compressionLevel : ModeBasedResolver.SUPPORTED_COMPRESSION_LEVELS) { + for (String compressionLevel : CompressionLevel.NAMES_ARRAY) { for (String mode : Mode.NAMES_ARRAY) { String indexName = INDEX_NAME + compressionLevel + "_" + mode; String modelId = indexName; @@ -288,7 +299,7 @@ public void testTraining_whenValid_thenSucceed() { .field(TRAIN_FIELD_PARAMETER, TRAINING_FIELD_NAME) .field(KNNConstants.DIMENSION, DIMENSION) .field(MODEL_DESCRIPTION, "") - .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel) .field(MODE_PARAMETER, mode) .endObject(); validateTraining(modelId, builder); @@ -303,7 +314,7 @@ public void testTraining_whenValid_thenSucceed() { .endObject(); String mapping = builder.toString(); validateIndex(indexName, mapping); - validateSearch(indexName, METHOD_PARAMETER_NPROBES, METHOD_PARAMETER_NLIST_DEFAULT); + validateSearch(indexName, METHOD_PARAMETER_NPROBES, METHOD_PARAMETER_NLIST_DEFAULT, compressionLevel, mode); } } @@ -330,7 +341,13 @@ public void testTraining_whenValid_thenSucceed() { .endObject(); String mapping = builder.toString(); validateIndex(indexName, mapping); - validateSearch(indexName, METHOD_PARAMETER_NPROBES, METHOD_PARAMETER_NLIST_DEFAULT); + validateSearch( + indexName, + METHOD_PARAMETER_NPROBES, + METHOD_PARAMETER_NLIST_DEFAULT, + CompressionLevel.NOT_CONFIGURED.getName(), + mode + ); } } @@ -381,7 +398,13 @@ private void validateTraining(String modelId, XContentBuilder builder) { } @SneakyThrows - private void validateSearch(String indexName, String methodParameterName, int methodParameterValue) { + private void validateSearch( + String indexName, + String methodParameterName, + int methodParameterValue, + String compressionLevelString, + String mode + ) { // Basic search Response response = searchKNNIndex( indexName, @@ -436,7 +459,9 @@ private void validateSearch(String indexName, String methodParameterName, int me String exactSearchResponseBody = EntityUtils.toString(exactSearchResponse.getEntity()); List exactSearchKnnResults = parseSearchResponseScore(exactSearchResponseBody, FIELD_NAME); assertEquals(NUM_DOCS, exactSearchKnnResults.size()); - Assert.assertEquals(exactSearchKnnResults, knnResults); + if (CompressionLevel.x4.getName().equals(compressionLevelString) == false && Mode.ON_DISK.getName().equals(mode)) { + Assert.assertEquals(exactSearchKnnResults, knnResults); + } // Search with rescore response = searchKNNIndex( @@ -464,6 +489,8 @@ private void validateSearch(String indexName, String methodParameterName, int me responseBody = EntityUtils.toString(response.getEntity()); knnResults = parseSearchResponseScore(responseBody, FIELD_NAME); assertEquals(K, knnResults.size()); - Assert.assertEquals(exactSearchKnnResults, knnResults); + if (CompressionLevel.x4.getName().equals(compressionLevelString) == false && Mode.ON_DISK.getName().equals(mode)) { + Assert.assertEquals(exactSearchKnnResults, knnResults); + } } } diff --git a/src/test/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplierTests.java b/src/test/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplierTests.java index 4399b3318..5dbd0fc8b 100644 --- a/src/test/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplierTests.java +++ b/src/test/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplierTests.java @@ -18,6 +18,7 @@ import org.opensearch.knn.index.engine.KNNMethodContext; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.engine.KNNLibrary; +import org.opensearch.knn.index.engine.ResolvedMethodContext; import org.opensearch.test.OpenSearchTestCase; public class LibraryInitializedSupplierTests extends OpenSearchTestCase { @@ -105,5 +106,15 @@ public Boolean isInitialized() { public void setInitialized(Boolean isInitialized) { this.initialized = isInitialized; } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + SpaceType spaceType + ) { + return null; + } } } diff --git a/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportActionTests.java b/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportActionTests.java index 151626ef5..30c5d33a1 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportActionTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportActionTests.java @@ -304,7 +304,7 @@ public void testTrainingIndexSize() { // Setup the request TrainingModelRequest trainingModelRequest = new TrainingModelRequest( null, - getDefaultKNNMethodContext(), + getDefaultKNNMethodContextForModel(), dimension, trainingIndexName, "training-field", @@ -353,7 +353,7 @@ public void testTrainIndexSize_whenDataTypeIsBinary() { // Setup the request TrainingModelRequest trainingModelRequest = new TrainingModelRequest( null, - getDefaultKNNMethodContext(), + getDefaultKNNMethodContextForModel(), dimension, trainingIndexName, "training-field", @@ -403,7 +403,7 @@ public void testTrainIndexSize_whenDataTypeIsByte() { // Setup the request TrainingModelRequest trainingModelRequest = new TrainingModelRequest( null, - getDefaultKNNMethodContext(), + getDefaultKNNMethodContextForModel(), dimension, trainingIndexName, "training-field", diff --git a/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java b/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java index 79292fb53..2c423e0ef 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java @@ -44,7 +44,6 @@ import java.util.List; import java.util.Map; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -52,7 +51,7 @@ public class TrainingModelRequestTests extends KNNTestCase { public void testStreams() throws IOException { String modelId = "test-model-id"; - KNNMethodContext knnMethodContext = getDefaultKNNMethodContext(); + KNNMethodContext knnMethodContext = getDefaultKNNMethodContextForModel(); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; @@ -142,7 +141,7 @@ public void testStreams() throws IOException { public void testGetters() { String modelId = "test-model-id"; - KNNMethodContext knnMethodContext = getDefaultKNNMethodContext(); + KNNMethodContext knnMethodContext = getDefaultKNNMethodContextForModel(); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; @@ -170,7 +169,6 @@ public void testGetters() { trainingModelRequest.setTrainingDataSizeInKB(trainingSetSizeInKB); assertEquals(modelId, trainingModelRequest.getModelId()); - assertEquals(knnMethodContext, trainingModelRequest.getKnnMethodContext()); assertEquals(dimension, trainingModelRequest.getDimension()); assertEquals(trainingIndex, trainingModelRequest.getTrainingIndex()); assertEquals(trainingField, trainingModelRequest.getTrainingField()); @@ -187,18 +185,13 @@ public void testValidation_invalid_modelIdAlreadyExists() { // Setup the training request String modelId = "test-model-id"; - KNNEngine knnEngine = mock(KNNEngine.class); - when(knnEngine.validateMethod(any(), any())).thenReturn(null); - when(knnEngine.isTrainingRequired(any())).thenReturn(true); - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.getKnnEngine()).thenReturn(knnEngine); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + getDefaultKNNMethodContextForModel(), dimension, trainingIndex, trainingField, @@ -251,18 +244,13 @@ public void testValidation_blocked_modelId() { // Setup the training request String modelId = "test-model-id"; - KNNEngine knnEngine = mock(KNNEngine.class); - when(knnEngine.validateMethod(any(), any())).thenReturn(null); - when(knnEngine.isTrainingRequired(any())).thenReturn(true); - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.getKnnEngine()).thenReturn(knnEngine); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + getDefaultKNNMethodContextForModel(), dimension, trainingIndex, trainingField, @@ -298,48 +286,26 @@ public void testValidation_invalid_invalidMethodContext() { String modelId = "test-model-id"; // Mock throwing an exception on validation - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - String validationExceptionMessage = "knn method invalid"; - ValidationException validationException = new ValidationException(); - validationException.addValidationError(validationExceptionMessage); - when(knnMethodContext.validate(any())).thenReturn(validationException); - - when(knnMethodContext.isTrainingRequired()).thenReturn(false); - when(knnMethodContext.getMethodComponentContext()).thenReturn(MethodComponentContext.EMPTY); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; - TrainingModelRequest trainingModelRequest = new TrainingModelRequest( - modelId, - knnMethodContext, - dimension, - trainingIndex, - trainingField, - null, - null, - VectorDataType.DEFAULT, - Mode.NOT_CONFIGURED, - CompressionLevel.NOT_CONFIGURED + ValidationException validationException = expectThrows( + ValidationException.class, + () -> new TrainingModelRequest( + modelId, + getDefaultKNNMethodContext(), + dimension, + trainingIndex, + trainingField, + null, + null, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED + ) ); - - // Mock the model dao to return null so that no exception is produced - ModelDao modelDao = mock(ModelDao.class); - when(modelDao.getMetadata(modelId)).thenReturn(null); - - // This cluster service will result in no validation exceptions - ClusterService clusterService = getClusterServiceForValidReturns(trainingIndex, trainingField, dimension); - - // Initialize static components with the mocks - TrainingModelRequest.initialize(modelDao, clusterService); - - // Test that validation produces model already exists error message - ActionRequestValidationException exception = trainingModelRequest.validate(); - assertNotNull(exception); - List validationErrors = exception.validationErrors(); - assertEquals(2, validationErrors.size()); - assertTrue(validationErrors.get(0).contains(validationExceptionMessage)); - assertTrue(validationErrors.get(1).contains("Method does not require training.")); + assertTrue(validationException.getMessage().contains("engine from training context")); } public void testValidation_invalid_trainingIndexDoesNotExist() { From bf11cbb12731f5471f441f92f9360065a6caedd1 Mon Sep 17 00:00:00 2001 From: Heemin Kim Date: Wed, 11 Sep 2024 11:43:22 -0700 Subject: [PATCH 03/59] Use correct type for binary vector in ivf training (#2086) Signed-off-by: Heemin Kim --- jni/src/faiss_wrapper.cpp | 16 ++++++++++------ .../opensearch-knn.release-notes-2.17.0.0.md | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/jni/src/faiss_wrapper.cpp b/jni/src/faiss_wrapper.cpp index ba15c3ce7..227fcb477 100644 --- a/jni/src/faiss_wrapper.cpp +++ b/jni/src/faiss_wrapper.cpp @@ -71,7 +71,7 @@ void SetExtraParameters(knn_jni::JNIUtilInterface * jniUtil, JNIEnv *env, void InternalTrainIndex(faiss::Index * index, faiss::idx_t n, const float* x); // Train a binary index with data provided -void InternalTrainBinaryIndex(faiss::IndexBinary * index, faiss::idx_t n, const float* x); +void InternalTrainBinaryIndex(faiss::IndexBinary * index, faiss::idx_t n, const uint8_t* x); // Converts the int FilterIds to Faiss ids type array. void convertFilterIdsToFaissIdType(const int* filterIds, int filterIdsLength, faiss::idx_t* convertedFilterIds); @@ -286,7 +286,7 @@ void knn_jni::faiss_wrapper::CreateBinaryIndexFromTemplate(knn_jni::JNIUtilInter auto *inputVectors = reinterpret_cast*>(vectorsAddressJ); int dim = (int)dimJ; if (dim % 8 != 0) { - throw std::runtime_error("Dimensions should be multiply of 8"); + throw std::runtime_error("Dimensions should be multiple of 8"); } int numVectors = (int) (inputVectors->size() / (uint64_t) (dim / 8)); int numIds = jniUtil->GetJavaIntArrayLength(env, idsJ); @@ -848,8 +848,12 @@ jbyteArray knn_jni::faiss_wrapper::TrainBinaryIndex(knn_jni::JNIUtilInterface * } // Train index if needed - auto *trainingVectorsPointerCpp = reinterpret_cast*>(trainVectorsPointerJ); - int numVectors = trainingVectorsPointerCpp->size()/(int) dimensionJ; + int dim = (int)dimensionJ; + if (dim % 8 != 0) { + throw std::runtime_error("Dimensions should be multiple of 8"); + } + auto *trainingVectorsPointerCpp = reinterpret_cast*>(trainVectorsPointerJ); + int numVectors = (int) (trainingVectorsPointerCpp->size() / (dim / 8)); if(!indexWriter->is_trained) { InternalTrainBinaryIndex(indexWriter.get(), numVectors, trainingVectorsPointerCpp->data()); } @@ -997,12 +1001,12 @@ void InternalTrainIndex(faiss::Index * index, faiss::idx_t n, const float* x) { } } -void InternalTrainBinaryIndex(faiss::IndexBinary * index, faiss::idx_t n, const float* x) { +void InternalTrainBinaryIndex(faiss::IndexBinary * index, faiss::idx_t n, const uint8_t* x) { if (auto * indexIvf = dynamic_cast(index)) { indexIvf->make_direct_map(); } if (!index->is_trained) { - index->train(n, reinterpret_cast(x)); + index->train(n, x); } } diff --git a/release-notes/opensearch-knn.release-notes-2.17.0.0.md b/release-notes/opensearch-knn.release-notes-2.17.0.0.md index 8b4aa8e95..1e046be9b 100644 --- a/release-notes/opensearch-knn.release-notes-2.17.0.0.md +++ b/release-notes/opensearch-knn.release-notes-2.17.0.0.md @@ -18,6 +18,7 @@ Compatible with OpenSearch 2.17.0 * Fix graph merge stats size calculation [#1844](https://github.com/opensearch-project/k-NN/pull/1844) * Disallow a vector field to have an invalid character for a physical file name. [#1936](https://github.com/opensearch-project/k-NN/pull/1936) * Fix memory overflow caused by cache behavior [#2015](https://github.com/opensearch-project/k-NN/pull/2015) +* Use correct type for binary vector in ivf training [#2086](https://github.com/opensearch-project/k-NN/pull/2086) ### Infrastructure * Parallelize make to reduce build time [#2006] (https://github.com/opensearch-project/k-NN/pull/2006) ### Maintenance From 019bcc3509a6793e93b4801360b0d8e20528259e Mon Sep 17 00:00:00 2001 From: Heemin Kim Date: Wed, 11 Sep 2024 12:54:40 -0700 Subject: [PATCH 04/59] Switch MINGW32 to MINGW64 (#2090) In the CI, we uses MINGW64. With MINGW32, the hamming distance calculation is not correct which result in bad recall for binary vector in window platform. Signed-off-by: Heemin Kim --- .../opensearch-knn.release-notes-2.17.0.0.md | 1 + scripts/windowsScript.ps1 | 24 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/release-notes/opensearch-knn.release-notes-2.17.0.0.md b/release-notes/opensearch-knn.release-notes-2.17.0.0.md index 1e046be9b..d5bf80319 100644 --- a/release-notes/opensearch-knn.release-notes-2.17.0.0.md +++ b/release-notes/opensearch-knn.release-notes-2.17.0.0.md @@ -19,6 +19,7 @@ Compatible with OpenSearch 2.17.0 * Disallow a vector field to have an invalid character for a physical file name. [#1936](https://github.com/opensearch-project/k-NN/pull/1936) * Fix memory overflow caused by cache behavior [#2015](https://github.com/opensearch-project/k-NN/pull/2015) * Use correct type for binary vector in ivf training [#2086](https://github.com/opensearch-project/k-NN/pull/2086) +* Switch MINGW32 to MINGW64 [#2090](https://github.com/opensearch-project/k-NN/pull/2090) ### Infrastructure * Parallelize make to reduce build time [#2006] (https://github.com/opensearch-project/k-NN/pull/2006) ### Maintenance diff --git a/scripts/windowsScript.ps1 b/scripts/windowsScript.ps1 index 2b0d4f799..e9373223a 100644 --- a/scripts/windowsScript.ps1 +++ b/scripts/windowsScript.ps1 @@ -7,14 +7,14 @@ git submodule update --init -- jni/external/nmslib git submodule update --init -- jni/external/faiss # _MSC_VER is a predefined macro which defines the version of Visual Studio Compiler -# As we are using x86_64-w64-mingw32-gcc compiler we need to replace this macro with __MINGW32__ -(Get-Content jni/external/faiss/faiss/impl/index_read.cpp).replace('_MSC_VER', '__MINGW32__') | Set-Content jni/external/faiss/faiss/impl/index_read.cpp -(Get-Content jni/external/faiss/faiss/impl/index_write.cpp).replace('_MSC_VER', '__MINGW32__') | Set-Content jni/external/faiss/faiss/impl/index_write.cpp -(Get-Content jni/external/faiss/faiss/impl/platform_macros.h).replace('_MSC_VER', '__MINGW32__') | Set-Content jni/external/faiss/faiss/impl/platform_macros.h +# As we are using x86_64-w64-mingw32-gcc compiler we need to replace this macro with __MINGW64__ +(Get-Content jni/external/faiss/faiss/impl/index_read.cpp).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/impl/index_read.cpp +(Get-Content jni/external/faiss/faiss/impl/index_write.cpp).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/impl/index_write.cpp +(Get-Content jni/external/faiss/faiss/impl/platform_macros.h).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/impl/platform_macros.h (Get-Content jni/external/faiss/faiss/impl/platform_macros.h).replace('#define __PRETTY_FUNCTION__ __FUNCSIG__', ' ') | Set-Content jni/external/faiss/faiss/impl/platform_macros.h -(Get-Content jni/external/faiss/faiss/utils/utils.cpp).replace('_MSC_VER', '__MINGW32__') | Set-Content jni/external/faiss/faiss/utils/utils.cpp -(Get-Content jni/external/faiss/faiss/utils/prefetch.h).replace('_MSC_VER', '__MINGW32__') | Set-Content jni/external/faiss/faiss/utils/prefetch.h -(Get-Content jni/external/faiss/faiss/invlists/InvertedListsIOHook.cpp).replace('_MSC_VER', '__MINGW32__') | Set-Content jni/external/faiss/faiss/invlists/InvertedListsIOHook.cpp +(Get-Content jni/external/faiss/faiss/utils/utils.cpp).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/utils/utils.cpp +(Get-Content jni/external/faiss/faiss/utils/prefetch.h).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/utils/prefetch.h +(Get-Content jni/external/faiss/faiss/invlists/InvertedListsIOHook.cpp).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/invlists/InvertedListsIOHook.cpp (Get-Content jni/external/faiss/faiss/AutoTune.cpp).replace('__PRETTY_FUNCTION__', 'NULL') | Set-Content jni/external/faiss/faiss/AutoTune.cpp (Get-Content jni/external/faiss/faiss/utils/distances_simd.cpp).replace('FAISS_PRAGMA_IMPRECISE_FUNCTION_BEGIN', ' ') | Set-Content jni/external/faiss/faiss/utils/distances_simd.cpp (Get-Content jni/external/faiss/faiss/utils/distances_simd.cpp).replace('FAISS_PRAGMA_IMPRECISE_FUNCTION_END', ' ') | Set-Content jni/external/faiss/faiss/utils/distances_simd.cpp @@ -23,17 +23,17 @@ git submodule update --init -- jni/external/faiss # is a Unix header and is not available on Windows. So, adding condition to include it if not running on Windows # Replace '#include ' with -# #ifndef __MINGW32__ +# #ifndef __MINGW64__ # #include # #endif -(Get-Content jni/external/faiss/faiss/invlists/OnDiskInvertedLists.cpp).replace('#include ', "#ifndef __MINGW32__`n#include `n#endif") | Set-Content jni/external/faiss/faiss/invlists/OnDiskInvertedLists.cpp -# intrin.h function like __builtin_ctz, __builtin_clzll is not available in MINGW32. So, adding condition to include it if not running on Windows +(Get-Content jni/external/faiss/faiss/invlists/OnDiskInvertedLists.cpp).replace('#include ', "#ifndef __MINGW64__`n#include `n#endif") | Set-Content jni/external/faiss/faiss/invlists/OnDiskInvertedLists.cpp +# intrin.h function like __builtin_ctz, __builtin_clzll is not available in MINGW64. So, adding condition to include it if not running on Windows # Replace '#include ' with -# #ifndef __MINGW32__ +# #ifndef __MINGW64__ # include # and # Closing the above #ifndef with # #define __builtin_popcountl __popcnt64 # #endif -(Get-Content jni/external/faiss/faiss/impl/platform_macros.h).replace('#include ', "#ifndef __MINGW32__`n#include `n") | Set-Content jni/external/faiss/faiss/impl/platform_macros.h +(Get-Content jni/external/faiss/faiss/impl/platform_macros.h).replace('#include ', "#ifndef __MINGW64__`n#include `n") | Set-Content jni/external/faiss/faiss/impl/platform_macros.h (Get-Content jni/external/faiss/faiss/impl/platform_macros.h).replace('#define __builtin_popcountl __popcnt64', "#define __builtin_popcountl __popcnt64`n#endif`n") | Set-Content jni/external/faiss/faiss/impl/platform_macros.h From 0c79147f2ecd6a4c82109594faa8ff92efc52c0d Mon Sep 17 00:00:00 2001 From: Heemin Kim Date: Wed, 11 Sep 2024 14:18:23 -0700 Subject: [PATCH 05/59] Add recall test with small dataset (#2080) Signed-off-by: Heemin Kim --- .../opensearch/knn/integ/BinaryIndexIT.java | 40 +++-- .../org/opensearch/knn/integ/IndexIT.java | 139 ++++++++++++++++++ src/test/resources/data/README.md | 8 + .../data/test_ground_truth_binary_100.csv | 100 +++++++++++++ .../data/test_ground_truth_l2_100.csv | 100 +++++++++++++ .../knn/KNNJsonIndexMappingsBuilder.java | 8 + 6 files changed, 380 insertions(+), 15 deletions(-) create mode 100644 src/test/java/org/opensearch/knn/integ/IndexIT.java create mode 100644 src/test/resources/data/test_ground_truth_binary_100.csv create mode 100644 src/test/resources/data/test_ground_truth_l2_100.csv diff --git a/src/test/java/org/opensearch/knn/integ/BinaryIndexIT.java b/src/test/java/org/opensearch/knn/integ/BinaryIndexIT.java index 731ae93f4..d5b79768d 100644 --- a/src/test/java/org/opensearch/knn/integ/BinaryIndexIT.java +++ b/src/test/java/org/opensearch/knn/integ/BinaryIndexIT.java @@ -23,7 +23,10 @@ import java.io.IOException; import java.net.URL; +import java.util.Arrays; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; @@ -41,9 +44,11 @@ public static void setUpClass() throws IOException { } URL testIndexVectors = BinaryIndexIT.class.getClassLoader().getResource("data/test_vectors_binary_1000x128.json"); URL testQueries = BinaryIndexIT.class.getClassLoader().getResource("data/test_queries_binary_100x128.csv"); + URL groundTruthValues = BinaryIndexIT.class.getClassLoader().getResource("data/test_ground_truth_binary_100.csv"); assert testIndexVectors != null; assert testQueries != null; - testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath()); + assert groundTruthValues != null; + testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath(), groundTruthValues.getPath()); } @After @@ -83,18 +88,19 @@ public void testFaissHnswBinary_whenSmallDataSet_thenCreateIngestQueryWorks() { } @SneakyThrows - public void testFaissHnswBinary_when1000Data_thenCreateIngestQueryWorks() { + public void testFaissHnswBinary_when1000Data_thenRecallIsAboveNinePointZero() { // Create Index createKnnHnswBinaryIndex(KNNEngine.FAISS, INDEX_NAME, FIELD_NAME, 128); ingestTestData(INDEX_NAME, FIELD_NAME); - int k = 10; + int k = 100; for (int i = 0; i < testData.queries.length; i++) { - // Query List knnResults = runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[i], k); - - // Validate - assertEquals(k, knnResults.size()); + float recall = getRecall( + Set.of(Arrays.copyOf(testData.groundTruthValues[i], k)), + knnResults.stream().map(KNNResult::getDocId).collect(Collectors.toSet()) + ); + assertTrue("Recall: " + recall, recall > 0.1); } } @@ -109,6 +115,18 @@ public void testFaissHnswBinary_whenRadialSearch_thenThrowException() { assertTrue(e.getMessage(), e.getMessage().contains("Binary data type does not support radial search")); } + private float getRecall(final Set truth, final Set result) { + // Count the number of relevant documents retrieved + result.retainAll(truth); + int relevantRetrieved = result.size(); + + // Total number of relevant documents + int totalRelevant = truth.size(); + + // Calculate recall + return (float) relevantRetrieved / totalRelevant; + } + private List runRnnQuery( final String indexName, final String fieldName, @@ -171,12 +189,4 @@ private void createKnnHnswBinaryIndex(final KNNEngine knnEngine, final String in createKnnIndex(indexName, knnIndexMapping); } - - private byte[] toByte(final float[] vector) { - byte[] bytes = new byte[vector.length]; - for (int i = 0; i < vector.length; i++) { - bytes[i] = (byte) vector[i]; - } - return bytes; - } } diff --git a/src/test/java/org/opensearch/knn/integ/IndexIT.java b/src/test/java/org/opensearch/knn/integ/IndexIT.java new file mode 100644 index 000000000..02faf6a0f --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/IndexIT.java @@ -0,0 +1,139 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import com.google.common.primitives.Floats; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang.ArrayUtils; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.junit.After; +import org.junit.BeforeClass; +import org.opensearch.client.Response; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNJsonQueryBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.KNNResult; +import org.opensearch.knn.TestUtils; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +/** + * This class contains integration tests for index + */ +@Log4j2 +public class IndexIT extends KNNRestTestCase { + private static TestUtils.TestData testData; + + @BeforeClass + public static void setUpClass() throws IOException { + if (IndexIT.class.getClassLoader() == null) { + throw new IllegalStateException("ClassLoader of IndexIT Class is null"); + } + URL testIndexVectors = IndexIT.class.getClassLoader().getResource("data/test_vectors_1000x128.json"); + URL testQueries = IndexIT.class.getClassLoader().getResource("data/test_queries_100x128.csv"); + URL groundTruthValues = IndexIT.class.getClassLoader().getResource("data/test_ground_truth_l2_100.csv"); + assert testIndexVectors != null; + assert testQueries != null; + assert groundTruthValues != null; + testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath(), groundTruthValues.getPath()); + } + + @After + public void cleanUp() { + try { + deleteKNNIndex(INDEX_NAME); + } catch (Exception e) { + log.error(e); + } + } + + @SneakyThrows + public void testFaissHnsw_when1000Data_thenRecallIsAboveNinePointZero() { + // Create Index + createKnnHnswIndex(KNNEngine.FAISS, INDEX_NAME, FIELD_NAME, 128); + ingestTestData(INDEX_NAME, FIELD_NAME); + + int k = 100; + for (int i = 0; i < testData.queries.length; i++) { + List knnResults = runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[i], k); + float recall = getRecall( + Set.of(Arrays.copyOf(testData.groundTruthValues[i], k)), + knnResults.stream().map(KNNResult::getDocId).collect(Collectors.toSet()) + ); + assertTrue("Recall: " + recall, recall > 0.9); + } + } + + private float getRecall(final Set truth, final Set result) { + // Count the number of relevant documents retrieved + result.retainAll(truth); + int relevantRetrieved = result.size(); + + // Total number of relevant documents + int totalRelevant = truth.size(); + + // Calculate recall + return (float) relevantRetrieved / totalRelevant; + } + + private List runKnnQuery(final String indexName, final String fieldName, final float[] queryVector, final int k) + throws Exception { + String query = KNNJsonQueryBuilder.builder() + .fieldName(fieldName) + .vector(ArrayUtils.toObject(queryVector)) + .k(k) + .build() + .getQueryString(); + Response response = searchKNNIndex(indexName, query, k); + return parseSearchResponse(EntityUtils.toString(response.getEntity()), fieldName); + } + + private void ingestTestData(final String indexName, final String fieldName) throws Exception { + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + fieldName, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + + // Assert we have the right number of documents in the index + refreshAllIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + } + + private void createKnnHnswIndex(final KNNEngine knnEngine, final String indexName, final String fieldName, final int dimension) + throws IOException { + KNNJsonIndexMappingsBuilder.Method method = KNNJsonIndexMappingsBuilder.Method.builder() + .methodName(METHOD_HNSW) + .spaceType(SpaceType.L2.getValue()) + .engine(knnEngine.getName()) + .build(); + + String knnIndexMapping = KNNJsonIndexMappingsBuilder.builder() + .fieldName(fieldName) + .dimension(dimension) + .vectorDataType(VectorDataType.FLOAT.getValue()) + .method(method) + .build() + .getIndexMapping(); + + createKnnIndex(indexName, knnIndexMapping); + } +} diff --git a/src/test/resources/data/README.md b/src/test/resources/data/README.md index f3711eae3..df103870f 100644 --- a/src/test/resources/data/README.md +++ b/src/test/resources/data/README.md @@ -10,6 +10,14 @@ test_queries_100x128.csv and packing 8 bits to 1 byte with ends up with 16 lengt For quantization technique, we calculated the median(49935.95941056451) of all values in test_vectors_1000x128.json and converted it as 0 if it is less than the median and 1 if it is equal to or larger than the median. +# test_ground_truth_binary_100.csv +The file contains the ground truth for the query test_queries_binary_100x128.csv against the data +test_vectors_binary_1000x128.json using hamming distance. + +# test_ground_truth_l2_100.csv +The file contains the ground truth for the query test_queries_100x128.csv against the data test_vectors_1000x128.json +using l2 distance + # test_vectors_nested_1000x128.json The file contains a simulated data to represent nested field. Consecutive ids are assigned for data from same parent document. diff --git a/src/test/resources/data/test_ground_truth_binary_100.csv b/src/test/resources/data/test_ground_truth_binary_100.csv new file mode 100644 index 000000000..bafc11a28 --- /dev/null +++ b/src/test/resources/data/test_ground_truth_binary_100.csv @@ -0,0 +1,100 @@ +76960303, 62740044, 2607799, 66428795, 37435892, 30998561, 60106217, 9575954, 43933006, 76703609, 7687278, 92033260, 94711842, 17081350, 54987042, 62693428, 45075471, 23848565, 49598724, 12571310, 61263068, 63152504, 16971929, 90004325, 6986898, 247198, 68694897, 16387575, 92803766, 41092172, 84840549, 79922758, 74137926, 91990218, 32250045, 34236719, 33061250, 22108665, 75407347, 34698463, 42307449, 16099750, 69786756, 71469330, 79136082, 4099191, 7182642, 57359924, 24314885, 18806856, 56484900, 20002147, 3294781, 8128637, 57803235, 46870723, 99224392, 94076128, 44550764, 2917920, 88904910, 45049308, 26664538, 19376156, 91240048, 60176618, 92787493, 4095116, 94516935, 91727510, 4434662, 68102437, 58749, 66045587, 9860968, 61271144, 36580610, 93515664, 20885148, 53802686, 97011160, 32159704, 6153406, 21993752, 38658347, 35456853, 3773993, 176257, 75135448, 24826575, 30090481, 68000591, 92867155, 29932657, 39171895, 76671482, 72738685, 39847321, 7685448, 86118021, 25636669, 8791066, 39373729, 34044787, 24915585, 79806380, 87720882, 50668599, 83302115, 72357096, 79322415, 67513640, 15536795, 53632373, 99515901, 48774913, 30771409, 99971982, 65851721, 50806615, 97641116, 53888755, 61141874, 19457589, 36753250, 43506672, 71083565, 26493119, 97281847, 8634541, 57241521, 17539625, 84684495, 40865610, 75128745, 73235980, 28796059, 30694952, 8651647, 42947632, 1788101, 51472275, 70036438, 4978543, 9860195, 16595436, 45794415, 1569515, 40027975, 13348726, 77377183, 7423788, 5640302, 68875490, 39801351, 1022677, 37183543, 93933709, 42967683, 3233569, 11543098, 84293052, 80555751, 35192533, 7955293, 83948335, 38365584, 75986488, 29188588, 27187213, 7646095, 73109997, 6808825, 18783702, 50007421, 95957797, 96726697, 16054533, 90654836, 37659250, 33935899, 56153345, 72373496, 73786944, 8733068, 1431742, 40781854, 56424103, 96193415, 74452589, 11581181, 99333375, 31727379, 26744917, 56955985, 37192445, 17385531, 22129328, 4064751, 40534591, 54058788, 94595668, 97057187, 61897582, 22721500, 5111370, 44627776, 39553046, 82339363, 9599614, 93057697, 83210802, 38645117, 72725103, 17764950, 16405341, 83533741, 75543508, 59371804, 44348328, 19939935, 21001913, 27185644, 76315420, 72019362, 3487592, 91141395, 58224549, 93270084, 66667729, 28787861, 508198, 4515343, 55602660, 78602717, 78549759, 98943869, 21919959, 70782102, 36933038, 54014062, 6157724, 15948937, 94539824, 24591705, 65715134, 21070110, 53648154, 40984766, 86460488, 30395570, 15064655, 19486173, 65880522, 76434144, 30569392, 86301513, 98739783, 82886381, 21289531, 37675718, 89217461, 16097038, 30543215, 66271566, 52293995, 8913721, 26063929, 63506281, 13468268, 80014588, 73617245, 84904436, 26507214, 44847298, 36845587, 415901, 85116755, 48658605, 51047803, 70372191, 32426752, 42073124, 12303248, 74724075, 18663507, 51464002, 7066775, 17146629, 33237508, 5970607, 43376279, 41481685, 7517032, 52060076, 40781449, 30811010, 74110882, 99021067, 23194618, 51466049, 12024238, 60393039, 24953498, 64055960, 60955663, 14731700, 66319530, 59501487, 66663942, 34497327, 9603598, 36930650, 77187825, 55669657, 86022504, 24168411, 87710366, 33565483, 17037369, 45381876, 84127901, 50702367, 17386996, 14349098, 82779622, 21397057, 44060493, 71657078, 79821897, 45995325, 16380211, 34432810, 44983451, 2717150, 74743862, 11365791, 82897371, 48893685, 9398733, 321665, 94911072, 62452034, 91281584, 95251277, 32274392, 31904591, 68824981, 32161669, 36139942, 40677414, 90310261, 76540481, 79942022, 97940276, 90272749, 44473167, 47361209, 21533347, 13231279, 26863229, 26998766, 78785507, 49882705, 28928175, 54762643, 7104732, 9623492, 44664587, 36312813, 68702632, 95581843, 53842979, 96735716, 2208785, 96420429, 26114953, 20568363, 85023028, 81274566, 89419466, 14093520, 75229462, 49236559, 36189527, 44915235, 92398073, 77272628, 17365188, 14626618, 62430984, 54663246, 7919588, 81853704, 53547802, 73392814, 22879907, 22942635, 62490109, 38009615, 38061439, 44177011, 37224844, 64602895, 78589145, 64157906, 47213183, 92071990, 63059024, 10649306, 92554120, 38341669, 31161687, 62051033, 17016156, 29959549, 82979980, 6111563, 42199455, 54427233, 874791, 84002370, 46851987, 19101477, 37560164, 77620120, 23110625, 92541302, 6793819, 57163802, 9188443, 98371444, 71300104, 63967300, 33249630, 29029316, 44889423, 2204165, 93562257, 23740167, 55960386, 7011964, 4508700, 58751351, 29834512, 31126490, 77898274, 61859143, 20122224, 28851716, 27625735, 65047700, 50188404, 29994197, 95726235, 47298834, 43152977, 55247455, 6819644, 67474219, 77284619, 30218878, 73021291, 62496012, 5822202, 77072625, 38256849, 68939068, 50567636, 39986008, 45848907, 26292919, 77413012, 36396314, 1250437, 86821229, 72278539, 4985896, 56515456, 72358170, 44846932, 36812683, 57020481, 77300457, 83368048, 79191827, 92530431, 99917599, 82327024, 10366309, 5267545, 69136837, 8115266, 13470059, 83651211, 45407418, 20642888, 9058407, 26102057, 85242963, 35996293, 80316608, 45790169, 45667668, 33431960, 94090109, 13173644, 29510992, 81855445, 58208470, 57961282, 98462867, 88698958, 96852321, 86001008, 83133790, 38510840, 16445503, 90457870, 87160386, 63372756, 54199160, 3880712, 62115552, 3183975, 81805959, 23569917, 75052463, 1936762, 47792865, 55470718, 14326617, 36468541, 22997281, 68128438, 1197320, 20836893, 15015906, 67227442, 3633375, 32699744, 65338021, 89499542, 69605283, 30891921, 99604946, 30787683, 61373987, 83789391, 87598888, 99861373, 93790285, 8729146, 45990383, 52613508, 8129978, 20765474, 75777973, 33123618, 77301523, 569864, 98653983, 17058722, 55189057, 82052050, 51149804, 16019925, 79880247, 2331773, 30463802, 48892201, 91957544, 73124510, 92692283, 89214616, 54868730, 99690194, 59177718, 22766820, 43322743, 77694700, 47708850, 70367851, 83747892, 41245325, 67281495, 56461322, 47090124, 89811711, 38022834, 77787724, 18131876, 16424199, 49641577, 36135, 32058615, 4091162, 69355476, 82532312, 27665211, 1204161, 4069912, 44479073, 72777973, 72238278, 9257405, 78300864, 83083131, 67030811, 11757872, 62762857, 97783876, 83269727, 45996863, 40686254, 96709982, 52261574, 82726008, 76330843, 96318094, 40152546, 73031054, 30163921, 6521313, 90090964, 92353856, 29065964, 65038678, 61960400, 54517921, 93566986, 90158785, 35512853, 51588015, 48673079, 14947650, 33797252, 33336148, 8373289, 19891772, 3233084, 25842078, 17957593, 33304202, 35092039, 17068582, 43357947, 51715482, 93359396, 99658235, 61623915, 10961421, 11923835, 48395186, 88653118, 79657802, 26776922, 42782652, 82427263, 57248122, 91802888, 6038457, 38494874, 91831487, 59981773, 83150534, 26392416, 84406788, 10358899, 9829782, 48675329, 6497830, 67451935, 18466635, 34667286, 27325428, 51590803, 89996536, 59614746, 84166196, 64098930, 53393358, 20486294, 73222868, 82178706, 62232211, 44844121, 15902805, 54263880, 70004753, 55753905, 98648327, 61928316, 44784505, 3235882, 78561158, 42644903, 62803858, 77312810, 33553959, 23134715, 7300047, 60102965, 48088883, 4204661, 53666583, 26275734, 58180653, 40197395, 36685023, 66322959, 65081429, 4798568, 72614359, 62936963, 36505482, 37739481, 61741594, 49597667, 41380093, 34295794, 15535065, 9175338, 18411915, 92604458, 112651, 92998591, 37620363, 51426311, 56473732, 99297164, 41442762, 71316369, 59329511, 91664334, 88047921, 59910624, 81774825, 68316156, 82651278, 4119747, 54232247, 92692978, 49271185, 63015256, 16684829, 68204242, 45919976, 10264691, 31491938, 99524975, 86242799, 4668450, 98948034, 61982238, 70420215, 33201905, 89699445, 46540998, 34946859, 32151165, 168541, 56531125, 526217, 18833224, 43501211, 74614639, 84349107, 59109400, 23432750, 99549373, 22435353, 68041839, 45617087, 97561745, 30139692, 29516592, 69579137, 92215320, 75820087, 8099994, 75153252, 53084256, 34698428, 66611759, 62357986, 8055981, 33895336, 60430369, 68816413, 95010552, 88444207, 26397786, 13862149, 63628376, 22200378, 4199704, 37957788, 15075176, 65454636, 11161731, 72495719, 15163258, 30764367, 69848388, 26734892, 24058273, 70596786, 19898053, 55770687, 68110330, 12416768, 24619760, 76170907, 67031644, 43045786, 60581278, 47887470, 86543538, 94360702, 99729357, 48260151, 55615885, 78909561, 61815223, 29635537, 42692881, 12348118, 71522968, 36808486, 18504197, 99965001, 33699435, 22113133, 27041967, 52204879, 78766358, 85711894, 71920426, 18699206, 91938191, 19272365, 86798033, 65074535, 14363867, 29401781, 85571389, 39201414, 16567550, 1375023, 20645197, 20140249, 57658654, 37166608, 12664567, 57240218, 83155442, 17727650, 29819940, 5832946, 10453030, 6871053, 10597197, 67793644, 66832478, 669105, 91255408, 14220886, 8696647, 65017137, 76825057, 22405120, 26139110, 80251430, 78218845, 3509435, 69641513, 71965942, 72274002, 88251446, 65271999, 45237957, 3088684, 49328608, 7229550, 51315460, 9259676, 66903004, 42237907, 57393458, 37280276, 64848072, 47893286, 749283, 28734791, 17976208, 90061527, 81677380, 38008118, 6525948, 61712234, 78884452, 85695762, 80246713, 8807940, 64087743, 89046466, 20867149, 70727211, 31733363, 88130087, 62552524, 65275241, 73168565, 61728685, 55850790, 72793444, 85117092, 30653863, 32590267, 45428665, 66885828, 44245960, 6505939, 70541760, 68644627, 29466406, 55143724, 40356877, 74441448, 36780454, 89078848, 69697787, 45306070, 9886593, 44481640, 61859581, 4787945, 4806458, 70800879, 27411561, 2891150, 18001617, 90013093, 17894977, 95290172, 59405277, 14723410, 98130363, 17857111, 81898046, 21673260, 79738755, 59197747, 69255765, 66116458, 95395112, 13819358, 89804152, 51507425, 5073754, 81172706, 37501808, 97379790, 824872, 99226875, 62428472, 84187166, 37891451, 67124282, 89637706, 15148031, 66250369, 33628349, 67084351, 30366150, 10309525, 1239555, 71333116, 74357852, 14045383, 54606384, 7502255, 59318837, 66137019, 93053405, 22450468, 20681921, 48360198, 72732205, 41092102, 99125126, 24733232, 28550822, 34493392, 96906410, 47738185, 5482538, 6221471, 44842615 +36930650, 18466635, 82052050, 5267545, 36139942, 98943869, 42644903, 73124510, 41245325, 10597197, 80316608, 84840549, 23569917, 30787683, 1569515, 76434144, 77377183, 77312810, 30543215, 79136082, 18783702, 16684829, 31727379, 56531125, 39553046, 97940276, 17894977, 68702632, 48675329, 44915235, 17976208, 30764367, 73222868, 40027975, 17016156, 3233569, 36685023, 99729357, 874791, 80555751, 78766358, 30811010, 76960303, 56484900, 24953498, 14731700, 26292919, 83155442, 47738185, 40152546, 247198, 50806615, 5111370, 74614639, 45075471, 83368048, 13470059, 17764950, 68041839, 80251430, 13231279, 78785507, 10961421, 28796059, 62115552, 14326617, 61271144, 36933038, 1788101, 32250045, 72495719, 81677380, 14626618, 24591705, 19898053, 81898046, 22942635, 15902805, 38061439, 55753905, 78589145, 64157906, 33123618, 1022677, 89217461, 99690194, 63967300, 93562257, 71316369, 18131876, 74357852, 24915585, 83302115, 79322415, 87710366, 56955985, 46870723, 17727650, 48774913, 94076128, 99971982, 669105, 82897371, 48893685, 9886593, 88904910, 20642888, 45667668, 90272749, 29516592, 3509435, 35092039, 62357986, 33895336, 66045587, 30694952, 83150534, 26397786, 37280276, 6157724, 16595436, 22879907, 62490109, 38009615, 33061250, 59197747, 52613508, 92071990, 77301523, 51149804, 79880247, 55615885, 62936963, 30463802, 98371444, 48658605, 22766820, 2204165, 16424199, 82532312, 65047700, 92033260, 4069912, 85571389, 8733068, 12024238, 60393039, 60955663, 73021291, 9603598, 97783876, 37166608, 89699445, 50567636, 45381876, 45996863, 29819940, 40534591, 45995325, 34432810, 97057187, 61897582, 2717150, 11365791, 2917920, 45306070, 56515456, 19457589, 18833224, 99917599, 93566986, 9599614, 19376156, 92803766, 60176618, 8115266, 48673079, 4434662, 49598724, 47361209, 44348328, 84684495, 3233084, 72019362, 99658235, 40865610, 91141395, 48395186, 54762643, 73235980, 66667729, 95581843, 3183975, 33628349, 20568363, 9860968, 89419466, 75229462, 10358899, 65454636, 53802686, 34236719, 81853704, 67227442, 78884452, 21070110, 35456853, 21673260, 64087743, 24619760, 8729146, 30090481, 98648327, 43045786, 62803858, 75777973, 55850790, 16097038, 7300047, 98653983, 58180653, 85117092, 76703609, 63506281, 13468268, 2331773, 36845587, 78909561, 72738685, 18411915, 7685448, 89214616, 42073124, 12303248, 12348118, 18663507, 71469330, 67281495, 7066775, 41442762, 52204879, 91664334, 96726697, 7517032, 28851716, 92692978, 74110882, 27665211, 24314885, 63015256, 50668599, 23194618, 40356877, 16567550, 37435892, 78300864, 66319530, 3294781, 62496012, 59501487, 34497327, 74452589, 54606384, 62428472, 26744917, 15536795, 36396314, 53632373, 6986898, 65851721, 168541, 4985896, 6521313, 67124282, 89637706, 94911072, 44846932, 43501211, 79191827, 38645117, 76825057, 90158785, 23432750, 92787493, 70800879, 75543508, 59371804, 33336148, 18001617, 30139692, 8634541, 57241521, 13173644, 72274002, 3487592, 63372756, 66611759, 58224549, 45237957, 36312813, 30998561, 96420429, 79922758, 91802888, 78602717, 78549759, 81274566, 68816413, 60106217, 95290172, 95010552, 36580610, 22200378, 51472275, 28734791, 67451935, 20885148, 90061527, 17365188, 22997281, 34493392, 38008118, 64098930, 15015906, 24058273, 73392814, 30891921, 12416768, 30395570, 70004753, 37224844, 64602895, 62552524, 9575954, 44784505, 47213183, 78561158, 10649306, 98739783, 38341669, 73168565, 61263068, 569864, 43933006, 72793444, 76671482, 66271566, 8913721, 48260151, 65081429, 46851987, 32590267, 37560164, 23110625, 29635537, 57163802, 9188443, 16099750, 51047803, 96906410, 54868730, 112651, 66885828, 27187213, 29029316, 6808825, 56473732, 8791066, 4508700, 39373729, 38022834, 2607799, 16054533, 68316156, 52060076, 66428795, 99021067, 68204242, 9257405, 72373496, 51466049, 47298834, 1431742, 20002147, 99226875, 99333375, 84127901, 57803235, 17385531, 99515901, 4064751, 5832946, 34946859, 59318837, 30163921, 90090964, 74743862, 8696647, 61141874, 82339363, 23848565, 40677414, 59109400, 9058407, 83533741, 2891150, 22435353, 93053405, 97561745, 92215320, 69641513, 88698958, 96852321, 86001008, 17957593, 83133790, 76315420, 16445503, 61623915, 9623492, 44664587, 66250369, 42782652, 60430369, 81805959, 26114953, 51315460, 85023028, 21919959, 26392416, 63628376, 4978543, 9860195, 11161731, 92398073, 10309525, 15948937, 77272628, 84166196, 6525948, 14723410, 26734892, 55770687, 69605283, 38658347, 44844121, 40984766, 54263880, 44177011, 61373987, 70727211, 75135448, 65880522, 13348726, 45990383, 69255765, 13819358, 29959549, 33553959, 11543098, 42307449, 84904436, 26507214, 72614359, 36505482, 19101477, 48892201, 415901, 92692283, 15535065, 75986488, 9175338, 74724075, 7646095, 36808486, 63152504, 18504197, 99965001, 99297164, 58751351, 22113133, 71333116, 29466406, 29834512, 43376279, 31126490, 88047921, 85711894, 41481685, 32058615, 77898274, 57359924, 20122224, 90654836, 27625735, 72732205, 37659250, 54232247, 7687278, 72777973, 95726235, 94711842, 1375023, 55247455, 86242799, 67030811, 98948034, 5822202, 96193415, 77072625, 57658654, 77187825, 38256849, 86022504, 36780454, 33201905, 17081350, 82779622, 7502255, 54058788, 32151165, 79821897, 94595668, 73031054, 54987042, 72358170, 44627776, 91281584, 36753250, 45049308, 16387575, 61960400, 32274392, 68824981, 24733232, 82327024, 61859581, 93057697, 10366309, 91240048, 69136837, 4787945, 35512853, 4806458, 16405341, 4095116, 79942022, 94516935, 66137019, 26139110, 33797252, 19891772, 98462867, 25842078, 21001913, 51715482, 93359396, 68102437, 75128745, 65271999, 93270084, 3088684, 54199160, 79657802, 53842979, 82427263, 4515343, 55602660, 6038457, 74137926, 91831487, 59981773, 42947632, 70782102, 88444207, 67084351, 30366150, 54014062, 64848072, 9829782, 749283, 4199704, 34667286, 94539824, 69848388, 1197320, 7919588, 61712234, 3633375, 53393358, 70596786, 85695762, 21993752, 89046466, 87598888, 15064655, 76170907, 12571310, 24826575, 88130087, 7423788, 95395112, 20765474, 61728685, 60581278, 82886381, 93933709, 39171895, 17058722, 52293995, 26063929, 66322959, 54427233, 80014588, 84002370, 44847298, 83948335, 38365584, 70372191, 33249630, 44245960, 81172706, 71522968, 37620363, 83747892, 70541760, 16971929, 49641577, 90004325, 40781449, 71920426, 33935899, 49271185, 72238278, 39201414, 73786944, 45919976, 99524975, 43152977, 64055960, 83083131, 4668450, 77284619, 62762857, 41092102, 11581181, 17037369, 67513640, 39986008, 77413012, 57240218, 82726008, 21397057, 44060493, 30771409, 71657078, 66832478, 44983451, 72278539, 68694897, 9398733, 62452034, 65017137, 36812683, 57020481, 95251277, 92530431, 44481640, 26664538, 84349107, 51588015, 41092172, 22405120, 85242963, 14947650, 91727510, 26493119, 44473167, 8373289, 94090109, 21533347, 58208470, 57961282, 38510840, 27185644, 33304202, 43357947, 90457870, 22450468, 88251446, 28550822, 75153252, 53084256, 11923835, 34698428, 88653118, 49328608, 508198, 57248122, 9259676, 75052463, 8651647, 14093520, 57393458, 13862149, 70036438, 93515664, 37957788, 27325428, 15163258, 59614746, 54663246, 65715134, 65338021, 80246713, 3773993, 19486173, 5482538, 61928316, 86301513, 8129978, 63059024, 68875490, 31161687, 29932657, 21289531, 47887470, 37675718, 94360702, 60102965, 4204661, 26275734, 42199455, 55189057, 34698463, 51507425, 84293052, 73617245, 30653863, 37739481, 35192533, 1239555, 91957544, 92541302, 34295794, 29188588, 6793819, 71300104, 92604458, 44889423, 47708850, 23740167, 86118021, 50007421, 56461322, 33699435, 17146629, 7182642, 55143724, 61859143, 4119747, 91938191, 1204161, 19272365, 86798033, 65074535, 14363867, 44479073, 50188404, 56153345, 87720882, 29401781, 31491938, 74441448, 67474219, 30218878, 66663942, 61982238, 70420215, 24168411, 84187166, 37192445, 45848907, 17386996, 40686254, 1250437, 76330843, 14349098, 46540998, 37891451, 97641116, 14220886, 62693428, 321665, 99125126, 29065964, 526217, 65038678, 54517921, 32161669, 83210802, 90310261, 83651211, 45407418, 76540481, 97281847, 29510992, 69579137, 81855445, 26863229, 71965942, 90013093, 17068582, 8099994, 87160386, 58749, 6221471, 7104732, 3880712, 7229550, 38494874, 47792865, 55470718, 36468541, 42237907, 36189527, 51590803, 32159704, 98130363, 53547802, 6153406, 20486294, 62232211, 99604946, 176257, 83789391, 3235882, 75407347, 30569392, 5640302, 39801351, 65275241, 62051033, 92867155, 37183543, 42967683, 86543538, 53666583, 40197395, 62740044, 49597667, 45428665, 39847321, 85116755, 69786756, 59177718, 92998591, 77694700, 51426311, 4099191, 47090124, 34044787, 59329511, 95957797, 14045383, 59910624, 97379790, 36135, 81774825, 82651278, 69355476, 79806380, 29994197, 10264691, 20645197, 6819644, 824872, 56424103, 11757872, 33565483, 50702367, 6871053, 16380211, 67793644, 53888755, 44550764, 77300457, 43506672, 31904591, 44842615, 26102057, 35996293, 45790169, 17539625, 75820087, 19939935, 49882705, 8055981, 2208785, 1936762, 66903004, 49236559, 97011160, 62430984, 17857111, 45794415, 53648154, 8807940, 86460488, 99861373, 48360198, 22108665, 92554120, 82979980, 23134715, 6111563, 16019925, 7955293, 43322743, 42692881, 5073754, 73109997, 6505939, 25636669, 33237508, 68644627, 5970607, 18806856, 40781854, 83269727, 8128637, 89078848, 52261574, 10453030, 91255408, 69697787, 72725103, 99549373, 45617087, 33431960, 26998766, 28928175, 91990218, 47893286, 6497830, 15075176, 59405277, 68128438, 20681921, 82178706, 20867149, 31733363, 93790285, 67031644, 48088883, 89804152, 4798568, 61815223, 61741594, 41380093, 77620120, 32426752, 70367851, 51464002, 55960386, 89811711, 27041967, 4091162, 18699206, 55669657, 22129328, 96318094, 86821229, 78218845, 89996536, 32699744, 68110330, 79738755, 68000591, 37501808, 7011964, 77787724, 72357096, 68939068, 96709982, 99224392, 22721500, 92353856, 15148031, 27411561, 20836893, 89499542, 66116458, 28787861, 26776922, 12664567, 71083565, 96735716, 20140249, 84406788 +15064655, 56461322, 44846932, 92803766, 21070110, 4069912, 68204242, 30218878, 62428472, 54987042, 75153252, 48395186, 78549759, 73168565, 51507425, 57163802, 87710366, 97641116, 74614639, 19891772, 81855445, 78785507, 26776922, 47792865, 90061527, 72495719, 92071990, 33553959, 42967683, 30543215, 63506281, 46851987, 9188443, 99690194, 59177718, 47708850, 68644627, 88047921, 55143724, 82532312, 72777973, 83302115, 24953498, 37166608, 89637706, 83368048, 5267545, 23432750, 13231279, 3233084, 51715482, 45237957, 26114953, 34236719, 6153406, 73392814, 62232211, 12571310, 77377183, 37675718, 43933006, 17058722, 8913721, 48260151, 13468268, 874791, 92604458, 12348118, 71522968, 71469330, 41245325, 7066775, 71316369, 4119747, 72238278, 45919976, 1431742, 67030811, 99226875, 86022504, 17386996, 5832946, 10597197, 669105, 62452034, 93566986, 24733232, 82327024, 40677414, 4095116, 83533741, 97940276, 91727510, 80316608, 33431960, 8099994, 22450468, 61623915, 10961421, 33895336, 62115552, 9860968, 14326617, 36468541, 61271144, 51472275, 48675329, 3633375, 32699744, 30891921, 38009615, 30787683, 55753905, 22108665, 5640302, 31161687, 21289531, 77312810, 94360702, 66322959, 54427233, 55615885, 4798568, 32590267, 6793819, 48658605, 73109997, 70541760, 56473732, 7011964, 99297164, 33237508, 59910624, 7517032, 69355476, 79806380, 7687278, 65047700, 63015256, 12024238, 43152977, 60955663, 66319530, 56424103, 77072625, 97783876, 84187166, 26292919, 36396314, 57240218, 1250437, 22129328, 82779622, 79821897, 6871053, 45995325, 67793644, 168541, 50806615, 72278539, 4985896, 90090964, 44550764, 48893685, 45306070, 95251277, 61960400, 32274392, 68824981, 59109400, 4787945, 45407418, 92787493, 76540481, 26102057, 35996293, 93053405, 45667668, 97561745, 17539625, 96852321, 17894977, 53084256, 6221471, 53842979, 49328608, 60430369, 33628349, 81274566, 14093520, 88444207, 63628376, 749283, 37957788, 10309525, 32250045, 15163258, 20836893, 81853704, 38658347, 22879907, 22942635, 40027975, 37224844, 78589145, 52613508, 78561158, 66116458, 63059024, 33123618, 7300047, 42199455, 11543098, 99729357, 84293052, 36845587, 78909561, 23110625, 72738685, 29188588, 89214616, 70372191, 32426752, 44889423, 44245960, 36808486, 83747892, 50007421, 33699435, 27041967, 74357852, 16424199, 41481685, 81774825, 68316156, 40781449, 28851716, 27625735, 72732205, 92692978, 71920426, 24314885, 33935899, 87720882, 40356877, 47298834, 1375023, 14731700, 6819644, 62496012, 59501487, 66663942, 96193415, 74452589, 54606384, 45996863, 83155442, 44060493, 29819940, 54058788, 59318837, 16380211, 61897582, 86821229, 68694897, 2917920, 56515456, 5111370, 94911072, 9886593, 91281584, 43506672, 43501211, 32161669, 61859581, 19376156, 69136837, 41092172, 85242963, 66137019, 2891150, 26139110, 59371804, 8373289, 8634541, 13173644, 21533347, 35092039, 16445503, 72019362, 28928175, 73235980, 44664587, 54199160, 7229550, 42947632, 21919959, 95290172, 67451935, 59405277, 15948937, 27325428, 81677380, 34493392, 59614746, 67227442, 85695762, 35456853, 73222868, 21673260, 80246713, 70004753, 75135448, 48360198, 64157906, 13348726, 69255765, 75407347, 86301513, 43045786, 10649306, 20765474, 75777973, 29959549, 61263068, 77301523, 93933709, 48088883, 76671482, 66271566, 42307449, 80555751, 62936963, 37739481, 61741594, 415901, 41380093, 75986488, 9175338, 85116755, 18411915, 12303248, 77694700, 79136082, 23740167, 17146629, 95957797, 29466406, 18131876, 96726697, 20122224, 24915585, 90654836, 74110882, 19272365, 50188404, 23194618, 72373496, 37435892, 55247455, 86242799, 40781854, 77284619, 3294781, 34497327, 57658654, 79322415, 41092102, 17037369, 56955985, 15536795, 6986898, 50702367, 40686254, 14349098, 71657078, 40152546, 94076128, 44983451, 11365791, 92353856, 56531125, 36812683, 57020481, 36753250, 79191827, 31904591, 91240048, 38645117, 8115266, 35512853, 48673079, 27411561, 75543508, 4434662, 33797252, 68041839, 90272749, 57241521, 29510992, 92215320, 3509435, 88698958, 25842078, 17957593, 38510840, 33304202, 43357947, 90457870, 99658235, 28550822, 54762643, 7104732, 68702632, 30998561, 2208785, 55602660, 3183975, 74137926, 78602717, 23569917, 66903004, 85023028, 59981773, 36580610, 54014062, 10358899, 22200378, 9829782, 11161731, 22997281, 14626618, 32159704, 26734892, 17857111, 7919588, 16595436, 65715134, 24058273, 45794415, 62490109, 15902805, 12416768, 38061439, 83789391, 59197747, 19486173, 3235882, 8129978, 7423788, 68875490, 95395112, 62803858, 61728685, 47887470, 1022677, 16097038, 60102965, 58180653, 51149804, 85117092, 80014588, 73617245, 30653863, 36505482, 48892201, 73124510, 29635537, 16099750, 7685448, 71300104, 42073124, 63967300, 33249630, 27187213, 93562257, 63152504, 6808825, 67281495, 55960386, 86118021, 25636669, 4508700, 89811711, 5970607, 78766358, 85711894, 97379790, 49641577, 32058615, 61859143, 52060076, 27665211, 86798033, 76960303, 99021067, 16567550, 8733068, 94711842, 31491938, 56484900, 74441448, 67474219, 72357096, 5822202, 36930650, 24168411, 89699445, 11581181, 39986008, 45381876, 77413012, 53632373, 84127901, 17385531, 76330843, 17727650, 47738185, 34946859, 10453030, 37891451, 247198, 34432810, 99971982, 97057187, 73031054, 74743862, 69697787, 9398733, 99125126, 61141874, 19457589, 92530431, 82339363, 23848565, 90158785, 17764950, 9058407, 70800879, 45790169, 49598724, 33336148, 30139692, 29516592, 58208470, 69641513, 84684495, 19939935, 83133790, 72274002, 76315420, 88251446, 11923835, 34698428, 66611759, 88653118, 62357986, 9623492, 79657802, 96420429, 57248122, 51315460, 75052463, 38494874, 1936762, 68816413, 89419466, 75229462, 83150534, 26397786, 49236559, 70036438, 4199704, 36189527, 6497830, 93515664, 28734791, 4978543, 17976208, 15075176, 9860195, 51590803, 68128438, 62430984, 54663246, 6525948, 61712234, 19898053, 55770687, 21993752, 81898046, 8807940, 68110330, 64087743, 79738755, 24826575, 65880522, 76434144, 67031644, 61928316, 44784505, 47213183, 98739783, 42644903, 65275241, 569864, 89217461, 37183543, 86543538, 39171895, 53666583, 26275734, 82052050, 52293995, 76703609, 26063929, 16019925, 84904436, 65081429, 26507214, 62740044, 7955293, 19101477, 91957544, 38365584, 37560164, 92692283, 77620120, 92541302, 34295794, 98371444, 51047803, 54868730, 112651, 92998591, 42692881, 5073754, 81172706, 7646095, 51426311, 99965001, 18783702, 58751351, 16971929, 91664334, 16054533, 82651278, 4091162, 54232247, 49271185, 92033260, 14363867, 16684829, 56153345, 39201414, 10264691, 95726235, 20645197, 20002147, 73021291, 70420215, 9603598, 38256849, 33201905, 31727379, 68939068, 8128637, 96709982, 52261574, 99224392, 48774913, 7502255, 40534591, 32151165, 96318094, 66832478, 91255408, 6521313, 67124282, 321665, 72358170, 16387575, 39553046, 15148031, 26664538, 9599614, 84349107, 83210802, 90310261, 51588015, 99549373, 4806458, 20642888, 94090109, 69579137, 47361209, 57961282, 75820087, 44348328, 98462867, 27185644, 93359396, 87160386, 40865610, 91141395, 93270084, 66250369, 36312813, 28796059, 82427263, 81805959, 98943869, 70782102, 1788101, 91990218, 57393458, 84406788, 47893286, 6157724, 65454636, 18466635, 34667286, 97011160, 89996536, 30764367, 64098930, 14723410, 98130363, 24591705, 20681921, 53547802, 65338021, 70596786, 78884452, 69605283, 20486294, 82178706, 30395570, 24619760, 176257, 64602895, 88130087, 30569392, 92554120, 92867155, 60581278, 82886381, 17016156, 4204661, 3233569, 72793444, 34698463, 79880247, 72614359, 44847298, 35192533, 61815223, 39847321, 22766820, 66885828, 4099191, 41442762, 39373729, 71333116, 29834512, 14045383, 90004325, 30811010, 37659250, 50668599, 29994197, 51466049, 99524975, 78300864, 4668450, 98948034, 20140249, 99333375, 50567636, 67513640, 45848907, 57803235, 46870723, 4064751, 30771409, 46540998, 94595668, 30163921, 22721500, 2717150, 65017137, 88904910, 44627776, 29065964, 77300457, 18833224, 99917599, 44481640, 36139942, 76825057, 14947650, 26493119, 45617087, 97281847, 80251430, 78218845, 26863229, 21001913, 90013093, 17068582, 84840549, 68102437, 75128745, 3487592, 58749, 95581843, 28787861, 3880712, 508198, 4515343, 6038457, 9259676, 20568363, 8651647, 67084351, 42237907, 30366150, 13862149, 64848072, 44915235, 20885148, 17365188, 94539824, 38008118, 1197320, 15015906, 86460488, 54263880, 61373987, 70727211, 31733363, 5482538, 45990383, 13819358, 55850790, 89804152, 98653983, 30463802, 83948335, 15535065, 43322743, 29029316, 2204165, 37620363, 6505939, 22113133, 59329511, 77787724, 52204879, 36135, 57359924, 18699206, 65074535, 29401781, 85571389, 73786944, 64055960, 83083131, 11757872, 77187825, 83269727, 26744917, 37192445, 89078848, 17081350, 82726008, 82897371, 62693428, 45049308, 526217, 65038678, 10366309, 60176618, 83651211, 79942022, 94516935, 22435353, 18001617, 86001008, 3088684, 66045587, 79922758, 91802888, 95010552, 37280276, 92398073, 44844121, 99604946, 20867149, 87598888, 99861373, 76170907, 98648327, 68000591, 62552524, 9575954, 39801351, 62051033, 29932657, 23134715, 6111563, 2331773, 84002370, 1239555, 49597667, 45428665, 69786756, 74724075, 18663507, 37501808, 47090124, 8791066, 7182642, 77898274, 91938191, 1204161, 60393039, 824872, 61982238, 55669657, 36780454, 14220886, 8696647, 45075471, 44842615, 72725103, 22405120, 16405341, 44473167, 63372756, 65271999, 66667729, 26392416, 89046466, 1569515, 8729146, 30090481, 38341669, 82979980, 55189057, 40197395, 36685023, 70367851, 51464002, 18504197, 34044787, 2607799, 43376279, 44479073, 9257405, 18806856, 62762857, 65851721, 71083565, 13470059, 26998766, 58224549, 96735716, 30694952, 55470718, 77272628, 69848388, 53648154, 3773993, 93790285, 66428795, 33565483, 54517921, 93057697, 71965942, 49882705, 8055981, 42782652, 60106217, 36933038, 53802686, 84166196, 53393358, 89499542, 40984766, 33061250, 44177011, 38022834, 31126490, 99515901, 53888755, 91831487, 96906410, 12664567, 21397057 +3183975, 20836893, 61982238, 45049308, 9623492, 96420429, 89419466, 37675718, 4798568, 61741594, 83302115, 83083131, 40781854, 86821229, 82339363, 82327024, 69136837, 2891150, 29510992, 38510840, 22450468, 78785507, 34698428, 3088684, 82427263, 79922758, 57393458, 99604946, 20867149, 31733363, 19101477, 6793819, 7646095, 90004325, 39201414, 39986008, 96318094, 44983451, 2717150, 6521313, 8115266, 90158785, 72725103, 76540481, 8373289, 3509435, 83133790, 49882705, 75153252, 3487592, 10961421, 8055981, 79657802, 91802888, 6038457, 51315460, 4978543, 98130363, 20681921, 70596786, 68110330, 70727211, 13819358, 77301523, 26275734, 30543215, 11543098, 30463802, 48892201, 49597667, 42692881, 73109997, 77787724, 71333116, 91664334, 14045383, 54232247, 33935899, 91938191, 65047700, 45919976, 6819644, 30218878, 73021291, 72357096, 86022504, 89699445, 99333375, 15536795, 22129328, 14349098, 5832946, 97057187, 91281584, 65038678, 61960400, 26664538, 38645117, 70800879, 27411561, 35996293, 91727510, 80316608, 26493119, 18001617, 13231279, 44348328, 17957593, 68102437, 99658235, 91141395, 73235980, 38494874, 9860968, 95290172, 36933038, 51472275, 36189527, 20885148, 45794415, 55770687, 44844121, 12416768, 92071990, 68875490, 10649306, 65275241, 73168565, 47887470, 89804152, 82052050, 52293995, 65081429, 35192533, 57163802, 98371444, 32426752, 112651, 63967300, 51464002, 55960386, 33699435, 33237508, 27041967, 55143724, 81774825, 52060076, 20122224, 99021067, 23194618, 10264691, 16567550, 12024238, 1375023, 43152977, 74441448, 78300864, 20002147, 67030811, 824872, 57658654, 79322415, 26292919, 36396314, 82779622, 7502255, 6871053, 22721500, 321665, 72358170, 99125126, 44846932, 32161669, 61859581, 10366309, 99549373, 45407418, 97940276, 68041839, 90272749, 80251430, 88698958, 96852321, 3233084, 25842078, 33304202, 90013093, 43357947, 28550822, 61623915, 28796059, 53842979, 4515343, 60430369, 91831487, 85023028, 14326617, 42237907, 84406788, 10358899, 47893286, 67451935, 92398073, 32250045, 27325428, 17365188, 89996536, 54663246, 64098930, 14723410, 35456853, 62232211, 86460488, 33061250, 89046466, 61373987, 76170907, 48360198, 30090481, 75407347, 30569392, 86301513, 8129978, 98739783, 92554120, 92867155, 55850790, 61263068, 77312810, 569864, 72793444, 42199455, 40197395, 36685023, 26063929, 16019925, 66322959, 79880247, 46851987, 26507214, 37739481, 61815223, 415901, 39847321, 9188443, 12303248, 66885828, 44245960, 37620363, 36808486, 23740167, 56473732, 7011964, 95957797, 52204879, 7182642, 59910624, 49641577, 24915585, 69355476, 19272365, 65074535, 29994197, 37435892, 4668450, 77284619, 59501487, 96193415, 70420215, 11757872, 41092102, 33565483, 37192445, 12664567, 57240218, 17386996, 40686254, 96709982, 1250437, 17727650, 29819940, 71657078, 46540998, 54058788, 40152546, 65851721, 73031054, 67793644, 30163921, 4985896, 44550764, 11365791, 2917920, 36812683, 57020481, 16387575, 526217, 20642888, 85242963, 83533741, 93053405, 4434662, 29516592, 78218845, 69579137, 47361209, 76315420, 8099994, 72019362, 53084256, 62357986, 45237957, 36312813, 3880712, 74137926, 26114953, 59981773, 30366150, 22200378, 6497830, 15075176, 9860195, 34667286, 90061527, 94539824, 32159704, 59614746, 1197320, 15015906, 89499542, 53648154, 73222868, 81898046, 21673260, 44177011, 1569515, 70004753, 93790285, 37224844, 12571310, 19486173, 5482538, 22108665, 62552524, 45990383, 47213183, 66116458, 7423788, 39801351, 38341669, 29959549, 89217461, 33553959, 93933709, 42967683, 86543538, 94360702, 39171895, 98653983, 17058722, 8913721, 30653863, 84002370, 78909561, 91957544, 38365584, 37560164, 92692283, 77620120, 23110625, 72738685, 99690194, 92998591, 43322743, 77694700, 81172706, 71469330, 7066775, 34044787, 22113133, 5970607, 74357852, 90654836, 27625735, 24314885, 18806856, 98948034, 20140249, 66663942, 56424103, 77072625, 77187825, 37166608, 62428472, 45848907, 45996863, 6986898, 4064751, 48774913, 10453030, 59318837, 16380211, 34432810, 82897371, 9398733, 9886593, 36753250, 77300457, 18833224, 43506672, 32274392, 45075471, 31904591, 68824981, 24733232, 44842615, 4787945, 51588015, 4806458, 41092172, 9058407, 26102057, 45790169, 44473167, 33431960, 21533347, 57961282, 69641513, 84684495, 26863229, 86001008, 19939935, 71965942, 58749, 54762643, 7104732, 66250369, 68702632, 508198, 2208785, 78602717, 23569917, 60106217, 75229462, 95010552, 67084351, 63628376, 37280276, 48675329, 44915235, 17976208, 53802686, 15948937, 72495719, 81677380, 62430984, 15163258, 84166196, 38008118, 17857111, 24591705, 53393358, 38658347, 82178706, 62490109, 40984766, 24619760, 176257, 87598888, 15064655, 67031644, 98648327, 44784505, 3235882, 42644903, 31161687, 62803858, 61728685, 6111563, 7300047, 53666583, 58180653, 55189057, 34698463, 63506281, 80555751, 7955293, 83948335, 92541302, 29188588, 9175338, 85116755, 18411915, 89214616, 12348118, 37501808, 93562257, 6808825, 47090124, 17146629, 58751351, 68644627, 59329511, 2607799, 36135, 72732205, 92692978, 1204161, 66428795, 16684829, 56153345, 50668599, 51466049, 1431742, 60955663, 67474219, 5822202, 99226875, 97783876, 33201905, 83269727, 68939068, 77413012, 57803235, 17385531, 99515901, 47738185, 91255408, 14220886, 90090964, 74743862, 8696647, 56531125, 65017137, 44627776, 5267545, 83210802, 22405120, 26139110, 75543508, 59371804, 33797252, 97561745, 17539625, 81855445, 98462867, 72274002, 17068582, 90457870, 40865610, 33895336, 49328608, 62115552, 9259676, 47792865, 36468541, 70782102, 61271144, 88444207, 36580610, 91990218, 13862149, 64848072, 9829782, 4199704, 59405277, 65454636, 10309525, 22997281, 14626618, 68128438, 34236719, 6525948, 26734892, 3633375, 32699744, 6153406, 78884452, 22879907, 22942635, 64087743, 99861373, 79738755, 55753905, 24826575, 76434144, 13348726, 61928316, 9575954, 52613508, 43045786, 29932657, 60581278, 82886381, 33123618, 1022677, 16097038, 48088883, 3233569, 76671482, 76703609, 13468268, 874791, 84293052, 44847298, 1239555, 34295794, 15535065, 16099750, 7685448, 51047803, 54868730, 42073124, 22766820, 29029316, 71522968, 18663507, 63152504, 6505939, 79136082, 18504197, 70541760, 86118021, 4099191, 99297164, 89811711, 71316369, 38022834, 43376279, 32058615, 7517032, 77898274, 61859143, 4091162, 28851716, 37659250, 4119747, 82532312, 27665211, 14363867, 44479073, 50188404, 87720882, 72238278, 94711842, 31491938, 56484900, 55247455, 64055960, 86242799, 9603598, 24168411, 87710366, 26744917, 53632373, 52261574, 99224392, 34946859, 97641116, 72278539, 48893685, 69697787, 67124282, 89637706, 29065964, 43501211, 71083565, 83368048, 92530431, 44481640, 15148031, 93057697, 19376156, 84349107, 59109400, 76825057, 23432750, 35512853, 17764950, 16405341, 4095116, 79942022, 94516935, 14947650, 22435353, 13173644, 58208470, 21001913, 27185644, 35092039, 16445503, 87160386, 84840549, 88251446, 11923835, 65271999, 66611759, 48395186, 88653118, 54199160, 26776922, 30998561, 96735716, 42782652, 57248122, 81805959, 30694952, 33628349, 66903004, 42947632, 14093520, 1788101, 26397786, 49236559, 749283, 51590803, 77272628, 61712234, 67227442, 16595436, 69605283, 85695762, 80246713, 3773993, 54263880, 30787683, 8729146, 75135448, 59197747, 65880522, 64602895, 78589145, 20765474, 75777973, 17016156, 37183543, 4204661, 85117092, 42307449, 72614359, 36845587, 62936963, 32590267, 29635537, 71300104, 70372191, 59177718, 44889423, 47708850, 51426311, 25636669, 8791066, 29466406, 31126490, 88047921, 82651278, 57359924, 74110882, 79806380, 86798033, 92033260, 72777973, 85571389, 40356877, 24953498, 20645197, 14731700, 3294781, 55669657, 38256849, 11581181, 50567636, 8128637, 84127901, 83155442, 17081350, 82726008, 46870723, 44060493, 32151165, 79821897, 168541, 53888755, 54987042, 45306070, 5111370, 62452034, 61141874, 54517921, 39553046, 79191827, 99917599, 93566986, 23848565, 40677414, 83651211, 66137019, 45617087, 97281847, 8634541, 57241521, 94090109, 75820087, 17894977, 26998766, 58224549, 44664587, 95581843, 28787861, 20568363, 75052463, 83150534, 26392416, 70036438, 28734791, 6157724, 11161731, 97011160, 30764367, 69848388, 81853704, 65715134, 65338021, 21070110, 20486294, 30891921, 15902805, 8807940, 30395570, 40027975, 78561158, 69255765, 5640302, 62051033, 21289531, 82979980, 23134715, 51149804, 99729357, 48260151, 51507425, 73617245, 2331773, 36505482, 73124510, 75986488, 45428665, 48658605, 33249630, 27187213, 83747892, 4508700, 41442762, 16971929, 18131876, 85711894, 97379790, 16424199, 68316156, 18699206, 7687278, 63015256, 76960303, 4069912, 68204242, 9257405, 95726235, 60393039, 62496012, 34497327, 74452589, 54606384, 36780454, 31727379, 17037369, 84187166, 56955985, 76330843, 21397057, 30771409, 247198, 10597197, 50806615, 61897582, 669105, 56515456, 88904910, 95251277, 74614639, 90310261, 33336148, 45667668, 30139692, 92215320, 51715482, 93359396, 6221471, 78549759, 8651647, 98943869, 81274566, 54014062, 37957788, 18466635, 34493392, 7919588, 73392814, 21993752, 38009615, 38061439, 64157906, 95395112, 43933006, 60102965, 54427233, 84904436, 55615885, 92604458, 69786756, 5073754, 2204165, 41245325, 99965001, 78766358, 16054533, 71920426, 72373496, 73786944, 8733068, 47298834, 66319530, 36930650, 89078848, 40534591, 45995325, 37891451, 94076128, 99971982, 92353856, 68694897, 62693428, 94911072, 19457589, 9599614, 13470059, 48673079, 19891772, 28928175, 75128745, 63372756, 66667729, 55602660, 7229550, 1936762, 55470718, 21919959, 53547802, 19898053, 77377183, 63059024, 41380093, 96906410, 74724075, 70367851, 67281495, 50007421, 56461322, 39373729, 29834512, 41481685, 30811010, 29401781, 62762857, 67513640, 45381876, 50702367, 94595668, 92803766, 93270084, 68816413, 24058273, 62740044, 18783702, 96726697, 40781449, 49271185, 99524975, 66832478, 36139942, 60176618, 92787493, 49598724, 66045587, 93515664, 68000591, 80014588, 88130087, 66271566, 91240048, 83789391 +23110625, 78561158, 92692283, 87710366, 94911072, 24591705, 63015256, 98462867, 25842078, 55602660, 38341669, 77312810, 39171895, 58180653, 84002370, 74724075, 56473732, 16971929, 4069912, 31727379, 65851721, 73031054, 19457589, 59109400, 60176618, 27185644, 17894977, 16445503, 66611759, 61271144, 15075176, 32250045, 72495719, 54663246, 77377183, 42644903, 72793444, 37560164, 29029316, 17146629, 71316369, 76960303, 98948034, 17037369, 53632373, 17386996, 4985896, 2717150, 33336148, 75820087, 26863229, 17957593, 22450468, 10961421, 68702632, 62115552, 90061527, 14626618, 38008118, 81853704, 86460488, 61373987, 40027975, 61928316, 45990383, 31161687, 1022677, 3233569, 17058722, 51149804, 8913721, 54427233, 874791, 79880247, 46851987, 62936963, 79136082, 91664334, 20122224, 72732205, 99524975, 37435892, 40152546, 43506672, 91240048, 83210802, 40677414, 23432750, 4787945, 35512853, 17764950, 48673079, 21533347, 17068582, 51715482, 26998766, 49882705, 75153252, 53084256, 3183975, 81805959, 78549759, 81274566, 89419466, 70782102, 91990218, 4199704, 67451935, 11161731, 15948937, 14723410, 26734892, 3633375, 62232211, 22942635, 8807940, 38061439, 76434144, 13348726, 75407347, 68875490, 82886381, 61263068, 77301523, 37675718, 48088883, 30543215, 16019925, 13468268, 84293052, 36505482, 7955293, 83948335, 72738685, 6793819, 12348118, 44889423, 25636669, 29466406, 29834512, 74110882, 91938191, 87720882, 68204242, 23194618, 85571389, 8733068, 12024238, 56484900, 6819644, 73021291, 66663942, 74452589, 77187825, 38256849, 99333375, 57803235, 6986898, 22129328, 99224392, 54987042, 86821229, 22721500, 44550764, 11365791, 18833224, 79191827, 99917599, 68824981, 82327024, 61859581, 10366309, 23848565, 90158785, 4806458, 9058407, 4095116, 97940276, 93053405, 68041839, 29510992, 47361209, 81855445, 92215320, 3509435, 38510840, 43357947, 68102437, 40865610, 3487592, 54762643, 28787861, 33895336, 26776922, 30998561, 23569917, 9259676, 9860968, 85023028, 47792865, 75229462, 36468541, 84406788, 63628376, 48675329, 37957788, 59405277, 10309525, 77272628, 94539824, 32159704, 98130363, 1197320, 16595436, 35456853, 81898046, 89046466, 70727211, 31733363, 64157906, 30090481, 22108665, 9575954, 98739783, 20765474, 29932657, 82979980, 23134715, 66271566, 66322959, 37739481, 48892201, 38365584, 92541302, 45428665, 9188443, 18411915, 69786756, 42692881, 23740167, 55960386, 47090124, 41442762, 39373729, 89811711, 38022834, 97379790, 61859143, 24915585, 79806380, 49271185, 56153345, 72373496, 39201414, 95726235, 47298834, 78300864, 66319530, 57658654, 9603598, 50567636, 45381876, 26292919, 36396314, 99515901, 14349098, 46870723, 21397057, 79821897, 37891451, 94076128, 34432810, 97057187, 72278539, 90090964, 8696647, 61141874, 29065964, 91281584, 45075471, 32161669, 93057697, 36139942, 19376156, 8115266, 83651211, 26102057, 85242963, 83533741, 26139110, 80316608, 22435353, 4434662, 44473167, 44348328, 78785507, 61623915, 93270084, 66250369, 82427263, 30694952, 33628349, 51315460, 38494874, 66903004, 60106217, 42947632, 14093520, 95010552, 88444207, 1788101, 54014062, 17976208, 18466635, 34667286, 27325428, 89996536, 6153406, 53393358, 70596786, 73392814, 78884452, 89499542, 55770687, 21993752, 64087743, 99604946, 87598888, 59197747, 24826575, 64602895, 88130087, 78589145, 8129978, 7423788, 5640302, 92554120, 16097038, 43933006, 26275734, 36685023, 42307449, 76703609, 80014588, 16099750, 70372191, 42073124, 63967300, 47708850, 81172706, 7646095, 99965001, 7011964, 8791066, 27041967, 74357852, 96726697, 85711894, 16424199, 41481685, 32058615, 68316156, 77898274, 52060076, 30811010, 28851716, 82532312, 27665211, 1204161, 65047700, 65074535, 16684829, 50188404, 99021067, 29401781, 45919976, 1375023, 60955663, 3294781, 96193415, 79322415, 37166608, 33201905, 11581181, 84187166, 68939068, 8128637, 15536795, 96709982, 17385531, 52261574, 48774913, 29819940, 10597197, 99971982, 30163921, 61897582, 669105, 6521313, 9398733, 321665, 72358170, 16387575, 526217, 95251277, 39553046, 83368048, 92530431, 92803766, 51588015, 16405341, 79942022, 2891150, 49598724, 45667668, 29516592, 8373289, 33431960, 94090109, 13173644, 13231279, 84684495, 96852321, 83133790, 72274002, 8099994, 11923835, 62357986, 3088684, 95581843, 96420429, 57248122, 79922758, 78602717, 20568363, 75052463, 68816413, 55470718, 67084351, 42237907, 10358899, 49236559, 37280276, 9829782, 51472275, 36189527, 44915235, 68128438, 6525948, 85695762, 38658347, 30891921, 15902805, 33061250, 3773993, 54263880, 30787683, 93790285, 8729146, 55753905, 62552524, 52613508, 92071990, 30569392, 61728685, 33123618, 21289531, 86543538, 7300047, 82052050, 52293995, 84904436, 30653863, 44847298, 35192533, 61815223, 32590267, 77620120, 34295794, 15535065, 29635537, 96906410, 32426752, 92998591, 12303248, 44245960, 73109997, 6505939, 99297164, 58751351, 68644627, 95957797, 71333116, 43376279, 88047921, 55143724, 16054533, 59910624, 40781449, 24314885, 86798033, 72777973, 40356877, 94711842, 55247455, 20645197, 40781854, 4668450, 67030811, 30218878, 59501487, 11757872, 36930650, 97783876, 86022504, 89699445, 67513640, 45996863, 40686254, 83155442, 17081350, 47738185, 44060493, 30771409, 54058788, 32151165, 96318094, 45995325, 67793644, 168541, 50806615, 97641116, 74743862, 48893685, 69697787, 2917920, 67124282, 5111370, 56531125, 9886593, 44627776, 65038678, 74614639, 32274392, 93566986, 82339363, 15148031, 24733232, 44842615, 76825057, 70800879, 59371804, 45790169, 26493119, 45617087, 18001617, 97561745, 57241521, 80251430, 69579137, 17539625, 58208470, 88698958, 86001008, 71965942, 33304202, 35092039, 93359396, 91141395, 6221471, 9623492, 36312813, 79657802, 49328608, 508198, 2208785, 91802888, 6038457, 74137926, 8651647, 98943869, 1936762, 83150534, 95290172, 57393458, 30366150, 64848072, 47893286, 70036438, 6497830, 4978543, 65454636, 62430984, 84166196, 69848388, 17857111, 15015906, 24058273, 65338021, 45794415, 20486294, 73222868, 22879907, 44844121, 80246713, 68110330, 30395570, 44177011, 1569515, 20867149, 79738755, 37224844, 76170907, 48360198, 47213183, 63059024, 43045786, 10649306, 95395112, 65275241, 62051033, 75777973, 37183543, 60102965, 55189057, 76671482, 11543098, 85117092, 34698463, 63506281, 51507425, 55615885, 36845587, 415901, 41380093, 29188588, 9175338, 7685448, 71300104, 89214616, 92604458, 54868730, 112651, 33249630, 27187213, 70367851, 2204165, 93562257, 6808825, 18504197, 41245325, 51426311, 86118021, 4099191, 33699435, 4508700, 22113133, 2607799, 78766358, 7517032, 57359924, 4091162, 90654836, 69355476, 7687278, 66428795, 92033260, 50668599, 51466049, 83302115, 67474219, 77284619, 824872, 34497327, 77072625, 62762857, 41092102, 62428472, 33565483, 39986008, 45848907, 1250437, 76330843, 5832946, 34946859, 66832478, 91255408, 82897371, 92353856, 45306070, 56515456, 88904910, 36753250, 45049308, 26664538, 38645117, 99549373, 41092172, 20642888, 76540481, 66137019, 91727510, 75543508, 90272749, 69641513, 3233084, 90457870, 84840549, 75128745, 34698428, 65271999, 73235980, 66667729, 28796059, 66045587, 26114953, 14326617, 93515664, 92398073, 20885148, 53802686, 81677380, 22997281, 34493392, 64098930, 20836893, 38009615, 12416768, 70004753, 12571310, 75135448, 5482538, 66116458, 62803858, 73168565, 47887470, 89217461, 93933709, 89804152, 99729357, 26507214, 80555751, 19101477, 61741594, 91957544, 49597667, 75986488, 85116755, 98371444, 48658605, 51047803, 22766820, 43322743, 71522968, 18663507, 71469330, 37620363, 51464002, 83747892, 7066775, 56461322, 33237508, 18131876, 31126490, 14045383, 27625735, 44479073, 72238278, 9257405, 16567550, 60393039, 31491938, 43152977, 24953498, 64055960, 1431742, 86242799, 20002147, 62496012, 5822202, 99226875, 61982238, 55669657, 24168411, 26744917, 56955985, 12664567, 77413012, 57240218, 17727650, 71657078, 59318837, 6871053, 16380211, 62693428, 65017137, 44846932, 36812683, 77300457, 61960400, 71083565, 54517921, 31904591, 84349107, 69136837, 13470059, 72725103, 22405120, 27411561, 33797252, 19891772, 21001913, 90013093, 76315420, 87160386, 99658235, 28928175, 28550822, 63372756, 8055981, 45237957, 3880712, 4515343, 60430369, 59981773, 26397786, 36580610, 22200378, 749283, 51590803, 30764367, 34236719, 19898053, 40984766, 83789391, 99861373, 15064655, 68000591, 44784505, 69255765, 39801351, 13819358, 17016156, 569864, 42967683, 6111563, 94360702, 98653983, 42199455, 26063929, 62740044, 4798568, 78909561, 30463802, 73124510, 39847321, 66885828, 5073754, 77694700, 37501808, 70541760, 34044787, 59329511, 52204879, 49641577, 82651278, 4119747, 92692978, 33935899, 19272365, 10264691, 83083131, 14731700, 72357096, 70420215, 54606384, 37192445, 89078848, 82779622, 7502255, 40534591, 10453030, 247198, 44983451, 68694897, 62452034, 99125126, 9599614, 35996293, 94516935, 14947650, 97281847, 78218845, 57961282, 88251446, 88653118, 7104732, 44664587, 53842979, 42782652, 91831487, 21919959, 28734791, 9860195, 6157724, 17365188, 97011160, 59614746, 7919588, 67227442, 32699744, 53547802, 21070110, 176257, 98648327, 55850790, 33553959, 4204661, 53666583, 73617245, 2331773, 57163802, 36808486, 63152504, 18783702, 50007421, 77787724, 7182642, 36135, 81774825, 54232247, 18699206, 29994197, 74441448, 56424103, 36780454, 84127901, 50702367, 82726008, 94595668, 53888755, 89637706, 43501211, 90310261, 45407418, 8634541, 19939935, 72019362, 58749, 48395186, 7229550, 61712234, 20681921, 65715134, 69605283, 53648154, 19486173, 65880522, 3235882, 40197395, 1239555, 99690194, 67281495, 90004325, 71920426, 14363867, 73786944, 20140249, 83269727, 4064751, 46540998, 14220886, 57020481, 5267545, 30139692, 58224549, 54199160, 36933038, 13862149, 15163258, 82178706, 21673260, 62490109, 92867155, 60581278, 48260151, 65081429, 18806856, 92787493, 96735716, 26392416, 24619760, 67031644, 86301513, 29959549, 59177718, 37659250, 44481640, 72614359, 5970607 +7300047, 874791, 9398733, 65338021, 3233569, 56484900, 33336148, 62357986, 22200378, 5640302, 92692978, 63015256, 33565483, 57803235, 526217, 60176618, 8115266, 3487592, 30998561, 20568363, 94539824, 54663246, 1197320, 3235882, 26063929, 62740044, 37560164, 27187213, 23740167, 97379790, 29401781, 12024238, 89078848, 82779622, 11365791, 23848565, 19376156, 9259676, 60106217, 36189527, 4978543, 76170907, 64602895, 7423788, 1022677, 76671482, 42307449, 30653863, 39847321, 56153345, 37435892, 98948034, 68939068, 99224392, 65851721, 67793644, 44983451, 69697787, 62452034, 22405120, 9058407, 70800879, 75543508, 47361209, 21001913, 35092039, 22450468, 11923835, 58224549, 96735716, 91802888, 7229550, 42237907, 15948937, 34667286, 68128438, 34493392, 89046466, 61928316, 82979980, 72793444, 13468268, 54427233, 79880247, 55615885, 38365584, 49597667, 92692283, 9175338, 22766820, 55143724, 57359924, 37659250, 1204161, 86798033, 9257405, 29994197, 20645197, 73021291, 89699445, 45381876, 84127901, 4064751, 40534591, 40152546, 6521313, 2917920, 67124282, 45049308, 18833224, 54517921, 93057697, 83210802, 40677414, 4787945, 20642888, 76540481, 83533741, 22435353, 45617087, 8373289, 29510992, 72274002, 53084256, 93270084, 60430369, 51315460, 13862149, 4199704, 65454636, 53802686, 15015906, 85695762, 38658347, 62232211, 1569515, 40027975, 12571310, 59197747, 65880522, 64157906, 66116458, 65275241, 31161687, 62051033, 569864, 89217461, 16097038, 36685023, 66271566, 85117092, 80014588, 65081429, 30463802, 61815223, 19101477, 32590267, 96906410, 18504197, 91664334, 68316156, 24314885, 4069912, 87720882, 31491938, 9603598, 24168411, 37166608, 50702367, 14349098, 46870723, 46540998, 54058788, 72278539, 56515456, 88904910, 44627776, 36753250, 82327024, 5267545, 13470059, 90158785, 72725103, 16405341, 57241521, 78218845, 21533347, 3509435, 33304202, 17894977, 17068582, 16445503, 68102437, 45237957, 53842979, 42782652, 78549759, 70782102, 36933038, 47893286, 67451935, 17976208, 16595436, 53648154, 81898046, 62490109, 3773993, 61373987, 79738755, 88130087, 62552524, 9575954, 44784505, 47213183, 68875490, 21289531, 77312810, 37183543, 4204661, 82052050, 4798568, 44847298, 1239555, 83948335, 73124510, 98371444, 51047803, 70372191, 112651, 63967300, 66885828, 12348118, 18663507, 83747892, 56473732, 71316369, 18131876, 43376279, 74357852, 14045383, 36135, 20122224, 27625735, 54232247, 71920426, 82532312, 39201414, 95726235, 83302115, 60393039, 47298834, 55247455, 83083131, 60955663, 77284619, 59501487, 70420215, 74452589, 11581181, 56955985, 36396314, 83155442, 17081350, 44060493, 5832946, 10453030, 168541, 50806615, 66832478, 91255408, 2717150, 8696647, 94911072, 99917599, 82339363, 15148031, 61859581, 91240048, 38645117, 45667668, 69579137, 81855445, 69641513, 86001008, 3233084, 38510840, 90013093, 87160386, 88251446, 91141395, 6221471, 66611759, 7104732, 66250369, 36312813, 33895336, 66045587, 96420429, 30694952, 33628349, 75052463, 98943869, 9860968, 81274566, 55470718, 75229462, 14326617, 36580610, 9860195, 59405277, 72495719, 17365188, 14626618, 62430984, 59614746, 81853704, 19898053, 21993752, 21673260, 30787683, 87598888, 5482538, 98648327, 68000591, 45990383, 69255765, 86301513, 39801351, 92554120, 92867155, 75777973, 55850790, 47887470, 37675718, 30543215, 52293995, 66322959, 72614359, 62936963, 61741594, 15535065, 45428665, 29635537, 48658605, 54868730, 69786756, 12303248, 29029316, 44889423, 2204165, 7646095, 37501808, 36808486, 79136082, 18783702, 56461322, 99297164, 52204879, 2607799, 96726697, 49641577, 24915585, 28851716, 27665211, 33935899, 65074535, 76960303, 16684829, 99021067, 85571389, 40356877, 99226875, 96193415, 11757872, 77187825, 55669657, 38256849, 87710366, 36780454, 15536795, 26292919, 1250437, 52261574, 82726008, 48774913, 21397057, 45995325, 99971982, 61897582, 74743862, 68694897, 62693428, 89637706, 9886593, 36812683, 19457589, 91281584, 16387575, 43501211, 26664538, 35512853, 17764950, 94516935, 66137019, 91727510, 2891150, 18001617, 44473167, 58208470, 75820087, 98462867, 25842078, 8099994, 51715482, 26998766, 49882705, 28550822, 75128745, 58749, 10961421, 65271999, 66667729, 28796059, 3880712, 57248122, 62115552, 3183975, 26114953, 1936762, 85023028, 21919959, 36468541, 1788101, 26392416, 63628376, 64848072, 749283, 44915235, 77272628, 97011160, 32159704, 69848388, 98130363, 26734892, 24591705, 20681921, 53393358, 73222868, 44844121, 68110330, 86460488, 83789391, 8729146, 19486173, 48360198, 13348726, 52613508, 8129978, 43045786, 98739783, 13819358, 42644903, 29932657, 60581278, 33123618, 29959549, 93933709, 43933006, 53666583, 58180653, 40197395, 11543098, 51149804, 76703609, 16019925, 73617245, 46851987, 80555751, 7955293, 41380093, 77620120, 34295794, 72738685, 71300104, 89214616, 99690194, 81172706, 71522968, 6808825, 67281495, 55960386, 17146629, 34044787, 89811711, 38022834, 77787724, 71333116, 78766358, 7182642, 85711894, 81774825, 82651278, 4091162, 65047700, 92033260, 44479073, 10264691, 16567550, 51466049, 18806856, 86242799, 40781854, 67474219, 72357096, 34497327, 77072625, 97783876, 31727379, 84187166, 45996863, 12664567, 53632373, 6986898, 40686254, 17385531, 99515901, 22129328, 17727650, 47738185, 29819940, 30771409, 71657078, 32151165, 96318094, 247198, 94595668, 97057187, 86821229, 4985896, 14220886, 44550764, 48893685, 321665, 65017137, 77300457, 45075471, 83368048, 32161669, 10366309, 36139942, 90310261, 59109400, 76825057, 4434662, 49598724, 29516592, 57961282, 71965942, 27185644, 43357947, 93359396, 84840549, 78785507, 48395186, 54762643, 3088684, 68702632, 82427263, 6038457, 74137926, 95290172, 88444207, 26397786, 6497830, 93515664, 37957788, 27325428, 90061527, 81677380, 30764367, 38008118, 64098930, 7919588, 61712234, 73392814, 89499542, 55770687, 20486294, 12416768, 64087743, 24619760, 54263880, 70004753, 31733363, 15064655, 55753905, 24826575, 78589145, 76434144, 30090481, 30569392, 20765474, 38341669, 73168565, 77301523, 86543538, 98653983, 34698463, 48260151, 84904436, 2331773, 26507214, 78909561, 37739481, 23110625, 92541302, 6793819, 16099750, 18411915, 7685448, 59177718, 92998591, 33249630, 5073754, 77694700, 74724075, 6505939, 51464002, 51426311, 99965001, 47090124, 7011964, 4508700, 22113133, 29834512, 16971929, 16054533, 52060076, 90004325, 4119747, 74110882, 49271185, 7687278, 66428795, 14363867, 94711842, 74441448, 64055960, 78300864, 14731700, 20002147, 6819644, 67030811, 62496012, 66663942, 54606384, 62428472, 26744917, 50567636, 37192445, 45848907, 77413012, 57240218, 6871053, 94076128, 53888755, 54987042, 22721500, 90090964, 92353856, 45306070, 5111370, 56531125, 99125126, 44846932, 61141874, 29065964, 95251277, 65038678, 32274392, 92530431, 93566986, 68824981, 44481640, 44842615, 84349107, 83651211, 51588015, 99549373, 92787493, 26102057, 79942022, 35996293, 45790169, 97561745, 90272749, 33431960, 17539625, 92215320, 88698958, 96852321, 26863229, 19939935, 17957593, 83133790, 72019362, 40865610, 61623915, 75153252, 34698428, 88653118, 73235980, 28787861, 508198, 2208785, 79922758, 55602660, 81805959, 23569917, 38494874, 91831487, 68816413, 59981773, 83150534, 95010552, 61271144, 91990218, 30366150, 10358899, 48675329, 28734791, 6157724, 20885148, 18466635, 51590803, 22997281, 89996536, 34236719, 6525948, 14723410, 32699744, 53547802, 45794415, 70596786, 22942635, 30891921, 15902805, 44177011, 20867149, 75135448, 22108665, 78561158, 10649306, 95395112, 61263068, 6111563, 26275734, 55189057, 99729357, 63506281, 36845587, 9188443, 92604458, 42073124, 70367851, 71469330, 37620363, 73109997, 70541760, 7066775, 33699435, 41442762, 58751351, 5970607, 31126490, 41481685, 7517032, 77898274, 69355476, 91938191, 23194618, 72777973, 72238278, 4668450, 3294781, 824872, 61982238, 62762857, 86022504, 83269727, 17386996, 96709982, 76330843, 34946859, 59318837, 10597197, 34432810, 30163921, 82897371, 72358170, 57020481, 43506672, 74614639, 71083565, 4806458, 48673079, 27411561, 97940276, 26139110, 33797252, 26493119, 97281847, 44348328, 84684495, 76315420, 99658235, 28928175, 95581843, 78602717, 66903004, 54014062, 84406788, 9829782, 51472275, 70036438, 15075176, 92398073, 17857111, 20836893, 3633375, 78884452, 22879907, 8807940, 40984766, 38009615, 38061439, 70727211, 99861373, 77377183, 92071990, 61728685, 17016156, 42967683, 94360702, 39171895, 60102965, 35192533, 415901, 75986488, 57163802, 85116755, 43322743, 42692881, 47708850, 93562257, 63152504, 41245325, 4099191, 8791066, 39373729, 68644627, 29466406, 27041967, 88047921, 61859143, 40781449, 30811010, 90654836, 79806380, 18699206, 19272365, 50188404, 50668599, 68204242, 73786944, 24953498, 57658654, 79322415, 41092102, 33201905, 17037369, 8128637, 37891451, 61960400, 39553046, 79191827, 31904591, 69136837, 4095116, 30139692, 8634541, 94090109, 80251430, 63372756, 8055981, 9623492, 44664587, 54199160, 42947632, 47792865, 57393458, 37280276, 11161731, 32250045, 15163258, 84166196, 65715134, 24058273, 69605283, 21070110, 35456853, 80246713, 67031644, 75407347, 62803858, 82886381, 48088883, 89804152, 42199455, 84002370, 48892201, 91957544, 29188588, 44245960, 50007421, 95957797, 16424199, 72732205, 72373496, 8733068, 1431742, 20140249, 56424103, 99333375, 39986008, 669105, 23432750, 41092172, 85242963, 14947650, 59371804, 80316608, 19891772, 90457870, 4515343, 8651647, 89419466, 14093520, 67084351, 49236559, 10309525, 67227442, 6153406, 33061250, 30395570, 93790285, 37224844, 63059024, 33553959, 23134715, 51507425, 84293052, 36505482, 33237508, 59329511, 59910624, 32058615, 45919976, 99524975, 43152977, 5822202, 36930650, 67513640, 7502255, 73031054, 97641116, 24733232, 9599614, 45407418, 13173644, 79657802, 26776922, 82178706, 99604946, 17058722, 25636669, 66319530, 30218878, 92803766, 93053405, 68041839, 13231279, 8913721, 32426752, 1375023, 16380211, 176257, 49328608, 86118021, 79821897 +22721500, 90158785, 5640302, 31161687, 11581181, 7011964, 11365791, 70782102, 28734791, 14626618, 54663246, 6525948, 44784505, 47213183, 34295794, 23740167, 94076128, 62452034, 19457589, 45049308, 98462867, 90457870, 93270084, 60430369, 13862149, 48675329, 34667286, 68128438, 61712234, 65338021, 21673260, 70727211, 78561158, 75777973, 43933006, 85117092, 874791, 84904436, 26507214, 36845587, 71522968, 40781449, 69355476, 74110882, 9398733, 36812683, 18833224, 79191827, 15148031, 26664538, 29516592, 69579137, 26863229, 17894977, 54762643, 30998561, 20568363, 83150534, 17976208, 30764367, 98130363, 89499542, 30891921, 64087743, 10649306, 47887470, 7300047, 60102965, 4204661, 51507425, 62740044, 6793819, 57163802, 18411915, 77694700, 18663507, 6808825, 99965001, 58751351, 39373729, 95957797, 43376279, 87720882, 83302115, 31491938, 24168411, 62428472, 89078848, 50702367, 96709982, 40534591, 45995325, 67793644, 2717150, 56515456, 5111370, 92803766, 59109400, 51588015, 79942022, 35996293, 75543508, 57241521, 75820087, 21001913, 16445503, 88251446, 6221471, 81805959, 30694952, 26392416, 63628376, 70036438, 32250045, 51590803, 84166196, 78884452, 69605283, 15902805, 8807940, 79738755, 12571310, 19486173, 61928316, 45990383, 39801351, 20765474, 42644903, 65275241, 62051033, 55850790, 21289531, 86543538, 94360702, 72793444, 76703609, 48260151, 54427233, 72614359, 91957544, 41380093, 23110625, 45428665, 63967300, 33249630, 27187213, 74724075, 18504197, 2607799, 41481685, 63015256, 56153345, 9257405, 45919976, 10264691, 95726235, 98948034, 56955985, 37192445, 45381876, 77413012, 57803235, 40686254, 83155442, 17385531, 4064751, 47738185, 37891451, 16380211, 94595668, 53888755, 48893685, 69697787, 62693428, 526217, 74614639, 44481640, 91240048, 45407418, 76540481, 14947650, 66137019, 91727510, 45790169, 33336148, 45617087, 47361209, 81855445, 21533347, 27185644, 22450468, 28550822, 75153252, 66667729, 28787861, 53842979, 66045587, 508198, 2208785, 62115552, 33628349, 42947632, 47792865, 9829782, 749283, 36189527, 93515664, 10309525, 18466635, 90061527, 17365188, 17857111, 35456853, 53648154, 22942635, 62490109, 99604946, 3773993, 65880522, 5482538, 13348726, 98648327, 69255765, 75407347, 66116458, 7423788, 62803858, 61728685, 33553959, 82979980, 98653983, 52293995, 26063929, 16019925, 13468268, 80014588, 79880247, 19101477, 38365584, 77620120, 9175338, 98371444, 54868730, 70372191, 37620363, 36808486, 70541760, 50007421, 96726697, 88047921, 7182642, 55143724, 97379790, 77898274, 30811010, 37659250, 27665211, 19272365, 86798033, 72238278, 72373496, 85571389, 12024238, 47298834, 99524975, 56484900, 64055960, 78300864, 86242799, 60955663, 20002147, 67474219, 30218878, 20140249, 99226875, 56424103, 70420215, 55669657, 84187166, 68939068, 67513640, 39986008, 15536795, 44060493, 5832946, 99971982, 168541, 97641116, 66832478, 669105, 44983451, 6521313, 92353856, 65038678, 54517921, 32274392, 83368048, 92530431, 31904591, 24733232, 44842615, 10366309, 69136837, 8115266, 23432750, 92787493, 9058407, 4095116, 2891150, 33797252, 49598724, 45667668, 13173644, 29510992, 57961282, 88698958, 3233084, 71965942, 72274002, 17068582, 3487592, 48395186, 66250369, 79657802, 33895336, 49328608, 96735716, 74137926, 78549759, 51315460, 89419466, 75229462, 42237907, 10358899, 4978543, 15948937, 72495719, 97011160, 59614746, 53393358, 70596786, 73392814, 22879907, 44844121, 87598888, 62552524, 9575954, 1022677, 77312810, 48088883, 26275734, 55189057, 82052050, 11543098, 51149804, 84293052, 55615885, 2331773, 80555751, 62936963, 7955293, 83948335, 32590267, 73124510, 92692283, 92604458, 96906410, 22766820, 66885828, 44889423, 70367851, 73109997, 67281495, 51426311, 18783702, 56473732, 47090124, 4508700, 99297164, 77787724, 27041967, 18131876, 78766358, 31126490, 81774825, 57359924, 20122224, 24915585, 28851716, 71920426, 82532312, 49271185, 4069912, 16567550, 51466049, 24953498, 20645197, 4668450, 3294781, 824872, 66663942, 37166608, 36780454, 33201905, 99333375, 22129328, 82779622, 30771409, 10453030, 32151165, 40152546, 73031054, 30163921, 61897582, 86821229, 14220886, 44550764, 74743862, 68694897, 45306070, 321665, 9886593, 39553046, 32161669, 82327024, 19376156, 38645117, 60176618, 76825057, 13470059, 17764950, 16405341, 80316608, 22435353, 25842078, 17957593, 38510840, 35092039, 43357947, 8099994, 49882705, 68102437, 10961421, 11923835, 91141395, 66611759, 62357986, 3088684, 68702632, 3880712, 82427263, 9259676, 98943869, 1936762, 66903004, 91831487, 21919959, 36468541, 91990218, 54014062, 84406788, 22200378, 6497830, 9860195, 6157724, 65454636, 81677380, 34236719, 7919588, 81853704, 20681921, 32699744, 6153406, 45794415, 21070110, 38658347, 81898046, 80246713, 61373987, 31733363, 8729146, 75135448, 77377183, 3235882, 8129978, 92554120, 82886381, 33123618, 569864, 37183543, 93933709, 3233569, 40197395, 42307449, 63506281, 65081429, 61815223, 37560164, 75986488, 29635537, 16099750, 51047803, 99690194, 92998591, 5073754, 12348118, 29029316, 44245960, 47708850, 2204165, 7646095, 6505939, 33699435, 8791066, 33237508, 89811711, 59329511, 29834512, 74357852, 16054533, 82651278, 79806380, 18699206, 33935899, 65074535, 14363867, 44479073, 16684829, 99021067, 23194618, 40356877, 60393039, 1375023, 55247455, 14731700, 41092102, 87710366, 31727379, 33565483, 8128637, 26292919, 36396314, 57240218, 99515901, 82726008, 29819940, 54058788, 96318094, 247198, 10597197, 97057187, 54987042, 72278539, 91255408, 4985896, 90090964, 8696647, 56531125, 89637706, 88904910, 43506672, 71083565, 68824981, 84349107, 4787945, 22405120, 48673079, 26102057, 85242963, 83533741, 27411561, 94516935, 90272749, 8634541, 94090109, 3509435, 84684495, 86001008, 19939935, 51715482, 87160386, 72019362, 84840549, 99658235, 28928175, 61623915, 58749, 28796059, 26776922, 42782652, 95290172, 95010552, 1788101, 64848072, 44915235, 67451935, 37957788, 11161731, 20885148, 64098930, 24591705, 24058273, 21993752, 62232211, 12416768, 89046466, 176257, 83789391, 24826575, 30090481, 95395112, 60581278, 29959549, 53666583, 58180653, 66271566, 8913721, 34698463, 66322959, 61741594, 48892201, 39847321, 85116755, 9188443, 59177718, 42073124, 51464002, 55960386, 56461322, 41442762, 22113133, 71316369, 68644627, 5970607, 38022834, 71333116, 16971929, 91664334, 59910624, 49641577, 36135, 32058615, 68316156, 27625735, 72732205, 54232247, 29401781, 73786944, 94711842, 43152977, 83083131, 77284619, 73021291, 61982238, 11757872, 74452589, 86022504, 17037369, 45996863, 53632373, 84127901, 17081350, 52261574, 14349098, 46870723, 48774913, 59318837, 82897371, 2917920, 94911072, 72358170, 99125126, 43501211, 61960400, 45075471, 99917599, 93566986, 82339363, 23848565, 5267545, 83210802, 90310261, 35512853, 4806458, 26493119, 18001617, 8373289, 69641513, 96852321, 83133790, 26998766, 53084256, 75128745, 34698428, 36312813, 54199160, 4515343, 91802888, 55602660, 6038457, 3183975, 75052463, 8651647, 14093520, 88444207, 49236559, 37280276, 4199704, 92398073, 27325428, 77272628, 34493392, 62430984, 94539824, 32159704, 14723410, 26734892, 53547802, 85695762, 40984766, 38009615, 70004753, 99861373, 40027975, 15064655, 64602895, 78589145, 64157906, 67031644, 68000591, 22108665, 63059024, 13819358, 38341669, 6111563, 16097038, 36685023, 76671482, 4798568, 49597667, 72738685, 48658605, 7685448, 112651, 42692881, 81172706, 71469330, 37501808, 93562257, 79136082, 29466406, 85711894, 14045383, 16424199, 61859143, 52060076, 90004325, 4091162, 90654836, 92033260, 68204242, 6819644, 72357096, 59501487, 5822202, 96193415, 34497327, 57658654, 62762857, 77187825, 89699445, 50567636, 45848907, 12664567, 17727650, 21397057, 71657078, 46540998, 6871053, 34432810, 65017137, 91281584, 77300457, 61859581, 40677414, 72725103, 93053405, 44473167, 33431960, 19891772, 92215320, 58224549, 8055981, 73235980, 9623492, 7229550, 78602717, 85023028, 60106217, 59981773, 55470718, 61271144, 36933038, 36580610, 30366150, 15075176, 59405277, 53802686, 38008118, 1197320, 20836893, 67227442, 16595436, 3633375, 68110330, 24619760, 30787683, 1569515, 20867149, 37224844, 76170907, 55753905, 30569392, 43045786, 68875490, 98739783, 92867155, 77301523, 37675718, 89217461, 42967683, 89804152, 30543215, 73617245, 30653863, 84002370, 46851987, 37739481, 1239555, 89214616, 83747892, 41245325, 4099191, 25636669, 52204879, 92692978, 24314885, 65047700, 50188404, 29994197, 39201414, 8733068, 40781854, 66319530, 83269727, 26744917, 6986898, 17386996, 76330843, 99224392, 7502255, 65851721, 67124282, 44846932, 29065964, 57020481, 36753250, 95251277, 93057697, 83651211, 99549373, 20642888, 70800879, 97940276, 97561745, 80251430, 78218845, 58208470, 13231279, 90013093, 76315420, 93359396, 78785507, 40865610, 44664587, 57248122, 26114953, 81274566, 26397786, 67084351, 57393458, 47893286, 22997281, 89996536, 15015906, 55770687, 73222868, 82178706, 86460488, 30395570, 54263880, 48360198, 88130087, 52613508, 86301513, 73168565, 29932657, 61263068, 17058722, 78909561, 35192533, 30463802, 92541302, 71300104, 32426752, 12303248, 43322743, 7517032, 91938191, 66428795, 76960303, 74441448, 67030811, 77072625, 36930650, 97783876, 54606384, 1250437, 50806615, 36139942, 41092172, 26139110, 59371804, 4434662, 68041839, 97281847, 17539625, 44348328, 33304202, 63372756, 65271999, 88653118, 7104732, 95581843, 96420429, 23569917, 15163258, 69848388, 20486294, 38061439, 33061250, 59197747, 76434144, 92071990, 23134715, 39171895, 42199455, 99729357, 36505482, 415901, 29188588, 69786756, 63152504, 7066775, 86118021, 4119747, 1204161, 72777973, 37435892, 9603598, 34946859, 61141874, 16387575, 45237957, 79922758, 9860968, 14326617, 51472275, 44177011, 34044787, 50668599, 79322415, 38494874, 17016156, 44847298, 17146629, 18806856, 1431742, 38256849, 44627776, 30139692, 65715134, 93790285, 7687278, 62496012, 79821897, 68816413, 19898053, 15535065, 9599614 +37166608, 37957788, 19891772, 75153252, 61928316, 70541760, 50007421, 40356877, 17539625, 95010552, 44784505, 42307449, 63506281, 89699445, 62428472, 2917920, 5111370, 91240048, 45407418, 83533741, 66137019, 18001617, 13173644, 33628349, 72495719, 66322959, 71522968, 63152504, 79136082, 41245325, 55960386, 68316156, 7687278, 94711842, 55247455, 66663942, 74452589, 86022504, 77413012, 76330843, 91255408, 62693428, 83368048, 44481640, 61859581, 92803766, 83210802, 92787493, 35996293, 21533347, 65271999, 33895336, 64848072, 47893286, 15075176, 19898053, 9575954, 62803858, 86543538, 52293995, 84293052, 34295794, 45428665, 98371444, 63967300, 27187213, 23740167, 51426311, 7182642, 71920426, 44479073, 45919976, 99524975, 64055960, 67030811, 77284619, 37192445, 82779622, 44060493, 29819940, 61897582, 54987042, 67124282, 94911072, 62452034, 57020481, 36753250, 74614639, 17764950, 70800879, 97940276, 33336148, 45667668, 57241521, 3233084, 17957593, 62357986, 53842979, 66045587, 26114953, 23569917, 9259676, 1788101, 10358899, 70036438, 93515664, 18466635, 32250045, 34493392, 94539824, 89996536, 30764367, 53547802, 69605283, 21993752, 54263880, 30787683, 75135448, 59197747, 22108665, 45990383, 69255765, 8129978, 5640302, 39801351, 92867155, 93933709, 94360702, 4204661, 58180653, 55189057, 30543215, 66271566, 11543098, 99729357, 84904436, 4798568, 61815223, 73124510, 57163802, 16099750, 66885828, 12348118, 71469330, 95957797, 74357852, 96726697, 88047921, 27625735, 24314885, 68204242, 95726235, 56484900, 60955663, 96193415, 41092102, 36396314, 17081350, 79821897, 10597197, 73031054, 53888755, 72358170, 61141874, 16387575, 79191827, 24733232, 5267545, 19376156, 83651211, 41092172, 14947650, 93053405, 90272749, 94090109, 69579137, 69641513, 98462867, 88698958, 26863229, 51715482, 87160386, 53084256, 91141395, 88653118, 7104732, 93270084, 28796059, 3880712, 42782652, 51315460, 20568363, 98943869, 81274566, 54014062, 51472275, 27325428, 34236719, 15015906, 81853704, 21070110, 53648154, 22879907, 22942635, 12416768, 44177011, 70727211, 40027975, 12571310, 64602895, 30090481, 68000591, 77377183, 78561158, 92071990, 98739783, 65275241, 3233569, 26275734, 72793444, 36685023, 16019925, 13468268, 51507425, 26507214, 7955293, 48892201, 32590267, 59177718, 92998591, 6505939, 6808825, 18783702, 56473732, 41442762, 71316369, 55143724, 49641577, 61859143, 69355476, 79806380, 33935899, 66428795, 50188404, 87720882, 50668599, 16567550, 31491938, 67474219, 98948034, 62496012, 9603598, 55669657, 11581181, 45848907, 8128637, 89078848, 40686254, 52261574, 47738185, 7502255, 46540998, 34946859, 45995325, 37891451, 168541, 6521313, 92353856, 9398733, 88904910, 29065964, 93057697, 90310261, 38645117, 59109400, 60176618, 35512853, 20642888, 48673079, 76540481, 91727510, 22435353, 33797252, 29510992, 84684495, 96852321, 86001008, 90457870, 84840549, 26998766, 78785507, 28928175, 58749, 66611759, 54762643, 9623492, 44664587, 95581843, 508198, 55602660, 78602717, 42947632, 21919959, 26397786, 749283, 36189527, 67451935, 9860195, 53802686, 68128438, 32159704, 17857111, 32699744, 85695762, 38009615, 64087743, 3773993, 83789391, 79738755, 93790285, 19486173, 24826575, 65880522, 76434144, 13348726, 75407347, 66116458, 73168565, 29932657, 33123618, 1022677, 53666583, 98653983, 76671482, 51149804, 85117092, 8913721, 34698463, 48260151, 26063929, 73617245, 30653863, 65081429, 92692283, 77620120, 23110625, 39847321, 71300104, 96906410, 12303248, 5073754, 70367851, 18663507, 37620363, 56461322, 99297164, 58751351, 33237508, 71333116, 16971929, 59910624, 97379790, 32058615, 81774825, 82651278, 92692978, 18699206, 86798033, 92033260, 14363867, 56153345, 23194618, 29401781, 9257405, 29994197, 83302115, 47298834, 24953498, 74441448, 78300864, 30218878, 61982238, 62762857, 24168411, 87710366, 99333375, 15536795, 50702367, 17727650, 5832946, 40534591, 32151165, 94076128, 16380211, 97057187, 30163921, 66832478, 14220886, 90090964, 48893685, 99125126, 19457589, 91281584, 526217, 43501211, 45075471, 26664538, 23848565, 4095116, 79942022, 75543508, 80316608, 45790169, 97561745, 8634541, 72274002, 43357947, 8099994, 22450468, 72019362, 28550822, 3487592, 10961421, 58224549, 79657802, 82427263, 57248122, 38494874, 1936762, 68816413, 89419466, 14093520, 47792865, 36468541, 9829782, 48675329, 4199704, 11161731, 20885148, 10309525, 14723410, 24591705, 67227442, 73392814, 38658347, 73222868, 21673260, 15902805, 40984766, 99604946, 20867149, 87598888, 55753905, 5482538, 88130087, 3235882, 47213183, 95395112, 92554120, 42644903, 62051033, 75777973, 77301523, 33553959, 23134715, 43933006, 39171895, 60102965, 48088883, 54427233, 55615885, 2331773, 46851987, 72614359, 78909561, 37739481, 415901, 15535065, 9188443, 99690194, 69786756, 42073124, 22766820, 44889423, 44245960, 47708850, 93562257, 36808486, 25636669, 34044787, 68644627, 38022834, 29834512, 2607799, 85711894, 16054533, 41481685, 57359924, 90004325, 30811010, 65047700, 19272365, 63015256, 39201414, 60393039, 83083131, 86242799, 6819644, 20140249, 36930650, 54606384, 36780454, 33201905, 17037369, 26744917, 50567636, 67513640, 39986008, 56955985, 45996863, 84127901, 57803235, 17386996, 96709982, 82726008, 4064751, 30771409, 59318837, 6871053, 34432810, 50806615, 669105, 11365791, 82897371, 69697787, 8696647, 56531125, 65017137, 9886593, 44627776, 65038678, 18833224, 99917599, 15148031, 82327024, 36139942, 76825057, 8115266, 13470059, 4787945, 51588015, 4434662, 68041839, 45617087, 81855445, 58208470, 71965942, 17068582, 61623915, 75128745, 48395186, 36312813, 4515343, 60430369, 62115552, 75052463, 91831487, 70782102, 61271144, 36933038, 26392416, 36580610, 30366150, 22200378, 28734791, 34667286, 90061527, 51590803, 81677380, 64098930, 26734892, 30891921, 8807940, 24619760, 1569515, 176257, 99861373, 37224844, 78589145, 67031644, 7423788, 10649306, 31161687, 61728685, 47887470, 29959549, 37675718, 77312810, 82979980, 42967683, 7300047, 17058722, 76703609, 874791, 36505482, 30463802, 83948335, 37560164, 92541302, 75986488, 29188588, 6793819, 48658605, 51047803, 92604458, 54868730, 43322743, 77694700, 81172706, 2204165, 73109997, 18504197, 7066775, 86118021, 4099191, 47090124, 7011964, 8791066, 39373729, 18131876, 36135, 7517032, 4091162, 24915585, 37659250, 54232247, 74110882, 82532312, 49271185, 72238278, 72373496, 85571389, 51466049, 8733068, 4668450, 20002147, 3294781, 31727379, 84187166, 68939068, 6986898, 17385531, 46870723, 99224392, 48774913, 247198, 99971982, 67793644, 97641116, 86821229, 44983451, 74743862, 56515456, 36812683, 45049308, 54517921, 92530431, 93566986, 82339363, 68824981, 44842615, 9599614, 84349107, 22405120, 16405341, 9058407, 2891150, 97281847, 80251430, 78218845, 57961282, 83133790, 27185644, 90013093, 76315420, 68102437, 40865610, 11923835, 63372756, 6221471, 45237957, 3088684, 28787861, 54199160, 49328608, 96735716, 96420429, 7229550, 81805959, 30694952, 60106217, 83150534, 67084351, 42237907, 49236559, 44915235, 6157724, 17365188, 84166196, 54663246, 69848388, 20681921, 16595436, 3633375, 24058273, 65338021, 6153406, 53393358, 70596786, 55770687, 35456853, 62232211, 81898046, 44844121, 80246713, 68110330, 38061439, 33061250, 70004753, 76170907, 48360198, 62552524, 52613508, 30569392, 86301513, 63059024, 20765474, 55850790, 21289531, 16097038, 89804152, 82052050, 40197395, 84002370, 80555751, 61741594, 38365584, 29635537, 9175338, 18411915, 32426752, 112651, 33249630, 42692881, 37501808, 29466406, 91664334, 78766358, 14045383, 16424199, 52060076, 20122224, 90654836, 28851716, 91938191, 1204161, 76960303, 99021067, 73786944, 10264691, 18806856, 12024238, 1431742, 72357096, 824872, 70420215, 77072625, 79322415, 97783876, 45381876, 26292919, 22129328, 14349098, 71657078, 10453030, 96318094, 65851721, 72278539, 22721500, 89637706, 61960400, 32161669, 40677414, 90158785, 99549373, 85242963, 27411561, 26139110, 59371804, 30139692, 44473167, 47361209, 92215320, 13231279, 44348328, 25842078, 33304202, 35092039, 49882705, 73235980, 66250369, 79922758, 91802888, 9860968, 14326617, 88444207, 63628376, 6497830, 77272628, 14626618, 62430984, 15163258, 61712234, 45794415, 62490109, 86460488, 89046466, 15064655, 64157906, 89217461, 42199455, 62740044, 36845587, 62936963, 19101477, 41380093, 89214616, 70372191, 29029316, 33699435, 89811711, 77787724, 4119747, 27665211, 65074535, 1375023, 14731700, 59501487, 56424103, 34497327, 57658654, 38256849, 33565483, 57240218, 83155442, 1250437, 21397057, 40152546, 4985896, 2717150, 44550764, 68694897, 44846932, 71083565, 31904591, 10366309, 23432750, 26102057, 49598724, 8373289, 75820087, 3509435, 19939935, 38510840, 93359396, 99658235, 30998561, 6038457, 3183975, 74137926, 66903004, 95290172, 91990218, 57393458, 37280276, 17976208, 59405277, 92398073, 15948937, 59614746, 6525948, 20836893, 82178706, 61373987, 8729146, 43045786, 68875490, 38341669, 60581278, 61263068, 37183543, 80014588, 44847298, 35192533, 1239555, 72738685, 85116755, 7685448, 74724075, 7646095, 51464002, 83747892, 67281495, 99965001, 4508700, 59329511, 27041967, 52204879, 43376279, 31126490, 40781449, 72732205, 4069912, 72777973, 43152977, 37435892, 40781854, 66319530, 73021291, 99226875, 77187825, 53632373, 99515901, 39553046, 69136837, 4806458, 33431960, 21001913, 17894977, 16445503, 88251446, 34698428, 66667729, 68702632, 2208785, 8651647, 55470718, 84406788, 4978543, 65454636, 97011160, 22997281, 1197320, 65715134, 78884452, 20486294, 98648327, 6111563, 79880247, 91957544, 49597667, 17146629, 77898274, 20645197, 11757872, 54058788, 45306070, 321665, 77300457, 95251277, 32274392, 72725103, 26493119, 26776922, 78549759, 85023028, 59981773, 75229462, 13862149, 38008118, 98130363, 89499542, 17016156, 22113133, 5970607, 16684829, 5822202, 83269727, 43506672, 30395570, 31733363, 82886381, 94595668, 94516935, 8055981, 7919588, 569864, 29516592, 13819358, 12664567 +3509435, 45381876, 48675329, 11161731, 9575954, 52204879, 31727379, 84127901, 92353856, 19376156, 91240048, 66611759, 33895336, 44915235, 24826575, 63506281, 9188443, 6808825, 10264691, 89699445, 36396314, 168541, 72278539, 96420429, 68816413, 97011160, 81853704, 61928316, 69255765, 66116458, 1022677, 48088883, 66271566, 66322959, 41380093, 99690194, 73109997, 99965001, 4508700, 54232247, 69355476, 63015256, 50188404, 67474219, 70420215, 45996863, 57803235, 83651211, 97940276, 17539625, 35092039, 65271999, 58224549, 53842979, 95010552, 15902805, 54263880, 88130087, 64157906, 52613508, 62051033, 92867155, 47887470, 77301523, 53666583, 55189057, 42307449, 83948335, 92692283, 92541302, 34295794, 15535065, 70372191, 29029316, 18663507, 7182642, 16054533, 9257405, 56484900, 4668450, 14731700, 67030811, 41092102, 86022504, 24168411, 56955985, 22129328, 50806615, 90090964, 61141874, 45075471, 84349107, 35512853, 90272749, 86001008, 17957593, 61623915, 66250369, 3880712, 4515343, 6038457, 23569917, 55470718, 75229462, 36468541, 1788101, 67084351, 30366150, 749283, 67451935, 6157724, 65454636, 10309525, 18466635, 84166196, 65715134, 35456853, 62232211, 38009615, 176257, 76170907, 8129978, 7423788, 95395112, 33123618, 569864, 94360702, 72793444, 99729357, 84904436, 55615885, 30653863, 26507214, 415901, 73124510, 7685448, 7646095, 18783702, 71333116, 59910624, 41481685, 79806380, 7687278, 29994197, 83302115, 43152977, 86242799, 66319530, 57240218, 76330843, 62693428, 94911072, 65017137, 44627776, 99917599, 26664538, 76825057, 13173644, 69641513, 51715482, 49882705, 28550822, 54762643, 3088684, 66045587, 96735716, 42782652, 26114953, 9259676, 89419466, 59981773, 42237907, 57393458, 22200378, 9829782, 15075176, 34667286, 27325428, 17365188, 68128438, 6525948, 20681921, 12416768, 70004753, 64602895, 30090481, 77377183, 30569392, 43045786, 68875490, 65275241, 38341669, 82886381, 61263068, 93933709, 42967683, 3233569, 58180653, 36685023, 52293995, 34698463, 874791, 62740044, 44847298, 7955293, 48892201, 38365584, 45428665, 29188588, 29635537, 98371444, 42692881, 2204165, 6505939, 79136082, 7066775, 56473732, 89811711, 71316369, 4091162, 30811010, 65047700, 19272365, 44479073, 56153345, 72777973, 51466049, 60393039, 1431742, 60955663, 20002147, 56424103, 79322415, 54606384, 87710366, 89078848, 17386996, 29819940, 10453030, 32151165, 34432810, 669105, 4985896, 11365791, 45306070, 62452034, 9886593, 526217, 65038678, 43506672, 71083565, 54517921, 39553046, 82327024, 27411561, 66137019, 22435353, 33797252, 78218845, 57961282, 75820087, 84684495, 26863229, 33304202, 17894977, 22450468, 28928175, 11923835, 63372756, 6221471, 36312813, 33628349, 38494874, 98943869, 26392416, 13862149, 51472275, 32250045, 90061527, 22997281, 34493392, 62430984, 94539824, 64098930, 53547802, 65338021, 22879907, 8807940, 24619760, 44177011, 89046466, 12571310, 75135448, 55753905, 67031644, 13348726, 68000591, 61728685, 55850790, 77312810, 82979980, 16097038, 26275734, 40197395, 76671482, 51149804, 76703609, 51507425, 73617245, 80555751, 62936963, 36505482, 37739481, 35192533, 61815223, 61741594, 32590267, 49597667, 37560164, 23110625, 85116755, 16099750, 71300104, 89214616, 112651, 63967300, 77694700, 12348118, 81172706, 71522968, 37501808, 55960386, 50007421, 99297164, 68644627, 29466406, 97379790, 90654836, 4119747, 71920426, 92033260, 50668599, 72238278, 18806856, 47298834, 20645197, 64055960, 6819644, 77284619, 73021291, 99226875, 66663942, 96193415, 77072625, 36930650, 38256849, 33201905, 83269727, 62428472, 40686254, 17081350, 96709982, 52261574, 46870723, 47738185, 46540998, 40534591, 54058788, 6871053, 94076128, 10597197, 99971982, 65851721, 2717150, 48893685, 69697787, 8696647, 68694897, 2917920, 56531125, 72358170, 99125126, 45049308, 43501211, 83368048, 79191827, 44481640, 24733232, 32161669, 9599614, 10366309, 8115266, 51588015, 76540481, 35996293, 75543508, 49598724, 33336148, 97561745, 30139692, 44473167, 21533347, 13231279, 44348328, 3233084, 25842078, 38510840, 21001913, 93359396, 84840549, 26998766, 78785507, 68102437, 7104732, 73235980, 66667729, 28787861, 30998561, 49328608, 2208785, 60430369, 79922758, 91802888, 81805959, 78549759, 81274566, 83150534, 91990218, 63628376, 93515664, 37957788, 20885148, 15163258, 38008118, 69848388, 32699744, 78884452, 21993752, 38658347, 80246713, 40984766, 99604946, 38061439, 70727211, 99861373, 93790285, 40027975, 59197747, 5482538, 78589145, 98648327, 3235882, 47213183, 86301513, 10649306, 98739783, 92554120, 20765474, 31161687, 73168565, 89217461, 7300047, 42199455, 8913721, 16019925, 84002370, 1239555, 77620120, 75986488, 39847321, 6793819, 96906410, 54868730, 69786756, 59177718, 32426752, 36808486, 63152504, 51464002, 83747892, 51426311, 8791066, 17146629, 39373729, 34044787, 18131876, 43376279, 91664334, 96726697, 31126490, 55143724, 32058615, 68316156, 40781449, 37659250, 74110882, 33935899, 16684829, 68204242, 23194618, 72373496, 40356877, 95726235, 31491938, 99524975, 37435892, 83083131, 40781854, 30218878, 77187825, 11581181, 33565483, 17037369, 84187166, 39986008, 45848907, 17385531, 1250437, 48774913, 44060493, 59318837, 45995325, 40152546, 16380211, 73031054, 14220886, 82897371, 321665, 29065964, 19457589, 36753250, 16387575, 18833224, 74614639, 32274392, 68824981, 93057697, 5267545, 90158785, 23432750, 72725103, 92787493, 22405120, 16405341, 9058407, 48673079, 26102057, 59371804, 93053405, 97281847, 8373289, 8634541, 57241521, 94090109, 81855445, 92215320, 19939935, 83133790, 17068582, 8099994, 87160386, 3487592, 48395186, 62357986, 8055981, 44664587, 93270084, 57248122, 7229550, 62115552, 51315460, 20568363, 8651647, 85023028, 42947632, 14093520, 47792865, 21919959, 95290172, 61271144, 70036438, 36189527, 17976208, 15948937, 30764367, 54663246, 1197320, 7919588, 20836893, 67227442, 3633375, 45794415, 89499542, 73222868, 22942635, 44844121, 30787683, 1569515, 83789391, 8729146, 78561158, 92071990, 75407347, 63059024, 5640302, 21289531, 37675718, 60102965, 17058722, 82052050, 30543215, 13468268, 54427233, 79880247, 65081429, 2331773, 46851987, 4798568, 36845587, 78909561, 19101477, 18411915, 42073124, 33249630, 12303248, 22766820, 66885828, 5073754, 27187213, 74724075, 47708850, 86118021, 47090124, 25636669, 33237508, 16971929, 78766358, 81774825, 7517032, 52060076, 91938191, 49271185, 1204161, 86798033, 14363867, 87720882, 39201414, 45919976, 16567550, 12024238, 1375023, 59501487, 5822202, 61982238, 57658654, 9603598, 11757872, 62762857, 26744917, 50567636, 8128637, 6986898, 82726008, 99224392, 17727650, 7502255, 97057187, 61897582, 66832478, 53888755, 91255408, 44550764, 9398733, 89637706, 88904910, 82339363, 15148031, 36139942, 92803766, 83210802, 40677414, 59109400, 13470059, 17764950, 41092172, 70800879, 85242963, 83533741, 79942022, 14947650, 91727510, 2891150, 45790169, 68041839, 45617087, 80251430, 29510992, 69579137, 71965942, 72019362, 88251446, 40865610, 53084256, 68702632, 79657802, 508198, 78602717, 30694952, 1936762, 60106217, 36933038, 54014062, 84406788, 64848072, 4199704, 4978543, 72495719, 14626618, 32159704, 34236719, 14723410, 15015906, 16595436, 70596786, 19898053, 73392814, 69605283, 21070110, 21673260, 33061250, 3773993, 30395570, 61373987, 20867149, 15064655, 19486173, 48360198, 65880522, 44784505, 13819358, 42644903, 75777973, 60581278, 33553959, 37183543, 39171895, 11543098, 48260151, 9175338, 48658605, 92998591, 70367851, 71469330, 93562257, 70541760, 56461322, 33699435, 22113133, 59329511, 38022834, 77787724, 2607799, 85711894, 16424199, 49641577, 61859143, 24915585, 27625735, 82532312, 76960303, 73786944, 94711842, 55247455, 78300864, 3294781, 824872, 34497327, 74452589, 55669657, 26292919, 53632373, 83155442, 99515901, 4064751, 79821897, 6521313, 67124282, 77300457, 61960400, 92530431, 93566986, 23848565, 38645117, 45407418, 20642888, 45667668, 19891772, 58208470, 96852321, 27185644, 90013093, 72274002, 16445503, 75153252, 91141395, 88653118, 45237957, 95581843, 26776922, 55602660, 3183975, 74137926, 91831487, 26397786, 36580610, 10358899, 9860195, 59405277, 92398073, 53802686, 24058273, 20486294, 82178706, 30891921, 62490109, 68110330, 86460488, 64087743, 79738755, 76434144, 62803858, 29959549, 43933006, 98653983, 85117092, 72614359, 91957544, 57163802, 92604458, 43322743, 41245325, 23740167, 4099191, 7011964, 29834512, 27041967, 74357852, 77898274, 57359924, 90004325, 20122224, 72732205, 65074535, 4069912, 99021067, 29401781, 85571389, 72357096, 20140249, 68939068, 67513640, 37192445, 12664567, 77413012, 14349098, 5832946, 30771409, 71657078, 96318094, 97641116, 54987042, 86821229, 44983451, 22721500, 74743862, 56515456, 5111370, 61859581, 44842615, 90310261, 60176618, 4787945, 99549373, 4095116, 94516935, 26139110, 4434662, 29516592, 47361209, 98462867, 88698958, 43357947, 90457870, 99658235, 58749, 10961421, 34698428, 82427263, 75052463, 66903004, 9860968, 88444207, 28734791, 89996536, 59614746, 98130363, 24591705, 61712234, 53393358, 55770687, 53648154, 22108665, 62552524, 45990383, 6111563, 4204661, 26063929, 80014588, 84293052, 30463802, 44245960, 37620363, 18504197, 67281495, 5970607, 92692978, 66428795, 8733068, 74441448, 37166608, 99333375, 15536795, 82779622, 37891451, 67793644, 31904591, 69136837, 4806458, 33431960, 9623492, 54199160, 14326617, 70782102, 49236559, 37280276, 51590803, 81677380, 85695762, 81898046, 37224844, 39801351, 29932657, 51047803, 44889423, 58751351, 14045383, 36135, 28851716, 27665211, 24314885, 24953498, 98948034, 62496012, 36780454, 34946859, 30163921, 44846932, 36812683, 91281584, 26493119, 18001617, 76315420, 28796059, 47893286, 6497830, 77272628, 26734892, 17857111, 87598888, 31733363, 23134715, 89804152, 95957797, 88047921, 18699206, 247198, 94595668, 75128745, 6153406, 86543538, 82651278, 50702367, 95251277, 80316608, 41442762, 97783876, 17016156, 21397057, 72738685, 57020481 +69605283, 4064751, 62452034, 72019362, 3880712, 76170907, 60581278, 16097038, 48892201, 14731700, 6819644, 31727379, 14220886, 88904910, 51588015, 58749, 38494874, 98943869, 4199704, 40984766, 7423788, 84293052, 65081429, 112651, 93562257, 79136082, 70541760, 56473732, 95957797, 77787724, 74357852, 7182642, 18806856, 79322415, 39553046, 4095116, 18001617, 17957593, 54762643, 26114953, 59981773, 10358899, 44915235, 17365188, 62490109, 44177011, 89046466, 37675718, 77312810, 6111563, 76671482, 8913721, 84904436, 78909561, 61741594, 63967300, 37501808, 63152504, 31126490, 54232247, 69355476, 20645197, 74441448, 66319530, 73021291, 72357096, 59501487, 96193415, 11581181, 96709982, 82779622, 29819940, 40534591, 94076128, 97057187, 168541, 26664538, 9599614, 8115266, 90158785, 45617087, 58208470, 43357947, 6038457, 78549759, 9860968, 95010552, 9829782, 97011160, 67227442, 21993752, 35456853, 33061250, 70004753, 30090481, 9575954, 75407347, 98739783, 55850790, 29932657, 47887470, 76703609, 73617245, 4798568, 36845587, 62936963, 57163802, 16099750, 98371444, 70372191, 33249630, 18663507, 4099191, 27041967, 96726697, 90004325, 4091162, 27625735, 65047700, 99021067, 72238278, 55247455, 83083131, 30218878, 24168411, 83269727, 57240218, 57803235, 30771409, 66832478, 54987042, 90090964, 44550764, 56515456, 526217, 95251277, 71083565, 84349107, 69136837, 13470059, 14947650, 21533347, 44348328, 69641513, 76315420, 53084256, 11923835, 91141395, 6221471, 93270084, 28787861, 33895336, 96735716, 96420429, 79922758, 78602717, 23569917, 61271144, 36933038, 26392416, 42237907, 749283, 70036438, 6497830, 15075176, 9860195, 6157724, 77272628, 32159704, 34236719, 81853704, 70596786, 21070110, 21673260, 176257, 93790285, 40027975, 8729146, 12571310, 76434144, 22108665, 10649306, 42644903, 29959549, 569864, 82052050, 52293995, 30653863, 2331773, 1239555, 91957544, 39847321, 7685448, 51047803, 69786756, 22766820, 66885828, 7646095, 71469330, 58751351, 34044787, 68644627, 38022834, 41481685, 32058615, 52060076, 24314885, 18699206, 50188404, 9257405, 40356877, 51466049, 12024238, 99524975, 99333375, 37192445, 84127901, 40686254, 82726008, 14349098, 53888755, 44846932, 43506672, 61960400, 45075471, 92530431, 82339363, 44481640, 15148031, 91240048, 60176618, 23432750, 17764950, 27411561, 79942022, 94516935, 75543508, 4434662, 97561745, 8634541, 57961282, 71965942, 27185644, 90013093, 90457870, 93359396, 87160386, 34698428, 9623492, 66250369, 28796059, 33628349, 51315460, 75052463, 60106217, 1788101, 48675329, 61712234, 38658347, 53648154, 12416768, 99604946, 54263880, 64602895, 64157906, 61728685, 33123618, 21289531, 77301523, 33553959, 48088883, 55189057, 11543098, 26063929, 63506281, 26507214, 72614359, 30463802, 49597667, 15535065, 72738685, 6793819, 42692881, 44245960, 81172706, 36808486, 6505939, 51464002, 51426311, 55960386, 7011964, 29834512, 55143724, 85711894, 14045383, 49641577, 68316156, 57359924, 90654836, 33935899, 19272365, 66428795, 16684829, 68204242, 72373496, 29994197, 8733068, 60393039, 78300864, 20002147, 98948034, 77072625, 11757872, 97783876, 87710366, 33565483, 17037369, 8128637, 17385531, 46870723, 71657078, 10453030, 6871053, 37891451, 65851721, 73031054, 72278539, 22721500, 92353856, 62693428, 89637706, 36812683, 36753250, 16387575, 32274392, 79191827, 23848565, 5267545, 36139942, 90310261, 70800879, 97940276, 59371804, 80316608, 22435353, 90272749, 33431960, 69579137, 47361209, 17539625, 84684495, 88698958, 19939935, 25842078, 22450468, 26998766, 68102437, 63372756, 65271999, 73235980, 66667729, 95581843, 82427263, 62115552, 81805959, 42947632, 47792865, 75229462, 21919959, 36468541, 91990218, 51472275, 36189527, 17976208, 18466635, 16595436, 32699744, 24058273, 65338021, 73392814, 55770687, 81898046, 68110330, 30395570, 70727211, 31733363, 37224844, 75135448, 19486173, 65880522, 5482538, 77377183, 3235882, 8129978, 5640302, 68875490, 13819358, 92554120, 92867155, 62803858, 82886381, 1022677, 89217461, 42967683, 72793444, 98653983, 13468268, 66322959, 51507425, 874791, 44847298, 37739481, 92541302, 34295794, 29188588, 99690194, 67281495, 7066775, 50007421, 56461322, 17146629, 33237508, 52204879, 2607799, 91664334, 82651278, 71920426, 7687278, 87720882, 23194618, 29401781, 10264691, 83302115, 1375023, 67474219, 3294781, 61982238, 9603598, 41092102, 86022504, 37166608, 56955985, 26292919, 17081350, 1250437, 22129328, 45995325, 247198, 10597197, 34432810, 50806615, 669105, 11365791, 82897371, 69697787, 2917920, 9886593, 45049308, 65038678, 18833224, 83368048, 32161669, 92803766, 59109400, 99549373, 41092172, 16405341, 76540481, 83533741, 93053405, 33797252, 49598724, 68041839, 97281847, 44473167, 57241521, 13173644, 29510992, 75820087, 98462867, 38510840, 33304202, 17894977, 78785507, 40865610, 28550822, 3487592, 8055981, 3088684, 36312813, 68702632, 60430369, 91802888, 30694952, 81274566, 89419466, 14093520, 14326617, 70782102, 36580610, 63628376, 37957788, 65454636, 92398073, 20885148, 90061527, 22997281, 68128438, 62430984, 15163258, 6525948, 26734892, 7919588, 20836893, 64087743, 3773993, 61373987, 20867149, 99861373, 98648327, 68000591, 44784505, 86301513, 43045786, 38341669, 73168565, 86543538, 94360702, 43933006, 3233569, 53666583, 17058722, 40197395, 36685023, 99729357, 16019925, 46851987, 19101477, 83948335, 75986488, 29635537, 48658605, 71300104, 92604458, 59177718, 42073124, 12303248, 77694700, 27187213, 12348118, 74724075, 37620363, 83747892, 6808825, 18504197, 47090124, 33699435, 25636669, 8791066, 41442762, 22113133, 71316369, 43376279, 88047921, 59910624, 77898274, 30811010, 37659250, 79806380, 86798033, 76960303, 14363867, 39201414, 73786944, 16567550, 56484900, 64055960, 86242799, 4668450, 56424103, 70420215, 36930650, 74452589, 55669657, 54606384, 36780454, 33201905, 89699445, 62428472, 67513640, 39986008, 45381876, 45996863, 12664567, 89078848, 50702367, 17386996, 99224392, 21397057, 7502255, 46540998, 34946859, 54058788, 86821229, 4985896, 74743862, 8696647, 9398733, 57020481, 43501211, 74614639, 24733232, 61859581, 44842615, 93057697, 4787945, 35512853, 45407418, 35996293, 91727510, 2891150, 26493119, 29516592, 8373289, 78218845, 92215320, 26863229, 3233084, 21001913, 17068582, 16445503, 84840549, 88251446, 99658235, 61623915, 88653118, 7104732, 58224549, 53842979, 26776922, 66045587, 30998561, 2208785, 57248122, 3183975, 8651647, 1936762, 68816413, 55470718, 67084351, 57393458, 30366150, 54014062, 13862149, 84406788, 37280276, 47893286, 93515664, 28734791, 67451935, 4978543, 10309525, 32250045, 27325428, 69848388, 17857111, 15015906, 20681921, 45794415, 15902805, 8807940, 38009615, 24619760, 87598888, 55753905, 24826575, 45990383, 52613508, 47213183, 92071990, 30569392, 95395112, 20765474, 31161687, 82979980, 37183543, 7300047, 39171895, 26275734, 89804152, 42199455, 34698463, 42307449, 55615885, 80555751, 36505482, 35192533, 7955293, 415901, 73124510, 23110625, 9175338, 9188443, 43322743, 71522968, 23740167, 99965001, 18783702, 4508700, 59329511, 5970607, 29466406, 18131876, 97379790, 16424199, 27665211, 49271185, 1204161, 85571389, 45919976, 95726235, 94711842, 31491938, 43152977, 60955663, 40781854, 77284619, 20140249, 66663942, 62762857, 77187825, 84187166, 68939068, 26744917, 45848907, 53632373, 6986898, 76330843, 48774913, 5832946, 32151165, 30163921, 44983451, 48893685, 56531125, 321665, 72358170, 44627776, 91281584, 77300457, 54517921, 93566986, 68824981, 40677414, 72725103, 4806458, 22405120, 85242963, 66137019, 45790169, 33336148, 80251430, 81855445, 13231279, 3509435, 86001008, 83133790, 35092039, 72274002, 51715482, 49882705, 28928175, 75153252, 75128745, 42782652, 508198, 4515343, 7229550, 85023028, 83150534, 95290172, 88444207, 26397786, 49236559, 53802686, 34667286, 34493392, 94539824, 89996536, 84166196, 64098930, 14723410, 65715134, 53547802, 53393358, 89499542, 20486294, 44844121, 80246713, 38061439, 30787683, 83789391, 15064655, 61928316, 62552524, 78561158, 69255765, 62051033, 61263068, 60102965, 58180653, 30543215, 51149804, 85117092, 62740044, 32590267, 37560164, 92692283, 18411915, 89214616, 54868730, 92998591, 29029316, 47708850, 70367851, 73109997, 39373729, 89811711, 78766358, 7517032, 61859143, 28851716, 92692978, 74110882, 65074535, 63015256, 4069912, 44479073, 56153345, 24953498, 1431742, 824872, 34497327, 50567636, 17727650, 59318837, 16380211, 94595668, 67793644, 97641116, 2717150, 6521313, 68694897, 67124282, 5111370, 94911072, 19457589, 10366309, 19376156, 83210802, 83651211, 92787493, 20642888, 45667668, 19891772, 8099994, 79657802, 9259676, 20568363, 91831487, 11161731, 51590803, 59614746, 54663246, 1197320, 24591705, 3633375, 6153406, 78884452, 85695762, 82178706, 22879907, 22942635, 86460488, 78589145, 75777973, 17016156, 93933709, 66271566, 80014588, 84002370, 38365584, 41380093, 45428665, 2204165, 41245325, 86118021, 99297164, 16971929, 16054533, 24915585, 40781449, 4119747, 82532312, 91938191, 37435892, 38256849, 77413012, 52261574, 47738185, 44060493, 40152546, 91255408, 99125126, 61141874, 29065964, 38645117, 48673079, 30139692, 94090109, 48395186, 74137926, 64848072, 15948937, 30764367, 30891921, 1569515, 79738755, 48360198, 67031644, 13348726, 39801351, 4204661, 79880247, 77620120, 85116755, 32426752, 36135, 81774825, 20122224, 72732205, 92033260, 50668599, 72777973, 47298834, 15536795, 36396314, 99515901, 96318094, 65017137, 99917599, 31904591, 82327024, 26139110, 44664587, 54199160, 66903004, 22200378, 72495719, 81677380, 14626618, 73222868, 59197747, 88130087, 65275241, 48260151, 54427233, 5073754, 44889423, 71333116, 67030811, 5822202, 57658654, 79821897, 45306070, 45237957, 55602660, 59405277, 38008118, 23134715, 61815223, 96906410, 62496012, 99226875, 83155442, 99971982, 9058407, 96852321, 62357986, 49328608, 98130363, 19898053, 63059024, 61897582, 76825057, 26102057, 10961421, 66611759, 62232211, 66116458 +84840549, 19376156, 72274002, 60430369, 96906410, 4508700, 24915585, 65047700, 26744917, 67793644, 16387575, 49882705, 62357986, 20765474, 89214616, 97379790, 33935899, 91938191, 12664567, 50702367, 99971982, 33431960, 6221471, 9259676, 36189527, 17976208, 53802686, 38008118, 24058273, 37224844, 59197747, 69255765, 86543538, 5073754, 72732205, 7687278, 16567550, 70420215, 9603598, 86022504, 36396314, 54058788, 92353856, 29065964, 45049308, 71083565, 31904591, 69136837, 72725103, 9058407, 21533347, 19939935, 3233084, 90013093, 58224549, 66250369, 3183975, 20568363, 91831487, 14626618, 54663246, 6525948, 65338021, 8129978, 55850790, 60581278, 3233569, 26275734, 415901, 45428665, 54868730, 37620363, 79806380, 24314885, 12024238, 1431742, 67474219, 824872, 74452589, 15536795, 47738185, 6871053, 97057187, 82897371, 45306070, 67124282, 61960400, 36139942, 92803766, 40677414, 99549373, 92787493, 33797252, 30139692, 78218845, 44348328, 69641513, 76315420, 93359396, 75128745, 10961421, 44664587, 54199160, 30998561, 75052463, 75229462, 83150534, 95290172, 57393458, 63628376, 59405277, 27325428, 90061527, 30764367, 69848388, 15015906, 3633375, 55770687, 81898046, 30891921, 44844121, 8807940, 54263880, 176257, 8729146, 62552524, 45990383, 86301513, 7423788, 5640302, 61728685, 11543098, 66322959, 62740044, 32590267, 37560164, 18411915, 98371444, 32426752, 42692881, 29029316, 73109997, 6808825, 34044787, 5970607, 29466406, 40781449, 4119747, 92033260, 85571389, 73786944, 8733068, 43152977, 40781854, 99226875, 55669657, 37166608, 33201905, 99333375, 50567636, 44060493, 34946859, 96318094, 16380211, 61897582, 91255408, 74743862, 56531125, 36812683, 91281584, 36753250, 82339363, 68824981, 44481640, 82327024, 44842615, 20642888, 94516935, 29516592, 29510992, 84684495, 17068582, 78785507, 53084256, 66611759, 3088684, 95581843, 74137926, 85023028, 47792865, 26397786, 13862149, 93515664, 28734791, 65454636, 51590803, 81677380, 32159704, 84166196, 73222868, 62232211, 68110330, 12416768, 86460488, 30395570, 61373987, 1569515, 79738755, 55753905, 64157906, 98648327, 92071990, 75407347, 30569392, 66116458, 39801351, 95395112, 42644903, 65275241, 62803858, 75777973, 73168565, 33123618, 82979980, 42967683, 7300047, 42199455, 66271566, 34698463, 42307449, 48260151, 63506281, 54427233, 84002370, 35192533, 30463802, 73124510, 77620120, 29635537, 92604458, 42073124, 47708850, 70367851, 71522968, 18504197, 41245325, 67281495, 56461322, 33699435, 17146629, 39373729, 33237508, 71316369, 59329511, 95957797, 71333116, 32058615, 7517032, 49271185, 68204242, 23194618, 72777973, 47298834, 20645197, 83083131, 4668450, 77284619, 62496012, 59501487, 57658654, 54606384, 17037369, 39986008, 45848907, 76330843, 48774913, 21397057, 5832946, 97641116, 72278539, 14220886, 9886593, 57020481, 65038678, 18833224, 54517921, 39553046, 15148031, 38645117, 51588015, 41092172, 26102057, 83533741, 27411561, 22435353, 93053405, 49598724, 19891772, 58208470, 35092039, 8099994, 16445503, 22450468, 26998766, 68102437, 88251446, 40865610, 8055981, 49328608, 2208785, 96420429, 26114953, 68816413, 60106217, 55470718, 14326617, 36468541, 84406788, 18466635, 34493392, 59614746, 34236719, 17857111, 7919588, 81853704, 20681921, 16595436, 65715134, 53547802, 6153406, 45794415, 53393358, 85695762, 21993752, 38658347, 20486294, 22879907, 22942635, 40984766, 31733363, 64602895, 88130087, 67031644, 22108665, 3235882, 47213183, 13819358, 31161687, 62051033, 93933709, 48088883, 55189057, 40197395, 36685023, 80014588, 46851987, 48892201, 83948335, 38365584, 41380093, 92541302, 29188588, 57163802, 85116755, 99690194, 59177718, 12303248, 66885828, 77694700, 44889423, 2204165, 23740167, 50007421, 25636669, 58751351, 27041967, 74357852, 19272365, 14363867, 44479073, 16684829, 50668599, 72373496, 29994197, 39201414, 18806856, 37435892, 24953498, 60955663, 14731700, 67030811, 5822202, 66663942, 96193415, 11757872, 41092102, 68939068, 17081350, 14349098, 29819940, 46540998, 32151165, 37891451, 34432810, 94595668, 54987042, 86821229, 6521313, 90090964, 11365791, 65017137, 88904910, 99917599, 10366309, 23848565, 83210802, 90310261, 70800879, 14947650, 91727510, 2891150, 26139110, 4434662, 68041839, 13173644, 69579137, 92215320, 57961282, 25842078, 83133790, 38510840, 33304202, 51715482, 99658235, 3487592, 88653118, 73235980, 45237957, 66667729, 36312813, 96735716, 4515343, 57248122, 7229550, 81805959, 23569917, 30694952, 8651647, 1936762, 91990218, 54014062, 10358899, 47893286, 51472275, 48675329, 67451935, 20885148, 15948937, 34667286, 72495719, 77272628, 17365188, 97011160, 1197320, 26734892, 15902805, 64087743, 99604946, 70727211, 87598888, 19486173, 24826575, 5482538, 78589145, 77377183, 9575954, 78561158, 98739783, 38341669, 92867155, 94360702, 43933006, 4204661, 58180653, 82052050, 13468268, 51507425, 79880247, 73617245, 2331773, 80555751, 61815223, 7955293, 19101477, 61741594, 91957544, 15535065, 72738685, 39847321, 6793819, 9175338, 7685448, 71300104, 70372191, 112651, 33249630, 22766820, 74724075, 44245960, 83747892, 70541760, 51426311, 18783702, 86118021, 47090124, 7011964, 22113133, 77787724, 16971929, 2607799, 91664334, 78766358, 16054533, 36135, 54232247, 92692978, 74110882, 1204161, 86798033, 87720882, 9257405, 45919976, 10264691, 51466049, 94711842, 31491938, 1375023, 56484900, 55247455, 78300864, 20002147, 30218878, 20140249, 34497327, 77187825, 38256849, 24168411, 83269727, 62428472, 56955985, 77413012, 53632373, 40686254, 99515901, 22129328, 99224392, 79821897, 66832478, 2717150, 69697787, 68694897, 2917920, 9398733, 44627776, 95251277, 61859581, 84349107, 4787945, 4806458, 45407418, 22405120, 16405341, 35996293, 66137019, 75543508, 59371804, 45790169, 33336148, 26493119, 45667668, 45617087, 97281847, 80251430, 47361209, 75820087, 3509435, 17894977, 72019362, 63372756, 48395186, 68702632, 28796059, 3880712, 61271144, 1788101, 67084351, 30366150, 64848072, 4978543, 15075176, 11161731, 10309525, 15163258, 14723410, 98130363, 24591705, 61712234, 89499542, 62490109, 44177011, 30787683, 20867149, 76170907, 65880522, 52613508, 63059024, 43045786, 29932657, 29959549, 569864, 37183543, 23134715, 60102965, 98653983, 52293995, 16019925, 84904436, 30653863, 65081429, 78909561, 36505482, 1239555, 23110625, 69786756, 27187213, 12348118, 18663507, 71469330, 6505939, 7066775, 8791066, 99297164, 89811711, 38022834, 29834512, 52204879, 96726697, 31126490, 16424199, 49641577, 68316156, 52060076, 27625735, 69355476, 27665211, 18699206, 65074535, 56153345, 60393039, 73021291, 3294781, 61982238, 31727379, 33565483, 89078848, 84127901, 17385531, 52261574, 4064751, 82779622, 71657078, 10453030, 45995325, 40152546, 10597197, 65851721, 73031054, 50806615, 48893685, 8696647, 56515456, 89637706, 94911072, 72358170, 99125126, 44846932, 61141874, 19457589, 526217, 77300457, 83368048, 79191827, 93566986, 9599614, 93057697, 91240048, 59109400, 60176618, 90158785, 4095116, 81855445, 88698958, 21001913, 61623915, 65271999, 9623492, 93270084, 26776922, 66045587, 42782652, 508198, 79922758, 91802888, 33628349, 38494874, 98943869, 81274566, 42947632, 14093520, 21919959, 37280276, 4199704, 6497830, 9860195, 6157724, 92398073, 22997281, 62430984, 94539824, 64098930, 20836893, 70596786, 19898053, 53648154, 3773993, 93790285, 12571310, 48360198, 76434144, 68000591, 44784505, 10649306, 61263068, 77301523, 77312810, 16097038, 39171895, 89804152, 72793444, 30543215, 85117092, 8913721, 99729357, 44847298, 49597667, 92692283, 34295794, 75986488, 9188443, 92998591, 7646095, 37501808, 51464002, 99965001, 41442762, 68644627, 18131876, 43376279, 88047921, 7182642, 55143724, 59910624, 77898274, 37659250, 71920426, 82532312, 4069912, 29401781, 72238278, 95726235, 83302115, 74441448, 64055960, 86242799, 66319530, 62762857, 36780454, 11581181, 84187166, 45381876, 6986898, 30771409, 40534591, 62693428, 321665, 32274392, 45075471, 5267545, 83651211, 35512853, 17764950, 76540481, 85242963, 18001617, 90272749, 44473167, 17539625, 13231279, 96852321, 17957593, 43357947, 28928175, 11923835, 91141395, 53842979, 6038457, 78602717, 89419466, 59981773, 36580610, 49236559, 22200378, 749283, 37957788, 32250045, 89996536, 32699744, 73392814, 78884452, 69605283, 38061439, 33061250, 24619760, 89046466, 83789391, 75135448, 13348726, 30090481, 21289531, 89217461, 53666583, 26063929, 874791, 4798568, 37739481, 16099750, 48658605, 51047803, 63967300, 43322743, 81172706, 36808486, 63152504, 55960386, 4099191, 61859143, 57359924, 20122224, 28851716, 76960303, 40356877, 6819644, 56424103, 87710366, 67513640, 57803235, 17386996, 96709982, 82726008, 17727650, 59318837, 30163921, 44983451, 22721500, 44550764, 5111370, 62452034, 43501211, 74614639, 24733232, 26664538, 32161669, 76825057, 8115266, 48673079, 79942022, 80316608, 97561745, 8373289, 57241521, 98462867, 26863229, 71965942, 87160386, 55602660, 36933038, 26392416, 42237907, 44915235, 68128438, 21070110, 82178706, 21673260, 80246713, 99861373, 47887470, 17016156, 17058722, 51149804, 84293052, 26507214, 93562257, 14045383, 90004325, 66428795, 63015256, 50188404, 99524975, 98948034, 72357096, 77072625, 97783876, 89699445, 37192445, 83155442, 1250437, 46870723, 7502255, 168541, 669105, 97940276, 8634541, 94090109, 90457870, 28550822, 75153252, 34698428, 7104732, 78549759, 66903004, 70036438, 38009615, 61928316, 92554120, 1022677, 33553959, 6111563, 55615885, 72614359, 79136082, 81774825, 82651278, 4091162, 30811010, 79322415, 8128637, 26292919, 247198, 13470059, 86001008, 27185644, 58749, 54762643, 28787861, 79657802, 33895336, 9860968, 95010552, 88444207, 9829782, 67227442, 70004753, 40027975, 15064655, 76671482, 76703609, 36845587, 62936963, 85711894, 41481685, 90654836, 94076128, 4985896, 62115552, 51315460, 35456853, 68875490, 82886381, 99021067, 53888755, 43506672, 92530431, 23432750, 70782102, 37675718, 36930650, 45996863, 57240218, 82427263, 56473732 +99524975, 9860195, 75135448, 96735716, 36580610, 99861373, 36845587, 8791066, 17146629, 94595668, 92787493, 40984766, 12416768, 52293995, 62740044, 18783702, 25636669, 5970607, 85711894, 57359924, 68204242, 68939068, 37192445, 12664567, 17081350, 48774913, 96318094, 56515456, 526217, 26664538, 19376156, 35996293, 80251430, 84840549, 49882705, 95581843, 53842979, 66045587, 65715134, 53648154, 93790285, 30569392, 8129978, 92867155, 82886381, 37675718, 6111563, 7955293, 19101477, 77620120, 34295794, 51426311, 37659250, 65074535, 44479073, 56424103, 36780454, 84127901, 50702367, 99515901, 46870723, 86821229, 22721500, 2917920, 61141874, 5267545, 94516935, 98462867, 68102437, 91990218, 44915235, 6157724, 24058273, 86460488, 61373987, 64602895, 68000591, 7423788, 60581278, 33123618, 26275734, 40197395, 79880247, 92541302, 57163802, 98371444, 71300104, 2204165, 58751351, 33237508, 41481685, 52060076, 4091162, 74110882, 1204161, 74441448, 64055960, 73021291, 76330843, 21397057, 34946859, 97641116, 62693428, 71083565, 45075471, 79191827, 44481640, 10366309, 36139942, 84349107, 91240048, 85242963, 45617087, 44473167, 29510992, 27185644, 87160386, 53084256, 75128745, 508198, 96420429, 91802888, 9259676, 1788101, 26392416, 22200378, 4978543, 97011160, 22879907, 21673260, 33061250, 30395570, 21289531, 7300047, 42199455, 76671482, 11543098, 42307449, 63506281, 30653863, 30463802, 39847321, 96906410, 112651, 43322743, 6808825, 89811711, 38022834, 16971929, 30811010, 33935899, 66428795, 50668599, 9257405, 51466049, 1431742, 20002147, 62496012, 83269727, 99224392, 44060493, 50806615, 53888755, 91255408, 6521313, 11365791, 9398733, 62452034, 72358170, 65038678, 68824981, 15148031, 32161669, 69136837, 83210802, 40677414, 13470059, 35512853, 27411561, 91727510, 26998766, 48395186, 54762643, 3088684, 66250369, 36312813, 42782652, 4515343, 55602660, 23569917, 30694952, 38494874, 98943869, 91831487, 60106217, 42947632, 55470718, 21919959, 67084351, 13862149, 84406788, 10358899, 4199704, 36189527, 51590803, 68128438, 69848388, 32699744, 65338021, 82178706, 44844121, 70004753, 20867149, 87598888, 37224844, 24826575, 65880522, 67031644, 13348726, 3235882, 75407347, 20765474, 62803858, 4204661, 30543215, 26063929, 13468268, 80014588, 84904436, 65081429, 84002370, 35192533, 61741594, 415901, 1239555, 91957544, 38365584, 37560164, 41380093, 15535065, 54868730, 33249630, 12303248, 5073754, 74724075, 70367851, 79136082, 18504197, 23740167, 99965001, 86118021, 50007421, 22113133, 29834512, 7182642, 82651278, 4119747, 65047700, 19272365, 50188404, 95726235, 47298834, 78300864, 60955663, 4668450, 67474219, 3294781, 59501487, 38256849, 54606384, 89699445, 33565483, 26744917, 94076128, 669105, 90090964, 74743862, 48893685, 321665, 29065964, 36753250, 45049308, 54517921, 39553046, 83368048, 99917599, 82339363, 93057697, 60176618, 99549373, 22405120, 16405341, 48673079, 26102057, 14947650, 97940276, 18001617, 13173644, 69641513, 84684495, 21001913, 76315420, 40865610, 28550822, 63372756, 65271999, 58224549, 8055981, 93270084, 3880712, 68816413, 59981773, 34493392, 38008118, 54663246, 24591705, 15015906, 53393358, 78884452, 21993752, 38658347, 62490109, 64087743, 99604946, 24619760, 44177011, 1569515, 15064655, 76170907, 78589145, 45990383, 98739783, 39801351, 95395112, 75777973, 1022677, 93933709, 23134715, 60102965, 53666583, 72793444, 58180653, 36685023, 34698463, 76703609, 55615885, 26507214, 80555751, 9188443, 18411915, 70372191, 69786756, 42073124, 81172706, 71522968, 18663507, 37620363, 37501808, 36808486, 47090124, 39373729, 59329511, 95957797, 77787724, 29466406, 52204879, 18131876, 2607799, 96726697, 16054533, 16424199, 49641577, 72732205, 69355476, 49271185, 63015256, 14363867, 56153345, 23194618, 85571389, 29994197, 39201414, 73786944, 18806856, 60393039, 77284619, 20140249, 5822202, 66663942, 34497327, 70420215, 11757872, 62762857, 77187825, 55669657, 24168411, 33201905, 99333375, 31727379, 8128637, 77413012, 57803235, 17385531, 82726008, 4064751, 17727650, 46540998, 6871053, 37891451, 16380211, 10597197, 34432810, 72278539, 4985896, 89637706, 65017137, 16387575, 32274392, 9599614, 23848565, 90310261, 59109400, 4787945, 41092172, 76540481, 66137019, 75543508, 80316608, 45790169, 29516592, 78218845, 47361209, 21533347, 92215320, 75820087, 3509435, 44348328, 38510840, 90013093, 17068582, 22450468, 88251446, 91141395, 34698428, 66611759, 62357986, 44664587, 66667729, 28787861, 54199160, 79657802, 26776922, 30998561, 62115552, 8651647, 14326617, 42237907, 30366150, 63628376, 49236559, 37280276, 64848072, 93515664, 37957788, 27325428, 77272628, 62430984, 30764367, 64098930, 14723410, 98130363, 26734892, 17857111, 20836893, 20681921, 19898053, 55770687, 62232211, 15902805, 38061439, 3773993, 31733363, 55753905, 19486173, 5482538, 44784505, 52613508, 69255765, 92554120, 29932657, 77312810, 82979980, 43933006, 89804152, 55189057, 54427233, 2331773, 36505482, 61815223, 83948335, 49597667, 92692283, 75986488, 45428665, 7685448, 51047803, 92604458, 63967300, 22766820, 29029316, 47708850, 7646095, 71469330, 73109997, 6505939, 51464002, 83747892, 67281495, 55960386, 4099191, 56473732, 56461322, 33699435, 4508700, 99297164, 27041967, 91664334, 88047921, 32058615, 7517032, 77898274, 24915585, 28851716, 86798033, 99021067, 29401781, 72238278, 10264691, 56484900, 37435892, 83083131, 40781854, 67030811, 96193415, 57658654, 9603598, 79322415, 97783876, 84187166, 67513640, 39986008, 56955985, 45848907, 45381876, 89078848, 36396314, 53632373, 22129328, 30771409, 99971982, 65851721, 61897582, 66832478, 14220886, 2717150, 82897371, 69697787, 8696647, 45306070, 18833224, 43506672, 43501211, 61960400, 24733232, 92803766, 8115266, 72725103, 17764950, 20642888, 9058407, 70800879, 79942022, 22435353, 4434662, 68041839, 97281847, 8373289, 8634541, 69579137, 17539625, 57961282, 26863229, 86001008, 3233084, 33304202, 35092039, 99658235, 61623915, 75153252, 10961421, 11923835, 6221471, 7104732, 60430369, 57248122, 79922758, 6038457, 74137926, 66903004, 85023028, 36468541, 36933038, 88444207, 67451935, 59405277, 15948937, 22997281, 14626618, 94539824, 7919588, 45794415, 69605283, 85695762, 73222868, 30891921, 89046466, 79738755, 40027975, 48360198, 76434144, 64157906, 98648327, 61928316, 62552524, 9575954, 47213183, 78561158, 13819358, 31161687, 62051033, 61728685, 55850790, 33553959, 86543538, 39171895, 3233569, 98653983, 66322959, 51507425, 73617245, 46851987, 44847298, 48892201, 32590267, 73124510, 29188588, 16099750, 48658605, 92998591, 77694700, 27187213, 93562257, 41245325, 7011964, 41442762, 55143724, 97379790, 36135, 61859143, 90004325, 54232247, 71920426, 18699206, 72777973, 55247455, 20645197, 86242799, 14731700, 30218878, 77072625, 36930650, 37166608, 11581181, 17037369, 15536795, 57240218, 6986898, 40686254, 52261574, 14349098, 29819940, 7502255, 54058788, 59318837, 247198, 168541, 44550764, 92353856, 68694897, 44846932, 88904910, 91281584, 77300457, 74614639, 44842615, 38645117, 76825057, 90158785, 83533741, 2891150, 26139110, 33797252, 33336148, 97561745, 30139692, 33431960, 96852321, 25842078, 83133790, 17894977, 72274002, 93359396, 82427263, 3183975, 26114953, 78549759, 89419466, 47792865, 75229462, 95290172, 57393458, 47893286, 48675329, 65454636, 92398073, 53802686, 18466635, 32250045, 17365188, 32159704, 34236719, 6525948, 3633375, 73392814, 35456853, 81898046, 80246713, 68110330, 176257, 12571310, 22108665, 63059024, 10649306, 47887470, 17016156, 29959549, 569864, 37183543, 42967683, 82052050, 85117092, 99729357, 48260151, 23110625, 9175338, 89214616, 42692881, 66885828, 44245960, 63152504, 70541760, 7066775, 34044787, 68644627, 71333116, 74357852, 59910624, 20122224, 90654836, 27665211, 24314885, 7687278, 4069912, 72373496, 31491938, 24953498, 66319530, 98948034, 72357096, 824872, 74452589, 41092102, 96709982, 1250437, 71657078, 40534591, 10453030, 32151165, 79821897, 40152546, 97057187, 30163921, 67124282, 44627776, 36812683, 95251277, 31904591, 93566986, 83651211, 45407418, 93053405, 90272749, 94090109, 19891772, 71965942, 8099994, 51715482, 28928175, 58749, 88653118, 68702632, 28796059, 49328608, 51315460, 20568363, 1936762, 9860968, 81274566, 95010552, 61271144, 70036438, 28734791, 20885148, 81677380, 59614746, 61712234, 16595436, 53547802, 70596786, 20486294, 30787683, 66116458, 86301513, 5640302, 42644903, 65275241, 38341669, 77301523, 89217461, 16097038, 48088883, 17058722, 51149804, 16019925, 84293052, 72614359, 78909561, 37739481, 72738685, 29635537, 99690194, 12348118, 71316369, 43376279, 40781449, 92692978, 92033260, 87720882, 40356877, 45919976, 16567550, 83302115, 94711842, 1375023, 43152977, 6819644, 61982238, 62428472, 50567636, 45996863, 17386996, 5832946, 73031054, 67793644, 54987042, 5111370, 94911072, 9886593, 92530431, 61859581, 4806458, 59371804, 49598724, 26493119, 57241521, 19939935, 90457870, 3487592, 73235980, 78602717, 33628349, 75052463, 14093520, 83150534, 70782102, 749283, 11161731, 72495719, 89996536, 81853704, 6153406, 21070110, 83789391, 70727211, 59197747, 88130087, 30090481, 77377183, 92071990, 61263068, 94360702, 4798568, 62936963, 14045383, 81774825, 68316156, 82532312, 99226875, 83155442, 47738185, 44983451, 99125126, 19457589, 45667668, 58208470, 13231279, 88698958, 17957593, 16445503, 78785507, 9623492, 2208785, 81805959, 26397786, 9829782, 6497830, 17976208, 15075176, 90061527, 15163258, 84166196, 1197320, 67227442, 89499542, 54263880, 8729146, 43045786, 68875490, 73168565, 874791, 32426752, 78766358, 31126490, 27625735, 91938191, 76960303, 16684829, 12024238, 86022504, 26292919, 45995325, 82327024, 23432750, 51588015, 4095116, 72019362, 45237957, 51472275, 10309525, 34667286, 8807940, 66271566, 8913721, 85116755, 59177718, 44889423, 79806380, 87710366, 82779622, 56531125, 57020481, 81855445, 33895336, 7229550, 22942635, 38009615, 8733068, 54014062, 6793819, 43357947 +56424103, 91957544, 33797252, 62803858, 6111563, 53666583, 34044787, 84349107, 18001617, 72274002, 66250369, 6153406, 29959549, 90004325, 18699206, 20140249, 47738185, 46540998, 71083565, 39553046, 9599614, 68041839, 25842078, 6497830, 65454636, 32699744, 38061439, 40197395, 48260151, 84904436, 18411915, 68644627, 7182642, 97379790, 55669657, 83269727, 45848907, 14349098, 53888755, 44842615, 45667668, 58208470, 19939935, 8055981, 95581843, 749283, 54663246, 35456853, 98648327, 39801351, 23134715, 16097038, 94360702, 60102965, 89804152, 51507425, 26507214, 77620120, 59177718, 32426752, 47708850, 71469330, 51426311, 41442762, 5970607, 27041967, 88047921, 59910624, 9257405, 67474219, 99226875, 74452589, 34946859, 94076128, 22405120, 94516935, 59371804, 29516592, 99658235, 26776922, 2208785, 57248122, 7229550, 14093520, 49236559, 70036438, 92398073, 20681921, 30891921, 8807940, 3773993, 176257, 99861373, 12571310, 3235882, 69255765, 10649306, 95395112, 75777973, 43933006, 36845587, 7955293, 49597667, 41380093, 92604458, 54868730, 99690194, 81172706, 2204165, 36808486, 41245325, 4099191, 8791066, 52204879, 36135, 30811010, 19272365, 39201414, 73786944, 8733068, 24953498, 57658654, 26744917, 15536795, 83155442, 7502255, 79821897, 59318837, 45995325, 5111370, 62452034, 44846932, 45049308, 74614639, 61960400, 32274392, 31904591, 24733232, 51588015, 76540481, 45790169, 26493119, 97281847, 97561745, 21533347, 69641513, 90457870, 58749, 6221471, 79657802, 66045587, 3183975, 78602717, 1936762, 75229462, 83150534, 26392416, 13862149, 48675329, 4199704, 6525948, 61712234, 70596786, 82178706, 33061250, 30395570, 19486173, 30090481, 44784505, 47213183, 78561158, 86301513, 61728685, 82886381, 47887470, 89217461, 33553959, 4204661, 99729357, 65081429, 35192533, 34295794, 75986488, 12348118, 71522968, 6505939, 18504197, 7011964, 59329511, 95957797, 16971929, 55143724, 7517032, 85571389, 31491938, 74441448, 83083131, 60955663, 72357096, 59501487, 70420215, 89699445, 62428472, 31727379, 56955985, 12664567, 89078848, 36396314, 17385531, 76330843, 30771409, 86821229, 72278539, 22721500, 82897371, 8696647, 89637706, 65017137, 72358170, 57020481, 43506672, 43501211, 79191827, 32161669, 5267545, 40677414, 83651211, 20642888, 9058407, 48673079, 85242963, 79942022, 35996293, 14947650, 33431960, 13173644, 17894977, 72019362, 10961421, 28796059, 3880712, 42782652, 20568363, 8651647, 85023028, 47792865, 30366150, 37280276, 28734791, 37957788, 4978543, 15075176, 10309525, 81677380, 30764367, 26734892, 17857111, 67227442, 55770687, 53648154, 22879907, 62490109, 38009615, 12416768, 24619760, 54263880, 40027975, 76170907, 24826575, 68000591, 9575954, 43045786, 20765474, 55850790, 86543538, 48088883, 98653983, 58180653, 30543215, 11543098, 66322959, 4798568, 80555751, 36505482, 61741594, 32590267, 23110625, 29635537, 16099750, 7685448, 92998591, 5073754, 44245960, 70367851, 70541760, 55960386, 99965001, 86118021, 33699435, 4508700, 99297164, 33237508, 31126490, 49641577, 57359924, 4091162, 27625735, 79806380, 49271185, 65074535, 16684829, 87720882, 23194618, 29994197, 16567550, 1431742, 78300864, 86242799, 66319530, 98948034, 824872, 11757872, 38256849, 54606384, 33201905, 17037369, 84127901, 6986898, 82726008, 29819940, 5832946, 16380211, 66832478, 54987042, 14220886, 6521313, 90090964, 48893685, 45306070, 321665, 99125126, 88904910, 44627776, 91281584, 16387575, 95251277, 65038678, 54517921, 92530431, 44481640, 90310261, 45407418, 92787493, 16405341, 4095116, 27411561, 26139110, 80316608, 93053405, 45617087, 19891772, 17539625, 92215320, 26863229, 86001008, 83133790, 90013093, 76315420, 51715482, 49882705, 68102437, 88251446, 63372756, 73235980, 44664587, 28787861, 30998561, 49328608, 508198, 82427263, 55602660, 38494874, 98943869, 89419466, 95290172, 84406788, 64848072, 93515664, 18466635, 90061527, 97011160, 14723410, 20836893, 24058273, 78884452, 20486294, 22942635, 40984766, 68110330, 64087743, 44177011, 89046466, 70727211, 87598888, 37224844, 8729146, 66116458, 42644903, 38341669, 73168565, 77312810, 93933709, 42967683, 3233569, 17058722, 55189057, 76671482, 8913721, 34698463, 55615885, 30653863, 2331773, 37739481, 48892201, 73124510, 92541302, 85116755, 98371444, 48658605, 51047803, 71300104, 42073124, 42692881, 37501808, 93562257, 51464002, 67281495, 39373729, 89811711, 29466406, 2607799, 96726697, 78766358, 77898274, 24915585, 40781449, 90654836, 28851716, 92692978, 74110882, 33935899, 66428795, 44479073, 68204242, 72238278, 45919976, 10264691, 60393039, 1375023, 99524975, 43152977, 20645197, 40781854, 97783876, 87710366, 36780454, 11581181, 99333375, 84187166, 57240218, 17386996, 99224392, 48774913, 54058788, 37891451, 10597197, 94595668, 99971982, 73031054, 168541, 44983451, 4985896, 2717150, 74743862, 62693428, 9886593, 19457589, 77300457, 68824981, 15148031, 38645117, 13470059, 70800879, 2891150, 75543508, 30139692, 78218845, 57961282, 75820087, 44348328, 84684495, 88698958, 3233084, 17957593, 17068582, 8099994, 16445503, 87160386, 28550822, 54762643, 68702632, 91802888, 74137926, 26114953, 23569917, 33628349, 51315460, 9259676, 91831487, 14326617, 21919959, 36468541, 67084351, 42237907, 10358899, 22200378, 9829782, 51472275, 44915235, 67451935, 9860195, 6157724, 34493392, 15163258, 89996536, 34236719, 7919588, 81853704, 53547802, 45794415, 89499542, 85695762, 21070110, 81898046, 44844121, 99604946, 70004753, 20867149, 83789391, 31733363, 48360198, 22108665, 75407347, 60581278, 21289531, 26275734, 42199455, 82052050, 36685023, 52293995, 85117092, 63506281, 54427233, 80014588, 19101477, 415901, 83948335, 45428665, 9175338, 9188443, 70372191, 69786756, 112651, 33249630, 43322743, 74724075, 83747892, 25636669, 77787724, 29834512, 85711894, 16424199, 54232247, 91938191, 1204161, 86798033, 63015256, 99021067, 72777973, 40356877, 18806856, 94711842, 55247455, 30218878, 79322415, 67513640, 45381876, 50702367, 40686254, 17081350, 99515901, 21397057, 10453030, 97057187, 67793644, 50806615, 30163921, 61897582, 669105, 11365791, 67124282, 29065964, 36753250, 18833224, 93566986, 61859581, 36139942, 92803766, 60176618, 72725103, 35512853, 26102057, 66137019, 33336148, 44473167, 57241521, 94090109, 29510992, 69579137, 38510840, 21001913, 27185644, 43357947, 84840549, 26998766, 48395186, 88653118, 66667729, 60106217, 36933038, 88444207, 26397786, 54014062, 63628376, 59405277, 27325428, 51590803, 77272628, 68128438, 59614746, 64098930, 15015906, 73222868, 80246713, 1569515, 15064655, 55753905, 59197747, 64602895, 78589145, 67031644, 62552524, 30569392, 13819358, 65275241, 33123618, 77301523, 51149804, 79880247, 62740044, 72614359, 37560164, 6793819, 89214616, 22766820, 29029316, 7646095, 63152504, 18783702, 71333116, 74357852, 14045383, 81774825, 61859143, 4119747, 71920426, 82532312, 50668599, 29401781, 72373496, 51466049, 12024238, 47298834, 37435892, 64055960, 4668450, 6819644, 77284619, 36930650, 68939068, 8128637, 46870723, 71657078, 32151165, 6871053, 34432810, 65851721, 44550764, 92353856, 69697787, 9398733, 526217, 45075471, 26664538, 59109400, 76825057, 90158785, 23432750, 4806458, 22435353, 90272749, 80251430, 47361209, 81855445, 98462867, 33304202, 93359396, 40865610, 75153252, 91141395, 34698428, 9623492, 60430369, 78549759, 66903004, 9860968, 68816413, 59981773, 55470718, 61271144, 91990218, 57393458, 47893286, 17976208, 11161731, 53802686, 34667286, 17365188, 14626618, 84166196, 38008118, 69848388, 21673260, 15902805, 30787683, 5482538, 88130087, 61928316, 45990383, 63059024, 7423788, 98739783, 31161687, 62051033, 1022677, 82979980, 66271566, 42307449, 84293052, 84002370, 44847298, 62936963, 78909561, 96906410, 12303248, 66885828, 44889423, 79136082, 6808825, 7066775, 56473732, 22113133, 71316369, 38022834, 91664334, 16054533, 32058615, 82651278, 52060076, 20122224, 72732205, 92033260, 14363867, 50188404, 95726235, 20002147, 3294781, 62496012, 5822202, 66663942, 77072625, 9603598, 41092102, 24168411, 37166608, 39986008, 37192445, 53632373, 96709982, 17727650, 82779622, 40534591, 247198, 97641116, 91255408, 68694897, 56531125, 36812683, 83368048, 99917599, 82327024, 93057697, 23848565, 17764950, 41092172, 97940276, 4434662, 3509435, 22450468, 28928175, 75128745, 11923835, 45237957, 3088684, 36312813, 54199160, 33895336, 4515343, 96420429, 79922758, 6038457, 62115552, 81805959, 75052463, 20885148, 72495719, 32159704, 24591705, 79738755, 93790285, 75135448, 65880522, 76434144, 64157906, 77377183, 52613508, 92867155, 29932657, 37675718, 37183543, 76703609, 46851987, 30463802, 61815223, 1239555, 92692283, 29188588, 39847321, 57163802, 63967300, 77694700, 18663507, 73109997, 50007421, 17146629, 58751351, 18131876, 37659250, 69355476, 27665211, 24314885, 65047700, 67030811, 73021291, 96193415, 62762857, 50567636, 45996863, 26292919, 77413012, 57803235, 52261574, 22129328, 4064751, 44060493, 96318094, 40152546, 2917920, 56515456, 82339363, 10366309, 69136837, 8115266, 99549373, 8373289, 8634541, 13231279, 35092039, 61623915, 62357986, 7104732, 58224549, 93270084, 96735716, 30694952, 42947632, 36189527, 15948937, 62430984, 1197320, 16595436, 65715134, 3633375, 65338021, 13348726, 8129978, 5640302, 92554120, 17016156, 72793444, 16019925, 13468268, 73617245, 72738685, 27187213, 37620363, 23740167, 56461322, 47090124, 43376279, 41481685, 7687278, 4069912, 56484900, 14731700, 61982238, 34497327, 77187825, 1250437, 94911072, 61141874, 91240048, 83210802, 83533741, 49598724, 96852321, 65271999, 53842979, 81274566, 70782102, 36580610, 94539824, 19898053, 73392814, 69605283, 38658347, 86460488, 61373987, 61263068, 874791, 38365584, 15535065, 68316156, 56153345, 33565483, 91727510, 78785507, 3487592, 95010552, 1788101, 32250045, 98130363, 21993752, 92071990, 569864, 7300047, 26063929, 76960303, 83302115, 86022504, 4787945, 66611759, 22997281, 53393358, 62232211, 68875490, 39171895, 71965942, 19376156, 53084256 +42199455, 59981773, 26063929, 53084256, 17146629, 55143724, 56515456, 5267545, 3235882, 31161687, 37675718, 30543215, 99965001, 54232247, 38256849, 45381876, 4985896, 89637706, 526217, 24733232, 26664538, 84349107, 33336148, 3509435, 3233084, 21001913, 6221471, 60430369, 48675329, 81853704, 78884452, 38061439, 75407347, 66116458, 61741594, 29188588, 112651, 7011964, 16424199, 90654836, 37435892, 50567636, 59109400, 8115266, 72725103, 22435353, 97561745, 17894977, 3088684, 75229462, 36580610, 13862149, 10358899, 15163258, 62232211, 76170907, 75135448, 7423788, 60581278, 21289531, 42967683, 76671482, 73617245, 55615885, 35192533, 41380093, 92692283, 99690194, 12303248, 4119747, 92692978, 65047700, 39201414, 12024238, 24953498, 14731700, 824872, 70420215, 83269727, 84187166, 57803235, 96709982, 44060493, 59318837, 321665, 43501211, 40677414, 38645117, 4095116, 26102057, 83533741, 27411561, 2891150, 98462867, 76315420, 28550822, 63372756, 62357986, 93270084, 26776922, 51472275, 44915235, 59405277, 65454636, 97011160, 32159704, 15015906, 3633375, 80246713, 30395570, 1022677, 44847298, 36845587, 32590267, 34295794, 39847321, 48658605, 51047803, 33249630, 77694700, 7646095, 37501808, 77787724, 32058615, 50188404, 23194618, 85571389, 51466049, 6819644, 73021291, 86022504, 33565483, 57240218, 22129328, 10597197, 669105, 69697787, 44627776, 45075471, 45407418, 94516935, 66137019, 68041839, 69579137, 17539625, 21533347, 44348328, 33304202, 11923835, 95581843, 33895336, 66045587, 9860968, 61271144, 63628376, 6497830, 90061527, 98130363, 16595436, 65715134, 73222868, 82178706, 22879907, 86460488, 15064655, 12571310, 19486173, 5482538, 67031644, 30569392, 86301513, 8129978, 10649306, 92554120, 6111563, 17058722, 82052050, 85117092, 84904436, 30653863, 62740044, 72614359, 30463802, 15535065, 32426752, 92998591, 29029316, 73109997, 4508700, 99297164, 33237508, 74357852, 81774825, 82651278, 52060076, 4091162, 20122224, 86798033, 63015256, 14363867, 4069912, 44479073, 29994197, 66319530, 68939068, 56955985, 37192445, 12664567, 26292919, 6986898, 17727650, 48774913, 54058788, 168541, 54987042, 44983451, 44550764, 62452034, 9886593, 44846932, 36753250, 32274392, 79191827, 13470059, 92787493, 48673079, 33797252, 26493119, 30139692, 29516592, 8634541, 80251430, 13173644, 78218845, 57961282, 69641513, 84684495, 78785507, 88251446, 99658235, 61623915, 48395186, 2208785, 7229550, 78549759, 91831487, 36468541, 30366150, 54014062, 4199704, 15075176, 6157724, 55770687, 81898046, 15902805, 20867149, 83789391, 31733363, 55753905, 59197747, 64157906, 13348726, 52613508, 68875490, 13819358, 38341669, 33123618, 569864, 7300047, 39171895, 36685023, 42307449, 13468268, 46851987, 26507214, 19101477, 415901, 83948335, 38365584, 77620120, 92541302, 57163802, 9175338, 85116755, 70372191, 63967300, 63152504, 7066775, 4099191, 56461322, 34044787, 29834512, 85711894, 14045383, 36135, 30811010, 37659250, 69355476, 1204161, 19272365, 73786944, 18806856, 74441448, 1431742, 4668450, 62496012, 99226875, 74452589, 87710366, 62428472, 39986008, 45848907, 36396314, 84127901, 83155442, 1250437, 14349098, 46540998, 45995325, 94076128, 34432810, 99971982, 50806615, 30163921, 72278539, 6521313, 90090964, 74743862, 9398733, 45306070, 29065964, 15148031, 93057697, 92803766, 76825057, 23432750, 51588015, 99549373, 41092172, 20642888, 76540481, 97940276, 91727510, 45790169, 45617087, 57241521, 19891772, 29510992, 13231279, 75820087, 27185644, 90457870, 22450468, 93359396, 40865610, 65271999, 66611759, 54762643, 8055981, 66667729, 42782652, 82427263, 57248122, 91802888, 6038457, 3183975, 55470718, 21919959, 36933038, 42237907, 37280276, 22200378, 64848072, 9829782, 70036438, 36189527, 34667286, 27325428, 17365188, 22997281, 14626618, 20836893, 67227442, 22942635, 99604946, 79738755, 65880522, 61928316, 44784505, 92071990, 63059024, 20765474, 62051033, 77301523, 77312810, 89217461, 86543538, 16097038, 72793444, 58180653, 55189057, 40197395, 52293995, 63506281, 874791, 65081429, 78909561, 49597667, 23110625, 72738685, 45428665, 6793819, 9188443, 92604458, 69786756, 43322743, 12348118, 44889423, 70367851, 71469330, 36808486, 70541760, 67281495, 55960386, 47090124, 33699435, 25636669, 89811711, 5970607, 71333116, 29466406, 88047921, 59910624, 61859143, 57359924, 28851716, 72732205, 33935899, 49271185, 66428795, 50668599, 72373496, 78300864, 67474219, 66663942, 61982238, 96193415, 34497327, 36930650, 37166608, 36780454, 8128637, 15536795, 50702367, 82726008, 99224392, 4064751, 47738185, 21397057, 29819940, 5832946, 71657078, 34946859, 96318094, 40152546, 37891451, 73031054, 67793644, 53888755, 91255408, 8696647, 68694897, 62693428, 56531125, 72358170, 61141874, 77300457, 43506672, 99917599, 82339363, 61859581, 23848565, 19376156, 83210802, 90158785, 4787945, 35512853, 9058407, 80316608, 93053405, 49598724, 97281847, 92215320, 25842078, 83133790, 72274002, 8099994, 26998766, 49882705, 68102437, 75153252, 3487592, 7104732, 58224549, 73235980, 66250369, 68702632, 28796059, 3880712, 53842979, 96735716, 79922758, 23569917, 20568363, 66903004, 89419466, 47792865, 83150534, 95290172, 95010552, 70782102, 88444207, 1788101, 67084351, 91990218, 57393458, 49236559, 749283, 67451935, 81677380, 68128438, 34493392, 59614746, 34236719, 38008118, 54663246, 64098930, 14723410, 69605283, 21993752, 20486294, 21673260, 8807940, 68110330, 38009615, 12416768, 3773993, 24619760, 1569515, 176257, 70727211, 99861373, 24826575, 48360198, 64602895, 78589145, 22108665, 43045786, 65275241, 92867155, 73168565, 61728685, 17016156, 61263068, 82979980, 93933709, 43933006, 60102965, 48088883, 4204661, 98653983, 51149804, 66322959, 54427233, 84002370, 36505482, 37739481, 73124510, 37560164, 75986488, 18411915, 98371444, 71300104, 54868730, 42692881, 74724075, 44245960, 50007421, 8791066, 22113133, 71316369, 68644627, 38022834, 18131876, 31126490, 41481685, 68316156, 90004325, 24915585, 40781449, 71920426, 74110882, 7687278, 92033260, 16684829, 56153345, 29401781, 83302115, 47298834, 55247455, 20645197, 83083131, 20002147, 30218878, 3294781, 56424103, 77072625, 9603598, 11757872, 79322415, 41092102, 54606384, 24168411, 33201905, 11581181, 99333375, 45996863, 53632373, 17386996, 40686254, 17385531, 52261574, 30771409, 10453030, 32151165, 6871053, 247198, 16380211, 94595668, 65851721, 66832478, 86821229, 2717150, 65017137, 16387575, 18833224, 71083565, 82327024, 10366309, 36139942, 69136837, 16405341, 70800879, 35996293, 59371804, 45667668, 90272749, 8373289, 33431960, 19939935, 17957593, 90013093, 10961421, 88653118, 45237957, 44664587, 36312813, 28787861, 54199160, 79657802, 508198, 96420429, 78602717, 81805959, 9259676, 38494874, 68816413, 42947632, 26397786, 28734791, 4978543, 9860195, 11161731, 10309525, 15948937, 32250045, 72495719, 94539824, 1197320, 7919588, 24591705, 53547802, 65338021, 70596786, 85695762, 38658347, 35456853, 53648154, 62490109, 54263880, 44177011, 61373987, 70004753, 93790285, 45990383, 69255765, 5640302, 95395112, 82886381, 33553959, 37183543, 3233569, 26275734, 76703609, 16019925, 51507425, 4798568, 16099750, 59177718, 66885828, 47708850, 18663507, 2204165, 37620363, 6808825, 51426311, 56473732, 59329511, 16971929, 27041967, 52204879, 96726697, 78766358, 7182642, 49641577, 27625735, 82532312, 24314885, 65074535, 99021067, 68204242, 72238278, 40356877, 45919976, 10264691, 16567550, 60393039, 31491938, 99524975, 86242799, 60955663, 40781854, 5822202, 57658654, 97783876, 55669657, 31727379, 7502255, 97057187, 97641116, 11365791, 94911072, 99125126, 36812683, 45049308, 95251277, 74614639, 92530431, 68824981, 32161669, 9599614, 91240048, 83651211, 85242963, 14947650, 26139110, 18001617, 47361209, 17068582, 43357947, 16445503, 51715482, 84840549, 28928175, 58749, 34698428, 9623492, 30998561, 4515343, 55602660, 74137926, 8651647, 81274566, 14093520, 26392416, 47893286, 93515664, 20885148, 62430984, 30764367, 20681921, 24058273, 6153406, 45794415, 19898053, 30891921, 33061250, 89046466, 87598888, 98648327, 68000591, 78561158, 98739783, 62803858, 75777973, 29932657, 89804152, 66271566, 80014588, 62936963, 61815223, 7955293, 1239555, 91957544, 29635537, 5073754, 51464002, 23740167, 86118021, 95957797, 2607799, 43376279, 91664334, 16054533, 18699206, 43152977, 56484900, 62762857, 89699445, 17037369, 77413012, 89078848, 99515901, 46870723, 82779622, 40534591, 61897582, 22721500, 14220886, 82897371, 2917920, 88904910, 19457589, 91281584, 65038678, 93566986, 44842615, 90310261, 60176618, 4806458, 17764950, 94090109, 81855445, 88698958, 96852321, 71965942, 35092039, 62115552, 26114953, 33628349, 51315460, 1936762, 85023028, 60106217, 84406788, 18466635, 77272628, 6525948, 69848388, 17857111, 32699744, 89499542, 40984766, 64087743, 40027975, 37224844, 88130087, 76434144, 62552524, 9575954, 29959549, 11543098, 79880247, 80555751, 7685448, 89214616, 81172706, 71522968, 83747892, 18504197, 18783702, 58751351, 39373729, 97379790, 7517032, 77898274, 91938191, 76960303, 87720882, 9257405, 95726235, 1375023, 67030811, 77284619, 59501487, 77187825, 26744917, 17081350, 79821897, 54517921, 39553046, 83368048, 44481640, 22405120, 79942022, 4434662, 58208470, 26863229, 86001008, 38510840, 87160386, 72019362, 37957788, 53802686, 26734892, 73392814, 21070110, 30787683, 77377183, 47213183, 55850790, 47887470, 94360702, 34698463, 22766820, 27187213, 93562257, 41442762, 27665211, 72777973, 94711842, 98948034, 72357096, 20140249, 67513640, 48893685, 67124282, 5111370, 31904591, 75128745, 49328608, 14326617, 17976208, 92398073, 51590803, 84166196, 53393358, 39801351, 42644903, 8913721, 99729357, 84293052, 48892201, 96906410, 42073124, 6505939, 79136082, 76330843, 92353856, 44473167, 91141395, 75052463, 98943869, 89996536, 61712234, 44844121, 8729146, 23134715, 53666583, 41245325, 79806380, 64055960, 61960400, 30694952, 30090481, 48260151, 2331773, 57020481, 75543508, 8733068 +38658347, 45428665, 58751351, 1431742, 4064751, 99971982, 90090964, 88698958, 66667729, 79922758, 57393458, 73222868, 30395570, 92867155, 55850790, 44479073, 36780454, 12664567, 38645117, 20642888, 75543508, 45617087, 53084256, 28796059, 97011160, 45794415, 78884452, 21993752, 40984766, 75135448, 63059024, 42644903, 60581278, 13468268, 29188588, 71300104, 33249630, 74724075, 18663507, 37501808, 99965001, 33699435, 77787724, 54232247, 27665211, 9257405, 51466049, 83083131, 73021291, 824872, 37166608, 6986898, 40534591, 54987042, 44550764, 2917920, 39553046, 31904591, 32161669, 59371804, 57961282, 88251446, 28550822, 3880712, 78602717, 55470718, 36933038, 64848072, 77272628, 34236719, 12416768, 86460488, 79738755, 45990383, 569864, 7300047, 26275734, 42199455, 78909561, 30463802, 92541302, 51047803, 37620363, 5970607, 27041967, 96726697, 88047921, 32058615, 24314885, 86798033, 14363867, 23194618, 72238278, 10264691, 16567550, 37435892, 20645197, 14731700, 20002147, 61982238, 83269727, 33565483, 30771409, 54058788, 96318094, 14220886, 74743862, 36812683, 16387575, 71083565, 61859581, 72725103, 17764950, 91727510, 80316608, 49598724, 90272749, 33431960, 13173644, 84684495, 3233084, 72274002, 76315420, 84840549, 28928175, 40865610, 75153252, 45237957, 44664587, 93270084, 57248122, 91802888, 26114953, 23569917, 1936762, 91831487, 36580610, 22200378, 6157724, 18466635, 22997281, 14626618, 15163258, 32159704, 30764367, 59614746, 53393358, 21673260, 64087743, 99604946, 99861373, 19486173, 78589145, 62552524, 8129978, 21289531, 72793444, 36685023, 79880247, 62936963, 35192533, 92604458, 96906410, 66885828, 12348118, 7646095, 86118021, 47090124, 25636669, 71316369, 38022834, 95957797, 74357852, 14045383, 49641577, 24915585, 37659250, 1204161, 56153345, 50668599, 73786944, 96193415, 77072625, 31727379, 84127901, 21397057, 94595668, 97057187, 30163921, 8696647, 61141874, 65038678, 82327024, 93057697, 83210802, 51588015, 99549373, 83533741, 4434662, 33336148, 57241521, 94090109, 44348328, 98462867, 71965942, 38510840, 21001913, 17068582, 22450468, 26998766, 3487592, 65271999, 73235980, 96420429, 60430369, 66903004, 42947632, 54014062, 84406788, 10358899, 47893286, 36189527, 27325428, 34493392, 15015906, 3633375, 69605283, 85695762, 20486294, 22108665, 44784505, 78561158, 82979980, 37183543, 16097038, 39171895, 40197395, 55615885, 30653863, 65081429, 2331773, 84002370, 26507214, 32590267, 15535065, 6793819, 85116755, 16099750, 42073124, 112651, 63967300, 22766820, 42692881, 27187213, 67281495, 56461322, 43376279, 7182642, 16054533, 16424199, 41481685, 52060076, 90654836, 74110882, 92033260, 4069912, 87720882, 40356877, 18806856, 99524975, 74441448, 6819644, 11757872, 74452589, 24168411, 39986008, 45381876, 57803235, 50702367, 52261574, 82726008, 7502255, 37891451, 61897582, 91255408, 22721500, 6521313, 69697787, 68694897, 9398733, 72358170, 88904910, 74614639, 99917599, 15148031, 26664538, 8115266, 23432750, 48673079, 76540481, 26102057, 2891150, 33797252, 68041839, 8373289, 80251430, 78218845, 47361209, 69641513, 96852321, 33304202, 90013093, 72019362, 61623915, 63372756, 58224549, 9623492, 68702632, 96735716, 82427263, 7229550, 30694952, 8651647, 9860968, 95290172, 61271144, 63628376, 93515664, 17976208, 9860195, 10309525, 51590803, 17365188, 81677380, 69848388, 53547802, 24058273, 65338021, 81898046, 44844121, 15902805, 8807940, 68110330, 44177011, 1569515, 70004753, 176257, 12571310, 55753905, 64602895, 5482538, 13348726, 77377183, 43045786, 68875490, 13819358, 92554120, 31161687, 47887470, 3233569, 30543215, 11543098, 85117092, 42307449, 26063929, 84904436, 46851987, 80555751, 83948335, 39847321, 57163802, 9188443, 59177718, 77694700, 99297164, 39373729, 85711894, 36135, 40781449, 18699206, 33935899, 66428795, 68204242, 60393039, 43152977, 40781854, 30218878, 20140249, 5822202, 97783876, 41092102, 62428472, 99333375, 26744917, 8128637, 77413012, 53632373, 96709982, 46870723, 34946859, 45995325, 247198, 94076128, 73031054, 66832478, 72278539, 82897371, 56515456, 65017137, 36753250, 54517921, 45075471, 36139942, 76825057, 90158785, 83651211, 35512853, 45407418, 16405341, 85242963, 27411561, 14947650, 22435353, 18001617, 69579137, 21533347, 58208470, 13231279, 19939935, 27185644, 8099994, 16445503, 90457870, 93359396, 49882705, 34698428, 6221471, 3088684, 79657802, 53842979, 26776922, 66045587, 49328608, 62115552, 20568363, 81274566, 60106217, 59981773, 75229462, 14326617, 83150534, 1788101, 42237907, 91990218, 49236559, 51472275, 70036438, 4199704, 67451935, 11161731, 32250045, 62430984, 14723410, 26734892, 7919588, 6153406, 53648154, 30787683, 20867149, 87598888, 31733363, 40027975, 15064655, 68000591, 75407347, 61728685, 29932657, 82886381, 33123618, 29959549, 61263068, 77312810, 89217461, 42967683, 86543538, 89804152, 82052050, 76671482, 16019925, 66322959, 73617245, 36845587, 61815223, 61741594, 415901, 1239555, 37560164, 92692283, 77620120, 23110625, 9175338, 7685448, 69786756, 92998591, 12303248, 43322743, 5073754, 81172706, 71469330, 93562257, 63152504, 6808825, 18504197, 41245325, 7066775, 50007421, 4508700, 29834512, 18131876, 31126490, 7517032, 82651278, 61859143, 90004325, 20122224, 30811010, 72732205, 92692978, 49271185, 65047700, 72373496, 12024238, 86242799, 67474219, 77284619, 72357096, 34497327, 70420215, 9603598, 54606384, 86022504, 68939068, 67513640, 56955985, 37192445, 45848907, 40686254, 17081350, 47738185, 48774913, 82779622, 71657078, 32151165, 40152546, 16380211, 34432810, 67793644, 50806615, 53888755, 11365791, 92353856, 48893685, 62693428, 94911072, 19457589, 526217, 43506672, 83368048, 82339363, 10366309, 19376156, 91240048, 69136837, 4806458, 41092172, 22405120, 94516935, 97940276, 26139110, 17539625, 92215320, 3509435, 83133790, 17894977, 78785507, 75128745, 11923835, 54762643, 88653118, 2208785, 4515343, 6038457, 74137926, 81805959, 78549759, 75052463, 38494874, 85023028, 70782102, 26397786, 26392416, 6497830, 15075176, 53802686, 54663246, 6525948, 98130363, 16595436, 22879907, 80246713, 83789391, 93790285, 8729146, 88130087, 76434144, 67031644, 52613508, 92071990, 30569392, 66116458, 7423788, 5640302, 10649306, 65275241, 37675718, 23134715, 48088883, 4204661, 17058722, 55189057, 66271566, 76703609, 99729357, 48260151, 874791, 80014588, 62740044, 36505482, 19101477, 38365584, 41380093, 32426752, 29029316, 44245960, 70367851, 36808486, 70541760, 51426311, 4099191, 56473732, 8791066, 41442762, 33237508, 89811711, 22113133, 71333116, 2607799, 91664334, 55143724, 97379790, 77898274, 91938191, 16684829, 50188404, 99021067, 83302115, 8733068, 47298834, 56484900, 55247455, 64055960, 78300864, 3294781, 62496012, 66663942, 56424103, 36930650, 62762857, 79322415, 33201905, 89699445, 45996863, 26292919, 36396314, 17385531, 99515901, 22129328, 14349098, 99224392, 17727650, 44060493, 46540998, 79821897, 6871053, 10597197, 65851721, 97641116, 44983451, 4985896, 45306070, 321665, 89637706, 62452034, 44846932, 44627776, 91281584, 18833224, 93566986, 44481640, 5267545, 92803766, 59109400, 60176618, 92787493, 9058407, 4095116, 93053405, 26493119, 97281847, 30139692, 29516592, 75820087, 26863229, 17957593, 35092039, 43357947, 87160386, 58749, 91141395, 62357986, 8055981, 66250369, 36312813, 95581843, 30998561, 508198, 9259676, 68816413, 89419466, 95010552, 28734791, 37957788, 65454636, 90061527, 94539824, 89996536, 64098930, 1197320, 17857111, 24591705, 20681921, 65715134, 35456853, 82178706, 38061439, 33061250, 3773993, 61373987, 65880522, 98648327, 61928316, 9575954, 3235882, 98739783, 17016156, 1022677, 93933709, 94360702, 98653983, 58180653, 8913721, 34698463, 54427233, 84293052, 37739481, 48892201, 49597667, 72738685, 98371444, 44889423, 71522968, 2204165, 73109997, 79136082, 55960386, 17146629, 68644627, 16971929, 4091162, 4119747, 71920426, 79806380, 63015256, 76960303, 29401781, 85571389, 29994197, 45919976, 95726235, 31491938, 1375023, 59501487, 57658654, 38256849, 87710366, 11581181, 50567636, 15536795, 89078848, 57240218, 1250437, 5832946, 59318837, 669105, 67124282, 95251277, 32274392, 79191827, 92530431, 24733232, 9599614, 84349107, 90310261, 79942022, 66137019, 45790169, 44473167, 19891772, 86001008, 25842078, 68102437, 7104732, 28787861, 54199160, 33895336, 42782652, 55602660, 3183975, 98943869, 47792865, 88444207, 13862149, 37280276, 92398073, 68128438, 38008118, 20836893, 61712234, 81853704, 70596786, 55770687, 62232211, 62490109, 24619760, 54263880, 70727211, 37224844, 24826575, 64157906, 69255765, 86301513, 95395112, 20765474, 38341669, 62803858, 73168565, 33553959, 43933006, 60102965, 51149804, 52293995, 51507425, 7955293, 75986488, 89214616, 47708850, 7011964, 29466406, 78766358, 27625735, 69355476, 82532312, 19272365, 94711842, 67030811, 17037369, 84187166, 29819940, 86821229, 2717150, 5111370, 56531125, 29065964, 57020481, 45049308, 43501211, 61960400, 44842615, 4787945, 70800879, 29510992, 10961421, 66611759, 14093520, 67084351, 9829782, 749283, 48675329, 15948937, 84166196, 67227442, 89499542, 22942635, 30891921, 38009615, 59197747, 30090481, 62051033, 75777973, 72614359, 44847298, 99690194, 23740167, 18783702, 34044787, 52204879, 59910624, 68316156, 57359924, 72777973, 24953498, 60955663, 4668450, 77187825, 76330843, 99125126, 9886593, 77300457, 40677414, 13470059, 35996293, 97561745, 8634541, 81855445, 36468541, 44915235, 4978543, 20885148, 72495719, 32699744, 73392814, 21070110, 89046466, 76170907, 47213183, 77301523, 6111563, 73124510, 34295794, 48658605, 70372191, 6505939, 51464002, 83747892, 59329511, 7687278, 65074535, 39201414, 66319530, 99226875, 55669657, 17386996, 10453030, 68824981, 21919959, 59405277, 34667286, 19898053, 53666583, 63506281, 4798568, 91957544, 29635537, 81774825, 28851716, 98948034, 168541, 23848565, 45667668, 51715482, 99658235, 48395186, 51315460, 30366150, 48360198, 39801351, 18411915, 83155442, 54868730, 33628349 +68939068, 99515901, 67124282, 47361209, 75128745, 15015906, 1569515, 62552524, 66832478, 26139110, 63372756, 81274566, 83150534, 32159704, 59197747, 1204161, 4064751, 99917599, 44842615, 66137019, 40865610, 54199160, 55470718, 42644903, 54427233, 78909561, 30463802, 51047803, 27187213, 18504197, 9257405, 95726235, 55247455, 40781854, 34497327, 36930650, 36780454, 67793644, 95251277, 31904591, 10366309, 90013093, 17894977, 91802888, 74137926, 1197320, 78884452, 21993752, 12571310, 68000591, 92071990, 569864, 3233569, 42307449, 69786756, 37501808, 51426311, 33935899, 92033260, 76960303, 14731700, 45381876, 12664567, 77413012, 6986898, 50702367, 46870723, 34946859, 32151165, 14220886, 48893685, 94911072, 65017137, 9886593, 44627776, 65038678, 54517921, 5267545, 83651211, 16405341, 45617087, 13173644, 81855445, 76315420, 84840549, 28550822, 28796059, 1936762, 21919959, 57393458, 54014062, 54663246, 14723410, 69848388, 53393358, 19898053, 20486294, 15902805, 99604946, 24619760, 8729146, 78589145, 76434144, 66116458, 63059024, 55850790, 89217461, 86543538, 7300047, 48260151, 79880247, 65081429, 61815223, 83948335, 85116755, 92998591, 33249630, 42692881, 2204165, 37620363, 36808486, 83747892, 67281495, 47090124, 7011964, 43376279, 91664334, 74357852, 78766358, 24915585, 30811010, 27665211, 66428795, 44479073, 56153345, 85571389, 73786944, 16567550, 51466049, 6819644, 99226875, 9603598, 11757872, 62428472, 76330843, 79821897, 247198, 669105, 82897371, 2917920, 9398733, 99125126, 16387575, 93566986, 82327024, 60176618, 92787493, 22405120, 75543508, 49598724, 44473167, 8373289, 57241521, 69579137, 21533347, 53084256, 88653118, 45237957, 66667729, 66045587, 49328608, 81805959, 9259676, 68816413, 60106217, 42947632, 26397786, 42237907, 70036438, 77272628, 22997281, 6153406, 85695762, 44844121, 21673260, 86460488, 44177011, 176257, 87598888, 31733363, 99861373, 37224844, 55753905, 64157906, 21289531, 77312810, 93933709, 42967683, 39171895, 60102965, 53666583, 26275734, 51149804, 13468268, 55615885, 2331773, 44847298, 37560164, 45428665, 9175338, 71300104, 63967300, 22766820, 79136082, 51464002, 88047921, 16054533, 68316156, 57359924, 37659250, 54232247, 92692978, 86798033, 72238278, 18806856, 43152977, 24953498, 20002147, 3294781, 33565483, 50567636, 45848907, 26292919, 82726008, 44060493, 40534591, 40152546, 99971982, 69697787, 56531125, 88904910, 29065964, 57020481, 36753250, 74614639, 45075471, 83368048, 44481640, 15148031, 61859581, 8115266, 35512853, 45407418, 20642888, 14947650, 2891150, 93053405, 4434662, 30139692, 29516592, 94090109, 57961282, 84684495, 26863229, 35092039, 17068582, 26998766, 3487592, 58749, 6221471, 62357986, 44664587, 66250369, 36312813, 96735716, 57248122, 79922758, 75052463, 36933038, 88444207, 64848072, 48675329, 17976208, 10309525, 18466635, 81677380, 14626618, 34493392, 34236719, 89499542, 82178706, 8807940, 30395570, 61373987, 76170907, 64602895, 13348726, 45990383, 3235882, 78561158, 69255765, 61728685, 47887470, 37675718, 82979980, 98653983, 42199455, 82052050, 66271566, 85117092, 80014588, 30653863, 72614359, 62936963, 36505482, 19101477, 23110625, 92541302, 98371444, 92604458, 96906410, 42073124, 66885828, 74724075, 71469330, 41245325, 70541760, 55960386, 50007421, 4099191, 56461322, 22113133, 38022834, 29834512, 96726697, 7182642, 55143724, 32058615, 40781449, 4119747, 24314885, 14363867, 4069912, 99021067, 60393039, 47298834, 20645197, 77284619, 73021291, 72357096, 62496012, 96193415, 77072625, 74452589, 11581181, 99333375, 53632373, 17081350, 17385531, 1250437, 14349098, 99224392, 7502255, 97057187, 50806615, 44550764, 8696647, 45306070, 72358170, 44846932, 61141874, 45049308, 18833224, 23848565, 36139942, 19376156, 90310261, 99549373, 17764950, 41092172, 48673079, 68041839, 8634541, 78218845, 69641513, 83133790, 72274002, 22450468, 75153252, 91141395, 65271999, 66611759, 48395186, 58224549, 42782652, 96420429, 7229550, 6038457, 78602717, 66903004, 75229462, 1788101, 22200378, 47893286, 53802686, 15948937, 68128438, 15163258, 94539824, 26734892, 17857111, 20836893, 3633375, 53547802, 24058273, 73392814, 69605283, 21070110, 73222868, 22942635, 30891921, 80246713, 68110330, 38009615, 64087743, 33061250, 30787683, 79738755, 40027975, 75135448, 61928316, 44784505, 52613508, 75407347, 86301513, 98739783, 65275241, 38341669, 29932657, 60581278, 17016156, 61263068, 89804152, 40197395, 30543215, 26063929, 73617245, 26507214, 36845587, 32590267, 73124510, 75986488, 59177718, 12303248, 5073754, 44889423, 81172706, 7646095, 99965001, 33699435, 17146629, 41442762, 58751351, 71316369, 16971929, 85711894, 14045383, 59910624, 97379790, 49641577, 4091162, 20122224, 90654836, 28851716, 71920426, 74110882, 23194618, 29994197, 40356877, 8733068, 94711842, 12024238, 31491938, 1431742, 83083131, 86242799, 20140249, 824872, 5822202, 61982238, 56424103, 70420215, 62762857, 97783876, 38256849, 54606384, 37166608, 31727379, 67513640, 84127901, 57240218, 83155442, 52261574, 48774913, 21397057, 96318094, 94595668, 65851721, 30163921, 53888755, 90090964, 74743862, 11365791, 68694897, 43501211, 71083565, 39553046, 92530431, 68824981, 32161669, 9599614, 93057697, 83210802, 40677414, 76825057, 23432750, 76540481, 91727510, 33336148, 97281847, 90272749, 58208470, 88698958, 86001008, 78785507, 49882705, 28928175, 68702632, 95581843, 30998561, 82427263, 30694952, 8651647, 9860968, 91831487, 14093520, 95290172, 26392416, 65454636, 11161731, 97011160, 30764367, 84166196, 61712234, 16595436, 32699744, 70596786, 53648154, 62232211, 12416768, 89046466, 70004753, 20867149, 83789391, 70727211, 24826575, 48360198, 65880522, 30090481, 30569392, 68875490, 10649306, 62051033, 62803858, 29959549, 37183543, 23134715, 6111563, 4204661, 72793444, 17058722, 55189057, 76671482, 11543098, 52293995, 8913721, 16019925, 4798568, 80555751, 35192533, 38365584, 92692283, 72738685, 39847321, 48658605, 99690194, 112651, 71522968, 63152504, 6808825, 25636669, 4508700, 34044787, 89811711, 31126490, 7517032, 82651278, 90004325, 72732205, 91938191, 49271185, 19272365, 39201414, 99524975, 66319530, 57658654, 24168411, 39986008, 40686254, 22129328, 17727650, 47738185, 5832946, 30771409, 46540998, 59318837, 6871053, 45995325, 37891451, 73031054, 92353856, 56515456, 5111370, 321665, 89637706, 36812683, 19457589, 77300457, 82339363, 91240048, 38645117, 4787945, 70800879, 26102057, 85242963, 83533741, 94516935, 80316608, 22435353, 45667668, 18001617, 33431960, 92215320, 96852321, 19939935, 17957593, 71965942, 33304202, 43357947, 16445503, 90457870, 93359396, 87160386, 68102437, 61623915, 10961421, 3880712, 79657802, 26776922, 4515343, 60430369, 62115552, 3183975, 20568363, 85023028, 59981773, 36468541, 70782102, 49236559, 4199704, 6497830, 93515664, 37957788, 6157724, 27325428, 17365188, 62430984, 89996536, 59614746, 64098930, 81853704, 67227442, 65338021, 45794415, 38658347, 35456853, 38061439, 88130087, 67031644, 98648327, 77377183, 9575954, 95395112, 13819358, 31161687, 75777973, 33123618, 94360702, 58180653, 34698463, 76703609, 66322959, 874791, 84293052, 84002370, 46851987, 48892201, 415901, 49597667, 77620120, 18411915, 89214616, 6505939, 7066775, 18783702, 86118021, 99297164, 39373729, 68644627, 5970607, 77787724, 71333116, 29466406, 27041967, 18131876, 2607799, 81774825, 27625735, 82532312, 63015256, 16684829, 50188404, 29401781, 72373496, 83302115, 74441448, 78300864, 59501487, 77187825, 86022504, 33201905, 83269727, 17037369, 26744917, 15536795, 57803235, 17386996, 71657078, 44983451, 6521313, 61960400, 84349107, 4806458, 35996293, 97940276, 59371804, 29510992, 3233084, 21001913, 27185644, 99658235, 7104732, 73235980, 28787861, 33895336, 508198, 2208785, 55602660, 26114953, 38494874, 98943869, 36580610, 91990218, 30366150, 84406788, 9829782, 36189527, 4978543, 9860195, 59405277, 32250045, 90061527, 51590803, 6525948, 98130363, 24591705, 22879907, 81898046, 40984766, 54263880, 15064655, 47213183, 7423788, 43045786, 20765474, 92867155, 73168565, 82886381, 1022677, 16097038, 48088883, 84904436, 34295794, 15535065, 57163802, 7685448, 32426752, 77694700, 12348118, 18663507, 56473732, 59329511, 36135, 69355476, 18699206, 7687278, 72777973, 45919976, 10264691, 56484900, 37435892, 4668450, 67474219, 98948034, 55669657, 89699445, 84187166, 56955985, 36396314, 94076128, 61897582, 54987042, 72278539, 91255408, 4985896, 22721500, 62693428, 62452034, 526217, 32274392, 24733232, 69136837, 13470059, 9058407, 4095116, 27411561, 79942022, 33797252, 26493119, 8099994, 51715482, 72019362, 34698428, 93270084, 53842979, 23569917, 33628349, 51315460, 47792865, 95010552, 61271144, 67084351, 63628376, 28734791, 67451935, 92398073, 20885148, 72495719, 7919588, 65715134, 5640302, 39801351, 99729357, 1239555, 29188588, 6793819, 29635537, 9188443, 16099750, 47708850, 23740167, 33237508, 52204879, 16424199, 41481685, 65047700, 65074535, 87720882, 68204242, 67030811, 30218878, 79322415, 45996863, 8128637, 89078848, 96709982, 82779622, 29819940, 16380211, 10597197, 168541, 97641116, 86821229, 91281584, 43506672, 26664538, 92803766, 97561745, 80251430, 19891772, 98462867, 25842078, 38510840, 88251446, 11923835, 8055981, 9623492, 3088684, 78549759, 89419466, 13862149, 10358899, 37280276, 749283, 15075176, 34667286, 38008118, 20681921, 55770687, 93790285, 22108665, 8129978, 77301523, 43933006, 36685023, 63506281, 51507425, 62740044, 37739481, 7955293, 41380093, 70372191, 29029316, 44245960, 70367851, 8791066, 61859143, 79806380, 50668599, 64055960, 60955663, 37192445, 79191827, 59109400, 3509435, 54762643, 51472275, 44915235, 3773993, 19486173, 5482538, 61741594, 91957544, 54868730, 43322743, 93562257, 77898274, 52060076, 66663942, 87710366, 34432810, 2717150, 90158785, 72725103, 51588015, 17539625, 13231279, 44348328, 14326617, 62490109, 92554120, 73109997, 1375023, 41092102, 54058788, 75820087, 33553959, 10453030, 45790169, 95957797 +32151165, 67084351, 59910624, 5822202, 36930650, 35512853, 63372756, 53842979, 68816413, 26397786, 99965001, 85711894, 9599614, 85242963, 96852321, 75128745, 95581843, 72495719, 61712234, 17016156, 3233569, 51149804, 29635537, 81172706, 41442762, 28851716, 47298834, 56955985, 99515901, 50806615, 67124282, 65017137, 36753250, 54517921, 99917599, 92787493, 69579137, 59405277, 11161731, 64098930, 20681921, 78884452, 22879907, 88130087, 75407347, 98739783, 61728685, 47887470, 89804152, 48260151, 63506281, 13468268, 30463802, 96906410, 81774825, 71920426, 44479073, 67030811, 62496012, 33201905, 89078848, 17081350, 73031054, 669105, 92353856, 48893685, 91281584, 526217, 74614639, 92530431, 44842615, 20642888, 16405341, 33797252, 78218845, 35092039, 66045587, 55602660, 91831487, 30366150, 92398073, 18466635, 34493392, 99604946, 70004753, 24826575, 68000591, 75777973, 33123618, 82979980, 23134715, 6111563, 94360702, 60102965, 55615885, 61815223, 23110625, 9188443, 33249630, 5073754, 12348118, 6505939, 67281495, 86118021, 50007421, 88047921, 32058615, 20122224, 30811010, 74110882, 50188404, 85571389, 45919976, 99524975, 9603598, 54606384, 36780454, 83269727, 45381876, 50702367, 22129328, 6871053, 10597197, 9398733, 43501211, 32274392, 5267545, 90310261, 59109400, 99549373, 4806458, 68041839, 45667668, 13173644, 92215320, 83133790, 84840549, 26998766, 54199160, 30998561, 1936762, 21919959, 95010552, 749283, 37957788, 97011160, 89499542, 85695762, 21070110, 64602895, 13348726, 98648327, 3235882, 92071990, 38341669, 55850790, 39171895, 48088883, 26275734, 42199455, 40197395, 8913721, 66322959, 51507425, 2331773, 36505482, 1239555, 18411915, 51047803, 54868730, 44245960, 71522968, 2204165, 83747892, 51426311, 39373729, 33237508, 38022834, 78766358, 7182642, 97379790, 36135, 4091162, 72732205, 27665211, 18699206, 91938191, 92033260, 29401781, 66319530, 20140249, 77187825, 31727379, 45996863, 76330843, 14349098, 47738185, 7502255, 40534591, 79821897, 45995325, 40152546, 16380211, 6521313, 82897371, 45306070, 62693428, 88904910, 29065964, 19457589, 57020481, 77300457, 43506672, 93566986, 68824981, 15148031, 10366309, 23848565, 84349107, 83210802, 76825057, 23432750, 41092172, 48673079, 27411561, 14947650, 97940276, 26139110, 59371804, 80316608, 21533347, 75820087, 8099994, 51715482, 49882705, 65271999, 62357986, 36312813, 28787861, 49328608, 57248122, 3183975, 81274566, 89419466, 14093520, 91990218, 48675329, 17976208, 6157724, 22997281, 69848388, 15015906, 65715134, 70596786, 53648154, 62232211, 38009615, 64087743, 89046466, 176257, 83789391, 70727211, 99861373, 79738755, 93790285, 12571310, 55753905, 59197747, 78589145, 69255765, 30569392, 7423788, 68875490, 13819358, 42644903, 62051033, 86543538, 43933006, 76671482, 52293995, 99729357, 84293052, 84904436, 36845587, 62936963, 35192533, 415901, 83948335, 91957544, 73124510, 75986488, 48658605, 69786756, 59177718, 70367851, 37620363, 18783702, 56473732, 99297164, 16971929, 27041967, 18131876, 31126490, 14045383, 68316156, 4119747, 63015256, 76960303, 14363867, 4069912, 31491938, 24953498, 1431742, 40781854, 67474219, 59501487, 99226875, 61982238, 38256849, 33565483, 26292919, 53632373, 6986898, 83155442, 46870723, 48774913, 44060493, 30771409, 37891451, 247198, 99971982, 67793644, 168541, 30163921, 66832478, 86821229, 56531125, 321665, 89637706, 62452034, 99125126, 44846932, 61141874, 45049308, 95251277, 61960400, 39553046, 31904591, 82327024, 36139942, 40677414, 83651211, 72725103, 9058407, 66137019, 45790169, 93053405, 45617087, 30139692, 57241521, 80251430, 13231279, 88698958, 21001913, 27185644, 90457870, 22450468, 28550822, 54762643, 88653118, 73235980, 96735716, 42782652, 2208785, 62115552, 78549759, 23569917, 98943869, 14326617, 36468541, 42237907, 37280276, 22200378, 44915235, 93515664, 15075176, 65454636, 90061527, 81677380, 38008118, 14723410, 1197320, 17857111, 24591705, 53393358, 86460488, 30395570, 76170907, 48360198, 65880522, 77377183, 61928316, 78561158, 66116458, 95395112, 65275241, 29932657, 82886381, 89217461, 17058722, 30543215, 85117092, 79880247, 73617245, 84002370, 26507214, 44847298, 37560164, 34295794, 15535065, 85116755, 71300104, 89214616, 42073124, 92998591, 77694700, 18663507, 93562257, 73109997, 51464002, 18504197, 55960386, 56461322, 22113133, 71316369, 68644627, 71333116, 16054533, 49641577, 41481685, 90004325, 24915585, 27625735, 37659250, 82532312, 56153345, 23194618, 72777973, 40356877, 10264691, 95726235, 18806856, 94711842, 43152977, 20645197, 74441448, 64055960, 6819644, 70420215, 77072625, 97783876, 74452589, 89699445, 99333375, 26744917, 50567636, 67513640, 17727650, 29819940, 61897582, 53888755, 72278539, 14220886, 90090964, 69697787, 8696647, 68694897, 5111370, 83368048, 26664538, 19376156, 91240048, 90158785, 45407418, 17764950, 4095116, 26102057, 49598724, 97561745, 44473167, 8373289, 8634541, 19891772, 58208470, 86001008, 3233084, 90013093, 72274002, 76315420, 16445503, 68102437, 28928175, 53084256, 6221471, 58224549, 45237957, 44664587, 3880712, 96420429, 26114953, 30694952, 33628349, 75052463, 38494874, 85023028, 60106217, 59981773, 55470718, 75229462, 83150534, 95290172, 70782102, 88444207, 49236559, 47893286, 6497830, 10309525, 15163258, 89996536, 84166196, 20836893, 67227442, 3633375, 65338021, 19898053, 69605283, 15902805, 24619760, 54263880, 44177011, 31733363, 15064655, 64157906, 67031644, 30090481, 22108665, 9575954, 47213183, 5640302, 37183543, 42967683, 16097038, 98653983, 55189057, 66271566, 34698463, 54427233, 30653863, 65081429, 80555751, 37739481, 38365584, 92692283, 77620120, 92541302, 45428665, 9175338, 92604458, 63967300, 22766820, 43322743, 66885828, 74724075, 7646095, 41245325, 23740167, 47090124, 7011964, 8791066, 52204879, 2607799, 74357852, 61859143, 52060076, 49271185, 1204161, 65047700, 66428795, 65074535, 72238278, 29994197, 39201414, 12024238, 1375023, 55247455, 86242799, 60955663, 73021291, 3294781, 824872, 56424103, 11757872, 62762857, 79322415, 55669657, 17037369, 84187166, 39986008, 37192445, 12664567, 84127901, 57803235, 40686254, 99224392, 34946859, 59318837, 94076128, 34432810, 94595668, 22721500, 44550764, 74743862, 56515456, 72358170, 44627776, 16387575, 65038678, 71083565, 24733232, 32161669, 93057697, 69136837, 13470059, 51588015, 76540481, 70800879, 75543508, 22435353, 18001617, 90272749, 17539625, 44348328, 17957593, 33304202, 87160386, 61623915, 10961421, 7104732, 9623492, 3088684, 79657802, 33895336, 508198, 7229550, 74137926, 78602717, 81805959, 9259676, 8651647, 1788101, 57393458, 54014062, 84406788, 34667286, 32250045, 77272628, 14626618, 6525948, 98130363, 26734892, 32699744, 24058273, 6153406, 73392814, 21993752, 73222868, 22942635, 30891921, 21673260, 38061439, 40027975, 37224844, 8729146, 76434144, 45990383, 52613508, 86301513, 39801351, 20765474, 92867155, 60581278, 21289531, 29959549, 1022677, 569864, 33553959, 7300047, 4204661, 42307449, 80014588, 62740044, 29188588, 7685448, 99690194, 32426752, 42692881, 27187213, 29029316, 47708850, 79136082, 6808825, 7066775, 34044787, 5970607, 29466406, 29834512, 55143724, 7517032, 77898274, 82651278, 90654836, 54232247, 79806380, 33935899, 7687278, 19272365, 86798033, 16684829, 51466049, 60393039, 83083131, 14731700, 20002147, 72357096, 57658654, 24168411, 87710366, 37166608, 77413012, 57240218, 17386996, 17385531, 82726008, 21397057, 71657078, 46540998, 96318094, 97057187, 97641116, 91255408, 11365791, 94911072, 9886593, 36812683, 79191827, 92803766, 33336148, 26493119, 97281847, 29516592, 94090109, 3509435, 69641513, 26863229, 38510840, 17894977, 93359396, 66611759, 48395186, 66250369, 28796059, 26776922, 60430369, 91802888, 20568363, 66903004, 47792865, 26392416, 64848072, 9829782, 51472275, 4199704, 4978543, 53802686, 15948937, 62430984, 94539824, 30764367, 34236719, 54663246, 7919588, 20486294, 82178706, 44844121, 40984766, 12416768, 3773993, 61373987, 20867149, 62552524, 63059024, 43045786, 10649306, 31161687, 62803858, 73168565, 37675718, 77312810, 53666583, 72793444, 58180653, 82052050, 36685023, 76703609, 16019925, 874791, 46851987, 78909561, 49597667, 41380093, 39847321, 112651, 12303248, 71469330, 37501808, 36808486, 70541760, 33699435, 4508700, 59329511, 95957797, 43376279, 91664334, 16424199, 40781449, 92692978, 69355476, 99021067, 68204242, 8733068, 56484900, 98948034, 66663942, 41092102, 11581181, 68939068, 45848907, 15536795, 54058788, 44983451, 4985896, 44481640, 38645117, 94516935, 4434662, 25842078, 71965942, 17068582, 43357947, 72019362, 58749, 11923835, 91141395, 34698428, 66667729, 82427263, 4515343, 6038457, 51315460, 61271144, 36933038, 13862149, 10358899, 63628376, 9860195, 16595436, 53547802, 35456853, 81898046, 68110330, 87598888, 75135448, 44784505, 7955293, 19101477, 61741594, 44889423, 17146629, 96726697, 57359924, 9257405, 72373496, 73786944, 16567550, 83302115, 78300864, 4668450, 96193415, 34497327, 86022504, 36396314, 5832946, 65851721, 54987042, 2917920, 18833224, 45075471, 61859581, 60176618, 4787945, 83533741, 79942022, 35996293, 91727510, 2891150, 81855445, 84684495, 98462867, 78785507, 88251446, 99658235, 40865610, 8055981, 68702632, 79922758, 42947632, 70036438, 51590803, 68128438, 59614746, 45794415, 80246713, 33061250, 30787683, 1569515, 19486173, 5482538, 8129978, 92554120, 61263068, 93933709, 11543098, 26063929, 72614359, 32590267, 72738685, 16099750, 98371444, 4099191, 58751351, 87720882, 50668599, 37435892, 62428472, 8128637, 1250437, 52261574, 10453030, 2717150, 82339363, 22405120, 19939935, 75153252, 3487592, 36189527, 28734791, 67451935, 20885148, 27325428, 17365188, 32159704, 62490109, 8807940, 77301523, 4798568, 48892201, 57163802, 70372191, 63152504, 25636669, 89811711, 24314885, 8115266, 33431960, 29510992, 47361209, 57961282, 93270084, 9860968, 36580610, 6793819, 77787724, 77284619, 30218878, 96709982, 82779622, 55770687, 4064751, 38658347, 81853704 +94711842, 3235882, 7687278, 75229462, 83789391, 48360198, 37659250, 24953498, 74441448, 34432810, 69697787, 60581278, 29466406, 6871053, 168541, 44627776, 9599614, 48673079, 69579137, 99658235, 32159704, 21070110, 17058722, 73617245, 72614359, 17146629, 85711894, 66428795, 29994197, 45848907, 84127901, 46870723, 16387575, 95251277, 68824981, 45407418, 16405341, 76540481, 83533741, 33336148, 53084256, 44664587, 85023028, 42947632, 14626618, 15163258, 24591705, 15015906, 21993752, 38061439, 67031644, 68000591, 30569392, 55850790, 29932657, 77301523, 39171895, 66271566, 76703609, 874791, 84904436, 72738685, 39847321, 12303248, 66885828, 96726697, 36135, 57359924, 33935899, 91938191, 39201414, 95726235, 18806856, 86022504, 82726008, 4064751, 94595668, 97057187, 91255408, 43501211, 82339363, 92803766, 79942022, 26493119, 98462867, 21001913, 27185644, 76315420, 40865610, 75153252, 3487592, 93270084, 81274566, 21919959, 70782102, 54014062, 10358899, 63628376, 20885148, 72495719, 81853704, 24058273, 38658347, 53648154, 76434144, 8129978, 62051033, 1022677, 26063929, 30653863, 15535065, 70372191, 63967300, 92998591, 5073754, 71469330, 73109997, 4099191, 56461322, 77787724, 16971929, 68316156, 90654836, 4119747, 56153345, 50668599, 31491938, 99524975, 86242799, 20002147, 6819644, 67030811, 66663942, 34497327, 9603598, 37166608, 11581181, 33565483, 26292919, 36396314, 53632373, 48774913, 40534591, 32151165, 65851721, 67793644, 86821229, 11365791, 321665, 61141874, 36753250, 45075471, 10366309, 19376156, 40677414, 13470059, 4787945, 35512853, 92787493, 41092172, 97940276, 22435353, 45667668, 8634541, 17539625, 93359396, 28550822, 66611759, 88653118, 66045587, 49328608, 82427263, 81805959, 33628349, 59981773, 88444207, 36580610, 70036438, 44915235, 59405277, 77272628, 54663246, 20836893, 55770687, 80246713, 3773993, 24619760, 76170907, 59197747, 64602895, 61928316, 33123618, 61263068, 569864, 33553959, 3233569, 76671482, 51149804, 75986488, 9175338, 85116755, 71300104, 99690194, 27187213, 36808486, 86118021, 25636669, 71316369, 2607799, 78766358, 81774825, 4091162, 54232247, 65074535, 72777973, 45919976, 67474219, 70420215, 62762857, 38256849, 31727379, 50567636, 82779622, 97641116, 53888755, 44983451, 48893685, 2917920, 99125126, 71083565, 15148031, 69136837, 60176618, 23432750, 22405120, 70800879, 94516935, 91727510, 80316608, 29510992, 83133790, 8099994, 78785507, 58749, 34698428, 62357986, 7104732, 58224549, 36312813, 68702632, 95581843, 60430369, 51315460, 9259676, 75052463, 14093520, 47792865, 83150534, 4199704, 6497830, 15075176, 92398073, 15948937, 27325428, 90061527, 17365188, 94539824, 26734892, 17857111, 61712234, 3633375, 70596786, 19898053, 40984766, 33061250, 44177011, 89046466, 61373987, 70727211, 37224844, 15064655, 8729146, 55753905, 78561158, 68875490, 92554120, 29959549, 37675718, 86543538, 6111563, 36685023, 84293052, 46851987, 4798568, 44847298, 61815223, 1239555, 38365584, 32590267, 37560164, 29635537, 48658605, 69786756, 44889423, 2204165, 7646095, 70541760, 51426311, 5970607, 7182642, 90004325, 72732205, 92692978, 82532312, 65047700, 19272365, 76960303, 50188404, 99021067, 83302115, 98948034, 61982238, 96193415, 33201905, 89699445, 8128637, 6986898, 96709982, 99515901, 52261574, 99224392, 44060493, 50806615, 82897371, 67124282, 56515456, 5111370, 94911072, 36812683, 19457589, 32274392, 82327024, 44842615, 93057697, 23848565, 5267545, 83210802, 72725103, 66137019, 26139110, 75543508, 59371804, 93053405, 4434662, 18001617, 97561745, 8373289, 80251430, 13173644, 78218845, 47361209, 81855445, 21533347, 58208470, 88698958, 17957593, 90457870, 87160386, 72019362, 61623915, 3088684, 54199160, 33895336, 508198, 79922758, 91802888, 91831487, 60106217, 9829782, 51472275, 6157724, 18466635, 32250045, 34493392, 34236719, 98130363, 20681921, 6153406, 89499542, 20486294, 73222868, 86460488, 30787683, 176257, 99861373, 93790285, 65880522, 22108665, 45990383, 9575954, 52613508, 69255765, 39801351, 20765474, 65275241, 38341669, 62803858, 21289531, 47887470, 17016156, 77312810, 89217461, 42967683, 43933006, 4204661, 89804152, 82052050, 52293995, 34698463, 16019925, 54427233, 80014588, 65081429, 30463802, 61741594, 415901, 73124510, 92692283, 77620120, 34295794, 57163802, 9188443, 98371444, 51047803, 89214616, 32426752, 112651, 33249630, 44245960, 37620363, 63152504, 50007421, 47090124, 68644627, 95957797, 74357852, 88047921, 16054533, 41481685, 61859143, 52060076, 30811010, 27625735, 69355476, 71920426, 49271185, 86798033, 44479073, 16684829, 85571389, 47298834, 55247455, 20645197, 1431742, 78300864, 30218878, 56424103, 11757872, 79322415, 55669657, 24168411, 37192445, 57803235, 17081350, 17385531, 1250437, 76330843, 29819940, 5832946, 7502255, 34946859, 10453030, 96318094, 247198, 4985896, 22721500, 74743862, 92353856, 9398733, 89637706, 62452034, 88904910, 91281584, 45049308, 77300457, 18833224, 39553046, 83368048, 79191827, 93566986, 61859581, 84349107, 90310261, 20642888, 9058407, 2891150, 30139692, 92215320, 75820087, 44348328, 17894977, 43357947, 9623492, 66250369, 28787861, 28796059, 96735716, 42782652, 7229550, 95010552, 36468541, 36933038, 67084351, 22200378, 36189527, 28734791, 53802686, 51590803, 97011160, 68128438, 65715134, 32699744, 53393358, 78884452, 69605283, 54263880, 1569515, 70004753, 20867149, 19486173, 5482538, 64157906, 75407347, 63059024, 43045786, 5640302, 10649306, 98739783, 31161687, 75777973, 73168565, 93933709, 94360702, 48088883, 53666583, 8913721, 42307449, 63506281, 13468268, 51507425, 62740044, 36845587, 62936963, 19101477, 48892201, 49597667, 23110625, 92604458, 59177718, 42073124, 43322743, 12348118, 29029316, 70367851, 79136082, 18504197, 23740167, 7066775, 55960386, 18783702, 33699435, 58751351, 33237508, 89811711, 29834512, 43376279, 16424199, 7517032, 82651278, 28851716, 74110882, 1204161, 92033260, 4069912, 23194618, 29401781, 9257405, 72373496, 12024238, 1375023, 43152977, 56484900, 4668450, 66319530, 824872, 62496012, 99226875, 74452589, 54606384, 83269727, 99333375, 17037369, 45996863, 15536795, 77413012, 89078848, 83155442, 21397057, 71657078, 79821897, 59318837, 40152546, 37891451, 73031054, 30163921, 72278539, 45306070, 44846932, 29065964, 57020481, 99917599, 31904591, 26664538, 36139942, 59109400, 85242963, 35996293, 45790169, 49598724, 68041839, 29516592, 33431960, 69641513, 84684495, 26863229, 3233084, 25842078, 71965942, 90013093, 72274002, 84840549, 26998766, 75128745, 10961421, 11923835, 63372756, 65271999, 6221471, 48395186, 66667729, 26776922, 2208785, 4515343, 96420429, 6038457, 3183975, 30694952, 38494874, 8651647, 9860968, 68816413, 89419466, 55470718, 1788101, 26392416, 30366150, 13862149, 49236559, 47893286, 67451935, 37957788, 4978543, 34667286, 89996536, 59614746, 14723410, 1197320, 67227442, 73392814, 85695762, 82178706, 62232211, 22942635, 21673260, 62490109, 68110330, 99604946, 31733363, 40027975, 12571310, 75135448, 24826575, 98648327, 44784505, 47213183, 92071990, 86301513, 7423788, 92867155, 16097038, 26275734, 42199455, 55189057, 40197395, 85117092, 66322959, 80555751, 78909561, 36505482, 37739481, 7955293, 91957544, 41380093, 29188588, 6793819, 7685448, 42692881, 77694700, 37501808, 51464002, 6808825, 41245325, 8791066, 99297164, 41442762, 59329511, 38022834, 71333116, 27041967, 18131876, 91664334, 55143724, 14045383, 49641577, 32058615, 79806380, 24314885, 14363867, 87720882, 68204242, 16567550, 8733068, 14731700, 73021291, 3294781, 72357096, 20140249, 59501487, 77072625, 87710366, 62428472, 84187166, 68939068, 50702367, 45995325, 16380211, 66832478, 2717150, 44550764, 68694897, 56531125, 65017137, 72358170, 9886593, 526217, 43506672, 61960400, 54517921, 92530431, 44481640, 24733232, 38645117, 90158785, 83651211, 4095116, 97281847, 90272749, 94090109, 19891772, 57961282, 86001008, 19939935, 33304202, 17068582, 68102437, 28928175, 53842979, 55602660, 74137926, 23569917, 66903004, 14326617, 26397786, 65454636, 10309525, 81677380, 22997281, 84166196, 16595436, 53547802, 65338021, 35456853, 22879907, 81898046, 30891921, 15902805, 87598888, 78589145, 30090481, 95395112, 13819358, 82979980, 60102965, 98653983, 30543215, 79880247, 55615885, 84002370, 26507214, 35192533, 92541302, 96906410, 54868730, 56473732, 7011964, 22113133, 31126490, 59910624, 97379790, 63015256, 40356877, 51466049, 60393039, 37435892, 83083131, 40781854, 5822202, 36930650, 97783876, 77187825, 26744917, 39986008, 56955985, 12664567, 40686254, 22129328, 14349098, 17727650, 30771409, 46540998, 61897582, 54987042, 669105, 14220886, 90090964, 8696647, 74614639, 8115266, 17764950, 26102057, 27411561, 33797252, 13231279, 38510840, 35092039, 49882705, 91141395, 8055981, 62115552, 78602717, 78549759, 61271144, 749283, 48675329, 93515664, 17976208, 9860195, 11161731, 69848388, 7919588, 44844121, 8807940, 12416768, 30395570, 13348726, 77377183, 62552524, 66116458, 42644903, 61728685, 82886381, 23134715, 7300047, 58180653, 11543098, 2331773, 83948335, 45428665, 81172706, 71522968, 6505939, 83747892, 67281495, 4508700, 34044787, 52204879, 20122224, 27665211, 77284619, 57658654, 67513640, 45381876, 54058788, 10597197, 65038678, 32161669, 91240048, 99549373, 14947650, 44473167, 57241521, 16445503, 51715482, 22450468, 73235980, 45237957, 79657802, 30998561, 57393458, 37280276, 64848072, 62430984, 30764367, 45794415, 38009615, 79738755, 37183543, 72793444, 48260151, 22766820, 47708850, 93562257, 99965001, 77898274, 24915585, 40781449, 73786944, 64055960, 60955663, 36780454, 57240218, 6521313, 62693428, 76825057, 51588015, 45617087, 3509435, 96852321, 88251446, 3880712, 57248122, 26114953, 20568363, 98943869, 42237907, 91990218, 38008118, 64098930, 6525948, 88130087, 16099750, 18411915, 74724075, 18663507, 39373729, 18699206, 10264691, 47738185, 4806458, 41092102, 17386996, 99971982, 54762643, 84406788, 99729357, 94076128, 1936762, 95290172, 64087743, 72238278 +68110330, 45990383, 3233084, 45237957, 89804152, 61815223, 45428665, 99917599, 75153252, 63059024, 86543538, 61859581, 2891150, 3487592, 91141395, 66611759, 18466635, 17857111, 59177718, 91664334, 88047921, 92033260, 45919976, 62496012, 89699445, 30771409, 7502255, 74614639, 26139110, 69579137, 26863229, 73235980, 64098930, 86460488, 70004753, 22108665, 53666583, 30543215, 13468268, 51507425, 37560164, 16099750, 97379790, 72357096, 62428472, 17081350, 47738185, 90090964, 91240048, 66137019, 45667668, 18001617, 19891772, 96852321, 86001008, 65271999, 96420429, 3183975, 95010552, 61271144, 54014062, 84406788, 94539824, 32159704, 30764367, 53393358, 15902805, 70727211, 61928316, 44784505, 62051033, 61263068, 77301523, 89217461, 23134715, 79880247, 26507214, 7955293, 63967300, 12348118, 7182642, 90004325, 27665211, 33935899, 14363867, 95726235, 37435892, 86242799, 60955663, 36930650, 37166608, 99333375, 39986008, 77413012, 79821897, 99971982, 73031054, 4985896, 2917920, 99125126, 36812683, 19457589, 82339363, 82327024, 45790169, 4434662, 90272749, 47361209, 17539625, 13231279, 99658235, 3088684, 49328608, 42782652, 55602660, 66903004, 57393458, 64848072, 47893286, 27325428, 51590803, 14626618, 69848388, 19898053, 85695762, 21993752, 73222868, 38009615, 87598888, 99861373, 79738755, 5482538, 69255765, 92867155, 4204661, 11543098, 16019925, 54427233, 29188588, 39847321, 42073124, 92998591, 22766820, 70367851, 79136082, 18504197, 50007421, 56461322, 89811711, 14045383, 82651278, 20122224, 4119747, 74110882, 79806380, 24314885, 91938191, 78300864, 6819644, 30218878, 86022504, 84127901, 57240218, 17385531, 52261574, 17727650, 48774913, 82779622, 40152546, 97641116, 30163921, 91255408, 8696647, 67124282, 57020481, 43501211, 83368048, 10366309, 69136837, 83651211, 85242963, 83533741, 97940276, 59371804, 33797252, 68041839, 19939935, 76315420, 93359396, 11923835, 88653118, 36312813, 9860968, 95290172, 36933038, 26392416, 36189527, 37957788, 92398073, 32250045, 22997281, 15163258, 34236719, 98130363, 53547802, 6153406, 82178706, 62490109, 12416768, 20867149, 31733363, 93790285, 55753905, 24826575, 64602895, 78589145, 62552524, 9575954, 66116458, 8129978, 10649306, 95395112, 61728685, 60581278, 17016156, 94360702, 48088883, 3233569, 58180653, 36685023, 85117092, 99729357, 55615885, 4798568, 80555751, 38365584, 77620120, 85116755, 32426752, 27187213, 44889423, 6505939, 33237508, 71316369, 38022834, 74357852, 78766358, 16054533, 16424199, 81774825, 24915585, 90654836, 72732205, 71920426, 4069912, 73786944, 12024238, 40781854, 73021291, 3294781, 99226875, 66663942, 34497327, 77072625, 57658654, 41092102, 36780454, 50567636, 67513640, 6986898, 99515901, 76330843, 99224392, 29819940, 96318094, 59318837, 247198, 97057187, 14220886, 68694897, 45306070, 56531125, 94911072, 88904910, 44627776, 29065964, 36753250, 31904591, 26664538, 36139942, 38645117, 76825057, 17764950, 14947650, 93053405, 57241521, 29510992, 81855445, 88698958, 17957593, 17068582, 68102437, 58749, 30998561, 62115552, 30694952, 1936762, 91990218, 17976208, 15075176, 34667286, 59614746, 22942635, 33061250, 44177011, 83789391, 40027975, 8729146, 59197747, 77377183, 92071990, 86301513, 43045786, 92554120, 62803858, 75777973, 29932657, 82886381, 21289531, 47887470, 569864, 55189057, 8913721, 34698463, 42307449, 48260151, 80014588, 84904436, 1239555, 91957544, 34295794, 98371444, 7685448, 99690194, 112651, 5073754, 71469330, 93562257, 6808825, 55960386, 7011964, 99297164, 41442762, 58751351, 2607799, 40781449, 7687278, 65074535, 63015256, 50188404, 56153345, 99021067, 23194618, 47298834, 55247455, 67474219, 77284619, 20140249, 824872, 5822202, 61982238, 9603598, 79322415, 97783876, 77187825, 55669657, 26744917, 45848907, 45381876, 12664567, 26292919, 40686254, 5832946, 71657078, 34946859, 45995325, 2717150, 44550764, 48893685, 89637706, 72358170, 95251277, 18833224, 54517921, 45075471, 39553046, 92530431, 68824981, 32161669, 44842615, 93057697, 90158785, 4787945, 45407418, 41092172, 20642888, 22405120, 76540481, 26102057, 27411561, 80316608, 97281847, 78218845, 57961282, 75820087, 71965942, 72274002, 16445503, 90457870, 72019362, 40865610, 10961421, 63372756, 7104732, 44664587, 66667729, 28787861, 28796059, 79657802, 33895336, 57248122, 7229550, 78602717, 81805959, 33628349, 9259676, 20568363, 85023028, 68816413, 89419466, 55470718, 88444207, 26397786, 37280276, 22200378, 749283, 48675329, 11161731, 34493392, 54663246, 6525948, 14723410, 15015906, 67227442, 32699744, 89499542, 53648154, 62232211, 8807940, 99604946, 37224844, 75135448, 48360198, 65880522, 88130087, 3235882, 78561158, 30569392, 7423788, 68875490, 77312810, 82979980, 93933709, 98653983, 40197395, 51149804, 26063929, 84002370, 62936963, 78909561, 32590267, 23110625, 6793819, 57163802, 92604458, 96906410, 54868730, 69786756, 42692881, 74724075, 47708850, 51464002, 23740167, 99965001, 18783702, 86118021, 56473732, 33699435, 25636669, 4508700, 22113133, 5970607, 71333116, 29466406, 29834512, 96726697, 55143724, 36135, 68316156, 4091162, 27625735, 37659250, 92692978, 82532312, 18699206, 86798033, 44479073, 87720882, 68204242, 72238278, 85571389, 29994197, 40356877, 18806856, 60393039, 31491938, 1375023, 99524975, 43152977, 24953498, 11757872, 74452589, 11581181, 17037369, 37192445, 45996863, 15536795, 50702367, 83155442, 96709982, 14349098, 54058788, 94076128, 65851721, 66832478, 86821229, 72278539, 74743862, 11365791, 69697787, 9398733, 321665, 9886593, 44846932, 61141874, 43506672, 32274392, 93566986, 24733232, 19376156, 59109400, 35512853, 51588015, 92787493, 16405341, 29516592, 44473167, 8634541, 33431960, 94090109, 13173644, 58208470, 84684495, 83133790, 27185644, 90013093, 43357947, 51715482, 26998766, 78785507, 28928175, 66250369, 54199160, 26776922, 96735716, 508198, 82427263, 2208785, 91802888, 6038457, 14093520, 47792865, 70782102, 1788101, 49236559, 51472275, 6497830, 44915235, 28734791, 67451935, 9860195, 59405277, 6157724, 65454636, 20885148, 81677380, 84166196, 38008118, 1197320, 26734892, 20681921, 3633375, 70596786, 78884452, 55770687, 64087743, 54263880, 30787683, 76434144, 67031644, 39801351, 20765474, 55850790, 42967683, 16097038, 39171895, 72793444, 17058722, 874791, 84293052, 65081429, 2331773, 72614359, 44847298, 30463802, 415901, 92692283, 92541302, 51047803, 43322743, 29029316, 81172706, 18663507, 37620363, 37501808, 36808486, 83747892, 41245325, 70541760, 7066775, 17146629, 68644627, 95957797, 16971929, 27041967, 31126490, 49641577, 32058615, 52060076, 30811010, 65047700, 29401781, 9257405, 10264691, 16567550, 51466049, 8733068, 94711842, 56484900, 74441448, 14731700, 67030811, 98948034, 96193415, 54606384, 33201905, 83269727, 84187166, 68939068, 8128637, 53632373, 17386996, 1250437, 4064751, 21397057, 44060493, 40534591, 6871053, 16380211, 10597197, 67793644, 50806615, 61897582, 82897371, 45049308, 65038678, 71083565, 44481640, 83210802, 40677414, 8115266, 23432750, 72725103, 9058407, 4095116, 33336148, 26493119, 8373289, 80251430, 92215320, 3509435, 25842078, 35092039, 22450468, 87160386, 28550822, 75128745, 34698428, 54762643, 58224549, 9623492, 3880712, 53842979, 4515343, 74137926, 23569917, 42947632, 36468541, 10358899, 63628376, 9829782, 90061527, 17365188, 20836893, 24591705, 61712234, 81853704, 24058273, 45794415, 20486294, 35456853, 81898046, 44844121, 21673260, 40984766, 89046466, 1569515, 176257, 19486173, 64157906, 68000591, 47213183, 5640302, 98739783, 13819358, 42644903, 65275241, 37675718, 37183543, 60102965, 82052050, 66271566, 66322959, 73617245, 36845587, 36505482, 37739481, 35192533, 61741594, 48892201, 83948335, 49597667, 72738685, 9175338, 9188443, 70372191, 71522968, 34044787, 41481685, 28851716, 54232247, 49271185, 1204161, 76960303, 50668599, 72777973, 83302115, 1431742, 83083131, 4668450, 20002147, 38256849, 24168411, 31727379, 33565483, 89078848, 36396314, 22129328, 37891451, 94595668, 53888755, 669105, 44983451, 22721500, 62452034, 65017137, 91281584, 526217, 77300457, 61960400, 79191827, 9599614, 92803766, 13470059, 99549373, 4806458, 48673079, 70800879, 79942022, 35996293, 22435353, 45617087, 97561745, 21533347, 69641513, 38510840, 8099994, 84840549, 61623915, 6221471, 62357986, 8055981, 93270084, 68702632, 66045587, 60430369, 78549759, 51315460, 91831487, 81274566, 59981773, 75229462, 21919959, 67084351, 42237907, 13862149, 70036438, 53802686, 68128438, 89996536, 38658347, 22879907, 30891921, 38061439, 3773993, 24619760, 61373987, 12571310, 30090481, 75407347, 38341669, 31161687, 73168565, 29959549, 1022677, 33553959, 6111563, 43933006, 26275734, 42199455, 63506281, 46851987, 73124510, 48658605, 89214616, 12303248, 77694700, 2204165, 67281495, 8791066, 39373729, 52204879, 59910624, 7517032, 77898274, 61859143, 57359924, 19272365, 39201414, 56424103, 70420215, 82726008, 46870723, 46540998, 32151165, 34432810, 54987042, 5111370, 16387575, 15148031, 5267545, 60176618, 75543508, 49598724, 44348328, 98462867, 21001913, 33304202, 53084256, 48395186, 95581843, 75052463, 14326617, 36580610, 93515664, 15948937, 72495719, 73392814, 69605283, 80246713, 30395570, 76170907, 33123618, 7300047, 76671482, 76703609, 41380093, 15535065, 75986488, 33249630, 66885828, 63152504, 51426311, 59329511, 77787724, 43376279, 85711894, 16684829, 72373496, 62762857, 57803235, 10453030, 168541, 6521313, 92353856, 56515456, 62693428, 23848565, 90310261, 94516935, 91727510, 17894977, 49882705, 88251446, 26114953, 38494874, 83150534, 4199704, 4978543, 62430984, 16595436, 65338021, 15064655, 13348726, 30653863, 18411915, 71300104, 44245960, 47090124, 18131876, 20645197, 64055960, 66319530, 59501487, 30366150, 10309525, 77272628, 7919588, 21070110, 52613508, 62740044, 29635537, 7646095, 73109997, 4099191, 69355476, 66428795, 87710366, 56955985, 30139692, 8651647, 98943869, 60106217, 97011160, 65715134, 52293995, 19101477, 84349107, 98648327, 79922758 +90310261, 22405120, 71300104, 4099191, 2607799, 17764950, 69641513, 84002370, 96906410, 99226875, 99971982, 6521313, 61859581, 8373289, 3509435, 49236559, 51472275, 88130087, 72793444, 2331773, 18806856, 11757872, 36780454, 79821897, 14220886, 67124282, 44627776, 91240048, 38645117, 45617087, 90013093, 26998766, 28928175, 95581843, 96735716, 79922758, 89996536, 73392814, 38061439, 68000591, 20765474, 62803858, 40197395, 12303248, 99965001, 22113133, 52204879, 1204161, 83083131, 34497327, 38256849, 37192445, 53632373, 46540998, 56531125, 99917599, 60176618, 16405341, 68041839, 26493119, 18001617, 78218845, 3487592, 58749, 65271999, 93270084, 66667729, 2208785, 1936762, 9860968, 14093520, 36933038, 18466635, 51590803, 34236719, 98130363, 53547802, 22879907, 44844121, 61373987, 75135448, 61928316, 44784505, 43045786, 38341669, 16097038, 26275734, 58180653, 42199455, 76671482, 85117092, 63506281, 66322959, 874791, 73617245, 30463802, 19101477, 37560164, 45428665, 42073124, 77694700, 25636669, 58751351, 29834512, 82651278, 28851716, 71920426, 86798033, 65074535, 68204242, 10264691, 8733068, 20002147, 20140249, 89699445, 26744917, 50702367, 10597197, 54987042, 2717150, 62693428, 526217, 32274392, 82339363, 36139942, 92803766, 59109400, 90158785, 4806458, 92787493, 20642888, 76540481, 26139110, 80316608, 47361209, 92215320, 71965942, 17894977, 72274002, 43357947, 11923835, 58224549, 3880712, 66045587, 42782652, 508198, 4515343, 96420429, 66903004, 30366150, 13862149, 47893286, 59405277, 34493392, 54663246, 3633375, 45794415, 80246713, 89046466, 30787683, 76170907, 78589145, 55850790, 60581278, 21289531, 29959549, 30543215, 26063929, 80014588, 84904436, 4798568, 7955293, 73124510, 85116755, 16099750, 70372191, 74724075, 93562257, 18783702, 86118021, 8791066, 18131876, 68316156, 30811010, 27625735, 69355476, 49271185, 7687278, 87720882, 39201414, 83302115, 64055960, 60955663, 72357096, 824872, 59501487, 77072625, 77187825, 37166608, 36396314, 57803235, 17081350, 46870723, 34946859, 37891451, 168541, 50806615, 4985896, 9398733, 29065964, 43501211, 45075471, 83368048, 79191827, 9599614, 83651211, 35512853, 9058407, 35996293, 75543508, 59371804, 97281847, 97561745, 94090109, 84684495, 96852321, 83133790, 90457870, 93359396, 88251446, 61623915, 66250369, 28796059, 7229550, 74137926, 23569917, 30694952, 36468541, 26392416, 36580610, 6497830, 44915235, 4978543, 14626618, 68128438, 94539824, 30764367, 84166196, 15015906, 21070110, 38658347, 82178706, 62232211, 62490109, 86460488, 87598888, 93790285, 77377183, 8129978, 75777973, 17016156, 37675718, 569864, 89217461, 82979980, 39171895, 60102965, 53666583, 55189057, 76703609, 65081429, 26507214, 62740044, 44847298, 78909561, 37739481, 35192533, 38365584, 29635537, 9175338, 89214616, 92604458, 59177718, 32426752, 18663507, 7646095, 33699435, 99297164, 41442762, 39373729, 34044787, 5970607, 71333116, 43376279, 91664334, 97379790, 77898274, 24915585, 37659250, 54232247, 44479073, 50188404, 51466049, 12024238, 73021291, 62496012, 62762857, 11581181, 84187166, 56955985, 45381876, 77413012, 89078848, 82726008, 82779622, 32151165, 96318094, 34432810, 94595668, 97641116, 44983451, 22721500, 74743862, 69697787, 5111370, 91281584, 36753250, 26664538, 32161669, 84349107, 48673079, 70800879, 26102057, 79942022, 2891150, 4434662, 49598724, 33336148, 44473167, 80251430, 86001008, 16445503, 84840549, 91141395, 9623492, 3088684, 33895336, 30998561, 62115552, 51315460, 75052463, 81274566, 42237907, 22200378, 70036438, 4199704, 67451935, 27325428, 61712234, 65338021, 55770687, 53648154, 73222868, 22942635, 21673260, 40984766, 33061250, 44177011, 1569515, 70004753, 99861373, 12571310, 19486173, 64602895, 30090481, 62552524, 78561158, 86301513, 7423788, 95395112, 65275241, 31161687, 73168565, 29932657, 33123618, 1022677, 61263068, 93933709, 23134715, 34698463, 99729357, 16019925, 13468268, 54427233, 46851987, 72614359, 62936963, 61741594, 41380093, 92692283, 77620120, 23110625, 15535065, 72738685, 39847321, 57163802, 43322743, 42692881, 66885828, 27187213, 29029316, 71522968, 36808486, 18504197, 41245325, 7066775, 56461322, 59329511, 16971929, 31126490, 59910624, 49641577, 36135, 40781449, 72732205, 65047700, 19272365, 66428795, 63015256, 16684829, 85571389, 95726235, 99524975, 43152977, 56484900, 86242799, 40781854, 3294781, 61982238, 56424103, 97783876, 74452589, 86022504, 33201905, 50567636, 45848907, 45996863, 15536795, 12664567, 96709982, 99515901, 14349098, 44060493, 7502255, 59318837, 6871053, 40152546, 97057187, 30163921, 53888755, 72278539, 90090964, 82897371, 68694897, 2917920, 321665, 72358170, 88904910, 19457589, 54517921, 44481640, 15148031, 10366309, 5267545, 8115266, 72725103, 99549373, 85242963, 27411561, 94516935, 91727510, 33797252, 13173644, 17539625, 81855445, 58208470, 26863229, 19939935, 38510840, 76315420, 22450468, 68102437, 99658235, 28550822, 44664587, 60430369, 91802888, 3183975, 26114953, 20568363, 98943869, 85023028, 59981773, 47792865, 55470718, 21919959, 95290172, 95010552, 61271144, 67084351, 15075176, 11161731, 34667286, 90061527, 77272628, 15163258, 38008118, 64098930, 14723410, 7919588, 16595436, 69605283, 20486294, 81898046, 68110330, 38009615, 3773993, 24619760, 48360198, 5482538, 76434144, 13348726, 98648327, 9575954, 92071990, 69255765, 63059024, 98739783, 13819358, 92554120, 6111563, 94360702, 89804152, 11543098, 48260151, 30653863, 61815223, 415901, 32590267, 49597667, 98371444, 51047803, 69786756, 33249630, 22766820, 37501808, 51464002, 51426311, 55960386, 17146629, 71316369, 38022834, 29466406, 74357852, 16054533, 57359924, 4091162, 92692978, 74110882, 18699206, 92033260, 50668599, 29401781, 9257405, 29994197, 31491938, 20645197, 6819644, 67474219, 77284619, 98948034, 70420215, 57658654, 55669657, 41092102, 24168411, 87710366, 17037369, 6986898, 17386996, 83155442, 52261574, 99224392, 4064751, 17727650, 47738185, 48774913, 71657078, 40534591, 54058788, 94076128, 86821229, 91255408, 92353856, 48893685, 62452034, 65017137, 16387575, 18833224, 43506672, 74614639, 61960400, 71083565, 39553046, 93566986, 24733232, 44842615, 19376156, 76825057, 13470059, 41092172, 93053405, 45667668, 29510992, 17957593, 17068582, 87160386, 6221471, 73235980, 45237957, 36312813, 79657802, 26776922, 49328608, 82427263, 55602660, 78602717, 33628349, 8651647, 75229462, 14326617, 26397786, 91990218, 57393458, 54014062, 10358899, 64848072, 48675329, 65454636, 92398073, 10309525, 15948937, 97011160, 32159704, 59614746, 26734892, 81853704, 67227442, 20681921, 32699744, 24058273, 6153406, 78884452, 89499542, 30891921, 8807940, 12416768, 70727211, 31733363, 40027975, 15064655, 55753905, 3235882, 75407347, 30569392, 66116458, 5640302, 68875490, 42644903, 82886381, 33553959, 86543538, 48088883, 36685023, 52293995, 42307449, 36845587, 80555751, 1239555, 75986488, 29188588, 18411915, 54868730, 5073754, 44245960, 81172706, 71469330, 6505939, 79136082, 83747892, 70541760, 23740167, 50007421, 56473732, 47090124, 68644627, 77787724, 27041967, 78766358, 88047921, 7182642, 55143724, 41481685, 7517032, 61859143, 52060076, 90004325, 20122224, 4119747, 24314885, 14363867, 4069912, 56153345, 99021067, 72238278, 73786944, 40356877, 60393039, 37435892, 74441448, 4668450, 79322415, 62428472, 68939068, 39986008, 84127901, 76330843, 5832946, 30771409, 10453030, 45995325, 73031054, 67793644, 61897582, 45306070, 56515456, 36812683, 57020481, 45049308, 92530431, 93057697, 4787945, 45407418, 97940276, 66137019, 30139692, 29516592, 57241521, 75820087, 88698958, 25842078, 35092039, 51715482, 49882705, 40865610, 75128745, 10961421, 88653118, 62357986, 68702632, 57248122, 91831487, 83150534, 1788101, 84406788, 63628376, 36189527, 93515664, 6157724, 53802686, 32250045, 81677380, 69848388, 1197320, 17857111, 20836893, 65715134, 70596786, 19898053, 30395570, 20867149, 83789391, 8729146, 59197747, 65880522, 67031644, 22108665, 52613508, 10649306, 39801351, 77312810, 7300047, 4204661, 3233569, 82052050, 66271566, 8913721, 51507425, 84293052, 36505482, 91957544, 92541302, 9188443, 7685448, 112651, 12348118, 44889423, 70367851, 2204165, 67281495, 7011964, 95957797, 96726697, 81774825, 90654836, 82532312, 76960303, 72777973, 72373496, 16567550, 94711842, 47298834, 1375023, 55247455, 1431742, 78300864, 66319530, 67030811, 5822202, 9603598, 54606384, 67513640, 57240218, 1250437, 21397057, 247198, 16380211, 669105, 44550764, 99125126, 44846932, 31904591, 83210802, 14947650, 45790169, 69579137, 21533347, 13231279, 57961282, 44348328, 98462867, 21001913, 8099994, 75153252, 53084256, 34698428, 63372756, 48395186, 8055981, 28787861, 54199160, 53842979, 6038457, 68816413, 60106217, 42947632, 37280276, 9829782, 28734791, 9860195, 22997281, 21993752, 99604946, 54263880, 176257, 37224844, 47213183, 62051033, 61728685, 37183543, 42967683, 79880247, 83948335, 6793819, 48658605, 63152504, 33237508, 89811711, 14045383, 16424199, 23194618, 83269727, 99333375, 31727379, 33565483, 26292919, 66832478, 8696647, 89637706, 94911072, 77300457, 95251277, 68824981, 69136837, 40677414, 4095116, 83533741, 33431960, 19891772, 27185644, 78785507, 54762643, 7104732, 78549759, 38494874, 37957788, 17976208, 20885148, 62430984, 6525948, 35456853, 15902805, 79738755, 64157906, 45990383, 92867155, 47887470, 55615885, 34295794, 92998591, 47708850, 73109997, 4508700, 85711894, 27665211, 33935899, 91938191, 24953498, 14731700, 66663942, 96193415, 40686254, 17385531, 22129328, 29819940, 23848565, 8634541, 72019362, 81805959, 89419466, 70782102, 749283, 72495719, 17365188, 85695762, 24826575, 51149804, 48892201, 63967300, 6808825, 32058615, 79806380, 45919976, 36930650, 8128637, 65851721, 11365791, 9886593, 61141874, 82327024, 23432750, 51588015, 90272749, 3233084, 33304202, 66611759, 9259676, 88444207, 24591705, 53393358, 77301523, 98653983, 17058722, 99690194, 37620363, 30218878, 65038678, 22435353, 64087743, 43933006 +10649306, 82052050, 2917920, 81855445, 24314885, 83302115, 11581181, 11365791, 508198, 569864, 85117092, 4099191, 17385531, 45075471, 44481640, 26863229, 96420429, 14626618, 9575954, 33553959, 8913721, 73124510, 7687278, 65074535, 94711842, 50567636, 26292919, 89078848, 5832946, 43501211, 39553046, 68824981, 45407418, 93359396, 9623492, 42782652, 26392416, 22879907, 70004753, 31733363, 30090481, 47213183, 42644903, 61263068, 63506281, 84904436, 2331773, 48658605, 89214616, 74724075, 7646095, 58751351, 30811010, 73021291, 3294781, 70420215, 33201905, 39986008, 45381876, 4064751, 59318837, 168541, 9886593, 29065964, 45049308, 10366309, 13470059, 49598724, 13173644, 69641513, 84684495, 19939935, 90457870, 78785507, 11923835, 8055981, 53842979, 95290172, 95010552, 10358899, 17976208, 18466635, 32250045, 98130363, 67227442, 32699744, 55770687, 30891921, 62490109, 3773993, 15064655, 19486173, 64602895, 98739783, 13819358, 33123618, 47887470, 42967683, 51149804, 76703609, 4798568, 72614359, 35192533, 91957544, 34295794, 51047803, 54868730, 70372191, 92998591, 70367851, 47090124, 95957797, 77787724, 43376279, 90004325, 20122224, 69355476, 18806856, 4668450, 6819644, 97783876, 37166608, 67513640, 6986898, 48774913, 40534591, 96318094, 74743862, 56531125, 91281584, 526217, 18833224, 32274392, 72725103, 70800879, 93053405, 8373289, 8634541, 29510992, 25842078, 35092039, 72274002, 99658235, 34698428, 3088684, 79922758, 81805959, 26397786, 63628376, 90061527, 73392814, 53648154, 38061439, 176257, 79738755, 8729146, 98648327, 77377183, 44784505, 38341669, 62803858, 61728685, 60581278, 17016156, 89217461, 39171895, 60102965, 26507214, 15535065, 6793819, 57163802, 71300104, 77694700, 29029316, 6808825, 23740167, 55960386, 7011964, 68644627, 2607799, 78766358, 7517032, 54232247, 27665211, 92033260, 56153345, 72373496, 45919976, 16567550, 95726235, 8733068, 60393039, 24953498, 34497327, 47738185, 6871053, 45995325, 247198, 34432810, 73031054, 4985896, 82897371, 68694897, 99125126, 65038678, 83368048, 79191827, 24733232, 32161669, 69136837, 92803766, 97940276, 66137019, 75543508, 59371804, 69579137, 47361209, 27185644, 63372756, 66611759, 68702632, 28787861, 28796059, 66045587, 49328608, 30694952, 91831487, 85023028, 42947632, 47792865, 75229462, 67084351, 70036438, 4978543, 6157724, 15948937, 72495719, 17365188, 15163258, 30764367, 84166196, 6525948, 26734892, 24058273, 78884452, 21993752, 35456853, 21673260, 40984766, 86460488, 87598888, 48360198, 22108665, 61928316, 52613508, 78561158, 30569392, 31161687, 17058722, 36685023, 26063929, 874791, 84293052, 84002370, 44847298, 19101477, 48892201, 38365584, 72738685, 98371444, 12303248, 66885828, 27187213, 44245960, 18663507, 67281495, 99965001, 56473732, 34044787, 16971929, 27041967, 16054533, 97379790, 90654836, 37659250, 82532312, 14363867, 44479073, 16684829, 23194618, 29994197, 43152977, 824872, 61982238, 57658654, 36930650, 33565483, 84187166, 84127901, 37891451, 94076128, 94595668, 97641116, 19457589, 61960400, 9599614, 36139942, 59109400, 60176618, 23432750, 4787945, 4806458, 41092172, 94516935, 80316608, 33336148, 45667668, 45617087, 97281847, 90272749, 30139692, 57961282, 3509435, 83133790, 90013093, 16445503, 75153252, 10961421, 66250369, 36312813, 33895336, 2208785, 7229550, 62115552, 74137926, 51315460, 98943869, 89419466, 70782102, 61271144, 30366150, 9829782, 47893286, 51472275, 28734791, 11161731, 89996536, 54663246, 1197320, 24591705, 16595436, 45794415, 69605283, 21070110, 22942635, 99604946, 54263880, 61373987, 93790285, 5482538, 68000591, 92071990, 8129978, 7423788, 95395112, 21289531, 29959549, 77301523, 16097038, 48088883, 53666583, 76671482, 66271566, 11543098, 34698463, 48260151, 16019925, 62740044, 62936963, 49597667, 92692283, 75986488, 32426752, 43322743, 73109997, 6505939, 7066775, 51426311, 18783702, 50007421, 71316369, 59329511, 29466406, 96726697, 31126490, 59910624, 16424199, 32058615, 81774825, 57359924, 4091162, 92692978, 91938191, 19272365, 63015256, 50188404, 99021067, 87720882, 50668599, 72238278, 9257405, 51466049, 47298834, 56484900, 86242799, 60955663, 67474219, 98948034, 5822202, 66663942, 11757872, 86022504, 24168411, 36780454, 99333375, 26744917, 37192445, 57803235, 17386996, 82726008, 22129328, 21397057, 29819940, 7502255, 34946859, 40152546, 54987042, 86821229, 2717150, 48893685, 5111370, 62693428, 62452034, 72358170, 88904910, 44627776, 16387575, 74614639, 99917599, 31904591, 82339363, 44842615, 5267545, 90158785, 35512853, 17764950, 22405120, 76540481, 85242963, 79942022, 26139110, 57241521, 80251430, 98462867, 86001008, 17894977, 43357947, 28550822, 91141395, 88653118, 44664587, 93270084, 66667729, 54199160, 26776922, 30998561, 4515343, 60430369, 20568363, 75052463, 1936762, 59981773, 14093520, 83150534, 36933038, 36580610, 54014062, 4199704, 6497830, 15075176, 92398073, 20885148, 10309525, 53802686, 34667286, 27325428, 22997281, 62430984, 64098930, 7919588, 20836893, 61712234, 20681921, 3633375, 70596786, 15902805, 33061250, 24619760, 30787683, 70727211, 99861373, 75135448, 3235882, 69255765, 75407347, 86301513, 43045786, 92554120, 62051033, 75777973, 29932657, 82886381, 1022677, 37675718, 77312810, 93933709, 94360702, 7300047, 4204661, 98653983, 58180653, 55189057, 30543215, 30653863, 7955293, 83948335, 39847321, 29635537, 96906410, 59177718, 42073124, 33249630, 22766820, 47708850, 37501808, 36808486, 51464002, 17146629, 99297164, 41442762, 22113133, 38022834, 91664334, 74357852, 88047921, 49641577, 41481685, 77898274, 61859143, 28851716, 27625735, 74110882, 18699206, 33935899, 65047700, 76960303, 4069912, 85571389, 39201414, 10264691, 1375023, 20645197, 74441448, 83083131, 40781854, 30218878, 59501487, 77072625, 54606384, 83269727, 31727379, 68939068, 56955985, 8128637, 15536795, 12664567, 50702367, 46870723, 99224392, 71657078, 54058788, 32151165, 79821897, 99971982, 669105, 6521313, 90090964, 69697787, 8696647, 45306070, 56515456, 321665, 65017137, 36812683, 57020481, 36753250, 77300457, 54517921, 92530431, 93566986, 15148031, 26664538, 61859581, 93057697, 84349107, 91240048, 8115266, 51588015, 92787493, 16405341, 4095116, 14947650, 2891150, 22435353, 4434662, 33797252, 26493119, 97561745, 29516592, 17539625, 58208470, 17957593, 71965942, 38510840, 33304202, 17068582, 72019362, 84840549, 3487592, 58749, 65271999, 6221471, 48395186, 62357986, 58224549, 96735716, 82427263, 78549759, 33628349, 9259676, 8651647, 21919959, 88444207, 1788101, 13862149, 44915235, 37957788, 59405277, 97011160, 68128438, 34493392, 94539824, 32159704, 34236719, 38008118, 81853704, 65715134, 6153406, 81898046, 8807940, 38009615, 30395570, 44177011, 1569515, 83789391, 37224844, 12571310, 76434144, 13348726, 62552524, 63059024, 39801351, 20765474, 92867155, 73168565, 86543538, 26275734, 72793444, 42199455, 40197395, 52293995, 42307449, 99729357, 66322959, 54427233, 55615885, 65081429, 36845587, 80555751, 78909561, 36505482, 30463802, 415901, 1239555, 41380093, 23110625, 18411915, 7685448, 99690194, 112651, 63967300, 42692881, 71522968, 37620363, 93562257, 63152504, 79136082, 70541760, 86118021, 33699435, 25636669, 71333116, 52204879, 18131876, 7182642, 68316156, 40781449, 72732205, 71920426, 49271185, 12024238, 31491938, 99524975, 37435892, 1431742, 14731700, 20002147, 66319530, 72357096, 20140249, 99226875, 56424103, 38256849, 41092102, 62428472, 77413012, 36396314, 53632373, 57240218, 96709982, 14349098, 17727650, 30771409, 10453030, 97057187, 67793644, 30163921, 61897582, 44983451, 22721500, 92353856, 94911072, 19376156, 40677414, 90310261, 76825057, 20642888, 35996293, 91727510, 18001617, 33431960, 78218845, 19891772, 92215320, 44348328, 88698958, 3233084, 76315420, 8099994, 68102437, 88251446, 40865610, 53084256, 55602660, 3183975, 78602717, 23569917, 9860968, 81274566, 60106217, 55470718, 36468541, 42237907, 91990218, 84406788, 48675329, 69848388, 53547802, 65338021, 19898053, 89499542, 38658347, 73222868, 82178706, 44844121, 80246713, 12416768, 40027975, 76170907, 59197747, 64157906, 67031644, 65275241, 82979980, 37183543, 23134715, 6111563, 43933006, 89804152, 73617245, 46851987, 61815223, 37560164, 29188588, 85116755, 18504197, 56461322, 29834512, 55143724, 85711894, 14045383, 82651278, 52060076, 79806380, 86798033, 66428795, 68204242, 73786944, 55247455, 78300864, 96193415, 62762857, 79322415, 87710366, 99515901, 76330843, 10597197, 72278539, 91255408, 9398733, 67124282, 89637706, 44846932, 95251277, 71083565, 82327024, 83210802, 38645117, 83651211, 99549373, 48673079, 26102057, 68041839, 94090109, 75820087, 96852321, 28928175, 75128745, 54762643, 45237957, 95581843, 3880712, 79657802, 91802888, 66903004, 68816413, 14326617, 57393458, 37280276, 22200378, 36189527, 67451935, 9860195, 65454636, 51590803, 77272628, 81677380, 15015906, 20486294, 89046466, 20867149, 55753905, 24826575, 65880522, 78589145, 45990383, 66116458, 5640302, 55850790, 3233569, 51507425, 80014588, 79880247, 16099750, 92604458, 12348118, 44889423, 71469330, 83747892, 41245325, 39373729, 33237508, 1204161, 72777973, 40356877, 67030811, 74452589, 77187825, 89699445, 45996863, 40686254, 1250437, 82779622, 16380211, 65851721, 66832478, 14220886, 44550764, 61141874, 9058407, 83533741, 27411561, 22450468, 87160386, 49882705, 7104732, 73235980, 57248122, 6038457, 26114953, 38494874, 49236559, 749283, 93515664, 14723410, 68110330, 88130087, 68875490, 13468268, 37739481, 32590267, 9175338, 69786756, 5073754, 81172706, 8791066, 4508700, 89811711, 36135, 24915585, 29401781, 64055960, 77284619, 9603598, 83155442, 17081350, 44060493, 50806615, 53888755, 43506672, 44473167, 21533347, 13231279, 21001913, 51715482, 61623915, 64848072, 62232211, 77620120, 92541302, 45428665, 5970607, 4119747, 62496012, 55669657, 17037369, 23848565, 45790169, 26998766, 9188443, 45848907, 52261574, 59614746, 17857111, 53393358, 61741594, 2204165, 85695762, 64087743, 46540998 +11543098, 54427233, 53084256, 96735716, 4091162, 6871053, 91255408, 79191827, 92787493, 29510992, 17068582, 28928175, 78561158, 8129978, 4204661, 89214616, 70372191, 12303248, 7646095, 74357852, 16054533, 57359924, 8733068, 32151165, 94595668, 14220886, 19457589, 78218845, 78785507, 16595436, 22108665, 9575954, 66116458, 98739783, 86543538, 13468268, 30653863, 71300104, 36808486, 55960386, 33935899, 29994197, 74452589, 37192445, 17081350, 17727650, 37891451, 66832478, 94911072, 82327024, 90310261, 60176618, 41092172, 18001617, 57241521, 75229462, 59405277, 32159704, 24058273, 55770687, 21993752, 12416768, 44784505, 7423788, 93933709, 80014588, 9175338, 92604458, 66885828, 5073754, 44889423, 86118021, 95957797, 29834512, 32058615, 20122224, 69355476, 71920426, 24314885, 99524975, 55247455, 86242799, 67030811, 98948034, 96193415, 34497327, 97783876, 56955985, 26292919, 77413012, 10453030, 50806615, 11365791, 5111370, 36753250, 54517921, 82339363, 19376156, 69136837, 90158785, 4787945, 17764950, 48673079, 26139110, 17539625, 17957593, 90013093, 72274002, 72019362, 40865610, 93270084, 66250369, 96420429, 6038457, 20568363, 36468541, 42237907, 15948937, 77272628, 14626618, 89996536, 26734892, 15015906, 73222868, 62232211, 22942635, 99604946, 64602895, 30090481, 68000591, 47213183, 31161687, 55850790, 29932657, 58180653, 40197395, 16019925, 874791, 73617245, 19101477, 32590267, 73124510, 72738685, 45428665, 29635537, 74724075, 29029316, 37620363, 51426311, 16971929, 78766358, 97379790, 16424199, 27625735, 49271185, 7687278, 39201414, 94711842, 37435892, 86022504, 24168411, 83269727, 11581181, 33565483, 68939068, 26744917, 45381876, 6986898, 4064751, 29819940, 34946859, 86821229, 74743862, 69697787, 67124282, 526217, 43501211, 61960400, 93566986, 44481640, 61859581, 9599614, 23848565, 38645117, 22405120, 83533741, 79942022, 91727510, 8373289, 21533347, 38510840, 27185644, 35092039, 22450468, 62357986, 3088684, 95581843, 28787861, 66045587, 74137926, 51315460, 9860968, 60106217, 13862149, 84406788, 49236559, 37280276, 47893286, 48675329, 28734791, 15075176, 9860195, 34667286, 32250045, 62430984, 94539824, 34236719, 54663246, 6525948, 1197320, 81853704, 78884452, 38658347, 80246713, 3773993, 87598888, 76170907, 75135448, 48360198, 76434144, 61928316, 62552524, 42644903, 60581278, 21289531, 77312810, 39171895, 48088883, 89804152, 42199455, 30543215, 76671482, 62740044, 44847298, 96906410, 32426752, 77694700, 27187213, 71522968, 51464002, 41245325, 67281495, 56461322, 33699435, 29466406, 59910624, 82651278, 90004325, 72732205, 74110882, 91938191, 66428795, 45919976, 43152977, 56484900, 24953498, 40781854, 20002147, 6819644, 3294781, 824872, 99226875, 66663942, 11757872, 55669657, 37166608, 17037369, 45848907, 50702367, 83155442, 76330843, 44060493, 5832946, 46540998, 44983451, 2917920, 62693428, 321665, 99125126, 88904910, 16387575, 18833224, 26664538, 44842615, 36139942, 91240048, 35512853, 9058407, 80316608, 33336148, 45667668, 30139692, 33431960, 92215320, 3509435, 69641513, 96852321, 19939935, 28550822, 63372756, 65271999, 45237957, 30998561, 26114953, 23569917, 33628349, 9259676, 98943869, 70782102, 1788101, 4199704, 93515664, 67451935, 10309525, 53802686, 17365188, 68128438, 30764367, 20836893, 65338021, 45794415, 85695762, 53648154, 82178706, 81898046, 30891921, 8807940, 24619760, 44177011, 61373987, 8729146, 55753905, 65880522, 3235882, 92071990, 39801351, 75777973, 37675718, 66271566, 34698463, 66322959, 65081429, 46851987, 78909561, 30463802, 415901, 91957544, 37560164, 6793819, 16099750, 18411915, 51047803, 54868730, 99690194, 22766820, 6808825, 70541760, 50007421, 4099191, 7011964, 17146629, 41442762, 58751351, 89811711, 22113133, 2607799, 41481685, 36135, 30811010, 92692978, 56153345, 68204242, 23194618, 29401781, 85571389, 95726235, 83302115, 12024238, 60393039, 1431742, 66319530, 59501487, 61982238, 62762857, 38256849, 33201905, 50567636, 36396314, 84127901, 57803235, 40686254, 47738185, 48774913, 40534591, 67793644, 61897582, 72278539, 22721500, 90090964, 48893685, 68694897, 62452034, 44627776, 29065964, 91281584, 45075471, 83368048, 68824981, 15148031, 32161669, 83651211, 51588015, 99549373, 76540481, 85242963, 94516935, 97940276, 66137019, 75543508, 22435353, 45790169, 45617087, 97281847, 44473167, 19891772, 47361209, 58208470, 75820087, 44348328, 84684495, 25842078, 17894977, 43357947, 8099994, 90457870, 84840549, 68102437, 75128745, 3487592, 44664587, 508198, 2208785, 60430369, 79922758, 55602660, 91831487, 85023028, 47792865, 55470718, 61271144, 88444207, 91990218, 30366150, 54014062, 10358899, 749283, 37957788, 20885148, 90061527, 72495719, 34493392, 84166196, 14723410, 98130363, 61712234, 20681921, 53547802, 6153406, 73392814, 89499542, 21673260, 86460488, 176257, 20867149, 31733363, 99861373, 78589145, 67031644, 45990383, 68875490, 10649306, 13819358, 92554120, 65275241, 38341669, 92867155, 47887470, 1022677, 61263068, 33553959, 42967683, 94360702, 60102965, 3233569, 36685023, 84904436, 4798568, 72614359, 35192533, 92692283, 42073124, 92998591, 42692881, 81172706, 2204165, 6505939, 18504197, 39373729, 59329511, 27041967, 91664334, 96726697, 88047921, 7182642, 68316156, 28851716, 37659250, 54232247, 1204161, 92033260, 63015256, 76960303, 14363867, 44479073, 72777973, 9257405, 72373496, 10264691, 31491938, 20645197, 4668450, 14731700, 67474219, 57658654, 8128637, 12664567, 53632373, 57240218, 17386996, 52261574, 99224392, 82779622, 7502255, 96318094, 34432810, 97057187, 168541, 2717150, 92353856, 9398733, 89637706, 57020481, 45049308, 95251277, 65038678, 71083565, 92530431, 5267545, 84349107, 92803766, 83210802, 59109400, 13470059, 4806458, 45407418, 16405341, 2891150, 4434662, 33797252, 94090109, 69579137, 26863229, 86001008, 93359396, 26998766, 88251446, 75153252, 58749, 11923835, 91141395, 34698428, 6221471, 66611759, 48395186, 58224549, 66667729, 36312813, 3880712, 53842979, 26776922, 57248122, 91802888, 7229550, 3183975, 78549759, 68816413, 14326617, 83150534, 21919959, 36933038, 26392416, 57393458, 11161731, 92398073, 27325428, 3633375, 70596786, 19898053, 21070110, 44844121, 40984766, 38061439, 33061250, 54263880, 89046466, 1569515, 37224844, 13348726, 69255765, 75407347, 63059024, 62051033, 17016156, 29959549, 89217461, 7300047, 82052050, 8913721, 42307449, 76703609, 26063929, 51507425, 84293052, 84002370, 36845587, 80555751, 1239555, 83948335, 23110625, 92541302, 29188588, 57163802, 48658605, 69786756, 47708850, 37501808, 73109997, 23740167, 18783702, 8791066, 34044787, 52204879, 43376279, 85711894, 7517032, 52060076, 4119747, 18699206, 65074535, 4069912, 72238278, 51466049, 74441448, 60955663, 77284619, 73021291, 72357096, 20140249, 9603598, 36930650, 89699445, 84187166, 67513640, 99515901, 82726008, 22129328, 14349098, 79821897, 59318837, 94076128, 10597197, 99971982, 65851721, 54987042, 669105, 82897371, 56531125, 65017137, 44846932, 61141874, 77300457, 99917599, 31904591, 93057697, 40677414, 20642888, 4095116, 26102057, 14947650, 26493119, 90272749, 3233084, 71965942, 33304202, 76315420, 16445503, 61623915, 73235980, 54199160, 49328608, 78602717, 81805959, 38494874, 1936762, 42947632, 14093520, 64848072, 36189527, 44915235, 51590803, 22997281, 15163258, 17857111, 7919588, 65715134, 15902805, 30787683, 70004753, 83789391, 40027975, 59197747, 19486173, 5482538, 88130087, 64157906, 5640302, 20765474, 569864, 82979980, 43933006, 53666583, 26275734, 98653983, 52293995, 63506281, 79880247, 26507214, 48892201, 15535065, 75986488, 12348118, 44245960, 70367851, 71469330, 63152504, 79136082, 99965001, 47090124, 25636669, 4508700, 5970607, 31126490, 55143724, 81774825, 61859143, 27665211, 86798033, 16684829, 87720882, 50668599, 47298834, 64055960, 78300864, 83083131, 77187825, 87710366, 62428472, 15536795, 89078848, 17385531, 46870723, 30771409, 247198, 45306070, 9886593, 36812683, 74614639, 32274392, 76825057, 8115266, 23432750, 72725103, 70800879, 59371804, 49598724, 29516592, 81855445, 83133790, 49882705, 99658235, 79657802, 62115552, 30694952, 66903004, 95290172, 67084351, 36580610, 22200378, 4978543, 17976208, 6157724, 97011160, 69605283, 20486294, 35456853, 22879907, 62490109, 68110330, 93790285, 12571310, 98648327, 77377183, 52613508, 86301513, 33123618, 77301523, 16097038, 72793444, 85117092, 99729357, 48260151, 55615885, 36505482, 61815223, 7955293, 38365584, 49597667, 41380093, 39847321, 85116755, 9188443, 63967300, 43322743, 83747892, 33237508, 71316369, 18131876, 24915585, 79806380, 65047700, 19272365, 1375023, 77072625, 99333375, 21397057, 54058788, 40152546, 16380211, 30163921, 53888755, 4985896, 44550764, 72358170, 24733232, 10366309, 27411561, 35996293, 13173644, 88698958, 87160386, 10961421, 54762643, 33895336, 4515343, 81274566, 89419466, 63628376, 51472275, 70036438, 81677380, 32699744, 38009615, 64087743, 15064655, 24826575, 30569392, 43045786, 62803858, 73168565, 82886381, 37183543, 23134715, 6111563, 17058722, 55189057, 51149804, 2331773, 98371444, 33249630, 56473732, 68644627, 38022834, 14045383, 40356877, 16567550, 41092102, 54606384, 39986008, 96709982, 1250437, 45995325, 73031054, 6521313, 8696647, 56515456, 93053405, 68041839, 97561745, 98462867, 51715482, 7104732, 8055981, 9623492, 68702632, 82427263, 75052463, 59981773, 26397786, 65454636, 18466635, 69848388, 24591705, 67227442, 53393358, 30395570, 70727211, 79738755, 95395112, 61728685, 62936963, 37739481, 77620120, 34295794, 7685448, 59177718, 112651, 99297164, 49641577, 77898274, 82532312, 50188404, 99021067, 56424103, 70420215, 79322415, 36780454, 31727379, 97641116, 8634541, 80251430, 13231279, 57961282, 88653118, 28796059, 42782652, 8651647, 95010552, 9829782, 6497830, 59614746, 61741594, 18663507, 93562257, 7066775, 77787724, 71333116, 30218878, 45996863, 71657078, 43506672, 39553046, 21001913, 38008118, 40781449, 90654836, 73786944, 62496012, 18806856, 5822202, 64098930 +17016156, 76671482, 85117092, 59177718, 57803235, 92787493, 18001617, 92215320, 28550822, 53842979, 66903004, 73617245, 69786756, 56473732, 14045383, 10264691, 12024238, 74441448, 36780454, 6521313, 36139942, 27185644, 51715482, 28787861, 34493392, 24591705, 92554120, 38365584, 71300104, 99965001, 61859143, 65047700, 60955663, 29819940, 91255408, 2917920, 84349107, 20642888, 33336148, 78218845, 62357986, 36468541, 67084351, 91990218, 30366150, 10358899, 72495719, 16595436, 48360198, 64602895, 5482538, 67031644, 68000591, 68875490, 73168565, 26063929, 37560164, 34295794, 43322743, 7066775, 47090124, 99297164, 16971929, 28851716, 4069912, 67030811, 33201905, 84187166, 14349098, 46870723, 62693428, 16387575, 83368048, 82339363, 90158785, 4806458, 80251430, 98462867, 26998766, 49882705, 53084256, 91141395, 1936762, 14093520, 95010552, 26397786, 44915235, 6157724, 90061527, 59614746, 67227442, 24058273, 38658347, 62490109, 65880522, 3235882, 75407347, 42644903, 61728685, 60581278, 1022677, 37183543, 89804152, 72793444, 17058722, 7955293, 1239555, 92692283, 92541302, 39847321, 63967300, 33249630, 33699435, 36135, 4091162, 37659250, 92033260, 51466049, 31491938, 99524975, 54606384, 77413012, 6986898, 96709982, 52261574, 22129328, 82779622, 6871053, 40152546, 247198, 73031054, 86821229, 90090964, 92353856, 69697787, 36753250, 526217, 43501211, 92530431, 26664538, 61859581, 91240048, 90310261, 16405341, 9058407, 48673079, 27411561, 59371804, 97561745, 88698958, 8099994, 54762643, 3088684, 66667729, 28796059, 3880712, 79922758, 7229550, 62115552, 3183975, 21919959, 49236559, 4199704, 22997281, 68128438, 94539824, 20681921, 73392814, 73222868, 38061439, 89046466, 15064655, 12571310, 19486173, 13348726, 98648327, 66116458, 8129978, 38341669, 62051033, 33123618, 21289531, 37675718, 39171895, 36685023, 8913721, 13468268, 66322959, 874791, 80014588, 62740044, 15535065, 16099750, 96906410, 5073754, 77694700, 12348118, 29029316, 73109997, 23740167, 18131876, 27625735, 4119747, 79806380, 63015256, 16684829, 50188404, 50668599, 47298834, 1375023, 67474219, 73021291, 62496012, 56424103, 77072625, 33565483, 68939068, 45996863, 89078848, 53632373, 47738185, 79821897, 45995325, 94595668, 99971982, 97641116, 30163921, 61897582, 72278539, 44550764, 11365791, 9398733, 89637706, 99125126, 88904910, 32274392, 45075471, 32161669, 9599614, 92803766, 83210802, 23432750, 4787945, 41092172, 4095116, 26102057, 45667668, 45617087, 30139692, 57241521, 47361209, 21533347, 58208470, 3509435, 25842078, 87160386, 84840549, 58749, 34698428, 65271999, 7104732, 2208785, 96420429, 23569917, 30694952, 75229462, 36580610, 13862149, 47893286, 65454636, 15948937, 77272628, 38008118, 98130363, 65338021, 55770687, 53648154, 80246713, 15902805, 12416768, 54263880, 75135448, 24826575, 78589145, 64157906, 61928316, 52613508, 39801351, 55850790, 29932657, 29959549, 26275734, 66271566, 51149804, 54427233, 55615885, 84002370, 62936963, 35192533, 61815223, 23110625, 6793819, 29635537, 85116755, 42073124, 7646095, 70541760, 4099191, 7011964, 17146629, 38022834, 27041967, 91664334, 85711894, 59910624, 49641577, 90654836, 69355476, 91938191, 7687278, 1204161, 56153345, 18806856, 37435892, 83083131, 86242799, 34497327, 79322415, 74452589, 38256849, 86022504, 87710366, 83269727, 62428472, 99333375, 50567636, 37192445, 48774913, 71657078, 54058788, 10597197, 34432810, 168541, 66832478, 62452034, 19457589, 39553046, 99917599, 38645117, 35996293, 94516935, 33797252, 8634541, 94090109, 83133790, 21001913, 76315420, 90457870, 61623915, 75153252, 3487592, 11923835, 93270084, 36312813, 66045587, 42782652, 60430369, 91802888, 81805959, 9860968, 89419466, 63628376, 37280276, 22200378, 51472275, 6497830, 59405277, 15163258, 64098930, 26734892, 6153406, 70596786, 19898053, 81898046, 44844121, 68110330, 86460488, 64087743, 176257, 79738755, 93790285, 88130087, 22108665, 45990383, 78561158, 47887470, 82979980, 86543538, 7300047, 52293995, 79880247, 84293052, 84904436, 30653863, 65081429, 2331773, 72614359, 36845587, 80555751, 19101477, 91957544, 41380093, 51047803, 112651, 12303248, 66885828, 27187213, 44245960, 47708850, 70367851, 6808825, 18783702, 86118021, 50007421, 25636669, 39373729, 34044787, 33237508, 71333116, 7182642, 55143724, 68316156, 7517032, 20122224, 27665211, 18699206, 86798033, 76960303, 99021067, 68204242, 29401781, 40356877, 94711842, 43152977, 20645197, 64055960, 1431742, 66319530, 824872, 59501487, 99226875, 70420215, 11757872, 77187825, 55669657, 37166608, 31727379, 12664567, 50702367, 17386996, 17385531, 17727650, 40534591, 37891451, 2717150, 74743862, 56515456, 65017137, 36812683, 77300457, 95251277, 71083565, 93566986, 44481640, 10366309, 23848565, 5267545, 19376156, 40677414, 76825057, 72725103, 35512853, 22405120, 85242963, 66137019, 45790169, 49598724, 90272749, 19891772, 17539625, 57961282, 75820087, 96852321, 86001008, 19939935, 3233084, 22450468, 99658235, 28928175, 40865610, 63372756, 45237957, 44664587, 68702632, 95581843, 33895336, 96735716, 57248122, 26114953, 51315460, 38494874, 81274566, 60106217, 59981773, 42947632, 42237907, 93515664, 37957788, 4978543, 18466635, 32250045, 51590803, 97011160, 81677380, 89996536, 34236719, 3633375, 21993752, 22879907, 33061250, 30395570, 20867149, 99861373, 40027975, 37224844, 55753905, 76434144, 9575954, 69255765, 30569392, 63059024, 95395112, 20765474, 61263068, 82052050, 30543215, 76703609, 63506281, 46851987, 44847298, 37739481, 30463802, 32590267, 29188588, 98371444, 48658605, 54868730, 92998591, 81172706, 71522968, 2204165, 71469330, 37501808, 79136082, 56461322, 58751351, 89811711, 68644627, 95957797, 43376279, 31126490, 16424199, 41481685, 81774825, 82651278, 52060076, 40781449, 30811010, 49271185, 19272365, 66428795, 44479073, 23194618, 56484900, 14731700, 66663942, 61982238, 57658654, 89699445, 11581181, 26744917, 67513640, 36396314, 84127901, 57240218, 99515901, 82726008, 76330843, 7502255, 32151165, 96318094, 59318837, 97057187, 67793644, 50806615, 53888755, 54987042, 44983451, 4985896, 14220886, 67124282, 94911072, 44846932, 44627776, 74614639, 68824981, 44842615, 45407418, 76540481, 14947650, 97940276, 22435353, 93053405, 26493119, 97281847, 13173644, 69579137, 13231279, 69641513, 26863229, 38510840, 33304202, 72274002, 43357947, 10961421, 6221471, 48395186, 8055981, 9623492, 54199160, 26776922, 30998561, 55602660, 33628349, 75052463, 8651647, 68816413, 70782102, 57393458, 9829782, 67451935, 15075176, 34667286, 14626618, 14723410, 69848388, 1197320, 7919588, 32699744, 53393358, 78884452, 69605283, 99604946, 24619760, 44177011, 70004753, 70727211, 87598888, 76170907, 30090481, 92071990, 86301513, 31161687, 92867155, 82886381, 77301523, 77312810, 93933709, 16097038, 43933006, 60102965, 3233569, 53666583, 58180653, 42199455, 55189057, 40197395, 42307449, 51507425, 36505482, 61741594, 49597667, 77620120, 75986488, 45428665, 9188443, 92604458, 99690194, 70372191, 22766820, 44889423, 93562257, 36808486, 63152504, 6505939, 51464002, 41442762, 22113133, 71316369, 5970607, 52204879, 74357852, 78766358, 88047921, 57359924, 90004325, 92692978, 71920426, 72238278, 9257405, 24953498, 3294781, 72357096, 9603598, 24168411, 45848907, 83155442, 17081350, 99224392, 21397057, 44060493, 30771409, 34946859, 10453030, 16380211, 82897371, 321665, 72358170, 61141874, 29065964, 91281584, 57020481, 43506672, 54517921, 79191827, 59109400, 13470059, 83651211, 51588015, 99549373, 70800879, 75543508, 80316608, 4434662, 44473167, 8373289, 29510992, 44348328, 17068582, 16445503, 78785507, 66250369, 82427263, 78549759, 20568363, 55470718, 14326617, 36933038, 36189527, 9860195, 11161731, 10309525, 54663246, 20836893, 15015906, 53547802, 45794415, 62232211, 22942635, 30891921, 21673260, 40984766, 3773993, 83789391, 59197747, 77377183, 44784505, 43045786, 10649306, 65275241, 75777973, 23134715, 6111563, 48088883, 4204661, 11543098, 34698463, 26507214, 48892201, 9175338, 18411915, 7685448, 67281495, 59329511, 77787724, 16054533, 97379790, 32058615, 72732205, 54232247, 82532312, 33935899, 29994197, 45919976, 16567550, 83302115, 20002147, 6819644, 97783876, 41092102, 17037369, 8128637, 1250437, 4064751, 5832946, 46540998, 94076128, 669105, 22721500, 48893685, 8696647, 18833224, 61960400, 31904591, 93057697, 60176618, 8115266, 83533741, 79942022, 91727510, 2891150, 26139110, 68041839, 29516592, 81855445, 84684495, 71965942, 90013093, 17894977, 93359396, 88251446, 75128745, 66611759, 88653118, 58224549, 73235980, 79657802, 74137926, 9259676, 98943869, 91831487, 85023028, 83150534, 61271144, 88444207, 26392416, 54014062, 64848072, 48675329, 70036438, 92398073, 53802686, 62430984, 32159704, 30764367, 17857111, 61712234, 65715134, 21070110, 20486294, 8807940, 30787683, 61373987, 5640302, 13819358, 62803858, 569864, 89217461, 94360702, 16019925, 4798568, 415901, 72738685, 57163802, 42692881, 51426311, 55960386, 4508700, 29834512, 96726697, 77898274, 24915585, 65074535, 72373496, 73786944, 95726235, 55247455, 98948034, 20140249, 5822202, 36930650, 62762857, 45381876, 15536795, 65851721, 68694897, 5111370, 56531125, 24733232, 82327024, 69136837, 72019362, 68102437, 508198, 4515343, 1788101, 84406788, 17976208, 27325428, 84166196, 82178706, 1569515, 62552524, 7423788, 83948335, 89214616, 32426752, 74724075, 37620363, 83747892, 41245325, 8791066, 29466406, 2607799, 74110882, 14363867, 72777973, 85571389, 39201414, 40781854, 4668450, 77284619, 39986008, 56955985, 26292919, 40686254, 45306070, 15148031, 17764950, 17957593, 35092039, 47792865, 749283, 20885148, 17365188, 89499542, 85695762, 35456853, 38009615, 31733363, 8729146, 33553959, 42967683, 99729357, 78909561, 18663507, 18504197, 87720882, 8733068, 60393039, 78300864, 30218878, 45049308, 65038678, 49328608, 6038457, 78602717, 28734791, 47213183, 98739783, 98653983, 48260151, 73124510, 24314885, 9886593, 33431960, 95290172, 6525948, 81853704, 96193415 +76540481, 92604458, 61859143, 32161669, 37957788, 83948335, 64055960, 17037369, 17385531, 99224392, 62693428, 62357986, 47792865, 16595436, 30787683, 12348118, 44245960, 97783876, 91281584, 33336148, 38510840, 43357947, 54762643, 88653118, 4515343, 75407347, 65275241, 42199455, 52293995, 34698463, 62740044, 92998591, 47708850, 43376279, 1431742, 44060493, 30163921, 44550764, 93566986, 40677414, 90310261, 26493119, 18001617, 97561745, 19891772, 21533347, 93359396, 72019362, 10961421, 55602660, 26114953, 42237907, 9860195, 62232211, 3773993, 89046466, 83789391, 88130087, 13348726, 33553959, 86543538, 40197395, 8913721, 42307449, 36845587, 62936963, 38365584, 9188443, 41442762, 95957797, 52060076, 57359924, 4119747, 92692978, 27665211, 33935899, 34497327, 74452589, 68939068, 21397057, 30771409, 7502255, 34946859, 10453030, 97057187, 54987042, 44983451, 91255408, 9398733, 56515456, 43506672, 9599614, 92803766, 8115266, 70800879, 35996293, 14947650, 91727510, 33797252, 57241521, 86001008, 3233084, 25842078, 72274002, 22450468, 26998766, 28928175, 9623492, 53842979, 96735716, 82427263, 36580610, 64848072, 15075176, 97011160, 81677380, 62430984, 85695762, 38658347, 30395570, 15064655, 12571310, 67031644, 38341669, 92867155, 29932657, 16097038, 4204661, 3233569, 26275734, 72793444, 76671482, 76703609, 26063929, 46851987, 37739481, 61741594, 77620120, 57163802, 16099750, 44889423, 71469330, 41245325, 23740167, 7066775, 51426311, 4508700, 68644627, 36135, 7517032, 24915585, 28851716, 27625735, 82532312, 18699206, 4069912, 50668599, 39201414, 1375023, 37435892, 24953498, 55247455, 78300864, 83083131, 3294781, 20140249, 87710366, 89699445, 15536795, 89078848, 96709982, 79821897, 66832478, 669105, 6521313, 19457589, 36753250, 45049308, 95251277, 71083565, 92530431, 61859581, 5267545, 83210802, 59109400, 83651211, 41092172, 4095116, 26102057, 2891150, 47361209, 90013093, 84840549, 58749, 28796059, 79657802, 508198, 57248122, 9860968, 60106217, 88444207, 749283, 36189527, 20885148, 53802686, 32250045, 27325428, 34493392, 94539824, 54663246, 22942635, 70004753, 19486173, 5640302, 39801351, 23134715, 6111563, 43933006, 7300047, 39171895, 874791, 79880247, 84293052, 73617245, 4798568, 92541302, 29188588, 89214616, 59177718, 32426752, 63967300, 66885828, 5073754, 71522968, 37620363, 63152504, 6505939, 8791066, 89811711, 71316369, 38022834, 71333116, 74357852, 49641577, 24314885, 65047700, 19272365, 87720882, 68204242, 83302115, 94711842, 12024238, 99524975, 40781854, 98948034, 824872, 62496012, 59501487, 66663942, 96193415, 37166608, 33565483, 45848907, 50702367, 1250437, 22129328, 14349098, 17727650, 46540998, 96318094, 16380211, 61897582, 86821229, 8696647, 94911072, 36812683, 57020481, 24733232, 23848565, 19376156, 38645117, 23432750, 51588015, 17764950, 9058407, 97940276, 59371804, 97281847, 69641513, 88698958, 96852321, 17957593, 71965942, 49882705, 63372756, 6221471, 48395186, 58224549, 73235980, 93270084, 3880712, 3183975, 23569917, 20568363, 38494874, 1936762, 59981773, 36468541, 61271144, 84406788, 10358899, 49236559, 51472275, 70036438, 93515664, 38008118, 14723410, 7919588, 15015906, 21993752, 22879907, 38009615, 54263880, 1569515, 31733363, 76434144, 30090481, 62552524, 52613508, 30569392, 66116458, 63059024, 7423788, 43045786, 98739783, 62803858, 75777973, 33123618, 21289531, 48088883, 66271566, 11543098, 13468268, 54427233, 84904436, 75986488, 39847321, 29635537, 33249630, 77694700, 74724075, 81172706, 70367851, 93562257, 79136082, 6808825, 70541760, 25636669, 17146629, 58751351, 39373729, 34044787, 33237508, 5970607, 55143724, 85711894, 20122224, 90654836, 79806380, 49271185, 7687278, 1204161, 86798033, 72777973, 72373496, 29994197, 74441448, 60955663, 67030811, 99226875, 9603598, 24168411, 83269727, 62428472, 84187166, 37192445, 45996863, 6986898, 40686254, 99515901, 82779622, 32151165, 99971982, 65851721, 73031054, 50806615, 53888755, 2717150, 11365791, 68694897, 67124282, 65017137, 44627776, 16387575, 18833224, 74614639, 32274392, 45075471, 79191827, 31904591, 68824981, 15148031, 36139942, 35512853, 99549373, 4806458, 45407418, 92787493, 48673079, 83533741, 27411561, 66137019, 80316608, 45790169, 45667668, 90272749, 44473167, 8634541, 29510992, 69579137, 58208470, 13231279, 44348328, 83133790, 76315420, 16445503, 40865610, 28550822, 75153252, 34698428, 7104732, 66250369, 26776922, 42782652, 91802888, 95290172, 36933038, 26397786, 30366150, 9829782, 6497830, 44915235, 67451935, 59405277, 6157724, 65454636, 90061527, 51590803, 89996536, 59614746, 98130363, 20681921, 65338021, 45794415, 73392814, 69605283, 20486294, 35456853, 64087743, 33061250, 37224844, 5482538, 45990383, 44784505, 78561158, 92554120, 20765474, 77301523, 37675718, 89804152, 17058722, 30543215, 48260151, 16019925, 80014588, 55615885, 84002370, 72614359, 78909561, 7955293, 19101477, 1239555, 91957544, 6793819, 98371444, 71300104, 69786756, 42073124, 112651, 22766820, 73109997, 36808486, 83747892, 55960386, 4099191, 56473732, 7011964, 33699435, 99297164, 27041967, 2607799, 91664334, 78766358, 7182642, 14045383, 68316156, 71920426, 76960303, 14363867, 29401781, 85571389, 40356877, 16567550, 47298834, 20645197, 67474219, 77284619, 62762857, 54606384, 36780454, 8128637, 77413012, 84127901, 57803235, 83155442, 48774913, 5832946, 71657078, 54058788, 37891451, 247198, 34432810, 72278539, 69697787, 72358170, 44846932, 93057697, 10366309, 60176618, 72725103, 4787945, 20642888, 85242963, 79942022, 4434662, 45617087, 33431960, 94090109, 3509435, 21001913, 27185644, 51715482, 68102437, 88251446, 75128745, 3487592, 11923835, 65271999, 45237957, 36312813, 28787861, 33895336, 96420429, 74137926, 78549759, 75052463, 8651647, 98943869, 66903004, 91831487, 68816413, 89419466, 75229462, 70782102, 26392416, 54014062, 37280276, 28734791, 4978543, 11161731, 15948937, 72495719, 17365188, 34236719, 17857111, 20836893, 81853704, 3633375, 32699744, 53547802, 24058273, 6153406, 89499542, 80246713, 15902805, 12416768, 86460488, 44177011, 61373987, 93790285, 75135448, 24826575, 77377183, 9575954, 69255765, 86301513, 95395112, 61728685, 82886381, 29959549, 1022677, 37183543, 42967683, 94360702, 60102965, 55189057, 82052050, 85117092, 99729357, 51507425, 30653863, 26507214, 80555751, 36505482, 30463802, 48892201, 41380093, 72738685, 48658605, 51047803, 54868730, 99690194, 67281495, 99965001, 18783702, 86118021, 50007421, 59329511, 77787724, 88047921, 81774825, 30811010, 54232247, 69355476, 74110882, 66428795, 92033260, 65074535, 44479073, 56153345, 51466049, 18806856, 8733068, 73021291, 61982238, 56424103, 70420215, 77072625, 57658654, 11757872, 77187825, 38256849, 33201905, 99333375, 39986008, 56955985, 57240218, 17386996, 52261574, 76330843, 47738185, 59318837, 22721500, 74743862, 45306070, 62452034, 99125126, 88904910, 77300457, 54517921, 99917599, 82339363, 13470059, 26139110, 75543508, 93053405, 49598724, 8373289, 78218845, 57961282, 98462867, 33304202, 35092039, 90457870, 78785507, 8055981, 66667729, 54199160, 7229550, 30694952, 33628349, 51315460, 42947632, 14093520, 95010552, 1788101, 67084351, 47893286, 4199704, 14626618, 15163258, 32159704, 26734892, 65715134, 70596786, 55770687, 21070110, 73222868, 21673260, 8807940, 40984766, 68110330, 99604946, 24619760, 79738755, 48360198, 65880522, 68000591, 61928316, 3235882, 92071990, 8129978, 10649306, 62051033, 60581278, 77312810, 82979980, 36685023, 63506281, 61815223, 415901, 32590267, 92692283, 15535065, 45428665, 85116755, 18411915, 12303248, 18663507, 51464002, 29466406, 29834512, 41481685, 77898274, 82651278, 63015256, 23194618, 72238278, 45919976, 60393039, 56484900, 14731700, 6819644, 66319530, 5822202, 36930650, 55669657, 41092102, 26744917, 36396314, 82726008, 46870723, 29819940, 45995325, 40152546, 94595668, 67793644, 168541, 4985896, 82897371, 48893685, 2917920, 89637706, 9886593, 61141874, 39553046, 83368048, 30139692, 13173644, 81855445, 92215320, 19939935, 87160386, 99658235, 61623915, 91141395, 44664587, 68702632, 66045587, 2208785, 79922758, 81805959, 85023028, 55470718, 21919959, 91990218, 57393458, 22200378, 77272628, 68128438, 64098930, 69848388, 24591705, 53393358, 19898053, 82178706, 30891921, 38061439, 70727211, 99861373, 40027975, 76170907, 78589145, 98648327, 68875490, 42644903, 73168565, 17016156, 61263068, 569864, 53666583, 98653983, 58180653, 51149804, 66322959, 35192533, 49597667, 73124510, 37560164, 23110625, 34295794, 9175338, 70372191, 43322743, 27187213, 29029316, 7646095, 56461322, 16971929, 18131876, 16054533, 97379790, 16424199, 72732205, 37659250, 16684829, 50188404, 99021067, 73786944, 4668450, 20002147, 72357096, 79322415, 31727379, 67513640, 17081350, 4064751, 56531125, 29065964, 526217, 43501211, 91240048, 76825057, 22405120, 22435353, 68041839, 29516592, 80251430, 17539625, 26863229, 53084256, 30998561, 60430369, 62115552, 9259676, 14326617, 83150534, 63628376, 30764367, 6525948, 20867149, 59197747, 22108665, 47213183, 13819358, 31161687, 47887470, 89217461, 93933709, 2331773, 2204165, 37501808, 18504197, 47090124, 96726697, 59910624, 32058615, 90004325, 40781449, 9257405, 95726235, 31491938, 43152977, 30218878, 50567636, 12664567, 26292919, 53632373, 40534591, 94076128, 97641116, 90090964, 321665, 65038678, 44842615, 84349107, 90158785, 16405341, 84684495, 17068582, 8099994, 66611759, 6038457, 78602717, 13862149, 48675329, 17976208, 92398073, 10309525, 84166196, 61712234, 67227442, 78884452, 53648154, 81898046, 44844121, 62490109, 176257, 64602895, 55850790, 44847298, 7685448, 42692881, 52204879, 91938191, 86242799, 45381876, 6871053, 10597197, 92353856, 5111370, 26664538, 82327024, 69136837, 94516935, 49328608, 18466635, 22997281, 1197320, 87598888, 55753905, 64157906, 65081429, 96906410, 22113133, 31126490, 10264691, 11581181, 14220886, 44481640, 75820087, 17894977, 95581843, 81274566, 8729146, 4091162, 86022504, 61960400, 3088684, 34667286 +30998561, 27665211, 49271185, 2717150, 78785507, 84166196, 99971982, 82339363, 10309525, 62552524, 20122224, 73786944, 22721500, 68694897, 49598724, 75820087, 66250369, 54199160, 26392416, 30764367, 54663246, 81898046, 77377183, 66116458, 17016156, 89804152, 84002370, 1239555, 49597667, 18504197, 52204879, 97379790, 99125126, 65038678, 36139942, 76825057, 4787945, 33431960, 62357986, 66045587, 13862149, 17976208, 9860195, 77272628, 64098930, 6525948, 64602895, 5482538, 78589145, 20765474, 75777973, 37183543, 16097038, 94360702, 4204661, 55615885, 39847321, 43322743, 2204165, 56461322, 22113133, 91664334, 16054533, 30811010, 72732205, 18699206, 77284619, 5822202, 62762857, 33565483, 84187166, 56955985, 12664567, 30771409, 96318094, 16380211, 94595668, 2917920, 45306070, 29065964, 59109400, 83651211, 4434662, 13173644, 16445503, 88251446, 58224549, 45237957, 66667729, 36312813, 79657802, 62115552, 66903004, 68816413, 55470718, 14326617, 84406788, 27325428, 22997281, 14626618, 89996536, 69848388, 7919588, 65338021, 45794415, 20486294, 1569515, 55753905, 19486173, 68000591, 52613508, 47213183, 62051033, 61728685, 47887470, 61263068, 43933006, 7300047, 26275734, 42199455, 66271566, 11543098, 80014588, 45428665, 9175338, 29029316, 18783702, 71333116, 36135, 24915585, 40781449, 24314885, 92033260, 76960303, 14363867, 8733068, 83083131, 86242799, 50702367, 17081350, 82726008, 5832946, 54058788, 32151165, 66832478, 72278539, 74743862, 69697787, 60176618, 99549373, 70800879, 94516935, 45617087, 19891772, 47361209, 81855445, 35092039, 43357947, 84840549, 28928175, 66611759, 49328608, 4515343, 60430369, 74137926, 23569917, 75052463, 85023028, 60106217, 47792865, 75229462, 26397786, 42237907, 28734791, 90061527, 61712234, 24058273, 55770687, 44844121, 86460488, 22108665, 45990383, 9575954, 63059024, 68875490, 42644903, 31161687, 569864, 48088883, 58180653, 85117092, 99729357, 48260151, 30653863, 62740044, 7685448, 89214616, 92604458, 69786756, 42073124, 12348118, 71522968, 37501808, 47090124, 71316369, 59329511, 16971929, 27041967, 18131876, 78766358, 55143724, 49641577, 77898274, 33935899, 50668599, 72238278, 9257405, 10264691, 16567550, 1431742, 67474219, 20140249, 70420215, 38256849, 24168411, 50567636, 89078848, 99515901, 40152546, 97641116, 30163921, 67124282, 88904910, 45049308, 18833224, 43501211, 54517921, 31904591, 93566986, 93057697, 40677414, 51588015, 4806458, 92787493, 27411561, 29516592, 3509435, 26863229, 90013093, 10961421, 48395186, 44664587, 3088684, 55602660, 83150534, 36580610, 22200378, 36189527, 67451935, 38008118, 1197320, 53393358, 8807940, 40984766, 20867149, 87598888, 31733363, 79738755, 93790285, 76170907, 12571310, 75135448, 88130087, 76434144, 67031644, 98648327, 92071990, 69255765, 5640302, 39801351, 33553959, 60102965, 53666583, 34698463, 63506281, 79880247, 26507214, 37739481, 35192533, 32590267, 37560164, 15535065, 75986488, 85116755, 96906410, 70372191, 59177718, 70367851, 18663507, 7646095, 79136082, 25636669, 41442762, 43376279, 14045383, 16424199, 32058615, 27625735, 91938191, 99021067, 40356877, 18806856, 47298834, 31491938, 60955663, 40781854, 67030811, 73021291, 72357096, 824872, 34497327, 77187825, 41092102, 86022504, 36780454, 33201905, 89699445, 45381876, 15536795, 6986898, 17386996, 46870723, 4064751, 47738185, 10453030, 247198, 67793644, 168541, 669105, 44983451, 4985896, 56515456, 5111370, 56531125, 89637706, 94911072, 526217, 74614639, 39553046, 99917599, 9599614, 10366309, 5267545, 19376156, 69136837, 17764950, 22405120, 16405341, 4095116, 91727510, 22435353, 8373289, 44348328, 84684495, 96852321, 72274002, 76315420, 51715482, 49882705, 61623915, 75153252, 73235980, 96735716, 81805959, 9259676, 20568363, 91831487, 89419466, 95290172, 30366150, 63628376, 37280276, 6157724, 20885148, 53802686, 15948937, 20836893, 65715134, 53547802, 62490109, 61373987, 70727211, 13348726, 30090481, 8129978, 7423788, 65275241, 73168565, 82886381, 33123618, 89217461, 93933709, 3233569, 17058722, 30543215, 51149804, 8913721, 51507425, 84904436, 2331773, 44847298, 36845587, 61815223, 61741594, 16099750, 51047803, 54868730, 12303248, 42692881, 77694700, 37620363, 93562257, 73109997, 83747892, 67281495, 99965001, 86118021, 50007421, 56473732, 58751351, 39373729, 38022834, 29466406, 2607799, 7182642, 59910624, 81774825, 7517032, 69355476, 74110882, 82532312, 79806380, 65074535, 63015256, 68204242, 95726235, 83302115, 60393039, 43152977, 14731700, 20002147, 99226875, 66663942, 61982238, 54606384, 37166608, 99333375, 17037369, 26744917, 67513640, 37192445, 83155442, 17385531, 52261574, 48774913, 21397057, 71657078, 10597197, 86821229, 6521313, 90090964, 9886593, 16387575, 61960400, 44481640, 15148031, 82327024, 83210802, 38645117, 8115266, 35512853, 9058407, 26102057, 97940276, 80316608, 33336148, 80251430, 17539625, 21533347, 13231279, 17068582, 8099994, 22450468, 87160386, 99658235, 75128745, 3487592, 28787861, 42782652, 79922758, 6038457, 59981773, 21919959, 88444207, 54014062, 47893286, 48675329, 59614746, 34236719, 98130363, 26734892, 67227442, 20681921, 16595436, 3633375, 6153406, 78884452, 85695762, 22879907, 62232211, 30891921, 15902805, 68110330, 12416768, 64087743, 30395570, 54263880, 70004753, 176257, 99861373, 59197747, 64157906, 78561158, 43045786, 10649306, 92867155, 55850790, 82979980, 23134715, 86543538, 6111563, 39171895, 72793444, 98653983, 82052050, 40197395, 36685023, 54427233, 65081429, 46851987, 80555751, 78909561, 19101477, 415901, 91957544, 92692283, 57163802, 18411915, 63967300, 22766820, 5073754, 81172706, 51464002, 41245325, 23740167, 7066775, 55960386, 4099191, 4508700, 29834512, 96726697, 31126490, 88047921, 68316156, 4091162, 90654836, 7687278, 1204161, 19272365, 66428795, 23194618, 29401781, 72777973, 51466049, 56484900, 37435892, 55247455, 77072625, 57658654, 9603598, 11757872, 36930650, 79322415, 87710366, 68939068, 36396314, 53632373, 57240218, 57803235, 1250437, 76330843, 14349098, 34946859, 59318837, 6871053, 94076128, 97057187, 65851721, 44627776, 36753250, 77300457, 95251277, 71083565, 45075471, 79191827, 61859581, 23848565, 84349107, 94090109, 78218845, 29510992, 69641513, 88698958, 3233084, 27185644, 17894977, 90457870, 40865610, 54762643, 88653118, 3880712, 26776922, 508198, 57248122, 7229550, 78602717, 78549759, 30694952, 8651647, 1936762, 81274566, 61271144, 36933038, 91990218, 57393458, 44915235, 59405277, 92398073, 32250045, 51590803, 72495719, 15163258, 32159704, 17857111, 15015906, 19898053, 73392814, 38658347, 82178706, 21673260, 38009615, 38061439, 33061250, 37224844, 8729146, 24826575, 61928316, 44784505, 86301513, 13819358, 29932657, 66322959, 73617245, 83948335, 73124510, 23110625, 92541302, 34295794, 29635537, 48658605, 71300104, 92998591, 33249630, 66885828, 47708850, 6505939, 70541760, 33699435, 17146629, 99297164, 34044787, 89811711, 68644627, 85711894, 82651278, 61859143, 90004325, 28851716, 54232247, 71920426, 86798033, 44479073, 16684829, 72373496, 85571389, 29994197, 12024238, 4668450, 97783876, 74452589, 55669657, 11581181, 39986008, 45996863, 26292919, 77413012, 40686254, 44060493, 46540998, 79821897, 73031054, 50806615, 14220886, 9398733, 62693428, 72358170, 61141874, 36812683, 43506672, 32274392, 24733232, 26664538, 92803766, 90158785, 23432750, 85242963, 35996293, 14947650, 2891150, 75543508, 33797252, 26493119, 90272749, 30139692, 44473167, 57241521, 69579137, 58208470, 92215320, 86001008, 19939935, 25842078, 93359396, 34698428, 6221471, 68702632, 33895336, 2208785, 3183975, 36468541, 67084351, 10358899, 49236559, 64848072, 749283, 37957788, 4978543, 65454636, 97011160, 68128438, 14723410, 81853704, 32699744, 89499542, 53648154, 99604946, 3773993, 30787683, 40027975, 15064655, 65880522, 92554120, 62803858, 60581278, 21289531, 55189057, 52293995, 42307449, 16019925, 13468268, 72614359, 62936963, 30463802, 48892201, 29188588, 6793819, 74724075, 71469330, 36808486, 51426311, 7011964, 33237508, 5970607, 77787724, 74357852, 41481685, 37659250, 65047700, 4069912, 50188404, 1375023, 99524975, 74441448, 78300864, 66319530, 62496012, 8128637, 84127901, 99224392, 45995325, 37891451, 34432810, 61897582, 54987042, 44550764, 92353856, 48893685, 44846932, 19457589, 57020481, 83368048, 92530431, 32161669, 91240048, 45407418, 20642888, 48673079, 76540481, 83533741, 26139110, 68041839, 45667668, 18001617, 71965942, 33304202, 11923835, 91141395, 7104732, 93270084, 95581843, 82427263, 33628349, 98943869, 42947632, 95010552, 1788101, 4199704, 6497830, 93515664, 34667286, 24591705, 70596786, 35456853, 22942635, 89046466, 83789391, 95395112, 38341669, 77301523, 77312810, 42967683, 76671482, 76703609, 874791, 36505482, 7955293, 38365584, 41380093, 77620120, 9188443, 98371444, 32426752, 44889423, 63152504, 8791066, 57359924, 4119747, 92692978, 56153345, 87720882, 45919976, 24953498, 20645197, 3294781, 59501487, 56424103, 83269727, 31727379, 45848907, 40534591, 53888755, 11365791, 82897371, 321665, 65017137, 91281584, 44842615, 90310261, 72725103, 41092172, 79942022, 66137019, 59371804, 97281847, 38510840, 72019362, 26998766, 68102437, 28550822, 53084256, 53842979, 96420429, 91802888, 70782102, 15075176, 18466635, 17365188, 81677380, 34493392, 62430984, 21070110, 24619760, 98739783, 4798568, 112651, 27187213, 44245960, 52060076, 64055960, 6819644, 98948034, 30218878, 17727650, 82779622, 29819940, 91255408, 8696647, 62452034, 13470059, 97561745, 98462867, 17957593, 21001913, 63372756, 65271999, 8055981, 28796059, 26114953, 51315460, 38494874, 9860968, 9829782, 94539824, 69605283, 80246713, 44177011, 48360198, 75407347, 30569392, 37675718, 26063929, 84293052, 99690194, 6808825, 95957797, 39201414, 62428472, 22129328, 68824981, 93053405, 83133790, 9623492, 70036438, 11161731, 29959549, 72738685, 96193415, 96709982, 8634541, 57961282, 58749, 3235882, 1022677, 94711842, 7502255, 45790169, 14093520, 21993752, 73222868, 51472275 +168541, 83789391, 86242799, 1197320, 24058273, 55189057, 29466406, 66319530, 92353856, 43501211, 90310261, 19891772, 88444207, 84166196, 55770687, 48360198, 38341669, 60581278, 47887470, 84293052, 29635537, 91938191, 6819644, 96709982, 36753250, 84349107, 48673079, 22435353, 71965942, 90457870, 7229550, 81805959, 14093520, 84406788, 20486294, 62232211, 73168565, 60102965, 89804152, 76703609, 1239555, 91957544, 51047803, 29029316, 52204879, 43376279, 52060076, 4119747, 69355476, 63015256, 33201905, 17037369, 67793644, 30163921, 669105, 99125126, 32274392, 19376156, 4806458, 92787493, 79942022, 49598724, 30139692, 78218845, 93359396, 91802888, 30694952, 98943869, 75229462, 57393458, 9829782, 22942635, 12416768, 38061439, 54263880, 44177011, 93790285, 8729146, 24826575, 6111563, 13468268, 44847298, 62936963, 61741594, 48892201, 18411915, 71300104, 34044787, 4091162, 72732205, 7687278, 50188404, 1375023, 99524975, 40781854, 73021291, 37192445, 8128637, 46870723, 97057187, 45049308, 18833224, 79191827, 8115266, 72725103, 16405341, 94090109, 75820087, 26863229, 83133790, 76315420, 8099994, 66611759, 58224549, 66667729, 30998561, 96735716, 20568363, 66903004, 95010552, 67451935, 65454636, 11161731, 15948937, 51590803, 20681921, 176257, 40027975, 64157906, 52613508, 98739783, 62051033, 75777973, 23134715, 4204661, 53666583, 17058722, 66271566, 55615885, 2331773, 72614359, 38365584, 70372191, 47090124, 71316369, 68644627, 31126490, 61859143, 37659250, 4069912, 39201414, 94711842, 66663942, 79322415, 50567636, 45381876, 26292919, 53632373, 17386996, 76330843, 17727650, 48774913, 5832946, 40534591, 6871053, 73031054, 72278539, 2717150, 56531125, 321665, 94911072, 61960400, 91240048, 90158785, 35512853, 99549373, 9058407, 29516592, 57241521, 92215320, 33304202, 17068582, 7104732, 36312813, 3880712, 66045587, 2208785, 4515343, 38494874, 1936762, 91831487, 81274566, 68816413, 26397786, 4199704, 28734791, 10309525, 18466635, 72495719, 81677380, 59614746, 61712234, 78884452, 30891921, 99604946, 89046466, 19486173, 64602895, 5482538, 76434144, 13348726, 22108665, 47213183, 30569392, 13819358, 92554120, 92867155, 55850790, 82886381, 77301523, 82052050, 48260151, 51507425, 80014588, 80555751, 35192533, 30463802, 37560164, 34295794, 29188588, 96906410, 22766820, 5073754, 74724075, 44245960, 2204165, 23740167, 50007421, 56473732, 59329511, 38022834, 16971929, 27041967, 91664334, 74357852, 78766358, 85711894, 41481685, 68316156, 30811010, 27625735, 33935899, 19272365, 65074535, 50668599, 60393039, 64055960, 4668450, 20002147, 30218878, 56424103, 11581181, 67513640, 57803235, 17081350, 29819940, 79821897, 34432810, 66832478, 74743862, 11365791, 69697787, 65017137, 44846932, 44627776, 36812683, 526217, 43506672, 39553046, 92530431, 31904591, 36139942, 59109400, 60176618, 22405120, 26102057, 85242963, 80316608, 80251430, 17539625, 21533347, 34698428, 66250369, 508198, 6038457, 23569917, 60106217, 59981773, 70782102, 37280276, 44915235, 17976208, 92398073, 17365188, 62430984, 32159704, 89996536, 7919588, 24591705, 67227442, 3633375, 70596786, 53648154, 81898046, 62490109, 40984766, 38009615, 33061250, 70727211, 65880522, 67031644, 98648327, 68000591, 77377183, 61928316, 78561158, 92071990, 39801351, 20765474, 29959549, 94360702, 48088883, 72793444, 98653983, 85117092, 66322959, 26507214, 36505482, 73124510, 92541302, 15535065, 98371444, 48658605, 54868730, 43322743, 42692881, 77694700, 12348118, 51464002, 83747892, 6808825, 55960386, 99965001, 7011964, 33699435, 29834512, 18131876, 90004325, 90654836, 82532312, 76960303, 16684829, 68204242, 45919976, 51466049, 83302115, 12024238, 31491938, 24953498, 20645197, 67474219, 3294781, 72357096, 824872, 41092102, 45848907, 36396314, 57240218, 83155442, 14349098, 96318094, 59318837, 247198, 97641116, 14220886, 90090964, 82897371, 68694897, 9886593, 82339363, 44481640, 32161669, 82327024, 44842615, 23848565, 5267545, 83210802, 45407418, 94516935, 93053405, 68041839, 18001617, 58208470, 57961282, 84684495, 25842078, 27185644, 72274002, 28550822, 53084256, 3487592, 11923835, 6221471, 73235980, 9623492, 28796059, 54199160, 49328608, 96420429, 60430369, 62115552, 89419466, 47792865, 36933038, 37957788, 34667286, 14626618, 15163258, 14723410, 98130363, 65715134, 32699744, 65338021, 53393358, 89499542, 85695762, 38658347, 44844121, 80246713, 3773993, 30395570, 24619760, 30787683, 37224844, 55753905, 88130087, 78589145, 9575954, 3235882, 86301513, 63059024, 7423788, 43045786, 68875490, 31161687, 1022677, 569864, 33553959, 16097038, 39171895, 76671482, 51149804, 52293995, 874791, 84002370, 61815223, 23110625, 75986488, 39847321, 6793819, 9175338, 89214616, 99690194, 42073124, 63967300, 66885828, 27187213, 81172706, 7646095, 73109997, 63152504, 79136082, 67281495, 18783702, 41442762, 58751351, 22113133, 5970607, 59910624, 49641577, 7517032, 77898274, 57359924, 92692978, 56153345, 87720882, 29401781, 72777973, 8733068, 47298834, 43152977, 83083131, 14731700, 67030811, 59501487, 5822202, 61982238, 57658654, 9603598, 83269727, 31727379, 56955985, 1250437, 82726008, 47738185, 44060493, 7502255, 46540998, 32151165, 94595668, 65851721, 50806615, 44983451, 2917920, 72358170, 19457589, 71083565, 15148031, 9599614, 10366309, 38645117, 76825057, 13470059, 23432750, 83651211, 35996293, 14947650, 91727510, 75543508, 4434662, 33797252, 97281847, 47361209, 96852321, 19939935, 21001913, 90013093, 17894977, 35092039, 43357947, 87160386, 72019362, 99658235, 75153252, 91141395, 63372756, 65271999, 8055981, 93270084, 95581843, 53842979, 57248122, 26114953, 33628349, 51315460, 9860968, 42947632, 21919959, 95290172, 1788101, 67084351, 51472275, 59405277, 20885148, 22997281, 94539824, 6525948, 69848388, 17857111, 16595436, 45794415, 73392814, 21993752, 21070110, 70004753, 87598888, 31733363, 8129978, 10649306, 95395112, 42644903, 61728685, 21289531, 17016156, 61263068, 82979980, 37183543, 93933709, 43933006, 58180653, 16019925, 73617245, 84904436, 46851987, 78909561, 37739481, 32426752, 92998591, 33249630, 70367851, 93562257, 36808486, 70541760, 7066775, 25636669, 17146629, 71333116, 2607799, 24915585, 74110882, 79806380, 49271185, 65047700, 86798033, 92033260, 29994197, 18806856, 37435892, 20140249, 70420215, 36930650, 74452589, 77187825, 87710366, 99333375, 68939068, 39986008, 89078848, 84127901, 6986898, 17385531, 22129328, 21397057, 37891451, 94076128, 10597197, 91255408, 6521313, 8696647, 62693428, 88904910, 16387575, 77300457, 45075471, 93566986, 24733232, 26664538, 69136837, 92803766, 4787945, 51588015, 26139110, 59371804, 33336148, 45617087, 44473167, 33431960, 69579137, 81855445, 16445503, 84840549, 26998766, 78785507, 68102437, 75128745, 48395186, 62357986, 45237957, 44664587, 68702632, 28787861, 79657802, 42782652, 82427263, 9259676, 75052463, 85023028, 83150534, 36468541, 26392416, 42237907, 91990218, 63628376, 6497830, 15075176, 9860195, 32250045, 27325428, 90061527, 97011160, 34493392, 34236719, 38008118, 64098930, 20836893, 15015906, 69605283, 82178706, 21673260, 68110330, 61373987, 20867149, 79738755, 12571310, 62552524, 45990383, 44784505, 75407347, 62803858, 42967683, 86543538, 7300047, 3233569, 26275734, 54427233, 79880247, 65081429, 19101477, 83948335, 92692283, 72738685, 9188443, 16099750, 7685448, 59177718, 112651, 44889423, 18504197, 51426311, 33237508, 88047921, 16424199, 20122224, 40781449, 71920426, 66428795, 23194618, 85571389, 73786944, 40356877, 10264691, 95726235, 74441448, 77284619, 98948034, 62496012, 99226875, 34497327, 77072625, 11757872, 55669657, 86022504, 33565483, 45996863, 12664567, 40686254, 99515901, 52261574, 82779622, 10453030, 40152546, 61897582, 54987042, 86821229, 4985896, 44550764, 45306070, 67124282, 56515456, 89637706, 61141874, 29065964, 91281584, 57020481, 65038678, 54517921, 99917599, 68824981, 61859581, 40677414, 20642888, 4095116, 70800879, 83533741, 97940276, 26493119, 90272749, 8373289, 8634541, 86001008, 3233084, 38510840, 58749, 10961421, 3088684, 26776922, 79922758, 55602660, 78602717, 55470718, 61271144, 13862149, 10358899, 47893286, 749283, 36189527, 4978543, 77272628, 54663246, 26734892, 81853704, 53547802, 6153406, 19898053, 22879907, 15902805, 86460488, 15064655, 76170907, 75135448, 65275241, 29932657, 77312810, 89217461, 42199455, 11543098, 8913721, 34698463, 26063929, 63506281, 30653863, 62740044, 4798568, 49597667, 92604458, 12303248, 71522968, 71469330, 37620363, 41245325, 8791066, 89811711, 77787724, 96726697, 14045383, 97379790, 32058615, 81774825, 82651278, 54232247, 27665211, 24314885, 18699206, 14363867, 99021067, 72238278, 55247455, 1431742, 96193415, 54606384, 24168411, 62428472, 84187166, 99224392, 4064751, 71657078, 34946859, 54058788, 45995325, 16380211, 22721500, 48893685, 9398733, 5111370, 83368048, 93057697, 41092172, 2891150, 45790169, 45667668, 3509435, 44348328, 98462867, 17957593, 22450468, 88251446, 33895336, 74137926, 14326617, 30366150, 64848072, 53802686, 68128438, 30764367, 73222868, 64087743, 30090481, 69255765, 66116458, 5640302, 33123618, 37675718, 36685023, 42307449, 36845587, 7955293, 41380093, 77620120, 85116755, 47708850, 18663507, 6505939, 99297164, 39373729, 55143724, 1204161, 9257405, 16567550, 78300864, 89699445, 15536795, 50702367, 95251277, 17764950, 27411561, 66137019, 69641513, 51715482, 28928175, 40865610, 3183975, 8651647, 93515664, 6157724, 8807940, 99861373, 59197747, 40197395, 99729357, 415901, 32590267, 57163802, 37501808, 86118021, 4099191, 95957797, 16054533, 28851716, 44479073, 72373496, 56484900, 60955663, 62762857, 97783876, 38256849, 36780454, 77413012, 53888755, 74614639, 97561745, 13173644, 29510992, 49882705, 61623915, 54762643, 78549759, 36580610, 54014062, 22200378, 48675329, 70036438, 35456853, 1569515, 30543215, 69786756, 56461322, 36135, 30771409, 76540481, 88698958, 49236559, 45428665, 4508700, 26744917, 99971982, 13231279, 88653118, 37166608, 7182642, 62452034 +17764950, 4515343, 11923835, 77787724, 4064751, 75543508, 76315420, 96420429, 24058273, 61373987, 37224844, 65275241, 75777973, 60581278, 75986488, 27187213, 20140249, 321665, 69641513, 43357947, 93270084, 47792865, 6497830, 20885148, 53547802, 76434144, 47213183, 98739783, 77312810, 4204661, 76703609, 32590267, 72738685, 4099191, 25636669, 8791066, 38022834, 74357852, 96726697, 14363867, 20002147, 57658654, 17727650, 168541, 67124282, 29065964, 18833224, 61960400, 39553046, 79191827, 10366309, 92803766, 72725103, 80316608, 71965942, 16445503, 84840549, 99658235, 3088684, 20568363, 8651647, 1788101, 67084351, 44915235, 67451935, 59405277, 10309525, 81853704, 65715134, 89499542, 53648154, 68110330, 89046466, 78561158, 43045786, 20765474, 21289531, 6111563, 40197395, 34698463, 874791, 84904436, 19101477, 96906410, 99690194, 70372191, 42073124, 66885828, 77898274, 74110882, 99021067, 39201414, 24953498, 74441448, 4668450, 26744917, 45996863, 8128637, 34946859, 79821897, 97057187, 82897371, 92353856, 99917599, 15148031, 26664538, 93057697, 90158785, 51588015, 70800879, 4434662, 29516592, 78218845, 58208470, 84684495, 38510840, 58224549, 9623492, 66667729, 28796059, 85023028, 89419466, 14093520, 75229462, 36468541, 30366150, 84406788, 37280276, 27325428, 14626618, 15163258, 34236719, 54663246, 1197320, 15015906, 16595436, 70596786, 44844121, 1569515, 99861373, 8729146, 44784505, 37675718, 58180653, 82052050, 30543215, 36685023, 35192533, 29635537, 57163802, 98371444, 92604458, 18663507, 7646095, 67281495, 58751351, 34044787, 22113133, 95957797, 2607799, 31126490, 14045383, 7517032, 49271185, 1204161, 65047700, 66428795, 65074535, 16684829, 72777973, 40356877, 45919976, 1375023, 40781854, 66319530, 67474219, 30218878, 824872, 61982238, 96193415, 62762857, 86022504, 11581181, 15536795, 46870723, 47738185, 7502255, 40534591, 96318094, 50806615, 30163921, 53888755, 54987042, 44983451, 22721500, 48893685, 56531125, 9886593, 45049308, 83368048, 31904591, 82327024, 19376156, 90310261, 59109400, 4787945, 45407418, 41092172, 83533741, 35996293, 59371804, 30139692, 8634541, 33431960, 3509435, 26863229, 8099994, 49882705, 40865610, 91141395, 34698428, 6221471, 88653118, 8055981, 68702632, 91802888, 23569917, 9829782, 4199704, 4978543, 15075176, 92398073, 62430984, 14723410, 55770687, 21070110, 30891921, 80246713, 33061250, 70004753, 30090481, 92071990, 82886381, 42967683, 98653983, 78909561, 30463802, 61741594, 415901, 77620120, 85116755, 9188443, 89214616, 54868730, 32426752, 71522968, 2204165, 93562257, 18504197, 5970607, 29466406, 18131876, 78766358, 16424199, 82651278, 29994197, 83302115, 77284619, 3294781, 59501487, 34497327, 9603598, 11757872, 36930650, 79322415, 24168411, 12664567, 26292919, 36396314, 84127901, 57240218, 40686254, 17081350, 17385531, 76330843, 21397057, 5832946, 30771409, 16380211, 10597197, 34432810, 97641116, 66832478, 91255408, 11365791, 44846932, 36812683, 19457589, 65038678, 32274392, 44481640, 24733232, 38645117, 26493119, 97281847, 44473167, 29510992, 69579137, 44348328, 72274002, 17068582, 90457870, 26998766, 68102437, 28550822, 75128745, 65271999, 44664587, 26776922, 82427263, 2208785, 79922758, 3183975, 74137926, 26114953, 1936762, 91831487, 59981773, 95010552, 42237907, 91990218, 57393458, 70036438, 94539824, 45794415, 69605283, 62490109, 8807940, 12416768, 176257, 20867149, 70727211, 31733363, 76170907, 19486173, 24826575, 65880522, 88130087, 98648327, 68000591, 3235882, 52613508, 7423788, 31161687, 73168565, 569864, 33553959, 93933709, 86543538, 60102965, 53666583, 66271566, 26063929, 2331773, 84002370, 4798568, 72614359, 62936963, 48892201, 1239555, 91957544, 73124510, 45428665, 18411915, 71300104, 69786756, 33249630, 29029316, 63152504, 70541760, 86118021, 33699435, 41442762, 68644627, 29834512, 81774825, 52060076, 24915585, 30811010, 27625735, 91938191, 76960303, 56153345, 87720882, 68204242, 8733068, 94711842, 56484900, 55247455, 78300864, 83083131, 66663942, 56424103, 83269727, 33565483, 50567636, 39986008, 50702367, 96709982, 52261574, 82726008, 46540998, 37891451, 67793644, 14220886, 2717150, 2917920, 89637706, 61141874, 526217, 43506672, 71083565, 68824981, 84349107, 83210802, 60176618, 99549373, 22405120, 4095116, 27411561, 94516935, 2891150, 90272749, 8373289, 80251430, 13173644, 47361209, 92215320, 13231279, 17957593, 83133790, 88251446, 61623915, 66611759, 36312813, 95581843, 54199160, 96735716, 60430369, 6038457, 78602717, 30694952, 51315460, 83150534, 63628376, 22200378, 749283, 93515664, 28734791, 6157724, 18466635, 34667286, 32250045, 51590803, 17365188, 34493392, 84166196, 6525948, 26734892, 61712234, 20681921, 19898053, 21993752, 21673260, 15902805, 93790285, 55753905, 9575954, 8129978, 63059024, 10649306, 42644903, 38341669, 55850790, 33123618, 82979980, 94360702, 11543098, 85117092, 66322959, 80014588, 84293052, 55615885, 41380093, 92541302, 29188588, 6793819, 48658605, 22766820, 74724075, 44889423, 37620363, 73109997, 18783702, 56461322, 39373729, 52204879, 97379790, 49641577, 41481685, 57359924, 90654836, 69355476, 27665211, 79806380, 7687278, 19272365, 50668599, 72238278, 72373496, 85571389, 47298834, 99524975, 37435892, 64055960, 14731700, 77187825, 38256849, 54606384, 99333375, 17037369, 56955985, 37192445, 44060493, 71657078, 54058788, 59318837, 65851721, 669105, 6521313, 90090964, 74743862, 69697787, 62693428, 94911072, 62452034, 99125126, 44627776, 57020481, 16405341, 79942022, 66137019, 91727510, 93053405, 68041839, 45667668, 97561745, 94090109, 17539625, 98462867, 96852321, 90013093, 17894977, 78785507, 58749, 63372756, 54762643, 62357986, 66250369, 33895336, 508198, 75052463, 38494874, 98943869, 66903004, 68816413, 55470718, 21919959, 95290172, 10358899, 51472275, 90061527, 77272628, 89996536, 38008118, 69848388, 98130363, 32699744, 6153406, 73392814, 73222868, 62232211, 22942635, 40984766, 86460488, 99604946, 24619760, 83789391, 79738755, 40027975, 12571310, 48360198, 64157906, 13348726, 62552524, 75407347, 86301513, 13819358, 92554120, 61263068, 39171895, 3233569, 26275734, 42199455, 55189057, 76671482, 52293995, 8913721, 48260151, 13468268, 54427233, 79880247, 30653863, 65081429, 26507214, 36505482, 37739481, 7955293, 83948335, 92692283, 92998591, 77694700, 44245960, 6505939, 6808825, 41245325, 23740167, 99965001, 99297164, 33237508, 89811711, 59329511, 43376279, 16054533, 68316156, 61859143, 20122224, 71920426, 24314885, 18699206, 33935899, 73786944, 10264691, 16567550, 18806856, 97783876, 74452589, 41092102, 37166608, 33201905, 89699445, 31727379, 89078848, 99515901, 22129328, 99224392, 82779622, 29819940, 32151165, 99971982, 73031054, 61897582, 86821229, 5111370, 65017137, 16387575, 45075471, 9599614, 23848565, 5267545, 91240048, 23432750, 9058407, 85242963, 26139110, 45790169, 33797252, 45617087, 57241521, 57961282, 75820087, 86001008, 3233084, 27185644, 93359396, 72019362, 10961421, 3880712, 49328608, 42782652, 7229550, 81805959, 78549759, 33628349, 60106217, 61271144, 36933038, 88444207, 26397786, 49236559, 36189527, 37957788, 53802686, 97011160, 81677380, 68128438, 30764367, 59614746, 65338021, 82178706, 38009615, 64087743, 38061439, 3773993, 44177011, 87598888, 59197747, 5482538, 78589145, 39801351, 62803858, 29932657, 17016156, 77301523, 89217461, 43933006, 48088883, 49597667, 37560164, 15535065, 51047803, 59177718, 112651, 43322743, 5073754, 71469330, 36808486, 51464002, 55960386, 50007421, 56473732, 7011964, 4508700, 71333116, 16971929, 27041967, 91664334, 7182642, 59910624, 4091162, 72732205, 37659250, 63015256, 44479073, 50188404, 29401781, 9257405, 43152977, 20645197, 1431742, 67030811, 98948034, 73021291, 72357096, 68939068, 67513640, 45848907, 1250437, 10453030, 6871053, 40152546, 44550764, 8696647, 9398733, 72358170, 91281584, 36753250, 77300457, 92530431, 82339363, 44842615, 36139942, 69136837, 13470059, 4806458, 92787493, 48673079, 76540481, 14947650, 81855445, 88698958, 25842078, 35092039, 22450468, 48395186, 30998561, 42947632, 14326617, 70782102, 26392416, 54014062, 17976208, 9860195, 65454636, 20836893, 53393358, 85695762, 38658347, 20486294, 22879907, 81898046, 30395570, 54263880, 30787683, 64602895, 77377183, 45990383, 5640302, 95395112, 47887470, 29959549, 1022677, 37183543, 16097038, 72793444, 17058722, 62740044, 44847298, 80555751, 23110625, 34295794, 39847321, 16099750, 7685448, 12348118, 81172706, 70367851, 7066775, 47090124, 17146629, 32058615, 40781449, 54232247, 82532312, 92033260, 4069912, 23194618, 95726235, 51466049, 60393039, 86242799, 60955663, 6819644, 5822202, 87710366, 77413012, 57803235, 17386996, 83155442, 48774913, 94076128, 94595668, 4985896, 45306070, 95251277, 54517921, 76825057, 8115266, 83651211, 49598724, 33336148, 19939935, 21001913, 28928175, 75153252, 79657802, 66045587, 57248122, 55602660, 9860968, 81274566, 36580610, 13862149, 64848072, 48675329, 15948937, 22997281, 32159704, 17857111, 67227442, 3633375, 78884452, 35456853, 68875490, 92867155, 23134715, 51149804, 16019925, 51507425, 73617245, 46851987, 36845587, 9175338, 63967300, 12303248, 42692881, 37501808, 79136082, 83747892, 51426311, 88047921, 90004325, 4119747, 86798033, 99226875, 55669657, 36780454, 84187166, 74614639, 32161669, 97940276, 22435353, 21533347, 53084256, 3487592, 7104732, 28787861, 53842979, 62115552, 47893286, 7919588, 75135448, 67031644, 22108665, 69255765, 30569392, 66116458, 62051033, 7300047, 89804152, 42307449, 63506281, 61815223, 38365584, 71316369, 55143724, 85711894, 36135, 31491938, 70420215, 62428472, 45995325, 247198, 68694897, 56515456, 43501211, 93566986, 61859581, 35512853, 26102057, 18001617, 33304202, 51715482, 73235980, 45237957, 9259676, 72495719, 64098930, 15064655, 61728685, 47708850, 92692978, 62496012, 77072625, 45381876, 88904910, 40677414, 20642888, 19891772, 11161731, 99729357, 28851716, 6986898, 72278539, 87160386, 24591705, 61928316, 12024238, 14349098, 53632373 +99690194, 5267545, 8099994, 72019362, 42237907, 18466635, 42644903, 37183543, 92033260, 97057187, 9886593, 92215320, 34493392, 34236719, 569864, 84904436, 30653863, 89214616, 7517032, 54232247, 16567550, 12024238, 50702367, 83155442, 90090964, 40677414, 35512853, 51588015, 4434662, 97281847, 57961282, 35092039, 3487592, 60430369, 78602717, 36468541, 65454636, 17365188, 15163258, 26734892, 81853704, 45794415, 8807940, 24619760, 76434144, 30569392, 874791, 7685448, 2204165, 36808486, 83747892, 55960386, 91664334, 36135, 72732205, 4119747, 23194618, 72777973, 62428472, 99224392, 47738185, 45995325, 99125126, 36753250, 74614639, 93057697, 8115266, 4806458, 83533741, 80316608, 33336148, 33431960, 69641513, 72274002, 22450468, 78785507, 58224549, 45237957, 55602660, 6038457, 20568363, 59981773, 83150534, 67451935, 54663246, 69848388, 80246713, 44177011, 15064655, 55753905, 3235882, 47213183, 20765474, 55850790, 61263068, 43933006, 7300047, 82052050, 76671482, 55615885, 80555751, 78909561, 19101477, 48892201, 29635537, 9175338, 32426752, 44889423, 56461322, 96726697, 78766358, 90004325, 90654836, 71920426, 49271185, 87720882, 85571389, 39201414, 40356877, 20645197, 97783876, 77187825, 56955985, 168541, 669105, 44983451, 11365791, 45306070, 5111370, 44627776, 19457589, 95251277, 19376156, 90310261, 99549373, 17764950, 48673079, 26102057, 17539625, 21001913, 63372756, 88653118, 30998561, 82427263, 91802888, 14326617, 1788101, 6497830, 7919588, 16595436, 32699744, 24058273, 73392814, 78884452, 21993752, 22942635, 38061439, 24826575, 65880522, 78589145, 77377183, 9575954, 52613508, 92071990, 43045786, 98739783, 31161687, 75777973, 61728685, 33123618, 1022677, 82979980, 4204661, 3233569, 89804152, 98653983, 55189057, 66271566, 11543098, 8913721, 76703609, 99729357, 2331773, 62936963, 36505482, 83948335, 23110625, 72738685, 45428665, 39847321, 9188443, 51047803, 70372191, 42073124, 22766820, 74724075, 29029316, 7646095, 50007421, 34044787, 97379790, 16424199, 40781449, 92692978, 27665211, 24314885, 16684829, 68204242, 29401781, 72373496, 29994197, 45919976, 8733068, 1375023, 99524975, 43152977, 14731700, 6819644, 59501487, 70420215, 54606384, 37166608, 17037369, 45848907, 26292919, 57803235, 1250437, 5832946, 91255408, 44550764, 48893685, 8696647, 36812683, 16387575, 43506672, 39553046, 31904591, 36139942, 13470059, 76540481, 14947650, 22435353, 93053405, 45617087, 90272749, 47361209, 38510840, 27185644, 43357947, 58749, 10961421, 66611759, 8055981, 36312813, 74137926, 9860968, 85023028, 95290172, 61271144, 26397786, 13862149, 10358899, 9829782, 51472275, 70036438, 17976208, 59405277, 6157724, 11161731, 97011160, 81677380, 59614746, 61712234, 65715134, 38658347, 20486294, 38009615, 30787683, 176257, 20867149, 59197747, 64157906, 45990383, 33553959, 93933709, 42967683, 17058722, 58180653, 40197395, 30463802, 61815223, 32590267, 29188588, 6793819, 33249630, 77694700, 27187213, 12348118, 41245325, 23740167, 18783702, 17146629, 4508700, 68644627, 38022834, 29834512, 74357852, 88047921, 55143724, 41481685, 79806380, 91938191, 19272365, 63015256, 9257405, 83302115, 1431742, 20002147, 66319530, 5822202, 56424103, 9603598, 11757872, 45381876, 45996863, 89078848, 4064751, 82779622, 44060493, 30771409, 7502255, 10453030, 247198, 30163921, 66832478, 14220886, 82897371, 92353856, 68694897, 56531125, 65017137, 57020481, 526217, 18833224, 61960400, 83368048, 99917599, 44481640, 82327024, 41092172, 16405341, 70800879, 66137019, 26493119, 45667668, 30139692, 8373289, 58208470, 98462867, 96852321, 17894977, 6221471, 48395186, 66250369, 28796059, 96735716, 508198, 2208785, 4515343, 57248122, 7229550, 62115552, 78549759, 30694952, 98943869, 1936762, 63628376, 22200378, 4199704, 93515664, 37957788, 15075176, 20885148, 34667286, 51590803, 30764367, 1197320, 19898053, 85695762, 22879907, 62232211, 81898046, 30891921, 21673260, 40984766, 1569515, 83789391, 8729146, 76170907, 19486173, 69255765, 10649306, 13819358, 62051033, 62803858, 73168565, 29932657, 21289531, 77301523, 23134715, 16097038, 72793444, 36685023, 48260151, 26063929, 16019925, 79880247, 84293052, 46851987, 85116755, 48658605, 96906410, 69786756, 59177718, 112651, 63967300, 12303248, 66885828, 5073754, 47708850, 79136082, 6808825, 67281495, 51426311, 99965001, 47090124, 25636669, 89811711, 71316369, 27041967, 85711894, 81774825, 57359924, 27625735, 33935899, 65047700, 94711842, 47298834, 31491938, 56484900, 24953498, 40781854, 4668450, 98948034, 30218878, 73021291, 72357096, 34497327, 77072625, 41092102, 83269727, 89699445, 11581181, 99333375, 50567636, 36396314, 84127901, 46540998, 40534591, 32151165, 94076128, 16380211, 73031054, 4985896, 2917920, 56515456, 94911072, 72358170, 91281584, 43501211, 92530431, 93566986, 82339363, 68824981, 60176618, 72725103, 45407418, 4095116, 85242963, 97940276, 91727510, 33797252, 80251430, 81855445, 88698958, 19939935, 3233084, 83133790, 90013093, 16445503, 51715482, 87160386, 68102437, 53084256, 11923835, 54762643, 7104732, 9623492, 26776922, 66045587, 3183975, 33628349, 68816413, 47792865, 55470718, 36580610, 57393458, 53802686, 90061527, 14723410, 3633375, 53547802, 65338021, 53393358, 21070110, 3773993, 89046466, 70004753, 87598888, 31733363, 12571310, 64602895, 88130087, 68000591, 61928316, 86301513, 63059024, 5640302, 68875490, 65275241, 38341669, 60581278, 29959549, 89217461, 42199455, 30543215, 63506281, 13468268, 66322959, 65081429, 72614359, 415901, 38365584, 73124510, 41380093, 92541302, 98371444, 92998591, 71469330, 37620363, 93562257, 6505939, 70541760, 7066775, 4099191, 56473732, 7011964, 58751351, 95957797, 43376279, 14045383, 68316156, 4091162, 20122224, 30811010, 74110882, 18699206, 86798033, 66428795, 76960303, 4069912, 50188404, 56153345, 60393039, 37435892, 55247455, 78300864, 67474219, 3294781, 96193415, 36930650, 84187166, 26744917, 12664567, 96709982, 14349098, 48774913, 29819940, 34946859, 6871053, 40152546, 37891451, 99971982, 67793644, 61897582, 54987042, 72278539, 74743862, 69697787, 89637706, 77300457, 71083565, 32274392, 45075471, 79191827, 24733232, 32161669, 9599614, 91240048, 92803766, 9058407, 2891150, 75543508, 59371804, 49598724, 57241521, 94090109, 13173644, 78218845, 29510992, 13231279, 17957593, 71965942, 17068582, 90457870, 84840549, 88251446, 28928175, 40865610, 65271999, 93270084, 3880712, 49328608, 96420429, 79922758, 66903004, 81274566, 54014062, 84406788, 49236559, 37280276, 36189527, 28734791, 14626618, 62430984, 17857111, 15015906, 67227442, 70596786, 89499542, 44844121, 62490109, 15902805, 86460488, 99604946, 40027975, 37224844, 13348726, 98648327, 7423788, 95395112, 92867155, 47887470, 77312810, 86543538, 6111563, 85117092, 42307449, 54427233, 80014588, 73617245, 62740044, 4798568, 44847298, 61741594, 49597667, 15535065, 57163802, 16099750, 18411915, 92604458, 81172706, 73109997, 33699435, 39373729, 16971929, 18131876, 7182642, 16054533, 59910624, 77898274, 61859143, 52060076, 24915585, 69355476, 82532312, 7687278, 99021067, 72238278, 73786944, 95726235, 51466049, 18806856, 74441448, 64055960, 77284619, 62496012, 66663942, 38256849, 86022504, 87710366, 36780454, 31727379, 39986008, 37192445, 15536795, 53632373, 99515901, 76330843, 22129328, 34432810, 97641116, 2717150, 9398733, 67124282, 26664538, 44842615, 23848565, 84349107, 83210802, 38645117, 83651211, 4787945, 92787493, 20642888, 26139110, 29516592, 8634541, 19891772, 3509435, 44348328, 86001008, 26998766, 28550822, 75128745, 91141395, 34698428, 62357986, 73235980, 44664587, 3088684, 95581843, 53842979, 81805959, 26114953, 9259676, 75052463, 38494874, 8651647, 89419466, 36933038, 88444207, 26392416, 67084351, 64848072, 749283, 48675329, 92398073, 32250045, 27325428, 72495719, 89996536, 84166196, 38008118, 98130363, 20681921, 55770687, 69605283, 35456853, 53648154, 73222868, 82178706, 68110330, 64087743, 61373987, 70727211, 99861373, 67031644, 62552524, 44784505, 78561158, 66116458, 92554120, 17016156, 37675718, 39171895, 53666583, 26275734, 51507425, 1239555, 91957544, 77787724, 71333116, 29466406, 52204879, 2607799, 31126490, 32058615, 1204161, 50668599, 10264691, 83083131, 824872, 99226875, 61982238, 57658654, 62762857, 74452589, 55669657, 33565483, 67513640, 8128637, 77413012, 57240218, 6986898, 40686254, 17385531, 52261574, 82726008, 46870723, 17727650, 21397057, 54058788, 79821897, 96318094, 59318837, 65851721, 50806615, 62693428, 44846932, 45049308, 65038678, 61859581, 69136837, 59109400, 76825057, 90158785, 22405120, 94516935, 45790169, 18001617, 69579137, 21533347, 75820087, 26863229, 99658235, 75153252, 66667729, 79657802, 23569917, 42947632, 75229462, 70782102, 30366150, 4978543, 9860195, 10309525, 94539824, 6525948, 20836893, 24591705, 6153406, 12416768, 30395570, 93790285, 75407347, 39801351, 51149804, 36845587, 37739481, 77620120, 71300104, 42692881, 44245960, 18663507, 37501808, 63152504, 18504197, 86118021, 8791066, 33237508, 5970607, 49641577, 82651278, 37659250, 65074535, 86242799, 67030811, 24168411, 94595668, 22721500, 62452034, 88904910, 29065964, 15148031, 10366309, 79942022, 44473167, 25842078, 76315420, 93359396, 68702632, 54199160, 33895336, 91831487, 21919959, 91990218, 15948937, 68128438, 64098930, 75135448, 48360198, 5482538, 94360702, 52293995, 34698463, 26507214, 35192533, 92692283, 75986488, 54868730, 59329511, 14363867, 44479073, 60955663, 17386996, 10597197, 23432750, 27411561, 35996293, 68041839, 97561745, 84684495, 49882705, 51315460, 14093520, 77272628, 22997281, 79738755, 30090481, 8129978, 82886381, 48088883, 84002370, 37560164, 43322743, 20140249, 79322415, 33201905, 71657078, 53888755, 321665, 54517921, 61623915, 28787861, 42782652, 60106217, 47893286, 22108665, 7955293, 34295794, 71522968, 68939068, 17081350, 61141874, 33304202, 95010552, 44915235, 32159704, 41442762, 28851716, 86821229, 6521313, 33061250, 54263880, 60102965, 51464002, 99297164, 22113133, 70367851 +53084256, 70541760, 83083131, 90310261, 99549373, 12416768, 3294781, 6521313, 20642888, 11923835, 32159704, 75777973, 67281495, 74357852, 82779622, 8115266, 51588015, 18001617, 6221471, 75229462, 94539824, 70596786, 19486173, 68000591, 86301513, 61741594, 99297164, 68644627, 79806380, 19272365, 16567550, 29819940, 54058788, 97057187, 32274392, 44481640, 83651211, 75543508, 33336148, 62357986, 60430369, 9259676, 20836893, 81898046, 30395570, 7423788, 5640302, 95395112, 65275241, 7300047, 40197395, 54427233, 46851987, 96906410, 59177718, 63967300, 71522968, 61859143, 92692978, 33935899, 14363867, 40356877, 64055960, 14731700, 824872, 68939068, 37192445, 17727650, 7502255, 67793644, 54987042, 44550764, 67124282, 45049308, 44842615, 4787945, 76540481, 70800879, 83533741, 97940276, 45667668, 78218845, 38510840, 72019362, 58749, 63372756, 57248122, 3183975, 38494874, 91831487, 95290172, 36933038, 10358899, 72495719, 22997281, 34493392, 14723410, 15015906, 81853704, 85695762, 21673260, 76170907, 88130087, 78589145, 3235882, 23134715, 26063929, 13468268, 26507214, 72614359, 36845587, 36505482, 38365584, 32590267, 41380093, 92541302, 66885828, 27187213, 71469330, 79136082, 43376279, 91664334, 57359924, 7687278, 39201414, 12024238, 74441448, 72357096, 62496012, 17037369, 77413012, 52261574, 72278539, 91255408, 89637706, 65017137, 36753250, 68824981, 32161669, 26139110, 80316608, 93053405, 8373289, 21533347, 83133790, 71965942, 21001913, 33304202, 90013093, 40865610, 75128745, 96420429, 68816413, 22200378, 93515664, 28734791, 37957788, 59405277, 6157724, 15948937, 18466635, 30764367, 65338021, 45794415, 38658347, 82178706, 80246713, 86460488, 54263880, 70004753, 37224844, 44784505, 30569392, 92867155, 62803858, 55850790, 21289531, 29959549, 77312810, 4204661, 42307449, 84293052, 15535065, 45428665, 9188443, 51047803, 33249630, 44245960, 37620363, 18783702, 50007421, 7011964, 4508700, 41442762, 71316369, 82532312, 65047700, 29994197, 99524975, 55247455, 20002147, 6819644, 59501487, 74452589, 54606384, 24168411, 62428472, 96709982, 22129328, 14349098, 46870723, 44060493, 40534591, 66832478, 92353856, 56515456, 94911072, 9886593, 36812683, 16387575, 65038678, 15148031, 23848565, 84349107, 92803766, 40677414, 38645117, 13470059, 16405341, 27411561, 4434662, 97561745, 30139692, 57241521, 19891772, 29510992, 17539625, 81855445, 69641513, 19939935, 25842078, 72274002, 51715482, 87160386, 84840549, 28550822, 34698428, 48395186, 88653118, 9623492, 45237957, 66250369, 79657802, 7229550, 23569917, 9860968, 13862149, 64848072, 36189527, 65454636, 97011160, 14626618, 62430984, 15163258, 59614746, 34236719, 1197320, 17857111, 16595436, 69605283, 20486294, 62232211, 30787683, 20867149, 83789391, 12571310, 5482538, 64157906, 47213183, 66116458, 42644903, 73168565, 33553959, 86543538, 26275734, 89804152, 79880247, 55615885, 65081429, 80555751, 62936963, 30463802, 83948335, 37560164, 72738685, 92604458, 54868730, 99690194, 69786756, 112651, 22766820, 5073754, 74724075, 44889423, 7646095, 63152504, 8791066, 17146629, 5970607, 18131876, 68316156, 82651278, 27625735, 69355476, 91938191, 1204161, 86798033, 87720882, 10264691, 43152977, 24953498, 67030811, 9603598, 11757872, 38256849, 41092102, 87710366, 89699445, 15536795, 26292919, 36396314, 6986898, 50702367, 40686254, 10453030, 96318094, 6871053, 40152546, 37891451, 73031054, 53888755, 669105, 86821229, 44983451, 4985896, 14220886, 82897371, 68694897, 62452034, 72358170, 44846932, 57020481, 93566986, 61859581, 83210802, 59109400, 22405120, 26102057, 85242963, 66137019, 59371804, 44473167, 69579137, 58208470, 26863229, 3233084, 8099994, 93270084, 28796059, 96735716, 4515343, 81805959, 98943869, 14326617, 26397786, 36580610, 749283, 4199704, 9860195, 53802686, 81677380, 68128438, 89996536, 98130363, 7919588, 61712234, 53547802, 24058273, 6153406, 22879907, 30891921, 40984766, 24619760, 99861373, 79738755, 15064655, 67031644, 45990383, 69255765, 75407347, 43045786, 3233569, 17058722, 42199455, 11543098, 52293995, 85117092, 874791, 73617245, 62740044, 4798568, 92692283, 77620120, 6793819, 71300104, 42073124, 92998591, 43322743, 77694700, 12348118, 29029316, 47708850, 2204165, 73109997, 6808825, 18504197, 41245325, 99965001, 58751351, 33237508, 95957797, 77787724, 2607799, 96726697, 55143724, 14045383, 49641577, 32058615, 4091162, 20122224, 40781449, 30811010, 90654836, 28851716, 72732205, 4119747, 54232247, 18699206, 49271185, 4069912, 99021067, 50668599, 68204242, 73786944, 1375023, 99226875, 56424103, 34497327, 77072625, 11581181, 39986008, 45848907, 83155442, 99515901, 76330843, 4064751, 46540998, 32151165, 16380211, 10597197, 34432810, 168541, 50806615, 61897582, 90090964, 48893685, 88904910, 44627776, 526217, 74614639, 99917599, 26664538, 5267545, 76825057, 23432750, 45407418, 92787493, 9058407, 48673079, 79942022, 94516935, 14947650, 33797252, 8634541, 57961282, 44348328, 98462867, 88698958, 17957593, 35092039, 76315420, 26998766, 68102437, 88251446, 28928175, 75153252, 44664587, 54199160, 53842979, 33895336, 82427263, 26114953, 33628349, 75052463, 60106217, 42947632, 14093520, 21919959, 95010552, 26392416, 42237907, 30366150, 9829782, 47893286, 51472275, 6497830, 67451935, 27325428, 17365188, 65715134, 21993752, 21070110, 73222868, 8807940, 68110330, 64087743, 89046466, 31733363, 93790285, 65880522, 62552524, 92071990, 63059024, 39801351, 13819358, 20765474, 38341669, 60581278, 33123618, 47887470, 17016156, 1022677, 77301523, 6111563, 16097038, 94360702, 39171895, 60102965, 72793444, 98653983, 36685023, 76671482, 66271566, 30653863, 78909561, 37739481, 61815223, 19101477, 1239555, 73124510, 23110625, 29188588, 85116755, 12303248, 81172706, 37501808, 36808486, 86118021, 56461322, 33699435, 78766358, 31126490, 88047921, 85711894, 16054533, 97379790, 77898274, 37659250, 44479073, 23194618, 29401781, 95726235, 31491938, 20645197, 78300864, 40781854, 4668450, 36930650, 62762857, 97783876, 37166608, 31727379, 84187166, 8128637, 12664567, 89078848, 57803235, 21397057, 5832946, 34946859, 59318837, 94076128, 2717150, 69697787, 9398733, 45306070, 56531125, 61141874, 91281584, 43501211, 61960400, 92530431, 82339363, 82327024, 19376156, 91240048, 60176618, 90158785, 17764950, 91727510, 29516592, 94090109, 80251430, 84684495, 17894977, 22450468, 61623915, 10961421, 91141395, 7104732, 58224549, 66667729, 68702632, 66045587, 42782652, 2208785, 79922758, 91802888, 6038457, 8651647, 47792865, 55470718, 1788101, 91990218, 54014062, 49236559, 37280276, 10309525, 90061527, 77272628, 54663246, 78884452, 62490109, 15902805, 1569515, 70727211, 87598888, 55753905, 13348726, 98648327, 77377183, 10649306, 98739783, 92554120, 31161687, 61728685, 89217461, 43933006, 55189057, 30543215, 48260151, 16019925, 66322959, 51507425, 84904436, 415901, 39847321, 29635537, 9175338, 98371444, 7685448, 93562257, 83747892, 29466406, 16971929, 41481685, 81774825, 63015256, 72238278, 9257405, 51466049, 60393039, 66319530, 77284619, 66663942, 96193415, 57658654, 36780454, 99333375, 33565483, 50567636, 67513640, 56955985, 45996863, 57240218, 17385531, 1250437, 82726008, 99224392, 30771409, 30163921, 11365791, 8696647, 62693428, 99125126, 43506672, 54517921, 39553046, 31904591, 93057697, 10366309, 35996293, 22435353, 45617087, 13231279, 96852321, 17068582, 90457870, 49882705, 65271999, 36312813, 28787861, 3880712, 30998561, 49328608, 55602660, 78602717, 78549759, 59981773, 83150534, 36468541, 61271144, 67084351, 63628376, 44915235, 4978543, 11161731, 20885148, 51590803, 69848388, 3633375, 53393358, 73392814, 89499542, 53648154, 33061250, 3773993, 44177011, 64602895, 52613508, 8129978, 62051033, 29932657, 37675718, 569864, 82979980, 37183543, 42967683, 48088883, 58180653, 8913721, 84002370, 57163802, 48658605, 89214616, 32426752, 70367851, 18663507, 6505939, 7066775, 51426311, 55960386, 56473732, 47090124, 22113133, 38022834, 29834512, 27041967, 7182642, 36135, 7517032, 52060076, 90004325, 74110882, 66428795, 76960303, 45919976, 18806856, 83302115, 8733068, 56484900, 37435892, 1431742, 67474219, 73021291, 55669657, 86022504, 83269727, 45381876, 53632373, 84127901, 48774913, 79821897, 45995325, 94595668, 99971982, 74743862, 321665, 95251277, 71083565, 79191827, 24733232, 9599614, 69136837, 72725103, 35512853, 2891150, 45790169, 49598724, 26493119, 47361209, 92215320, 86001008, 43357947, 16445503, 95581843, 26776922, 30694952, 51315460, 20568363, 66903004, 85023028, 81274566, 70782102, 57393458, 84406788, 92398073, 84166196, 6525948, 26734892, 20681921, 32699744, 22942635, 44844121, 99604946, 176257, 40027975, 8729146, 59197747, 76434144, 68875490, 82886381, 51149804, 34698463, 2331773, 91957544, 49597667, 34295794, 16099750, 18411915, 51464002, 23740167, 4099191, 34044787, 89811711, 59910624, 16424199, 71920426, 24314885, 92033260, 85571389, 86242799, 98948034, 30218878, 61982238, 77187825, 33201905, 26744917, 17081350, 71657078, 247198, 65851721, 29065964, 18833224, 36139942, 4095116, 68041839, 90272749, 3509435, 27185644, 93359396, 3487592, 8055981, 73235980, 3088684, 74137926, 1936762, 88444207, 70036438, 15075176, 32250045, 64098930, 24591705, 19898053, 55770687, 35456853, 38061439, 75135448, 48360198, 30090481, 22108665, 61928316, 78561158, 93933709, 53666583, 76703609, 80014588, 44847298, 7955293, 48892201, 39373729, 52204879, 65074535, 56153345, 60955663, 70420215, 79322415, 19457589, 83368048, 41092172, 97281847, 78785507, 99658235, 66611759, 54762643, 62115552, 89419466, 17976208, 34667286, 61373987, 24826575, 9575954, 61263068, 82052050, 99729357, 35192533, 75986488, 70372191, 42692881, 24915585, 27665211, 16684829, 50188404, 94711842, 47298834, 20140249, 47738185, 2917920, 5111370, 77300457, 45075471, 4806458, 33431960, 75820087, 508198, 63506281, 59329511, 71333116, 72777973, 5822202, 17386996, 22721500, 72373496, 48675329, 38008118, 67227442, 38009615, 97641116, 13173644, 25636669 +11161731, 93053405, 42947632, 61928316, 62496012, 62762857, 69697787, 66137019, 92215320, 33628349, 48675329, 81677380, 44177011, 72793444, 16019925, 5073754, 89811711, 29466406, 41481685, 40781449, 71920426, 86798033, 9257405, 29994197, 24953498, 94595668, 44842615, 9599614, 22405120, 14947650, 91727510, 63372756, 26776922, 23569917, 51315460, 9259676, 55470718, 64848072, 28734791, 97011160, 65880522, 44784505, 30569392, 63059024, 65275241, 55850790, 73617245, 77620120, 85116755, 83747892, 70541760, 25636669, 85711894, 37659250, 99524975, 98948034, 66663942, 83269727, 39986008, 45848907, 1250437, 59318837, 66832478, 72278539, 61859581, 19376156, 40677414, 35512853, 68041839, 18001617, 94090109, 98462867, 17957593, 83133790, 28928175, 3487592, 65271999, 44664587, 96735716, 7229550, 98943869, 81274566, 36933038, 88444207, 54014062, 68128438, 34493392, 94539824, 15015906, 3633375, 22942635, 80246713, 64087743, 30787683, 55753905, 43045786, 13819358, 92867155, 569864, 58180653, 40197395, 42307449, 99729357, 66322959, 65081429, 36505482, 75986488, 9175338, 92604458, 12303248, 22766820, 79136082, 47090124, 8791066, 5970607, 38022834, 91664334, 92692978, 19272365, 50668599, 85571389, 95726235, 56484900, 66319530, 86022504, 62428472, 17037369, 45996863, 77413012, 36396314, 84127901, 10453030, 53888755, 22721500, 14220886, 8696647, 29065964, 93057697, 8115266, 13470059, 92787493, 48673079, 83533741, 22435353, 13173644, 69579137, 47361209, 96852321, 28550822, 61623915, 58749, 3880712, 49328608, 42782652, 91802888, 59981773, 749283, 15163258, 53393358, 78884452, 20486294, 82178706, 62232211, 21673260, 40984766, 24619760, 20867149, 24826575, 48360198, 78589145, 67031644, 78561158, 92071990, 69255765, 75407347, 42644903, 33123618, 6111563, 48088883, 17058722, 51149804, 13468268, 36845587, 415901, 34295794, 15535065, 39847321, 29635537, 57163802, 9188443, 99690194, 59177718, 42073124, 112651, 66885828, 77694700, 44889423, 71469330, 50007421, 71316369, 2607799, 78766358, 36135, 30811010, 28851716, 72732205, 4119747, 54232247, 82532312, 66428795, 39201414, 51466049, 31491938, 1431742, 86242799, 4668450, 79322415, 50567636, 40686254, 96709982, 30163921, 9398733, 45306070, 89637706, 65017137, 43501211, 74614639, 54517921, 83368048, 82339363, 23848565, 76825057, 26139110, 4434662, 49598724, 97281847, 30139692, 29516592, 44473167, 78218845, 29510992, 35092039, 78785507, 11923835, 66611759, 45237957, 95581843, 79657802, 508198, 26114953, 38494874, 9860968, 68816413, 83150534, 95290172, 1788101, 63628376, 70036438, 36189527, 27325428, 32159704, 84166196, 38008118, 69848388, 24591705, 81853704, 20681921, 85695762, 21070110, 68110330, 38061439, 83789391, 76170907, 9575954, 3235882, 47213183, 66116458, 8129978, 92554120, 38341669, 1022677, 42967683, 7300047, 4204661, 55189057, 66271566, 52293995, 48260151, 51507425, 79880247, 30653863, 62740044, 72614359, 44847298, 78909561, 61815223, 16099750, 63967300, 81172706, 70367851, 63152504, 41245325, 7066775, 18783702, 86118021, 29834512, 74357852, 55143724, 4091162, 49271185, 4069912, 44479073, 23194618, 45919976, 10264691, 18806856, 60393039, 37435892, 64055960, 78300864, 72357096, 61982238, 56424103, 77072625, 11757872, 77187825, 38256849, 36780454, 50702367, 82726008, 76330843, 4064751, 44060493, 29819940, 30771409, 6871053, 45995325, 16380211, 168541, 82897371, 92353856, 56515456, 5111370, 99125126, 36753250, 77300457, 95251277, 92530431, 36139942, 69136837, 92803766, 90310261, 23432750, 99549373, 4806458, 9058407, 79942022, 26493119, 8634541, 33431960, 17539625, 13231279, 84684495, 88698958, 21001913, 33304202, 90457870, 72019362, 68102437, 6221471, 58224549, 93270084, 33895336, 82427263, 96420429, 60430369, 57248122, 79922758, 55602660, 62115552, 81805959, 14093520, 21919959, 30366150, 13862149, 49236559, 22200378, 4199704, 44915235, 67451935, 15075176, 32250045, 51590803, 17365188, 30764367, 17857111, 67227442, 53547802, 70596786, 19898053, 73392814, 21993752, 73222868, 15902805, 86460488, 61373987, 99861373, 15064655, 75135448, 59197747, 64157906, 13348726, 98648327, 68000591, 45990383, 5640302, 95395112, 60581278, 29959549, 61263068, 82979980, 34698463, 63506281, 46851987, 19101477, 48892201, 1239555, 41380093, 92541302, 29188588, 18411915, 42692881, 12348118, 74724075, 29029316, 2204165, 37620363, 37501808, 73109997, 6505939, 51426311, 55960386, 17146629, 41442762, 39373729, 33237508, 71333116, 31126490, 7182642, 68316156, 82651278, 52060076, 57359924, 24915585, 27625735, 24314885, 7687278, 65047700, 63015256, 68204242, 83302115, 60955663, 14731700, 96193415, 34497327, 87710366, 37166608, 89699445, 31727379, 68939068, 56955985, 8128637, 26292919, 53632373, 57803235, 99515901, 52261574, 99224392, 48774913, 37891451, 10597197, 97057187, 65851721, 669105, 91255408, 67124282, 9886593, 88904910, 61141874, 44627776, 93566986, 44481640, 24733232, 26664538, 5267545, 38645117, 20642888, 4095116, 76540481, 26102057, 35996293, 59371804, 33797252, 8373289, 57241521, 80251430, 19891772, 58208470, 69641513, 17894977, 26998766, 88251446, 40865610, 54762643, 88653118, 7104732, 54199160, 6038457, 74137926, 78602717, 75052463, 66903004, 91831487, 89419466, 47792865, 14326617, 95010552, 26392416, 67084351, 42237907, 51472275, 20885148, 18466635, 34667286, 90061527, 77272628, 89996536, 65715134, 69605283, 81898046, 44844121, 30395570, 8729146, 88130087, 76434144, 52613508, 20765474, 31161687, 62051033, 62803858, 29932657, 47887470, 37675718, 86543538, 43933006, 39171895, 60102965, 3233569, 89804152, 98653983, 42199455, 82052050, 11543098, 8913721, 55615885, 83948335, 32590267, 23110625, 7685448, 71300104, 96906410, 43322743, 18663507, 18504197, 4099191, 7011964, 4508700, 58751351, 34044787, 59329511, 77787724, 43376279, 96726697, 14045383, 90004325, 74110882, 33935899, 76960303, 87720882, 29401781, 72777973, 73786944, 20645197, 67474219, 77284619, 30218878, 3294781, 5822202, 9603598, 97783876, 55669657, 33565483, 26744917, 37192445, 45381876, 12664567, 57240218, 6986898, 47738185, 21397057, 34946859, 32151165, 79821897, 247198, 50806615, 44983451, 4985896, 6521313, 44550764, 68694897, 62693428, 321665, 72358170, 526217, 71083565, 32274392, 68824981, 15148031, 32161669, 82327024, 10366309, 91240048, 45407418, 41092172, 16405341, 27411561, 94516935, 33336148, 21533347, 86001008, 71965942, 38510840, 90013093, 49882705, 99658235, 34698428, 8055981, 66250369, 36312813, 68702632, 4515343, 30694952, 75229462, 70782102, 36580610, 57393458, 84406788, 37280276, 6497830, 93515664, 6157724, 59614746, 64098930, 98130363, 32699744, 24058273, 89499542, 38658347, 8807940, 38009615, 12416768, 3773993, 54263880, 70727211, 93790285, 19486173, 30090481, 77377183, 62552524, 68875490, 39801351, 33553959, 93933709, 16097038, 53666583, 36685023, 76671482, 85117092, 874791, 80014588, 84904436, 26507214, 30463802, 91957544, 38365584, 92692283, 72738685, 98371444, 48658605, 44245960, 47708850, 36808486, 51464002, 56461322, 99297164, 68644627, 16971929, 52204879, 18131876, 88047921, 16054533, 27665211, 14363867, 50188404, 72373496, 16567550, 47298834, 74441448, 83083131, 20002147, 20140249, 70420215, 57658654, 74452589, 41092102, 24168411, 33201905, 99333375, 15536795, 17081350, 14349098, 46870723, 17727650, 5832946, 73031054, 86821229, 2717150, 48893685, 56531125, 94911072, 19457589, 65038678, 18833224, 45075471, 99917599, 83210802, 60176618, 83651211, 4787945, 45667668, 45617087, 90272749, 57961282, 75820087, 3509435, 44348328, 26863229, 3233084, 17068582, 93359396, 84840549, 53084256, 48395186, 9623492, 66667729, 2208785, 3183975, 8651647, 10358899, 47893286, 37957788, 17976208, 65454636, 10309525, 72495719, 62430984, 34236719, 6525948, 1197320, 26734892, 20836893, 16595436, 6153406, 45794415, 53648154, 33061250, 1569515, 176257, 40027975, 7423788, 61728685, 37183543, 23134715, 76703609, 26063929, 84293052, 2331773, 84002370, 4798568, 62936963, 7955293, 37560164, 45428665, 89214616, 32426752, 33249630, 27187213, 71522968, 6808825, 67281495, 99965001, 56473732, 22113133, 81774825, 7517032, 77898274, 20122224, 90654836, 69355476, 91938191, 1204161, 92033260, 16684829, 72238278, 43152977, 55247455, 67030811, 59501487, 99226875, 83155442, 17385531, 82779622, 7502255, 46540998, 40534591, 40152546, 94076128, 34432810, 99971982, 61897582, 90090964, 16387575, 43506672, 61960400, 39553046, 90158785, 72725103, 17764950, 70800879, 85242963, 97940276, 2891150, 75543508, 45790169, 27185644, 72274002, 8099994, 16445503, 51715482, 75128745, 10961421, 91141395, 73235980, 53842979, 78549759, 20568363, 36468541, 9829782, 4978543, 9860195, 59405277, 22997281, 14723410, 22879907, 30891921, 89046466, 87598888, 37224844, 12571310, 98739783, 75777973, 21289531, 77301523, 89217461, 26275734, 30543215, 54427233, 49597667, 73124510, 6793819, 51047803, 70372191, 7646095, 93562257, 23740167, 27041967, 16424199, 32058615, 65074535, 94711842, 40781854, 36930650, 22129328, 97641116, 54987042, 74743862, 44846932, 45049308, 31904591, 80316608, 97561745, 43357947, 76315420, 87160386, 75153252, 62357986, 3088684, 28787861, 66045587, 30998561, 1936762, 92398073, 53802686, 14626618, 54663246, 7919588, 61712234, 65338021, 35456853, 62490109, 99604946, 70004753, 64602895, 5482538, 17016156, 80555751, 61741594, 92998591, 33699435, 61859143, 56153345, 40356877, 1375023, 6819644, 84187166, 67513640, 96318094, 67793644, 11365791, 62452034, 36812683, 91281584, 84349107, 59109400, 51588015, 22450468, 28796059, 55770687, 31733363, 86301513, 10649306, 73168565, 77312810, 37739481, 54868730, 69786756, 49641577, 18699206, 8733068, 12024238, 73021291, 824872, 54606384, 89078848, 17386996, 71657078, 2917920, 57020481, 79191827, 19939935, 25842078, 60106217, 61271144, 26397786, 91990218, 15948937, 79738755, 82886381, 94360702, 35192533, 95957797, 59910624, 97379790, 79806380, 11581181, 54058788, 85023028, 22108665, 81855445, 99021067 +5073754, 749283, 36685023, 25636669, 32058615, 47738185, 34946859, 10366309, 66137019, 96852321, 93359396, 58224549, 95010552, 67084351, 3633375, 93790285, 48360198, 64602895, 5482538, 32590267, 75986488, 42692881, 33237508, 38022834, 90654836, 87720882, 17386996, 72358170, 99125126, 91281584, 92787493, 4095116, 59371804, 68041839, 30366150, 69848388, 30787683, 9575954, 44784505, 13819358, 66271566, 15535065, 57163802, 89214616, 63152504, 77787724, 7517032, 20122224, 27625735, 71920426, 65074535, 56153345, 60393039, 74441448, 20140249, 32151165, 97057187, 89637706, 15148031, 22435353, 30139692, 3509435, 75153252, 54762643, 49328608, 61271144, 54014062, 47893286, 59405277, 62430984, 81853704, 54263880, 70004753, 83789391, 99861373, 68875490, 48260151, 72614359, 92998591, 37620363, 33699435, 88047921, 16424199, 27665211, 24314885, 86798033, 72373496, 56484900, 40781854, 824872, 11757872, 79322415, 54606384, 87710366, 82726008, 95251277, 71083565, 36139942, 45617087, 90272749, 57241521, 13173644, 21533347, 58208470, 17957593, 43357947, 88251446, 99658235, 28928175, 10961421, 6221471, 9623492, 3088684, 74137926, 23569917, 59981773, 47792865, 83150534, 64848072, 10309525, 18466635, 90061527, 54663246, 67227442, 16595436, 24058273, 22879907, 99604946, 43045786, 93933709, 42967683, 7300047, 72793444, 42307449, 99729357, 63506281, 13468268, 84002370, 48892201, 1239555, 34295794, 29188588, 70372191, 59177718, 77694700, 27187213, 18663507, 37501808, 6808825, 8791066, 68644627, 74357852, 78766358, 28851716, 4069912, 16684829, 50668599, 29994197, 73021291, 66663942, 97783876, 33201905, 33565483, 17037369, 8128637, 5832946, 10453030, 6871053, 168541, 11365791, 67124282, 321665, 29065964, 45049308, 526217, 31904591, 24733232, 40677414, 72725103, 51588015, 17764950, 20642888, 16405341, 94516935, 97940276, 33797252, 33431960, 13231279, 69641513, 72274002, 78785507, 44664587, 66250369, 36312813, 68702632, 54199160, 68816413, 10358899, 22200378, 15075176, 20885148, 77272628, 6525948, 65715134, 53547802, 69605283, 68110330, 87598888, 88130087, 30090481, 92071990, 63059024, 10649306, 31161687, 82886381, 77301523, 94360702, 30543215, 85117092, 8913721, 34698463, 51507425, 874791, 37739481, 19101477, 45428665, 48658605, 92604458, 63967300, 74724075, 44889423, 47708850, 6505939, 7066775, 86118021, 4099191, 89811711, 95957797, 27041967, 41481685, 72732205, 49271185, 7687278, 63015256, 76960303, 50188404, 72777973, 1375023, 37435892, 55247455, 67474219, 77284619, 74452589, 11581181, 56955985, 12664567, 26292919, 77413012, 89078848, 36396314, 96709982, 17385531, 21397057, 7502255, 59318837, 65851721, 73031054, 54987042, 86821229, 8696647, 16387575, 77300457, 18833224, 43501211, 61960400, 45075471, 39553046, 93057697, 92803766, 59109400, 83651211, 27411561, 79942022, 2891150, 75543508, 45790169, 18001617, 29510992, 69579137, 44348328, 88698958, 19939935, 27185644, 35092039, 76315420, 72019362, 49882705, 40865610, 11923835, 66611759, 88653118, 66667729, 95581843, 53842979, 42782652, 2208785, 96420429, 7229550, 6038457, 78602717, 78549759, 75052463, 66903004, 89419466, 42237907, 13862149, 48675329, 92398073, 27325428, 68128438, 34493392, 30764367, 14723410, 20836893, 53648154, 73222868, 22942635, 44844121, 38009615, 86460488, 30395570, 76170907, 55753905, 78589145, 61928316, 75407347, 8129978, 39801351, 75777973, 73168565, 61728685, 47887470, 33553959, 37183543, 86543538, 60102965, 48088883, 26275734, 82052050, 40197395, 73617245, 84904436, 44847298, 78909561, 36505482, 30463802, 37560164, 6793819, 9175338, 98371444, 32426752, 44245960, 71522968, 50007421, 71316369, 29466406, 52204879, 18131876, 96726697, 31126490, 55143724, 16054533, 59910624, 82651278, 24915585, 37659250, 74110882, 29401781, 85571389, 39201414, 16567550, 18806856, 47298834, 24953498, 86242799, 14731700, 30218878, 34497327, 9603598, 36930650, 86022504, 37166608, 89699445, 50567636, 39986008, 17081350, 17727650, 54058788, 61897582, 53888755, 44983451, 91255408, 6521313, 90090964, 2917920, 9398733, 5111370, 88904910, 57020481, 36753250, 54517921, 99917599, 44481640, 32161669, 82327024, 61859581, 5267545, 90310261, 60176618, 23432750, 45407418, 48673079, 26102057, 80316608, 49598724, 26493119, 97281847, 44473167, 80251430, 19891772, 57961282, 75820087, 25842078, 38510840, 17068582, 8099994, 16445503, 75128745, 28796059, 508198, 60430369, 62115552, 26114953, 9860968, 91831487, 14093520, 55470718, 36468541, 70782102, 88444207, 1788101, 26397786, 37280276, 4199704, 44915235, 93515664, 28734791, 37957788, 65454636, 17365188, 81677380, 89996536, 38008118, 26734892, 17857111, 24591705, 61712234, 65338021, 19898053, 38658347, 20486294, 35456853, 64087743, 38061439, 44177011, 61373987, 79738755, 40027975, 37224844, 12571310, 65880522, 64157906, 67031644, 22108665, 62552524, 52613508, 66116458, 65275241, 38341669, 92867155, 29932657, 17016156, 6111563, 42199455, 54427233, 80014588, 79880247, 30653863, 62936963, 61815223, 7955293, 9188443, 16099750, 71300104, 66885828, 2204165, 7646095, 79136082, 70541760, 4508700, 41442762, 39373729, 49641577, 36135, 52060076, 30811010, 54232247, 79806380, 44479073, 99021067, 40356877, 45919976, 20645197, 4668450, 67030811, 70420215, 57658654, 24168411, 36780454, 83269727, 84187166, 26744917, 84127901, 83155442, 30771409, 46540998, 79821897, 16380211, 10597197, 50806615, 30163921, 669105, 4985896, 2717150, 74743862, 48893685, 69697787, 68694897, 94911072, 9886593, 19457589, 65038678, 74614639, 83368048, 79191827, 9599614, 19376156, 91240048, 69136837, 83210802, 4787945, 41092172, 70800879, 35996293, 8373289, 47361209, 84684495, 3233084, 83133790, 33304202, 26998766, 68102437, 3487592, 91141395, 34698428, 65271999, 62357986, 7104732, 8055981, 73235980, 79657802, 26776922, 66045587, 96735716, 57248122, 79922758, 51315460, 8651647, 14326617, 36933038, 57393458, 84406788, 63628376, 49236559, 11161731, 34667286, 72495719, 97011160, 14626618, 15163258, 94539824, 32159704, 84166196, 64098930, 20681921, 6153406, 78884452, 55770687, 21993752, 82178706, 12416768, 33061250, 1569515, 31733363, 75135448, 59197747, 3235882, 47213183, 78561158, 98739783, 95395112, 42644903, 62803858, 55850790, 60581278, 98653983, 58180653, 55189057, 16019925, 66322959, 84293052, 4798568, 85116755, 99690194, 70367851, 73109997, 51464002, 83747892, 67281495, 99965001, 56461322, 99297164, 43376279, 14045383, 97379790, 57359924, 92692978, 69355476, 18699206, 33935899, 14363867, 72238278, 95726235, 83302115, 8733068, 1431742, 60955663, 66319530, 5822202, 99226875, 61982238, 96193415, 62762857, 38256849, 68939068, 52261574, 22129328, 14349098, 4064751, 82779622, 44060493, 96318094, 40152546, 22721500, 62452034, 44627776, 36812683, 43506672, 92530431, 68824981, 38645117, 76825057, 83533741, 91727510, 33336148, 45667668, 97561745, 8634541, 94090109, 78218845, 92215320, 90013093, 17894977, 51715482, 87160386, 93270084, 28787861, 3880712, 82427263, 81805959, 33628349, 20568363, 1936762, 85023028, 95290172, 9829782, 6497830, 53802686, 51590803, 22997281, 34236719, 70596786, 85695762, 81898046, 80246713, 15902805, 8807940, 89046466, 19486173, 76434144, 98648327, 69255765, 7423788, 20765474, 62051033, 33123618, 21289531, 77312810, 89217461, 16097038, 39171895, 4204661, 89804152, 17058722, 51149804, 76703609, 55615885, 46851987, 80555751, 35192533, 415901, 83948335, 91957544, 41380093, 77620120, 29635537, 96906410, 54868730, 69786756, 22766820, 12348118, 71469330, 41245325, 56473732, 58751351, 22113133, 71333116, 29834512, 65047700, 66428795, 92033260, 68204242, 23194618, 9257405, 73786944, 94711842, 99524975, 6819644, 98948034, 3294781, 62496012, 77187825, 99333375, 31727379, 37192445, 45381876, 15536795, 53632373, 57803235, 6986898, 50702367, 1250437, 99224392, 71657078, 40534591, 37891451, 247198, 94076128, 34432810, 97641116, 66832478, 14220886, 45306070, 56515456, 56531125, 93566986, 26664538, 84349107, 8115266, 90158785, 35512853, 22405120, 14947650, 26139110, 81855445, 26863229, 86001008, 21001913, 4515343, 91802888, 9259676, 81274566, 21919959, 91990218, 51472275, 36189527, 4978543, 15948937, 32250045, 1197320, 7919588, 32699744, 53393358, 21070110, 62232211, 3773993, 15064655, 24826575, 68000591, 77377183, 45990383, 86301513, 37675718, 82979980, 23134715, 11543098, 52293995, 26507214, 62740044, 73124510, 92692283, 7685448, 51047803, 42073124, 93562257, 36808486, 18783702, 7011964, 17146629, 59329511, 16971929, 2607799, 7182642, 85711894, 81774825, 90004325, 4091162, 40781449, 4119747, 91938191, 43152977, 78300864, 20002147, 72357096, 41092102, 62428472, 45848907, 45996863, 40686254, 76330843, 46870723, 29819940, 45995325, 92353856, 62693428, 61141874, 9058407, 76540481, 17539625, 71965942, 22450468, 28550822, 61623915, 63372756, 45237957, 33895336, 30998561, 55602660, 30694952, 38494874, 67451935, 59614746, 98130363, 45794415, 73392814, 89499542, 30891921, 21673260, 40984766, 20867149, 70727211, 30569392, 92554120, 43933006, 3233569, 53666583, 76671482, 2331773, 36845587, 61741594, 92541302, 72738685, 12303248, 43322743, 29029316, 51426311, 68316156, 77898274, 19272365, 12024238, 31491938, 56424103, 77072625, 67513640, 48774913, 72278539, 82897371, 65017137, 32274392, 82339363, 99549373, 93053405, 29516592, 84840549, 53084256, 48395186, 17976208, 9860195, 13348726, 5640302, 1022677, 61263068, 569864, 26063929, 38365584, 49597667, 23110625, 18411915, 33249630, 81172706, 18504197, 47090124, 34044787, 91664334, 61859143, 82532312, 59501487, 55669657, 99971982, 67793644, 44550764, 23848565, 13470059, 85242963, 98462867, 90457870, 42947632, 26392416, 36580610, 70036438, 15015906, 176257, 8729146, 29959549, 39847321, 23740167, 1204161, 10264691, 83083131, 57240218, 94595668, 4806458, 3183975, 60106217, 75229462, 6157724, 62490109, 55960386, 5970607, 64055960, 99515901, 44842615, 4434662, 58749, 24619760, 65081429, 51466049, 44846932, 98943869, 112651 +31161687, 37891451, 38341669, 77620120, 80316608, 56473732, 50188404, 40356877, 96193415, 168541, 79191827, 24733232, 48673079, 35996293, 58749, 83150534, 10358899, 22879907, 62803858, 33553959, 48260151, 98371444, 71300104, 33249630, 44245960, 6505939, 86022504, 4787945, 13173644, 84684495, 83133790, 43357947, 49328608, 4515343, 81274566, 67084351, 57393458, 70036438, 65715134, 64087743, 55850790, 89217461, 40197395, 29188588, 42692881, 7066775, 55960386, 47090124, 29834512, 74357852, 85711894, 59910624, 61859143, 54232247, 68204242, 60393039, 30218878, 50702367, 40152546, 97641116, 11365791, 45306070, 43501211, 82327024, 36139942, 45407418, 41092172, 91727510, 22435353, 80251430, 81855445, 21533347, 21001913, 61623915, 66611759, 33628349, 38494874, 9860968, 55470718, 70782102, 1788101, 84166196, 61712234, 67227442, 32699744, 19898053, 44177011, 70727211, 19486173, 22108665, 47213183, 75407347, 98739783, 39801351, 20765474, 77301523, 6111563, 53666583, 42199455, 8913721, 63506281, 874791, 26507214, 30463802, 16099750, 18411915, 43322743, 66885828, 12348118, 51464002, 51426311, 86118021, 5970607, 95957797, 96726697, 88047921, 4119747, 69355476, 18699206, 23194618, 20002147, 6819644, 824872, 56424103, 41092102, 7502255, 44550764, 74743862, 94911072, 9886593, 61141874, 91281584, 57020481, 43506672, 93566986, 92803766, 23432750, 83651211, 35512853, 70800879, 26102057, 66137019, 93053405, 4434662, 26493119, 18001617, 17539625, 26998766, 28550822, 68702632, 66045587, 78549759, 8651647, 59981773, 9829782, 67451935, 9860195, 15948937, 27325428, 89996536, 98130363, 20836893, 3633375, 69605283, 38009615, 99604946, 3773993, 89046466, 1569515, 37224844, 76434144, 13348726, 92071990, 62051033, 42967683, 89804152, 46851987, 36845587, 61741594, 57163802, 70372191, 63152504, 41245325, 25636669, 58751351, 29466406, 43376279, 55143724, 91938191, 99021067, 50668599, 39201414, 16567550, 64055960, 62762857, 37166608, 62428472, 99333375, 77413012, 82726008, 46870723, 48774913, 82779622, 44060493, 79821897, 6871053, 94595668, 73031054, 30163921, 61897582, 53888755, 86821229, 2717150, 2917920, 62693428, 44846932, 61960400, 92530431, 26664538, 8115266, 72725103, 76540481, 8373289, 33431960, 19891772, 47361209, 71965942, 76315420, 90457870, 84840549, 68102437, 75153252, 10961421, 3880712, 79657802, 53842979, 60430369, 81805959, 23569917, 42947632, 14093520, 75229462, 95290172, 36580610, 54014062, 48675329, 28734791, 37957788, 15075176, 6157724, 65454636, 11161731, 14626618, 59614746, 6153406, 73392814, 89499542, 21070110, 81898046, 38061439, 93790285, 55753905, 78589145, 8129978, 73168565, 29932657, 21289531, 17016156, 86543538, 43933006, 39171895, 98653983, 66271566, 52293995, 26063929, 65081429, 19101477, 48892201, 91957544, 75986488, 51047803, 92604458, 54868730, 112651, 63967300, 12303248, 44889423, 7646095, 67281495, 50007421, 4099191, 56461322, 33699435, 8791066, 89811711, 41481685, 68316156, 4091162, 27665211, 1204161, 65047700, 76960303, 10264691, 83302115, 94711842, 12024238, 47298834, 1375023, 99524975, 56484900, 1431742, 78300864, 55669657, 38256849, 87710366, 11581181, 33565483, 17037369, 84187166, 37192445, 15536795, 53632373, 57240218, 83155442, 52261574, 17727650, 247198, 67124282, 56515456, 56531125, 29065964, 36753250, 16387575, 77300457, 32274392, 31904591, 9599614, 5267545, 84349107, 91240048, 69136837, 59109400, 76825057, 51588015, 4806458, 79942022, 68041839, 97561745, 29516592, 44473167, 8634541, 94090109, 92215320, 13231279, 57961282, 75820087, 69641513, 33304202, 72274002, 51715482, 22450468, 93359396, 99658235, 65271999, 79922758, 55602660, 30694952, 51315460, 75052463, 98943869, 47792865, 36468541, 22200378, 64848072, 51590803, 72495719, 81677380, 22997281, 68128438, 54663246, 64098930, 14723410, 26734892, 15015906, 24058273, 70596786, 53648154, 30891921, 80246713, 24619760, 176257, 79738755, 48360198, 77377183, 45990383, 66116458, 86301513, 5640302, 95395112, 92867155, 60581278, 82886381, 61263068, 37675718, 23134715, 94360702, 72793444, 58180653, 34698463, 76703609, 51507425, 79880247, 84293052, 73617245, 84002370, 37739481, 35192533, 49597667, 37560164, 34295794, 15535065, 39847321, 9188443, 48658605, 89214616, 99690194, 27187213, 47708850, 70541760, 7011964, 41442762, 77787724, 71333116, 27041967, 18131876, 2607799, 16054533, 49641577, 77898274, 52060076, 57359924, 37659250, 71920426, 74110882, 86798033, 44479073, 72238278, 45919976, 31491938, 24953498, 74441448, 40781854, 4668450, 66319530, 62496012, 34497327, 77072625, 9603598, 67513640, 56955985, 45848907, 8128637, 40686254, 96709982, 32151165, 54987042, 91255408, 5111370, 36812683, 65038678, 83368048, 32161669, 90310261, 90158785, 16405341, 26139110, 45667668, 90272749, 30139692, 29510992, 69579137, 44348328, 88698958, 86001008, 27185644, 35092039, 8099994, 28928175, 3487592, 34698428, 93270084, 36312813, 33895336, 26776922, 66903004, 91831487, 61271144, 26392416, 63628376, 37280276, 749283, 6497830, 92398073, 20885148, 10309525, 32250045, 77272628, 53393358, 35456853, 22942635, 21673260, 61373987, 70004753, 20867149, 15064655, 12571310, 75135448, 98648327, 52613508, 69255765, 30569392, 13819358, 92554120, 65275241, 61728685, 60102965, 4204661, 26275734, 17058722, 55189057, 82052050, 76671482, 51149804, 16019925, 84904436, 55615885, 2331773, 72614359, 80555751, 62936963, 1239555, 83948335, 38365584, 73124510, 23110625, 45428665, 6793819, 9175338, 96906410, 59177718, 5073754, 74724075, 81172706, 71522968, 36808486, 79136082, 99965001, 17146629, 34044787, 33237508, 14045383, 90654836, 82532312, 33935899, 49271185, 19272365, 85571389, 95726235, 83083131, 67030811, 99226875, 57658654, 36930650, 97783876, 83269727, 68939068, 45996863, 36396314, 6986898, 76330843, 4064751, 46540998, 10597197, 66832478, 4985896, 22721500, 82897371, 68694897, 99125126, 19457589, 74614639, 82339363, 44481640, 15148031, 93057697, 83210802, 38645117, 99549373, 22405120, 4095116, 97940276, 45617087, 98462867, 96852321, 88251446, 53084256, 63372756, 62357986, 73235980, 9623492, 45237957, 66250369, 28796059, 6038457, 74137926, 85023028, 60106217, 21919959, 13862149, 49236559, 51472275, 36189527, 93515664, 59405277, 15163258, 30764367, 6525948, 69848388, 17857111, 7919588, 20681921, 55770687, 20486294, 82178706, 44844121, 62490109, 15902805, 68110330, 86460488, 83789391, 99861373, 59197747, 24826575, 64602895, 68000591, 78561158, 43045786, 42644903, 33123618, 1022677, 48088883, 30543215, 36685023, 85117092, 42307449, 99729357, 66322959, 36505482, 415901, 72738685, 29635537, 7685448, 42073124, 77694700, 18663507, 2204165, 37620363, 18783702, 99297164, 39373729, 22113133, 38022834, 52204879, 31126490, 36135, 81774825, 82651278, 90004325, 20122224, 24915585, 40781449, 28851716, 92692978, 66428795, 92033260, 14363867, 29401781, 9257405, 72373496, 55247455, 60955663, 98948034, 3294781, 59501487, 5822202, 77187825, 54606384, 36780454, 89699445, 31727379, 89078848, 84127901, 14349098, 29819940, 5832946, 71657078, 40534591, 54058788, 10453030, 59318837, 94076128, 97057187, 67793644, 50806615, 669105, 14220886, 6521313, 90090964, 92353856, 8696647, 9398733, 89637706, 62452034, 44627776, 95251277, 44842615, 60176618, 92787493, 20642888, 83533741, 2891150, 75543508, 59371804, 45790169, 49598724, 33336148, 97281847, 57241521, 58208470, 17957593, 17894977, 87160386, 78785507, 91141395, 6221471, 54762643, 88653118, 44664587, 95581843, 28787861, 30998561, 82427263, 91802888, 26114953, 9259676, 20568363, 68816413, 95010552, 88444207, 4199704, 18466635, 90061527, 32159704, 24591705, 81853704, 45794415, 78884452, 8807940, 30395570, 31733363, 40027975, 76170907, 5482538, 67031644, 30090481, 61928316, 3235882, 75777973, 47887470, 29959549, 77312810, 82979980, 37183543, 93933709, 7300047, 13468268, 30653863, 62740044, 4798568, 61815223, 32590267, 92692283, 92998591, 22766820, 29029316, 70367851, 71469330, 93562257, 73109997, 23740167, 4508700, 71316369, 16971929, 32058615, 27625735, 72732205, 24314885, 7687278, 65074535, 4069912, 56153345, 29994197, 73786944, 51466049, 14731700, 73021291, 72357096, 20140249, 61982238, 70420215, 79322415, 33201905, 50567636, 12664567, 57803235, 17386996, 17081350, 99515901, 22129328, 47738185, 44983451, 72358170, 526217, 71083565, 54517921, 68824981, 61859581, 40677414, 13470059, 94516935, 14947650, 3509435, 19939935, 3233084, 90013093, 40865610, 75128745, 48395186, 7104732, 3088684, 54199160, 42782652, 508198, 2208785, 96420429, 57248122, 7229550, 89419466, 42237907, 30366150, 84406788, 53802686, 34667286, 1197320, 21993752, 62232211, 40984766, 12416768, 64157906, 9575954, 10649306, 11543098, 41380093, 69786756, 6808825, 68644627, 59329511, 91664334, 78766358, 7182642, 16424199, 30811010, 79806380, 72777973, 18806856, 8733068, 43152977, 20645197, 86242799, 77284619, 11757872, 26744917, 26292919, 17385531, 30771409, 96318094, 45995325, 16380211, 34432810, 69697787, 65017137, 45049308, 23848565, 19376156, 17764950, 85242963, 78218845, 25842078, 38510840, 72019362, 49882705, 8055981, 66667729, 62115552, 3183975, 78602717, 47893286, 34493392, 34236719, 16595436, 53547802, 65338021, 33061250, 30787683, 8729146, 65880522, 88130087, 62552524, 569864, 16097038, 3233569, 54427233, 80014588, 78909561, 85116755, 7517032, 87720882, 37435892, 67474219, 66663942, 24168411, 39986008, 1250437, 99224392, 21397057, 34946859, 99971982, 72278539, 321665, 45075471, 39553046, 99917599, 27411561, 33797252, 26863229, 26397786, 17976208, 62430984, 94539824, 44784505, 63059024, 92541302, 32426752, 18504197, 97379790, 74452589, 10366309, 58224549, 96735716, 91990218, 17365188, 38008118, 85695762, 73222868, 54263880, 87598888, 7423788, 44847298, 37501808, 16684829, 45381876, 65851721, 48893685, 18833224, 9058407, 17068582, 16445503, 11923835, 1936762, 97011160, 68875490, 7955293, 83747892, 63015256, 88904910, 14326617, 36933038, 44915235, 4978543, 38658347 +48360198, 62496012, 17081350, 19376156, 92803766, 92787493, 54014062, 10358899, 30463802, 41245325, 90310261, 33797252, 28928175, 62490109, 15535065, 44245960, 33699435, 95957797, 37659250, 99021067, 24953498, 34946859, 14220886, 90090964, 9599614, 69136837, 94516935, 84840549, 11923835, 19898053, 75407347, 8129978, 60581278, 17016156, 91957544, 77694700, 50007421, 90004325, 38256849, 17037369, 26744917, 76330843, 94595668, 97057187, 45306070, 16387575, 68824981, 5267545, 69579137, 75128745, 3487592, 36312813, 66045587, 51590803, 17365188, 14626618, 61712234, 65715134, 38658347, 62232211, 55850790, 77301523, 40197395, 63506281, 54427233, 874791, 61741594, 48892201, 63967300, 5073754, 44889423, 70367851, 4508700, 58751351, 38022834, 92692978, 7687278, 8733068, 74441448, 20002147, 86022504, 33201905, 12664567, 26292919, 89078848, 40534591, 37891451, 66832478, 2917920, 9398733, 43501211, 93566986, 10366309, 91727510, 22435353, 18001617, 33431960, 80251430, 47361209, 88698958, 83133790, 72274002, 58749, 88653118, 58224549, 3088684, 95581843, 30694952, 33628349, 47792865, 97011160, 32159704, 24058273, 6153406, 55770687, 44844121, 68110330, 78589145, 68000591, 47213183, 30569392, 98739783, 62051033, 47887470, 37675718, 93933709, 89804152, 13468268, 80014588, 73617245, 46851987, 44847298, 80555751, 61815223, 38365584, 98371444, 69786756, 42073124, 12303248, 22766820, 42692881, 66885828, 63152504, 70541760, 56461322, 17146629, 34044787, 33237508, 5970607, 24915585, 28851716, 4119747, 49271185, 87720882, 23194618, 72373496, 18806856, 56484900, 78300864, 67474219, 67030811, 59501487, 34497327, 33565483, 56955985, 8128637, 36396314, 4064751, 21397057, 44060493, 10453030, 32151165, 40152546, 247198, 67793644, 61897582, 91255408, 2717150, 92353856, 57020481, 45049308, 74614639, 82339363, 38645117, 72725103, 48673079, 26102057, 27411561, 29510992, 17539625, 44348328, 38510840, 76315420, 8099994, 26998766, 99658235, 48395186, 93270084, 66250369, 49328608, 23569917, 68816413, 55470718, 21919959, 51472275, 36189527, 93515664, 20885148, 27325428, 81677380, 22997281, 89996536, 84166196, 6525948, 30395570, 61373987, 40027975, 8729146, 12571310, 75135448, 65880522, 22108665, 69255765, 39801351, 29932657, 86543538, 84293052, 84002370, 62740044, 72614359, 78909561, 1239555, 32590267, 72738685, 7685448, 71300104, 89214616, 32426752, 81172706, 37620363, 73109997, 6808825, 67281495, 77787724, 2607799, 96726697, 16424199, 68316156, 52060076, 30811010, 72732205, 91938191, 86798033, 50668599, 29401781, 94711842, 31491938, 14731700, 77284619, 72357096, 62762857, 55669657, 83269727, 11581181, 50702367, 96709982, 22129328, 17727650, 48774913, 30771409, 7502255, 73031054, 168541, 48893685, 68694897, 56515456, 56531125, 62452034, 36812683, 19457589, 36753250, 61960400, 71083565, 31904591, 93057697, 23848565, 36139942, 8115266, 23432750, 41092172, 22405120, 66137019, 97561745, 21001913, 33304202, 72019362, 78785507, 68102437, 65271999, 66611759, 54199160, 30998561, 96735716, 42782652, 6038457, 98943869, 66903004, 9860968, 59981773, 83150534, 36468541, 1788101, 36580610, 49236559, 6497830, 59405277, 65454636, 90061527, 20836893, 67227442, 89499542, 69605283, 21993752, 21070110, 53648154, 81898046, 22942635, 38061439, 54263880, 15064655, 55753905, 19486173, 64602895, 67031644, 3235882, 63059024, 95395112, 20765474, 38341669, 569864, 89217461, 37183543, 42967683, 58180653, 36685023, 11543098, 84904436, 2331773, 4798568, 7955293, 19101477, 415901, 57163802, 92604458, 54868730, 27187213, 7646095, 79136082, 83747892, 18783702, 86118021, 7011964, 25636669, 8791066, 89811711, 29466406, 27041967, 59910624, 7517032, 61859143, 54232247, 33935899, 65047700, 50188404, 56153345, 72777973, 29994197, 6819644, 73021291, 11757872, 74452589, 68939068, 14349098, 5832946, 79821897, 96318094, 6871053, 34432810, 99971982, 97641116, 86821229, 72278539, 74743862, 11365791, 69697787, 321665, 89637706, 94911072, 99125126, 91281584, 526217, 95251277, 45075471, 26664538, 82327024, 84349107, 59109400, 83651211, 99549373, 20642888, 16405341, 9058407, 4095116, 79942022, 4434662, 49598724, 45667668, 97281847, 44473167, 8634541, 21533347, 58208470, 92215320, 84684495, 98462867, 96852321, 19939935, 17957593, 71965942, 35092039, 17068582, 43357947, 44664587, 68702632, 3880712, 91802888, 78602717, 20568363, 70782102, 61271144, 88444207, 26397786, 91990218, 13862149, 84406788, 63628376, 22200378, 4978543, 9860195, 15948937, 18466635, 34493392, 62430984, 94539824, 64098930, 26734892, 24591705, 3633375, 32699744, 65338021, 45794415, 78884452, 73222868, 40984766, 33061250, 3773993, 30787683, 1569515, 70004753, 83789391, 37224844, 24826575, 5482538, 62552524, 92071990, 66116458, 86301513, 10649306, 13819358, 77312810, 6111563, 43933006, 39171895, 26275734, 42199455, 82052050, 8913721, 99729357, 30653863, 65081429, 36845587, 36505482, 75986488, 45428665, 29188588, 16099750, 18411915, 70372191, 59177718, 43322743, 47708850, 7066775, 41442762, 71316369, 43376279, 74357852, 88047921, 85711894, 41481685, 36135, 32058615, 57359924, 74110882, 27665211, 24314885, 66428795, 4069912, 72238278, 40356877, 12024238, 37435892, 20645197, 1431742, 86242799, 40781854, 4668450, 3294781, 824872, 66663942, 79322415, 37166608, 84127901, 17386996, 83155442, 82726008, 46540998, 65851721, 50806615, 4985896, 44550764, 8696647, 5111370, 44627776, 39553046, 79191827, 99917599, 40677414, 90158785, 51588015, 76540481, 70800879, 35996293, 75543508, 80316608, 45790169, 93053405, 26493119, 13173644, 81855445, 57961282, 75820087, 69641513, 17894977, 93359396, 88251446, 28550822, 53084256, 91141395, 6221471, 73235980, 45237957, 66667729, 28796059, 26776922, 96420429, 60430369, 55602660, 7229550, 91831487, 85023028, 14093520, 14326617, 36933038, 67084351, 42237907, 57393458, 92398073, 77272628, 59614746, 54663246, 14723410, 69848388, 81853704, 73392814, 20486294, 35456853, 82178706, 80246713, 15902805, 24619760, 44177011, 87598888, 30090481, 98648327, 45990383, 9575954, 44784505, 52613508, 92554120, 31161687, 92867155, 62803858, 23134715, 16097038, 60102965, 85117092, 76703609, 26063929, 51507425, 79880247, 55615885, 92541302, 9175338, 48658605, 96906410, 112651, 2204165, 71469330, 36808486, 51464002, 23740167, 56473732, 47090124, 68644627, 59329511, 52204879, 91664334, 14045383, 49641577, 82651278, 4091162, 20122224, 90654836, 69355476, 79806380, 1204161, 19272365, 92033260, 44479073, 68204242, 9257405, 45919976, 16567550, 1375023, 99524975, 43152977, 83083131, 98948034, 30218878, 20140249, 96193415, 77072625, 9603598, 97783876, 54606384, 99333375, 37192445, 15536795, 77413012, 40686254, 1250437, 99224392, 82779622, 29819940, 53888755, 67124282, 62693428, 65017137, 9886593, 61141874, 29065964, 65038678, 43506672, 44481640, 24733232, 91240048, 83210802, 17764950, 83533741, 14947650, 97940276, 2891150, 26139110, 59371804, 68041839, 33336148, 30139692, 94090109, 78218845, 19891772, 3233084, 87160386, 61623915, 10961421, 54762643, 62357986, 508198, 2208785, 74137926, 81805959, 78549759, 51315460, 75052463, 60106217, 89419466, 42947632, 95010552, 37280276, 64848072, 9829782, 70036438, 4199704, 28734791, 10309525, 34667286, 72495719, 15163258, 30764367, 7919588, 20681921, 53393358, 70596786, 85695762, 30891921, 8807940, 38009615, 86460488, 99604946, 176257, 31733363, 99861373, 93790285, 59197747, 64157906, 61928316, 43045786, 68875490, 42644903, 61728685, 33123618, 21289531, 29959549, 61263068, 33553959, 82979980, 94360702, 4204661, 3233569, 53666583, 17058722, 42307449, 48260151, 66322959, 62936963, 35192533, 77620120, 85116755, 51047803, 12348118, 29834512, 18131876, 78766358, 16054533, 81774825, 71920426, 82532312, 65074535, 16684829, 73786944, 83302115, 60393039, 47298834, 64055960, 60955663, 5822202, 99226875, 56424103, 62428472, 31727379, 45848907, 57240218, 6986898, 99515901, 59318837, 54987042, 44983451, 72358170, 88904910, 77300457, 32274392, 92530431, 15148031, 61859581, 60176618, 13470059, 4787945, 35512853, 4806458, 45407418, 85242963, 45617087, 29516592, 57241521, 3509435, 86001008, 90013093, 16445503, 49882705, 75153252, 34698428, 63372756, 28787861, 79657802, 53842979, 79922758, 3183975, 1936762, 81274566, 75229462, 26392416, 30366150, 47893286, 17976208, 6157724, 11161731, 53802686, 32250045, 34236719, 1197320, 15015906, 16595436, 12416768, 76434144, 13348726, 7423788, 75777973, 73168565, 1022677, 7300047, 48088883, 98653983, 30543215, 76671482, 66271566, 34698463, 37739481, 49597667, 73124510, 39847321, 29635537, 9188443, 99690194, 92998591, 33249630, 71522968, 18663507, 6505939, 18504197, 55960386, 99965001, 4099191, 39373729, 22113133, 16971929, 7182642, 97379790, 77898274, 27625735, 63015256, 76960303, 85571389, 95726235, 51466049, 55247455, 66319530, 61982238, 70420215, 57658654, 36930650, 41092102, 24168411, 89699445, 50567636, 53632373, 57803235, 54058788, 45995325, 10597197, 6521313, 18833224, 54517921, 83368048, 90272749, 26863229, 40865610, 7104732, 4515343, 95290172, 749283, 48675329, 44915235, 67451935, 37957788, 38008118, 98130363, 53547802, 21673260, 89046466, 70727211, 79738755, 77377183, 5640302, 82886381, 72793444, 55189057, 51149804, 16019925, 26507214, 41380093, 92692283, 34295794, 37501808, 93562257, 31126490, 18699206, 39201414, 77187825, 87710366, 67513640, 17385531, 52261574, 30163921, 669105, 22721500, 82897371, 44846932, 76825057, 8373289, 13231279, 51715482, 22450468, 8055981, 82427263, 26114953, 38494874, 8651647, 17857111, 22879907, 64087743, 88130087, 78561158, 83948335, 23110625, 6793819, 51426311, 99297164, 55143724, 40781449, 10264691, 36780454, 84187166, 45381876, 45996863, 46870723, 47738185, 71657078, 16380211, 44842615, 27185644, 33895336, 62115552, 9259676, 68128438, 76170907, 52293995, 37560164, 29029316, 14363867, 39986008, 94076128, 32161669, 90457870, 9623492, 65275241, 74724075, 25842078, 15075176, 20867149, 71333116, 57248122 +95726235, 99524975, 44481640, 83651211, 64848072, 37192445, 69641513, 40865610, 36933038, 70036438, 32159704, 61928316, 77072625, 52261574, 2917920, 5267545, 78785507, 66667729, 53393358, 40027975, 64602895, 29932657, 48088883, 85117092, 54427233, 15535065, 16099750, 58751351, 22113133, 38022834, 78766358, 36780454, 38645117, 45407418, 17764950, 45667668, 90457870, 54762643, 68702632, 42947632, 95010552, 14723410, 53547802, 12416768, 5482538, 63059024, 39801351, 61263068, 16097038, 7300047, 55189057, 51149804, 76703609, 13468268, 92692283, 96906410, 12303248, 18504197, 41442762, 29401781, 73786944, 87710366, 21397057, 30771409, 62693428, 62452034, 19457589, 54517921, 23848565, 83210802, 79942022, 75153252, 53084256, 44664587, 36312813, 96735716, 51315460, 55470718, 6157724, 30891921, 68110330, 68000591, 62552524, 92867155, 72793444, 2331773, 415901, 32590267, 27187213, 37620363, 36808486, 51426311, 18131876, 49641577, 81774825, 66428795, 14363867, 4069912, 72238278, 9257405, 16567550, 56484900, 96193415, 36930650, 56955985, 8128637, 6986898, 32151165, 94076128, 53888755, 22721500, 5111370, 88904910, 526217, 26664538, 44842615, 93057697, 59109400, 91727510, 33797252, 94090109, 13231279, 44348328, 84684495, 16445503, 28928175, 66250369, 95581843, 1936762, 37280276, 94539824, 17857111, 24058273, 70596786, 89499542, 53648154, 15902805, 54263880, 76434144, 77377183, 22108665, 8129978, 42644903, 37675718, 77312810, 93933709, 94360702, 26275734, 66271566, 51507425, 79880247, 26507214, 36845587, 7955293, 1239555, 38365584, 85116755, 71300104, 63967300, 33249630, 70367851, 18783702, 86118021, 34044787, 2607799, 96726697, 16054533, 74110882, 18699206, 63015256, 76960303, 51466049, 60393039, 31491938, 60955663, 4668450, 6819644, 57658654, 37166608, 33201905, 11581181, 31727379, 84127901, 17081350, 17385531, 99515901, 22129328, 34946859, 40534591, 247198, 99971982, 73031054, 50806615, 72278539, 91255408, 74743862, 82897371, 8696647, 56531125, 94911072, 61141874, 65038678, 61960400, 71083565, 45075471, 92530431, 82327024, 61859581, 36139942, 91240048, 76825057, 35512853, 99549373, 48673079, 33336148, 8634541, 13173644, 19891772, 57961282, 88698958, 76315420, 26998766, 75128745, 91141395, 88653118, 45237957, 33895336, 81805959, 9259676, 68816413, 60106217, 21919959, 10358899, 49236559, 47893286, 48675329, 22997281, 84166196, 45794415, 85695762, 40984766, 86460488, 64087743, 99604946, 61373987, 70004753, 176257, 83789391, 37224844, 75135448, 64157906, 45990383, 69255765, 92554120, 62051033, 47887470, 39171895, 4204661, 98653983, 30543215, 76671482, 11543098, 84293052, 84904436, 92541302, 75986488, 9188443, 59177718, 32426752, 22766820, 42692881, 29029316, 71522968, 2204165, 71469330, 79136082, 56461322, 25636669, 39373729, 29834512, 31126490, 7182642, 14045383, 68316156, 90004325, 82532312, 19272365, 83302115, 74441448, 1431742, 3294781, 9603598, 62762857, 84187166, 45381876, 26292919, 89078848, 53632373, 82726008, 4064751, 47738185, 40152546, 66832478, 669105, 14220886, 68694897, 321665, 44846932, 91281584, 36753250, 16387575, 95251277, 43506672, 74614639, 79191827, 93566986, 9599614, 90310261, 8115266, 4806458, 22405120, 76540481, 83533741, 94516935, 97940276, 66137019, 59371804, 80316608, 8373289, 17539625, 3509435, 86001008, 19939935, 71965942, 38510840, 27185644, 90013093, 17894977, 84840549, 28550822, 61623915, 65271999, 66611759, 7104732, 93270084, 3088684, 79657802, 49328608, 57248122, 78602717, 38494874, 8651647, 98943869, 81274566, 75229462, 83150534, 95290172, 70782102, 61271144, 91990218, 57393458, 54014062, 4199704, 93515664, 67451935, 17976208, 77272628, 34493392, 69848388, 19898053, 55770687, 21070110, 35456853, 8807940, 38009615, 33061250, 99861373, 30090481, 9575954, 78561158, 92071990, 75407347, 5640302, 68875490, 98739783, 17016156, 29959549, 58180653, 82052050, 99729357, 26063929, 66322959, 73617245, 55615885, 4798568, 72614359, 78909561, 36505482, 19101477, 48892201, 73124510, 41380093, 72738685, 9175338, 18411915, 73109997, 6505939, 41245325, 70541760, 55960386, 50007421, 7011964, 33237508, 5970607, 95957797, 77787724, 43376279, 91664334, 85711894, 41481685, 32058615, 52060076, 24915585, 27665211, 24314885, 49271185, 86798033, 92033260, 24953498, 55247455, 20645197, 20002147, 30218878, 73021291, 99226875, 11757872, 74452589, 86022504, 68939068, 39986008, 57240218, 1250437, 29819940, 5832946, 10597197, 65851721, 168541, 97641116, 48893685, 67124282, 56515456, 9886593, 45049308, 82339363, 15148031, 84349107, 23432750, 27411561, 14947650, 75543508, 22435353, 4434662, 49598724, 18001617, 29516592, 57241521, 80251430, 21533347, 96852321, 17957593, 8099994, 49882705, 58749, 6221471, 66045587, 91802888, 26114953, 23569917, 33628349, 66903004, 89419466, 88444207, 26397786, 26392416, 30366150, 22200378, 36189527, 44915235, 37957788, 4978543, 15075176, 10309525, 81677380, 1197320, 7919588, 67227442, 3633375, 69605283, 81898046, 22942635, 21673260, 38061439, 65880522, 88130087, 13348726, 47213183, 7423788, 65275241, 55850790, 60581278, 89217461, 3233569, 40197395, 42307449, 48260151, 65081429, 80555751, 62936963, 91957544, 77620120, 34295794, 45428665, 39847321, 48658605, 69786756, 112651, 92998591, 12348118, 18663507, 37501808, 51464002, 67281495, 56473732, 47090124, 68644627, 59329511, 71333116, 16971929, 27041967, 52204879, 97379790, 16424199, 7517032, 82651278, 61859143, 40781449, 30811010, 28851716, 27625735, 54232247, 85571389, 40356877, 45919976, 94711842, 47298834, 64055960, 67474219, 98948034, 62496012, 59501487, 77187825, 55669657, 62428472, 57803235, 50702367, 40686254, 96709982, 76330843, 14349098, 46870723, 17727650, 48774913, 71657078, 54058788, 79821897, 59318837, 94595668, 30163921, 54987042, 92353856, 69697787, 89637706, 99125126, 29065964, 18833224, 83368048, 99917599, 19376156, 69136837, 4787945, 92787493, 41092172, 16405341, 4095116, 70800879, 93053405, 68041839, 26493119, 45617087, 97561745, 90272749, 44473167, 78218845, 69579137, 92215320, 98462867, 26863229, 33304202, 17068582, 22450468, 88251446, 28787861, 28796059, 54199160, 3880712, 2208785, 96420429, 55602660, 7229550, 3183975, 78549759, 13862149, 749283, 51472275, 28734791, 20885148, 34667286, 32250045, 27325428, 72495719, 17365188, 97011160, 14626618, 62430984, 89996536, 30764367, 34236719, 15015906, 73392814, 78884452, 82178706, 44844121, 80246713, 44177011, 70727211, 87598888, 31733363, 8729146, 19486173, 48360198, 44784505, 66116458, 43045786, 10649306, 20765474, 31161687, 62803858, 75777973, 77301523, 82979980, 86543538, 16019925, 874791, 80014588, 30653863, 46851987, 62740044, 44847298, 35192533, 61815223, 61741594, 49597667, 29635537, 51047803, 92604458, 99690194, 43322743, 44889423, 81172706, 6808825, 7066775, 99965001, 4099191, 33699435, 99297164, 29466406, 74357852, 77898274, 57359924, 4091162, 90654836, 69355476, 1204161, 50668599, 72373496, 29994197, 39201414, 37435892, 78300864, 86242799, 14731700, 66319530, 77284619, 20140249, 5822202, 66663942, 97783876, 38256849, 41092102, 54606384, 24168411, 83269727, 99333375, 17037369, 26744917, 67513640, 45848907, 45996863, 12664567, 77413012, 17386996, 44060493, 46540998, 44983451, 4985896, 90090964, 65017137, 77300457, 31904591, 32161669, 10366309, 51588015, 26139110, 30139692, 83133790, 35092039, 72274002, 72019362, 68102437, 99658235, 63372756, 58224549, 73235980, 60430369, 79922758, 30694952, 20568363, 75052463, 91831487, 14093520, 14326617, 63628376, 6497830, 11161731, 18466635, 51590803, 54663246, 64098930, 20836893, 24591705, 81853704, 65715134, 21993752, 62232211, 79738755, 93790285, 12571310, 55753905, 59197747, 24826575, 78589145, 98648327, 30569392, 95395112, 569864, 23134715, 42967683, 6111563, 60102965, 53666583, 89804152, 17058722, 36685023, 52293995, 84002370, 37739481, 23110625, 29188588, 44245960, 93562257, 63152504, 8791066, 89811711, 36135, 72732205, 37659250, 71920426, 99021067, 68204242, 10264691, 18806856, 67030811, 72357096, 824872, 61982238, 56424103, 34497327, 79322415, 99224392, 45995325, 37891451, 16380211, 34432810, 67793644, 86821229, 2717150, 9398733, 36812683, 92803766, 60176618, 90158785, 85242963, 33431960, 25842078, 21001913, 51715482, 93359396, 10961421, 34698428, 53842979, 26776922, 42782652, 82427263, 4515343, 6038457, 62115552, 47792865, 36468541, 1788101, 42237907, 36580610, 9829782, 9860195, 59405277, 53802686, 15948937, 68128438, 15163258, 59614746, 38008118, 6525948, 98130363, 61712234, 65338021, 73222868, 89046466, 30787683, 1569515, 20867149, 67031644, 52613508, 86301513, 13819358, 61728685, 82886381, 33123618, 21289531, 37183543, 43933006, 42199455, 34698463, 83948335, 6793819, 98371444, 7685448, 42073124, 66885828, 5073754, 77694700, 7646095, 83747892, 23740167, 17146629, 59910624, 4119747, 33935899, 44479073, 16684829, 72777973, 8733068, 1375023, 40781854, 33565483, 50567636, 82779622, 7502255, 10453030, 96318094, 6871053, 61897582, 6521313, 44550764, 57020481, 43501211, 68824981, 13470059, 72725103, 26102057, 35996293, 2891150, 45790169, 97281847, 29510992, 47361209, 81855445, 58208470, 75820087, 3233084, 87160386, 11923835, 48395186, 9623492, 30998561, 74137926, 90061527, 16595436, 6153406, 38658347, 20486294, 22879907, 62490109, 3773993, 3235882, 38341669, 33553959, 8913721, 63506281, 30463802, 37560164, 57163802, 70372191, 74724075, 47708850, 88047921, 20122224, 65074535, 50188404, 56153345, 87720882, 12024238, 43152977, 83083131, 97057187, 45306070, 72358170, 39553046, 20642888, 9058407, 3487592, 62357986, 85023028, 67084351, 26734892, 32699744, 30395570, 15064655, 1022677, 89214616, 4508700, 71316369, 79806380, 91938191, 7687278, 89699445, 83155442, 11365791, 32274392, 24733232, 43357947, 8055981, 84406788, 92398073, 76170907, 73168565, 54868730, 55143724, 92692978, 65047700, 70420215, 15536795, 36396314, 44627776, 9860968, 20681921, 508198, 59981773, 65454636, 24619760, 23194618, 40677414 +75986488, 74137926, 43322743, 4099191, 7517032, 92787493, 19101477, 25636669, 50668599, 7919588, 52613508, 38341669, 72614359, 29029316, 16971929, 16424199, 1431742, 8128637, 17081350, 5832946, 5111370, 77300457, 78218845, 40865610, 88653118, 54199160, 59614746, 176257, 75135448, 82052050, 91957544, 27187213, 63152504, 67281495, 29466406, 76960303, 37166608, 17727650, 37891451, 69697787, 321665, 48673079, 26493119, 30139692, 19891772, 17894977, 90457870, 84840549, 88251446, 66667729, 36580610, 15948937, 65715134, 24058273, 64602895, 8129978, 63059024, 39171895, 98653983, 62740044, 77620120, 89214616, 34044787, 59329511, 83302115, 94711842, 62762857, 82726008, 21397057, 34432810, 30163921, 66832478, 89637706, 94911072, 29065964, 57020481, 45075471, 5267545, 84349107, 38645117, 83651211, 4787945, 35512853, 66137019, 22435353, 92215320, 63372756, 79922758, 78549759, 20568363, 59405277, 73222868, 33061250, 5482538, 92554120, 65275241, 62803858, 1022677, 48260151, 44847298, 41380093, 72738685, 71300104, 44889423, 41442762, 58751351, 71333116, 29834512, 18131876, 88047921, 27625735, 66428795, 16684829, 29994197, 1375023, 37435892, 83083131, 38256849, 87710366, 37192445, 6986898, 52261574, 59318837, 94595668, 67793644, 97641116, 53888755, 669105, 11365791, 36753250, 26664538, 93057697, 60176618, 76825057, 13470059, 94516935, 91727510, 80316608, 97281847, 75820087, 93359396, 49882705, 99658235, 58224549, 44664587, 93270084, 79657802, 66045587, 60430369, 57248122, 38494874, 66903004, 1788101, 13862149, 51472275, 6497830, 93515664, 28734791, 20885148, 54663246, 78884452, 55770687, 20486294, 8807940, 3773993, 87598888, 76170907, 19486173, 65880522, 39801351, 20765474, 73168565, 4204661, 53666583, 85117092, 34698463, 76703609, 874791, 73617245, 30463802, 48892201, 32590267, 92692283, 29188588, 48658605, 70372191, 32426752, 112651, 2204165, 7646095, 36808486, 33237508, 85711894, 57359924, 4091162, 30811010, 4119747, 7687278, 14363867, 72777973, 18806856, 24953498, 20645197, 74441448, 3294781, 77187825, 33565483, 67513640, 12664567, 77413012, 84127901, 50702367, 40686254, 17385531, 1250437, 99224392, 71657078, 40534591, 45995325, 61897582, 54987042, 91255408, 82897371, 68694897, 2917920, 62693428, 19457589, 16387575, 61960400, 54517921, 79191827, 93566986, 44481640, 23848565, 36139942, 90310261, 23432750, 4806458, 22405120, 45790169, 18001617, 97561745, 8634541, 47361209, 21533347, 3509435, 27185644, 76315420, 28928175, 66611759, 3088684, 95581843, 59981773, 42947632, 55470718, 30366150, 4199704, 34667286, 77272628, 84166196, 34236719, 6525948, 26734892, 81853704, 16595436, 53547802, 38658347, 44844121, 80246713, 38061439, 30395570, 24619760, 61373987, 1569515, 93790285, 40027975, 12571310, 76434144, 68000591, 69255765, 92867155, 37675718, 23134715, 86543538, 60102965, 48088883, 40197395, 30543215, 36685023, 76671482, 30653863, 36505482, 23110625, 15535065, 85116755, 16099750, 92998591, 33249630, 77694700, 47708850, 71469330, 37620363, 79136082, 7066775, 56461322, 47090124, 17146629, 39373729, 38022834, 43376279, 74357852, 59910624, 61859143, 37659250, 54232247, 49271185, 1204161, 92033260, 4069912, 29401781, 72238278, 72373496, 85571389, 39201414, 86242799, 20002147, 66663942, 56424103, 97783876, 86022504, 84187166, 56955985, 53632373, 17386996, 96709982, 46870723, 47738185, 34946859, 86821229, 74743862, 62452034, 65017137, 44846932, 61141874, 44627776, 36812683, 526217, 43501211, 83368048, 31904591, 32161669, 9599614, 9058407, 76540481, 79942022, 35996293, 26139110, 33431960, 94090109, 58208470, 57961282, 44348328, 72274002, 16445503, 28550822, 58749, 10961421, 62357986, 508198, 82427263, 91802888, 55602660, 8651647, 9860968, 75229462, 95290172, 36933038, 10358899, 37280276, 22200378, 749283, 67451935, 15075176, 92398073, 72495719, 68128438, 62430984, 89996536, 38008118, 67227442, 6153406, 19898053, 30891921, 64087743, 48360198, 30090481, 92071990, 31161687, 62051033, 55850790, 29932657, 33553959, 42967683, 72793444, 42199455, 80014588, 84293052, 84904436, 84002370, 73124510, 29635537, 57163802, 7685448, 51047803, 92604458, 96906410, 54868730, 99690194, 69786756, 22766820, 41245325, 86118021, 7011964, 8791066, 2607799, 78766358, 81774825, 82532312, 27665211, 18699206, 44479073, 16567550, 56484900, 60955663, 40781854, 67030811, 77284619, 20140249, 34497327, 11757872, 17037369, 50567636, 45848907, 57240218, 96318094, 247198, 94076128, 16380211, 50806615, 44983451, 22721500, 2717150, 9398733, 99125126, 9886593, 88904910, 95251277, 32274392, 15148031, 10366309, 40677414, 59109400, 72725103, 45407418, 93053405, 45617087, 44473167, 17539625, 69641513, 84684495, 96852321, 86001008, 19939935, 3233084, 38510840, 21001913, 8099994, 22450468, 87160386, 78785507, 75153252, 3487592, 65271999, 6221471, 54762643, 9623492, 66250369, 36312813, 28787861, 53842979, 26776922, 96735716, 2208785, 62115552, 81805959, 26114953, 33628349, 95010552, 36468541, 70782102, 26397786, 26392416, 67084351, 54014062, 84406788, 63628376, 49236559, 36189527, 44915235, 4978543, 6157724, 65454636, 53802686, 81677380, 34493392, 14723410, 1197320, 24591705, 70596786, 73392814, 69605283, 21070110, 62232211, 81898046, 21673260, 38009615, 54263880, 89046466, 30787683, 70004753, 83789391, 99861373, 37224844, 15064655, 67031644, 98648327, 22108665, 61928316, 9575954, 47213183, 75407347, 10649306, 13819358, 33123618, 17016156, 43933006, 7300047, 89804152, 58180653, 66271566, 52293995, 99729357, 26063929, 54427233, 55615885, 26507214, 80555751, 37739481, 415901, 83948335, 34295794, 45428665, 39847321, 6793819, 9175338, 12303248, 42692881, 5073754, 44245960, 70367851, 51464002, 23740167, 99965001, 18783702, 33699435, 89811711, 22113133, 68644627, 95957797, 27041967, 52204879, 41481685, 36135, 82651278, 20122224, 28851716, 91938191, 9257405, 73786944, 14731700, 66319530, 9603598, 74452589, 55669657, 54606384, 24168411, 36780454, 33201905, 89699445, 83155442, 76330843, 48774913, 44060493, 29819940, 46540998, 6871053, 10597197, 99971982, 168541, 72278539, 56531125, 72358170, 91281584, 45049308, 43506672, 24733232, 61859581, 19376156, 91240048, 92803766, 41092172, 4095116, 97940276, 33797252, 49598724, 90272749, 57241521, 80251430, 33304202, 43357947, 8055981, 45237957, 28796059, 3880712, 4515343, 7229550, 75052463, 98943869, 91831487, 85023028, 68816413, 89419466, 83150534, 21919959, 88444207, 57393458, 64848072, 48675329, 70036438, 9860195, 27325428, 14626618, 64098930, 69848388, 98130363, 3633375, 65338021, 89499542, 53648154, 22879907, 15902805, 40984766, 12416768, 99604946, 20867149, 55753905, 13348726, 45990383, 3235882, 78561158, 86301513, 7423788, 43045786, 68875490, 60581278, 89217461, 37183543, 6111563, 26275734, 51149804, 42307449, 63506281, 13468268, 51507425, 79880247, 46851987, 36845587, 78909561, 61741594, 1239555, 92541302, 18411915, 59177718, 66885828, 74724075, 81172706, 37501808, 73109997, 83747892, 50007421, 99297164, 77787724, 55143724, 49641577, 69355476, 71920426, 65074535, 63015256, 50188404, 56153345, 99021067, 95726235, 51466049, 8733068, 99524975, 4668450, 824872, 62496012, 5822202, 99226875, 70420215, 36930650, 83269727, 11581181, 68939068, 39986008, 89078848, 4064751, 7502255, 79821897, 97057187, 65851721, 4985896, 90090964, 45306070, 65038678, 39553046, 99917599, 68824981, 82327024, 69136837, 83210802, 20642888, 83133790, 51715482, 26998766, 68102437, 53084256, 34698428, 42782652, 96420429, 78602717, 51315460, 60106217, 47792865, 14326617, 91990218, 47893286, 37957788, 32250045, 90061527, 51590803, 15163258, 20836893, 20681921, 21993752, 22942635, 62490109, 86460488, 8729146, 59197747, 88130087, 77377183, 44784505, 30569392, 66116458, 98739783, 95395112, 42644903, 61728685, 82886381, 21289531, 569864, 82979980, 93933709, 17058722, 55189057, 2331773, 4798568, 35192533, 49597667, 18663507, 70541760, 51426311, 55960386, 4508700, 31126490, 14045383, 16054533, 68316156, 52060076, 90654836, 24314885, 33935899, 19272365, 68204242, 40356877, 43152977, 64055960, 30218878, 72357096, 59501487, 77072625, 57658654, 41092102, 31727379, 45996863, 15536795, 36396314, 57803235, 82779622, 30771409, 32151165, 71083565, 92530431, 44842615, 8115266, 51588015, 17764950, 16405341, 70800879, 85242963, 14947650, 59371804, 68041839, 13173644, 81855445, 13231279, 71965942, 17068582, 7104732, 33895336, 30998561, 6038457, 14093520, 61271144, 42237907, 10309525, 18466635, 97011160, 22997281, 94539824, 32159704, 17857111, 61712234, 32699744, 35456853, 82178706, 68110330, 24826575, 78589145, 62552524, 5640302, 75777973, 16097038, 3233569, 11543098, 8913721, 16019925, 65081429, 7955293, 9188443, 98371444, 42073124, 12348118, 93562257, 6505939, 56473732, 71316369, 5970607, 96726697, 97379790, 32058615, 77898274, 24915585, 72732205, 92692978, 74110882, 79806380, 86798033, 45919976, 12024238, 60393039, 55247455, 67474219, 96193415, 99333375, 26744917, 45381876, 10453030, 14220886, 6521313, 48893685, 8696647, 67124282, 18833224, 82339363, 90158785, 26102057, 27411561, 75543508, 33336148, 45667668, 29510992, 88698958, 26863229, 25842078, 72019362, 11923835, 48395186, 68702632, 49328608, 23569917, 81274566, 9829782, 17365188, 30764367, 15015906, 45794415, 85695762, 79738755, 64157906, 47887470, 61263068, 77312810, 94360702, 38365584, 71522968, 6808825, 18504197, 90004325, 40781449, 65047700, 87720882, 10264691, 47298834, 78300864, 6819644, 98948034, 73021291, 61982238, 99515901, 54058788, 40152546, 73031054, 44550764, 92353856, 56515456, 74614639, 99549373, 2891150, 29516592, 8373289, 98462867, 17957593, 35092039, 75128745, 73235980, 3183975, 9259676, 1936762, 11161731, 53393358, 31733363, 29959549, 66322959, 61815223, 91664334, 23194618, 79322415, 62428472, 26292919, 22129328, 14349098, 4434662, 69579137, 90013093, 61623915, 91141395, 44177011, 70727211, 37560164, 63967300, 7182642, 30694952, 77301523, 62936963, 31491938, 17976208, 83533741 +3294781, 66611759, 43501211, 47361209, 49328608, 6038457, 19898053, 89214616, 50188404, 72373496, 94911072, 31904591, 61859581, 91727510, 8099994, 40865610, 48395186, 8055981, 96735716, 14626618, 1197320, 21993752, 31733363, 67031644, 47213183, 61263068, 77301523, 42967683, 5970607, 27041967, 91938191, 24953498, 61982238, 57658654, 22129328, 21397057, 96318094, 2917920, 99917599, 5267545, 36139942, 19376156, 41092172, 57241521, 71965942, 38510840, 78785507, 53084256, 75128745, 91802888, 55470718, 26734892, 86460488, 48360198, 78589145, 9575954, 63059024, 43045786, 43933006, 89804152, 76703609, 13468268, 54427233, 9175338, 22766820, 37620363, 20122224, 30811010, 92033260, 51466049, 37435892, 20645197, 46870723, 94076128, 97057187, 44983451, 11365791, 19457589, 36753250, 77300457, 82327024, 85242963, 94516935, 29516592, 8634541, 44348328, 51315460, 67084351, 42237907, 13862149, 28734791, 11161731, 51590803, 24591705, 24058273, 78884452, 85695762, 20486294, 44177011, 13348726, 569864, 23134715, 34698463, 99729357, 874791, 80555751, 83948335, 91957544, 70372191, 42692881, 5073754, 70541760, 43376279, 16424199, 41481685, 82532312, 56153345, 14731700, 36930650, 33201905, 8128637, 26292919, 32151165, 247198, 16380211, 48893685, 99125126, 71083565, 68824981, 26102057, 4434662, 45667668, 75820087, 3509435, 84684495, 21001913, 43357947, 72019362, 62357986, 9623492, 36312813, 54199160, 3183975, 26114953, 9259676, 66903004, 95290172, 26392416, 36580610, 57393458, 37280276, 20885148, 27325428, 62430984, 16595436, 38658347, 61373987, 75135448, 24826575, 76434144, 92071990, 31161687, 29932657, 47887470, 77312810, 7300047, 3233569, 98653983, 79880247, 73617245, 62740044, 36845587, 38365584, 15535065, 9188443, 48658605, 79136082, 18783702, 50007421, 56461322, 47090124, 7011964, 89811711, 22113133, 38022834, 32058615, 92692978, 27665211, 63015256, 76960303, 68204242, 16567550, 95726235, 8733068, 99524975, 43152977, 1431742, 40781854, 20002147, 34497327, 11581181, 99333375, 33565483, 17037369, 50567636, 67513640, 57240218, 99515901, 34946859, 40534591, 79821897, 94595668, 2717150, 56515456, 18833224, 74614639, 90310261, 23432750, 4787945, 16405341, 76540481, 26139110, 90272749, 33431960, 80251430, 69579137, 96852321, 27185644, 33304202, 35092039, 28550822, 10961421, 45237957, 68702632, 79657802, 26776922, 30998561, 96420429, 55602660, 78602717, 68816413, 47792865, 36468541, 36933038, 88444207, 17976208, 9860195, 10309525, 18466635, 68128438, 34493392, 30764367, 81853704, 65715134, 6153406, 53393358, 62232211, 30891921, 80246713, 8807940, 30395570, 176257, 87598888, 62552524, 52613508, 78561158, 13819358, 17016156, 86543538, 4204661, 17058722, 82052050, 36685023, 76671482, 66271566, 55615885, 72614359, 62936963, 415901, 92541302, 39847321, 7685448, 54868730, 92998591, 12303248, 44889423, 73109997, 6505939, 67281495, 23740167, 99965001, 25636669, 17146629, 59329511, 29466406, 14045383, 72732205, 4119747, 71920426, 33935899, 49271185, 7687278, 1204161, 19272365, 99021067, 23194618, 72777973, 29994197, 83302115, 60393039, 31491938, 4668450, 6819644, 67030811, 73021291, 59501487, 77072625, 9603598, 77187825, 68939068, 15536795, 12664567, 6986898, 96709982, 76330843, 47738185, 40152546, 34432810, 168541, 50806615, 90090964, 8696647, 68694897, 45306070, 56531125, 29065964, 57020481, 45049308, 526217, 43506672, 93566986, 26664538, 59109400, 60176618, 8115266, 13470059, 35512853, 4806458, 48673079, 59371804, 80316608, 18001617, 21533347, 17068582, 76315420, 16445503, 84840549, 28928175, 75153252, 34698428, 66045587, 82427263, 23569917, 8651647, 98943869, 83150534, 54014062, 64848072, 9829782, 48675329, 4199704, 53802686, 32250045, 90061527, 17365188, 97011160, 15163258, 54663246, 98130363, 20681921, 3633375, 73392814, 55770687, 21673260, 15902805, 38009615, 70004753, 99861373, 15064655, 8729146, 64157906, 68000591, 66116458, 8129978, 7423788, 10649306, 98739783, 92867155, 61728685, 1022677, 37183543, 93933709, 6111563, 39171895, 26275734, 72793444, 30543215, 11543098, 26063929, 2331773, 36505482, 77620120, 72738685, 45428665, 29635537, 57163802, 85116755, 51047803, 96906410, 99690194, 32426752, 66885828, 12348118, 74724075, 70367851, 83747892, 55960386, 56473732, 4508700, 33237508, 77787724, 91664334, 78766358, 88047921, 16054533, 82651278, 4091162, 27625735, 79806380, 66428795, 4069912, 16684829, 50668599, 72238278, 40356877, 45919976, 96193415, 97783876, 38256849, 87710366, 83269727, 89699445, 26744917, 39986008, 84127901, 17386996, 83155442, 1250437, 14349098, 30771409, 10453030, 99971982, 61897582, 22721500, 9398733, 67124282, 5111370, 62693428, 9886593, 44627776, 32274392, 39553046, 15148031, 44842615, 83210802, 17764950, 97940276, 66137019, 33336148, 44473167, 29510992, 17539625, 58208470, 92215320, 57961282, 88698958, 17894977, 90457870, 68102437, 3487592, 58749, 88653118, 66667729, 81805959, 30694952, 60106217, 59981773, 75229462, 14326617, 30366150, 84406788, 63628376, 51472275, 37957788, 92398073, 34667286, 77272628, 81677380, 22997281, 89996536, 59614746, 34236719, 38008118, 6525948, 17857111, 7919588, 20836893, 32699744, 65338021, 45794415, 89499542, 82178706, 44844121, 12416768, 99604946, 33061250, 24619760, 30787683, 93790285, 40027975, 76170907, 88130087, 22108665, 61928316, 68875490, 92554120, 20765474, 29959549, 37675718, 94360702, 48088883, 58180653, 66322959, 84293052, 84904436, 4798568, 44847298, 78909561, 35192533, 30463802, 48892201, 73124510, 92692283, 34295794, 75986488, 71300104, 69786756, 42073124, 63967300, 7646095, 37501808, 6808825, 86118021, 58751351, 68644627, 18131876, 74357852, 81774825, 61859143, 37659250, 86798033, 14363867, 85571389, 39201414, 94711842, 55247455, 74441448, 78300864, 86242799, 66319530, 98948034, 66663942, 62762857, 45996863, 52261574, 48774913, 82779622, 5832946, 6871053, 67793644, 97641116, 669105, 91255408, 4985896, 74743862, 92353856, 72358170, 88904910, 95251277, 65038678, 44481640, 24733232, 32161669, 93057697, 10366309, 84349107, 69136837, 76825057, 51588015, 99549373, 45407418, 92787493, 20642888, 83533741, 2891150, 22435353, 49598724, 68041839, 97281847, 8373289, 94090109, 19891772, 98462867, 3233084, 83133790, 93359396, 49882705, 88251446, 99658235, 61623915, 95581843, 508198, 2208785, 4515343, 60430369, 79922758, 7229550, 74137926, 20568363, 1936762, 91831487, 21919959, 1788101, 70036438, 36189527, 6497830, 6157724, 65454636, 94539824, 61712234, 67227442, 53547802, 73222868, 68110330, 38061439, 3773993, 89046466, 83789391, 70727211, 39801351, 42644903, 65275241, 62051033, 62803858, 75777973, 73168565, 82886381, 89217461, 85117092, 42307449, 51507425, 80014588, 30653863, 65081429, 19101477, 1239555, 32590267, 29188588, 6793819, 92604458, 59177718, 112651, 77694700, 71522968, 51464002, 33699435, 39373729, 95957797, 71333116, 52204879, 96726697, 49641577, 77898274, 74110882, 24314885, 18699206, 47298834, 1375023, 30218878, 20140249, 62496012, 99226875, 70420215, 54606384, 56955985, 50702367, 40686254, 17385531, 4064751, 17727650, 44060493, 46540998, 54058788, 73031054, 30163921, 66832478, 72278539, 14220886, 44550764, 82897371, 16387575, 82339363, 40677414, 90158785, 83651211, 22405120, 35996293, 75543508, 30139692, 13173644, 13231279, 86001008, 63372756, 93270084, 66250369, 28787861, 28796059, 42782652, 62115552, 75052463, 38494874, 9860968, 85023028, 42947632, 14093520, 70782102, 61271144, 26397786, 10358899, 44915235, 15075176, 59405277, 32159704, 84166196, 64098930, 14723410, 69848388, 22942635, 40984766, 54263880, 55753905, 59197747, 30090481, 98648327, 77377183, 45990383, 75407347, 95395112, 60581278, 82979980, 42199455, 55189057, 40197395, 51149804, 48260151, 16019925, 84002370, 46851987, 26507214, 49597667, 23110625, 18411915, 98371444, 43322743, 27187213, 47708850, 81172706, 2204165, 36808486, 34044787, 2607799, 31126490, 7182642, 55143724, 85711894, 68316156, 7517032, 90004325, 69355476, 10264691, 67474219, 77284619, 5822202, 56424103, 79322415, 31727379, 84187166, 45848907, 53632373, 59318837, 37891451, 69697787, 89637706, 65017137, 54517921, 45075471, 83368048, 79191827, 92530431, 9599614, 23848565, 91240048, 92803766, 72725103, 9058407, 4095116, 79942022, 14947650, 78218845, 81855445, 19939935, 17957593, 90013093, 72274002, 87160386, 11923835, 65271999, 6221471, 7104732, 58224549, 73235980, 44664587, 3088684, 53842979, 57248122, 78549759, 89419466, 91990218, 49236559, 67451935, 72495719, 15015906, 70596786, 69605283, 21070110, 22879907, 62490109, 1569515, 79738755, 37224844, 65880522, 38341669, 55850790, 33123618, 33553959, 60102965, 8913721, 63506281, 7955293, 33249630, 18663507, 63152504, 51426311, 41442762, 71316369, 36135, 52060076, 57359924, 40781449, 90654836, 28851716, 65074535, 29401781, 73786944, 824872, 55669657, 41092102, 86022504, 77413012, 57803235, 17081350, 71657078, 45995325, 65851721, 53888755, 54987042, 86821229, 321665, 44846932, 61141874, 36812683, 61960400, 38645117, 93053405, 33797252, 26863229, 25842078, 51715482, 22450468, 91141395, 54762643, 81274566, 95010552, 22200378, 47893286, 15948937, 35456853, 53648154, 81898046, 64087743, 20867149, 12571310, 19486173, 5482538, 44784505, 30569392, 86301513, 21289531, 53666583, 52293995, 37739481, 37560164, 44245960, 41245325, 4099191, 8791066, 29834512, 16971929, 97379790, 24915585, 65047700, 87720882, 18806856, 12024238, 56484900, 72357096, 37192445, 45381876, 10597197, 6521313, 91281584, 70800879, 45790169, 45617087, 3880712, 33895336, 4978543, 3235882, 5640302, 16097038, 61815223, 16099750, 29029316, 71469330, 93562257, 7066775, 54232247, 83083131, 24168411, 89078848, 99224392, 29819940, 7502255, 27411561, 26493119, 26998766, 33628349, 749283, 64602895, 69255765, 41380093, 18504197, 9257405, 64055960, 11757872, 74452589, 37166608, 36780454, 36396314, 82726008, 69641513, 93515664, 61741594, 59910624, 44479073, 60955663, 62452034, 97561745, 62428472, 99297164 +77413012, 73124510, 74724075, 59109400, 72495719, 13819358, 7300047, 84002370, 56531125, 29065964, 41092172, 23569917, 36580610, 73392814, 20765474, 23134715, 63506281, 35192533, 415901, 15535065, 98371444, 89214616, 54868730, 77694700, 79136082, 70420215, 57658654, 11581181, 50567636, 22129328, 6871053, 62693428, 16387575, 92530431, 23848565, 5267545, 36139942, 92803766, 60176618, 3509435, 98462867, 68702632, 26392416, 91990218, 18466635, 14626618, 89996536, 22879907, 93790285, 76170907, 13348726, 47213183, 7423788, 39801351, 31161687, 34698463, 29029316, 81172706, 17146629, 92692978, 79806380, 91938191, 65074535, 50668599, 47298834, 56484900, 4668450, 66663942, 54606384, 87710366, 37891451, 9886593, 91281584, 43501211, 61960400, 83368048, 93566986, 68824981, 69136837, 13470059, 90158785, 35996293, 94516935, 35092039, 53084256, 58749, 48395186, 42782652, 96420429, 33628349, 1936762, 91831487, 59981773, 55470718, 14326617, 10358899, 59405277, 77272628, 26734892, 88130087, 77377183, 9575954, 95395112, 73168565, 77301523, 51149804, 26063929, 72738685, 70372191, 12303248, 51464002, 55960386, 7011964, 99297164, 16054533, 7687278, 86798033, 92033260, 72373496, 29994197, 9603598, 38256849, 33201905, 99333375, 84187166, 15536795, 94076128, 97641116, 6521313, 56515456, 40677414, 51588015, 97940276, 91727510, 80316608, 93053405, 33336148, 57241521, 19891772, 84684495, 83133790, 72274002, 16445503, 72019362, 61623915, 11923835, 8055981, 95581843, 28787861, 26776922, 60430369, 62115552, 20568363, 68816413, 47792865, 13862149, 37957788, 90061527, 20836893, 3633375, 38658347, 21673260, 40984766, 86460488, 30395570, 78589145, 98648327, 42644903, 55850790, 89217461, 60102965, 26275734, 17058722, 85117092, 84293052, 62740044, 30463802, 91957544, 6793819, 57163802, 59177718, 33249630, 44245960, 47708850, 70367851, 71522968, 7646095, 33237508, 27041967, 52204879, 18131876, 37659250, 69355476, 82532312, 63015256, 76960303, 99021067, 8733068, 74441448, 6819644, 67030811, 67513640, 47738185, 21397057, 34432810, 86821229, 91255408, 4985896, 14220886, 44550764, 2917920, 65017137, 36753250, 45049308, 32274392, 44481640, 15148031, 24733232, 26664538, 82327024, 61859581, 44842615, 76825057, 92787493, 20642888, 4095116, 26139110, 68041839, 30139692, 8634541, 80251430, 13173644, 29510992, 69579137, 81855445, 21533347, 3487592, 34698428, 9623492, 66250369, 79657802, 53842979, 30998561, 3183975, 38494874, 98943869, 75229462, 83150534, 26397786, 67084351, 30366150, 63628376, 11161731, 32250045, 27325428, 51590803, 22997281, 30764367, 54663246, 24591705, 81853704, 24058273, 89046466, 87598888, 55753905, 64157906, 68000591, 45990383, 52613508, 69255765, 75407347, 30569392, 33123618, 17016156, 93933709, 72793444, 13468268, 84904436, 7955293, 48892201, 92692283, 39847321, 9175338, 9188443, 92604458, 99690194, 41245325, 70541760, 23740167, 18783702, 56473732, 8791066, 4508700, 39373729, 22113133, 59329511, 29466406, 43376279, 16424199, 32058615, 82651278, 72732205, 19272365, 50188404, 23194618, 39201414, 12024238, 31491938, 64055960, 78300864, 55669657, 24168411, 31727379, 99515901, 48774913, 5832946, 54058788, 32151165, 96318094, 16380211, 94595668, 168541, 61897582, 2717150, 48893685, 61141874, 45075471, 99917599, 10366309, 19376156, 4787945, 17764950, 22405120, 9058407, 48673079, 83533741, 66137019, 90272749, 8373289, 44348328, 69641513, 71965942, 17894977, 51715482, 84840549, 10961421, 91141395, 54762643, 44664587, 93270084, 508198, 78549759, 9259676, 9860968, 42947632, 57393458, 47893286, 51472275, 4199704, 93515664, 28734791, 15075176, 20885148, 84166196, 98130363, 65715134, 6153406, 80246713, 64087743, 30787683, 31733363, 59197747, 48360198, 65880522, 5482538, 22108665, 86301513, 43045786, 10649306, 75777973, 47887470, 1022677, 37675718, 16097038, 94360702, 48088883, 89804152, 8913721, 42307449, 99729357, 80014588, 73617245, 72614359, 36505482, 61815223, 83948335, 38365584, 45428665, 7685448, 22766820, 5073754, 93562257, 67281495, 99965001, 86118021, 56461322, 47090124, 33699435, 34044787, 71316369, 68644627, 95957797, 71333116, 2607799, 96726697, 14045383, 97379790, 49641577, 77898274, 61859143, 4091162, 20122224, 18699206, 49271185, 65047700, 14363867, 68204242, 16567550, 83302115, 94711842, 60393039, 99524975, 43152977, 37435892, 40781854, 67474219, 3294781, 824872, 59501487, 99226875, 34497327, 77187825, 89699445, 33565483, 39986008, 45848907, 26292919, 57803235, 6986898, 14349098, 79821897, 10597197, 73031054, 669105, 11365791, 68694897, 45306070, 5111370, 72358170, 57020481, 82339363, 32161669, 91240048, 83651211, 99549373, 4806458, 85242963, 27411561, 75543508, 45790169, 4434662, 49598724, 97281847, 92215320, 75820087, 96852321, 3233084, 25842078, 33304202, 68102437, 45237957, 3088684, 36312813, 28796059, 3880712, 49328608, 55602660, 74137926, 81805959, 26114953, 30694952, 51315460, 95010552, 36468541, 36933038, 1788101, 37280276, 36189527, 44915235, 67451935, 92398073, 53802686, 17365188, 68128438, 94539824, 6525948, 69848388, 7919588, 61712234, 15015906, 21993752, 21070110, 35456853, 73222868, 81898046, 62490109, 38061439, 24619760, 54263880, 44177011, 70727211, 79738755, 12571310, 75135448, 76434144, 67031644, 92071990, 66116458, 5640302, 65275241, 29932657, 569864, 86543538, 6111563, 43933006, 39171895, 4204661, 98653983, 42199455, 36685023, 76671482, 66271566, 11543098, 48260151, 54427233, 51507425, 2331773, 46851987, 80555751, 37560164, 41380093, 29635537, 51047803, 71300104, 112651, 42692881, 66885828, 37620363, 83747892, 7066775, 50007421, 4099191, 41442762, 5970607, 88047921, 55143724, 36135, 57359924, 44479073, 9257405, 73786944, 10264691, 95726235, 1375023, 86242799, 14731700, 5822202, 11757872, 62762857, 37166608, 68939068, 89078848, 53632373, 96709982, 17385531, 52261574, 82726008, 46870723, 29819940, 71657078, 40534591, 59318837, 40152546, 99971982, 97057187, 67793644, 54987042, 90090964, 67124282, 321665, 89637706, 62452034, 526217, 65038678, 18833224, 43506672, 54517921, 9599614, 93057697, 90310261, 8115266, 35512853, 16405341, 26102057, 79942022, 59371804, 45667668, 29516592, 78218845, 17539625, 58208470, 19939935, 17957593, 21001913, 27185644, 8099994, 78785507, 49882705, 88251446, 28928175, 40865610, 28550822, 75153252, 75128745, 63372756, 62357986, 75052463, 85023028, 60106217, 21919959, 54014062, 4978543, 17976208, 6157724, 65454636, 10309525, 15948937, 62430984, 15163258, 34236719, 38008118, 64098930, 67227442, 55770687, 69605283, 53648154, 22942635, 44844121, 38009615, 33061250, 1569515, 70004753, 20867149, 37224844, 30090481, 62552524, 3235882, 8129978, 98739783, 38341669, 61728685, 60581278, 21289531, 61263068, 42967683, 3233569, 58180653, 82052050, 65081429, 26507214, 4798568, 44847298, 36845587, 37739481, 19101477, 61741594, 32590267, 49597667, 23110625, 29188588, 48658605, 96906410, 69786756, 63967300, 27187213, 12348118, 18663507, 37501808, 73109997, 6505939, 6808825, 18504197, 58751351, 91664334, 59910624, 41481685, 68316156, 7517032, 52060076, 90654836, 71920426, 27665211, 24314885, 4069912, 56153345, 72777973, 45919976, 24953498, 73021291, 62496012, 56424103, 77072625, 41092102, 86022504, 26744917, 56955985, 12664567, 50702367, 17386996, 7502255, 247198, 66832478, 92353856, 8696647, 94911072, 88904910, 19457589, 39553046, 31904591, 84349107, 83210802, 23432750, 72725103, 76540481, 70800879, 33797252, 18001617, 97561745, 33431960, 47361209, 13231279, 88698958, 26863229, 86001008, 90013093, 17068582, 93359396, 6221471, 73235980, 54199160, 82427263, 4515343, 79922758, 78602717, 95290172, 61271144, 9829782, 17857111, 20681921, 65338021, 45794415, 53393358, 78884452, 30891921, 15902805, 8807940, 12416768, 3773993, 99861373, 40027975, 19486173, 64602895, 61928316, 92554120, 62803858, 82886381, 82979980, 53666583, 55189057, 52293995, 16019925, 66322959, 874791, 79880247, 62936963, 78909561, 77620120, 34295794, 75986488, 32426752, 42073124, 43322743, 44889423, 71469330, 63152504, 51426311, 89811711, 38022834, 77787724, 16971929, 74357852, 7182642, 30811010, 54232247, 33935899, 1204161, 16684829, 87720882, 40356877, 51466049, 55247455, 20645197, 83083131, 60955663, 20002147, 66319530, 77284619, 98948034, 61982238, 96193415, 36780454, 45381876, 36396314, 57240218, 40686254, 76330843, 30771409, 34946859, 10453030, 44983451, 72278539, 74743862, 44846932, 36812683, 95251277, 74614639, 26998766, 65271999, 66611759, 58224549, 33895336, 66045587, 7229550, 8651647, 66903004, 81274566, 89419466, 14093520, 88444207, 42237907, 84406788, 22200378, 749283, 9860195, 81677380, 34493392, 14723410, 70596786, 89499542, 85695762, 20486294, 61373987, 15064655, 8729146, 44784505, 78561158, 62051033, 92867155, 29959549, 77312810, 33553959, 37183543, 40197395, 1239555, 92541302, 18411915, 92998591, 24915585, 4119747, 1431742, 72357096, 36930650, 79322415, 97783876, 83269727, 17037369, 37192445, 45996863, 1250437, 4064751, 17727650, 45995325, 65851721, 9398733, 99125126, 77300457, 79191827, 45407418, 14947650, 2891150, 22435353, 94090109, 57961282, 38510840, 76315420, 90457870, 22450468, 99658235, 88653118, 7104732, 66667729, 96735716, 70782102, 49236559, 48675329, 6497830, 34667286, 97011160, 32159704, 1197320, 16595436, 19898053, 82178706, 68110330, 99604946, 63059024, 30543215, 76703609, 85116755, 25636669, 90004325, 40781449, 28851716, 74110882, 66428795, 29401781, 72238278, 85571389, 30218878, 74452589, 62428472, 8128637, 83155442, 82779622, 44060493, 46540998, 50806615, 22721500, 82897371, 44627776, 71083565, 44473167, 87160386, 2208785, 57248122, 64848072, 70036438, 32699744, 62232211, 68875490, 30653863, 2204165, 36808486, 31126490, 81774825, 27625735, 17081350, 53888755, 69697787, 26493119, 91802888, 6038457, 176257, 24826575, 55615885, 29834512, 78766358, 85711894, 18806856, 20140249, 99224392, 45617087, 43357947, 53547802, 83789391, 30163921, 59614746, 16099750, 84127901, 38645117 +65047700, 66322959, 65851721, 4985896, 44348328, 8055981, 27325428, 30395570, 31733363, 73109997, 33201905, 83269727, 73031054, 4095116, 26776922, 82886381, 43322743, 32058615, 2917920, 62693428, 72725103, 27411561, 85023028, 12416768, 86460488, 24826575, 17058722, 30543215, 52293995, 84002370, 36505482, 34295794, 7646095, 93562257, 4508700, 71920426, 68204242, 51466049, 8733068, 67474219, 22721500, 6521313, 77300457, 68824981, 8115266, 99549373, 97940276, 97561745, 80251430, 27185644, 62357986, 68702632, 95581843, 14326617, 36580610, 4199704, 11161731, 22997281, 5482538, 9575954, 78561158, 63059024, 13819358, 42644903, 6111563, 43933006, 55189057, 37739481, 61741594, 69786756, 77694700, 2204165, 37501808, 25636669, 17146629, 61859143, 91938191, 77284619, 99333375, 67513640, 54058788, 10453030, 91255408, 90090964, 45049308, 526217, 26664538, 9599614, 83210802, 4787945, 68041839, 33431960, 13231279, 3509435, 98462867, 3233084, 33304202, 84840549, 53084256, 75128745, 11923835, 3880712, 33628349, 9259676, 98943869, 67084351, 91990218, 10358899, 10309525, 68128438, 65715134, 3633375, 24058273, 53393358, 62232211, 81898046, 30891921, 40984766, 30787683, 61373987, 20867149, 12571310, 48360198, 66116458, 7423788, 98739783, 77301523, 89217461, 82979980, 60102965, 58180653, 11543098, 63506281, 84904436, 44847298, 91957544, 39847321, 42073124, 112651, 92998591, 33699435, 58751351, 71316369, 59329511, 27041967, 14045383, 16424199, 74110882, 7687278, 14363867, 50188404, 50668599, 73786944, 99524975, 37435892, 66319530, 30218878, 73021291, 824872, 62496012, 39986008, 45996863, 15536795, 26292919, 48774913, 21397057, 37891451, 34432810, 65038678, 18833224, 82339363, 10366309, 23848565, 84349107, 69136837, 4806458, 35996293, 94516935, 59371804, 90272749, 30139692, 13173644, 29510992, 69579137, 47361209, 51715482, 87160386, 78785507, 63372756, 3183975, 23569917, 30694952, 38494874, 66903004, 68816413, 59981773, 30366150, 64848072, 65454636, 92398073, 32159704, 38008118, 6525948, 69848388, 20836893, 20681921, 16595436, 99604946, 59197747, 19486173, 22108665, 44784505, 8129978, 10649306, 95395112, 20765474, 60581278, 33123618, 37675718, 77312810, 42967683, 72793444, 82052050, 79880247, 30653863, 65081429, 62740044, 35192533, 415901, 38365584, 45428665, 18411915, 32426752, 5073754, 44245960, 51464002, 83747892, 47090124, 41442762, 85711894, 68316156, 24915585, 69355476, 24314885, 33935899, 65074535, 23194618, 72777973, 72373496, 40356877, 10264691, 20645197, 78300864, 4668450, 59501487, 5822202, 66663942, 96193415, 57658654, 79322415, 77187825, 41092102, 24168411, 84187166, 12664567, 32151165, 96318094, 92353856, 45306070, 89637706, 72358170, 43501211, 61960400, 71083565, 39553046, 82327024, 61859581, 40677414, 59109400, 90158785, 17764950, 41092172, 70800879, 83533741, 2891150, 22435353, 8373289, 21533347, 92215320, 75820087, 25842078, 38510840, 17894977, 49882705, 75153252, 34698428, 65271999, 54762643, 73235980, 9623492, 36312813, 79657802, 33895336, 6038457, 62115552, 75052463, 91831487, 60106217, 89419466, 42947632, 14093520, 47792865, 55470718, 95290172, 1788101, 84406788, 47893286, 6497830, 67451935, 37957788, 4978543, 90061527, 17365188, 97011160, 54663246, 98130363, 24591705, 81853704, 67227442, 45794415, 19898053, 78884452, 85695762, 38061439, 64157906, 67031644, 13348726, 98648327, 61928316, 45990383, 75407347, 68875490, 31161687, 21289531, 29959549, 61263068, 86543538, 48088883, 26275734, 76671482, 51149804, 99729357, 54427233, 46851987, 36845587, 61815223, 19101477, 49597667, 92692283, 77620120, 72738685, 29188588, 29635537, 9188443, 48658605, 89214616, 92604458, 54868730, 12303248, 47708850, 71522968, 70541760, 7066775, 18783702, 86118021, 56473732, 7011964, 99297164, 34044787, 33237508, 95957797, 77787724, 16971929, 96726697, 97379790, 49641577, 77898274, 52060076, 90004325, 20122224, 30811010, 27625735, 72732205, 4119747, 79806380, 92033260, 4069912, 74441448, 1431742, 36930650, 55669657, 68939068, 26744917, 50567636, 45848907, 89078848, 96709982, 1250437, 29819940, 34946859, 168541, 86821229, 11365791, 48893685, 68694897, 56531125, 9886593, 61141874, 29065964, 32274392, 15148031, 92803766, 60176618, 76825057, 13470059, 23432750, 92787493, 85242963, 45790169, 93053405, 29516592, 78218845, 26863229, 17068582, 43357947, 88251446, 61623915, 10961421, 7104732, 45237957, 93270084, 3088684, 66250369, 96735716, 42782652, 96420429, 79922758, 91802888, 55602660, 26114953, 78549759, 9860968, 70782102, 88444207, 13862149, 9829782, 48675329, 36189527, 62430984, 84166196, 34236719, 64098930, 7919588, 89499542, 55770687, 69605283, 21993752, 20486294, 53648154, 62490109, 15902805, 33061250, 1569515, 79738755, 93790285, 76170907, 75135448, 55753905, 65880522, 30090481, 52613508, 92071990, 86301513, 43045786, 92867155, 62803858, 73168565, 61728685, 55850790, 47887470, 7300047, 89804152, 36685023, 8913721, 874791, 1239555, 15535065, 6793819, 57163802, 16099750, 74724075, 29029316, 18663507, 79136082, 51426311, 99965001, 8791066, 39373729, 89811711, 22113133, 38022834, 71333116, 52204879, 7182642, 55143724, 41481685, 36135, 81774825, 82651278, 92692978, 49271185, 66428795, 16684829, 56153345, 29401781, 39201414, 18806856, 86242799, 60955663, 6819644, 67030811, 3294781, 72357096, 61982238, 77072625, 62762857, 31727379, 33565483, 84127901, 57240218, 40686254, 99224392, 17727650, 30771409, 59318837, 16380211, 10597197, 66832478, 54987042, 44983451, 72278539, 44550764, 74743862, 67124282, 5111370, 321665, 88904910, 91281584, 92530431, 44481640, 24733232, 44842615, 93057697, 19376156, 51588015, 9058407, 66137019, 26139110, 80316608, 33797252, 49598724, 33336148, 26493119, 44473167, 57241521, 17539625, 58208470, 84684495, 96852321, 17957593, 35092039, 72274002, 76315420, 93359396, 68102437, 28928175, 6221471, 66045587, 30998561, 508198, 81805959, 81274566, 75229462, 42237907, 57393458, 63628376, 51472275, 44915235, 28734791, 15075176, 59405277, 34667286, 14626618, 89996536, 26734892, 17857111, 15015906, 32699744, 44844121, 54263880, 44177011, 176257, 70727211, 99861373, 8729146, 78589145, 68000591, 62552524, 3235882, 30569392, 92554120, 17016156, 1022677, 37183543, 16097038, 94360702, 40197395, 66271566, 16019925, 73617245, 62936963, 48892201, 83948335, 37560164, 92541302, 51047803, 71300104, 70372191, 63967300, 42692881, 66885828, 12348118, 81172706, 71469330, 63152504, 41245325, 56461322, 29466406, 18131876, 91664334, 74357852, 78766358, 31126490, 7517032, 40781449, 28851716, 54232247, 19272365, 63015256, 44479073, 99021067, 87720882, 72238278, 9257405, 85571389, 83302115, 47298834, 56484900, 24953498, 64055960, 83083131, 40781854, 14731700, 9603598, 74452589, 38256849, 86022504, 87710366, 36780454, 37192445, 45381876, 6986898, 17081350, 82726008, 22129328, 6871053, 45995325, 247198, 94076128, 97057187, 97641116, 53888755, 669105, 2717150, 82897371, 8696647, 56515456, 94911072, 44846932, 57020481, 36753250, 16387575, 95251277, 79191827, 32161669, 48673079, 76540481, 26102057, 45667668, 8634541, 81855445, 57961282, 88698958, 83133790, 71965942, 21001913, 22450468, 26998766, 28550822, 91141395, 66611759, 48395186, 28787861, 49328608, 82427263, 2208785, 78602717, 36468541, 22200378, 70036438, 6157724, 53802686, 15948937, 32250045, 77272628, 94539824, 30764367, 14723410, 1197320, 53547802, 65338021, 73392814, 38658347, 80246713, 8807940, 68110330, 64087743, 70004753, 87598888, 37224844, 15064655, 64602895, 88130087, 47213183, 69255765, 5640302, 65275241, 38341669, 569864, 23134715, 3233569, 42199455, 51507425, 80014588, 2331773, 80555751, 78909561, 30463802, 41380093, 9175338, 85116755, 98371444, 7685448, 96906410, 70367851, 18504197, 67281495, 55960386, 4099191, 68644627, 5970607, 2607799, 16054533, 59910624, 4091162, 82532312, 27665211, 18699206, 86798033, 45919976, 16567550, 60393039, 55247455, 98948034, 20140249, 56424103, 54606384, 89699445, 17037369, 36396314, 17386996, 52261574, 76330843, 14349098, 47738185, 44060493, 71657078, 46540998, 40152546, 67793644, 50806615, 61897582, 9398733, 44627776, 36812683, 83368048, 93566986, 90310261, 38645117, 45407418, 20642888, 14947650, 75543508, 4434662, 45617087, 19891772, 69641513, 19939935, 90013093, 99658235, 88653118, 66667729, 53842979, 60430369, 57248122, 1936762, 83150534, 95010552, 54014062, 17976208, 9860195, 81677380, 34493392, 61712234, 6153406, 73222868, 82178706, 38009615, 3773993, 76434144, 77377183, 62051033, 75777973, 29932657, 93933709, 85117092, 26063929, 84293052, 4798568, 72614359, 32590267, 59177718, 22766820, 44889423, 23740167, 50007421, 29834512, 29994197, 95726235, 12024238, 31491938, 1375023, 8128637, 77413012, 46870723, 4064751, 5832946, 79821897, 94595668, 14220886, 62452034, 43506672, 74614639, 54517921, 45075471, 5267545, 16405341, 97281847, 18001617, 94090109, 8099994, 72019362, 54199160, 4515343, 7229550, 20568363, 61271144, 26397786, 26392416, 49236559, 37280276, 93515664, 51590803, 72495719, 15163258, 21070110, 24619760, 33553959, 39171895, 4204661, 53666583, 55615885, 23110625, 99690194, 33249630, 6808825, 88047921, 90654836, 1204161, 76960303, 94711842, 20002147, 34497327, 70420215, 97783876, 53632373, 57803235, 83155442, 99515901, 82779622, 7502255, 40534591, 99971982, 99917599, 31904591, 91240048, 83651211, 22405120, 91727510, 86001008, 16445503, 40865610, 3487592, 58224549, 44664587, 51315460, 8651647, 21919959, 36933038, 18466635, 35456853, 22942635, 89046466, 40027975, 39801351, 98653983, 34698463, 76703609, 13468268, 26507214, 7955293, 75986488, 27187213, 37620363, 57359924, 37659250, 43152977, 99226875, 37166608, 11581181, 56955985, 17385531, 65017137, 99125126, 36139942, 35512853, 58749, 28796059, 20885148, 59614746, 70596786, 22879907, 21673260, 83789391, 42307449, 30163921, 69697787, 79942022, 749283, 48260151, 73124510, 36808486, 6505939, 11757872, 62428472, 50702367, 19457589, 90457870, 43376279, 74137926 +74614639, 59614746, 54232247, 69136837, 22450468, 53084256, 7423788, 6793819, 99971982, 54987042, 9398733, 77300457, 76825057, 40865610, 53842979, 74137926, 32699744, 15064655, 92692283, 7646095, 32058615, 52060076, 99515901, 96318094, 11365791, 56515456, 8115266, 45617087, 81855445, 19939935, 21001913, 72274002, 93359396, 58224549, 66045587, 51472275, 93515664, 92398073, 65338021, 86460488, 92071990, 8129978, 65275241, 26275734, 30543215, 83948335, 29188588, 85116755, 7685448, 89214616, 112651, 42692881, 47090124, 17146629, 28851716, 27625735, 12024238, 34497327, 11757872, 97783876, 11581181, 84187166, 77413012, 53632373, 45995325, 73031054, 48893685, 89637706, 62452034, 36812683, 83210802, 38645117, 23432750, 16405341, 85242963, 47361209, 88698958, 79657802, 15163258, 54663246, 98130363, 55770687, 8807940, 99604946, 3773993, 176257, 79738755, 30090481, 45990383, 3235882, 37675718, 52293995, 48260151, 26063929, 63506281, 46851987, 44847298, 36845587, 35192533, 9175338, 70372191, 42073124, 74724075, 47708850, 18663507, 36808486, 33237508, 29834512, 43376279, 88047921, 24915585, 63015256, 99021067, 5822202, 99226875, 86022504, 33565483, 56955985, 45381876, 8128637, 50702367, 17386996, 14349098, 65851721, 50806615, 61897582, 88904910, 61960400, 54517921, 45075471, 82339363, 15148031, 24733232, 26664538, 10366309, 72725103, 45407418, 9058407, 26102057, 35996293, 2891150, 18001617, 21533347, 38510840, 33304202, 75153252, 91141395, 6221471, 62357986, 73235980, 57248122, 78602717, 68816413, 75229462, 48675329, 34667286, 65715134, 3633375, 73392814, 78884452, 38061439, 70727211, 64602895, 62552524, 75407347, 68875490, 13819358, 1022677, 55189057, 16019925, 13468268, 55615885, 62740044, 61741594, 38365584, 23110625, 72738685, 63967300, 92998591, 33249630, 77694700, 44889423, 71469330, 23740167, 50007421, 4099191, 7011964, 4508700, 99297164, 58751351, 18131876, 55143724, 97379790, 57359924, 37659250, 4069912, 23194618, 47298834, 1375023, 43152977, 60955663, 40781854, 4668450, 14731700, 59501487, 96193415, 70420215, 74452589, 41092102, 37166608, 68939068, 57803235, 96709982, 4064751, 67793644, 66832478, 44983451, 14220886, 6521313, 44550764, 69697787, 2917920, 321665, 72358170, 45049308, 43501211, 32274392, 31904591, 68824981, 61859581, 19376156, 13470059, 14947650, 91727510, 45790169, 49598724, 26493119, 57961282, 84684495, 26863229, 83133790, 8099994, 90457870, 87160386, 78785507, 88251446, 99658235, 48395186, 30998561, 82427263, 9259676, 75052463, 85023028, 36580610, 54014062, 9829782, 17976208, 9860195, 6157724, 53802686, 27325428, 32159704, 89996536, 34236719, 24591705, 21993752, 38658347, 35456853, 70004753, 87598888, 31733363, 75135448, 13348726, 52613508, 78561158, 5640302, 29932657, 82886381, 33553959, 42967683, 39171895, 48088883, 42199455, 79880247, 30653863, 2331773, 26507214, 72614359, 37739481, 49597667, 41380093, 57163802, 98371444, 48658605, 22766820, 37620363, 37501808, 67281495, 56473732, 56461322, 41442762, 39373729, 95957797, 77787724, 16971929, 31126490, 7182642, 49641577, 20122224, 1204161, 95726235, 18806856, 94711842, 55247455, 20645197, 6819644, 67030811, 77284619, 73021291, 824872, 56424103, 36930650, 62428472, 26292919, 57240218, 83155442, 71657078, 6871053, 247198, 94595668, 91255408, 4985896, 2717150, 90090964, 45306070, 94911072, 44627776, 91281584, 16387575, 44481640, 82327024, 23848565, 91240048, 40677414, 4787945, 17764950, 20642888, 22405120, 70800879, 97281847, 8373289, 94090109, 90013093, 84840549, 28550822, 3487592, 10961421, 34698428, 9623492, 28787861, 28796059, 33895336, 49328608, 2208785, 62115552, 38494874, 36468541, 70782102, 61271144, 26397786, 49236559, 70036438, 4978543, 59405277, 15948937, 90061527, 22997281, 14626618, 68128438, 64098930, 6525948, 1197320, 81853704, 16595436, 45794415, 62232211, 22942635, 12416768, 93790285, 8729146, 48360198, 64157906, 77377183, 22108665, 30569392, 92554120, 61728685, 29959549, 569864, 37183543, 42307449, 76703609, 54427233, 874791, 65081429, 84002370, 4798568, 80555751, 19101477, 48892201, 15535065, 63152504, 83747892, 18504197, 71333116, 78766358, 16054533, 59910624, 36135, 68316156, 77898274, 40781449, 30811010, 90654836, 82532312, 65047700, 66428795, 73786944, 16567550, 56484900, 37435892, 83083131, 86242799, 61982238, 77072625, 38256849, 31727379, 17037369, 26744917, 12664567, 36396314, 82726008, 46870723, 48774913, 29819940, 30771409, 54058788, 40152546, 97641116, 30163921, 86821229, 72278539, 5111370, 19457589, 57020481, 79191827, 99917599, 44842615, 92803766, 27411561, 79942022, 97940276, 66137019, 80316608, 22435353, 33797252, 68041839, 90272749, 44473167, 8634541, 29510992, 69579137, 44348328, 69641513, 3233084, 17894977, 43357947, 16445503, 49882705, 61623915, 75128745, 66611759, 45237957, 44664587, 3088684, 66250369, 95581843, 54199160, 508198, 4515343, 96420429, 79922758, 78549759, 20568363, 9860968, 81274566, 89419466, 42947632, 47792865, 14326617, 21919959, 1788101, 13862149, 84406788, 47893286, 749283, 4199704, 36189527, 67451935, 18466635, 32250045, 72495719, 17365188, 97011160, 38008118, 17857111, 61712234, 15015906, 89499542, 44844121, 62490109, 80246713, 40984766, 64087743, 30395570, 89046466, 61373987, 76170907, 65880522, 5482538, 78589145, 76434144, 61928316, 69255765, 98739783, 39801351, 20765474, 62803858, 75777973, 73168565, 60581278, 21289531, 17016156, 61263068, 94360702, 72793444, 98653983, 58180653, 82052050, 40197395, 66271566, 51149804, 85117092, 34698463, 51507425, 61815223, 7955293, 415901, 1239555, 73124510, 37560164, 77620120, 92541302, 45428665, 51047803, 66885828, 27187213, 29029316, 44245960, 71522968, 73109997, 6808825, 7066775, 51426311, 33699435, 8791066, 22113133, 71316369, 59329511, 2607799, 91664334, 85711894, 81774825, 7517032, 4119747, 92692978, 74110882, 24314885, 18699206, 7687278, 86798033, 92033260, 76960303, 14363867, 44479073, 72777973, 72238278, 85571389, 45919976, 51466049, 83302115, 20002147, 30218878, 3294781, 54606384, 24168411, 89699445, 50567636, 45848907, 84127901, 6986898, 40686254, 52261574, 22129328, 44060493, 32151165, 79821897, 59318837, 53888755, 82897371, 8696647, 68694897, 67124282, 65017137, 44846932, 95251277, 65038678, 71083565, 32161669, 5267545, 90158785, 83651211, 35512853, 26139110, 4434662, 45667668, 57241521, 33431960, 78218845, 13231279, 96852321, 86001008, 25842078, 17957593, 35092039, 17068582, 76315420, 26998766, 68102437, 28928175, 11923835, 63372756, 54762643, 7104732, 93270084, 68702632, 3880712, 60430369, 81805959, 30694952, 14093520, 83150534, 26392416, 42237907, 57393458, 10358899, 22200378, 64848072, 28734791, 37957788, 11161731, 10309525, 77272628, 34493392, 30764367, 14723410, 26734892, 20836893, 69605283, 20486294, 73222868, 82178706, 22879907, 81898046, 38009615, 33061250, 99861373, 12571310, 55753905, 59197747, 86301513, 95395112, 42644903, 31161687, 62051033, 33123618, 77301523, 77312810, 82979980, 93933709, 86543538, 16097038, 60102965, 36685023, 11543098, 99729357, 80014588, 84293052, 84904436, 32590267, 16099750, 54868730, 32426752, 12303248, 70367851, 2204165, 6505939, 51464002, 41245325, 55960386, 99965001, 18783702, 5970607, 29466406, 14045383, 82651278, 90004325, 4091162, 33935899, 91938191, 49271185, 19272365, 16684829, 56153345, 29401781, 39201414, 24953498, 98948034, 62496012, 66663942, 62762857, 79322415, 83269727, 67513640, 89078848, 17385531, 1250437, 17727650, 5832946, 40534591, 37891451, 94076128, 10597197, 92353856, 61141874, 29065964, 526217, 43506672, 39553046, 93566986, 93057697, 36139942, 90310261, 51588015, 4806458, 4095116, 76540481, 97561745, 98462867, 71965942, 27185644, 51715482, 65271999, 88653118, 96735716, 42782652, 51315460, 66903004, 60106217, 95290172, 95010552, 88444207, 91990218, 37280276, 44915235, 15075176, 62430984, 94539824, 7919588, 53393358, 19898053, 21070110, 30891921, 68110330, 1569515, 20867149, 10649306, 38341669, 92867155, 55850790, 47887470, 89217461, 7300047, 4204661, 3233569, 89804152, 76671482, 8913721, 66322959, 73617245, 30463802, 34295794, 75986488, 71300104, 43322743, 86118021, 25636669, 34044787, 68644627, 52204879, 74357852, 96726697, 16424199, 69355476, 71920426, 27665211, 87720882, 68204242, 72373496, 10264691, 8733068, 99524975, 64055960, 1431742, 78300864, 66319530, 67474219, 72357096, 57658654, 99333375, 37192445, 17081350, 47738185, 82779622, 7502255, 46540998, 16380211, 34432810, 56531125, 9886593, 36753250, 18833224, 83368048, 59109400, 60176618, 99549373, 92787493, 94516935, 75543508, 59371804, 93053405, 33336148, 80251430, 13173644, 19891772, 58208470, 75820087, 3509435, 72019362, 66667729, 91802888, 55602660, 6038457, 26114953, 23569917, 33628349, 98943869, 1936762, 91831487, 36933038, 67084351, 30366150, 6497830, 20885148, 51590803, 84166196, 69848388, 53547802, 24058273, 53648154, 21673260, 44177011, 40027975, 24826575, 67031644, 68000591, 44784505, 47213183, 63059024, 23134715, 6111563, 91957544, 29635537, 96906410, 89811711, 72732205, 50668599, 40356877, 60393039, 31491938, 9603598, 87710366, 33201905, 39986008, 45996863, 21397057, 34946859, 10453030, 97057187, 168541, 669105, 74743862, 62693428, 9599614, 48673079, 83533741, 30139692, 8055981, 36312813, 26776922, 3183975, 8651647, 63628376, 65454636, 81677380, 20681921, 6153406, 70596786, 30787683, 83789391, 9575954, 66116458, 36505482, 18411915, 99690194, 41481685, 79806380, 50188404, 29994197, 20140249, 77187825, 55669657, 36780454, 15536795, 99224392, 99125126, 92530431, 84349107, 92215320, 67227442, 37224844, 19486173, 98648327, 43933006, 62936963, 39847321, 9188443, 92604458, 69786756, 81172706, 93562257, 79136082, 70541760, 27041967, 65074535, 9257405, 22721500, 29516592, 58749, 59981773, 55470718, 85695762, 24619760, 54263880, 88130087, 53666583, 17058722, 78909561, 59177718, 5073754, 12348118, 61859143, 74441448, 41092172, 17539625, 7229550, 43045786, 76330843, 38022834, 15902805 +36933038, 21673260, 39847321, 45667668, 90013093, 53547802, 62552524, 44784505, 59177718, 34497327, 36396314, 10453030, 67124282, 61859581, 36139942, 75543508, 69641513, 40865610, 45237957, 51315460, 47893286, 6153406, 21070110, 81898046, 19486173, 88130087, 66116458, 78909561, 85116755, 41245325, 62762857, 26744917, 47738185, 79821897, 99125126, 91727510, 30139692, 17068582, 33895336, 23569917, 51590803, 15163258, 24058273, 45794415, 68000591, 55850790, 29959549, 86543538, 94360702, 16019925, 84002370, 45428665, 5073754, 79136082, 86118021, 33935899, 9257405, 73786944, 8733068, 31491938, 74452589, 15536795, 77413012, 40686254, 76330843, 44060493, 34946859, 94595668, 30163921, 14220886, 2717150, 48893685, 56531125, 82339363, 44481640, 92803766, 38645117, 83651211, 16405341, 94090109, 3509435, 28550822, 96735716, 21919959, 54014062, 36189527, 34236719, 15015906, 67227442, 22942635, 44177011, 37224844, 76434144, 77377183, 92071990, 86301513, 8129978, 63059024, 95395112, 75777973, 33553959, 4204661, 53666583, 66322959, 2331773, 37739481, 73124510, 29635537, 9175338, 16099750, 92604458, 66885828, 29029316, 71522968, 37501808, 99297164, 22113133, 29466406, 31126490, 32058615, 40781449, 79806380, 18699206, 7687278, 72373496, 10264691, 62496012, 99226875, 62428472, 4064751, 82779622, 71657078, 6871053, 99971982, 22721500, 5111370, 29065964, 36753250, 54517921, 83368048, 99917599, 4787945, 83533741, 33336148, 29516592, 44473167, 8634541, 13173644, 86001008, 72274002, 78785507, 68102437, 58749, 3088684, 28787861, 49328608, 1936762, 14093520, 55470718, 88444207, 49236559, 48675329, 70036438, 10309525, 27325428, 77272628, 7919588, 61712234, 38658347, 80246713, 3773993, 52613508, 69255765, 43045786, 33123618, 48088883, 36685023, 99729357, 26063929, 62936963, 36505482, 61815223, 41380093, 92541302, 15535065, 71300104, 96906410, 77694700, 27187213, 36808486, 18504197, 55960386, 4099191, 71333116, 16971929, 52204879, 7182642, 16054533, 97379790, 4091162, 92692978, 24314885, 86798033, 14363867, 68204242, 29994197, 55247455, 74441448, 61982238, 96193415, 9603598, 33201905, 11581181, 57803235, 96709982, 5832946, 16380211, 34432810, 91255408, 19457589, 83210802, 35512853, 92787493, 41092172, 4434662, 18001617, 80251430, 19891772, 58208470, 13231279, 96852321, 71965942, 72019362, 88251446, 99658235, 28928175, 65271999, 93270084, 66250369, 66667729, 26776922, 42782652, 2208785, 57248122, 81805959, 26114953, 75052463, 47792865, 61271144, 26397786, 64848072, 6157724, 18466635, 14626618, 34493392, 17857111, 73392814, 55770687, 62490109, 40984766, 87598888, 76170907, 55753905, 48360198, 30090481, 61928316, 30569392, 7423788, 65275241, 60581278, 23134715, 3233569, 66271566, 11543098, 48260151, 80014588, 73617245, 55615885, 80555751, 38365584, 77620120, 75986488, 57163802, 98371444, 89214616, 54868730, 12303248, 22766820, 70367851, 51464002, 70541760, 4508700, 29834512, 91664334, 74357852, 88047921, 16424199, 28851716, 4119747, 71920426, 49271185, 19272365, 50668599, 72777973, 45919976, 83302115, 1431742, 60955663, 77284619, 66663942, 55669657, 36780454, 89699445, 56955985, 45381876, 17081350, 99515901, 14349098, 17727650, 46540998, 40534591, 40152546, 168541, 50806615, 66832478, 90090964, 74743862, 92353856, 69697787, 68694897, 2917920, 9886593, 61141874, 93566986, 26664538, 44842615, 91240048, 59109400, 90158785, 17764950, 76540481, 85242963, 2891150, 26139110, 59371804, 33797252, 90272749, 98462867, 17957593, 83133790, 35092039, 16445503, 61623915, 75153252, 75128745, 11923835, 6221471, 66611759, 7104732, 58224549, 9623492, 44664587, 36312813, 7229550, 33628349, 26392416, 30366150, 10358899, 9829782, 44915235, 9860195, 20885148, 53802686, 32159704, 30764367, 84166196, 54663246, 81853704, 20681921, 16595436, 3633375, 65338021, 53393358, 73222868, 62232211, 86460488, 54263880, 15064655, 59197747, 64157906, 67031644, 98648327, 22108665, 78561158, 39801351, 92554120, 92867155, 17016156, 61263068, 16097038, 43933006, 89804152, 72793444, 42199455, 85117092, 13468268, 54427233, 84293052, 30653863, 65081429, 26507214, 62740044, 4798568, 44847298, 415901, 1239555, 49597667, 51047803, 99690194, 32426752, 44889423, 81172706, 2204165, 71469330, 6505939, 67281495, 51426311, 18783702, 50007421, 33699435, 41442762, 43376279, 78766358, 36135, 20122224, 54232247, 82532312, 1204161, 92033260, 50188404, 95726235, 56484900, 83083131, 20002147, 98948034, 3294781, 824872, 70420215, 57658654, 38256849, 86022504, 33565483, 17037369, 84187166, 12664567, 57240218, 50702367, 17385531, 52261574, 10597197, 73031054, 61897582, 53888755, 72278539, 6521313, 82897371, 57020481, 65038678, 18833224, 43501211, 24733232, 90310261, 60176618, 13470059, 4806458, 22405120, 4095116, 27411561, 35996293, 94516935, 66137019, 49598724, 45617087, 97561745, 69579137, 47361209, 21533347, 84684495, 3233084, 43357947, 76315420, 93359396, 49882705, 48395186, 88653118, 95581843, 30998561, 508198, 60430369, 79922758, 20568363, 8651647, 9860968, 68816413, 59981773, 14326617, 67084351, 36580610, 57393458, 63628376, 37280276, 17976208, 59405277, 15948937, 32250045, 97011160, 22997281, 94539824, 69848388, 98130363, 1197320, 70596786, 69605283, 85695762, 22879907, 12416768, 30395570, 24619760, 30787683, 176257, 70727211, 93790285, 40027975, 75135448, 5482538, 47213183, 38341669, 73168565, 61728685, 29932657, 37675718, 569864, 89217461, 93933709, 7300047, 26275734, 40197395, 52293995, 8913721, 42307449, 63506281, 84904436, 30463802, 61741594, 37560164, 9188443, 42073124, 74724075, 47708850, 37620363, 93562257, 23740167, 56461322, 7011964, 8791066, 89811711, 71316369, 68644627, 5970607, 38022834, 18131876, 2607799, 85711894, 68316156, 61859143, 52060076, 90004325, 90654836, 72732205, 27665211, 66428795, 65074535, 40356877, 51466049, 60393039, 99524975, 43152977, 24953498, 64055960, 78300864, 67030811, 72357096, 20140249, 11757872, 41092102, 24168411, 37166608, 50567636, 67513640, 39986008, 37192445, 45848907, 17386996, 46870723, 30771409, 32151165, 96318094, 59318837, 45995325, 97057187, 67793644, 97641116, 45306070, 62693428, 65017137, 71083565, 45075471, 82327024, 5267545, 99549373, 9058407, 70800879, 79942022, 97940276, 80316608, 45790169, 97281847, 57241521, 33431960, 17539625, 81855445, 88698958, 19939935, 17894977, 51715482, 87160386, 3487592, 91141395, 62357986, 3880712, 79657802, 3183975, 74137926, 30694952, 66903004, 91831487, 60106217, 1788101, 91990218, 13862149, 22200378, 28734791, 15075176, 17365188, 59614746, 64098930, 6525948, 20836893, 24591705, 65715134, 32699744, 19898053, 53648154, 82178706, 30891921, 15902805, 68110330, 64087743, 61373987, 83789391, 24826575, 65880522, 64602895, 78589145, 13348726, 3235882, 5640302, 98739783, 20765474, 31161687, 47887470, 42967683, 6111563, 60102965, 58180653, 46851987, 36845587, 35192533, 83948335, 32590267, 18411915, 7685448, 69786756, 33249630, 7646095, 63152504, 6808825, 47090124, 58751351, 39373729, 34044787, 59329511, 95957797, 27041967, 55143724, 14045383, 49641577, 41481685, 81774825, 82651278, 57359924, 30811010, 91938191, 65047700, 44479073, 16684829, 56153345, 99021067, 72238278, 39201414, 94711842, 12024238, 1375023, 86242799, 14731700, 30218878, 59501487, 79322415, 97783876, 83269727, 99333375, 8128637, 26292919, 89078848, 6986898, 1250437, 29819940, 65851721, 44983451, 11365791, 8696647, 321665, 88904910, 44627776, 91281584, 526217, 95251277, 74614639, 39553046, 84349107, 76825057, 51588015, 45407418, 20642888, 22435353, 93053405, 8373289, 29510992, 92215320, 75820087, 25842078, 90457870, 53084256, 8055981, 28796059, 54199160, 96420429, 6038457, 38494874, 98943869, 42947632, 51472275, 6497830, 37957788, 72495719, 68128438, 62430984, 38008118, 20486294, 89046466, 1569515, 70004753, 8729146, 12571310, 45990383, 75407347, 42644903, 21289531, 82979980, 37183543, 55189057, 76671482, 51149804, 34698463, 7955293, 19101477, 72738685, 29188588, 48658605, 70372191, 112651, 63967300, 42692881, 12348118, 18663507, 7066775, 99965001, 25636669, 77787724, 7517032, 77898274, 37659250, 69355476, 16567550, 47298834, 37435892, 40781854, 73021291, 31727379, 68939068, 53632373, 82726008, 48774913, 247198, 54987042, 669105, 4985896, 62452034, 44846932, 16387575, 61960400, 79191827, 92530431, 31904591, 15148031, 32161669, 9599614, 93057697, 10366309, 19376156, 69136837, 48673079, 78218845, 44348328, 26863229, 38510840, 8099994, 26998766, 73235980, 68702632, 53842979, 4515343, 9259676, 81274566, 75229462, 83150534, 95290172, 95010552, 70782102, 749283, 92398073, 44844121, 8807940, 20867149, 79738755, 9575954, 68875490, 13819358, 77301523, 77312810, 39171895, 98653983, 874791, 79880247, 34295794, 44245960, 73109997, 96726697, 59910624, 27625735, 76960303, 4069912, 87720882, 85571389, 66319530, 54606384, 87710366, 45996863, 83155442, 54058788, 37891451, 9398733, 45049308, 77300457, 68824981, 23848565, 40677414, 8115266, 23432750, 72725103, 26102057, 14947650, 68041839, 26493119, 57961282, 21001913, 27185644, 34698428, 66045587, 82427263, 62115552, 78602717, 89419466, 36468541, 42237907, 34667286, 90061527, 14723410, 26734892, 78884452, 21993752, 38061439, 33061250, 62803858, 30543215, 51507425, 72614359, 92692283, 23110625, 6793819, 92998591, 56473732, 17146629, 24915585, 74110882, 29401781, 18806856, 4668450, 67474219, 36930650, 21397057, 94076128, 94911072, 72358170, 32274392, 22450468, 84840549, 10961421, 54762643, 78549759, 84406788, 4199704, 67451935, 81677380, 35456853, 99604946, 99861373, 62051033, 1022677, 82052050, 76703609, 48892201, 91957544, 43322743, 33237508, 63015256, 56424103, 77187825, 84127901, 22129328, 99224392, 7502255, 89637706, 36812683, 33304202, 63372756, 91802888, 93515664, 4978543, 65454636, 11161731, 89996536, 38009615, 10649306, 17058722, 83747892, 23194618, 6819644, 5822202, 77072625, 86821229, 56515456, 43506672, 55602660, 85023028, 31733363, 82886381, 20645197, 44550764, 89499542 +79922758, 43322743, 85711894, 25842078, 78549759, 38494874, 33565483, 61141874, 53842979, 37280276, 38008118, 65338021, 64087743, 70004753, 39801351, 92554120, 36685023, 35192533, 1239555, 16099750, 67281495, 56473732, 41442762, 32058615, 56484900, 34497327, 54987042, 86821229, 80316608, 23569917, 91831487, 61271144, 67084351, 22200378, 6157724, 62430984, 24591705, 77377183, 42644903, 26063929, 80014588, 37560164, 47090124, 88047921, 20122224, 90654836, 92033260, 99021067, 73786944, 824872, 50806615, 11365791, 321665, 89637706, 94911072, 83651211, 79942022, 75543508, 22435353, 45617087, 33304202, 79657802, 55602660, 64848072, 16595436, 98648327, 78561158, 8129978, 33123618, 17016156, 33553959, 6111563, 60102965, 76671482, 52293995, 48260151, 75986488, 74724075, 4099191, 82651278, 65074535, 76960303, 14363867, 1375023, 60955663, 62762857, 84187166, 77413012, 10453030, 10597197, 97641116, 30163921, 74743862, 2917920, 29065964, 65038678, 79191827, 23432750, 72725103, 13173644, 47361209, 35092039, 88251446, 28550822, 65271999, 68702632, 28787861, 2208785, 57248122, 60106217, 95010552, 70782102, 84406788, 10358899, 37957788, 32250045, 77272628, 22997281, 54663246, 67227442, 86460488, 1569515, 93790285, 65880522, 64602895, 88130087, 76434144, 13348726, 30090481, 73168565, 23134715, 58180653, 82052050, 79880247, 83948335, 92692283, 33249630, 47708850, 81172706, 7066775, 51426311, 99297164, 29834512, 52204879, 43376279, 52060076, 27665211, 4069912, 64055960, 14731700, 30218878, 96193415, 97783876, 37166608, 62428472, 50567636, 4064751, 17727650, 48774913, 37891451, 247198, 94595668, 65851721, 66832478, 53888755, 9886593, 44846932, 45075471, 90158785, 4787945, 51588015, 70800879, 97940276, 90272749, 44473167, 57241521, 92215320, 13231279, 44348328, 86001008, 27185644, 93359396, 91141395, 63372756, 54762643, 62357986, 7104732, 54199160, 33895336, 82427263, 75052463, 8651647, 9860968, 89419466, 42947632, 55470718, 1788101, 47893286, 44915235, 4978543, 68128438, 6525948, 3633375, 55770687, 35456853, 53648154, 21673260, 40984766, 12416768, 15064655, 44784505, 30569392, 29932657, 86543538, 4204661, 26275734, 16019925, 84002370, 46851987, 26507214, 62740044, 30463802, 61741594, 41380093, 6793819, 98371444, 71300104, 70372191, 112651, 27187213, 41245325, 33699435, 39373729, 71333116, 16971929, 31126490, 55143724, 14045383, 16054533, 7517032, 57359924, 40781449, 28851716, 54232247, 69355476, 18699206, 44479073, 9257405, 10264691, 47298834, 1431742, 4668450, 62496012, 61982238, 36930650, 36780454, 83269727, 8128637, 15536795, 57803235, 50702367, 17385531, 34946859, 79821897, 4985896, 6521313, 9398733, 45306070, 99125126, 91281584, 45049308, 77300457, 95251277, 43506672, 74614639, 83368048, 93057697, 36139942, 92803766, 76825057, 48673079, 4095116, 94516935, 66137019, 26493119, 58208470, 84684495, 88698958, 83133790, 26998766, 49882705, 68102437, 99658235, 40865610, 75153252, 34698428, 9623492, 28796059, 42782652, 4515343, 62115552, 30694952, 33628349, 51315460, 66903004, 81274566, 59981773, 88444207, 36580610, 57393458, 63628376, 15075176, 92398073, 89996536, 69848388, 98130363, 20836893, 15015906, 20681921, 32699744, 24058273, 73392814, 3773993, 30395570, 61373987, 79738755, 62552524, 3235882, 52613508, 95395112, 62051033, 75777973, 37183543, 89804152, 30543215, 66271566, 51149804, 34698463, 42307449, 36845587, 80555751, 34295794, 29188588, 18411915, 96906410, 99690194, 59177718, 29029316, 71522968, 93562257, 36808486, 55960386, 50007421, 8791066, 22113133, 68644627, 38022834, 95957797, 18131876, 7182642, 16424199, 41481685, 77898274, 4091162, 24915585, 74110882, 24314885, 1204161, 66428795, 68204242, 99524975, 24953498, 98948034, 11757872, 74452589, 87710366, 68939068, 67513640, 45381876, 12664567, 17386996, 17081350, 46870723, 7502255, 71657078, 40534591, 32151165, 45995325, 16380211, 73031054, 168541, 44983451, 2717150, 8696647, 68694897, 62452034, 72358170, 31904591, 93566986, 44481640, 32161669, 10366309, 60176618, 13470059, 99549373, 4806458, 92787493, 41092172, 26102057, 26139110, 4434662, 49598724, 68041839, 33431960, 19891772, 81855445, 21533347, 3509435, 69641513, 98462867, 38510840, 72274002, 43357947, 78785507, 10961421, 6221471, 66611759, 48395186, 58224549, 44664587, 66667729, 26776922, 26114953, 20568363, 68816413, 47792865, 36933038, 91990218, 749283, 48675329, 28734791, 17976208, 9860195, 20885148, 10309525, 27325428, 51590803, 72495719, 94539824, 32159704, 61712234, 65715134, 70596786, 69605283, 22879907, 81898046, 30891921, 62490109, 38009615, 99861373, 37224844, 75135448, 55753905, 19486173, 48360198, 5482538, 78589145, 64157906, 13819358, 20765474, 65275241, 38341669, 21289531, 29959549, 61263068, 37675718, 89217461, 42967683, 72793444, 42199455, 85117092, 8913721, 84293052, 55615885, 72614359, 62936963, 78909561, 36505482, 61815223, 19101477, 48892201, 77620120, 23110625, 15535065, 45428665, 54868730, 63967300, 92998591, 77694700, 70367851, 18663507, 63152504, 79136082, 86118021, 89811711, 77787724, 74357852, 97379790, 36135, 61859143, 4119747, 91938191, 63015256, 56153345, 87720882, 50668599, 29401781, 72777973, 94711842, 78300864, 20002147, 66319530, 67030811, 5822202, 66663942, 57658654, 9603598, 79322415, 77187825, 54606384, 24168411, 99333375, 26744917, 37192445, 26292919, 57240218, 6986898, 82726008, 76330843, 82779622, 21397057, 30771409, 34432810, 99971982, 82897371, 48893685, 62693428, 56531125, 65017137, 526217, 43501211, 71083565, 39553046, 92530431, 68824981, 15148031, 24733232, 26664538, 61859581, 9599614, 91240048, 69136837, 83210802, 59109400, 20642888, 22405120, 85242963, 14947650, 91727510, 59371804, 45790169, 33797252, 18001617, 30139692, 29516592, 75820087, 96852321, 19939935, 17957593, 21001913, 17068582, 51715482, 87160386, 72019362, 75128745, 8055981, 93270084, 36312813, 3880712, 14093520, 75229462, 26392416, 30366150, 9829782, 93515664, 67451935, 59405277, 15948937, 34667286, 81677380, 14626618, 15163258, 84166196, 64098930, 53393358, 19898053, 73222868, 80246713, 68110330, 99604946, 24619760, 44177011, 176257, 83789391, 70727211, 87598888, 31733363, 12571310, 59197747, 24826575, 45990383, 75407347, 66116458, 98739783, 31161687, 92867155, 55850790, 60581278, 77301523, 82979980, 94360702, 43933006, 7300047, 48088883, 40197395, 76703609, 99729357, 54427233, 874791, 73617245, 2331773, 37739481, 415901, 49597667, 9175338, 9188443, 48658605, 51047803, 89214616, 69786756, 22766820, 44245960, 7646095, 37620363, 37501808, 51464002, 18504197, 23740167, 4508700, 58751351, 29466406, 27041967, 59910624, 30811010, 72373496, 39201414, 16567550, 95726235, 18806856, 83302115, 86242799, 77284619, 73021291, 99226875, 55669657, 41092102, 89699445, 45848907, 45996863, 89078848, 53632373, 40686254, 83155442, 99515901, 52261574, 47738185, 44060493, 96318094, 59318837, 40152546, 61897582, 669105, 22721500, 56515456, 88904910, 19457589, 57020481, 16387575, 32274392, 23848565, 5267545, 40677414, 45407418, 17764950, 2891150, 93053405, 97561745, 80251430, 29510992, 26863229, 71965942, 28928175, 61623915, 53084256, 58749, 11923835, 45237957, 3088684, 66045587, 49328608, 508198, 6038457, 81805959, 1936762, 21919959, 95290172, 26397786, 51472275, 70036438, 36189527, 65454636, 97011160, 34493392, 30764367, 34236719, 14723410, 26734892, 17857111, 6153406, 89499542, 21070110, 38658347, 22942635, 15902805, 38061439, 33061250, 30787683, 40027975, 76170907, 47213183, 63059024, 7423788, 10649306, 62803858, 47887470, 77312810, 16097038, 53666583, 55189057, 13468268, 51507425, 30653863, 4798568, 38365584, 32590267, 29635537, 57163802, 92604458, 42073124, 12303248, 42692881, 66885828, 5073754, 44889423, 6808825, 70541760, 99965001, 25636669, 33237508, 71316369, 96726697, 78766358, 81774825, 68316156, 90004325, 92692978, 82532312, 79806380, 33935899, 7687278, 65047700, 85571389, 45919976, 51466049, 60393039, 55247455, 74441448, 83083131, 70420215, 38256849, 36396314, 96709982, 5832946, 6871053, 91255408, 69697787, 67124282, 5111370, 36812683, 18833224, 61960400, 54517921, 82327024, 38645117, 35512853, 9058407, 83533741, 35996293, 97281847, 8373289, 8634541, 94090109, 78218845, 57961282, 22450468, 88653118, 96420429, 91802888, 74137926, 9259676, 14326617, 36468541, 42237907, 54014062, 13862149, 49236559, 53802686, 17365188, 59614746, 1197320, 7919588, 81853704, 21993752, 62232211, 44844121, 89046466, 8729146, 9575954, 92071990, 43045786, 68875490, 61728685, 82886381, 1022677, 569864, 93933709, 98653983, 17058722, 11543098, 63506281, 44847298, 91957544, 73124510, 72738685, 39847321, 7685448, 32426752, 12348118, 73109997, 56461322, 59329511, 2607799, 49641577, 37659250, 16684829, 50188404, 23194618, 29994197, 8733068, 20645197, 40781854, 77072625, 31727379, 84127901, 14349098, 54058788, 94076128, 90090964, 44550764, 92353856, 44627776, 99917599, 82339363, 84349107, 90310261, 8115266, 16405341, 33336148, 3233084, 90013093, 8099994, 90457870, 84840549, 73235980, 95581843, 7229550, 85023028, 4199704, 11161731, 53547802, 45794415, 78884452, 54263880, 67031644, 5640302, 39171895, 3233569, 66322959, 65081429, 92541302, 85116755, 6505939, 7011964, 17146629, 91664334, 19272365, 86798033, 40356877, 31491938, 43152977, 37435892, 67474219, 3294781, 20140249, 59501487, 56424103, 33201905, 11581181, 17037369, 56955985, 1250437, 97057187, 67793644, 36753250, 44842615, 76540481, 45667668, 69579137, 17539625, 76315420, 16445503, 66250369, 96735716, 60430369, 98943869, 83150534, 18466635, 90061527, 85695762, 82178706, 68000591, 22108665, 61928316, 69255765, 86301513, 7955293, 2204165, 71469330, 83747892, 18783702, 5970607, 72732205, 49271185, 72238278, 72357096, 86022504, 39986008, 14220886, 19376156, 3487592, 30998561, 3183975, 78602717, 6497830, 8807940, 20867149, 27625735, 12024238, 6819644, 99224392, 29819940, 46540998, 27411561, 34044787, 71920426, 22129328, 72278539, 17894977, 84904436, 20486294 +17058722, 69786756, 73168565, 27625735, 79806380, 99524975, 68816413, 20765474, 42644903, 37891451, 4985896, 4434662, 49598724, 53084256, 1197320, 92071990, 58180653, 8913721, 12303248, 19272365, 68204242, 64055960, 16405341, 75543508, 28928175, 40865610, 54199160, 3183975, 78549759, 51315460, 72495719, 30891921, 7646095, 86118021, 17146629, 4508700, 18131876, 4069912, 24168411, 45996863, 32274392, 82327024, 17764950, 26102057, 59371804, 44473167, 29510992, 81855445, 84684495, 21001913, 43357947, 28787861, 33628349, 98943869, 66903004, 26392416, 10358899, 4978543, 92398073, 15948937, 69605283, 81898046, 31733363, 30090481, 68000591, 8129978, 16097038, 48088883, 66271566, 13468268, 19101477, 92604458, 96906410, 63967300, 83747892, 33699435, 5970607, 27041967, 30811010, 39201414, 20645197, 59501487, 77072625, 11757872, 89078848, 53632373, 84127901, 57240218, 71657078, 94076128, 67793644, 97641116, 99917599, 82339363, 84349107, 8115266, 83533741, 35996293, 93053405, 21533347, 3509435, 49328608, 67451935, 59405277, 81677380, 15015906, 67227442, 24058273, 45794415, 38061439, 15064655, 8729146, 13348726, 62552524, 77312810, 76671482, 54427233, 65081429, 84002370, 415901, 37560164, 16099750, 42692881, 2204165, 79136082, 56473732, 38022834, 16054533, 72732205, 49271185, 76960303, 87720882, 72373496, 47298834, 74441448, 14731700, 77187825, 87710366, 8128637, 6986898, 17081350, 14349098, 94595668, 22721500, 5111370, 62693428, 89637706, 9886593, 44846932, 61141874, 19457589, 45049308, 526217, 77300457, 65038678, 43501211, 39553046, 92530431, 31904591, 68824981, 44842615, 93057697, 69136837, 23432750, 35512853, 45407418, 97561745, 17539625, 51715482, 49882705, 88251446, 44664587, 68702632, 4515343, 47792865, 75229462, 70782102, 91990218, 84406788, 4199704, 6157724, 22997281, 89996536, 70596786, 22942635, 8807940, 12416768, 89046466, 78589145, 67031644, 98648327, 9575954, 63059024, 43045786, 39171895, 4204661, 79880247, 84904436, 30653863, 62936963, 41380093, 23110625, 6793819, 89214616, 42073124, 66885828, 37620363, 6505939, 56461322, 25636669, 99297164, 68644627, 29466406, 43376279, 36135, 7517032, 69355476, 56153345, 23194618, 10264691, 51466049, 83302115, 1431742, 40781854, 4668450, 38256849, 33201905, 11581181, 99333375, 31727379, 68939068, 56955985, 45381876, 26292919, 82726008, 46870723, 47738185, 21397057, 72278539, 2717150, 48893685, 69697787, 68694897, 2917920, 56531125, 94911072, 57020481, 95251277, 43506672, 79191827, 26664538, 61859581, 9599614, 10366309, 91240048, 76825057, 4787945, 92787493, 20642888, 9058407, 85242963, 94516935, 78218845, 88698958, 26863229, 90457870, 22450468, 75128745, 48395186, 45237957, 3088684, 66045587, 42782652, 57248122, 6038457, 62115552, 81805959, 81274566, 60106217, 14326617, 21919959, 36468541, 13862149, 48675329, 6497830, 93515664, 18466635, 77272628, 34493392, 98130363, 65338021, 53393358, 73392814, 20486294, 15902805, 68110330, 64087743, 24619760, 176257, 99861373, 19486173, 64602895, 64157906, 52613508, 7423788, 10649306, 95395112, 61728685, 21289531, 17016156, 61263068, 77301523, 37675718, 89217461, 33553959, 94360702, 60102965, 82052050, 11543098, 51149804, 52293995, 26063929, 51507425, 84293052, 73617245, 46851987, 36845587, 80555751, 35192533, 30463802, 38365584, 92692283, 9188443, 18411915, 71300104, 59177718, 33249630, 55960386, 34044787, 22113133, 77787724, 2607799, 91664334, 78766358, 16424199, 41481685, 81774825, 82651278, 52060076, 40781449, 37659250, 92692978, 71920426, 82532312, 91938191, 7687278, 1204161, 86798033, 16684829, 99021067, 72238278, 16567550, 8733068, 55247455, 83083131, 60955663, 66319530, 67030811, 3294781, 66663942, 56424103, 57658654, 62762857, 84187166, 37192445, 12664567, 99515901, 48774913, 79821897, 10597197, 34432810, 65851721, 669105, 44983451, 6521313, 74743862, 8696647, 9398733, 16387575, 24733232, 23848565, 19376156, 40677414, 59109400, 79942022, 91727510, 57241521, 94090109, 13173644, 47361209, 58208470, 96852321, 33304202, 17894977, 84840549, 68102437, 75153252, 91141395, 34698428, 63372756, 66250369, 95581843, 79657802, 26776922, 30998561, 79922758, 91802888, 30694952, 8651647, 1936762, 91831487, 59981773, 14093520, 55470718, 42237907, 30366150, 22200378, 749283, 51472275, 70036438, 28734791, 37957788, 10309525, 53802686, 90061527, 14626618, 32159704, 54663246, 7919588, 16595436, 85695762, 38658347, 21673260, 80246713, 33061250, 1569515, 87598888, 79738755, 48360198, 65880522, 76434144, 77377183, 22108665, 61928316, 45990383, 78561158, 30569392, 66116458, 98739783, 62051033, 62803858, 75777973, 55850790, 33123618, 86543538, 98653983, 42199455, 55189057, 40197395, 30543215, 85117092, 34698463, 42307449, 99729357, 26507214, 78909561, 7955293, 91957544, 32590267, 15535065, 85116755, 99690194, 70372191, 22766820, 74724075, 71522968, 18663507, 70541760, 67281495, 51426311, 41442762, 58751351, 39373729, 59329511, 95957797, 29834512, 85711894, 59910624, 49641577, 20122224, 28851716, 66428795, 92033260, 63015256, 44479073, 31491938, 1375023, 24953498, 86242799, 96193415, 79322415, 97783876, 74452589, 54606384, 17037369, 26744917, 52261574, 22129328, 29819940, 32151165, 96318094, 45995325, 40152546, 54987042, 90090964, 44550764, 92353856, 67124282, 56515456, 321665, 62452034, 65017137, 44627776, 91281584, 61960400, 90158785, 99549373, 41092172, 22405120, 4095116, 76540481, 14947650, 97940276, 26139110, 22435353, 68041839, 45667668, 8373289, 80251430, 92215320, 13231279, 98462867, 3233084, 17068582, 61623915, 58749, 54762643, 62357986, 36312813, 2208785, 96420429, 55602660, 7229550, 74137926, 23569917, 75052463, 38494874, 89419466, 88444207, 67084351, 57393458, 64848072, 44915235, 65454636, 11161731, 32250045, 27325428, 17365188, 97011160, 68128438, 38008118, 24591705, 65715134, 53547802, 6153406, 21070110, 53648154, 82178706, 62232211, 86460488, 44177011, 61373987, 70004753, 83789391, 40027975, 59197747, 44784505, 47213183, 86301513, 68875490, 31161687, 82886381, 37183543, 23134715, 43933006, 26275734, 89804152, 72793444, 16019925, 66322959, 80014588, 44847298, 61815223, 1239555, 92541302, 75986488, 29188588, 57163802, 9175338, 112651, 5073754, 77694700, 44889423, 81172706, 18504197, 23740167, 18783702, 8791066, 33237508, 16971929, 52204879, 96726697, 88047921, 7182642, 77898274, 90004325, 4091162, 24915585, 90654836, 74110882, 27665211, 24314885, 65047700, 50188404, 50668599, 72777973, 95726235, 18806856, 94711842, 56484900, 20002147, 77284619, 30218878, 73021291, 72357096, 20140249, 34497327, 9603598, 36930650, 37166608, 83269727, 50567636, 45848907, 77413012, 36396314, 57803235, 40686254, 17385531, 76330843, 54058788, 59318837, 168541, 30163921, 66832478, 45306070, 72358170, 36812683, 83368048, 93566986, 44481640, 32161669, 5267545, 92803766, 83210802, 90310261, 60176618, 51588015, 48673079, 70800879, 27411561, 80316608, 45790169, 30139692, 8634541, 69579137, 44348328, 25842078, 83133790, 27185644, 16445503, 93359396, 87160386, 26998766, 78785507, 99658235, 10961421, 66667729, 53842979, 33895336, 96735716, 82427263, 78602717, 9860968, 85023028, 83150534, 95290172, 36933038, 36580610, 63628376, 49236559, 37280276, 20885148, 34667286, 51590803, 30764367, 59614746, 26734892, 81853704, 20681921, 3633375, 32699744, 78884452, 89499542, 22879907, 44844121, 62490109, 99604946, 30395570, 30787683, 20867149, 37224844, 76170907, 75135448, 88130087, 75407347, 39801351, 65275241, 38341669, 29959549, 93933709, 42967683, 6111563, 3233569, 76703609, 63506281, 874791, 2331773, 62740044, 4798568, 49597667, 48658605, 7685448, 43322743, 47708850, 71469330, 37501808, 73109997, 41245325, 7066775, 50007421, 7011964, 89811711, 71316369, 71333116, 14045383, 68316156, 4119747, 29401781, 9257405, 29994197, 73786944, 45919976, 78300864, 6819644, 98948034, 824872, 62496012, 5822202, 61982238, 70420215, 86022504, 15536795, 17386996, 4064751, 82779622, 44060493, 34946859, 10453030, 73031054, 50806615, 91255408, 14220886, 82897371, 29065964, 18833224, 45075471, 38645117, 83651211, 66137019, 33797252, 33336148, 97281847, 18001617, 90272749, 29516592, 69641513, 19939935, 35092039, 8099994, 28550822, 3487592, 6221471, 66611759, 88653118, 7104732, 58224549, 9623492, 60430369, 9259676, 1788101, 36189527, 9860195, 62430984, 64098930, 14723410, 69848388, 35456853, 12571310, 5482538, 69255765, 92867155, 60581278, 47887470, 569864, 7300047, 53666583, 72614359, 48892201, 29635537, 51047803, 27187213, 29029316, 44245960, 70367851, 93562257, 51464002, 99965001, 4099191, 47090124, 74357852, 31126490, 55143724, 97379790, 32058615, 57359924, 33935899, 14363867, 40356877, 60393039, 37435892, 99226875, 89699445, 62428472, 33565483, 67513640, 39986008, 83155442, 96709982, 17727650, 16380211, 53888755, 86821229, 99125126, 88904910, 36753250, 71083565, 15148031, 72725103, 45617087, 33431960, 57961282, 38510840, 72019362, 11923835, 8055981, 42947632, 61271144, 26397786, 47893286, 17976208, 15163258, 94539824, 84166196, 17857111, 20836893, 19898053, 21993752, 3773993, 70727211, 93790285, 24826575, 5640302, 13819358, 29932657, 36685023, 48260151, 55615885, 37739481, 61741594, 83948335, 34295794, 72738685, 39847321, 92998591, 36808486, 65074535, 43152977, 67474219, 55669657, 41092102, 1250437, 5832946, 46540998, 40534591, 6871053, 247198, 99971982, 11365791, 74614639, 36139942, 2891150, 19891772, 75820087, 17957593, 71965942, 76315420, 73235980, 3880712, 508198, 26114953, 20568363, 95010552, 54014062, 9829782, 15075176, 34236719, 6525948, 61712234, 73222868, 54263880, 55753905, 3235882, 92554120, 36505482, 73124510, 98371444, 54868730, 6808825, 61859143, 85571389, 12024238, 50702367, 99224392, 30771409, 97057187, 61897582, 54517921, 13470059, 4806458, 90013093, 65271999, 55770687, 1022677, 77620120, 32426752, 12348118, 54232247, 18699206, 36780454, 7502255, 26493119, 86001008, 72274002, 93270084, 38009615, 82979980, 45428665, 63152504, 28796059, 40984766 +112651, 13819358, 92554120, 93566986, 4806458, 3487592, 15075176, 55850790, 16097038, 18504197, 16684829, 86242799, 61859581, 14947650, 45617087, 88698958, 22450468, 58749, 59981773, 47792865, 95290172, 51472275, 43045786, 3233569, 2331773, 16099750, 47708850, 95957797, 31126490, 9257405, 77072625, 37166608, 36780454, 74614639, 51588015, 90272749, 47361209, 73235980, 44664587, 91802888, 20568363, 34667286, 73392814, 89046466, 70004753, 569864, 89217461, 99729357, 78909561, 1239555, 23110625, 9188443, 74724075, 7011964, 58751351, 97379790, 4091162, 14363867, 39201414, 1375023, 43152977, 55247455, 5822202, 11757872, 83269727, 89078848, 50702367, 4064751, 10453030, 94595668, 44550764, 91281584, 83368048, 35512853, 99549373, 17764950, 86001008, 16445503, 90457870, 66667729, 78549759, 14326617, 84406788, 47893286, 70036438, 30891921, 8807940, 98739783, 47887470, 72793444, 8913721, 63506281, 73617245, 62936963, 61815223, 83948335, 91957544, 92692283, 57163802, 33249630, 12303248, 22766820, 83747892, 41245325, 4508700, 29834512, 52204879, 52060076, 59501487, 96193415, 99333375, 45381876, 14349098, 82779622, 37891451, 97057187, 67793644, 90090964, 48893685, 62693428, 36812683, 18833224, 44842615, 16405341, 85242963, 80316608, 45790169, 3233084, 38510840, 21001913, 68102437, 28550822, 63372756, 54762643, 9623492, 96420429, 57248122, 6038457, 74137926, 51315460, 89419466, 61271144, 36933038, 63628376, 67451935, 59405277, 18466635, 90061527, 51590803, 15163258, 89996536, 61712234, 85695762, 59197747, 5482538, 88130087, 30090481, 77377183, 52613508, 30569392, 5640302, 21289531, 61263068, 77312810, 23134715, 42199455, 76671482, 16019925, 84293052, 84002370, 45428665, 92604458, 66885828, 12348118, 18663507, 51464002, 51426311, 56473732, 41442762, 68644627, 38022834, 77787724, 27041967, 43376279, 96726697, 24314885, 72238278, 85571389, 29994197, 12024238, 60955663, 6819644, 57658654, 97783876, 33565483, 26744917, 67513640, 57240218, 96709982, 7502255, 71657078, 54058788, 59318837, 10597197, 73031054, 44983451, 22721500, 14220886, 2717150, 82897371, 62452034, 88904910, 526217, 43506672, 71083565, 54517921, 9599614, 36139942, 92803766, 60176618, 8115266, 90158785, 23432750, 20642888, 22405120, 9058407, 97940276, 26139110, 4434662, 33797252, 26493119, 18001617, 97561745, 81855445, 92215320, 57961282, 84684495, 72274002, 17068582, 88251446, 99658235, 65271999, 88653118, 508198, 82427263, 55602660, 3183975, 55470718, 91990218, 10358899, 22200378, 749283, 36189527, 6497830, 92398073, 94539824, 59614746, 64098930, 67227442, 32699744, 24058273, 89499542, 35456853, 8729146, 8129978, 7423788, 42644903, 38341669, 60581278, 37183543, 86543538, 7300047, 89804152, 55189057, 82052050, 13468268, 30653863, 46851987, 4798568, 80555751, 30463802, 7955293, 415901, 72738685, 29188588, 6793819, 89214616, 42073124, 7646095, 93562257, 6505939, 23740167, 55960386, 99965001, 47090124, 25636669, 33237508, 22113133, 2607799, 91664334, 7182642, 14045383, 77898274, 20122224, 82532312, 91938191, 19272365, 50188404, 68204242, 45919976, 10264691, 51466049, 83302115, 8733068, 78300864, 72357096, 70420215, 62762857, 55669657, 41092102, 31727379, 26292919, 99224392, 21397057, 30771409, 40534591, 74743862, 8696647, 45306070, 321665, 72358170, 44627776, 57020481, 45075471, 39553046, 92530431, 82339363, 26664538, 82327024, 93057697, 91240048, 83651211, 72725103, 41092172, 76540481, 35996293, 2891150, 93053405, 68041839, 44473167, 57241521, 58208470, 3509435, 44348328, 25842078, 17957593, 71965942, 90013093, 17894977, 43357947, 26998766, 91141395, 58224549, 28787861, 53842979, 26776922, 49328608, 96735716, 26114953, 33628349, 75052463, 98943869, 9860968, 83150534, 95010552, 26392416, 42237907, 36580610, 30366150, 64848072, 17976208, 6157724, 14626618, 34493392, 54663246, 6525948, 98130363, 15015906, 20681921, 65338021, 78884452, 22879907, 62232211, 22942635, 44844121, 68110330, 38009615, 64087743, 3773993, 22108665, 45990383, 78561158, 69255765, 62803858, 73168565, 29932657, 17016156, 42967683, 94360702, 98653983, 40197395, 85117092, 76703609, 48260151, 54427233, 79880247, 84904436, 62740044, 72614359, 44847298, 61741594, 39847321, 29635537, 9175338, 85116755, 7685448, 99690194, 59177718, 43322743, 42692881, 77694700, 44245960, 81172706, 71522968, 36808486, 86118021, 34044787, 71333116, 29466406, 85711894, 41481685, 61859143, 27625735, 92692978, 27665211, 18699206, 49271185, 1204161, 65074535, 56153345, 23194618, 29401781, 72777973, 72373496, 64055960, 1431742, 66319530, 98948034, 99226875, 66663942, 61982238, 34497327, 77187825, 62428472, 11581181, 39986008, 45996863, 6986898, 17386996, 22129328, 47738185, 45995325, 40152546, 247198, 94076128, 4985896, 11365791, 92353856, 69697787, 68694897, 5111370, 56531125, 29065964, 19457589, 95251277, 43501211, 61960400, 79191827, 99917599, 32161669, 69136837, 13470059, 27411561, 79942022, 91727510, 29510992, 17539625, 13231279, 75820087, 96852321, 8099994, 51715482, 84840549, 61623915, 11923835, 34698428, 45237957, 3088684, 28796059, 3880712, 62115552, 81805959, 8651647, 91831487, 75229462, 57393458, 13862149, 37280276, 48675329, 9860195, 65454636, 11161731, 27325428, 77272628, 30764367, 14723410, 1197320, 6153406, 70596786, 19898053, 38658347, 73222868, 21673260, 62490109, 80246713, 54263880, 87598888, 31733363, 40027975, 63059024, 10649306, 95395112, 20765474, 31161687, 62051033, 75777973, 61728685, 82886381, 33123618, 29959549, 1022677, 77301523, 33553959, 6111563, 39171895, 48088883, 53666583, 58180653, 26063929, 874791, 65081429, 26507214, 36845587, 32590267, 37560164, 18411915, 98371444, 51047803, 32426752, 63967300, 92998591, 27187213, 29029316, 44889423, 33699435, 89811711, 18131876, 88047921, 16054533, 36135, 81774825, 7517032, 24915585, 30811010, 90654836, 54232247, 65047700, 86798033, 92033260, 76960303, 4069912, 99021067, 73786944, 94711842, 20645197, 4668450, 14731700, 20002147, 20140249, 9603598, 54606384, 87710366, 17037369, 84187166, 68939068, 15536795, 40686254, 17385531, 1250437, 79821897, 50806615, 97641116, 30163921, 66832478, 91255408, 6521313, 2917920, 67124282, 44846932, 45049308, 32274392, 31904591, 68824981, 15148031, 24733232, 59109400, 76825057, 4787945, 45407418, 59371804, 49598724, 45667668, 29516592, 98462867, 83133790, 33304202, 72019362, 66611759, 93270084, 66250369, 33895336, 2208785, 79922758, 23569917, 1936762, 68816413, 60106217, 14093520, 67084351, 54014062, 44915235, 37957788, 53802686, 15948937, 97011160, 81677380, 62430984, 38008118, 16595436, 53393358, 55770687, 21070110, 82178706, 81898046, 40984766, 12416768, 38061439, 24619760, 44177011, 176257, 15064655, 76170907, 48360198, 65880522, 64602895, 67031644, 13348726, 98648327, 47213183, 37675718, 82979980, 26275734, 36685023, 34698463, 51507425, 73124510, 77620120, 75986488, 48658605, 96906410, 54868730, 70372191, 37620363, 79136082, 7066775, 18783702, 4099191, 56461322, 8791066, 17146629, 39373729, 71316369, 5970607, 74357852, 49641577, 82651278, 90004325, 40781449, 72732205, 4119747, 63015256, 44479073, 16567550, 18806856, 99524975, 56484900, 30218878, 73021291, 56424103, 74452589, 89699445, 12664567, 36396314, 53632373, 84127901, 83155442, 17727650, 46540998, 16380211, 99971982, 168541, 54987042, 669105, 86821229, 72278539, 89637706, 94911072, 99125126, 9886593, 36753250, 44481640, 10366309, 5267545, 84349107, 38645117, 70800879, 83533741, 66137019, 75543508, 97281847, 8373289, 8634541, 78218845, 19891772, 69579137, 69641513, 26863229, 19939935, 35092039, 76315420, 93359396, 87160386, 28928175, 40865610, 10961421, 36312813, 30998561, 60430369, 85023028, 42947632, 36468541, 70782102, 88444207, 1788101, 49236559, 9829782, 93515664, 10309525, 32250045, 72495719, 17365188, 68128438, 32159704, 34236719, 26734892, 45794415, 69605283, 20486294, 53648154, 1569515, 79738755, 93790285, 75135448, 55753905, 19486173, 78589145, 76434144, 68000591, 61928316, 62552524, 3235882, 60102965, 4204661, 51149804, 42307449, 80014588, 37739481, 35192533, 48892201, 38365584, 49597667, 41380093, 92541302, 15535065, 69786756, 71469330, 59329511, 78766358, 55143724, 68316156, 57359924, 37659250, 79806380, 87720882, 95726235, 60393039, 47298834, 40781854, 67474219, 3294781, 62496012, 79322415, 38256849, 24168411, 8128637, 99515901, 82726008, 76330843, 34946859, 61897582, 77300457, 83210802, 40677414, 90310261, 4095116, 33336148, 94090109, 13173644, 27185644, 78785507, 6221471, 62357986, 8055981, 79657802, 4515343, 7229550, 78602717, 30694952, 9259676, 38494874, 4199704, 4978543, 22997281, 84166196, 7919588, 24591705, 3633375, 53547802, 21993752, 15902805, 86460488, 30395570, 30787683, 20867149, 83789391, 70727211, 37224844, 24826575, 9575954, 86301513, 39801351, 65275241, 92867155, 43933006, 17058722, 30543215, 66271566, 11543098, 70367851, 63152504, 67281495, 59910624, 69355476, 71920426, 74110882, 66428795, 37435892, 24953498, 67030811, 36930650, 86022504, 50567636, 56955985, 45848907, 77413012, 57803235, 17081350, 52261574, 48774913, 29819940, 32151165, 65851721, 9398733, 56515456, 19376156, 48673079, 26102057, 22435353, 80251430, 48395186, 7104732, 68702632, 66903004, 69848388, 65715134, 99604946, 61373987, 99861373, 12571310, 44784505, 92071990, 68875490, 93933709, 36505482, 19101477, 34295794, 71300104, 5073754, 2204165, 37501808, 70541760, 99297164, 16424199, 28851716, 33935899, 40356877, 31491938, 824872, 46870723, 5832946, 34432810, 65017137, 23848565, 92787493, 94516935, 33431960, 21533347, 49882705, 53084256, 95581843, 66045587, 42782652, 81274566, 21919959, 28734791, 20885148, 17857111, 33061250, 64157906, 75407347, 55615885, 6808825, 50007421, 16971929, 32058615, 50668599, 83083131, 77284619, 44060493, 96318094, 6871053, 61141874, 75153252, 26397786, 20836893, 52293995, 66322959, 73109997, 7687278, 74441448, 33201905, 37192445, 16387575, 65038678, 75128745, 54199160, 66116458, 53888755, 81853704, 30139692 +59614746, 56461322, 6871053, 4787945, 28734791, 33237508, 14349098, 71657078, 57020481, 95251277, 43506672, 80316608, 81855445, 87160386, 78602717, 70782102, 26734892, 86301513, 20765474, 13468268, 72738685, 42692881, 47090124, 14045383, 37659250, 68204242, 8128637, 83155442, 67793644, 82339363, 82327024, 84349107, 16405341, 45790169, 83150534, 6497830, 31161687, 17016156, 94360702, 36845587, 80555751, 12348118, 71469330, 72732205, 18699206, 86242799, 97783876, 77187825, 37166608, 11581181, 96709982, 86821229, 68694897, 79191827, 61859581, 69136837, 38645117, 79942022, 45617087, 90272749, 8634541, 68102437, 36312813, 14326617, 65338021, 44844121, 64087743, 89046466, 76170907, 12571310, 21289531, 40197395, 85117092, 51507425, 46851987, 44847298, 30463802, 57163802, 48658605, 7685448, 6505939, 23740167, 7011964, 33699435, 29834512, 16424199, 20122224, 4119747, 99021067, 60955663, 6819644, 67030811, 56424103, 77072625, 87710366, 84187166, 68939068, 52261574, 82779622, 40152546, 73031054, 97641116, 74743862, 94911072, 91281584, 65038678, 43501211, 74614639, 66137019, 2891150, 47361209, 21533347, 51715482, 75153252, 45237957, 44664587, 68702632, 54199160, 81805959, 78549759, 66903004, 42947632, 95290172, 36468541, 13862149, 10358899, 37280276, 47893286, 89996536, 14723410, 7919588, 65715134, 32699744, 15902805, 22108665, 69255765, 5640302, 39801351, 95395112, 60581278, 29959549, 37675718, 77312810, 42967683, 86543538, 39171895, 76703609, 72614359, 23110625, 67281495, 56473732, 5970607, 18131876, 96726697, 88047921, 55143724, 49641577, 41481685, 77898274, 40781449, 82532312, 65074535, 14363867, 87720882, 72373496, 16567550, 95726235, 12024238, 9603598, 55669657, 77413012, 17386996, 247198, 10597197, 97057187, 168541, 53888755, 54987042, 9398733, 44846932, 36812683, 61960400, 92530431, 44481640, 5267545, 23432750, 51588015, 4806458, 26102057, 85242963, 18001617, 33431960, 19891772, 21001913, 43357947, 76315420, 22450468, 93359396, 88251446, 99658235, 53084256, 75128745, 58749, 28796059, 79657802, 49328608, 4515343, 62115552, 38494874, 9860968, 36189527, 30764367, 54663246, 17857111, 67227442, 53393358, 70596786, 73392814, 81898046, 30891921, 21673260, 70727211, 87598888, 75135448, 59197747, 5482538, 98648327, 8129978, 43045786, 13819358, 62051033, 89217461, 33553959, 26275734, 89804152, 48260151, 26063929, 80014588, 32590267, 49597667, 73124510, 29188588, 9175338, 71300104, 12303248, 43322743, 47708850, 37501808, 93562257, 25636669, 59329511, 27041967, 52204879, 28851716, 92692978, 71920426, 49271185, 51466049, 47298834, 31491938, 24953498, 20645197, 1431742, 30218878, 20140249, 59501487, 99226875, 96193415, 54606384, 37192445, 84127901, 6986898, 46870723, 34946859, 32151165, 16380211, 67124282, 56515456, 99125126, 9886593, 77300457, 15148031, 26664538, 90310261, 83651211, 72725103, 20642888, 4095116, 27411561, 35996293, 80251430, 17539625, 13231279, 96852321, 86001008, 83133790, 71965942, 38510840, 27185644, 16445503, 3487592, 9623492, 95581843, 3880712, 3183975, 74137926, 8651647, 81274566, 59981773, 47792865, 36580610, 49236559, 749283, 48675329, 37957788, 9860195, 59405277, 6525948, 69848388, 20681921, 24058273, 21070110, 53648154, 82178706, 22942635, 68110330, 12416768, 3773993, 37224844, 19486173, 76434144, 92071990, 73168565, 61728685, 55850790, 82886381, 23134715, 48088883, 3233569, 30543215, 36685023, 16019925, 874791, 84293052, 78909561, 37739481, 35192533, 61741594, 37560164, 77620120, 75986488, 6793819, 16099750, 98371444, 92998591, 27187213, 74724075, 63152504, 79136082, 83747892, 18504197, 39373729, 89811711, 29466406, 43376279, 81774825, 24915585, 27625735, 54232247, 27665211, 33935899, 91938191, 39201414, 73786944, 40356877, 83083131, 40781854, 20002147, 77284619, 98948034, 824872, 57658654, 11757872, 36780454, 31727379, 67513640, 53632373, 82726008, 40534591, 96318094, 61897582, 44983451, 91255408, 22721500, 14220886, 11365791, 8696647, 5111370, 321665, 72358170, 88904910, 61141874, 29065964, 36753250, 24733232, 36139942, 60176618, 76825057, 76540481, 94516935, 91727510, 22435353, 68041839, 45667668, 97561745, 94090109, 78218845, 57961282, 75820087, 84684495, 19939935, 17957593, 72019362, 26998766, 40865610, 65271999, 48395186, 93270084, 66250369, 53842979, 26776922, 508198, 91802888, 51315460, 21919959, 61271144, 1788101, 22200378, 67451935, 65454636, 92398073, 10309525, 32250045, 22997281, 68128438, 38008118, 98130363, 1197320, 61712234, 53547802, 19898053, 55770687, 69605283, 20486294, 62490109, 99604946, 33061250, 30395570, 79738755, 93790285, 55753905, 48360198, 13348726, 45990383, 63059024, 68875490, 98739783, 75777973, 1022677, 43933006, 7300047, 17058722, 51149804, 34698463, 63506281, 73617245, 30653863, 65081429, 84002370, 26507214, 4798568, 61815223, 19101477, 1239555, 92692283, 51047803, 42073124, 63967300, 44889423, 71522968, 18663507, 37620363, 73109997, 51464002, 70541760, 7066775, 55960386, 50007421, 8791066, 41442762, 58751351, 22113133, 68644627, 77787724, 74357852, 85711894, 32058615, 7517032, 82651278, 4091162, 74110882, 79806380, 65047700, 63015256, 76960303, 50188404, 56153345, 29401781, 85571389, 60393039, 56484900, 55247455, 73021291, 79322415, 74452589, 86022504, 24168411, 26744917, 57240218, 50702367, 40686254, 47738185, 48774913, 21397057, 44060493, 5832946, 54058788, 30163921, 2717150, 69697787, 2917920, 45306070, 62452034, 71083565, 32274392, 83368048, 31904591, 93566986, 44842615, 9599614, 93057697, 59109400, 8115266, 90158785, 35512853, 45407418, 92787493, 41092172, 26139110, 26493119, 29516592, 92215320, 3509435, 3233084, 35092039, 78785507, 61623915, 6221471, 54762643, 62357986, 7104732, 58224549, 73235980, 28787861, 96735716, 57248122, 79922758, 33628349, 9259676, 75052463, 68816413, 36933038, 91990218, 63628376, 64848072, 9829782, 18466635, 17365188, 14626618, 34493392, 64098930, 20836893, 89499542, 85695762, 21993752, 22879907, 38009615, 86460488, 61373987, 1569515, 20867149, 31733363, 40027975, 15064655, 30090481, 68000591, 9575954, 3235882, 47213183, 66116458, 92554120, 77301523, 37183543, 93933709, 6111563, 98653983, 55189057, 66271566, 52293995, 99729357, 79880247, 48892201, 91957544, 15535065, 39847321, 92604458, 96906410, 70372191, 59177718, 112651, 66885828, 44245960, 2204165, 51426311, 18783702, 4099191, 4508700, 34044787, 95957797, 78766358, 16054533, 59910624, 36135, 68316156, 57359924, 69355476, 1204161, 19272365, 66428795, 4069912, 50668599, 9257405, 10264691, 18806856, 64055960, 72357096, 62496012, 34497327, 89699445, 99333375, 26292919, 1250437, 99224392, 4064751, 94076128, 94595668, 72278539, 4985896, 6521313, 90090964, 89637706, 92803766, 83210802, 40677414, 70800879, 75543508, 59371804, 4434662, 44473167, 8373289, 57241521, 29510992, 88698958, 33304202, 17068582, 8099994, 84840549, 28928175, 10961421, 34698428, 66611759, 88653118, 66667729, 60430369, 7229550, 6038457, 26114953, 60106217, 75229462, 88444207, 26397786, 26392416, 67084351, 57393458, 54014062, 70036438, 20885148, 15948937, 90061527, 15163258, 32159704, 15015906, 3633375, 78884452, 35456853, 8807940, 38061439, 44177011, 70004753, 83789391, 67031644, 44784505, 52613508, 30569392, 38341669, 62803858, 33123618, 60102965, 4204661, 53666583, 42199455, 82052050, 76671482, 84904436, 415901, 38365584, 45428665, 85116755, 9188443, 18411915, 89214616, 99690194, 36808486, 86118021, 17146629, 99297164, 71333116, 61859143, 52060076, 90654836, 23194618, 72238278, 29994197, 1375023, 99524975, 4668450, 14731700, 66319530, 5822202, 66663942, 70420215, 41092102, 33201905, 33565483, 15536795, 12664567, 36396314, 99515901, 76330843, 22129328, 79821897, 59318837, 37891451, 34432810, 65851721, 44550764, 62693428, 65017137, 45049308, 54517921, 45075471, 32161669, 23848565, 22405120, 83533741, 14947650, 97940276, 97281847, 13173644, 44348328, 69641513, 98462867, 90013093, 17894977, 91141395, 66045587, 82427263, 85023028, 14093520, 42237907, 30366150, 84406788, 4199704, 44915235, 51590803, 72495719, 77272628, 81677380, 62430984, 34236719, 24591705, 6153406, 73222868, 62232211, 30787683, 65880522, 64602895, 77377183, 61928316, 65275241, 92867155, 16097038, 72793444, 58180653, 11543098, 66322959, 7955293, 83948335, 41380093, 54868730, 69786756, 33249630, 22766820, 77694700, 7646095, 41245325, 71316369, 38022834, 31126490, 7182642, 97379790, 44479073, 8733068, 94711842, 74441448, 3294781, 36930650, 62762857, 38256849, 45996863, 30771409, 46540998, 99971982, 66832478, 669105, 92353856, 56531125, 19457589, 68824981, 19376156, 99549373, 17764950, 48673079, 33797252, 49598724, 69579137, 72274002, 90457870, 11923835, 3088684, 33895336, 2208785, 20568363, 55470718, 95010552, 51472275, 15075176, 11161731, 53802686, 34667286, 84166196, 16595436, 45794415, 38658347, 24619760, 54263880, 99861373, 8729146, 88130087, 78589145, 62552524, 75407347, 10649306, 42644903, 29932657, 47887470, 61263068, 569864, 82979980, 8913721, 42307449, 62936963, 34295794, 5073754, 81172706, 2607799, 91664334, 7687278, 86798033, 16684829, 72777973, 83302115, 43152977, 61982238, 50567636, 39986008, 45848907, 89078848, 17081350, 7502255, 10453030, 45995325, 82897371, 48893685, 44627776, 16387575, 18833224, 10366309, 91240048, 13470059, 9058407, 93053405, 49882705, 28550822, 8055981, 30998561, 96420429, 55602660, 23569917, 30694952, 98943869, 91831487, 89419466, 93515664, 4978543, 17976208, 6157724, 27325428, 94539824, 80246713, 176257, 64157906, 78561158, 54427233, 55615885, 36505482, 29635537, 32426752, 29029316, 99965001, 16971929, 90004325, 24314885, 92033260, 78300864, 62428472, 57803235, 50806615, 526217, 39553046, 99917599, 30139692, 58208470, 25842078, 63372756, 1936762, 81853704, 40984766, 7423788, 2331773, 62740044, 92541302, 70367851, 6808825, 30811010, 37435892, 83269727, 56955985, 17727650, 97011160, 45381876, 17385531, 29819940, 33336148, 45919976, 26863229, 42782652, 24826575, 67474219, 17037369 +17539625, 14723410, 96726697, 77898274, 44844121, 43933006, 44889423, 74110882, 72777973, 98948034, 74743862, 5111370, 31904591, 68041839, 57961282, 72019362, 22108665, 92604458, 87720882, 86242799, 22721500, 44550764, 93566986, 94090109, 78602717, 14326617, 1788101, 57393458, 67451935, 81677380, 20486294, 8729146, 63059024, 89217461, 30543215, 84293052, 80555751, 93562257, 67281495, 58751351, 5970607, 90004325, 76960303, 94711842, 39986008, 52261574, 7502255, 2917920, 57020481, 54517921, 32274392, 45407418, 48673079, 33431960, 33304202, 10961421, 44664587, 49328608, 85023028, 81274566, 70782102, 22200378, 28734791, 4978543, 14626618, 34236719, 1197320, 85695762, 35456853, 30787683, 47213183, 86301513, 43045786, 13819358, 62803858, 98653983, 13468268, 84904436, 65081429, 62740044, 32590267, 92692283, 42073124, 12303248, 43322743, 7646095, 68644627, 41481685, 73786944, 40781854, 6819644, 30218878, 56424103, 77072625, 79821897, 45306070, 72358170, 82327024, 93057697, 99549373, 59371804, 80316608, 86001008, 38510840, 17894977, 66611759, 9259676, 55470718, 54014062, 49236559, 70036438, 92398073, 10309525, 90061527, 94539824, 45794415, 21070110, 53648154, 22879907, 38009615, 19486173, 62552524, 65275241, 82886381, 93933709, 94360702, 17058722, 99729357, 77620120, 6793819, 32426752, 22766820, 37620363, 18504197, 56473732, 25636669, 41442762, 77787724, 27041967, 88047921, 49641577, 66428795, 23194618, 60393039, 67030811, 72357096, 59501487, 99333375, 31727379, 17037369, 8128637, 15536795, 17727650, 34946859, 59318837, 97057187, 73031054, 61897582, 86821229, 4985896, 68694897, 29065964, 16387575, 61960400, 92530431, 44842615, 9599614, 60176618, 76825057, 8115266, 72725103, 22405120, 76540481, 97940276, 91727510, 90272749, 57241521, 80251430, 88698958, 8099994, 90457870, 78785507, 88251446, 75153252, 88653118, 66667729, 28796059, 60430369, 57248122, 6038457, 26114953, 33628349, 95290172, 13862149, 64848072, 51472275, 37957788, 11161731, 18466635, 62430984, 89996536, 59614746, 54663246, 17857111, 81853704, 32699744, 81898046, 80246713, 3773993, 44177011, 20867149, 37224844, 5482538, 78589145, 67031644, 68000591, 9575954, 30569392, 39801351, 55850790, 47887470, 17016156, 29959549, 77301523, 23134715, 58180653, 11543098, 54427233, 26507214, 23110625, 85116755, 9188443, 18411915, 48658605, 96906410, 99690194, 69786756, 59177718, 36808486, 99965001, 18783702, 4099191, 47090124, 33699435, 34044787, 95957797, 85711894, 59910624, 16424199, 36135, 61859143, 4091162, 4119747, 54232247, 91938191, 49271185, 86798033, 14363867, 4069912, 68204242, 10264691, 43152977, 14731700, 3294781, 20140249, 99226875, 77187825, 41092102, 87710366, 45848907, 12664567, 26292919, 89078848, 17081350, 82726008, 99224392, 46540998, 247198, 94076128, 168541, 53888755, 54987042, 669105, 72278539, 91255408, 8696647, 321665, 44846932, 19457589, 65038678, 43506672, 43501211, 91240048, 69136837, 83651211, 4787945, 26102057, 83533741, 26139110, 93053405, 8373289, 19891772, 81855445, 44348328, 43357947, 76315420, 28928175, 61623915, 75128745, 63372756, 58224549, 73235980, 9623492, 45237957, 68702632, 79657802, 30998561, 4515343, 62115552, 74137926, 30694952, 51315460, 20568363, 83150534, 36580610, 47893286, 93515664, 59405277, 6157724, 65454636, 15948937, 32159704, 84166196, 6525948, 3633375, 6153406, 19898053, 69605283, 73222868, 30891921, 38061439, 12571310, 24826575, 48360198, 30090481, 44784505, 92071990, 5640302, 10649306, 31161687, 75777973, 33553959, 82979980, 37183543, 42967683, 6111563, 16097038, 53666583, 85117092, 48260151, 55615885, 2331773, 62936963, 36505482, 35192533, 61815223, 61741594, 38365584, 73124510, 92541302, 75986488, 51047803, 71300104, 27187213, 12348118, 74724075, 71522968, 63152504, 41245325, 86118021, 89811711, 59329511, 52204879, 2607799, 16054533, 81774825, 69355476, 99021067, 9257405, 83302115, 8733068, 37435892, 24953498, 4668450, 67474219, 77284619, 66663942, 57658654, 36930650, 97783876, 55669657, 24168411, 89699445, 67513640, 36396314, 53632373, 6986898, 40686254, 1250437, 21397057, 71657078, 54058788, 34432810, 94595668, 67793644, 97641116, 11365791, 94911072, 91281584, 36753250, 95251277, 61859581, 84349107, 38645117, 13470059, 35512853, 17764950, 20642888, 79942022, 75543508, 4434662, 97561745, 29516592, 29510992, 84684495, 96852321, 83133790, 21001913, 27185644, 22450468, 58749, 34698428, 65271999, 48395186, 7104732, 8055981, 91802888, 3183975, 8651647, 9860968, 91831487, 42947632, 75229462, 21919959, 36933038, 63628376, 749283, 4199704, 6497830, 51590803, 72495719, 20836893, 61712234, 78884452, 21993752, 38658347, 40984766, 24619760, 61373987, 70004753, 83789391, 31733363, 75135448, 78561158, 66116458, 98739783, 21289531, 77312810, 39171895, 48088883, 89804152, 55189057, 36685023, 51149804, 52293995, 34698463, 16019925, 51507425, 874791, 79880247, 4798568, 72614359, 1239555, 91957544, 41380093, 72738685, 45428665, 70372191, 92998591, 66885828, 47708850, 18663507, 2204165, 71469330, 73109997, 6808825, 51426311, 50007421, 56461322, 55143724, 82651278, 40781449, 27625735, 37659250, 7687278, 19272365, 72238278, 72373496, 29994197, 39201414, 51466049, 1431742, 20002147, 66319530, 824872, 5822202, 79322415, 86022504, 26744917, 84127901, 50702367, 83155442, 76330843, 48774913, 82779622, 40534591, 96318094, 16380211, 50806615, 30163921, 44983451, 90090964, 82897371, 92353856, 88904910, 44627776, 36812683, 45049308, 77300457, 45075471, 79191827, 15148031, 24733232, 32161669, 10366309, 51588015, 4806458, 92787493, 9058407, 27411561, 22435353, 45790169, 33797252, 49598724, 26493119, 47361209, 58208470, 92215320, 98462867, 26863229, 3233084, 90013093, 72274002, 99658235, 3487592, 91141395, 36312813, 54199160, 33895336, 26776922, 66045587, 508198, 82427263, 2208785, 55602660, 98943869, 1936762, 14093520, 84406788, 9829782, 34667286, 77272628, 22997281, 69848388, 98130363, 65715134, 53393358, 89499542, 82178706, 62232211, 22942635, 15902805, 68110330, 12416768, 99604946, 33061250, 1569515, 176257, 79738755, 93790285, 64602895, 88130087, 76434144, 45990383, 3235882, 69255765, 68875490, 92554120, 92867155, 1022677, 61263068, 7300047, 3233569, 42199455, 82052050, 66271566, 42307449, 73617245, 46851987, 37739481, 7955293, 48892201, 49597667, 29188588, 9175338, 63967300, 5073754, 44245960, 6505939, 51464002, 7066775, 8791066, 4508700, 33237508, 22113133, 16971929, 31126490, 7182642, 97379790, 7517032, 71920426, 79806380, 24314885, 63015256, 16684829, 50188404, 56153345, 50668599, 40356877, 16567550, 95726235, 47298834, 56484900, 74441448, 60955663, 73021291, 61982238, 34497327, 11757872, 54606384, 57240218, 46870723, 4064751, 29819940, 40152546, 37891451, 2717150, 65017137, 71083565, 39553046, 83368048, 68824981, 44481640, 26664538, 5267545, 83210802, 40677414, 16405341, 85242963, 66137019, 2891150, 30139692, 44473167, 13173644, 13231279, 75820087, 3509435, 69641513, 19939935, 71965942, 17068582, 87160386, 26998766, 68102437, 40865610, 28550822, 11923835, 54762643, 66250369, 3880712, 96420429, 7229550, 78549759, 23569917, 38494874, 66903004, 47792865, 95010552, 61271144, 88444207, 91990218, 36189527, 9860195, 34493392, 15163258, 30764367, 26734892, 67227442, 53547802, 70596786, 55770687, 21673260, 8807940, 70727211, 99861373, 65880522, 77377183, 61928316, 52613508, 8129978, 95395112, 20765474, 42644903, 38341669, 62051033, 73168565, 29932657, 60581278, 37675718, 86543538, 72793444, 40197395, 8913721, 76703609, 63506281, 66322959, 30463802, 16099750, 7685448, 33249630, 29029316, 79136082, 83747892, 17146629, 99297164, 71333116, 29834512, 78766358, 14045383, 32058615, 24915585, 30811010, 90654836, 28851716, 82532312, 27665211, 65047700, 92033260, 85571389, 45919976, 12024238, 31491938, 55247455, 20645197, 64055960, 62496012, 9603598, 37166608, 33201905, 83269727, 33565483, 56955985, 45381876, 45996863, 22129328, 14349098, 5832946, 10453030, 45995325, 99971982, 65851721, 6521313, 67124282, 56515456, 62693428, 89637706, 62452034, 61141874, 18833224, 82339363, 23848565, 36139942, 4095116, 35996293, 33336148, 97281847, 17957593, 51715482, 93270084, 95581843, 81805959, 26392416, 20885148, 97011160, 68128438, 7919588, 20681921, 62490109, 15064655, 55753905, 59197747, 64157906, 98648327, 61728685, 60102965, 30653863, 44847298, 36845587, 78909561, 19101477, 415901, 37560164, 34295794, 29635537, 57163802, 42692881, 77694700, 81172706, 37501808, 70541760, 7011964, 39373729, 91664334, 68316156, 52060076, 57359924, 20122224, 72732205, 65074535, 44479073, 96193415, 62762857, 36780454, 62428472, 11581181, 50567636, 37192445, 77413012, 96709982, 17385531, 47738185, 6871053, 48893685, 99917599, 19376156, 92803766, 90310261, 23432750, 41092172, 94516935, 45617087, 78218845, 69579137, 21533347, 25842078, 35092039, 93359396, 84840549, 3088684, 96735716, 68816413, 26397786, 10358899, 15075176, 32250045, 27325428, 17365188, 64098930, 24591705, 24058273, 86460488, 30395570, 89046466, 87598888, 40027975, 569864, 26275734, 26063929, 83948335, 98371444, 54868730, 23740167, 55960386, 71316369, 38022834, 43376279, 92692978, 33935899, 29401781, 1375023, 78300864, 83083131, 17386996, 99515901, 69697787, 9398733, 56531125, 99125126, 9886593, 74614639, 90158785, 45667668, 18001617, 8634541, 16445503, 49882705, 53084256, 79922758, 89419466, 59981773, 67084351, 37280276, 53802686, 65338021, 64087743, 4204661, 15535065, 112651, 18131876, 18699206, 1204161, 74452589, 38256849, 68939068, 30771409, 32151165, 66832478, 526217, 59109400, 14947650, 28787861, 42782652, 75052463, 60106217, 36468541, 42237907, 15015906, 73392814, 54263880, 7423788, 80014588, 39847321, 89214616, 70367851, 74357852, 18806856, 99524975, 57803235, 44060493, 10597197, 14220886, 70800879, 30366150, 48675329, 44915235, 76170907, 13348726, 75407347, 33123618, 76671482, 84002370, 29466406, 70420215, 6221471, 62357986, 53842979, 17976208, 38008118, 16595436, 84187166 +7685448, 94911072, 24733232, 6505939, 80316608, 57241521, 54762643, 1788101, 53802686, 78589145, 37739481, 83948335, 57163802, 51426311, 37659250, 45848907, 4985896, 44846932, 32161669, 5267545, 29516592, 8099994, 66250369, 26397786, 48675329, 44915235, 176257, 82886381, 77301523, 42199455, 16019925, 84293052, 4798568, 77620120, 36808486, 39373729, 14045383, 19272365, 44479073, 57658654, 15536795, 54058788, 32151165, 90090964, 36753250, 43501211, 23432750, 97940276, 3509435, 19939935, 99658235, 48395186, 36312813, 95581843, 28796059, 3880712, 79657802, 62115552, 85023028, 30764367, 64098930, 62490109, 30395570, 75407347, 60581278, 39171895, 76671482, 13468268, 874791, 79880247, 55615885, 35192533, 39847321, 98371444, 12303248, 42692881, 29029316, 51464002, 8791066, 59329511, 81774825, 77898274, 52060076, 50188404, 99021067, 40356877, 94711842, 20002147, 72357096, 99333375, 29819940, 79821897, 59318837, 73031054, 2717150, 48893685, 45306070, 56531125, 89637706, 72358170, 74614639, 59109400, 60176618, 90158785, 48673079, 66137019, 4434662, 69641513, 35092039, 88653118, 68702632, 49328608, 55602660, 78602717, 20568363, 9860968, 60106217, 36580610, 10358899, 20885148, 17857111, 81853704, 44844121, 13348726, 9575954, 20765474, 17016156, 1022677, 37675718, 23134715, 86543538, 43933006, 80014588, 84904436, 30463802, 9175338, 16099750, 51047803, 99690194, 59177718, 81172706, 23740167, 29466406, 74357852, 96726697, 88047921, 32058615, 24915585, 30811010, 72732205, 7687278, 1204161, 76960303, 29994197, 12024238, 60393039, 47298834, 43152977, 78300864, 4668450, 67474219, 77284619, 30218878, 89699445, 11581181, 31727379, 26744917, 26292919, 53632373, 46870723, 4064751, 48774913, 82779622, 46540998, 6871053, 10597197, 86821229, 6521313, 43506672, 83368048, 9599614, 69136837, 35512853, 85242963, 91727510, 22435353, 90272749, 69579137, 96852321, 86001008, 43357947, 26998766, 62357986, 7104732, 60430369, 23569917, 59981773, 70782102, 26392416, 13862149, 749283, 4199704, 15075176, 34667286, 51590803, 97011160, 14626618, 62430984, 69848388, 61712234, 32699744, 6153406, 54263880, 89046466, 70727211, 40027975, 75135448, 59197747, 3235882, 52613508, 78561158, 63059024, 39801351, 38341669, 31161687, 55850790, 77312810, 89217461, 94360702, 48088883, 51149804, 85117092, 48260151, 62740044, 36845587, 45428665, 9188443, 54868730, 5073754, 37501808, 18504197, 41245325, 4099191, 56461322, 4508700, 5970607, 77787724, 27041967, 55143724, 49641577, 65047700, 23194618, 72777973, 9257405, 1431742, 6819644, 36930650, 38256849, 33201905, 84187166, 50567636, 57803235, 83155442, 168541, 30163921, 54987042, 14220886, 11365791, 56515456, 62693428, 88904910, 16387575, 45075471, 15148031, 61859581, 36139942, 83651211, 22405120, 4095116, 79942022, 49598724, 18001617, 97561745, 30139692, 13173644, 21533347, 88698958, 17894977, 76315420, 87160386, 68102437, 3487592, 34698428, 58224549, 73235980, 44664587, 82427263, 96420429, 8651647, 89419466, 75229462, 83150534, 54014062, 37280276, 36189527, 17976208, 90061527, 59614746, 89499542, 80246713, 38009615, 12416768, 99604946, 33061250, 76170907, 65880522, 64157906, 98648327, 47213183, 7423788, 10649306, 92554120, 75777973, 29932657, 21289531, 61263068, 89804152, 17058722, 42307449, 99729357, 26507214, 72614359, 44847298, 73124510, 15535065, 70372191, 112651, 63967300, 93562257, 70541760, 18783702, 7011964, 58751351, 34044787, 71333116, 29834512, 54232247, 71920426, 65074535, 87720882, 73786944, 45919976, 8733068, 99524975, 56484900, 40781854, 73021291, 59501487, 5822202, 56424103, 96193415, 79322415, 74452589, 54606384, 36780454, 83269727, 68939068, 12664567, 77413012, 84127901, 17081350, 17385531, 99224392, 5832946, 45995325, 94076128, 16380211, 67793644, 50806615, 669105, 44550764, 8696647, 5111370, 62452034, 57020481, 77300457, 95251277, 44481640, 83210802, 76825057, 8115266, 35996293, 33797252, 68041839, 33336148, 26493119, 8373289, 33431960, 80251430, 19891772, 75820087, 3233084, 83133790, 71965942, 38510840, 72274002, 16445503, 22450468, 84840549, 61623915, 53084256, 91141395, 96735716, 508198, 91802888, 78549759, 91831487, 88444207, 22200378, 6497830, 37957788, 9860195, 59405277, 32250045, 27325428, 17365188, 15163258, 89996536, 84166196, 53393358, 70596786, 85695762, 21070110, 20486294, 73222868, 22942635, 21673260, 8807940, 40984766, 44177011, 70004753, 83789391, 99861373, 79738755, 12571310, 55753905, 24826575, 48360198, 88130087, 67031644, 77377183, 22108665, 44784505, 86301513, 8129978, 43045786, 65275241, 62803858, 33123618, 47887470, 93933709, 7300047, 58180653, 82052050, 30543215, 66271566, 52293995, 34698463, 51507425, 73617245, 78909561, 36505482, 61815223, 7955293, 61741594, 48892201, 38365584, 37560164, 41380093, 92541302, 29188588, 6793819, 92998591, 22766820, 71522968, 18663507, 83747892, 99965001, 17146629, 99297164, 33237508, 71316369, 2607799, 91664334, 31126490, 7182642, 16054533, 97379790, 40781449, 4119747, 92692978, 33935899, 86798033, 92033260, 63015256, 56153345, 37435892, 20645197, 74441448, 86242799, 14731700, 98948034, 824872, 99226875, 70420215, 77072625, 9603598, 97783876, 33565483, 56955985, 37192445, 8128637, 57240218, 82726008, 22129328, 47738185, 30771409, 99971982, 61897582, 74743862, 9398733, 44627776, 36812683, 19457589, 65038678, 71083565, 99917599, 68824981, 44842615, 19376156, 72725103, 41092172, 16405341, 27411561, 75543508, 45790169, 97281847, 29510992, 13231279, 84684495, 17957593, 49882705, 28550822, 75153252, 93270084, 66045587, 4515343, 7229550, 6038457, 3183975, 26114953, 33628349, 1936762, 66903004, 47792865, 61271144, 67084351, 57393458, 49236559, 9829782, 51472275, 70036438, 28734791, 6157724, 65454636, 15948937, 18466635, 32159704, 34236719, 7919588, 20836893, 15015906, 65715134, 53547802, 73392814, 78884452, 15902805, 64087743, 3773993, 1569515, 31733363, 64602895, 98739783, 95395112, 13819358, 62051033, 92867155, 61728685, 82979980, 16097038, 60102965, 4204661, 3233569, 53666583, 72793444, 55189057, 76703609, 63506281, 62936963, 32590267, 23110625, 75986488, 48658605, 89214616, 42073124, 27187213, 12348118, 70367851, 71469330, 73109997, 63152504, 86118021, 25636669, 89811711, 22113133, 95957797, 52204879, 78766358, 85711894, 82651278, 90654836, 27625735, 69355476, 74110882, 82532312, 18699206, 91938191, 14363867, 4069912, 24953498, 55247455, 60955663, 66319530, 66663942, 77187825, 37166608, 67513640, 39986008, 45381876, 45996863, 89078848, 50702367, 40686254, 96709982, 99515901, 71657078, 34946859, 40534591, 10453030, 40152546, 34432810, 97057187, 97641116, 53888755, 44983451, 72278539, 29065964, 91281584, 45049308, 526217, 18833224, 61960400, 32274392, 92530431, 31904591, 93566986, 82339363, 82327024, 10366309, 91240048, 92803766, 40677414, 13470059, 4806458, 45407418, 92787493, 76540481, 93053405, 8634541, 17539625, 44348328, 98462867, 26863229, 21001913, 27185644, 17068582, 72019362, 75128745, 66667729, 28787861, 53842979, 33895336, 26776922, 42782652, 57248122, 51315460, 75052463, 98943869, 55470718, 21919959, 95290172, 36468541, 42237907, 30366150, 84406788, 47893286, 93515664, 72495719, 77272628, 94539824, 6525948, 98130363, 26734892, 20681921, 45794415, 38658347, 35456853, 53648154, 82178706, 22879907, 24619760, 93790285, 8729146, 5482538, 76434144, 30569392, 66116458, 5640302, 68875490, 42644903, 73168565, 29959549, 42967683, 6111563, 98653983, 40197395, 8913721, 80555751, 415901, 1239555, 49597667, 34295794, 29635537, 69786756, 32426752, 33249630, 74724075, 44245960, 7646095, 79136082, 67281495, 7066775, 55960386, 50007421, 56473732, 41442762, 38022834, 43376279, 59910624, 79806380, 16684829, 39201414, 31491938, 83083131, 67030811, 3294781, 62496012, 62762857, 86022504, 17037369, 6986898, 37891451, 91255408, 68694897, 2917920, 65017137, 54517921, 39553046, 93057697, 84349107, 51588015, 17764950, 20642888, 26102057, 45667668, 92215320, 25842078, 33304202, 90013093, 93359396, 78785507, 88251446, 58749, 65271999, 6221471, 66611759, 3088684, 79922758, 81805959, 30694952, 81274566, 42947632, 64848072, 67451935, 4978543, 11161731, 92398073, 81677380, 68128438, 67227442, 19898053, 81898046, 68110330, 86460488, 30787683, 20867149, 19486173, 61928316, 45990383, 92071990, 69255765, 36685023, 11543098, 26063929, 54427233, 30653863, 2331773, 91957544, 18411915, 92604458, 66885828, 77694700, 47708850, 2204165, 6808825, 33699435, 68644627, 18131876, 36135, 68316156, 7517032, 57359924, 90004325, 4091162, 20122224, 24314885, 49271185, 50668599, 72238278, 85571389, 10264691, 51466049, 20140249, 61982238, 24168411, 36396314, 17386996, 1250437, 7502255, 247198, 65851721, 66832478, 22721500, 92353856, 69697787, 99125126, 26664538, 70800879, 83533741, 94516935, 14947650, 2891150, 26139110, 45617087, 47361209, 57961282, 8055981, 54199160, 30998561, 2208785, 74137926, 95010552, 91990218, 10309525, 22997281, 54663246, 14723410, 1197320, 3633375, 69605283, 38061439, 61373987, 62552524, 569864, 37183543, 66322959, 84002370, 19101477, 43322743, 16424199, 28851716, 66428795, 29401781, 18806856, 11757872, 55669657, 62428472, 52261574, 82897371, 321665, 9886593, 79191827, 4787945, 44473167, 78218845, 81855445, 90457870, 40865610, 11923835, 63372756, 9623492, 9259676, 36933038, 34493392, 38008118, 16595436, 55770687, 30891921, 37224844, 15064655, 26275734, 65081429, 46851987, 92692283, 72738685, 44889423, 37620363, 61859143, 68204242, 72373496, 95726235, 83302115, 1375023, 41092102, 14349098, 21397057, 96318094, 67124282, 23848565, 90310261, 38645117, 99549373, 28928175, 10961421, 38494874, 14093520, 14326617, 63628376, 24591705, 24058273, 21993752, 87598888, 68000591, 33553959, 71300104, 96906410, 47090124, 16971929, 27665211, 16567550, 34497327, 76330843, 17727650, 44060493, 94595668, 59371804, 94090109, 58208470, 45237957, 68816413, 65338021, 41481685, 64055960, 87710366, 61141874, 9058407, 51715482, 62232211, 30090481, 85116755 +83651211, 72019362, 75128745, 6038457, 75229462, 64602895, 76434144, 26507214, 29029316, 91664334, 77187825, 62428472, 50806615, 30163921, 75153252, 49236559, 92071990, 61728685, 77301523, 11543098, 54427233, 29188588, 67281495, 78766358, 81774825, 76960303, 73786944, 34497327, 1250437, 52261574, 32151165, 669105, 11365791, 19457589, 92530431, 4787945, 40865610, 6221471, 66611759, 66667729, 33895336, 4515343, 36933038, 749283, 94539824, 34236719, 65715134, 45794415, 68000591, 62552524, 44784505, 75407347, 62051033, 29959549, 33553959, 37183543, 30543215, 42073124, 6808825, 41442762, 68644627, 16424199, 4069912, 87720882, 1375023, 24953498, 3294781, 79322415, 37192445, 45848907, 74743862, 94911072, 65038678, 31904591, 36139942, 45407418, 79942022, 94090109, 69641513, 26863229, 38510840, 78785507, 82427263, 2208785, 96420429, 62115552, 38494874, 57393458, 48675329, 44915235, 10309525, 81853704, 70596786, 89499542, 81898046, 176257, 37224844, 29932657, 82886381, 89804152, 52293995, 55615885, 36845587, 70372191, 32426752, 22766820, 7646095, 37620363, 96726697, 31126490, 4091162, 90654836, 54232247, 7687278, 66428795, 68204242, 99524975, 83083131, 14731700, 61982238, 36930650, 89699445, 8128637, 57803235, 6986898, 29819940, 59318837, 66832478, 53888755, 92353856, 48893685, 9886593, 526217, 95251277, 32274392, 32161669, 23848565, 99549373, 4095116, 85242963, 4434662, 33797252, 49598724, 97281847, 33431960, 92215320, 93359396, 11923835, 8055981, 66250369, 66045587, 78549759, 30694952, 1936762, 68816413, 55470718, 70782102, 37280276, 92398073, 81677380, 22997281, 32159704, 17857111, 16595436, 24058273, 22942635, 44177011, 70004753, 15064655, 8729146, 5482538, 67031644, 47213183, 69255765, 30569392, 63059024, 92554120, 65275241, 17016156, 48088883, 4204661, 40197395, 66271566, 80014588, 79880247, 36505482, 35192533, 48892201, 32590267, 72738685, 16099750, 59177718, 63967300, 92998591, 27187213, 93562257, 79136082, 4099191, 22113133, 5970607, 74357852, 85711894, 40781449, 27665211, 92033260, 29401781, 72777973, 824872, 11757872, 86022504, 24168411, 57240218, 17386996, 99515901, 17727650, 47738185, 34946859, 96318094, 16380211, 34432810, 73031054, 168541, 54987042, 44983451, 72278539, 14220886, 8696647, 2917920, 9398733, 56515456, 321665, 44846932, 9599614, 38645117, 4806458, 48673079, 26102057, 66137019, 59371804, 93053405, 33336148, 26493119, 30139692, 8373289, 57241521, 17539625, 13231279, 44348328, 90013093, 76315420, 99658235, 28928175, 58749, 63372756, 48395186, 58224549, 3088684, 28787861, 26776922, 96735716, 91802888, 33628349, 21919959, 26397786, 6157724, 65454636, 15163258, 14723410, 69848388, 98130363, 1197320, 61712234, 15015906, 32699744, 53547802, 19898053, 35456853, 53648154, 73222868, 30891921, 80246713, 15902805, 68110330, 99604946, 61373987, 70727211, 75135448, 24826575, 9575954, 52613508, 66116458, 68875490, 21289531, 37675718, 77312810, 93933709, 6111563, 60102965, 17058722, 42199455, 36685023, 51149804, 26063929, 72614359, 44847298, 80555751, 1239555, 34295794, 15535065, 6793819, 29635537, 9175338, 85116755, 7685448, 96906410, 99690194, 42692881, 37501808, 50007421, 56461322, 99297164, 39373729, 88047921, 14045383, 41481685, 32058615, 7517032, 90004325, 4119747, 69355476, 74110882, 18699206, 91938191, 99021067, 40356877, 10264691, 95726235, 74441448, 40781854, 77284619, 98948034, 30218878, 72357096, 9603598, 87710366, 50567636, 12664567, 40686254, 17081350, 46870723, 5832946, 79821897, 94076128, 99971982, 22721500, 90090964, 44550764, 45306070, 5111370, 89637706, 71083565, 51588015, 41092172, 91727510, 2891150, 26139110, 80316608, 22435353, 90272749, 44473167, 78218845, 81855445, 84684495, 19939935, 27185644, 68102437, 28550822, 53084256, 91141395, 65271999, 45237957, 36312813, 68702632, 54199160, 79657802, 49328608, 60430369, 79922758, 74137926, 51315460, 9259676, 47792865, 88444207, 1788101, 67084351, 91990218, 22200378, 28734791, 59405277, 20885148, 15948937, 32250045, 77272628, 97011160, 14626618, 34493392, 62430984, 6525948, 20836893, 65338021, 21993752, 38658347, 20486294, 40984766, 86460488, 30395570, 79738755, 55753905, 59197747, 48360198, 88130087, 3235882, 43045786, 39801351, 42644903, 75777973, 60581278, 1022677, 42967683, 86543538, 43933006, 58180653, 55189057, 48260151, 46851987, 4798568, 62936963, 78909561, 415901, 49597667, 41380093, 75986488, 45428665, 9188443, 98371444, 33249630, 44889423, 81172706, 71469330, 36808486, 23740167, 99965001, 25636669, 58751351, 34044787, 77787724, 97379790, 49271185, 65047700, 19272365, 50668599, 72373496, 51466049, 94711842, 60393039, 47298834, 55247455, 64055960, 78300864, 60955663, 67474219, 99226875, 97783876, 83269727, 17037369, 39986008, 45381876, 50702367, 83155442, 96709982, 17385531, 82726008, 4064751, 48774913, 44060493, 30771409, 6871053, 40152546, 65851721, 86821229, 68694897, 65017137, 88904910, 36812683, 43506672, 74614639, 54517921, 45075471, 39553046, 79191827, 82339363, 44481640, 15148031, 26664538, 61859581, 44842615, 93057697, 84349107, 69136837, 83210802, 40677414, 8115266, 13470059, 92787493, 27411561, 94516935, 14947650, 45667668, 8634541, 80251430, 29510992, 57961282, 98462867, 72274002, 90457870, 51715482, 88251446, 61623915, 34698428, 7104732, 30998561, 78602717, 20568363, 8651647, 9860968, 14326617, 83150534, 36468541, 64848072, 9829782, 70036438, 36189527, 6497830, 67451935, 18466635, 68128438, 54663246, 7919588, 24591705, 20681921, 3633375, 6153406, 55770687, 85695762, 21070110, 62490109, 12416768, 64087743, 1569515, 20867149, 83789391, 31733363, 12571310, 78589145, 13348726, 30090481, 86301513, 10649306, 92867155, 61263068, 89217461, 23134715, 16097038, 94360702, 53666583, 26275734, 82052050, 8913721, 76703609, 51507425, 84293052, 73617245, 84904436, 30653863, 2331773, 84002370, 19101477, 38365584, 37560164, 92692283, 77620120, 51047803, 89214616, 63152504, 51426311, 86118021, 56473732, 7011964, 33237508, 89811711, 29834512, 16971929, 52204879, 43376279, 68316156, 82651278, 27625735, 71920426, 82532312, 79806380, 33935899, 1204161, 14363867, 83302115, 12024238, 86242799, 4668450, 66319530, 5822202, 66663942, 41092102, 11581181, 99333375, 56955985, 26292919, 84127901, 76330843, 82779622, 7502255, 46540998, 10453030, 45995325, 97057187, 97641116, 91255408, 62452034, 72358170, 61141874, 44627776, 43501211, 83368048, 99917599, 93566986, 68824981, 90310261, 60176618, 72725103, 9058407, 83533741, 97940276, 68041839, 29516592, 19891772, 75820087, 25842078, 83133790, 35092039, 17068582, 43357947, 87160386, 49882705, 10961421, 88653118, 9623492, 95581843, 55602660, 81805959, 91831487, 61271144, 26392416, 13862149, 84406788, 93515664, 4978543, 17976208, 9860195, 17365188, 84166196, 26734892, 73392814, 62232211, 38009615, 38061439, 54263880, 99861373, 40027975, 76170907, 19486173, 64157906, 98648327, 61928316, 78561158, 5640302, 20765474, 38341669, 31161687, 47887470, 39171895, 98653983, 34698463, 42307449, 16019925, 66322959, 61741594, 91957544, 73124510, 23110625, 92541302, 18411915, 48658605, 69786756, 43322743, 66885828, 77694700, 74724075, 70367851, 73109997, 83747892, 18504197, 70541760, 55960386, 18783702, 47090124, 8791066, 59329511, 2607799, 55143724, 16054533, 59910624, 52060076, 57359924, 30811010, 92692978, 65074535, 44479073, 23194618, 72238278, 9257405, 85571389, 43152977, 37435892, 20645197, 6819644, 67030811, 73021291, 20140249, 62496012, 37166608, 77413012, 99224392, 21397057, 247198, 94595668, 2717150, 82897371, 69697787, 56531125, 99125126, 29065964, 57020481, 36753250, 77300457, 18833224, 61960400, 10366309, 19376156, 91240048, 23432750, 35512853, 22405120, 16405341, 70800879, 75543508, 45617087, 97561745, 69579137, 88698958, 96852321, 86001008, 33304202, 17894977, 8099994, 26998766, 73235980, 3880712, 53842979, 57248122, 3183975, 26114953, 23569917, 75052463, 85023028, 14093520, 95290172, 95010552, 54014062, 37957788, 59614746, 38008118, 21673260, 8807940, 33061250, 93790285, 77377183, 45990383, 8129978, 7423788, 98739783, 95395112, 569864, 7300047, 76671482, 13468268, 62740044, 37739481, 30463802, 92604458, 12303248, 5073754, 12348118, 47708850, 2204165, 7066775, 17146629, 71316369, 29466406, 7182642, 49641577, 36135, 61859143, 20122224, 86798033, 63015256, 18806856, 1431742, 20002147, 59501487, 56424103, 96193415, 77072625, 57658654, 74452589, 55669657, 38256849, 54606384, 36780454, 31727379, 67513640, 45996863, 71657078, 67793644, 4985896, 24733232, 82327024, 5267545, 90158785, 76540481, 13173644, 21533347, 58208470, 3509435, 3233084, 17957593, 71965942, 21001913, 54762643, 44664587, 93270084, 28796059, 42782652, 98943869, 66903004, 81274566, 89419466, 59981773, 42947632, 42237907, 36580610, 51472275, 53802686, 51590803, 30764367, 64098930, 67227442, 78884452, 69605283, 24619760, 65880522, 13819358, 73168565, 55850790, 33123618, 82979980, 85117092, 874791, 54868730, 112651, 6505939, 41245325, 4508700, 38022834, 71333116, 77898274, 72732205, 24314885, 16684829, 29994197, 39201414, 16567550, 8733068, 62762857, 33565483, 84187166, 26744917, 36396314, 22129328, 40534591, 54058788, 10597197, 61897582, 62693428, 91281584, 16387575, 17764950, 45790169, 18001617, 47361209, 16445503, 22450468, 84840549, 7229550, 47893286, 4199704, 34667286, 90061527, 53393358, 89046466, 62803858, 72793444, 61815223, 39847321, 57163802, 71300104, 44245960, 71522968, 51464002, 33699435, 95957797, 50188404, 56153345, 45919976, 56484900, 33201905, 68939068, 67124282, 45049308, 59109400, 76825057, 35996293, 3487592, 62357986, 60106217, 30366150, 10358899, 63628376, 15075176, 11161731, 72495719, 82178706, 22879907, 3773993, 30787683, 87598888, 3233569, 65081429, 83948335, 24915585, 28851716, 31491938, 15536795, 89078848, 53632373, 6521313, 92803766, 508198, 22108665, 63506281, 7955293, 27041967, 18131876, 37891451, 20642888, 27325428, 89996536, 99729357, 37659250, 14349098, 70420215, 44844121, 18663507 +71300104, 56461322, 44846932, 4787945, 31161687, 21289531, 47090124, 65715134, 98739783, 39171895, 76703609, 51426311, 55960386, 16424199, 96193415, 34432810, 74743862, 17539625, 98462867, 85023028, 37675718, 72738685, 51047803, 12348118, 37620363, 58751351, 95957797, 96726697, 61859143, 90004325, 12024238, 1431742, 11581181, 5832946, 94595668, 86821229, 94911072, 91281584, 75543508, 33431960, 80251430, 92215320, 61623915, 508198, 96420429, 78602717, 70782102, 36580610, 63628376, 67451935, 37957788, 98130363, 7919588, 21993752, 21070110, 19486173, 48360198, 47213183, 61263068, 43933006, 26063929, 19101477, 41380093, 57163802, 47708850, 67281495, 56473732, 5970607, 85711894, 7517032, 37659250, 31491938, 37435892, 20645197, 40781854, 6819644, 30218878, 67513640, 26292919, 99224392, 17727650, 48774913, 54058788, 94076128, 10597197, 82897371, 8696647, 61141874, 16387575, 95251277, 61960400, 79191827, 93057697, 69136837, 72725103, 4806458, 17764950, 4095116, 83533741, 79942022, 80316608, 22435353, 29510992, 57961282, 3509435, 35092039, 16445503, 68702632, 78549759, 9860968, 75229462, 28734791, 59405277, 32250045, 26734892, 53547802, 6153406, 21673260, 62490109, 15902805, 8807940, 83789391, 79738755, 22108665, 8129978, 60581278, 29959549, 77312810, 89217461, 48088883, 17058722, 84293052, 4798568, 30463802, 92692283, 6793819, 112651, 43322743, 6505939, 6808825, 89811711, 18131876, 74357852, 59910624, 82651278, 4119747, 54232247, 1204161, 76960303, 44479073, 99021067, 23194618, 72373496, 39201414, 95726235, 24953498, 74441448, 98948034, 97783876, 33201905, 83269727, 46870723, 40534591, 96318094, 168541, 54987042, 4985896, 11365791, 56515456, 43501211, 83368048, 82327024, 91240048, 26102057, 66137019, 2891150, 26139110, 4434662, 8634541, 58208470, 75820087, 69641513, 8099994, 22450468, 72019362, 68102437, 40865610, 34698428, 65271999, 66611759, 54762643, 88653118, 7104732, 9623492, 26776922, 81805959, 81274566, 42947632, 21919959, 4199704, 6497830, 4978543, 20836893, 19898053, 53648154, 22879907, 37224844, 13348726, 98648327, 78561158, 92071990, 7423788, 5640302, 20765474, 17016156, 77301523, 569864, 33553959, 93933709, 82052050, 66271566, 874791, 73617245, 65081429, 36505482, 38365584, 32590267, 49597667, 73124510, 77620120, 39847321, 70372191, 66885828, 74724075, 44245960, 41245325, 34044787, 49641577, 41481685, 81774825, 4091162, 71920426, 82532312, 27665211, 18699206, 65047700, 4069912, 50188404, 50668599, 83302115, 8733068, 60393039, 47298834, 56484900, 20002147, 824872, 9603598, 84187166, 26744917, 37192445, 84127901, 57240218, 40686254, 82779622, 29819940, 7502255, 6871053, 97641116, 61897582, 91255408, 2917920, 321665, 89637706, 9886593, 36753250, 65038678, 43506672, 93566986, 68824981, 26664538, 32161669, 84349107, 83210802, 8115266, 90158785, 23432750, 35512853, 51588015, 45407418, 59371804, 13173644, 19939935, 25842078, 38510840, 76315420, 28550822, 53084256, 58749, 11923835, 48395186, 45237957, 93270084, 3088684, 66250369, 28787861, 55602660, 62115552, 30694952, 8651647, 98943869, 66903004, 59981773, 47792865, 83150534, 36468541, 61271144, 1788101, 26392416, 70036438, 15075176, 15948937, 62430984, 30764367, 1197320, 32699744, 45794415, 55770687, 69605283, 44844121, 12416768, 24619760, 99861373, 8729146, 78589145, 86301513, 43045786, 95395112, 65275241, 29932657, 82886381, 4204661, 58180653, 76671482, 16019925, 79880247, 46851987, 44847298, 78909561, 35192533, 75986488, 29188588, 48658605, 99690194, 92998591, 33249630, 44889423, 71522968, 73109997, 36808486, 70541760, 4099191, 27041967, 43376279, 78766358, 14045383, 20122224, 92692978, 74110882, 86798033, 68204242, 72238278, 85571389, 40356877, 94711842, 99524975, 60955663, 3294781, 36930650, 79322415, 41092102, 86022504, 99333375, 31727379, 68939068, 45996863, 8128637, 89078848, 53632373, 6986898, 52261574, 67793644, 669105, 5111370, 62693428, 62452034, 29065964, 19457589, 57020481, 32274392, 39553046, 31904591, 44842615, 5267545, 36139942, 40677414, 59109400, 48673079, 76540481, 70800879, 85242963, 14947650, 97940276, 33797252, 33336148, 97281847, 29516592, 8373289, 69579137, 81855445, 21533347, 13231279, 71965942, 21001913, 33304202, 17894977, 87160386, 84840549, 78785507, 63372756, 53842979, 33895336, 96735716, 79922758, 91802888, 3183975, 26114953, 23569917, 38494874, 95290172, 88444207, 54014062, 10358899, 49236559, 37280276, 36189527, 6157724, 34667286, 97011160, 22997281, 14626618, 14723410, 81853704, 67227442, 65338021, 78884452, 73222868, 30891921, 86460488, 99604946, 3773993, 44177011, 89046466, 70004753, 20867149, 40027975, 15064655, 55753905, 88130087, 67031644, 10649306, 92867155, 62803858, 42967683, 86543538, 3233569, 40197395, 36685023, 85117092, 13468268, 84904436, 30653863, 62740044, 415901, 9188443, 16099750, 18411915, 54868730, 63967300, 12303248, 27187213, 29029316, 2204165, 93562257, 23740167, 33699435, 8791066, 17146629, 29834512, 88047921, 16054533, 52060076, 57359924, 72732205, 69355476, 33935899, 19272365, 65074535, 14363867, 56153345, 29401781, 29994197, 45919976, 16567550, 51466049, 55247455, 64055960, 66319530, 99226875, 56424103, 77187825, 87710366, 37166608, 36780454, 33565483, 39986008, 12664567, 57803235, 17385531, 76330843, 4064751, 47738185, 71657078, 79821897, 59318837, 45995325, 40152546, 37891451, 247198, 73031054, 45306070, 67124282, 526217, 18833224, 9599614, 92803766, 99549373, 41092172, 27411561, 93053405, 26493119, 45667668, 45617087, 97561745, 57241521, 44348328, 84684495, 86001008, 27185644, 17068582, 90457870, 26998766, 10961421, 8055981, 44664587, 28796059, 54199160, 3880712, 79657802, 66045587, 2208785, 9259676, 20568363, 60106217, 55470718, 57393458, 13862149, 9829782, 17976208, 9860195, 65454636, 11161731, 92398073, 53802686, 72495719, 17365188, 81677380, 34493392, 15163258, 38008118, 15015906, 20681921, 53393358, 70596786, 85695762, 20486294, 62232211, 81898046, 22942635, 68110330, 38009615, 38061439, 54263880, 61373987, 176257, 87598888, 31733363, 12571310, 75135448, 59197747, 24826575, 5482538, 30090481, 3235882, 75407347, 63059024, 1022677, 16097038, 7300047, 89804152, 72793444, 11543098, 51149804, 52293995, 54427233, 51507425, 80014588, 55615885, 72614359, 36845587, 80555751, 62936963, 37739481, 61815223, 48892201, 1239555, 91957544, 92541302, 15535065, 29635537, 7685448, 69786756, 42073124, 81172706, 70367851, 18663507, 7646095, 37501808, 79136082, 7011964, 4508700, 39373729, 68644627, 38022834, 29466406, 31126490, 55143724, 77898274, 28851716, 79806380, 24314885, 49271185, 92033260, 87720882, 9257405, 18806856, 78300864, 83083131, 4668450, 14731700, 73021291, 72357096, 59501487, 34497327, 77072625, 57658654, 54606384, 45848907, 77413012, 17081350, 30771409, 10453030, 53888755, 2717150, 90090964, 44550764, 92353856, 65017137, 36812683, 77300457, 71083565, 54517921, 45075471, 44481640, 15148031, 24733232, 90310261, 20642888, 22405120, 35996293, 94516935, 91727510, 45790169, 49598724, 18001617, 30139692, 94090109, 78218845, 19891772, 96852321, 17957593, 90013093, 43357947, 93359396, 75153252, 3487592, 6221471, 73235980, 36312813, 30998561, 82427263, 60430369, 57248122, 7229550, 74137926, 33628349, 51315460, 91831487, 14093520, 14326617, 42237907, 749283, 20885148, 32159704, 89996536, 64098930, 61712234, 16595436, 3633375, 24058273, 89499542, 35456853, 82178706, 64087743, 33061250, 30787683, 70727211, 76170907, 65880522, 64157906, 30569392, 68875490, 92554120, 38341669, 75777973, 61728685, 33123618, 37183543, 26275734, 98653983, 8913721, 34698463, 26507214, 7955293, 83948335, 45428665, 92604458, 32426752, 42692881, 71469330, 63152504, 51464002, 7066775, 18783702, 99297164, 22113133, 77787724, 16971929, 36135, 90654836, 7687278, 16684829, 72777973, 73786944, 86242799, 11757872, 38256849, 24168411, 50567636, 56955985, 50702367, 1250437, 22129328, 14349098, 21397057, 46540998, 32151165, 97057187, 30163921, 44983451, 72278539, 69697787, 68694897, 9398733, 92530431, 82339363, 19376156, 60176618, 13470059, 44473167, 47361209, 3233084, 83133790, 51715482, 49882705, 88251446, 99658235, 75128745, 95581843, 75052463, 36933038, 67084351, 84406788, 22200378, 51472275, 93515664, 18466635, 27325428, 90061527, 51590803, 77272628, 68128438, 59614746, 34236719, 54663246, 6525948, 69848388, 24591705, 40984766, 30395570, 93790285, 76434144, 77377183, 61928316, 9575954, 52613508, 39801351, 13819358, 47887470, 82979980, 23134715, 6111563, 60102965, 53666583, 55189057, 30543215, 42307449, 48260151, 84002370, 37560164, 23110625, 96906410, 59177718, 86118021, 25636669, 59329511, 91664334, 7182642, 32058615, 68316156, 91938191, 66428795, 67030811, 20140249, 5822202, 66663942, 61982238, 55669657, 62428472, 17037369, 45381876, 15536795, 96709982, 16380211, 99971982, 22721500, 6521313, 99125126, 88904910, 44627776, 61859581, 83651211, 62357986, 66667729, 4515343, 68816413, 95010552, 48675329, 94539824, 84166196, 73392814, 80246713, 68000591, 62552524, 44784505, 66116458, 73168565, 94360702, 42199455, 63506281, 66322959, 61741594, 98371444, 22766820, 5073754, 99965001, 33237508, 71316369, 71333116, 52204879, 97379790, 40781449, 30811010, 27625735, 10264691, 43152977, 67474219, 70420215, 74452589, 36396314, 17386996, 83155442, 34946859, 65851721, 50806615, 66832478, 56531125, 45049308, 10366309, 38645117, 90272749, 72274002, 91141395, 58224549, 49328608, 1936762, 91990218, 64848072, 47893286, 44915235, 17857111, 1569515, 64602895, 42644903, 55850790, 99729357, 2331773, 9175338, 77694700, 83747892, 18504197, 50007421, 41442762, 2607799, 63015256, 62762857, 89699445, 82726008, 44060493, 14220886, 48893685, 72358170, 23848565, 16405341, 88698958, 26863229, 28928175, 6038457, 89419466, 10309525, 38658347, 62051033, 34295794, 85116755, 89214616, 24915585, 99515901, 74614639, 99917599, 76825057, 68041839, 42782652, 92787493, 9058407, 26397786, 30366150, 45990383, 69255765, 1375023, 77284619, 62496012 +59329511, 22879907, 52204879, 2917920, 61960400, 99917599, 36933038, 73124510, 18411915, 18504197, 4668450, 17386996, 40152546, 68824981, 98943869, 4978543, 55770687, 44844121, 8129978, 58180653, 30543215, 99729357, 2331773, 92692283, 41442762, 72373496, 73786944, 11757872, 97783876, 41092102, 45381876, 12664567, 94076128, 48893685, 26664538, 69136837, 99549373, 4806458, 78785507, 8055981, 66250369, 95581843, 66045587, 1936762, 73392814, 78884452, 30891921, 87598888, 40027975, 52613508, 42644903, 36505482, 32426752, 12303248, 22766820, 34044787, 97379790, 54232247, 63015256, 8733068, 62496012, 59501487, 26744917, 76330843, 5832946, 56531125, 65017137, 29065964, 54517921, 36139942, 19376156, 90310261, 17764950, 93053405, 80251430, 69641513, 84684495, 33304202, 75128745, 66611759, 55470718, 1788101, 62430984, 1197320, 45794415, 35456853, 3773993, 64602895, 30569392, 75777973, 77312810, 89217461, 23134715, 55189057, 84293052, 80555751, 35192533, 61741594, 48892201, 51047803, 96906410, 42692881, 6808825, 41245325, 55960386, 33699435, 17146629, 90004325, 24915585, 69355476, 49271185, 77072625, 67513640, 56955985, 40534591, 37891451, 90090964, 321665, 91281584, 65038678, 45075471, 79191827, 93566986, 44481640, 91240048, 76825057, 13470059, 83651211, 22405120, 22435353, 81855445, 88698958, 90013093, 17894977, 8099994, 10961421, 48395186, 58224549, 66667729, 36312813, 30998561, 49328608, 79922758, 20568363, 68816413, 14326617, 21919959, 22200378, 47893286, 51472275, 17976208, 34667286, 90061527, 84166196, 54663246, 14723410, 32699744, 53648154, 82178706, 40984766, 99604946, 61373987, 8729146, 64157906, 62552524, 92071990, 63059024, 7423788, 10649306, 95395112, 62803858, 55850790, 7300047, 26275734, 36685023, 85117092, 54427233, 44847298, 30463802, 61815223, 38365584, 72738685, 54868730, 29029316, 47708850, 18663507, 7646095, 37620363, 37501808, 83747892, 67281495, 50007421, 7011964, 58751351, 68644627, 95957797, 91664334, 59910624, 81774825, 77898274, 40781449, 19272365, 68204242, 72777973, 16567550, 14731700, 98948034, 30218878, 73021291, 3294781, 5822202, 70420215, 55669657, 36780454, 39986008, 26292919, 89078848, 36396314, 53632373, 50702367, 17081350, 99515901, 22129328, 17727650, 48774913, 34946859, 96318094, 59318837, 247198, 73031054, 97641116, 72278539, 82897371, 44627776, 45049308, 526217, 39553046, 44842615, 10366309, 5267545, 84349107, 83210802, 59109400, 16405341, 9058407, 85242963, 27411561, 14947650, 33797252, 49598724, 68041839, 29516592, 44473167, 94090109, 3509435, 44348328, 83133790, 38510840, 68102437, 91141395, 34698428, 3880712, 26776922, 57248122, 6038457, 62115552, 74137926, 78602717, 9259676, 60106217, 47792865, 95290172, 95010552, 91990218, 36189527, 97011160, 22997281, 38008118, 24591705, 61712234, 67227442, 62232211, 81898046, 21673260, 68110330, 38061439, 33061250, 54263880, 48360198, 30090481, 68000591, 77377183, 98739783, 39801351, 13819358, 62051033, 47887470, 17016156, 77301523, 569864, 93933709, 42967683, 72793444, 48260151, 63506281, 874791, 84002370, 62740044, 4798568, 78909561, 7955293, 23110625, 9175338, 69786756, 42073124, 112651, 77694700, 27187213, 74724075, 70367851, 2204165, 51464002, 4099191, 56473732, 8791066, 33237508, 22113133, 29466406, 18131876, 43376279, 78766358, 31126490, 68316156, 4091162, 20122224, 30811010, 90654836, 18699206, 91938191, 4069912, 16684829, 23194618, 83302115, 1375023, 60955663, 72357096, 57658654, 54606384, 83269727, 17037369, 50567636, 15536795, 40686254, 21397057, 79821897, 45995325, 10597197, 99971982, 65851721, 168541, 68694897, 9886593, 57020481, 43501211, 9599614, 92803766, 38645117, 60176618, 8115266, 72725103, 35512853, 91727510, 45617087, 8634541, 19891772, 47361209, 57961282, 26863229, 86001008, 35092039, 72274002, 16445503, 88251446, 63372756, 44664587, 96420429, 55602660, 23569917, 51315460, 81274566, 26397786, 57393458, 13862149, 48675329, 70036438, 93515664, 67451935, 92398073, 18466635, 89996536, 6525948, 69848388, 65715134, 19898053, 21070110, 62490109, 86460488, 44177011, 176257, 15064655, 75135448, 78589145, 98648327, 3235882, 47213183, 43933006, 60102965, 3233569, 53666583, 82052050, 11543098, 13468268, 72614359, 19101477, 415901, 83948335, 92541302, 48658605, 71300104, 70372191, 44245960, 71522968, 18783702, 4508700, 71316369, 38022834, 77787724, 88047921, 49641577, 36135, 7517032, 92692978, 92033260, 65074535, 99021067, 72238278, 9257405, 85571389, 60393039, 43152977, 56484900, 1431742, 78300864, 86242799, 40781854, 6819644, 66319530, 67474219, 77284619, 99226875, 38256849, 30771409, 94595668, 67793644, 669105, 86821229, 44983451, 4985896, 2717150, 6521313, 74743862, 11365791, 45306070, 56515456, 5111370, 88904910, 83368048, 31904591, 82339363, 24733232, 61859581, 93057697, 23848565, 51588015, 92787493, 26102057, 94516935, 97940276, 66137019, 26139110, 45790169, 30139692, 57241521, 29510992, 49882705, 99658235, 9623492, 3088684, 28787861, 54199160, 79657802, 2208785, 3183975, 78549759, 66903004, 91831487, 61271144, 84406788, 64848072, 11161731, 15948937, 51590803, 72495719, 17365188, 81677380, 34493392, 59614746, 24058273, 53393358, 85695762, 38658347, 22942635, 64087743, 30395570, 24619760, 1569515, 70004753, 31733363, 37224844, 55753905, 24826575, 76434144, 22108665, 61928316, 9575954, 69255765, 75407347, 86301513, 33123618, 29959549, 61263068, 82979980, 6111563, 39171895, 98653983, 17058722, 8913721, 66322959, 46851987, 62936963, 91957544, 49597667, 77620120, 15535065, 98371444, 89214616, 92604458, 33249630, 43322743, 81172706, 73109997, 36808486, 51426311, 56461322, 47090124, 25636669, 99297164, 39373729, 29834512, 27041967, 85711894, 16424199, 32058615, 61859143, 72732205, 37659250, 86798033, 45919976, 10264691, 12024238, 99524975, 24953498, 36930650, 99333375, 54058788, 97057187, 66832478, 53888755, 54987042, 92353856, 69697787, 62452034, 32274392, 90158785, 20642888, 70800879, 59371804, 80316608, 45667668, 17539625, 75820087, 98462867, 19939935, 3233084, 25842078, 71965942, 21001913, 90457870, 22450468, 11923835, 65271999, 45237957, 93270084, 33895336, 96735716, 42782652, 60430369, 30694952, 33628349, 8651647, 89419466, 14093520, 83150534, 70782102, 26392416, 67084351, 30366150, 54014062, 10358899, 63628376, 37280276, 749283, 28734791, 59405277, 53802686, 14626618, 94539824, 34236719, 98130363, 7919588, 81853704, 20486294, 15902805, 8807940, 12416768, 93790285, 65880522, 5482538, 78561158, 5640302, 65275241, 38341669, 61728685, 1022677, 37675718, 33553959, 37183543, 94360702, 4204661, 89804152, 52293995, 42307449, 26063929, 51507425, 79880247, 36845587, 37739481, 41380093, 57163802, 85116755, 7685448, 99690194, 44889423, 93562257, 79136082, 99965001, 71333116, 16971929, 2607799, 41481685, 82651278, 52060076, 74110882, 24314885, 7687278, 1204161, 66428795, 76960303, 29401781, 51466049, 18806856, 94711842, 31491938, 37435892, 55247455, 64055960, 20002147, 824872, 34497327, 62762857, 86022504, 33201905, 96709982, 1250437, 14349098, 46870723, 4064751, 29819940, 46540998, 50806615, 30163921, 61897582, 22721500, 14220886, 61141874, 36753250, 16387575, 43506672, 74614639, 40677414, 4787945, 45407418, 48673079, 76540481, 35996293, 33431960, 13173644, 93359396, 28928175, 28550822, 53084256, 58749, 6221471, 54762643, 28796059, 82427263, 75052463, 85023028, 42947632, 42237907, 36580610, 49236559, 9829782, 44915235, 6157724, 65454636, 32250045, 27325428, 30764367, 64098930, 26734892, 17857111, 20836893, 20681921, 3633375, 65338021, 70596786, 80246713, 38009615, 76170907, 12571310, 19486173, 88130087, 44784505, 66116458, 20765474, 31161687, 73168565, 21289531, 16097038, 42199455, 51149804, 76703609, 84904436, 55615885, 34295794, 16099750, 5073754, 23740167, 86118021, 74357852, 55143724, 16054533, 28851716, 82532312, 44479073, 50188404, 87720882, 50668599, 29994197, 39201414, 20645197, 67030811, 66663942, 96193415, 9603598, 79322415, 87710366, 62428472, 11581181, 84187166, 37192445, 45996863, 8128637, 57240218, 6986898, 83155442, 17385531, 47738185, 6871053, 91255408, 44550764, 8696647, 72358170, 99125126, 77300457, 18833224, 92530431, 15148031, 82327024, 4095116, 2891150, 18001617, 90272749, 8373289, 78218845, 69579137, 58208470, 13231279, 17957593, 27185644, 17068582, 76315420, 87160386, 72019362, 84840549, 26998766, 40865610, 88653118, 73235980, 68702632, 508198, 4515343, 81805959, 59981773, 75229462, 88444207, 4199704, 9860195, 10309525, 32159704, 6153406, 73222868, 70727211, 79738755, 59197747, 45990383, 43045786, 92554120, 92867155, 29932657, 60581278, 16019925, 80014588, 73617245, 26507214, 32590267, 75986488, 45428665, 29188588, 6793819, 29635537, 66885828, 71469330, 63152504, 70541760, 7066775, 96726697, 7182642, 57359924, 79806380, 56153345, 95726235, 47298834, 83083131, 20140249, 61982238, 77187825, 31727379, 33565483, 68939068, 57803235, 52261574, 99224392, 7502255, 71657078, 10453030, 16380211, 34432810, 62693428, 89637706, 36812683, 19457589, 71083565, 32161669, 41092172, 79942022, 4434662, 33336148, 26493119, 97281847, 97561745, 92215320, 96852321, 43357947, 53842979, 91802888, 26114953, 38494874, 9860968, 36468541, 6497830, 68128438, 15015906, 16595436, 53547802, 21993752, 20867149, 67031644, 13348726, 82886381, 48088883, 34698463, 30653863, 65081429, 1239555, 39847321, 59177718, 12348118, 6505939, 89811711, 5970607, 27625735, 33935899, 14363867, 74452589, 24168411, 37166608, 45848907, 82726008, 82779622, 44060493, 32151165, 44846932, 95251277, 83533741, 75543508, 51715482, 61623915, 75153252, 7104732, 7229550, 37957788, 15075176, 20885148, 77272628, 15163258, 89499542, 69605283, 89046466, 99861373, 68875490, 86543538, 40197395, 37560164, 9188443, 63967300, 27665211, 65047700, 74441448, 56424103, 21533347, 3487592, 92998591, 14045383, 4119747, 77413012, 9398733, 67124282, 94911072, 23432750, 62357986, 30787683, 76671482, 66271566, 71920426, 89699445, 84127901, 83789391, 40356877 +33553959, 73031054, 35996293, 9829782, 92398073, 98739783, 52293995, 56473732, 13470059, 9058407, 9623492, 24591705, 18663507, 93562257, 8733068, 98948034, 61897582, 8696647, 24733232, 86001008, 33895336, 67084351, 70036438, 6497830, 37957788, 30891921, 99604946, 76434144, 62552524, 36685023, 63506281, 2331773, 37739481, 6793819, 27625735, 1375023, 67513640, 89078848, 7502255, 83210802, 14947650, 45667668, 57961282, 72274002, 75153252, 8055981, 42782652, 2208785, 4515343, 1788101, 4978543, 15948937, 55770687, 12416768, 3773993, 70004753, 75135448, 30090481, 7423788, 65275241, 62803858, 61728685, 77301523, 93933709, 16097038, 60102965, 72793444, 58180653, 34698463, 45428665, 98371444, 48658605, 2204165, 41245325, 67281495, 23740167, 18783702, 41442762, 78766358, 88047921, 97379790, 54232247, 49271185, 96193415, 36930650, 41092102, 33201905, 89699445, 99333375, 40686254, 47738185, 34432810, 53888755, 2717150, 11365791, 82897371, 91281584, 36753250, 39553046, 83368048, 22435353, 93053405, 97281847, 94090109, 80251430, 19891772, 81855445, 90013093, 87160386, 91141395, 65271999, 58224549, 3088684, 66045587, 62115552, 30694952, 85023028, 26392416, 51472275, 51590803, 64098930, 38009615, 44177011, 176257, 70727211, 79738755, 19486173, 24826575, 48360198, 5482538, 78561158, 69255765, 10649306, 95395112, 92867155, 73168565, 29932657, 89217461, 43933006, 53666583, 26275734, 98653983, 30543215, 99729357, 48260151, 1239555, 70372191, 69786756, 32426752, 71522968, 71469330, 55960386, 59329511, 59910624, 68316156, 61859143, 71920426, 50188404, 87720882, 68204242, 72777973, 73786944, 18806856, 55247455, 30218878, 62428472, 17037369, 50702367, 76330843, 17727650, 45995325, 94076128, 16380211, 94595668, 65851721, 168541, 30163921, 45306070, 5111370, 62452034, 72358170, 36812683, 57020481, 54517921, 32274392, 31904591, 93566986, 44842615, 93057697, 69136837, 59109400, 90158785, 45407418, 22405120, 2891150, 45790169, 33797252, 49598724, 97561745, 17539625, 34698428, 48395186, 88653118, 28796059, 79657802, 508198, 57248122, 33628349, 51315460, 9259676, 95290172, 26397786, 30366150, 18466635, 32250045, 72495719, 68128438, 61712234, 81853704, 65715134, 19898053, 69605283, 53648154, 62490109, 38061439, 15064655, 8729146, 92071990, 29959549, 6111563, 94360702, 55189057, 66271566, 76703609, 66322959, 54427233, 73617245, 84904436, 26507214, 4798568, 61815223, 48892201, 38365584, 92998591, 42692881, 77694700, 70367851, 37620363, 6808825, 51426311, 25636669, 38022834, 16971929, 16054533, 16424199, 49641577, 7517032, 90004325, 44479073, 99021067, 72238278, 40356877, 45919976, 60393039, 47298834, 74441448, 86242799, 60955663, 6819644, 67474219, 20140249, 97783876, 24168411, 37166608, 56955985, 15536795, 29819940, 30771409, 79821897, 59318837, 50806615, 54987042, 91255408, 22721500, 48893685, 2917920, 67124282, 56515456, 61141874, 43501211, 10366309, 19376156, 91240048, 40677414, 8115266, 51588015, 4095116, 76540481, 66137019, 80316608, 68041839, 45617087, 8634541, 21533347, 58208470, 84684495, 26863229, 25842078, 17957593, 38510840, 84840549, 78785507, 10961421, 53842979, 30998561, 23569917, 68816413, 36468541, 4199704, 67451935, 15075176, 9860195, 65454636, 11161731, 77272628, 34493392, 94539824, 54663246, 32699744, 85695762, 21070110, 20486294, 22879907, 22942635, 8807940, 64087743, 24619760, 1569515, 61928316, 30569392, 5640302, 33123618, 17016156, 39171895, 3233569, 89804152, 26063929, 84293052, 62740044, 80555751, 62936963, 36505482, 91957544, 49597667, 73124510, 77620120, 34295794, 72738685, 29635537, 57163802, 85116755, 71300104, 112651, 63152504, 18504197, 99965001, 47090124, 8791066, 58751351, 22113133, 68644627, 71333116, 2607799, 96726697, 31126490, 85711894, 41481685, 77898274, 74110882, 79806380, 24314885, 85571389, 39201414, 95726235, 51466049, 83302115, 83083131, 77284619, 824872, 59501487, 66663942, 61982238, 62762857, 79322415, 74452589, 82726008, 46540998, 10453030, 96318094, 44983451, 6521313, 92353856, 9398733, 62693428, 9886593, 44846932, 18833224, 71083565, 99917599, 44481640, 15148031, 26664538, 83651211, 4787945, 35512853, 17764950, 70800879, 91727510, 75543508, 59371804, 33336148, 90272749, 8373289, 13173644, 47361209, 75820087, 3233084, 21001913, 27185644, 17894977, 43357947, 8099994, 16445503, 90457870, 51715482, 22450468, 40865610, 11923835, 63372756, 54762643, 95581843, 3880712, 26776922, 91802888, 55602660, 78602717, 8651647, 14093520, 47792865, 57393458, 36189527, 93515664, 28734791, 17976208, 10309525, 53802686, 89996536, 98130363, 6153406, 73392814, 38658347, 82178706, 33061250, 83789391, 99861373, 40027975, 59197747, 98648327, 3235882, 47213183, 75777973, 82886381, 47887470, 37675718, 77312810, 82979980, 23134715, 17058722, 51507425, 30653863, 84002370, 78909561, 35192533, 7955293, 75986488, 29188588, 54868730, 42073124, 33249630, 12348118, 74724075, 44889423, 47708850, 37501808, 36808486, 6505939, 79136082, 5970607, 77787724, 91664334, 81774825, 52060076, 40781449, 30811010, 90654836, 27665211, 92033260, 72373496, 64055960, 78300864, 4668450, 66319530, 67030811, 3294781, 72357096, 62496012, 5822202, 38256849, 86022504, 87710366, 83269727, 45848907, 45381876, 45996863, 17386996, 83155442, 17081350, 17385531, 1250437, 99515901, 14349098, 82779622, 5832946, 71657078, 34946859, 10597197, 99971982, 97641116, 669105, 86821229, 90090964, 44627776, 29065964, 45049308, 16387575, 65038678, 61960400, 45075471, 79191827, 68824981, 61859581, 9599614, 5267545, 36139942, 90310261, 60176618, 72725103, 99549373, 4806458, 26102057, 79942022, 26493119, 18001617, 33431960, 69579137, 13231279, 98462867, 71965942, 17068582, 72019362, 49882705, 68102437, 99658235, 3487592, 58749, 66611759, 66250369, 36312813, 82427263, 96420429, 6038457, 20568363, 75052463, 1936762, 9860968, 91831487, 89419466, 83150534, 95010552, 70782102, 61271144, 88444207, 42237907, 36580610, 49236559, 37280276, 48675329, 59405277, 20885148, 27325428, 97011160, 15163258, 30764367, 84166196, 14723410, 69848388, 1197320, 26734892, 17857111, 24058273, 65338021, 35456853, 73222868, 62232211, 80246713, 40984766, 87598888, 77377183, 52613508, 8129978, 42967683, 86543538, 51149804, 8913721, 42307449, 874791, 55615885, 72614359, 83948335, 32590267, 92692283, 92541302, 15535065, 92604458, 59177718, 63967300, 66885828, 27187213, 4099191, 17146629, 4508700, 99297164, 34044787, 71316369, 95957797, 29466406, 18131876, 7182642, 55143724, 82532312, 76960303, 56153345, 50668599, 94711842, 37435892, 40781854, 99226875, 34497327, 70420215, 57658654, 9603598, 54606384, 50567636, 39986008, 8128637, 46870723, 48774913, 21397057, 66832478, 56531125, 94911072, 99125126, 88904910, 19457589, 526217, 43506672, 92803766, 92787493, 83533741, 27411561, 97940276, 44473167, 69641513, 96852321, 19939935, 83133790, 33304202, 93359396, 28550822, 53084256, 7104732, 73235980, 28787861, 49328608, 74137926, 81805959, 55470718, 36933038, 54014062, 10358899, 22200378, 47893286, 22997281, 14626618, 59614746, 38008118, 6525948, 15015906, 67227442, 20681921, 53547802, 78884452, 21673260, 30395570, 54263880, 61373987, 12571310, 88130087, 67031644, 44784505, 75407347, 86301513, 39801351, 20765474, 38341669, 31161687, 61263068, 569864, 4204661, 40197395, 76671482, 85117092, 16019925, 65081429, 46851987, 36845587, 61741594, 415901, 37560164, 41380093, 16099750, 7685448, 89214616, 96906410, 99690194, 81172706, 7646095, 50007421, 56461322, 89811711, 27041967, 74357852, 14045383, 36135, 57359924, 4119747, 18699206, 33935899, 1204161, 65047700, 19272365, 86798033, 65074535, 14363867, 4069912, 23194618, 9257405, 10264691, 16567550, 31491938, 99524975, 56484900, 20645197, 1431742, 77187825, 11581181, 84187166, 26744917, 37192445, 36396314, 96709982, 52261574, 54058788, 37891451, 247198, 72278539, 44550764, 89637706, 65017137, 32161669, 23848565, 76825057, 23432750, 41092172, 16405341, 48673079, 44348328, 88698958, 35092039, 26998766, 61623915, 62357986, 45237957, 44664587, 68702632, 26114953, 38494874, 98943869, 81274566, 75229462, 14326617, 84406788, 44915235, 62430984, 34236719, 20836893, 16595436, 3633375, 45794415, 53393358, 89499542, 86460488, 89046466, 30787683, 20867149, 93790285, 65880522, 64602895, 22108665, 45990383, 9575954, 63059024, 43045786, 13819358, 55850790, 1022677, 37183543, 48088883, 82052050, 11543098, 44847298, 30463802, 19101477, 23110625, 39847321, 18411915, 51047803, 12303248, 22766820, 43322743, 29029316, 44245960, 73109997, 70541760, 7066775, 33699435, 29834512, 52204879, 32058615, 82651278, 28851716, 69355476, 63015256, 16684829, 29401781, 24953498, 20002147, 56424103, 55669657, 36780454, 33565483, 57240218, 57803235, 44060493, 40534591, 74743862, 95251277, 92530431, 82327024, 38645117, 20642888, 94516935, 4434662, 30139692, 57241521, 78218845, 29510992, 92215320, 76315420, 28928175, 75128745, 93270084, 54199160, 60430369, 3183975, 66903004, 60106217, 59981773, 63628376, 64848072, 749283, 32159704, 21993752, 37224844, 64157906, 13348726, 68875490, 42644903, 21289531, 42199455, 13468268, 80014588, 79880247, 51464002, 43376279, 4091162, 24915585, 37659250, 92692978, 7687278, 66428795, 29994197, 43152977, 14731700, 68939068, 84127901, 40152546, 97057187, 4985896, 14220886, 68694897, 77300457, 26139110, 29516592, 88251446, 6221471, 78549759, 42947632, 21919959, 91990218, 6157724, 34667286, 90061527, 81677380, 70596786, 81898046, 15902805, 68110330, 31733363, 76170907, 78589145, 66116458, 60581278, 9175338, 5073754, 83747892, 7011964, 20122224, 72732205, 91938191, 12024238, 77072625, 11757872, 31727379, 26292919, 77413012, 53632373, 22129328, 99224392, 32151165, 6871053, 69697787, 321665, 74614639, 84349107, 85242963, 3509435, 66667729, 96735716, 44844121, 55753905, 68000591, 92554120, 62051033, 86118021, 39373729, 73021291, 12664567, 4064751, 67793644, 82339363, 79922758, 7229550, 17365188, 7919588, 9188443, 6986898, 13862149, 7300047, 33237508 +60430369, 56461322, 59371804, 78602717, 7685448, 42692881, 18783702, 76960303, 34946859, 26102057, 54199160, 30998561, 30891921, 92071990, 20765474, 75777973, 73168565, 4798568, 38365584, 32590267, 41245325, 68644627, 5970607, 27625735, 59501487, 56955985, 36139942, 8099994, 21919959, 17976208, 59614746, 48360198, 48260151, 54427233, 79880247, 37620363, 20122224, 72732205, 16567550, 4668450, 11581181, 50567636, 17386996, 17081350, 94076128, 67793644, 2917920, 67124282, 91281584, 45049308, 39553046, 19376156, 8115266, 20642888, 80316608, 84684495, 75128745, 66611759, 48395186, 14326617, 13862149, 18466635, 84166196, 53393358, 44844121, 86460488, 65880522, 47213183, 68875490, 3233569, 84293052, 30463802, 91957544, 6793819, 12303248, 22766820, 44889423, 83747892, 70541760, 50007421, 33699435, 33237508, 71316369, 16424199, 63015256, 72373496, 74441448, 67030811, 77072625, 26744917, 67513640, 79821897, 74743862, 88904910, 44481640, 5267545, 84349107, 69136837, 99549373, 27411561, 91727510, 22435353, 49598724, 68041839, 87160386, 78785507, 44664587, 66250369, 3183975, 74137926, 23569917, 66903004, 68816413, 95010552, 88444207, 92398073, 30764367, 17857111, 24591705, 67227442, 3633375, 32699744, 24058273, 54263880, 30787683, 87598888, 93790285, 22108665, 75407347, 43045786, 55850790, 17016156, 37183543, 23134715, 6111563, 48088883, 89804152, 66271566, 13468268, 51507425, 44847298, 48892201, 1239555, 71300104, 89214616, 92604458, 59177718, 77694700, 52204879, 18131876, 78766358, 88047921, 37659250, 71920426, 19272365, 4069912, 99021067, 95726235, 51466049, 8733068, 94711842, 12024238, 6819644, 70420215, 54606384, 99333375, 53632373, 84127901, 83155442, 46870723, 168541, 44983451, 6521313, 89637706, 72358170, 36753250, 526217, 95251277, 31904591, 82327024, 44842615, 59109400, 13470059, 92787493, 16405341, 94090109, 47361209, 17894977, 93359396, 10961421, 58224549, 36312813, 28796059, 66045587, 57248122, 6038457, 81274566, 36933038, 47893286, 51472275, 93515664, 72495719, 77272628, 97011160, 26734892, 61712234, 65338021, 78884452, 20486294, 21673260, 68110330, 38061439, 33061250, 83789391, 31733363, 76434144, 60581278, 29959549, 37675718, 93933709, 4204661, 30543215, 99729357, 63506281, 80555751, 92692283, 15535065, 96906410, 5073754, 12348118, 47708850, 37501808, 79136082, 23740167, 56473732, 17146629, 32058615, 24915585, 82532312, 18806856, 20645197, 40781854, 14731700, 20002147, 9603598, 97783876, 74452589, 77187825, 87710366, 31727379, 17037369, 1250437, 30771409, 45995325, 16380211, 97641116, 54987042, 90090964, 82897371, 5111370, 57020481, 61960400, 99917599, 93566986, 26664538, 91240048, 83210802, 90310261, 22405120, 9058407, 93053405, 33797252, 97281847, 18001617, 29516592, 8373289, 80251430, 17539625, 21001913, 27185644, 33304202, 72274002, 43357947, 72019362, 99658235, 40865610, 61623915, 75153252, 66667729, 68702632, 55602660, 62115552, 78549759, 9259676, 83150534, 26397786, 91990218, 57393458, 84406788, 6497830, 28734791, 4978543, 53802686, 90061527, 51590803, 81677380, 1197320, 70596786, 19898053, 85695762, 44177011, 1569515, 37224844, 55753905, 64157906, 67031644, 61928316, 9575954, 95395112, 31161687, 62051033, 47887470, 77312810, 16097038, 7300047, 60102965, 98653983, 17058722, 55189057, 82052050, 51149804, 42307449, 874791, 80014588, 73617245, 37560164, 23110625, 9175338, 70372191, 43322743, 71469330, 22113133, 38022834, 95957797, 29466406, 27041967, 91664334, 14045383, 41481685, 4091162, 28851716, 4119747, 92692978, 27665211, 79806380, 49271185, 86798033, 87720882, 68204242, 72777973, 73786944, 47298834, 1431742, 86242799, 98948034, 30218878, 73021291, 62496012, 99226875, 66663942, 89699445, 8128637, 12664567, 50702367, 99515901, 47738185, 82779622, 21397057, 71657078, 54058788, 96318094, 6871053, 10597197, 97057187, 30163921, 669105, 72278539, 4985896, 56515456, 62452034, 65017137, 99125126, 19457589, 16387575, 77300457, 43501211, 54517921, 15148031, 10366309, 60176618, 4787945, 4806458, 17764950, 70800879, 85242963, 26139110, 4434662, 92215320, 13231279, 3509435, 44348328, 17068582, 76315420, 16445503, 51715482, 53084256, 54762643, 73235980, 3088684, 95581843, 49328608, 508198, 2208785, 7229550, 85023028, 59981773, 47792865, 36580610, 30366150, 54014062, 63628376, 37280276, 22200378, 4199704, 36189527, 9860195, 65454636, 10309525, 15948937, 14626618, 62430984, 89996536, 54663246, 7919588, 81853704, 65715134, 53547802, 6153406, 73392814, 38658347, 73222868, 82178706, 62490109, 12416768, 89046466, 70004753, 20867149, 40027975, 76170907, 78589145, 13348726, 98648327, 68000591, 8129978, 63059024, 13819358, 62803858, 61728685, 1022677, 61263068, 77301523, 569864, 94360702, 43933006, 36685023, 76671482, 76703609, 65081429, 2331773, 46851987, 62740044, 72614359, 78909561, 36505482, 35192533, 7955293, 83948335, 73124510, 72738685, 75986488, 29188588, 85116755, 70367851, 71522968, 18663507, 6505939, 18504197, 4099191, 47090124, 58751351, 59329511, 74357852, 31126490, 97379790, 49641577, 40781449, 90654836, 18699206, 91938191, 7687278, 1204161, 65047700, 65074535, 23194618, 85571389, 39201414, 40356877, 56484900, 78300864, 72357096, 824872, 96193415, 34497327, 36930650, 62762857, 24168411, 68939068, 39986008, 77413012, 6986898, 96709982, 46540998, 37891451, 247198, 73031054, 91255408, 14220886, 92353856, 48893685, 69697787, 68694897, 94911072, 61141874, 36812683, 18833224, 32274392, 45075471, 82339363, 40677414, 35512853, 94516935, 14947650, 26493119, 45617087, 44473167, 57241521, 21533347, 96852321, 19939935, 71965942, 38510840, 90013093, 35092039, 22450468, 68102437, 28928175, 63372756, 6221471, 9623492, 79657802, 33895336, 96735716, 4515343, 91802888, 33628349, 9860968, 95290172, 61271144, 26392416, 49236559, 749283, 37957788, 59405277, 20885148, 15163258, 34236719, 64098930, 6525948, 20681921, 55770687, 69605283, 81898046, 8807940, 64087743, 3773993, 99861373, 79738755, 12571310, 24826575, 64602895, 77377183, 3235882, 52613508, 39801351, 92867155, 89217461, 86543538, 26275734, 85117092, 66322959, 26507214, 49597667, 41380093, 77620120, 34295794, 29635537, 57163802, 48658605, 99690194, 63967300, 44245960, 2204165, 73109997, 6808825, 67281495, 86118021, 25636669, 8791066, 77787724, 29834512, 96726697, 59910624, 68316156, 82651278, 57359924, 74110882, 24314885, 66428795, 16684829, 50188404, 50668599, 29994197, 99524975, 24953498, 83083131, 3294781, 20140249, 5822202, 56424103, 57658654, 41092102, 33201905, 37192445, 45381876, 15536795, 40686254, 52261574, 82726008, 76330843, 99224392, 40534591, 10453030, 32151165, 59318837, 40152546, 34432810, 94595668, 53888755, 44550764, 11365791, 8696647, 9398733, 9886593, 44846932, 29065964, 65038678, 43506672, 83368048, 68824981, 61859581, 93057697, 92803766, 76825057, 83651211, 72725103, 51588015, 48673079, 4095116, 79942022, 97940276, 75543508, 45790169, 30139692, 33431960, 19891772, 58208470, 57961282, 75820087, 98462867, 26863229, 83133790, 90457870, 49882705, 3487592, 88653118, 28787861, 26114953, 20568363, 38494874, 98943869, 42947632, 14093520, 75229462, 36468541, 70782102, 1788101, 64848072, 44915235, 15075176, 11161731, 68128438, 34493392, 32159704, 38008118, 69848388, 15015906, 45794415, 22879907, 62232211, 80246713, 24619760, 61373987, 70727211, 15064655, 8729146, 59197747, 19486173, 88130087, 44784505, 69255765, 30569392, 86301513, 98739783, 92554120, 65275241, 38341669, 21289531, 39171895, 72793444, 40197395, 11543098, 30653863, 36845587, 62936963, 37739481, 61815223, 19101477, 415901, 16099750, 18411915, 98371444, 51047803, 112651, 92998591, 33249630, 74724075, 36808486, 63152504, 51426311, 55960386, 7011964, 99297164, 39373729, 43376279, 85711894, 16054533, 36135, 81774825, 90004325, 69355476, 92033260, 14363867, 56153345, 29401781, 72238278, 9257405, 31491938, 37435892, 55247455, 37166608, 36780454, 84187166, 45996863, 26292919, 36396314, 57240218, 14349098, 4064751, 5832946, 99971982, 65851721, 50806615, 61897582, 66832478, 86821229, 56531125, 321665, 44627776, 74614639, 71083565, 92530431, 90158785, 2891150, 33336148, 97561745, 8634541, 69579137, 69641513, 88698958, 26998766, 88251446, 11923835, 34698428, 7104732, 45237957, 3880712, 96420429, 81805959, 51315460, 75052463, 8651647, 60106217, 67084351, 42237907, 34667286, 38009615, 99604946, 176257, 75135448, 5482538, 30090481, 62552524, 78561158, 7423788, 5640302, 42644903, 29932657, 82886381, 42967683, 58180653, 42199455, 8913721, 26063929, 55615885, 54868730, 69786756, 32426752, 42073124, 27187213, 81172706, 93562257, 99965001, 4508700, 34044787, 89811711, 77898274, 30811010, 54232247, 33935899, 45919976, 66319530, 67474219, 77284619, 61982238, 11757872, 33565483, 45848907, 89078848, 22721500, 2717150, 45306070, 32161669, 9599614, 23432750, 45407418, 66137019, 45667668, 13173644, 78218845, 29510992, 86001008, 25842078, 84840549, 91141395, 65271999, 82427263, 79922758, 1936762, 55470718, 9829782, 48675329, 6157724, 17365188, 98130363, 20836893, 21993752, 21070110, 35456853, 53648154, 15902805, 40984766, 33123618, 33553959, 52293995, 84002370, 61741594, 92541302, 39847321, 9188443, 29029316, 51464002, 41442762, 16971929, 55143724, 7517032, 52060076, 44479073, 1375023, 64055960, 79322415, 83269727, 17385531, 22129328, 17727650, 62693428, 79191827, 41092172, 83533741, 81855445, 58749, 62357986, 53842979, 26776922, 30694952, 10358899, 70036438, 67451935, 32250045, 14723410, 22942635, 30395570, 45990383, 34698463, 84904436, 66885828, 2607799, 7182642, 61859143, 10264691, 83302115, 60393039, 43152977, 38256849, 86022504, 62428472, 44060493, 23848565, 38645117, 76540481, 35996293, 90272749, 28550822, 8055981, 93270084, 91831487, 27325428, 22997281, 89499542, 66116458, 10649306, 16019925, 45428665, 7646095, 7066775, 71333116, 55669657, 48774913, 29819940, 17957593, 89419466, 94539824, 16595436, 82979980, 53666583, 60955663, 7502255, 3233084, 42782652, 57803235, 24733232 +76960303, 43357947, 12571310, 47213183, 54663246, 31161687, 5832946, 3183975, 27665211, 50702367, 40152546, 70800879, 47361209, 9860968, 13862149, 90061527, 84166196, 64098930, 65275241, 37183543, 19101477, 91957544, 91664334, 86821229, 2717150, 77300457, 72725103, 4787945, 91727510, 92215320, 78785507, 96420429, 95290172, 48675329, 94539824, 20836893, 61373987, 20867149, 66116458, 92554120, 20765474, 33553959, 82052050, 49597667, 77620120, 59177718, 43322743, 54232247, 83083131, 60955663, 87710366, 33565483, 99224392, 96318094, 11365791, 69697787, 9398733, 99125126, 29065964, 45049308, 82339363, 90310261, 4806458, 26102057, 4434662, 26493119, 8373289, 33431960, 35092039, 76315420, 51715482, 10961421, 63372756, 66611759, 62357986, 2208785, 60430369, 74137926, 75229462, 42237907, 28734791, 65454636, 81677380, 14626618, 89499542, 44844121, 1569515, 15064655, 55753905, 19486173, 78589145, 98648327, 38341669, 75777973, 40197395, 30543215, 34698463, 48260151, 55615885, 39847321, 70372191, 42073124, 92998591, 47708850, 89811711, 49641577, 81774825, 20122224, 28851716, 7687278, 92033260, 85571389, 39201414, 1375023, 37435892, 72357096, 20140249, 61982238, 89699445, 15536795, 17727650, 46540998, 54058788, 10453030, 97057187, 44983451, 6521313, 74743862, 526217, 93566986, 36139942, 22435353, 57241521, 29510992, 17539625, 13231279, 75820087, 72019362, 49882705, 88251446, 28928175, 91141395, 45237957, 79657802, 26776922, 6038457, 51315460, 85023028, 47792865, 36468541, 84406788, 22200378, 6497830, 9860195, 3633375, 68000591, 44784505, 92071990, 92867155, 61263068, 82979980, 76703609, 79880247, 62740044, 4798568, 72614359, 61741594, 34295794, 85116755, 54868730, 66885828, 12348118, 29029316, 73109997, 39373729, 34044787, 74357852, 31126490, 14045383, 82651278, 90004325, 71920426, 82532312, 24314885, 65047700, 14363867, 29994197, 73786944, 40356877, 24953498, 40781854, 824872, 59501487, 99226875, 97783876, 77187825, 38256849, 84187166, 39986008, 37192445, 77413012, 17386996, 82779622, 59318837, 30163921, 22721500, 45306070, 9886593, 38645117, 8115266, 76540481, 2891150, 59371804, 80316608, 58208470, 44348328, 69641513, 22450468, 26998766, 99658235, 3487592, 88653118, 8055981, 73235980, 3088684, 30998561, 79922758, 26114953, 78549759, 20568363, 91831487, 67084351, 49236559, 67451935, 6157724, 27325428, 38008118, 61712234, 20681921, 16595436, 32699744, 53547802, 6153406, 45794415, 19898053, 35456853, 81898046, 86460488, 70727211, 76170907, 65880522, 5482538, 52613508, 75407347, 43045786, 5640302, 39801351, 42644903, 62051033, 21289531, 17016156, 1022677, 60102965, 4204661, 89804152, 58180653, 11543098, 874791, 80014588, 84904436, 65081429, 36845587, 80555751, 37739481, 30463802, 1239555, 83948335, 75986488, 29635537, 57163802, 89214616, 22766820, 44889423, 37620363, 79136082, 70541760, 67281495, 7066775, 99965001, 56461322, 47090124, 58751351, 5970607, 16971929, 96726697, 55143724, 36135, 61859143, 57359924, 30811010, 1204161, 83302115, 94711842, 56484900, 1431742, 14731700, 34497327, 57658654, 9603598, 11757872, 41092102, 86022504, 17037369, 50567636, 56955985, 45848907, 84127901, 57240218, 57803235, 6986898, 83155442, 17081350, 1250437, 46870723, 99971982, 168541, 44550764, 82897371, 2917920, 67124282, 44846932, 36753250, 74614639, 32274392, 31904591, 24733232, 26664538, 32161669, 93057697, 83210802, 40677414, 90158785, 51588015, 22405120, 4095116, 35996293, 26139110, 33336148, 18001617, 29516592, 78218845, 96852321, 83133790, 38510840, 90013093, 8099994, 75153252, 58749, 11923835, 9623492, 66667729, 54199160, 508198, 62115552, 33628349, 61271144, 36580610, 63628376, 36189527, 4978543, 17976208, 59405277, 20885148, 34667286, 68128438, 15163258, 30764367, 34236719, 6525948, 98130363, 15015906, 65715134, 70596786, 22879907, 62490109, 68110330, 30787683, 64602895, 88130087, 30090481, 9575954, 68875490, 13819358, 62803858, 29932657, 77301523, 569864, 89217461, 86543538, 6111563, 43933006, 48088883, 53666583, 42199455, 76671482, 52293995, 8913721, 26507214, 44847298, 415901, 37560164, 92692283, 23110625, 9188443, 7685448, 92604458, 96906410, 32426752, 42692881, 44245960, 71522968, 7646095, 4099191, 56473732, 8791066, 41442762, 68644627, 71333116, 85711894, 97379790, 68316156, 7517032, 77898274, 24915585, 40781449, 27625735, 69355476, 79806380, 49271185, 66428795, 4069912, 99021067, 50668599, 10264691, 16567550, 18806856, 8733068, 12024238, 4668450, 20002147, 67474219, 67030811, 98948034, 3294781, 77072625, 36780454, 62428472, 45996863, 12664567, 36396314, 17385531, 47738185, 30771409, 247198, 16380211, 67793644, 50806615, 66832478, 53888755, 68694897, 5111370, 62693428, 89637706, 36812683, 19457589, 43501211, 54517921, 83368048, 92530431, 61859581, 9599614, 92803766, 76825057, 35512853, 45407418, 16405341, 45617087, 97281847, 30139692, 44473167, 3509435, 19939935, 25842078, 72274002, 16445503, 84840549, 34698428, 66250369, 53842979, 33895336, 82427263, 7229550, 38494874, 66903004, 14326617, 83150534, 21919959, 95010552, 26392416, 30366150, 10358899, 37280276, 749283, 44915235, 15075176, 92398073, 15948937, 97011160, 22997281, 89996536, 59614746, 26734892, 65338021, 20486294, 53648154, 21673260, 80246713, 8807940, 24619760, 89046466, 87598888, 31733363, 99861373, 93790285, 37224844, 59197747, 24826575, 67031644, 62552524, 3235882, 86301513, 63059024, 73168565, 61728685, 55850790, 47887470, 23134715, 7300047, 72793444, 36685023, 66271566, 85117092, 26063929, 73617245, 30653863, 38365584, 32590267, 73124510, 6793819, 51047803, 63967300, 27187213, 70367851, 36808486, 63152504, 6808825, 23740167, 18783702, 86118021, 33237508, 22113133, 52204879, 18131876, 78766358, 7182642, 52060076, 4091162, 37659250, 18699206, 33935899, 91938191, 50188404, 23194618, 29401781, 47298834, 74441448, 78300864, 77284619, 62496012, 66663942, 55669657, 54606384, 83269727, 26744917, 89078848, 40686254, 71657078, 45995325, 34432810, 54987042, 72278539, 14220886, 321665, 65017137, 72358170, 88904910, 95251277, 61960400, 71083565, 45075471, 44481640, 23848565, 19376156, 69136837, 92787493, 41092172, 20642888, 66137019, 75543508, 33797252, 97561745, 90272749, 80251430, 84684495, 71965942, 93359396, 28550822, 61623915, 65271999, 6221471, 58224549, 28796059, 66045587, 57248122, 91802888, 55602660, 23569917, 30694952, 8651647, 59981773, 55470718, 88444207, 47893286, 4199704, 93515664, 10309525, 53802686, 32250045, 72495719, 62430984, 14723410, 1197320, 17857111, 55770687, 21070110, 15902805, 40984766, 38009615, 176257, 48360198, 64157906, 13348726, 45990383, 69255765, 30569392, 7423788, 10649306, 60581278, 42967683, 94360702, 3233569, 17058722, 66322959, 84002370, 46851987, 48892201, 45428665, 9175338, 18411915, 71300104, 99690194, 112651, 5073754, 77694700, 81172706, 71469330, 93562257, 51464002, 83747892, 55960386, 50007421, 7011964, 4508700, 71316369, 29466406, 2607799, 88047921, 16424199, 41481685, 90654836, 72732205, 4119747, 92692978, 74110882, 63015256, 45919976, 60393039, 31491938, 64055960, 56424103, 62762857, 79322415, 37166608, 33201905, 68939068, 26292919, 53632373, 99515901, 52261574, 82726008, 14349098, 44060493, 29819940, 34946859, 79821897, 6871053, 10597197, 48893685, 56515456, 94911072, 44627776, 91281584, 57020481, 18833224, 43506672, 39553046, 15148031, 44842615, 5267545, 84349107, 60176618, 83651211, 99549373, 17764950, 9058407, 85242963, 83533741, 27411561, 14947650, 97940276, 45667668, 94090109, 13173644, 81855445, 21533347, 57961282, 26863229, 86001008, 21001913, 27185644, 33304202, 90457870, 68102437, 40865610, 48395186, 7104732, 44664587, 68702632, 28787861, 3880712, 96735716, 4515343, 75052463, 60106217, 89419466, 70782102, 36933038, 26397786, 64848072, 51472275, 11161731, 51590803, 24591705, 73222868, 82178706, 62232211, 38061439, 3773993, 44177011, 79738755, 78561158, 98739783, 82886381, 93933709, 26275734, 42307449, 16019925, 54427233, 84293052, 62936963, 36505482, 35192533, 61815223, 7955293, 41380093, 92541302, 15535065, 48658605, 18663507, 2204165, 6505939, 59329511, 38022834, 95957797, 77787724, 59910624, 32058615, 19272365, 86798033, 65074535, 16684829, 72777973, 72238278, 9257405, 72373496, 20645197, 73021291, 5822202, 36930650, 24168411, 11581181, 99333375, 8128637, 96709982, 48774913, 7502255, 32151165, 94076128, 94595668, 97641116, 669105, 4985896, 92353856, 56531125, 65038678, 68824981, 49598724, 68041839, 19891772, 69579137, 88698958, 3233084, 17957593, 17894977, 87160386, 75128745, 54762643, 93270084, 36312813, 78602717, 9259676, 81274566, 14093520, 1788101, 91990218, 57393458, 54014062, 77272628, 69848388, 7919588, 81853704, 67227442, 73392814, 21993752, 22942635, 30891921, 64087743, 33061250, 30395570, 70004753, 40027975, 22108665, 61928316, 8129978, 95395112, 33123618, 29959549, 37675718, 16097038, 55189057, 51149804, 99729357, 63506281, 51507425, 72738685, 29188588, 16099750, 98371444, 33249630, 41245325, 25636669, 17146629, 99297164, 29834512, 43376279, 44479073, 56153345, 87720882, 95726235, 43152977, 55247455, 96193415, 70420215, 74452589, 76330843, 4064751, 21397057, 37891451, 73031054, 91255408, 8696647, 79191827, 82327024, 59109400, 13470059, 79942022, 94516935, 45790169, 8634541, 17068582, 49328608, 42782652, 81805959, 98943869, 37957788, 34493392, 32159704, 24058273, 78884452, 85695762, 99604946, 54263880, 75135448, 39171895, 98653983, 13468268, 2331773, 78909561, 69786756, 12303248, 18504197, 27041967, 68204242, 51466049, 30218878, 67513640, 22129328, 65851721, 61897582, 90090964, 62452034, 61141874, 16387575, 99917599, 10366309, 91240048, 23432750, 48673079, 93053405, 98462867, 1936762, 9829782, 70036438, 18466635, 17365188, 38658347, 76434144, 77377183, 77312810, 86242799, 31727379, 68816413, 12416768, 83789391, 8729146, 74724075, 37501808, 51426311, 33699435, 16054533, 6819644, 66319530, 45381876, 40534591, 42947632, 53393358, 69605283, 99524975, 95581843, 53084256 +64602895, 62936963, 37560164, 86118021, 24168411, 60176618, 65338021, 68000591, 62051033, 55850790, 27187213, 16054533, 82339363, 75229462, 89046466, 61373987, 8729146, 84002370, 52204879, 65074535, 73021291, 62762857, 84127901, 2917920, 16387575, 93566986, 84349107, 47361209, 70782102, 6157724, 15948937, 69848388, 73392814, 1569515, 20867149, 37224844, 60581278, 61263068, 89217461, 39847321, 92604458, 33249630, 12303248, 74724075, 37620363, 4099191, 71316369, 78766358, 57359924, 33935899, 34497327, 26744917, 57803235, 17081350, 61141874, 82327024, 36139942, 4434662, 81855445, 3509435, 35092039, 76315420, 93359396, 81274566, 60106217, 55470718, 53802686, 59614746, 21070110, 20486294, 22942635, 87598888, 98648327, 45990383, 38341669, 92867155, 75777973, 73168565, 21289531, 3233569, 12348118, 22113133, 29834512, 43376279, 97379790, 24915585, 7687278, 56153345, 72373496, 47298834, 64055960, 98948034, 9603598, 37166608, 89699445, 31727379, 82726008, 71657078, 94595668, 97057187, 65851721, 97641116, 669105, 44550764, 74743862, 11365791, 29065964, 57020481, 18833224, 83368048, 79191827, 92530431, 31904591, 10366309, 91240048, 75543508, 21533347, 3487592, 73235980, 93270084, 66250369, 66667729, 36312813, 28787861, 49328608, 57248122, 81805959, 33628349, 20568363, 75052463, 98943869, 47792865, 26392416, 92398073, 10309525, 38658347, 22879907, 76170907, 59197747, 76434144, 69255765, 68875490, 20765474, 31161687, 52293995, 8913721, 73617245, 30653863, 30463802, 1239555, 49597667, 57163802, 96906410, 69786756, 66885828, 2204165, 51464002, 33699435, 58751351, 39373729, 96726697, 37659250, 4119747, 91938191, 49271185, 1204161, 68204242, 9257405, 1431742, 86242799, 20002147, 6819644, 77187825, 45996863, 99515901, 46540998, 40534591, 34432810, 22721500, 9886593, 36812683, 61960400, 71083565, 32274392, 45075471, 39553046, 99917599, 19376156, 13470059, 4787945, 16405341, 49598724, 97561745, 71965942, 43357947, 16445503, 51715482, 72019362, 99658235, 40865610, 75128745, 65271999, 58224549, 45237957, 44664587, 3880712, 66045587, 4515343, 79922758, 30694952, 85023028, 68816413, 67451935, 20885148, 18466635, 61712234, 67227442, 24058273, 53393358, 89499542, 69605283, 81898046, 24619760, 93790285, 88130087, 9575954, 8129978, 33553959, 16097038, 7300047, 48088883, 98653983, 66271566, 26063929, 73124510, 72738685, 29188588, 85116755, 16099750, 22766820, 44889423, 7646095, 6505939, 56473732, 99297164, 41442762, 34044787, 18131876, 59910624, 49641577, 41481685, 68316156, 7517032, 77898274, 69355476, 27665211, 79806380, 18699206, 66428795, 92033260, 87720882, 50668599, 23194618, 29994197, 94711842, 56484900, 4668450, 96193415, 36930650, 55669657, 41092102, 45381876, 52261574, 4064751, 48774913, 82779622, 7502255, 247198, 67793644, 69697787, 62693428, 65017137, 72358170, 99125126, 44846932, 88904910, 44627776, 19457589, 91281584, 95251277, 83651211, 35512853, 4806458, 92787493, 22405120, 48673079, 83533741, 2891150, 59371804, 93053405, 26493119, 90272749, 44473167, 58208470, 92215320, 84684495, 3233084, 21001913, 90457870, 87160386, 68102437, 61623915, 66611759, 54199160, 30998561, 508198, 9259676, 38494874, 66903004, 14326617, 1788101, 91990218, 57393458, 30366150, 749283, 15075176, 14626618, 34493392, 14723410, 7919588, 15015906, 73222868, 44844121, 40984766, 64087743, 44177011, 30090481, 77377183, 98739783, 39801351, 13819358, 42644903, 61728685, 29932657, 82886381, 47887470, 17016156, 37675718, 23134715, 4204661, 17058722, 51149804, 34698463, 13468268, 80014588, 79880247, 84293052, 65081429, 35192533, 23110625, 71300104, 89214616, 70372191, 63967300, 5073754, 77694700, 18663507, 79136082, 18504197, 41245325, 67281495, 23740167, 55960386, 18783702, 56461322, 47090124, 8791066, 88047921, 85711894, 61859143, 4091162, 27625735, 71920426, 74110882, 76960303, 99021067, 72777973, 16567550, 95726235, 18806856, 83302115, 60393039, 55247455, 83083131, 66319530, 67030811, 59501487, 99226875, 11757872, 11581181, 33565483, 45848907, 8128637, 77413012, 57240218, 46870723, 17727650, 44060493, 10453030, 6871053, 10597197, 168541, 30163921, 90090964, 82897371, 68694897, 67124282, 5111370, 9599614, 76825057, 51588015, 97940276, 91727510, 26139110, 22435353, 78218845, 96852321, 26863229, 86001008, 33304202, 90013093, 22450468, 26998766, 78785507, 53084256, 68702632, 53842979, 33895336, 96420429, 91802888, 6038457, 3183975, 74137926, 78549759, 8651647, 59981773, 88444207, 84406788, 10358899, 37280276, 9829782, 6497830, 37957788, 9860195, 65454636, 34667286, 27325428, 77272628, 68128438, 94539824, 34236719, 54663246, 6525948, 26734892, 3633375, 78884452, 30891921, 12416768, 86460488, 38061439, 33061250, 3773993, 19486173, 5482538, 78589145, 62552524, 47213183, 86301513, 5640302, 92554120, 77301523, 569864, 42967683, 6111563, 94360702, 43933006, 39171895, 53666583, 26275734, 89804152, 874791, 2331773, 44847298, 37739481, 61741594, 38365584, 6793819, 9188443, 42073124, 43322743, 44245960, 47708850, 93562257, 83747892, 51426311, 25636669, 95957797, 71333116, 2607799, 91664334, 55143724, 16424199, 90654836, 54232247, 65047700, 16684829, 51466049, 12024238, 31491938, 1375023, 99524975, 24953498, 20645197, 40781854, 3294781, 72357096, 824872, 56424103, 79322415, 36780454, 99333375, 67513640, 37192445, 36396314, 53632373, 6986898, 50702367, 83155442, 99224392, 47738185, 79821897, 45995325, 37891451, 99971982, 50806615, 66832478, 86821229, 72278539, 91255408, 14220886, 2717150, 9398733, 45306070, 56531125, 321665, 94911072, 65038678, 54517921, 44481640, 44842615, 93057697, 83210802, 23432750, 17764950, 76540481, 35996293, 66137019, 33336148, 8373289, 94090109, 80251430, 69641513, 98462867, 84840549, 49882705, 28928175, 75153252, 58749, 10961421, 91141395, 34698428, 63372756, 7104732, 28796059, 96735716, 55602660, 62115552, 23569917, 51315460, 9860968, 42947632, 14093520, 21919959, 95010552, 36468541, 61271144, 67084351, 36580610, 13862149, 48675329, 4199704, 36189527, 17976208, 17365188, 22997281, 84166196, 64098930, 17857111, 20681921, 53547802, 6153406, 70596786, 19898053, 21993752, 21673260, 15902805, 83789391, 79738755, 55753905, 22108665, 61928316, 44784505, 78561158, 66116458, 63059024, 43045786, 95395112, 65275241, 62803858, 33123618, 29959549, 72793444, 55189057, 36685023, 76703609, 16019925, 46851987, 26507214, 4798568, 72614359, 80555751, 78909561, 41380093, 34295794, 15535065, 29635537, 59177718, 112651, 92998591, 29029316, 71469330, 7066775, 33237508, 59329511, 16971929, 31126490, 7182642, 36135, 82651278, 82532312, 24314885, 29401781, 85571389, 73786944, 10264691, 8733068, 78300864, 77284619, 30218878, 97783876, 74452589, 86022504, 17037369, 40686254, 1250437, 76330843, 21397057, 29819940, 5832946, 30771409, 32151165, 53888755, 54987042, 8696647, 36753250, 526217, 43501211, 26664538, 61859581, 23848565, 69136837, 40677414, 45407418, 9058407, 85242963, 27411561, 94516935, 68041839, 45667668, 30139692, 57241521, 19891772, 29510992, 25842078, 17957593, 83133790, 72274002, 88251446, 54762643, 62357986, 3088684, 26776922, 91831487, 89419466, 36933038, 26397786, 54014062, 63628376, 22200378, 44915235, 93515664, 59405277, 11161731, 32250045, 51590803, 72495719, 81677380, 32159704, 89996536, 38008118, 1197320, 16595436, 65715134, 82178706, 62490109, 30395570, 54263880, 30787683, 176257, 70727211, 65880522, 13348726, 3235882, 52613508, 75407347, 30569392, 1022677, 77312810, 37183543, 60102965, 82052050, 40197395, 11543098, 85117092, 99729357, 48260151, 55615885, 36505482, 61815223, 19101477, 92692283, 92541302, 98371444, 51047803, 50007421, 17146629, 38022834, 77787724, 29466406, 27041967, 74357852, 81774825, 20122224, 92692978, 63015256, 44479073, 50188404, 72238278, 39201414, 74441448, 14731700, 5822202, 70420215, 77072625, 54606384, 87710366, 62428472, 26292919, 89078848, 17386996, 17385531, 54058788, 59318837, 40152546, 94076128, 44983451, 6521313, 92353856, 45049308, 43506672, 74614639, 15148031, 32161669, 90310261, 8115266, 90158785, 41092172, 79942022, 80316608, 97281847, 13173644, 69579137, 17539625, 13231279, 88698958, 38510840, 28550822, 88653118, 8055981, 60430369, 26114953, 83150534, 95290172, 47893286, 28734791, 4978543, 90061527, 62430984, 15163258, 24591705, 81853704, 45794415, 55770687, 85695762, 53648154, 80246713, 8807940, 38009615, 31733363, 99861373, 40027975, 12571310, 75135448, 48360198, 64157906, 92071990, 7423788, 42307449, 63506281, 66322959, 54427233, 7955293, 83948335, 91957544, 75986488, 45428665, 18411915, 48658605, 7685448, 99690194, 42692881, 37501808, 63152504, 6808825, 70541760, 99965001, 5970607, 14045383, 32058615, 52060076, 30811010, 72732205, 14363867, 4069912, 43152977, 60955663, 67474219, 20140249, 66663942, 61982238, 84187166, 15536795, 12664567, 14349098, 68824981, 24733232, 38645117, 59109400, 99549373, 70800879, 33797252, 45617087, 18001617, 57961282, 75820087, 44348328, 27185644, 48395186, 42782652, 82427263, 78602717, 1936762, 42237907, 49236559, 64848072, 51472275, 30764367, 98130363, 35456853, 62232211, 68110330, 99604946, 70004753, 24826575, 10649306, 82979980, 58180653, 42199455, 51507425, 84904436, 415901, 9175338, 54868730, 81172706, 71522968, 89811711, 68644627, 40781449, 86798033, 40356877, 37435892, 62496012, 57658654, 33201905, 68939068, 50567636, 39986008, 56955985, 96709982, 34946859, 96318094, 16380211, 61897582, 4985896, 48893685, 62452034, 77300457, 5267545, 92803766, 72725103, 4095116, 14947650, 33431960, 8099994, 6221471, 9623492, 7229550, 20836893, 67031644, 86543538, 76671482, 62740044, 36845587, 32590267, 77620120, 32426752, 70367851, 73109997, 36808486, 4508700, 38256849, 73031054, 20642888, 26102057, 19939935, 17894977, 17068582, 11923835, 79657802, 2208785, 70036438, 32699744, 15064655, 93933709, 30543215, 48892201, 28851716, 19272365, 83269727, 56515456, 89637706, 45790169, 29516592, 8634541, 95581843, 97011160, 7011964, 45919976, 22129328, 90004325 +9398733, 70800879, 62357986, 42237907, 62740044, 1239555, 45428665, 30163921, 60176618, 30694952, 75052463, 29932657, 37739481, 63967300, 4119747, 50188404, 61982238, 2891150, 8373289, 80251430, 7104732, 28796059, 85023028, 65454636, 89046466, 52613508, 80014588, 78909561, 18504197, 23740167, 99965001, 29834512, 14045383, 68316156, 82651278, 44479073, 29401781, 12024238, 36930650, 33565483, 21397057, 43506672, 74614639, 91240048, 80316608, 97281847, 98462867, 68102437, 48395186, 28787861, 82427263, 60106217, 59981773, 6497830, 18466635, 51590803, 97011160, 89996536, 64098930, 32699744, 65338021, 19898053, 73222868, 22942635, 19486173, 65880522, 78589145, 13348726, 33123618, 21289531, 29959549, 8913721, 42307449, 84293052, 73617245, 62936963, 61741594, 38365584, 29635537, 7685448, 69786756, 12303248, 37501808, 93562257, 33699435, 32058615, 1204161, 9257405, 40356877, 16567550, 8733068, 47298834, 55247455, 98948034, 54606384, 89699445, 99333375, 77413012, 99515901, 30771409, 10453030, 32151165, 11365791, 8696647, 67124282, 56515456, 95251277, 43501211, 79191827, 24733232, 5267545, 8115266, 35512853, 20642888, 79942022, 33336148, 90013093, 43357947, 90457870, 11923835, 54762643, 4515343, 57248122, 66903004, 42947632, 36468541, 1788101, 26397786, 36580610, 9829782, 9860195, 77272628, 68128438, 30764367, 20681921, 16595436, 69605283, 38009615, 70727211, 76170907, 13819358, 17016156, 37183543, 86543538, 43933006, 30543215, 34698463, 30653863, 4798568, 49597667, 92541302, 98371444, 70372191, 33249630, 66885828, 81172706, 7066775, 18783702, 56461322, 38022834, 7182642, 41481685, 90654836, 16684829, 56153345, 99021067, 6819644, 73021291, 20140249, 824872, 34497327, 74452589, 38256849, 84187166, 26744917, 89078848, 57803235, 83155442, 46540998, 34946859, 45995325, 50806615, 54987042, 90090964, 44550764, 45306070, 94911072, 88904910, 19457589, 45075471, 99917599, 15148031, 90158785, 4806458, 27411561, 14947650, 68041839, 45617087, 18001617, 97561745, 13173644, 92215320, 86001008, 72274002, 8099994, 26998766, 93270084, 36312813, 66045587, 96735716, 55602660, 62115552, 78549759, 37280276, 47893286, 51472275, 15075176, 6157724, 53802686, 32250045, 27325428, 59614746, 61712234, 15015906, 62490109, 44177011, 79738755, 37224844, 48360198, 88130087, 76434144, 67031644, 98648327, 92554120, 75777973, 1022677, 3233569, 58180653, 42199455, 36685023, 76671482, 11543098, 85117092, 874791, 65081429, 30463802, 9175338, 16099750, 12348118, 18663507, 6505939, 51464002, 83747892, 51426311, 4099191, 95957797, 52204879, 2607799, 77898274, 20122224, 28851716, 27625735, 91938191, 87720882, 72777973, 85571389, 51466049, 43152977, 20645197, 30218878, 96193415, 62762857, 97783876, 41092102, 24168411, 11581181, 67513640, 45848907, 53632373, 50702367, 17386996, 40686254, 82726008, 48774913, 82779622, 16380211, 10597197, 74743862, 92353856, 44627776, 36753250, 77300457, 18833224, 71083565, 13470059, 83651211, 22405120, 9058407, 48673079, 91727510, 26139110, 22435353, 90272749, 57241521, 69579137, 21533347, 75820087, 25842078, 38510840, 61623915, 3487592, 6221471, 88653118, 73235980, 3088684, 66667729, 508198, 2208785, 78602717, 23569917, 67084351, 30366150, 84406788, 49236559, 48675329, 17976208, 20885148, 15948937, 81677380, 62430984, 26734892, 53393358, 20486294, 44844121, 86460488, 99604946, 3773993, 30787683, 1569515, 176257, 83789391, 87598888, 31733363, 64602895, 77377183, 62552524, 44784505, 68875490, 20765474, 38341669, 62803858, 73168565, 60581278, 47887470, 77301523, 33553959, 82979980, 23134715, 26275734, 72793444, 99729357, 63506281, 79880247, 2331773, 36845587, 48892201, 83948335, 23110625, 29188588, 71300104, 96906410, 74724075, 44245960, 71522968, 36808486, 79136082, 41245325, 67281495, 47090124, 89811711, 68644627, 78766358, 54232247, 18699206, 65047700, 65074535, 99524975, 99226875, 9603598, 36780454, 31727379, 68939068, 14349098, 4064751, 54058788, 79821897, 96318094, 40152546, 247198, 34432810, 99971982, 73031054, 67793644, 168541, 61897582, 66832478, 91255408, 4985896, 2717150, 62452034, 16387575, 526217, 54517921, 93566986, 32161669, 10366309, 69136837, 83210802, 40677414, 17764950, 16405341, 4095116, 97940276, 45790169, 4434662, 33797252, 49598724, 8634541, 3509435, 88698958, 96852321, 83133790, 71965942, 21001913, 17068582, 76315420, 22450468, 93359396, 84840549, 49882705, 28550822, 53084256, 58749, 10961421, 34698428, 65271999, 8055981, 68702632, 54199160, 3880712, 53842979, 79922758, 33628349, 9860968, 14093520, 47792865, 21919959, 57393458, 54014062, 13862149, 4199704, 36189527, 17365188, 34493392, 38008118, 14723410, 69848388, 17857111, 7919588, 73392814, 38658347, 81898046, 24619760, 70004753, 99861373, 93790285, 8729146, 59197747, 3235882, 78561158, 30569392, 86301513, 5640302, 10649306, 39801351, 62051033, 92867155, 61728685, 82886381, 37675718, 77312810, 6111563, 94360702, 39171895, 48088883, 98653983, 55189057, 40197395, 76703609, 26063929, 66322959, 55615885, 36505482, 61815223, 37560164, 34295794, 85116755, 112651, 92998591, 43322743, 70367851, 2204165, 71469330, 63152504, 6808825, 56473732, 99297164, 71316369, 71333116, 27041967, 43376279, 55143724, 16054533, 97379790, 52060076, 24915585, 72732205, 37659250, 92692978, 74110882, 24314885, 49271185, 19272365, 14363867, 1431742, 78300864, 67474219, 62496012, 59501487, 5822202, 77187825, 62428472, 50567636, 45381876, 15536795, 84127901, 17081350, 96709982, 99224392, 44060493, 59318837, 65851721, 97641116, 22721500, 6521313, 62693428, 72358170, 61141874, 29065964, 91281584, 57020481, 39553046, 44481640, 44842615, 36139942, 19376156, 84349107, 59109400, 85242963, 66137019, 26493119, 45667668, 78218845, 19891772, 29510992, 13231279, 44348328, 3233084, 33304202, 51715482, 88251446, 99658235, 75128745, 91141395, 58224549, 44664587, 95581843, 79657802, 33895336, 60430369, 26114953, 1936762, 91831487, 68816413, 55470718, 83150534, 70782102, 63628376, 22200378, 44915235, 37957788, 11161731, 34667286, 90061527, 22997281, 94539824, 32159704, 34236719, 6525948, 24058273, 89499542, 21070110, 15902805, 8807940, 68110330, 64087743, 20867149, 15064655, 24826575, 92071990, 69255765, 7423788, 43045786, 98739783, 65275241, 93933709, 82052050, 51149804, 48260151, 16019925, 54427233, 84904436, 84002370, 7955293, 415901, 73124510, 92692283, 57163802, 48658605, 51047803, 54868730, 42073124, 22766820, 7646095, 37620363, 70541760, 55960386, 8791066, 17146629, 58751351, 34044787, 77787724, 74357852, 88047921, 59910624, 16424199, 49641577, 36135, 7517032, 57359924, 30811010, 71920426, 79806380, 7687278, 86798033, 66428795, 92033260, 68204242, 45919976, 94711842, 60393039, 31491938, 64055960, 83083131, 86242799, 40781854, 20002147, 77284619, 72357096, 37166608, 33201905, 17037369, 45996863, 26292919, 1250437, 47738185, 29819940, 7502255, 71657078, 40534591, 37891451, 97057187, 53888755, 14220886, 56531125, 32274392, 92530431, 68824981, 61859581, 9599614, 72725103, 99549373, 45407418, 92787493, 76540481, 26102057, 35996293, 94516935, 75543508, 30139692, 29516592, 94090109, 47361209, 27185644, 35092039, 87160386, 9623492, 66250369, 42782652, 91802888, 6038457, 20568363, 98943869, 61271144, 70036438, 92398073, 10309525, 14626618, 15163258, 84166196, 6153406, 85695762, 21993752, 80246713, 12416768, 33061250, 54263880, 40027975, 75135448, 9575954, 55850790, 52293995, 13468268, 51507425, 46851987, 72614359, 44847298, 41380093, 77620120, 15535065, 75986488, 39847321, 6793819, 9188443, 99690194, 5073754, 47708850, 7011964, 25636669, 4508700, 39373729, 33237508, 5970607, 96726697, 85711894, 81774825, 61859143, 4091162, 82532312, 27665211, 33935899, 76960303, 72373496, 29994197, 39201414, 18806856, 37435892, 24953498, 74441448, 67030811, 66663942, 56424103, 70420215, 57658654, 11757872, 55669657, 22129328, 46870723, 44983451, 69697787, 2917920, 89637706, 65017137, 99125126, 9886593, 44846932, 45049308, 65038678, 61960400, 83368048, 31904591, 93057697, 38645117, 23432750, 4787945, 41092172, 83533741, 33431960, 17539625, 58208470, 57961282, 69641513, 17957593, 16445503, 78785507, 63372756, 66611759, 30998561, 7229550, 3183975, 81805959, 51315460, 38494874, 8651647, 81274566, 14326617, 36933038, 26392416, 91990218, 10358899, 64848072, 93515664, 4978543, 59405277, 54663246, 98130363, 24591705, 45794415, 55770687, 35456853, 38061439, 30395570, 61373987, 5482538, 30090481, 68000591, 61928316, 45990383, 75407347, 8129978, 95395112, 61263068, 569864, 7300047, 4204661, 89804152, 17058722, 66271566, 26507214, 32590267, 18411915, 89214616, 59177718, 77694700, 86118021, 50007421, 16971929, 18131876, 31126490, 40781449, 72238278, 73786944, 10264691, 95726235, 83302115, 1375023, 56484900, 60955663, 14731700, 66319530, 3294781, 79322415, 83269727, 57240218, 52261574, 94595668, 669105, 321665, 36812683, 51588015, 44473167, 81855445, 84684495, 19939935, 28928175, 75153252, 45237957, 49328608, 89419466, 95010552, 28734791, 72495719, 20836893, 67227442, 3633375, 70596786, 78884452, 53648154, 82178706, 62232211, 21673260, 40984766, 64157906, 66116458, 63059024, 89217461, 60102965, 35192533, 91957544, 72738685, 92604458, 32426752, 42692881, 27187213, 44889423, 41442762, 22113133, 91664334, 90004325, 4069912, 23194618, 4668450, 86022504, 56955985, 8128637, 36396314, 6986898, 17385531, 76330843, 17727650, 6871053, 86821229, 82897371, 48893685, 68694897, 5111370, 92803766, 93053405, 17894977, 40865610, 96420429, 74137926, 9259676, 749283, 67451935, 1197320, 81853704, 65715134, 30891921, 12571310, 55753905, 22108665, 47213183, 42644903, 31161687, 19101477, 29029316, 73109997, 59329511, 69355476, 63015256, 50668599, 87710366, 12664567, 94076128, 72278539, 26664538, 82327024, 90310261, 76825057, 26863229, 72019362, 26776922, 95290172, 88444207, 53547802, 22879907, 53666583, 77072625, 39986008, 37192445, 5832946, 82339363, 16097038, 80555751, 29466406, 23848565, 59371804, 75229462, 42967683 +65074535, 65038678, 99917599, 31904591, 84349107, 26139110, 1569515, 18783702, 32058615, 12664567, 50702367, 37891451, 48893685, 68824981, 82327024, 75128745, 92398073, 10309525, 26734892, 99861373, 76170907, 17058722, 54427233, 4099191, 33699435, 59910624, 91938191, 34946859, 79821897, 321665, 60176618, 81855445, 66250369, 95581843, 55470718, 14626618, 34236719, 6153406, 52613508, 98739783, 65275241, 77312810, 80014588, 70372191, 27187213, 56461322, 17146629, 16054533, 99021067, 72777973, 72373496, 34497327, 86821229, 74743862, 94911072, 95251277, 83651211, 4787945, 45617087, 40865610, 54199160, 49328608, 74137926, 85023028, 21919959, 20885148, 77272628, 76434144, 68000591, 78561158, 75407347, 66116458, 26063929, 77620120, 48658605, 96906410, 32426752, 70367851, 37501808, 67281495, 50007421, 68644627, 71333116, 57359924, 74110882, 27665211, 18699206, 1204161, 824872, 15536795, 40686254, 83155442, 99515901, 52261574, 50806615, 97641116, 30163921, 66832478, 44983451, 45306070, 67124282, 56531125, 93566986, 59109400, 99549373, 41092172, 85242963, 94516935, 29516592, 8373289, 49882705, 28550822, 6221471, 88653118, 28787861, 6038457, 1936762, 68816413, 36580610, 54014062, 47893286, 59405277, 54663246, 17857111, 20836893, 81853704, 24058273, 53393358, 73222868, 80246713, 24619760, 55753905, 45990383, 44784505, 8129978, 43045786, 75777973, 82886381, 37675718, 569864, 23134715, 39171895, 60102965, 53666583, 8913721, 26507214, 32590267, 41380093, 23110625, 89214616, 92604458, 69786756, 18504197, 7011964, 33237508, 16971929, 78766358, 16424199, 81774825, 7517032, 16684829, 23194618, 95726235, 94711842, 74441448, 20002147, 62762857, 33565483, 50567636, 17081350, 76330843, 46870723, 5832946, 32151165, 10597197, 34432810, 99971982, 67793644, 91255408, 22721500, 90090964, 56515456, 5111370, 57020481, 526217, 44481640, 10366309, 48673079, 4095116, 97281847, 94090109, 58208470, 3509435, 44348328, 98462867, 17068582, 75153252, 66611759, 93270084, 28796059, 33895336, 60430369, 57248122, 55602660, 3183975, 81805959, 60106217, 61271144, 88444207, 26397786, 49236559, 28734791, 15075176, 15948937, 32159704, 6525948, 7919588, 19898053, 21993752, 30891921, 8807940, 64087743, 99604946, 64602895, 62552524, 9575954, 3235882, 63059024, 62051033, 21289531, 89804152, 98653983, 42199455, 36685023, 48260151, 13468268, 84293052, 4798568, 61815223, 19101477, 73124510, 75986488, 85116755, 7685448, 51047803, 54868730, 12303248, 22766820, 81172706, 7646095, 6505939, 55960386, 86118021, 99297164, 22113133, 5970607, 52204879, 74357852, 96726697, 31126490, 88047921, 4091162, 20122224, 19272365, 68204242, 45919976, 16567550, 78300864, 5822202, 54606384, 37166608, 68939068, 45848907, 17727650, 82779622, 21397057, 96318094, 6871053, 45995325, 94595668, 669105, 69697787, 62452034, 65017137, 9886593, 61141874, 29065964, 16387575, 83368048, 79191827, 92530431, 17764950, 75543508, 80316608, 45790169, 45667668, 44473167, 29510992, 84684495, 17894977, 76315420, 8099994, 88251446, 28928175, 48395186, 66667729, 30998561, 2208785, 20568363, 47792865, 83150534, 36933038, 57393458, 30366150, 13862149, 37280276, 22200378, 93515664, 37957788, 32250045, 90061527, 68128438, 15163258, 67227442, 65715134, 65338021, 70596786, 78884452, 89499542, 85695762, 68110330, 30395570, 54263880, 61373987, 31733363, 79738755, 37224844, 48360198, 88130087, 98648327, 77377183, 47213183, 68875490, 95395112, 42644903, 33123618, 29959549, 93933709, 58180653, 30543215, 16019925, 874791, 84904436, 91957544, 37560164, 92692283, 72738685, 45428665, 9175338, 71300104, 42073124, 33249630, 43322743, 77694700, 74724075, 44889423, 71522968, 51464002, 41245325, 25636669, 59329511, 95957797, 29834512, 27041967, 14045383, 97379790, 61859143, 30811010, 72732205, 79806380, 49271185, 92033260, 14363867, 56153345, 50668599, 72238278, 29994197, 40781854, 14731700, 67474219, 67030811, 77284619, 62496012, 70420215, 57658654, 74452589, 11581181, 26744917, 57803235, 6986898, 40534591, 16380211, 168541, 53888755, 54987042, 4985896, 11365791, 61960400, 15148031, 26664538, 9599614, 93057697, 36139942, 92803766, 40677414, 13470059, 51588015, 92787493, 9058407, 70800879, 66137019, 59371804, 93053405, 68041839, 97561745, 30139692, 80251430, 26863229, 86001008, 3233084, 83133790, 72274002, 87160386, 58749, 11923835, 62357986, 7104732, 3088684, 26776922, 96735716, 42782652, 82427263, 79922758, 91802888, 78602717, 78549759, 33628349, 8651647, 66903004, 59981773, 42947632, 91990218, 70036438, 6497830, 6157724, 53802686, 62430984, 84166196, 98130363, 1197320, 24591705, 61712234, 15015906, 16595436, 32699744, 82178706, 62232211, 33061250, 3773993, 20867149, 87598888, 93790285, 59197747, 67031644, 22108665, 69255765, 7423788, 39801351, 38341669, 31161687, 73168565, 61728685, 29932657, 60581278, 77301523, 33553959, 37183543, 42967683, 86543538, 94360702, 34698463, 63506281, 51507425, 44847298, 36845587, 62936963, 37739481, 35192533, 30463802, 48892201, 83948335, 49597667, 39847321, 92998591, 5073754, 29029316, 44245960, 37620363, 63152504, 99965001, 4508700, 41442762, 89811711, 77787724, 41481685, 68316156, 71920426, 24314885, 66428795, 76960303, 44479073, 87720882, 29401781, 9257405, 85571389, 83302115, 99524975, 43152977, 55247455, 20645197, 86242799, 4668450, 6819644, 56424103, 96193415, 9603598, 36930650, 77187825, 24168411, 8128637, 36396314, 82726008, 22129328, 4064751, 7502255, 40152546, 61897582, 14220886, 2917920, 62693428, 44627776, 36812683, 91281584, 18833224, 43506672, 74614639, 71083565, 39553046, 82339363, 24733232, 44842615, 23848565, 69136837, 83210802, 90310261, 76825057, 16405341, 14947650, 8634541, 13173644, 69579137, 47361209, 21533347, 19939935, 25842078, 71965942, 90013093, 43357947, 84840549, 61623915, 53084256, 10961421, 58224549, 36312813, 66045587, 4515343, 96420429, 7229550, 23569917, 9259676, 26392416, 749283, 44915235, 18466635, 34667286, 97011160, 94539824, 89996536, 69848388, 20681921, 53547802, 73392814, 69605283, 20486294, 35456853, 53648154, 22879907, 22942635, 70004753, 83789391, 40027975, 8729146, 64157906, 10649306, 13819358, 92554120, 62803858, 55850790, 17016156, 82979980, 43933006, 7300047, 4204661, 40197395, 11543098, 52293995, 85117092, 79880247, 30653863, 72614359, 78909561, 1239555, 38365584, 15535065, 29188588, 6793819, 16099750, 18411915, 99690194, 112651, 42692881, 12348118, 36808486, 79136082, 8791066, 71316369, 29466406, 55143724, 85711894, 82651278, 37659250, 33935899, 50188404, 8733068, 1375023, 24953498, 3294781, 20140249, 59501487, 99226875, 66663942, 77072625, 38256849, 17037369, 67513640, 39986008, 37192445, 45381876, 26292919, 77413012, 84127901, 44060493, 29819940, 46540998, 65851721, 92353856, 8696647, 68694897, 89637706, 44846932, 77300457, 43501211, 54517921, 45075471, 91240048, 35512853, 45407418, 20642888, 26102057, 79942022, 35996293, 91727510, 22435353, 33431960, 17539625, 21001913, 35092039, 90457870, 68102437, 99658235, 63372756, 65271999, 8055981, 9623492, 45237957, 44664587, 98943869, 75229462, 95010552, 70782102, 1788101, 9829782, 36189527, 17976208, 34493392, 30764367, 38008118, 55770687, 38658347, 44844121, 21673260, 12416768, 44177011, 89046466, 176257, 75135448, 19486173, 5482538, 78589145, 30090481, 92071990, 61263068, 89217461, 6111563, 3233569, 26275734, 66322959, 73617245, 55615885, 65081429, 2331773, 80555751, 36505482, 92541302, 34295794, 9188443, 59177718, 63967300, 66885828, 47708850, 93562257, 73109997, 6808825, 70541760, 51426311, 58751351, 39373729, 18131876, 24915585, 40781449, 4119747, 54232247, 69355476, 86798033, 63015256, 39201414, 73786944, 47298834, 64055960, 1431742, 98948034, 30218878, 97783876, 55669657, 86022504, 87710366, 36780454, 83269727, 89699445, 62428472, 56955985, 89078848, 17385531, 99224392, 47738185, 71657078, 94076128, 97057187, 72358170, 99125126, 19457589, 32161669, 61859581, 19376156, 23432750, 72725103, 76540481, 97940276, 2891150, 26493119, 78218845, 13231279, 88698958, 27185644, 16445503, 51715482, 93359396, 72019362, 26998766, 78785507, 54762643, 68702632, 79657802, 53842979, 508198, 30694952, 51315460, 75052463, 81274566, 89419466, 14093520, 14326617, 67084351, 64848072, 67451935, 9860195, 27325428, 59614746, 14723410, 45794415, 21070110, 40984766, 86460488, 15064655, 24826575, 13348726, 86301513, 5640302, 20765474, 1022677, 82052050, 66271566, 51149804, 42307449, 99729357, 84002370, 46851987, 62740044, 7955293, 29635537, 98371444, 18663507, 83747892, 56473732, 34044787, 43376279, 91664334, 77898274, 90004325, 28851716, 7687278, 65047700, 4069912, 40356877, 12024238, 56484900, 66319530, 61982238, 11757872, 33201905, 31727379, 45996863, 96709982, 1250437, 14349098, 48774913, 59318837, 73031054, 72278539, 44550764, 82897371, 45049308, 22405120, 83533741, 27411561, 4434662, 33797252, 49598724, 33336148, 57241521, 19891772, 75820087, 69641513, 96852321, 38510840, 22450468, 73235980, 38494874, 95290172, 36468541, 42237907, 84406788, 63628376, 51472275, 4978543, 11161731, 72495719, 81677380, 22997281, 15902805, 61928316, 47887470, 48088883, 57163802, 71469330, 23740167, 7066775, 47090124, 2607799, 7182642, 36135, 18806856, 73021291, 72357096, 41092102, 99333375, 53632373, 57240218, 17386996, 30771409, 54058788, 10453030, 9398733, 88904910, 36753250, 32274392, 5267545, 38645117, 90158785, 4806458, 18001617, 90272749, 57961282, 33304202, 3487592, 91141395, 34698428, 62115552, 26114953, 91831487, 48675329, 4199704, 65454636, 51590803, 3633375, 81898046, 38009615, 38061439, 30787683, 70727211, 12571310, 30569392, 38022834, 52060076, 90654836, 92692978, 82532312, 10264691, 51466049, 37435892, 247198, 6521313, 8115266, 17957593, 3880712, 9860968, 10358899, 17365188, 92867155, 2204165, 49641577, 27625735, 60393039, 31491938, 83083131, 79322415, 2717150, 92215320, 65880522, 16097038, 55189057, 76671482, 76703609, 61741594, 415901, 84187166, 64098930, 62490109, 72793444, 60955663 +36312813, 95251277, 66250369, 80014588, 4099191, 22113133, 4119747, 95726235, 32151165, 10366309, 76315420, 89499542, 33061250, 61373987, 37675718, 51149804, 67281495, 43376279, 76960303, 824872, 68939068, 26292919, 57803235, 6871053, 56515456, 29065964, 65038678, 91727510, 49882705, 75128745, 68702632, 15948937, 6153406, 89046466, 55753905, 64602895, 6111563, 26063929, 2331773, 26507214, 30463802, 73124510, 74110882, 65074535, 20002147, 99515901, 71657078, 247198, 168541, 30163921, 669105, 45075471, 44481640, 60176618, 83651211, 22405120, 79942022, 75543508, 68102437, 54199160, 42947632, 75229462, 83150534, 26397786, 28734791, 14626618, 54663246, 70596786, 99604946, 30395570, 31733363, 98648327, 39801351, 55850790, 23134715, 16097038, 37739481, 7685448, 27187213, 29029316, 37620363, 51464002, 18504197, 68644627, 59329511, 27041967, 49641577, 55247455, 40781854, 59501487, 34497327, 12664567, 53632373, 52261574, 94595668, 67793644, 11365791, 44846932, 88904910, 45049308, 43506672, 61960400, 92530431, 9599614, 59109400, 80316608, 8373289, 69579137, 19939935, 35092039, 93359396, 87160386, 6221471, 58224549, 73235980, 93270084, 66667729, 95581843, 75052463, 8651647, 47792865, 55470718, 21919959, 88444207, 17365188, 32159704, 65715134, 3633375, 65338021, 80246713, 1569515, 48360198, 78589145, 62552524, 75407347, 98739783, 38341669, 29932657, 60581278, 39171895, 42199455, 16019925, 13468268, 79880247, 84293052, 72614359, 36505482, 77620120, 15535065, 33249630, 12303248, 36808486, 55960386, 33237508, 5970607, 78766358, 85711894, 32058615, 1204161, 29994197, 94711842, 73021291, 99226875, 77187825, 11581181, 31727379, 45848907, 46870723, 4064751, 48774913, 5832946, 66832478, 86821229, 74743862, 56531125, 321665, 89637706, 94911072, 9886593, 16387575, 54517921, 15148031, 32161669, 5267545, 94516935, 66137019, 26139110, 90013093, 8099994, 16445503, 40865610, 48395186, 7104732, 44664587, 28796059, 30998561, 96735716, 82427263, 85023028, 61271144, 26392416, 37280276, 36189527, 44915235, 4978543, 51590803, 77272628, 15163258, 7919588, 61712234, 53393358, 73392814, 78884452, 69605283, 21070110, 40984766, 176257, 93790285, 37224844, 65880522, 76434144, 47213183, 30569392, 75777973, 61728685, 82886381, 29959549, 93933709, 94360702, 7300047, 48088883, 26275734, 89804152, 85117092, 84002370, 62740044, 36845587, 35192533, 19101477, 48892201, 1239555, 72738685, 9175338, 51047803, 71300104, 89214616, 70372191, 42692881, 81172706, 18783702, 7011964, 41442762, 16971929, 52204879, 31126490, 7517032, 52060076, 37659250, 82532312, 18699206, 7687278, 72777973, 83302115, 12024238, 24953498, 20645197, 4668450, 6819644, 98948034, 5822202, 36930650, 33201905, 17037369, 37192445, 15536795, 50702367, 17081350, 96709982, 17385531, 1250437, 40534591, 96318094, 99971982, 65851721, 73031054, 22721500, 2717150, 82897371, 65017137, 526217, 43501211, 31904591, 84349107, 69136837, 90158785, 23432750, 4787945, 35512853, 48673079, 85242963, 30139692, 80251430, 47361209, 21533347, 57961282, 3509435, 69641513, 96852321, 25842078, 83133790, 17894977, 72019362, 88251446, 99658235, 3487592, 74137926, 30694952, 51315460, 91831487, 60106217, 36933038, 1788101, 36580610, 91990218, 93515664, 59405277, 53802686, 62430984, 84166196, 34236719, 6525948, 17857111, 20836893, 32699744, 62232211, 44844121, 12416768, 24619760, 54263880, 70004753, 87598888, 8729146, 76170907, 12571310, 52613508, 69255765, 95395112, 92554120, 20765474, 62051033, 62803858, 17016156, 77312810, 86543538, 43933006, 98653983, 40197395, 34698463, 48260151, 54427233, 73617245, 44847298, 61741594, 91957544, 38365584, 37560164, 75986488, 57163802, 98371444, 63967300, 22766820, 77694700, 74724075, 71469330, 41245325, 7066775, 99965001, 56461322, 47090124, 77787724, 91664334, 55143724, 16054533, 57359924, 24915585, 54232247, 91938191, 19272365, 44479073, 99021067, 39201414, 73786944, 47298834, 99524975, 83083131, 14731700, 77072625, 9603598, 74452589, 54606384, 99333375, 50567636, 67513640, 8128637, 77413012, 40686254, 82726008, 79821897, 40152546, 34432810, 50806615, 53888755, 54987042, 4985896, 90090964, 69697787, 68694897, 62693428, 36812683, 19457589, 18833224, 71083565, 93566986, 26664538, 82327024, 23848565, 83210802, 76825057, 8115266, 99549373, 45407418, 97940276, 2891150, 49598724, 45617087, 97281847, 90272749, 44473167, 33431960, 81855445, 84684495, 98462867, 71965942, 21001913, 72274002, 84840549, 78785507, 75153252, 53084256, 88653118, 62357986, 28787861, 66045587, 49328608, 4515343, 57248122, 79922758, 3183975, 81805959, 33628349, 66903004, 70782102, 57393458, 84406788, 10358899, 9829782, 4199704, 6497830, 67451935, 17976208, 6157724, 92398073, 72495719, 22997281, 59614746, 85695762, 20486294, 81898046, 83789391, 40027975, 75135448, 5482538, 44784505, 92071990, 66116458, 8129978, 68875490, 10649306, 13819358, 31161687, 92867155, 73168565, 1022677, 569864, 60102965, 82052050, 52293995, 76703609, 51507425, 46851987, 4798568, 62936963, 83948335, 41380093, 92604458, 96906410, 54868730, 42073124, 66885828, 5073754, 44889423, 44245960, 47708850, 73109997, 79136082, 23740167, 86118021, 33699435, 17146629, 58751351, 39373729, 34044787, 71316369, 29834512, 18131876, 2607799, 88047921, 59910624, 81774825, 77898274, 4091162, 40781449, 90654836, 28851716, 72732205, 69355476, 86798033, 66428795, 63015256, 4069912, 74441448, 1431742, 66319530, 77284619, 62496012, 56424103, 11757872, 62762857, 38256849, 41092102, 37166608, 33565483, 56955985, 57240218, 17386996, 34946859, 45995325, 44983451, 14220886, 48893685, 9398733, 45306070, 67124282, 99125126, 36753250, 32274392, 24733232, 44842615, 93057697, 36139942, 90310261, 13470059, 4806458, 92787493, 17764950, 16405341, 9058407, 76540481, 70800879, 93053405, 4434662, 68041839, 26493119, 29516592, 94090109, 13173644, 26863229, 33304202, 90457870, 28928175, 28550822, 58749, 91141395, 34698428, 66611759, 54762643, 53842979, 33895336, 60430369, 55602660, 6038457, 78602717, 78549759, 38494874, 1936762, 81274566, 68816413, 59981773, 14326617, 67084351, 30366150, 13862149, 63628376, 49236559, 749283, 70036438, 15075176, 20885148, 32250045, 14723410, 69848388, 26734892, 67227442, 45794415, 19898053, 55770687, 38658347, 35456853, 21673260, 44177011, 59197747, 88130087, 13348726, 68000591, 22108665, 3235882, 86301513, 7423788, 65275241, 77301523, 89217461, 82979980, 76671482, 8913721, 84904436, 30653863, 78909561, 49597667, 23110625, 92541302, 39847321, 6793819, 85116755, 9188443, 18411915, 99690194, 59177718, 32426752, 112651, 7646095, 6808825, 51426311, 4508700, 38022834, 29466406, 74357852, 96726697, 20122224, 30811010, 71920426, 27665211, 92033260, 14363867, 9257405, 16567550, 8733068, 1375023, 64055960, 78300864, 67474219, 20140249, 57658654, 55669657, 45381876, 89078848, 6986898, 14349098, 21397057, 7502255, 10453030, 59318837, 10597197, 97057187, 44550764, 92353856, 2917920, 72358170, 61141874, 57020481, 79191827, 99917599, 68824981, 19376156, 40677414, 38645117, 41092172, 4095116, 26102057, 14947650, 59371804, 22435353, 8634541, 58208470, 92215320, 75820087, 86001008, 17068582, 26998766, 45237957, 42782652, 91802888, 62115552, 23569917, 20568363, 9860968, 95290172, 54014062, 22200378, 9860195, 18466635, 27325428, 97011160, 81677380, 30764367, 98130363, 1197320, 24591705, 15015906, 20681921, 24058273, 21993752, 22942635, 30891921, 62490109, 86460488, 38061439, 3773993, 30787683, 99861373, 79738755, 19486173, 64157906, 77377183, 45990383, 63059024, 42644903, 47887470, 61263068, 42967683, 4204661, 53666583, 17058722, 58180653, 30543215, 63506281, 55615885, 65081429, 80555751, 61815223, 92692283, 34295794, 45428665, 29188588, 29635537, 16099750, 69786756, 92998591, 37501808, 93562257, 6505939, 83747892, 99297164, 71333116, 14045383, 97379790, 16424199, 68316156, 90004325, 33935899, 65047700, 50188404, 50668599, 68204242, 29401781, 40356877, 51466049, 18806856, 60393039, 43152977, 56484900, 3294781, 72357096, 66663942, 61982238, 79322415, 89699445, 84187166, 26744917, 83155442, 22129328, 82779622, 46540998, 16380211, 97641116, 8696647, 5111370, 62452034, 77300457, 74614639, 39553046, 82339363, 61859581, 91240048, 72725103, 35996293, 45790169, 33797252, 18001617, 97561745, 57241521, 78218845, 44348328, 88698958, 27185644, 43357947, 63372756, 65271999, 9623492, 3088684, 79657802, 508198, 2208785, 26114953, 14093520, 47893286, 37957788, 10309525, 34667286, 34493392, 89996536, 38008118, 16595436, 73222868, 15902805, 70727211, 15064655, 30090481, 61928316, 78561158, 55189057, 66271566, 42307449, 874791, 7955293, 415901, 43322743, 12348118, 18663507, 63152504, 50007421, 25636669, 8791066, 36135, 82651278, 61859143, 49271185, 16684829, 56153345, 23194618, 72373496, 37435892, 86242799, 67030811, 30218878, 97783876, 24168411, 87710366, 36780454, 62428472, 76330843, 99224392, 44060493, 30771409, 37891451, 94076128, 61897582, 91281584, 83368048, 29510992, 13231279, 3233084, 38510840, 22450468, 61623915, 10961421, 11923835, 8055981, 26776922, 9259676, 98943869, 64848072, 51472275, 48675329, 68128438, 81853704, 53648154, 22879907, 38009615, 64087743, 67031644, 9575954, 32590267, 48658605, 70367851, 70541760, 41481685, 92692978, 24314885, 72238278, 85571389, 45919976, 10264691, 96193415, 86022504, 84127901, 17727650, 54058788, 91255408, 6521313, 27411561, 33336148, 45667668, 19891772, 96420429, 7229550, 95010552, 42237907, 65454636, 11161731, 94539824, 53547802, 68110330, 20867149, 24826575, 43045786, 21289531, 33553959, 3233569, 72793444, 11543098, 66322959, 56473732, 7182642, 27625735, 79806380, 87720882, 60955663, 70420215, 83269727, 39986008, 29819940, 72278539, 44627776, 92803766, 17539625, 3880712, 89419466, 36468541, 90061527, 8807940, 5640302, 33123618, 37183543, 71522968, 2204165, 89811711, 31491938, 45996863, 47738185, 51588015, 20642888, 17957593, 64098930, 82178706, 36685023, 95957797, 99729357, 36396314, 51715482, 83533741 +27411561, 22113133, 97379790, 44915235, 4978543, 53547802, 55753905, 92554120, 16097038, 89804152, 41245325, 55143724, 99971982, 65851721, 26664538, 92787493, 17068582, 35456853, 7955293, 36808486, 66663942, 6986898, 17385531, 82897371, 71083565, 38645117, 33797252, 38510840, 73235980, 21919959, 82886381, 21289531, 37675718, 55615885, 41380093, 72738685, 32426752, 71469330, 39373729, 89811711, 7182642, 52060076, 40781449, 66428795, 50668599, 72777973, 77072625, 86022504, 89699445, 26744917, 30771409, 71657078, 69697787, 29065964, 54517921, 2891150, 45617087, 94090109, 47361209, 3509435, 96852321, 26863229, 19939935, 3487592, 45237957, 93270084, 33895336, 91990218, 30366150, 54014062, 51472275, 15075176, 8807940, 40984766, 30395570, 30787683, 75135448, 30090481, 78561158, 68875490, 61263068, 99729357, 91957544, 92692283, 6808825, 58751351, 34044787, 59329511, 96726697, 4091162, 4069912, 18806856, 91255408, 22721500, 6521313, 89637706, 45075471, 83368048, 93566986, 22405120, 4095116, 26493119, 18001617, 8634541, 44348328, 51715482, 99658235, 63372756, 58224549, 96420429, 78602717, 66903004, 9860968, 93515664, 6157724, 38008118, 81853704, 33061250, 64157906, 22108665, 61928316, 62552524, 66116458, 7423788, 82979980, 93933709, 7300047, 60102965, 63506281, 79880247, 32590267, 37560164, 34295794, 85116755, 77694700, 51464002, 7066775, 4099191, 4508700, 49641577, 32058615, 77898274, 90004325, 71920426, 24314885, 19272365, 63015256, 76960303, 72238278, 9257405, 73786944, 24953498, 74441448, 14731700, 98948034, 62496012, 56424103, 79322415, 41092102, 17037369, 56955985, 84127901, 57240218, 17727650, 59318837, 94076128, 50806615, 30163921, 66832478, 14220886, 62452034, 61960400, 92530431, 9599614, 83210802, 17764950, 35996293, 14947650, 97561745, 90272749, 29510992, 58208470, 25842078, 90457870, 93359396, 87160386, 72019362, 84840549, 40865610, 11923835, 66667729, 3183975, 51315460, 98943869, 95290172, 36933038, 88444207, 37280276, 22200378, 749283, 48675329, 67451935, 59405277, 10309525, 53802686, 62430984, 67227442, 24058273, 78884452, 38658347, 82178706, 176257, 93790285, 64602895, 5482538, 68000591, 69255765, 63059024, 60581278, 55189057, 40197395, 11543098, 51149804, 73617245, 30653863, 65081429, 62740044, 72614359, 44847298, 36505482, 19101477, 39847321, 16099750, 7685448, 99690194, 70372191, 12348118, 29029316, 44889423, 81172706, 37501808, 6505939, 83747892, 18783702, 50007421, 56461322, 25636669, 41442762, 5970607, 29466406, 18131876, 91664334, 14045383, 28851716, 27625735, 49271185, 92033260, 14363867, 10264691, 51466049, 60393039, 56484900, 60955663, 77284619, 59501487, 70420215, 97783876, 55669657, 24168411, 87710366, 83269727, 84187166, 39986008, 45381876, 77413012, 99515901, 4064751, 54058788, 10453030, 53888755, 44983451, 90090964, 2917920, 5111370, 56531125, 65017137, 88904910, 84349107, 76825057, 4787945, 4806458, 59371804, 68041839, 45667668, 97281847, 8373289, 13231279, 69641513, 84684495, 72274002, 76315420, 58749, 54762643, 7104732, 3088684, 66250369, 36312813, 95581843, 26776922, 96735716, 42782652, 55602660, 23569917, 81274566, 89419466, 75229462, 37957788, 90061527, 94539824, 32159704, 30764367, 20836893, 24591705, 65715134, 3633375, 45794415, 55770687, 73222868, 62232211, 21673260, 24826575, 65880522, 44784505, 92071990, 75407347, 8129978, 13819358, 42967683, 6111563, 94360702, 48088883, 53666583, 98653983, 36685023, 13468268, 54427233, 80014588, 415901, 83948335, 38365584, 92541302, 15535065, 45428665, 57163802, 71300104, 89214616, 92604458, 42073124, 12303248, 27187213, 73109997, 55960386, 99965001, 8791066, 33237508, 78766358, 31126490, 61859143, 57359924, 74110882, 99021067, 72373496, 83302115, 37435892, 64055960, 40781854, 67474219, 72357096, 96193415, 11581181, 31727379, 37192445, 12664567, 82726008, 47738185, 29819940, 34946859, 45995325, 247198, 94595668, 61897582, 669105, 72278539, 44550764, 48893685, 62693428, 72358170, 61141874, 36753250, 526217, 77300457, 18833224, 82339363, 82327024, 10366309, 36139942, 40677414, 8115266, 23432750, 41092172, 48673079, 83533741, 79942022, 91727510, 33336148, 44473167, 92215320, 57961282, 86001008, 27185644, 33304202, 17894977, 49882705, 28928175, 28550822, 10961421, 91141395, 28787861, 2208785, 91802888, 62115552, 74137926, 9259676, 26392416, 67084351, 36580610, 49236559, 64848072, 47893286, 4199704, 17976208, 9860195, 15948937, 17365188, 15163258, 34236719, 6525948, 14723410, 26734892, 32699744, 65338021, 6153406, 53393358, 19898053, 73392814, 22942635, 38009615, 24619760, 20867149, 70727211, 99861373, 12571310, 88130087, 76434144, 45990383, 3235882, 86301513, 42644903, 65275241, 92867155, 61728685, 55850790, 17016156, 569864, 4204661, 58180653, 30543215, 76671482, 42307449, 48260151, 26063929, 16019925, 66322959, 26507214, 36845587, 23110625, 75986488, 29188588, 29635537, 69786756, 63967300, 43322743, 42692881, 5073754, 74724075, 70367851, 2204165, 63152504, 70541760, 47090124, 99297164, 77787724, 16054533, 16424199, 7517032, 82651278, 20122224, 72732205, 37659250, 92692978, 18699206, 68204242, 29994197, 39201414, 31491938, 1431742, 78300864, 20002147, 73021291, 3294781, 11757872, 62762857, 74452589, 36780454, 33201905, 67513640, 45848907, 36396314, 1250437, 99224392, 44060493, 79821897, 6871053, 40152546, 97057187, 97641116, 4985896, 74743862, 68694897, 321665, 94911072, 99125126, 45049308, 16387575, 65038678, 79191827, 31904591, 61859581, 44842615, 69136837, 83651211, 45407418, 9058407, 22435353, 45790169, 93053405, 29516592, 13173644, 88698958, 17957593, 43357947, 22450468, 65271999, 66611759, 88653118, 44664587, 508198, 79922758, 6038457, 30694952, 20568363, 1936762, 68816413, 60106217, 42947632, 47792865, 14326617, 26397786, 57393458, 84406788, 70036438, 6497830, 11161731, 92398073, 34667286, 77272628, 34493392, 64098930, 98130363, 17857111, 20681921, 69605283, 81898046, 62490109, 68110330, 86460488, 3773993, 54263880, 1569515, 70004753, 15064655, 8729146, 76170907, 59197747, 48360198, 78589145, 9575954, 98739783, 31161687, 62803858, 29932657, 89217461, 37183543, 52293995, 8913721, 84293052, 4798568, 80555751, 35192533, 49597667, 77620120, 9188443, 18411915, 98371444, 96906410, 54868730, 59177718, 22766820, 47708850, 7646095, 79136082, 86118021, 71316369, 68644627, 71333116, 16971929, 52204879, 24915585, 30811010, 65047700, 87720882, 23194618, 40356877, 8733068, 67030811, 30218878, 20140249, 824872, 5822202, 34497327, 36930650, 38256849, 37166608, 68939068, 50567636, 45996863, 8128637, 17386996, 83155442, 76330843, 22129328, 14349098, 21397057, 7502255, 96318094, 37891451, 10597197, 54987042, 67124282, 56515456, 44627776, 36812683, 68824981, 44481640, 32161669, 23848565, 92803766, 13470059, 51588015, 20642888, 76540481, 4434662, 57241521, 33431960, 17539625, 75820087, 71965942, 90013093, 16445503, 78785507, 68102437, 75128745, 34698428, 6221471, 54199160, 3880712, 79657802, 66045587, 38494874, 8651647, 14093520, 95010552, 36468541, 42237907, 13862149, 63628376, 22997281, 68128438, 89996536, 59614746, 84166196, 54663246, 69848388, 1197320, 16595436, 21993752, 21070110, 53648154, 22879907, 80246713, 15902805, 12416768, 44177011, 61373987, 87598888, 40027975, 13348726, 30569392, 5640302, 10649306, 39801351, 95395112, 20765474, 38341669, 62051033, 75777973, 29959549, 1022677, 77312810, 33553959, 23134715, 43933006, 3233569, 17058722, 66271566, 85117092, 76703609, 51507425, 874791, 84904436, 84002370, 78909561, 37739481, 61815223, 73124510, 6793819, 92998591, 71522968, 18663507, 37620363, 56473732, 33699435, 17146629, 38022834, 74357852, 81774825, 68316156, 27665211, 33935899, 1204161, 86798033, 44479073, 50188404, 45919976, 95726235, 94711842, 12024238, 55247455, 83083131, 86242799, 4668450, 66319530, 99226875, 57658654, 77187825, 33565483, 15536795, 17081350, 46870723, 46540998, 40534591, 67793644, 168541, 86821229, 2717150, 8696647, 9886593, 91281584, 95251277, 43506672, 43501211, 74614639, 39553046, 15148031, 59109400, 60176618, 90158785, 99549373, 97940276, 26139110, 98462867, 21001913, 26998766, 61623915, 75153252, 53084256, 48395186, 8055981, 9623492, 68702632, 53842979, 30998561, 7229550, 91831487, 83150534, 61271144, 1788101, 10358899, 9829782, 36189527, 28734791, 20885148, 72495719, 97011160, 7919588, 89499542, 85695762, 30891921, 44844121, 79738755, 37224844, 98648327, 47887470, 39171895, 72793444, 42199455, 2331773, 48892201, 48658605, 112651, 23740167, 51426311, 95957797, 29834512, 27041967, 43376279, 88047921, 59910624, 90654836, 54232247, 69355476, 79806380, 65074535, 16684829, 29401781, 47298834, 99524975, 6819644, 61982238, 99333375, 26292919, 50702367, 40686254, 96709982, 52261574, 48774913, 82779622, 5832946, 16380211, 34432810, 73031054, 92353856, 19457589, 32274392, 93057697, 5267545, 19376156, 91240048, 90310261, 72725103, 16405341, 26102057, 85242963, 94516935, 30139692, 80251430, 78218845, 19891772, 69579137, 21533347, 35092039, 88251446, 62357986, 28796059, 4515343, 60430369, 57248122, 81805959, 26114953, 33628349, 85023028, 70782102, 18466635, 32250045, 27325428, 14626618, 61712234, 15015906, 99604946, 19486173, 52613508, 47213183, 43045786, 33123618, 86543538, 82052050, 34698463, 46851987, 61741594, 9175338, 51047803, 33249630, 66885828, 44245960, 18504197, 67281495, 2607799, 36135, 7687278, 56153345, 85571389, 1375023, 43152977, 20645197, 9603598, 62428472, 53632373, 57803235, 11365791, 9398733, 45306070, 44846932, 99917599, 24733232, 35512853, 66137019, 75543508, 3233084, 49328608, 82427263, 75052463, 55470718, 20486294, 64087743, 38061439, 89046466, 83789391, 31733363, 67031644, 77377183, 73168565, 77301523, 26275734, 1239555, 93562257, 85711894, 4119747, 91938191, 16567550, 54606384, 89078848, 32151165, 57020481, 81855445, 83133790, 59981773, 65454636, 70596786, 62936963, 7011964, 82532312, 8099994, 78549759, 41481685, 70800879, 80316608, 51590803, 81677380, 30463802, 49598724 +65338021, 25842078, 26392416, 17976208, 16097038, 84187166, 65851721, 68694897, 27185644, 93933709, 45381876, 89078848, 33304202, 9259676, 42237907, 63628376, 33061250, 87598888, 61263068, 51149804, 18504197, 99965001, 79806380, 24314885, 18806856, 70420215, 77072625, 46540998, 4985896, 82897371, 45049308, 93566986, 3509435, 19939935, 7104732, 73235980, 1936762, 91831487, 20885148, 34667286, 38008118, 78884452, 44844121, 75135448, 30090481, 26275734, 66271566, 54427233, 38365584, 85116755, 59177718, 18663507, 7011964, 33699435, 43376279, 27625735, 14363867, 51466049, 99226875, 15536795, 10366309, 19376156, 99549373, 75543508, 49598724, 88698958, 3233084, 49882705, 53084256, 91141395, 48395186, 54762643, 68702632, 54199160, 30998561, 82427263, 2208785, 79922758, 7229550, 81805959, 26397786, 36580610, 64848072, 70036438, 14626618, 24591705, 15902805, 12416768, 8729146, 12571310, 5482538, 95395112, 86543538, 82052050, 42307449, 44847298, 32590267, 92541302, 70367851, 37620363, 73109997, 51426311, 4508700, 99297164, 40781449, 30811010, 28851716, 69355476, 27665211, 92033260, 63015256, 56153345, 60955663, 4668450, 96193415, 62762857, 55669657, 36780454, 12664567, 26292919, 6986898, 17385531, 32151165, 247198, 99971982, 321665, 89637706, 88904910, 36812683, 43506672, 44842615, 90310261, 76825057, 45617087, 97561745, 63372756, 93270084, 8651647, 4978543, 90061527, 72495719, 22997281, 89996536, 64098930, 73392814, 69605283, 21993752, 40984766, 68110330, 1569515, 31733363, 65880522, 62552524, 44784505, 30569392, 66116458, 5640302, 92867155, 60581278, 17016156, 569864, 82979980, 3233569, 76671482, 85117092, 76703609, 51507425, 79880247, 62740044, 61815223, 83948335, 34295794, 69786756, 63967300, 74724075, 2204165, 37501808, 83747892, 71316369, 68644627, 38022834, 71333116, 27041967, 20122224, 4119747, 49271185, 1204161, 10264691, 16567550, 95726235, 12024238, 31491938, 43152977, 37435892, 14731700, 98948034, 9603598, 54606384, 31727379, 68939068, 50567636, 57803235, 17386996, 82726008, 76330843, 14349098, 40534591, 10453030, 67793644, 669105, 65017137, 45075471, 82339363, 32161669, 93057697, 83210802, 13470059, 35512853, 51588015, 9058407, 94516935, 97940276, 2891150, 33797252, 29516592, 57241521, 47361209, 81855445, 44348328, 98462867, 26863229, 21001913, 51715482, 22450468, 26998766, 78785507, 10961421, 62357986, 8055981, 9623492, 28787861, 66045587, 508198, 62115552, 78602717, 78549759, 20568363, 75052463, 66903004, 59981773, 42947632, 14326617, 83150534, 36933038, 91990218, 47893286, 6157724, 65454636, 53802686, 97011160, 94539824, 15015906, 20681921, 80246713, 86460488, 64087743, 54263880, 59197747, 64602895, 67031644, 98648327, 22108665, 9575954, 78561158, 7423788, 10649306, 42644903, 47887470, 29959549, 77312810, 94360702, 60102965, 72793444, 58180653, 30543215, 36685023, 11543098, 99729357, 80014588, 73617245, 2331773, 4798568, 72614359, 36845587, 62936963, 7955293, 19101477, 92692283, 9175338, 92998591, 43322743, 93562257, 51464002, 6808825, 56473732, 47090124, 17146629, 22113133, 59329511, 95957797, 52204879, 18131876, 78766358, 31126490, 16054533, 41481685, 36135, 7517032, 57359924, 90004325, 24915585, 72732205, 37659250, 92692978, 86798033, 65074535, 16684829, 9257405, 85571389, 73786944, 8733068, 60393039, 73021291, 61982238, 74452589, 24168411, 99333375, 56955985, 53632373, 22129328, 48774913, 21397057, 54058788, 59318837, 45995325, 40152546, 94076128, 34432810, 97641116, 44983451, 22721500, 2717150, 48893685, 69697787, 9398733, 56515456, 62693428, 91281584, 95251277, 18833224, 39553046, 92530431, 44481640, 5267545, 60176618, 23432750, 72725103, 17764950, 20642888, 4095116, 14947650, 33336148, 8373289, 33431960, 29510992, 21533347, 86001008, 17957593, 71965942, 35092039, 17068582, 76315420, 87160386, 28928175, 3487592, 45237957, 96735716, 57248122, 3183975, 81274566, 60106217, 89419466, 95290172, 13862149, 10358899, 9829782, 4199704, 36189527, 93515664, 51590803, 17365188, 68128438, 62430984, 32159704, 30764367, 16595436, 53393358, 85695762, 30891921, 21673260, 38061439, 79738755, 15064655, 19486173, 48360198, 61928316, 39801351, 75777973, 43933006, 7300047, 42199455, 34698463, 26063929, 874791, 65081429, 84002370, 78909561, 36505482, 30463802, 73124510, 16099750, 18411915, 92604458, 32426752, 12303248, 44245960, 71522968, 63152504, 67281495, 7066775, 4099191, 25636669, 77787724, 16971929, 55143724, 16424199, 82532312, 18699206, 91938191, 87720882, 56484900, 20645197, 64055960, 83083131, 6819644, 59501487, 5822202, 36930650, 79322415, 38256849, 87710366, 39986008, 45996863, 17081350, 99224392, 30771409, 71657078, 37891451, 94595668, 72278539, 6521313, 44550764, 74743862, 92353856, 56531125, 99125126, 9886593, 44627776, 526217, 43501211, 74614639, 54517921, 79191827, 99917599, 15148031, 23848565, 84349107, 59109400, 90158785, 4806458, 22405120, 48673079, 26102057, 91727510, 26139110, 59371804, 22435353, 4434662, 26493119, 13173644, 69579137, 58208470, 57961282, 83133790, 90013093, 8099994, 16445503, 93359396, 84840549, 34698428, 88653118, 58224549, 66250369, 28796059, 79657802, 42782652, 60430369, 55602660, 74137926, 98943869, 21919959, 70782102, 84406788, 51472275, 6497830, 11161731, 77272628, 81677380, 34493392, 70596786, 38658347, 73222868, 8807940, 3773993, 89046466, 83789391, 40027975, 24826575, 64157906, 77377183, 45990383, 86301513, 8129978, 68875490, 62051033, 62803858, 61728685, 33123618, 89217461, 6111563, 39171895, 55189057, 16019925, 46851987, 80555751, 61741594, 1239555, 37560164, 41380093, 23110625, 75986488, 39847321, 48658605, 51047803, 89214616, 54868730, 99690194, 42692881, 12348118, 29029316, 81172706, 71469330, 70541760, 23740167, 39373729, 29466406, 2607799, 88047921, 7182642, 49641577, 68316156, 74110882, 44479073, 23194618, 72373496, 29994197, 39201414, 99524975, 1431742, 67474219, 30218878, 824872, 34497327, 37166608, 33201905, 83269727, 62428472, 11581181, 33565483, 67513640, 84127901, 1250437, 99515901, 4064751, 16380211, 10597197, 97057187, 53888755, 8696647, 2917920, 62452034, 29065964, 16387575, 77300457, 71083565, 32274392, 68824981, 61859581, 69136837, 16405341, 76540481, 70800879, 85242963, 80251430, 19891772, 92215320, 75820087, 17894977, 43357947, 72019362, 88251446, 66667729, 36312813, 53842979, 30694952, 33628349, 68816413, 61271144, 57393458, 30366150, 22200378, 48675329, 37957788, 9860195, 59405277, 92398073, 15948937, 32250045, 6525948, 14723410, 98130363, 1197320, 67227442, 3633375, 45794415, 55770687, 35456853, 82178706, 81898046, 55753905, 13348726, 47213183, 92071990, 69255765, 63059024, 55850790, 29932657, 48088883, 4204661, 8913721, 13468268, 66322959, 30653863, 48892201, 415901, 6793819, 57163802, 98371444, 96906410, 70372191, 22766820, 66885828, 5073754, 77694700, 27187213, 44889423, 56461322, 41442762, 58751351, 74357852, 14045383, 97379790, 32058615, 81774825, 82651278, 90654836, 76960303, 29401781, 72238278, 86242799, 40781854, 66319530, 62496012, 66663942, 11757872, 97783876, 77187825, 26744917, 37192445, 96709982, 52261574, 47738185, 44060493, 34946859, 79821897, 61897582, 54987042, 91255408, 14220886, 94911072, 44846932, 19457589, 83368048, 26664538, 36139942, 91240048, 92803766, 92787493, 41092172, 83533741, 66137019, 93053405, 68041839, 30139692, 69641513, 84684495, 72274002, 68102437, 99658235, 61623915, 75153252, 44664587, 3880712, 26776922, 91802888, 23569917, 55470718, 75229462, 95010552, 1788101, 37280276, 749283, 28734791, 10309525, 27325428, 59614746, 84166196, 69848388, 7919588, 61712234, 32699744, 6153406, 20486294, 53648154, 22879907, 62232211, 62490109, 30395570, 30787683, 20867149, 99861373, 88130087, 68000591, 3235882, 43045786, 65275241, 38341669, 31161687, 73168565, 82886381, 1022677, 42967683, 17058722, 37739481, 35192533, 91957544, 49597667, 15535065, 29635537, 9188443, 7685448, 112651, 47708850, 36808486, 86118021, 50007421, 33237508, 29834512, 91664334, 59910624, 77898274, 61859143, 52060076, 33935899, 66428795, 4069912, 99021067, 67030811, 77284619, 3294781, 20140249, 45848907, 57240218, 40686254, 83155442, 46870723, 17727650, 5832946, 7502255, 73031054, 66832478, 86821229, 90090964, 67124282, 72358170, 65038678, 61960400, 82327024, 9599614, 40677414, 38645117, 83651211, 4787945, 45407418, 45790169, 97281847, 18001617, 44473167, 78218845, 17539625, 90457870, 40865610, 75128745, 49328608, 6038457, 26114953, 36468541, 54014062, 44915235, 67451935, 15075176, 18466635, 54663246, 17857111, 20836893, 81853704, 89499542, 99604946, 24619760, 61373987, 70727211, 76170907, 78589145, 52613508, 75407347, 13819358, 92554120, 20765474, 37675718, 37183543, 89804152, 98653983, 55615885, 26507214, 77620120, 72738685, 45428665, 71300104, 33249630, 7646095, 79136082, 18783702, 8791066, 85711894, 7687278, 65047700, 50188404, 50668599, 72777973, 45919976, 83302115, 94711842, 55247455, 74441448, 78300864, 20002147, 57658654, 41092102, 8128637, 77413012, 50702367, 82779622, 96318094, 6871053, 30163921, 11365791, 5111370, 57020481, 36753250, 8115266, 79942022, 35996293, 45667668, 13231279, 11923835, 3088684, 33895336, 51315460, 38494874, 9860968, 14093520, 47792865, 88444207, 67084351, 26734892, 53547802, 76434144, 98739783, 21289531, 40197395, 52293995, 34044787, 4091162, 19272365, 47298834, 1375023, 24953498, 72357096, 86022504, 17037369, 168541, 50806615, 61141874, 27411561, 80316608, 90272749, 96852321, 38510840, 28550822, 58749, 66611759, 95581843, 4515343, 96420429, 85023028, 49236559, 34236719, 24058273, 21070110, 22942635, 44177011, 70004753, 176257, 37224844, 77301523, 33553959, 53666583, 48260151, 63506281, 84293052, 84904436, 29188588, 42073124, 41245325, 55960386, 89811711, 5970607, 54232247, 71920426, 89699445, 36396314, 45306070, 31904591, 24733232, 8634541, 94090109, 65271999, 6221471, 15163258, 65715134, 19898053, 38009615, 93790285, 56424103, 29819940, 23134715, 6505939, 40356877, 96726697, 68204242 +21993752, 82779622, 41092172, 75153252, 39801351, 80014588, 27041967, 74110882, 91281584, 28796059, 17976208, 20885148, 45990383, 75407347, 92867155, 17058722, 37560164, 37192445, 57803235, 14349098, 97057187, 45049308, 71083565, 26139110, 97561745, 25842078, 93270084, 53842979, 47893286, 77272628, 24591705, 54263880, 37224844, 29932657, 89804152, 52293995, 84293052, 59177718, 12348118, 44245960, 47708850, 70367851, 50007421, 16971929, 96726697, 61859143, 79806380, 24314885, 87720882, 23194618, 85571389, 33201905, 15536795, 36396314, 40534591, 54058788, 50806615, 61897582, 86821229, 8696647, 9398733, 32161669, 20642888, 22435353, 96852321, 3233084, 35092039, 68102437, 7104732, 3880712, 2208785, 78549759, 75052463, 60106217, 47792865, 61271144, 97011160, 94539824, 32159704, 6153406, 38658347, 73222868, 3773993, 93790285, 64602895, 22108665, 38341669, 86543538, 94360702, 8913721, 48260151, 84002370, 61815223, 38365584, 98371444, 42692881, 56473732, 71316369, 16054533, 32058615, 7517032, 65047700, 94711842, 67474219, 30218878, 6986898, 50702367, 99224392, 21397057, 34432810, 67793644, 97641116, 11365791, 62693428, 16387575, 74614639, 72725103, 4095116, 94516935, 91727510, 19891772, 19939935, 83133790, 51715482, 84840549, 10961421, 28787861, 26114953, 30694952, 36189527, 9860195, 10309525, 15948937, 81677380, 38008118, 26734892, 21070110, 61373987, 19486173, 44784505, 69255765, 62051033, 60581278, 16019925, 46851987, 62936963, 48892201, 7685448, 73109997, 63152504, 79136082, 6808825, 86118021, 56461322, 39373729, 68644627, 24915585, 28851716, 82532312, 33935899, 50188404, 99021067, 68204242, 72373496, 45919976, 20645197, 1431742, 67030811, 62496012, 96193415, 24168411, 16380211, 53888755, 22721500, 45075471, 31904591, 68824981, 36139942, 69136837, 83210802, 90310261, 60176618, 83533741, 75543508, 45790169, 4434662, 29516592, 57241521, 13173644, 17539625, 21533347, 98462867, 71965942, 28928175, 62357986, 79922758, 23569917, 33628349, 42947632, 6497830, 7919588, 20836893, 45794415, 85695762, 40984766, 64087743, 30395570, 44177011, 30787683, 64157906, 67031644, 68875490, 42644903, 73168565, 82979980, 43933006, 36685023, 11543098, 76703609, 13468268, 66322959, 874791, 4798568, 78909561, 91957544, 77620120, 34295794, 15535065, 72738685, 45428665, 39847321, 69786756, 27187213, 71522968, 51464002, 23740167, 7066775, 58751351, 33237508, 88047921, 30811010, 27665211, 7687278, 65074535, 29401781, 40356877, 74441448, 77284619, 73021291, 37166608, 62428472, 68939068, 26744917, 77413012, 17081350, 29819940, 5832946, 34946859, 6871053, 72278539, 14220886, 90090964, 321665, 72358170, 61141874, 36812683, 36753250, 65038678, 82339363, 15148031, 82327024, 84349107, 92803766, 40677414, 97940276, 97281847, 8373289, 80251430, 44348328, 84684495, 17068582, 93359396, 72019362, 26998766, 61623915, 11923835, 68702632, 54199160, 30998561, 42782652, 60430369, 7229550, 78602717, 81805959, 9860968, 14093520, 70782102, 36580610, 37280276, 65454636, 27325428, 90061527, 51590803, 72495719, 84166196, 64098930, 69848388, 61712234, 20681921, 22942635, 99604946, 33061250, 176257, 83789391, 99861373, 76170907, 88130087, 13348726, 98648327, 77377183, 62552524, 52613508, 78561158, 30569392, 8129978, 13819358, 92554120, 55850790, 33123618, 21289531, 77312810, 39171895, 3233569, 66271566, 34698463, 73617245, 2331773, 26507214, 62740044, 80555751, 37739481, 30463802, 61741594, 92541302, 54868730, 42073124, 92998591, 33249630, 22766820, 77694700, 18663507, 41245325, 70541760, 89811711, 95957797, 52204879, 59910624, 77898274, 82651278, 40781449, 69355476, 66428795, 63015256, 56153345, 31491938, 56484900, 55247455, 6819644, 20140249, 66663942, 61982238, 11757872, 79322415, 74452589, 55669657, 54606384, 45848907, 26292919, 96709982, 76330843, 22129328, 44060493, 71657078, 79821897, 37891451, 99971982, 73031054, 30163921, 54987042, 6521313, 92353856, 48893685, 2917920, 45306070, 29065964, 95251277, 18833224, 43506672, 93566986, 61859581, 44842615, 23848565, 35512853, 92787493, 76540481, 26102057, 79942022, 68041839, 33431960, 47361209, 17957593, 27185644, 43357947, 76315420, 78785507, 49882705, 75128745, 65271999, 95581843, 26776922, 49328608, 508198, 96420429, 81274566, 89419466, 88444207, 26397786, 91990218, 54014062, 13862149, 10358899, 9829782, 15075176, 32250045, 14626618, 15163258, 89996536, 30764367, 59614746, 34236719, 16595436, 24058273, 89499542, 20486294, 53648154, 12416768, 86460488, 89046466, 1569515, 70727211, 40027975, 15064655, 75135448, 55753905, 76434144, 98739783, 20765474, 65275241, 31161687, 77301523, 23134715, 60102965, 53666583, 26275734, 98653983, 42307449, 99729357, 79880247, 36845587, 7955293, 1239555, 49597667, 23110625, 75986488, 6793819, 16099750, 89214616, 43322743, 81172706, 7646095, 93562257, 6505939, 67281495, 33699435, 99297164, 71333116, 97379790, 16424199, 36135, 18699206, 49271185, 1204161, 4069912, 44479073, 50668599, 29994197, 16567550, 8733068, 1375023, 37435892, 64055960, 83083131, 86242799, 98948034, 72357096, 57658654, 62762857, 11581181, 67513640, 8128637, 53632373, 17386996, 99515901, 17727650, 48774913, 10453030, 45995325, 94595668, 65851721, 168541, 66832478, 91255408, 44550764, 74743862, 69697787, 68694897, 5111370, 62452034, 19457589, 57020481, 526217, 61960400, 24733232, 93057697, 19376156, 91240048, 90158785, 4787945, 16405341, 48673079, 14947650, 66137019, 80316608, 49598724, 90272749, 8634541, 29510992, 81855445, 75820087, 26863229, 33304202, 90013093, 8099994, 16445503, 87160386, 3487592, 58749, 6221471, 88653118, 73235980, 9623492, 45237957, 3088684, 66250369, 91802888, 3183975, 51315460, 59981773, 14326617, 1788101, 49236559, 64848072, 28734791, 6157724, 11161731, 53802686, 34667286, 17365188, 22997281, 68128438, 54663246, 3633375, 53393358, 22879907, 44844121, 87598888, 8729146, 9575954, 3235882, 66116458, 7423788, 5640302, 95395112, 75777973, 61728685, 1022677, 37183543, 7300047, 4204661, 42199455, 40197395, 63506281, 51507425, 84904436, 35192533, 415901, 85116755, 48658605, 92604458, 96906410, 63967300, 66885828, 51426311, 99965001, 18783702, 47090124, 25636669, 8791066, 59329511, 41481685, 52060076, 57359924, 72732205, 37659250, 92033260, 14363867, 18806856, 12024238, 47298834, 99524975, 43152977, 24953498, 20002147, 59501487, 70420215, 36930650, 87710366, 36780454, 31727379, 39986008, 12664567, 83155442, 52261574, 46870723, 4064751, 7502255, 46540998, 32151165, 10597197, 44983451, 67124282, 56531125, 94911072, 65017137, 9886593, 39553046, 83368048, 44481640, 26664538, 59109400, 23432750, 83651211, 99549373, 4806458, 85242963, 27411561, 35996293, 59371804, 33797252, 33336148, 45667668, 45617087, 18001617, 30139692, 44473167, 69579137, 58208470, 88698958, 86001008, 21001913, 72274002, 40865610, 53084256, 91141395, 54762643, 8055981, 66667729, 79657802, 57248122, 62115552, 9259676, 20568363, 38494874, 8651647, 98943869, 1936762, 91831487, 95010552, 36468541, 22200378, 37957788, 92398073, 34493392, 6525948, 17857111, 81853704, 65338021, 81898046, 30891921, 15902805, 68110330, 31733363, 79738755, 12571310, 59197747, 48360198, 61928316, 47213183, 43045786, 10649306, 62803858, 29959549, 61263068, 569864, 42967683, 6111563, 48088883, 55189057, 85117092, 26063929, 65081429, 44847298, 19101477, 32590267, 73124510, 41380093, 92692283, 71300104, 5073754, 44889423, 71469330, 37620363, 36808486, 7011964, 5970607, 38022834, 29466406, 43376279, 91664334, 85711894, 14045383, 68316156, 90004325, 20122224, 90654836, 4119747, 91938191, 86798033, 72238278, 9257405, 10264691, 95726235, 51466049, 60393039, 60955663, 824872, 56424103, 34497327, 9603598, 99333375, 17037369, 50567636, 40686254, 17385531, 1250437, 82726008, 2717150, 82897371, 56515456, 44846932, 88904910, 44627776, 54517921, 79191827, 92530431, 10366309, 38645117, 76825057, 13470059, 22405120, 9058407, 70800879, 2891150, 94090109, 3509435, 38510840, 17894977, 88251446, 99658235, 28550822, 66611759, 36312813, 33895336, 55602660, 74137926, 66903004, 85023028, 68816413, 75229462, 83150534, 21919959, 26392416, 42237907, 84406788, 63628376, 749283, 51472275, 48675329, 4199704, 44915235, 59405277, 18466635, 62430984, 1197320, 67227442, 32699744, 53547802, 70596786, 73392814, 69605283, 35456853, 62232211, 62490109, 38061439, 24619760, 5482538, 78589145, 68000591, 82886381, 47887470, 17016156, 37675718, 30543215, 54427233, 55615885, 30653863, 36505482, 83948335, 29188588, 57163802, 18411915, 99690194, 70372191, 12303248, 74724075, 29029316, 18504197, 4099191, 17146629, 77787724, 29834512, 2607799, 74357852, 7182642, 55143724, 54232247, 92692978, 71920426, 76960303, 16684829, 72777973, 73786944, 40781854, 14731700, 66319530, 3294781, 97783876, 77187825, 86022504, 89699445, 33565483, 45381876, 47738185, 59318837, 40152546, 247198, 669105, 4985896, 32274392, 9599614, 51588015, 26493119, 78218845, 13231279, 57961282, 34698428, 82427263, 55470718, 95290172, 67084351, 57393458, 30366150, 65715134, 19898053, 21673260, 80246713, 8807940, 20867149, 86301513, 63059024, 33553959, 72793444, 82052050, 29635537, 9175338, 9188443, 37501808, 4508700, 34044787, 22113133, 18131876, 31126490, 4091162, 19272365, 39201414, 78300864, 41092102, 83269727, 84127901, 96318094, 94076128, 99125126, 43501211, 99917599, 69641513, 90457870, 63372756, 48395186, 44664587, 66045587, 6038457, 36933038, 70036438, 93515664, 4978543, 14723410, 82178706, 38009615, 24826575, 65880522, 30090481, 89217461, 93933709, 32426752, 112651, 55960386, 41442762, 49641577, 81774825, 83302115, 77072625, 38256849, 84187166, 89078848, 30771409, 77300457, 17764950, 92215320, 96735716, 4515343, 67451935, 98130363, 15015906, 55770687, 16097038, 58180653, 76671482, 51149804, 51047803, 4668450, 5822202, 99226875, 93053405, 58224549, 70004753, 92071990, 27625735, 57240218, 89637706, 5267545, 8115266, 78884452, 72614359, 83747892, 78766358, 56955985, 45996863, 45407418, 22450468, 2204165 +88130087, 65851721, 61741594, 1239555, 4119747, 94516935, 7104732, 9860195, 93790285, 43045786, 29635537, 168541, 321665, 43506672, 24733232, 97940276, 80251430, 82427263, 33628349, 65454636, 7919588, 89046466, 6111563, 43322743, 93562257, 67281495, 85711894, 52060076, 85571389, 36930650, 89078848, 17081350, 34946859, 50806615, 91255408, 77300457, 79191827, 93566986, 48673079, 70800879, 30139692, 58208470, 21001913, 63372756, 78549759, 6157724, 20885148, 20681921, 38061439, 48360198, 44784505, 43933006, 55189057, 37739481, 54868730, 99690194, 47708850, 56473732, 30811010, 29401781, 62762857, 56955985, 17727650, 7502255, 46540998, 40534591, 94595668, 11365791, 45306070, 56531125, 89637706, 9886593, 91281584, 45049308, 26664538, 97561745, 86001008, 93359396, 88251446, 11923835, 45237957, 53842979, 42782652, 57248122, 68816413, 26392416, 28734791, 11161731, 92398073, 17365188, 84166196, 19898053, 69605283, 35456853, 53648154, 62490109, 38009615, 24826575, 65880522, 64157906, 67031644, 3235882, 98739783, 39801351, 55850790, 1022677, 34698463, 48260151, 80014588, 84904436, 91957544, 41380093, 89214616, 96906410, 44245960, 18663507, 89811711, 68644627, 38022834, 92033260, 63015256, 14363867, 16684829, 45919976, 1375023, 37435892, 45996863, 57240218, 10453030, 53888755, 74743862, 8696647, 67124282, 5267545, 84349107, 91240048, 90310261, 76825057, 72725103, 16405341, 26139110, 97281847, 35092039, 68102437, 95581843, 79657802, 75052463, 14326617, 1788101, 64848072, 70036438, 4199704, 97011160, 81677380, 22997281, 61712234, 65715134, 89499542, 21673260, 3773993, 70004753, 15064655, 30090481, 98648327, 30569392, 62051033, 75777973, 60581278, 82886381, 47887470, 33553959, 86543538, 89804152, 30543215, 51507425, 874791, 2331773, 80555751, 19101477, 38365584, 75986488, 69786756, 112651, 66885828, 51464002, 99965001, 4099191, 56461322, 8791066, 27041967, 41481685, 36135, 68316156, 82651278, 90004325, 28851716, 74110882, 24314885, 18699206, 72777973, 29994197, 16567550, 94711842, 1431742, 30218878, 59501487, 99226875, 96193415, 33201905, 26292919, 57803235, 59318837, 66832478, 669105, 4985896, 9398733, 99125126, 61960400, 54517921, 92530431, 93057697, 83210802, 40677414, 13470059, 80316608, 68041839, 26493119, 33431960, 3509435, 3233084, 78785507, 65271999, 54762643, 73235980, 3088684, 66667729, 66045587, 55602660, 26114953, 23569917, 38494874, 14093520, 21919959, 88444207, 37280276, 9829782, 749283, 44915235, 37957788, 34667286, 27325428, 54663246, 24591705, 15015906, 70596786, 44844121, 33061250, 24619760, 83789391, 99861373, 40027975, 8729146, 55753905, 22108665, 45990383, 52613508, 92554120, 65275241, 92867155, 77301523, 569864, 89217461, 98653983, 42199455, 82052050, 36685023, 54427233, 55615885, 65081429, 44847298, 61815223, 48892201, 73124510, 77620120, 34295794, 9175338, 18411915, 7685448, 81172706, 2204165, 36808486, 18783702, 33699435, 18131876, 59910624, 16424199, 81774825, 61859143, 57359924, 20122224, 90654836, 86798033, 65074535, 60955663, 14731700, 73021291, 62428472, 33565483, 17037369, 15536795, 36396314, 53632373, 40686254, 83155442, 96709982, 48774913, 97641116, 61897582, 82897371, 48893685, 62693428, 44846932, 88904910, 61141874, 44627776, 43501211, 74614639, 15148031, 9599614, 23848565, 59109400, 8115266, 90158785, 75543508, 44473167, 13231279, 57961282, 75820087, 44348328, 83133790, 72274002, 72019362, 84840549, 28928175, 40865610, 10961421, 66611759, 48395186, 62357986, 8055981, 66250369, 68702632, 96735716, 508198, 2208785, 98943869, 9860968, 55470718, 95010552, 61271144, 36933038, 67084351, 42237907, 57393458, 54014062, 13862149, 84406788, 49236559, 93515664, 15075176, 38008118, 64098930, 69848388, 16595436, 55770687, 21070110, 20486294, 80246713, 15902805, 40984766, 30787683, 1569515, 20867149, 76170907, 64602895, 76434144, 77377183, 8129978, 7423788, 13819358, 38341669, 29959549, 77312810, 82979980, 93933709, 16097038, 94360702, 48088883, 4204661, 58180653, 76671482, 66271566, 42307449, 76703609, 99729357, 26063929, 73617245, 30653863, 62936963, 39847321, 98371444, 70372191, 5073754, 29029316, 70367851, 7646095, 37620363, 41245325, 50007421, 34044787, 22113133, 59329511, 5970607, 29466406, 52204879, 43376279, 78766358, 7182642, 16054533, 77898274, 37659250, 79806380, 1204161, 65047700, 66428795, 50188404, 39201414, 73786944, 83302115, 60393039, 99524975, 55247455, 64055960, 66319530, 67474219, 98948034, 824872, 56424103, 79322415, 41092102, 54606384, 89699445, 31727379, 67513640, 37192445, 8128637, 84127901, 17385531, 14349098, 82779622, 21397057, 29819940, 30771409, 32151165, 45995325, 94076128, 34432810, 22721500, 2717150, 2917920, 5111370, 19457589, 95251277, 65038678, 71083565, 32161669, 92803766, 23432750, 4787945, 35512853, 4806458, 22405120, 9058407, 26102057, 59371804, 45790169, 33797252, 45667668, 18001617, 90272749, 19891772, 17539625, 81855445, 21533347, 98462867, 25842078, 17957593, 27185644, 33304202, 17894977, 43357947, 22450468, 99658235, 91141395, 6221471, 9623492, 36312813, 3880712, 3183975, 30694952, 8651647, 60106217, 47792865, 30366150, 63628376, 48675329, 53802686, 51590803, 15163258, 1197320, 20836893, 62232211, 81898046, 44177011, 61373987, 37224844, 19486173, 78561158, 86301513, 63059024, 73168565, 29932657, 33123618, 17016156, 60102965, 3233569, 53666583, 8913721, 63506281, 66322959, 79880247, 84293052, 26507214, 62740044, 78909561, 92692283, 23110625, 15535065, 85116755, 9188443, 16099750, 48658605, 51047803, 74724075, 63152504, 79136082, 18504197, 55960386, 86118021, 4508700, 99297164, 41442762, 58751351, 95957797, 2607799, 96726697, 14045383, 7517032, 4091162, 24915585, 69355476, 12024238, 56484900, 24953498, 78300864, 86242799, 4668450, 67030811, 77284619, 20140249, 66663942, 61982238, 9603598, 11757872, 97783876, 55669657, 24168411, 36780454, 83269727, 45848907, 12664567, 77413012, 76330843, 47738185, 5832946, 96318094, 40152546, 247198, 73031054, 86821229, 44983451, 90090964, 44550764, 56515456, 62452034, 65017137, 18833224, 32274392, 45075471, 44481640, 44842615, 10366309, 69136837, 83651211, 92787493, 83533741, 27411561, 66137019, 93053405, 49598724, 33336148, 29516592, 8373289, 8634541, 94090109, 13173644, 92215320, 69641513, 71965942, 76315420, 51715482, 87160386, 28550822, 61623915, 58224549, 44664587, 54199160, 4515343, 7229550, 51315460, 1936762, 66903004, 91831487, 85023028, 81274566, 75229462, 36468541, 22200378, 51472275, 36189527, 67451935, 4978543, 17976208, 59405277, 15948937, 34493392, 94539824, 34236719, 6525948, 98130363, 26734892, 67227442, 32699744, 24058273, 65338021, 6153406, 45794415, 73392814, 78884452, 21993752, 22879907, 22942635, 12416768, 86460488, 64087743, 176257, 31733363, 13348726, 61928316, 95395112, 62803858, 61263068, 37183543, 72793444, 51149804, 84002370, 72614359, 36505482, 30463802, 415901, 49597667, 92541302, 29188588, 71300104, 59177718, 42073124, 63967300, 22766820, 27187213, 12348118, 51426311, 39373729, 33237508, 29834512, 16971929, 88047921, 55143724, 97379790, 27665211, 4069912, 44479073, 95726235, 51466049, 8733068, 47298834, 62496012, 5822202, 34497327, 38256849, 26744917, 45381876, 52261574, 82726008, 4064751, 44060493, 71657078, 79821897, 30163921, 72278539, 92353856, 68694897, 72358170, 526217, 83368048, 31904591, 82339363, 68824981, 36139942, 38645117, 60176618, 51588015, 17764950, 20642888, 4095116, 76540481, 85242963, 79942022, 35996293, 2891150, 22435353, 57241521, 29510992, 47361209, 84684495, 88698958, 96852321, 17068582, 26998766, 49882705, 34698428, 88653118, 28787861, 28796059, 30998561, 60430369, 91802888, 6038457, 78602717, 81805959, 89419466, 95290172, 70782102, 26397786, 36580610, 91990218, 10309525, 18466635, 72495719, 77272628, 68128438, 32159704, 30764367, 59614746, 81853704, 3633375, 53393358, 85695762, 38658347, 73222868, 99604946, 54263880, 78589145, 62552524, 66116458, 68875490, 37675718, 23134715, 39171895, 26275734, 17058722, 11543098, 13468268, 35192533, 83948335, 72738685, 45428665, 92604458, 92998591, 33249630, 42692881, 71522968, 6808825, 7066775, 17146629, 71333116, 31126490, 32058615, 27625735, 71920426, 82532312, 91938191, 76960303, 50668599, 9257405, 72373496, 10264691, 18806856, 31491938, 20645197, 74441448, 6819644, 3294781, 72357096, 57658654, 74452589, 86022504, 11581181, 99333375, 84187166, 17386996, 1250437, 46870723, 99224392, 37891451, 97057187, 6521313, 69697787, 16387575, 99917599, 82327024, 61859581, 41092172, 14947650, 91727510, 4434662, 45617087, 26863229, 19939935, 8099994, 90457870, 3487592, 58749, 33895336, 26776922, 49328608, 79922758, 20568363, 10358899, 14626618, 62430984, 17857111, 8807940, 30395570, 79738755, 75135448, 59197747, 5482538, 47213183, 92071990, 69255765, 5640302, 20765474, 42967683, 40197395, 52293995, 16019925, 46851987, 4798568, 37560164, 32426752, 44889423, 6505939, 23740167, 7011964, 71316369, 74357852, 49641577, 40781449, 54232247, 49271185, 99021067, 87720882, 68204242, 23194618, 72238278, 40356877, 77072625, 77187825, 87710366, 39986008, 6986898, 50702367, 54058788, 16380211, 10597197, 99971982, 14220886, 94911072, 36753250, 39553046, 19376156, 99549373, 38510840, 90013093, 75153252, 93270084, 96420429, 9259676, 59981773, 42947632, 47893286, 6497830, 53547802, 82178706, 30891921, 70727211, 12571310, 9575954, 10649306, 42644903, 31161687, 21289531, 7300047, 85117092, 36845587, 32590267, 37501808, 83747892, 70541760, 47090124, 25636669, 92692978, 33935899, 7687278, 19272365, 56153345, 43152977, 20002147, 68939068, 22129328, 54987042, 29065964, 57020481, 78218845, 62115552, 32250045, 90061527, 89996536, 14723410, 68110330, 87598888, 68000591, 75407347, 57163802, 77694700, 71469330, 73109997, 83083131, 40781854, 70420215, 99515901, 6871053, 36812683, 69579137, 16445503, 53084256, 75128745, 74137926, 61728685, 7955293, 12303248, 77787724, 72732205, 37166608, 50567636, 67793644, 45407418, 83150534, 6793819, 91664334 +19939935, 67513640, 97561745, 73235980, 43933006, 39171895, 97783876, 45075471, 78549759, 15948937, 39801351, 38341669, 92541302, 56461322, 29819940, 7502255, 669105, 11365791, 19457589, 43506672, 91240048, 13470059, 4806458, 17539625, 25842078, 8099994, 30998561, 78602717, 1788101, 36189527, 69605283, 85695762, 33061250, 3773993, 99861373, 8729146, 69255765, 65275241, 82886381, 77312810, 40197395, 79880247, 84293052, 26507214, 91957544, 67281495, 18783702, 8791066, 16971929, 97379790, 7517032, 64055960, 40781854, 37192445, 40686254, 17081350, 99224392, 45995325, 69697787, 91727510, 19891772, 51715482, 99658235, 55602660, 13862149, 4978543, 7919588, 64602895, 45990383, 29932657, 4798568, 36845587, 7955293, 44889423, 51464002, 50007421, 25636669, 71316369, 59910624, 16424199, 49641577, 77898274, 30811010, 28851716, 76960303, 87720882, 85571389, 77284619, 98948034, 3294781, 17037369, 57240218, 17386996, 99515901, 46540998, 94076128, 30163921, 61897582, 82897371, 61960400, 44842615, 23848565, 83210802, 22405120, 33336148, 66250369, 28787861, 74137926, 23569917, 98943869, 85023028, 68816413, 26392416, 49236559, 37280276, 72495719, 89996536, 26734892, 20486294, 8807940, 59197747, 65880522, 78589145, 95395112, 21289531, 58180653, 13468268, 36505482, 35192533, 92692283, 77620120, 72738685, 43322743, 81172706, 71469330, 93562257, 6505939, 17146629, 99297164, 38022834, 2607799, 37659250, 82532312, 18699206, 1204161, 65074535, 4069912, 8733068, 37435892, 60955663, 4668450, 72357096, 62428472, 56955985, 26292919, 57803235, 76330843, 59318837, 54987042, 8696647, 68694897, 62693428, 9886593, 16387575, 71083565, 44481640, 15148031, 23432750, 99549373, 49598724, 57241521, 94090109, 98462867, 96852321, 38510840, 17894977, 16445503, 68102437, 28928175, 11923835, 91141395, 42782652, 60430369, 1936762, 66903004, 21919959, 91990218, 84406788, 70036438, 93515664, 9860195, 92398073, 97011160, 62430984, 30764367, 6153406, 45794415, 19898053, 22942635, 64087743, 99604946, 54263880, 61373987, 20867149, 87598888, 5482538, 88130087, 13348726, 22108665, 52613508, 30569392, 98739783, 62051033, 75777973, 61728685, 94360702, 48088883, 26275734, 51149804, 85117092, 76703609, 874791, 55615885, 62740044, 48892201, 38365584, 32590267, 73124510, 41380093, 85116755, 18411915, 70372191, 69786756, 27187213, 29029316, 47708850, 71522968, 55960386, 99965001, 4099191, 39373729, 68644627, 43376279, 96726697, 31126490, 52060076, 65047700, 44479073, 72777973, 43152977, 6819644, 99226875, 34497327, 36930650, 5832946, 40152546, 34432810, 66832478, 53888755, 2717150, 321665, 65017137, 29065964, 36753250, 54517921, 32274392, 10366309, 69136837, 40677414, 4787945, 35512853, 17764950, 48673079, 76540481, 27411561, 66137019, 26139110, 22435353, 97281847, 30139692, 8373289, 33431960, 80251430, 81855445, 21533347, 92215320, 17068582, 76315420, 72019362, 78785507, 88251446, 75128745, 63372756, 8055981, 45237957, 93270084, 81805959, 30694952, 51315460, 20568363, 81274566, 75229462, 95290172, 36468541, 70782102, 26397786, 42237907, 36580610, 4199704, 67451935, 65454636, 32250045, 90061527, 77272628, 81677380, 59614746, 64098930, 16595436, 65338021, 73392814, 53648154, 81898046, 1569515, 83789391, 76434144, 67031644, 77377183, 62552524, 47213183, 78561158, 92071990, 42644903, 62803858, 17016156, 29959549, 33553959, 82979980, 23134715, 6111563, 17058722, 30543215, 36685023, 11543098, 99729357, 54427233, 84002370, 78909561, 61815223, 29188588, 6793819, 16099750, 59177718, 42073124, 112651, 12303248, 66885828, 12348118, 2204165, 37620363, 37501808, 36808486, 79136082, 56473732, 41442762, 33237508, 29834512, 16054533, 68316156, 20122224, 71920426, 79806380, 49271185, 56153345, 99021067, 72238278, 29994197, 95726235, 51466049, 12024238, 60393039, 1375023, 99524975, 1431742, 83083131, 86242799, 20002147, 66319530, 73021291, 55669657, 99333375, 45848907, 8128637, 50702367, 83155442, 22129328, 46870723, 17727650, 21397057, 54058788, 79821897, 99971982, 73031054, 97641116, 86821229, 72278539, 90090964, 44550764, 74743862, 94911072, 62452034, 44846932, 44627776, 57020481, 45049308, 526217, 65038678, 74614639, 92530431, 93566986, 68824981, 26664538, 32161669, 19376156, 84349107, 8115266, 90158785, 9058407, 26102057, 85242963, 79942022, 14947650, 4434662, 26493119, 75820087, 69641513, 84684495, 26863229, 71965942, 72274002, 93359396, 87160386, 61623915, 75153252, 34698428, 54762643, 28796059, 66045587, 82427263, 2208785, 89419466, 36933038, 63628376, 64848072, 9829782, 47893286, 28734791, 37957788, 59405277, 22997281, 34493392, 94539824, 67227442, 53547802, 70596786, 78884452, 22879907, 44844121, 80246713, 38061439, 31733363, 93790285, 37224844, 15064655, 12571310, 19486173, 24826575, 30090481, 61928316, 75407347, 7423788, 5640302, 68875490, 10649306, 92554120, 20765474, 31161687, 92867155, 77301523, 37675718, 569864, 60102965, 3233569, 98653983, 42199455, 55189057, 8913721, 34698463, 48260151, 16019925, 73617245, 65081429, 72614359, 44847298, 80555751, 37739481, 19101477, 415901, 34295794, 98371444, 32426752, 22766820, 42692881, 7646095, 73109997, 18504197, 41245325, 7066775, 71333116, 27041967, 52204879, 91664334, 85711894, 14045383, 41481685, 81774825, 40781449, 27625735, 4119747, 54232247, 27665211, 86798033, 23194618, 29401781, 39201414, 73786944, 40356877, 10264691, 47298834, 24953498, 55247455, 67474219, 62496012, 59501487, 79322415, 38256849, 41092102, 54606384, 24168411, 36780454, 31727379, 26744917, 45996863, 89078848, 36396314, 82726008, 14349098, 97057187, 168541, 44983451, 4985896, 6521313, 9398733, 56515456, 89637706, 39553046, 99917599, 24733232, 9599614, 36139942, 90310261, 60176618, 51588015, 4095116, 35996293, 45790169, 45667668, 44473167, 13231279, 3509435, 27185644, 33304202, 35092039, 84840549, 49882705, 40865610, 66611759, 48395186, 58224549, 36312813, 95581843, 79657802, 26776922, 49328608, 57248122, 91802888, 33628349, 8651647, 9860968, 60106217, 57393458, 54014062, 10358899, 22200378, 6497830, 44915235, 10309525, 53802686, 18466635, 34667286, 84166196, 34236719, 14723410, 98130363, 1197320, 24591705, 55770687, 73222868, 21673260, 62490109, 15902805, 12416768, 30395570, 70004753, 176257, 70727211, 79738755, 76170907, 64157906, 3235882, 66116458, 73168565, 16097038, 4204661, 53666583, 89804152, 26063929, 66322959, 80014588, 84904436, 46851987, 1239555, 83948335, 49597667, 75986488, 39847321, 48658605, 7685448, 51047803, 71300104, 92604458, 96906410, 54868730, 99690194, 63967300, 92998591, 77694700, 70367851, 63152504, 23740167, 22113133, 29466406, 74357852, 88047921, 55143724, 32058615, 82651278, 61859143, 4091162, 92692978, 74110882, 24314885, 19272365, 66428795, 68204242, 18806856, 20140249, 9603598, 74452589, 87710366, 11581181, 53632373, 17385531, 96318094, 247198, 10597197, 94595668, 50806615, 22721500, 92353856, 5111370, 72358170, 88904910, 95251277, 43501211, 31904591, 93057697, 5267545, 38645117, 59109400, 76825057, 83651211, 70800879, 83533741, 97940276, 80316608, 33797252, 90272749, 8634541, 69579137, 47361209, 57961282, 44348328, 86001008, 3233084, 83133790, 21001913, 90013093, 26998766, 28550822, 53084256, 58749, 10961421, 88653118, 68702632, 54199160, 3880712, 508198, 7229550, 6038457, 62115552, 38494874, 91831487, 55470718, 14326617, 88444207, 30366150, 51472275, 48675329, 15075176, 6157724, 20885148, 51590803, 6525948, 17857111, 61712234, 81853704, 53393358, 21070110, 86460488, 24619760, 89046466, 75135448, 98648327, 44784505, 60581278, 47887470, 1022677, 89217461, 37183543, 66271566, 52293995, 51507425, 30653863, 30463802, 23110625, 15535065, 45428665, 29635537, 33249630, 6808825, 51426311, 47090124, 58751351, 34044787, 89811711, 59329511, 95957797, 18131876, 78766358, 57359924, 90004325, 72732205, 69355476, 63015256, 50188404, 9257405, 83302115, 20645197, 14731700, 30218878, 5822202, 62762857, 37166608, 33201905, 68939068, 45381876, 15536795, 84127901, 52261574, 4064751, 47738185, 44060493, 30771409, 34946859, 40534591, 6871053, 48893685, 45306070, 61141874, 91281584, 77300457, 18833224, 61859581, 72725103, 92787493, 20642888, 68041839, 18001617, 88698958, 17957593, 90457870, 7104732, 9623492, 53842979, 33895336, 96735716, 96420429, 3183975, 75052463, 14093520, 47792865, 17976208, 11161731, 54663246, 69848388, 3633375, 32699744, 21993752, 35456853, 62232211, 38009615, 48360198, 68000591, 9575954, 86301513, 63059024, 43045786, 61263068, 86543538, 82052050, 61741594, 37560164, 57163802, 89214616, 5073754, 74724075, 18663507, 83747892, 70541760, 86118021, 4508700, 5970607, 24915585, 33935899, 7687278, 14363867, 16684829, 45919976, 16567550, 94711842, 56484900, 56424103, 96193415, 77072625, 11757872, 77187825, 39986008, 6986898, 1250437, 82779622, 71657078, 65851721, 67793644, 91255408, 14220886, 2917920, 56531125, 36812683, 83368048, 79191827, 92803766, 16405341, 94516935, 93053405, 29516592, 13173644, 78218845, 29510992, 22450468, 44664587, 3088684, 66667729, 79922758, 9259676, 42947632, 61271144, 14626618, 68128438, 32159704, 38008118, 65715134, 38658347, 13819358, 33123618, 93933709, 42967683, 72793444, 76671482, 2331773, 62936963, 44245960, 7011964, 33699435, 36135, 92033260, 72373496, 31491938, 74441448, 824872, 66663942, 86022504, 84187166, 12664567, 96709982, 99125126, 82339363, 45407418, 41092172, 2891150, 59371804, 43357947, 3487592, 65271999, 62357986, 4515343, 26114953, 83150534, 95010552, 67084351, 749283, 17365188, 24058273, 89499542, 30891921, 68110330, 55753905, 8129978, 55850790, 7300047, 42307449, 63506281, 9175338, 9188443, 90654836, 91938191, 50668599, 67030811, 57658654, 83269727, 89699445, 50567636, 77413012, 48774913, 32151165, 37891451, 67124282, 82327024, 75543508, 45617087, 58208470, 6221471, 59981773, 27325428, 15163258, 20836893, 20681921, 82178706, 40984766, 44177011, 30787683, 40027975, 77787724, 7182642, 78300864, 70420215, 33565483, 10453030, 16380211, 15015906, 61982238 +65715134, 82886381, 29188588, 42782652, 88444207, 824872, 34946859, 90090964, 8373289, 69605283, 569864, 63506281, 98371444, 63152504, 72777973, 73021291, 62496012, 7502255, 65851721, 14220886, 74743862, 44473167, 84684495, 28928175, 6038457, 38494874, 95290172, 93515664, 89499542, 35456853, 44177011, 16097038, 42199455, 44245960, 18663507, 4099191, 52060076, 4119747, 74110882, 24314885, 65074535, 83302115, 30218878, 56424103, 17037369, 89078848, 94595668, 50806615, 56531125, 24733232, 93057697, 29510992, 81855445, 88251446, 58749, 7104732, 4515343, 61271144, 70036438, 9860195, 11161731, 3633375, 99861373, 15064655, 66116458, 86301513, 98739783, 34698463, 2331773, 48892201, 1239555, 16099750, 89214616, 81172706, 70367851, 41245325, 7066775, 18783702, 86118021, 25636669, 58751351, 16054533, 77284619, 5822202, 61982238, 79322415, 86022504, 62428472, 17081350, 10453030, 44550764, 56515456, 91281584, 99917599, 10366309, 83651211, 41092172, 48673079, 26102057, 79942022, 35996293, 59371804, 33797252, 13231279, 72274002, 17068582, 93359396, 87160386, 72019362, 11923835, 58224549, 73235980, 66250369, 36312813, 53842979, 30694952, 57393458, 10358899, 51590803, 6525948, 69848388, 32699744, 21993752, 38009615, 86460488, 24619760, 83789391, 93790285, 67031644, 44784505, 75407347, 63059024, 43045786, 92867155, 33553959, 93933709, 11543098, 84904436, 46851987, 37739481, 61741594, 48658605, 77694700, 5970607, 38022834, 55143724, 41481685, 57359924, 27665211, 18806856, 60393039, 78300864, 66319530, 66663942, 74452589, 24168411, 56955985, 45381876, 57803235, 17727650, 30771409, 46540998, 32151165, 68694897, 9886593, 61141874, 32274392, 92530431, 82339363, 9599614, 69136837, 90310261, 97940276, 45790169, 93053405, 45617087, 97281847, 94090109, 58208470, 26863229, 83133790, 75128745, 3487592, 91141395, 63372756, 6221471, 55602660, 7229550, 83150534, 42237907, 92398073, 17365188, 62430984, 15163258, 34236719, 16595436, 19898053, 73392814, 38658347, 62232211, 40984766, 20867149, 8729146, 55753905, 65880522, 64602895, 68000591, 9575954, 95395112, 13819358, 92554120, 60581278, 82979980, 66271566, 8913721, 44847298, 34295794, 29635537, 9188443, 92604458, 36808486, 6808825, 8791066, 17146629, 41442762, 33237508, 77787724, 43376279, 36135, 68316156, 90004325, 54232247, 71920426, 65047700, 44479073, 50668599, 29994197, 94711842, 56484900, 77072625, 11757872, 62762857, 41092102, 83269727, 8128637, 50702367, 96709982, 99224392, 44060493, 79821897, 99971982, 66832478, 91255408, 4985896, 8696647, 45306070, 67124282, 65017137, 77300457, 65038678, 61960400, 54517921, 45075471, 79191827, 44481640, 82327024, 91240048, 83210802, 40677414, 76825057, 8115266, 72725103, 45407418, 92787493, 66137019, 75543508, 68041839, 33431960, 80251430, 90013093, 35092039, 26998766, 78785507, 49882705, 10961421, 65271999, 8055981, 45237957, 95581843, 82427263, 96420429, 26114953, 23569917, 33628349, 75052463, 9860968, 68816413, 14093520, 55470718, 1788101, 67084351, 22200378, 28734791, 20885148, 18466635, 97011160, 81677380, 22997281, 94539824, 84166196, 98130363, 7919588, 20836893, 61712234, 15015906, 81853704, 45794415, 82178706, 62490109, 33061250, 3773993, 30395570, 54263880, 79738755, 75135448, 24826575, 5482538, 64157906, 98648327, 22108665, 61928316, 92071990, 30569392, 7423788, 42644903, 38341669, 62051033, 55850790, 42967683, 6111563, 60102965, 53666583, 89804152, 55189057, 40197395, 52293995, 48260151, 16019925, 13468268, 66322959, 80555751, 36505482, 61815223, 91957544, 37560164, 15535065, 57163802, 18411915, 51047803, 96906410, 54868730, 70372191, 42073124, 63967300, 66885828, 29029316, 47708850, 7646095, 37620363, 55960386, 50007421, 56473732, 34044787, 89811711, 59329511, 31126490, 32058615, 7517032, 24915585, 92692978, 91938191, 50188404, 87720882, 39201414, 45919976, 1375023, 55247455, 20645197, 64055960, 67474219, 98948034, 3294781, 20140249, 33201905, 33565483, 45996863, 17385531, 82726008, 22129328, 29819940, 96318094, 6871053, 37891451, 247198, 54987042, 669105, 86821229, 22721500, 82897371, 92353856, 48893685, 9398733, 62693428, 89637706, 44846932, 44627776, 71083565, 31904591, 68824981, 13470059, 35512853, 51588015, 76540481, 70800879, 94516935, 14947650, 91727510, 2891150, 4434662, 49598724, 26493119, 97561745, 30139692, 69579137, 21001913, 22450468, 40865610, 28550822, 34698428, 88653118, 93270084, 3088684, 54199160, 30998561, 79922758, 1936762, 59981773, 47792865, 14326617, 95010552, 26397786, 36580610, 91990218, 84406788, 49236559, 64848072, 749283, 51472275, 4978543, 53802686, 34493392, 32159704, 26734892, 67227442, 53393358, 85695762, 20486294, 53648154, 73222868, 99604946, 38061439, 70004753, 31733363, 37224844, 19486173, 30090481, 68875490, 61728685, 29932657, 47887470, 29959549, 37183543, 86543538, 7300047, 82052050, 99729357, 54427233, 84002370, 26507214, 62936963, 78909561, 83948335, 49597667, 41380093, 77620120, 39847321, 7685448, 32426752, 5073754, 12348118, 44889423, 93562257, 67281495, 51426311, 99965001, 7011964, 33699435, 99297164, 22113133, 68644627, 29834512, 16971929, 52204879, 96726697, 78766358, 88047921, 85711894, 16424199, 49641577, 82651278, 4091162, 40781449, 90654836, 18699206, 7687278, 86798033, 66428795, 92033260, 63015256, 56153345, 68204242, 29401781, 72238278, 16567550, 95726235, 47298834, 14731700, 34497327, 54606384, 89699445, 31727379, 37192445, 26292919, 36396314, 40686254, 83155442, 99515901, 76330843, 4064751, 48774913, 59318837, 40152546, 168541, 44983451, 321665, 57020481, 526217, 95251277, 43506672, 93566986, 15148031, 32161669, 23848565, 38645117, 23432750, 17764950, 27411561, 22435353, 90272749, 57961282, 69641513, 98462867, 96852321, 86001008, 3233084, 71965942, 33304202, 76315420, 84840549, 68102437, 61623915, 66611759, 54762643, 9623492, 68702632, 79657802, 49328608, 96735716, 91802888, 3183975, 78602717, 78549759, 8651647, 91831487, 81274566, 42947632, 36933038, 30366150, 47893286, 59405277, 6157724, 15948937, 27325428, 14626618, 38008118, 20681921, 65338021, 55770687, 22879907, 81898046, 80246713, 12416768, 64087743, 30787683, 1569515, 48360198, 88130087, 76434144, 62552524, 3235882, 5640302, 10649306, 39801351, 65275241, 62803858, 33123618, 77301523, 77312810, 94360702, 43933006, 48088883, 98653983, 36685023, 26063929, 874791, 80014588, 79880247, 73617245, 55615885, 30653863, 65081429, 35192533, 30463802, 59177718, 74724075, 71469330, 37501808, 73109997, 51464002, 83747892, 39373729, 71316369, 95957797, 29466406, 2607799, 74357852, 61859143, 30811010, 28851716, 19272365, 14363867, 23194618, 72373496, 40356877, 37435892, 83083131, 86242799, 6819644, 59501487, 96193415, 36930650, 55669657, 38256849, 68939068, 12664567, 77413012, 53632373, 57240218, 1250437, 14349098, 5832946, 40534591, 45995325, 72278539, 11365791, 62452034, 72358170, 16387575, 43501211, 26664538, 5267545, 19376156, 84349107, 92803766, 59109400, 16405341, 85242963, 83533741, 26139110, 45667668, 18001617, 29516592, 8634541, 13173644, 19891772, 17539625, 21533347, 17957593, 38510840, 75153252, 44664587, 60430369, 62115552, 51315460, 85023028, 89419466, 21919959, 70782102, 26392416, 54014062, 4199704, 15075176, 65454636, 10309525, 14723410, 21070110, 22942635, 30891921, 21673260, 89046466, 59197747, 13348726, 77377183, 45990383, 47213183, 69255765, 21289531, 61263068, 37675718, 89217461, 23134715, 39171895, 26275734, 72793444, 58180653, 30543215, 51149804, 42307449, 62740044, 38365584, 32590267, 73124510, 92541302, 85116755, 99690194, 33249630, 12303248, 43322743, 42692881, 27187213, 79136082, 70541760, 56461322, 4508700, 71333116, 59910624, 77898274, 20122224, 37659250, 79806380, 33935899, 1204161, 4069912, 16684829, 85571389, 10264691, 99524975, 1431742, 40781854, 72357096, 99226875, 70420215, 57658654, 9603598, 37166608, 36780454, 11581181, 50567636, 39986008, 45848907, 84127901, 6986898, 17386996, 82779622, 16380211, 97057187, 73031054, 97641116, 61897582, 53888755, 69697787, 2917920, 99125126, 88904910, 36753250, 45049308, 74614639, 39553046, 44842615, 36139942, 90158785, 4787945, 99549373, 20642888, 22405120, 4095116, 80316608, 47361209, 75820087, 19939935, 27185644, 17894977, 43357947, 48395186, 28787861, 28796059, 3880712, 26776922, 2208785, 81805959, 20568363, 98943869, 36468541, 9829782, 48675329, 44915235, 72495719, 77272628, 68128438, 70596786, 44844121, 68110330, 70727211, 52613508, 8129978, 20765474, 31161687, 73168565, 4204661, 51507425, 84293052, 36845587, 7955293, 23110625, 72738685, 9175338, 69786756, 22766820, 71522968, 2204165, 6505939, 27041967, 91664334, 7182642, 14045383, 27625735, 72732205, 49271185, 76960303, 99021067, 73786944, 51466049, 8733068, 12024238, 24953498, 74441448, 60955663, 4668450, 20002147, 67030811, 87710366, 15536795, 52261574, 21397057, 71657078, 10597197, 30163921, 19457589, 83368048, 60176618, 78218845, 3509435, 44348328, 88698958, 25842078, 51715482, 66667729, 33895336, 66045587, 508198, 60106217, 63628376, 37280276, 36189527, 67451935, 17976208, 34667286, 89996536, 30764367, 59614746, 54663246, 17857111, 24591705, 53547802, 24058273, 6153406, 78884452, 61373987, 176257, 40027975, 78589145, 78561158, 75777973, 17016156, 1022677, 3233569, 17058722, 76671482, 76703609, 19101477, 92692283, 75986488, 45428665, 6793819, 71300104, 112651, 92998591, 23740167, 47090124, 18131876, 97379790, 81774825, 69355476, 77187825, 26744917, 34432810, 5111370, 94911072, 36812683, 18833224, 33336148, 16445503, 90457870, 53084256, 74137926, 9259676, 75229462, 13862149, 37957788, 32250045, 8807940, 87598888, 76170907, 12571310, 72614359, 82532312, 9257405, 43152977, 97783876, 67513640, 2717150, 6521313, 29065964, 61859581, 4806458, 9058407, 99658235, 62357986, 57248122, 66903004, 6497830, 64098930, 1197320, 4798568, 99333375, 46870723, 47738185, 54058788, 94076128, 67793644, 92215320, 90061527, 415901, 8099994, 85117092, 18504197, 57241521, 15902805, 31491938, 84187166 +45919976, 91255408, 44481640, 57961282, 84293052, 8634541, 29932657, 26063929, 41380093, 57163802, 95957797, 53888755, 83210802, 35996293, 88698958, 2331773, 52060076, 57359924, 56424103, 50702367, 56515456, 61960400, 97281847, 13173644, 88653118, 26114953, 17365188, 17857111, 69605283, 64157906, 62803858, 37620363, 36808486, 50007421, 39373729, 72238278, 9257405, 59501487, 70420215, 61897582, 8696647, 5111370, 62452034, 16387575, 83368048, 24733232, 44842615, 90310261, 45407418, 41092172, 22405120, 45617087, 69641513, 40865610, 3088684, 8651647, 36189527, 21070110, 82178706, 8807940, 40027975, 17016156, 93933709, 11543098, 52293995, 16019925, 84904436, 65081429, 7955293, 91957544, 77694700, 70367851, 70541760, 23740167, 5970607, 18699206, 19272365, 94711842, 14731700, 5822202, 55669657, 84187166, 77413012, 21397057, 48893685, 95251277, 31904591, 36139942, 69136837, 26139110, 58208470, 86001008, 90013093, 22450468, 49882705, 95581843, 60106217, 14093520, 61271144, 26397786, 9829782, 4199704, 18466635, 51590803, 53547802, 89499542, 35456853, 22942635, 62490109, 80246713, 76434144, 3235882, 75407347, 39801351, 73168565, 37183543, 42967683, 85117092, 34698463, 42307449, 76703609, 48260151, 4798568, 72614359, 72738685, 98371444, 7685448, 71300104, 112651, 33249630, 22766820, 44889423, 18663507, 6808825, 7011964, 17146629, 4508700, 34044787, 90004325, 16567550, 64055960, 4668450, 20002147, 57658654, 77187825, 36780454, 26744917, 39986008, 37192445, 15536795, 12664567, 14349098, 82779622, 94076128, 16380211, 14220886, 67124282, 65017137, 99125126, 61141874, 57020481, 36753250, 15148031, 23848565, 84349107, 16405341, 94516935, 14947650, 78218845, 19939935, 25842078, 71965942, 72019362, 58749, 54762643, 7104732, 54199160, 3880712, 60430369, 74137926, 1936762, 54014062, 51472275, 70036438, 93515664, 37957788, 53393358, 19898053, 68110330, 64087743, 99604946, 44177011, 61373987, 176257, 79738755, 93790285, 37224844, 68000591, 5640302, 98739783, 13819358, 92867155, 61263068, 569864, 82979980, 98653983, 55189057, 99729357, 80014588, 48892201, 415901, 38365584, 49597667, 75986488, 45428665, 99690194, 32426752, 27187213, 63152504, 6505939, 41245325, 51426311, 18783702, 86118021, 8791066, 58751351, 59329511, 77787724, 96726697, 7182642, 14045383, 77898274, 61859143, 90654836, 24314885, 86798033, 66428795, 68204242, 72373496, 85571389, 29994197, 73786944, 83302115, 60393039, 47298834, 96193415, 36930650, 37166608, 29819940, 59318837, 45995325, 37891451, 30163921, 90090964, 2917920, 56531125, 321665, 9886593, 36812683, 91281584, 71083565, 93566986, 26664538, 82327024, 5267545, 19376156, 92803766, 40677414, 59109400, 13470059, 90158785, 45790169, 93053405, 45667668, 97561745, 29510992, 44348328, 98462867, 3233084, 83133790, 91141395, 6221471, 8055981, 66667729, 36312813, 28787861, 28796059, 57248122, 55602660, 30694952, 51315460, 21919959, 36933038, 88444207, 1788101, 26392416, 10358899, 48675329, 17976208, 9860195, 6157724, 53802686, 34667286, 77272628, 15163258, 94539824, 7919588, 61712234, 6153406, 73222868, 1569515, 15064655, 76170907, 19486173, 65880522, 47213183, 86301513, 63059024, 92554120, 42644903, 37675718, 23134715, 36685023, 8913721, 63506281, 61815223, 19101477, 83948335, 73124510, 9188443, 18411915, 48658605, 92604458, 96906410, 54868730, 42073124, 44245960, 7646095, 18504197, 55960386, 18131876, 74357852, 31126490, 7517032, 37659250, 54232247, 79806380, 1204161, 99021067, 29401781, 31491938, 1375023, 74441448, 78300864, 6819644, 77284619, 11757872, 62762857, 97783876, 86022504, 62428472, 11581181, 33565483, 45848907, 8128637, 17385531, 1250437, 99515901, 22129328, 47738185, 44060493, 30771409, 34946859, 99971982, 67793644, 97641116, 74743862, 92353856, 89637706, 72358170, 44846932, 526217, 65038678, 74614639, 92530431, 68824981, 61859581, 35512853, 9058407, 4095116, 70800879, 85242963, 83533741, 66137019, 91727510, 33797252, 68041839, 18001617, 30139692, 29516592, 44473167, 33431960, 80251430, 21533347, 13231279, 17957593, 21001913, 33304202, 26998766, 28928175, 10961421, 48395186, 93270084, 79657802, 53842979, 66045587, 49328608, 42782652, 81805959, 9259676, 20568363, 59981773, 55470718, 95010552, 36580610, 91990218, 30366150, 49236559, 64848072, 15075176, 59405277, 20885148, 27325428, 97011160, 68128438, 84166196, 54663246, 32699744, 55770687, 22879907, 30891921, 12416768, 89046466, 20867149, 87598888, 75135448, 24826575, 64602895, 5482538, 67031644, 13348726, 98648327, 61928316, 69255765, 7423788, 31161687, 62051033, 55850790, 33123618, 26275734, 82052050, 51507425, 874791, 26507214, 36505482, 37739481, 32590267, 92541302, 34295794, 39847321, 6793819, 29635537, 85116755, 16099750, 69786756, 59177718, 92998591, 66885828, 71522968, 71469330, 37501808, 83747892, 67281495, 33699435, 89811711, 68644627, 29834512, 27041967, 78766358, 59910624, 16424199, 81774825, 68316156, 82651278, 24915585, 30811010, 28851716, 92033260, 50188404, 72777973, 40356877, 95726235, 43152977, 56484900, 24953498, 30218878, 824872, 62496012, 66663942, 61982238, 34497327, 9603598, 74452589, 38256849, 41092102, 99333375, 68939068, 96709982, 82726008, 5832946, 71657078, 46540998, 96318094, 247198, 10597197, 94595668, 50806615, 66832478, 86821229, 44550764, 11365791, 82897371, 62693428, 29065964, 18833224, 43506672, 79191827, 93057697, 60176618, 8115266, 99549373, 92787493, 17764950, 76540481, 80316608, 4434662, 33336148, 90272749, 8373289, 57241521, 94090109, 81855445, 84684495, 35092039, 72274002, 17068582, 43357947, 16445503, 90457870, 87160386, 84840549, 28550822, 53084256, 11923835, 65271999, 44664587, 33895336, 2208785, 3183975, 78602717, 75052463, 98943869, 9860968, 85023028, 89419466, 83150534, 95290172, 70782102, 57393458, 47893286, 749283, 28734791, 11161731, 15948937, 90061527, 32159704, 30764367, 34236719, 38008118, 64098930, 69848388, 15015906, 81853704, 24058273, 45794415, 53648154, 21673260, 86460488, 3773993, 70004753, 31733363, 99861373, 59197747, 77377183, 62552524, 45990383, 8129978, 95395112, 65275241, 38341669, 75777973, 29959549, 77301523, 33553959, 16097038, 43933006, 39171895, 53666583, 72793444, 17058722, 54427233, 73617245, 78909561, 15535065, 70372191, 43322743, 42692881, 81172706, 93562257, 4099191, 25636669, 33237508, 71333116, 29466406, 16971929, 55143724, 97379790, 36135, 32058615, 69355476, 71920426, 74110882, 7687278, 65074535, 14363867, 87720882, 10264691, 12024238, 98948034, 3294781, 72357096, 54606384, 33201905, 31727379, 56955985, 45996863, 89078848, 36396314, 84127901, 40686254, 76330843, 46870723, 17727650, 48774913, 34432810, 73031054, 72278539, 6521313, 19457589, 77300457, 45075471, 39553046, 82339363, 32161669, 9599614, 10366309, 91240048, 38645117, 76825057, 4787945, 51588015, 27411561, 79942022, 2891150, 75543508, 22435353, 26493119, 19891772, 93359396, 68102437, 61623915, 3487592, 34698428, 63372756, 26776922, 508198, 91802888, 7229550, 33628349, 81274566, 14326617, 36468541, 84406788, 37280276, 22200378, 6497830, 44915235, 4978543, 24591705, 67227442, 3633375, 73392814, 21993752, 38658347, 44844121, 40984766, 38009615, 30395570, 24619760, 54263880, 30787683, 70727211, 8729146, 48360198, 78589145, 30090481, 9575954, 44784505, 52613508, 92071990, 30569392, 10649306, 20765474, 61728685, 1022677, 94360702, 60102965, 89804152, 76671482, 66271566, 55615885, 80555751, 62936963, 61741594, 1239555, 23110625, 9175338, 89214616, 63967300, 12303248, 5073754, 74724075, 47708850, 51464002, 56473732, 56461322, 47090124, 41442762, 38022834, 2607799, 4091162, 40781449, 27625735, 27665211, 65047700, 44479073, 16684829, 23194618, 18806856, 8733068, 99524975, 20645197, 67030811, 99226875, 83269727, 67513640, 53632373, 4064751, 7502255, 79821897, 40152546, 97057187, 54987042, 9398733, 94911072, 88904910, 44627776, 45049308, 43501211, 32274392, 99917599, 72725103, 4806458, 48673079, 26102057, 97940276, 59371804, 47361209, 92215320, 3509435, 96852321, 26863229, 8099994, 78785507, 88251446, 75128745, 66611759, 58224549, 73235980, 9623492, 96735716, 4515343, 96420429, 79922758, 23569917, 38494874, 42947632, 13862149, 67451935, 81677380, 22997281, 14626618, 34493392, 59614746, 14723410, 26734892, 20681921, 70596786, 81898046, 38061439, 33061250, 88130087, 22108665, 78561158, 60581278, 21289531, 89217461, 86543538, 6111563, 7300047, 4204661, 51149804, 13468268, 66322959, 79880247, 84002370, 30463802, 37560164, 92692283, 77620120, 29029316, 73109997, 79136082, 7066775, 99965001, 99297164, 22113133, 43376279, 91664334, 88047921, 85711894, 16054533, 49641577, 92692978, 82532312, 33935899, 50668599, 39201414, 55247455, 1431742, 83083131, 60955663, 66319530, 20140249, 77072625, 87710366, 17037369, 57803235, 6986898, 52261574, 40534591, 10453030, 22721500, 45306070, 54517921, 83651211, 20642888, 38510840, 76315420, 51715482, 99658235, 62357986, 66250369, 68702632, 82427263, 6038457, 66903004, 68816413, 47792865, 65454636, 92398073, 32250045, 89996536, 98130363, 1197320, 20836893, 65715134, 65338021, 62232211, 15902805, 83789391, 43045786, 68875490, 82886381, 47887470, 58180653, 42199455, 40197395, 30653863, 62740044, 29188588, 51047803, 72732205, 91938191, 49271185, 76960303, 4069912, 51466049, 40781854, 67474219, 73021291, 89699445, 50567636, 17386996, 83155442, 99224392, 6871053, 65851721, 168541, 669105, 44983451, 68694897, 23432750, 49598724, 27185644, 17894977, 75153252, 62115552, 78549759, 91831487, 67084351, 42237907, 10309525, 62430984, 85695762, 20486294, 12571310, 55753905, 66116458, 77312810, 3233569, 30543215, 44847298, 36845587, 2204165, 71316369, 52204879, 41481685, 4119747, 63015256, 56153345, 37435892, 86242799, 45381876, 26292919, 17081350, 54058788, 2717150, 69697787, 69579137, 45237957, 30998561, 63628376, 72495719, 16595436, 48088883, 79322415, 24168411, 17539625, 75820087, 75229462, 78884452, 35192533, 12348118, 20122224, 57240218, 4985896, 6525948, 46851987, 32151165 +75135448, 56484900, 68939068, 37891451, 29065964, 79191827, 91831487, 10649306, 62803858, 60102965, 77787724, 16971929, 50702367, 21397057, 54987042, 45407418, 26776922, 89996536, 62552524, 58180653, 82052050, 46851987, 75986488, 57163802, 98371444, 51464002, 25636669, 24314885, 39201414, 66663942, 14220886, 89637706, 45049308, 69136837, 72725103, 91727510, 13173644, 44348328, 84684495, 49882705, 28796059, 508198, 91802888, 9259676, 89419466, 84406788, 32250045, 14626618, 15015906, 31733363, 44784505, 95395112, 7955293, 415901, 44889423, 29834512, 72732205, 98948034, 96193415, 62762857, 87710366, 61897582, 669105, 321665, 45075471, 60176618, 99549373, 83533741, 26139110, 68041839, 29510992, 17894977, 17068582, 90457870, 93270084, 68702632, 49328608, 42947632, 83150534, 67451935, 32159704, 6525948, 81853704, 3633375, 21993752, 8729146, 59197747, 45990383, 47213183, 7423788, 13819358, 33553959, 98653983, 62740044, 73124510, 92604458, 42692881, 74724075, 70367851, 18663507, 41245325, 5970607, 29466406, 2607799, 37659250, 27665211, 23194618, 85571389, 18806856, 78300864, 4668450, 20002147, 20140249, 70420215, 39986008, 94595668, 99971982, 65851721, 97641116, 44983451, 62452034, 18833224, 61960400, 15148031, 23848565, 13470059, 79942022, 14947650, 33431960, 21533347, 25842078, 27185644, 33304202, 22450468, 3487592, 54199160, 42782652, 36580610, 37280276, 93515664, 28734791, 38008118, 80246713, 40984766, 87598888, 12571310, 65880522, 30569392, 8129978, 20765474, 82979980, 26275734, 11543098, 34698463, 48260151, 26063929, 63506281, 51507425, 4798568, 36845587, 35192533, 30463802, 92692283, 72738685, 18411915, 96906410, 27187213, 44245960, 37620363, 63152504, 51426311, 86118021, 47090124, 8791066, 41442762, 58751351, 22113133, 74357852, 88047921, 16054533, 57359924, 74110882, 65047700, 65074535, 14363867, 44479073, 72373496, 45919976, 24168411, 37166608, 99333375, 17037369, 84187166, 45848907, 82726008, 34432810, 2717150, 69697787, 56515456, 9886593, 61141874, 65038678, 32274392, 83368048, 92530431, 31904591, 9599614, 19376156, 92803766, 38645117, 17764950, 41092172, 20642888, 76540481, 45790169, 93053405, 29516592, 8373289, 78218845, 98462867, 3233084, 16445503, 93359396, 84840549, 88251446, 61623915, 75153252, 53084256, 10961421, 88653118, 4515343, 33628349, 51315460, 20568363, 38494874, 98943869, 95290172, 4978543, 11161731, 27325428, 90061527, 77272628, 26734892, 82178706, 44844121, 33061250, 30395570, 30787683, 20867149, 99861373, 55753905, 64157906, 98648327, 22108665, 78561158, 75407347, 42644903, 29932657, 61263068, 93933709, 48088883, 17058722, 30543215, 51149804, 52293995, 99729357, 80014588, 72614359, 91957544, 32426752, 92998591, 22766820, 56461322, 39373729, 33237508, 71316369, 95957797, 97379790, 41481685, 32058615, 24915585, 69355476, 49271185, 87720882, 37435892, 74441448, 64055960, 1431742, 83083131, 40781854, 61982238, 57658654, 9603598, 83269727, 31727379, 12664567, 44060493, 46540998, 79821897, 96318094, 67793644, 66832478, 82897371, 5111370, 94911072, 36812683, 16387575, 54517921, 99917599, 68824981, 24733232, 61859581, 44842615, 93057697, 10366309, 40677414, 76825057, 92787493, 9058407, 48673079, 94516935, 66137019, 2891150, 22435353, 4434662, 97561745, 71965942, 72274002, 76315420, 26998766, 99658235, 91141395, 63372756, 54762643, 44664587, 95581843, 79657802, 33895336, 79922758, 7229550, 36468541, 63628376, 64848072, 70036438, 44915235, 9860195, 20885148, 10309525, 53802686, 17365188, 81677380, 30764367, 54663246, 24591705, 6153406, 55770687, 85695762, 35456853, 73222868, 30891921, 12416768, 64087743, 3773993, 78589145, 76434144, 30090481, 9575954, 69255765, 65275241, 31161687, 60581278, 33123618, 37675718, 569864, 89217461, 23134715, 42199455, 76671482, 13468268, 73617245, 65081429, 84002370, 48892201, 83948335, 32590267, 37560164, 41380093, 77620120, 6793819, 9188443, 33249630, 43322743, 5073754, 77694700, 71522968, 7646095, 71469330, 6505939, 83747892, 18504197, 70541760, 67281495, 55960386, 33699435, 99297164, 71333116, 96726697, 85711894, 16424199, 82651278, 90004325, 28851716, 54232247, 82532312, 19272365, 63015256, 76960303, 4069912, 72238278, 16567550, 95726235, 77284619, 3294781, 62496012, 99226875, 34497327, 11757872, 74452589, 55669657, 38256849, 56955985, 45381876, 8128637, 15536795, 77413012, 22129328, 46870723, 30771409, 59318837, 45995325, 97057187, 53888755, 4985896, 8696647, 65017137, 44846932, 71083565, 44481640, 82327024, 83210802, 90158785, 23432750, 22405120, 35996293, 80316608, 33797252, 49598724, 26493119, 45617087, 97281847, 18001617, 44473167, 19891772, 69579137, 47361209, 17539625, 57961282, 96852321, 26863229, 19939935, 17957593, 83133790, 51715482, 72019362, 28928175, 75128745, 58749, 11923835, 65271999, 6221471, 62357986, 9623492, 2208785, 6038457, 74137926, 78602717, 81274566, 14093520, 75229462, 21919959, 88444207, 26392416, 42237907, 54014062, 10358899, 47893286, 749283, 17976208, 15075176, 59405277, 18466635, 34667286, 68128438, 34493392, 94539824, 84166196, 14723410, 98130363, 20836893, 53547802, 65338021, 21070110, 53648154, 81898046, 24619760, 1569515, 83789391, 79738755, 15064655, 19486173, 48360198, 64602895, 13348726, 52613508, 92071990, 39801351, 92867155, 82886381, 21289531, 77301523, 42967683, 86543538, 6111563, 94360702, 43933006, 7300047, 72793444, 85117092, 42307449, 66322959, 78909561, 61815223, 19101477, 61741594, 45428665, 29188588, 9175338, 71300104, 89214616, 54868730, 70372191, 59177718, 63967300, 12303248, 66885828, 36808486, 6808825, 23740167, 7066775, 50007421, 7011964, 17146629, 34044787, 59329511, 49641577, 36135, 81774825, 77898274, 61859143, 40781449, 30811010, 99021067, 50668599, 10264691, 51466049, 83302115, 94711842, 99524975, 43152977, 6819644, 66319530, 67474219, 73021291, 59501487, 97783876, 41092102, 26744917, 37192445, 53632373, 57803235, 6986898, 40686254, 76330843, 14349098, 4064751, 5832946, 34946859, 247198, 94076128, 16380211, 10597197, 30163921, 91255408, 22721500, 6521313, 44550764, 92353856, 62693428, 56531125, 72358170, 88904910, 19457589, 91281584, 57020481, 93566986, 82339363, 90310261, 59109400, 70800879, 75543508, 57241521, 75820087, 88698958, 38510840, 35092039, 8099994, 34698428, 3088684, 66045587, 82427263, 96420429, 55602660, 3183975, 81805959, 23569917, 66903004, 9860968, 60106217, 59981773, 47792865, 55470718, 14326617, 61271144, 36933038, 1788101, 91990218, 30366150, 4199704, 36189527, 6497830, 6157724, 72495719, 62430984, 17857111, 16595436, 24058273, 89499542, 38658347, 22942635, 21673260, 62490109, 15902805, 8807940, 68110330, 38009615, 38061439, 44177011, 61373987, 40027975, 37224844, 5482538, 66116458, 86301513, 5640302, 98739783, 92554120, 38341669, 73168565, 16097038, 4204661, 3233569, 55189057, 40197395, 36685023, 66271566, 2331773, 44847298, 36505482, 51047803, 99690194, 112651, 47708850, 81172706, 4099191, 89811711, 27041967, 52204879, 18131876, 31126490, 59910624, 4091162, 20122224, 92692978, 71920426, 33935899, 1204161, 66428795, 92033260, 68204242, 29401781, 9257405, 29994197, 73786944, 1375023, 24953498, 60955663, 14731700, 67030811, 30218878, 72357096, 5822202, 56424103, 77072625, 77187825, 62428472, 50567636, 89078848, 84127901, 17386996, 83155442, 1250437, 99515901, 52261574, 7502255, 71657078, 40534591, 10453030, 86821229, 74743862, 68694897, 45306070, 67124282, 99125126, 39553046, 32161669, 5267545, 8115266, 51588015, 4095116, 85242963, 59371804, 30139692, 3509435, 78785507, 40865610, 28550822, 48395186, 7104732, 8055981, 73235980, 3880712, 60430369, 26114953, 78549759, 85023028, 95010552, 70782102, 13862149, 51472275, 51590803, 59614746, 64098930, 69848388, 67227442, 32699744, 53393358, 69605283, 86460488, 99604946, 70727211, 93790285, 68000591, 77377183, 61928316, 43045786, 75777973, 61728685, 29959549, 77312810, 16019925, 79880247, 55615885, 26507214, 23110625, 15535065, 85116755, 16099750, 48658605, 7685448, 2204165, 73109997, 99965001, 18783702, 4508700, 43376279, 91664334, 7182642, 55143724, 14045383, 68316156, 7517032, 52060076, 90654836, 27625735, 91938191, 16684829, 50188404, 56153345, 40356877, 8733068, 60393039, 55247455, 20645197, 86242799, 824872, 54606384, 86022504, 33201905, 33565483, 26292919, 36396314, 57240218, 17385531, 54058788, 32151165, 50806615, 72278539, 11365791, 526217, 77300457, 74614639, 36139942, 84349107, 91240048, 83651211, 35512853, 27411561, 90272749, 8634541, 94090109, 80251430, 81855445, 90013093, 68102437, 58224549, 28787861, 53842979, 30998561, 57248122, 30694952, 75052463, 1936762, 68816413, 67084351, 22200378, 48675329, 92398073, 15163258, 7919588, 65715134, 45794415, 78884452, 89046466, 76170907, 17016156, 1022677, 39171895, 53666583, 89804152, 8913721, 76703609, 84293052, 80555751, 49597667, 92541302, 34295794, 39847321, 42073124, 29029316, 37501808, 38022834, 78766358, 79806380, 18699206, 7687278, 86798033, 72777973, 47298834, 79322415, 36780454, 11581181, 17081350, 99224392, 17727650, 47738185, 48774913, 29819940, 48893685, 2917920, 9398733, 95251277, 43506672, 4806458, 26102057, 92215320, 13231279, 69641513, 66611759, 45237957, 66250369, 66667729, 96735716, 62115552, 8651647, 49236559, 9829782, 65454636, 15948937, 97011160, 34236719, 1197320, 61712234, 70596786, 20486294, 22879907, 62232211, 24826575, 67031644, 63059024, 55850790, 54427233, 30653863, 37739481, 1239555, 69786756, 12348118, 93562257, 56473732, 31491938, 36930650, 67513640, 40152546, 90090964, 43501211, 16405341, 33336148, 45667668, 58208470, 86001008, 43357947, 26397786, 57393458, 37957788, 22997281, 20681921, 19898053, 54263880, 176257, 88130087, 47887470, 37183543, 874791, 62936963, 29635537, 79136082, 68644627, 4119747, 89699445, 82779622, 6871053, 73031054, 36753250, 26664538, 4787945, 97940276, 21001913, 87160386, 70004753, 3235882, 62051033, 84904436, 12024238, 44627776, 36312813, 38365584, 45996863, 96709982, 73392814, 68875490, 168541 +69641513, 68702632, 32159704, 4668450, 94911072, 29510992, 9860968, 68128438, 45794415, 8807940, 66271566, 79880247, 49597667, 6793819, 29834512, 26292919, 70800879, 83533741, 66137019, 44473167, 17957593, 66250369, 81805959, 75229462, 61271144, 77272628, 26734892, 20836893, 37224844, 8729146, 78589145, 77301523, 77312810, 85571389, 96193415, 99515901, 89637706, 74614639, 8115266, 22405120, 79942022, 4434662, 33431960, 47361209, 92215320, 98462867, 8055981, 9259676, 98943869, 53802686, 94539824, 34236719, 81853704, 89499542, 59197747, 44784505, 47213183, 16019925, 46851987, 78909561, 72738685, 16054533, 82651278, 54232247, 76960303, 16567550, 60393039, 77187825, 87710366, 68939068, 48774913, 32151165, 79821897, 8696647, 45306070, 62693428, 9886593, 61859581, 44348328, 26863229, 25842078, 76315420, 72019362, 78785507, 61623915, 75153252, 91141395, 82427263, 30694952, 55470718, 10358899, 28734791, 27325428, 81677380, 84166196, 14723410, 61373987, 62552524, 78561158, 65275241, 31161687, 62051033, 29932657, 89217461, 48088883, 82052050, 52293995, 80014588, 4798568, 39847321, 63967300, 92998591, 2204165, 79136082, 70541760, 8791066, 43376279, 96726697, 57359924, 92692978, 29401781, 40356877, 56484900, 24953498, 66319530, 72357096, 57658654, 36930650, 37192445, 5832946, 54058788, 65851721, 86821229, 56531125, 44846932, 77300457, 61960400, 54517921, 68824981, 32161669, 51588015, 45407418, 17764950, 94516935, 2891150, 75543508, 80316608, 33304202, 88653118, 6038457, 91831487, 47792865, 36468541, 26392416, 48675329, 4199704, 11161731, 22997281, 15015906, 85695762, 81898046, 80246713, 12416768, 89046466, 65880522, 8129978, 39801351, 20765474, 61263068, 72793444, 58180653, 66322959, 30653863, 72614359, 36845587, 83948335, 37560164, 29635537, 7685448, 51047803, 71300104, 22766820, 36808486, 6808825, 18504197, 67281495, 51426311, 58751351, 34044787, 71316369, 74357852, 97379790, 49271185, 1204161, 50668599, 68204242, 95726235, 8733068, 99524975, 64055960, 78300864, 83083131, 6819644, 98948034, 30218878, 17037369, 84187166, 12664567, 53632373, 57240218, 40686254, 1250437, 10453030, 96318094, 54987042, 4985896, 16387575, 95251277, 44481640, 93057697, 5267545, 40677414, 4787945, 14947650, 68041839, 29516592, 8373289, 57241521, 94090109, 69579137, 58208470, 13231279, 84684495, 35092039, 16445503, 99658235, 40865610, 66611759, 48395186, 9623492, 79657802, 33628349, 51315460, 85023028, 64848072, 37957788, 4978543, 6525948, 16595436, 65715134, 3633375, 24058273, 20486294, 30891921, 86460488, 38061439, 31733363, 99861373, 88130087, 9575954, 66116458, 92554120, 42644903, 29959549, 33553959, 93933709, 7300047, 17058722, 40197395, 36685023, 54427233, 65081429, 61741594, 415901, 32590267, 92692283, 89214616, 33249630, 43322743, 44889423, 7646095, 93562257, 51464002, 7066775, 4099191, 56461322, 39373729, 77787724, 16971929, 52204879, 2607799, 78766358, 85711894, 14045383, 41481685, 24915585, 72732205, 69355476, 79806380, 24314885, 33935899, 65047700, 14363867, 4069912, 99021067, 87720882, 23194618, 39201414, 12024238, 55247455, 60955663, 67474219, 20140249, 99226875, 74452589, 62428472, 67513640, 45848907, 76330843, 46870723, 99224392, 29819940, 97057187, 66832478, 2717150, 74743862, 11365791, 9398733, 67124282, 56515456, 61141874, 45049308, 32274392, 82339363, 10366309, 69136837, 90310261, 59109400, 23432750, 83651211, 20642888, 4095116, 97940276, 26139110, 49598724, 33336148, 26493119, 97281847, 30139692, 80251430, 21533347, 72274002, 84840549, 26998766, 28928175, 75128745, 58749, 11923835, 34698428, 63372756, 54762643, 45237957, 3088684, 28796059, 26776922, 66045587, 55602660, 75052463, 81274566, 95290172, 42237907, 13862149, 9860195, 65454636, 92398073, 20885148, 17365188, 34493392, 30764367, 54663246, 69848388, 32699744, 53547802, 65338021, 70596786, 19898053, 35456853, 53648154, 73222868, 22942635, 38009615, 64087743, 54263880, 1569515, 20867149, 5482538, 64157906, 13348726, 45990383, 43045786, 95395112, 13819358, 62803858, 61728685, 82886381, 33123618, 4204661, 3233569, 26275734, 98653983, 55189057, 11543098, 874791, 84293052, 62936963, 35192533, 9188443, 16099750, 98371444, 42692881, 74724075, 47708850, 71522968, 37501808, 55960386, 86118021, 47090124, 41442762, 22113133, 29466406, 91664334, 7182642, 77898274, 90004325, 27625735, 19272365, 65074535, 16684829, 31491938, 1375023, 20002147, 77284619, 3294781, 34497327, 9603598, 38256849, 54606384, 11581181, 33565483, 39986008, 84127901, 57803235, 52261574, 17727650, 82779622, 30771409, 7502255, 46540998, 40152546, 37891451, 10597197, 73031054, 669105, 14220886, 6521313, 90090964, 44550764, 68694897, 2917920, 62452034, 88904910, 44627776, 19457589, 65038678, 18833224, 43501211, 15148031, 24733232, 19376156, 91240048, 92803766, 60176618, 90158785, 99549373, 93053405, 45667668, 18001617, 13173644, 81855445, 57961282, 75820087, 19939935, 71965942, 90013093, 43357947, 51715482, 87160386, 68102437, 65271999, 6221471, 73235980, 3880712, 42782652, 78549759, 23569917, 60106217, 42947632, 14326617, 83150534, 95010552, 70782102, 88444207, 36580610, 749283, 6497830, 93515664, 17976208, 38008118, 53393358, 73392814, 55770687, 21070110, 82178706, 44844121, 33061250, 44177011, 30787683, 70004753, 83789391, 70727211, 40027975, 76170907, 55753905, 48360198, 67031644, 30090481, 98648327, 3235882, 52613508, 69255765, 30569392, 98739783, 75777973, 60581278, 21289531, 47887470, 37675718, 23134715, 86543538, 43933006, 30543215, 76671482, 51149804, 85117092, 34698463, 76703609, 84904436, 44847298, 36505482, 7955293, 19101477, 1239555, 91957544, 38365584, 77620120, 92541302, 34295794, 15535065, 85116755, 18411915, 92604458, 96906410, 70372191, 42073124, 112651, 12303248, 27187213, 12348118, 81172706, 63152504, 41245325, 7011964, 4508700, 33237508, 95957797, 18131876, 16424199, 81774825, 40781449, 30811010, 28851716, 4119747, 27665211, 18699206, 91938191, 66428795, 72373496, 73786944, 51466049, 18806856, 37435892, 20645197, 40781854, 14731700, 59501487, 11757872, 41092102, 37166608, 36780454, 33201905, 31727379, 26744917, 45381876, 45996863, 8128637, 89078848, 17386996, 17081350, 14349098, 47738185, 21397057, 59318837, 99971982, 168541, 50806615, 53888755, 72278539, 48893685, 69697787, 5111370, 72358170, 29065964, 91281584, 71083565, 92530431, 44842615, 36139942, 72725103, 4806458, 76540481, 35996293, 97561745, 17539625, 96852321, 83133790, 38510840, 21001913, 17068582, 90457870, 49882705, 88251446, 28550822, 53084256, 10961421, 62357986, 44664587, 508198, 4515343, 79922758, 91802888, 7229550, 3183975, 26114953, 38494874, 8651647, 21919959, 36933038, 84406788, 49236559, 37280276, 9829782, 47893286, 70036438, 67451935, 6157724, 10309525, 18466635, 34667286, 32250045, 51590803, 62430984, 89996536, 59614746, 7919588, 6153406, 21993752, 24619760, 87598888, 12571310, 64602895, 92071990, 86301513, 5640302, 17016156, 1022677, 569864, 6111563, 60102965, 73617245, 37739481, 30463802, 48892201, 41380093, 45428665, 9175338, 54868730, 59177718, 32426752, 70367851, 71469330, 18783702, 56473732, 33699435, 17146629, 99297164, 89811711, 59329511, 5970607, 71333116, 31126490, 88047921, 68316156, 7517032, 61859143, 37659250, 7687278, 92033260, 72238278, 9257405, 29994197, 45919976, 83302115, 43152977, 61982238, 77072625, 79322415, 55669657, 83269727, 50567636, 77413012, 96709982, 4064751, 34946859, 247198, 94076128, 94595668, 44983451, 82897371, 92353856, 321665, 526217, 45075471, 39553046, 83368048, 79191827, 9599614, 23848565, 83210802, 13470059, 26102057, 91727510, 90272749, 19891772, 27185644, 17894977, 22450468, 7104732, 58224549, 36312813, 28787861, 49328608, 96735716, 2208785, 96420429, 74137926, 20568363, 68816413, 14093520, 1788101, 91990218, 57393458, 54014062, 51472275, 90061527, 17857111, 61712234, 78884452, 62232211, 21673260, 62490109, 15902805, 30395570, 79738755, 93790285, 15064655, 75135448, 19486173, 24826575, 76434144, 68000591, 77377183, 61928316, 63059024, 10649306, 16097038, 42199455, 42307449, 51507425, 55615885, 84002370, 66885828, 44245960, 37620363, 6505939, 83747892, 23740167, 50007421, 38022834, 36135, 4091162, 20122224, 82532312, 44479073, 94711842, 47298834, 74441448, 824872, 66663942, 97783876, 15536795, 6986898, 22129328, 34432810, 61897582, 31904591, 93566986, 82327024, 76825057, 35512853, 48673079, 85242963, 59371804, 45790169, 33797252, 45617087, 78218845, 3509435, 86001008, 3233084, 8099994, 93359396, 3487592, 93270084, 66667729, 30998561, 60430369, 57248122, 78602717, 89419466, 30366150, 59405277, 72495719, 97011160, 98130363, 1197320, 68110330, 99604946, 3773993, 176257, 75407347, 7423788, 68875490, 94360702, 48260151, 13468268, 62740044, 80555751, 61815223, 23110625, 48658605, 69786756, 5073754, 29029316, 18663507, 73109997, 99965001, 55143724, 90654836, 74110882, 63015256, 1431742, 86242799, 73021291, 56424103, 62762857, 24168411, 89699445, 99333375, 83155442, 17385531, 67793644, 97641116, 30163921, 22721500, 65017137, 57020481, 36753250, 43506672, 38645117, 92787493, 9058407, 22435353, 88698958, 54199160, 53842979, 33895336, 66903004, 67084351, 36189527, 44915235, 15075176, 15948937, 64098930, 67227442, 20681921, 69605283, 40984766, 38341669, 92867155, 55850790, 37183543, 39171895, 89804152, 99729357, 26507214, 73124510, 75986488, 29188588, 57163802, 99690194, 25636669, 68644627, 49641577, 32058615, 52060076, 71920426, 86798033, 56153345, 62496012, 36396314, 50702367, 82726008, 44060493, 16380211, 99125126, 36812683, 99917599, 84349107, 41092172, 27411561, 8634541, 62115552, 1936762, 26397786, 63628376, 15163258, 24591705, 22879907, 82979980, 42967683, 53666583, 8913721, 26063929, 2331773, 59910624, 72777973, 10264691, 67030811, 86022504, 56955985, 40534591, 6871053, 26664538, 16405341, 95581843, 59981773, 22200378, 73168565, 77694700, 50188404, 5822202, 70420215, 71657078, 91255408, 14626618, 38658347, 22108665, 27041967, 45995325, 63506281 +7517032, 37166608, 22450468, 39171895, 17058722, 94911072, 34493392, 98739783, 76703609, 74357852, 99971982, 48673079, 13231279, 28928175, 28550822, 9623492, 15075176, 99604946, 78561158, 8129978, 26275734, 48892201, 25636669, 59329511, 824872, 31727379, 37192445, 96709982, 30163921, 9599614, 38645117, 4787945, 35512853, 38510840, 65271999, 6221471, 78602717, 83150534, 95010552, 26397786, 51472275, 53802686, 7919588, 19898053, 59197747, 52613508, 38341669, 16019925, 32590267, 15535065, 72738685, 92998591, 66885828, 81172706, 71333116, 29466406, 61859143, 40356877, 94711842, 86022504, 62428472, 45996863, 34946859, 32151165, 97057187, 54987042, 90090964, 5111370, 56531125, 99917599, 97561745, 19939935, 8099994, 49882705, 28796059, 33895336, 4199704, 44915235, 37957788, 73392814, 38658347, 73222868, 8807940, 54263880, 89046466, 43045786, 68875490, 42644903, 73168565, 1022677, 58180653, 42199455, 36685023, 85117092, 34698463, 78909561, 29188588, 16099750, 63967300, 44245960, 7646095, 37501808, 68316156, 72777973, 18806856, 8733068, 20645197, 1431742, 20002147, 77072625, 97783876, 74452589, 41092102, 17385531, 5832946, 74743862, 39553046, 61859581, 83210802, 51588015, 41092172, 80316608, 93053405, 33336148, 29510992, 17539625, 84684495, 17068582, 72019362, 40865610, 93270084, 95581843, 82427263, 91802888, 26114953, 66903004, 1788101, 36580610, 84406788, 47893286, 36189527, 9860195, 59405277, 72495719, 15163258, 26734892, 20486294, 80246713, 55753905, 5482538, 77377183, 61928316, 92554120, 92867155, 77301523, 16097038, 42307449, 26507214, 37739481, 35192533, 1239555, 92692283, 6793819, 57163802, 89214616, 32426752, 6808825, 41245325, 23740167, 17146629, 39373729, 43376279, 97379790, 16424199, 81774825, 37659250, 54232247, 1204161, 66428795, 92033260, 44479073, 50668599, 23194618, 12024238, 1375023, 67474219, 5822202, 17037369, 89078848, 52261574, 59318837, 10597197, 34432810, 50806615, 61897582, 44983451, 72278539, 8696647, 2917920, 99125126, 91281584, 92530431, 26664538, 36139942, 83651211, 72725103, 17764950, 4095116, 70800879, 26102057, 85242963, 97940276, 26493119, 13173644, 81855445, 92215320, 44348328, 72274002, 43357947, 76315420, 84840549, 88251446, 10961421, 88653118, 73235980, 66667729, 36312813, 68702632, 79922758, 55602660, 7229550, 62115552, 74137926, 20568363, 8651647, 61271144, 54014062, 6497830, 4978543, 11161731, 20885148, 90061527, 77272628, 94539824, 89996536, 81853704, 16595436, 53547802, 30891921, 3773993, 30395570, 88130087, 76434144, 30090481, 65275241, 29932657, 82052050, 30543215, 76671482, 66271566, 99729357, 13468268, 874791, 79880247, 84293052, 84904436, 30463802, 23110625, 45428665, 7685448, 71300104, 70372191, 12303248, 42692881, 27187213, 29029316, 37620363, 7066775, 4099191, 56473732, 56461322, 33699435, 34044787, 71316369, 95957797, 78766358, 49641577, 52060076, 27665211, 65047700, 72238278, 72373496, 29994197, 37435892, 64055960, 83083131, 98948034, 73021291, 9603598, 54606384, 89699445, 33565483, 56955985, 45848907, 1250437, 99224392, 47738185, 7502255, 71657078, 46540998, 40152546, 37891451, 73031054, 67793644, 86821229, 91255408, 82897371, 69697787, 65017137, 72358170, 9886593, 88904910, 36812683, 19457589, 36753250, 71083565, 45075471, 79191827, 24733232, 93057697, 19376156, 84349107, 90310261, 23432750, 4806458, 20642888, 66137019, 91727510, 49598724, 45617087, 94090109, 21533347, 3509435, 88698958, 17894977, 93359396, 87160386, 26998766, 78785507, 53084256, 58749, 63372756, 48395186, 54762643, 7104732, 53842979, 66045587, 38494874, 81274566, 60106217, 36933038, 57393458, 10358899, 49236559, 93515664, 67451935, 15948937, 32250045, 97011160, 32159704, 38008118, 14723410, 98130363, 65715134, 45794415, 70596786, 85695762, 21993752, 81898046, 22942635, 40984766, 64087743, 38061439, 33061250, 30787683, 1569515, 176257, 79738755, 93790285, 15064655, 75135448, 64602895, 67031644, 22108665, 9575954, 3235882, 92071990, 69255765, 75407347, 66116458, 86301513, 95395112, 61728685, 55850790, 82886381, 33123618, 37675718, 569864, 40197395, 63506281, 54427233, 51507425, 73617245, 30653863, 46851987, 61815223, 7955293, 73124510, 41380093, 75986488, 51047803, 92604458, 54868730, 42073124, 12348118, 93562257, 55960386, 86118021, 38022834, 16971929, 85711894, 30811010, 72732205, 92692978, 82532312, 18699206, 76960303, 56153345, 85571389, 45919976, 83302115, 78300864, 60955663, 40781854, 6819644, 77284619, 61982238, 57658654, 87710366, 36780454, 45381876, 8128637, 36396314, 57803235, 6986898, 17386996, 29819940, 40534591, 6871053, 669105, 2717150, 48893685, 9398733, 89637706, 44627776, 57020481, 95251277, 43506672, 61960400, 83368048, 82339363, 68824981, 10366309, 99549373, 9058407, 76540481, 27411561, 14947650, 22435353, 4434662, 33797252, 97281847, 90272749, 30139692, 8373289, 57241521, 19891772, 57961282, 69641513, 17957593, 71965942, 90013093, 75153252, 34698428, 66611759, 62357986, 58224549, 3088684, 66250369, 79657802, 508198, 2208785, 4515343, 96420429, 3183975, 1936762, 9860968, 91831487, 68816413, 89419466, 88444207, 42237907, 22200378, 64848072, 70036438, 14626618, 62430984, 59614746, 34236719, 6525948, 3633375, 55770687, 21070110, 22879907, 44844121, 15902805, 86460488, 24619760, 44177011, 61373987, 20867149, 40027975, 8729146, 76170907, 19486173, 78589145, 64157906, 98648327, 68000591, 10649306, 62051033, 61263068, 37183543, 93933709, 42967683, 48088883, 53666583, 51149804, 52293995, 8913721, 48260151, 80014588, 2331773, 84002370, 4798568, 44847298, 36505482, 92541302, 9188443, 98371444, 96906410, 112651, 5073754, 70367851, 73109997, 6505939, 70541760, 99965001, 18783702, 47090124, 58751351, 68644627, 18131876, 2607799, 96726697, 31126490, 59910624, 32058615, 27625735, 69355476, 71920426, 79806380, 24314885, 91938191, 7687278, 19272365, 65074535, 4069912, 16684829, 87720882, 68204242, 9257405, 39201414, 16567550, 51466049, 56484900, 24953498, 4668450, 14731700, 67030811, 59501487, 11757872, 79322415, 77187825, 33201905, 11581181, 53632373, 50702367, 46870723, 17727650, 82779622, 21397057, 44060493, 54058788, 79821897, 45995325, 168541, 66832478, 14220886, 92353856, 65038678, 31904591, 93566986, 82327024, 91240048, 60176618, 68041839, 18001617, 78218845, 75820087, 25842078, 83133790, 33304202, 3487592, 44664587, 28787861, 54199160, 96735716, 60430369, 57248122, 23569917, 98943869, 85023028, 59981773, 47792865, 75229462, 36468541, 67084351, 30366150, 9829782, 48675329, 17976208, 10309525, 18466635, 17365188, 22997281, 84166196, 64098930, 15015906, 20681921, 24058273, 65338021, 78884452, 35456853, 62232211, 21673260, 12416768, 48360198, 13348726, 45990383, 44784505, 30569392, 63059024, 7423788, 47887470, 17016156, 77312810, 82979980, 23134715, 3233569, 89804152, 72793444, 55189057, 66322959, 83948335, 77620120, 85116755, 69786756, 44889423, 36808486, 63152504, 79136082, 51426311, 50007421, 99297164, 33237508, 22113133, 91664334, 55143724, 14045383, 16054533, 90654836, 49271185, 63015256, 50188404, 99021067, 29401781, 95726235, 47298834, 74441448, 20140249, 62496012, 36930650, 55669657, 84187166, 26292919, 77413012, 84127901, 83155442, 22129328, 30771409, 247198, 94076128, 65851721, 53888755, 11365791, 68694897, 62452034, 44846932, 61141874, 29065964, 16387575, 18833224, 74614639, 44842615, 5267545, 76825057, 8115266, 79942022, 35996293, 2891150, 75543508, 59371804, 8634541, 69579137, 98462867, 96852321, 26863229, 27185644, 16445503, 90457870, 51715482, 99658235, 75128745, 91141395, 49328608, 6038457, 81805959, 78549759, 51315460, 14326617, 95290172, 26392416, 63628376, 37280276, 749283, 28734791, 92398073, 34667286, 30764367, 54663246, 1197320, 61712234, 32699744, 6153406, 53393358, 83789391, 70727211, 87598888, 99861373, 37224844, 12571310, 62552524, 20765474, 31161687, 75777973, 60581278, 33553959, 60102965, 98653983, 55615885, 80555751, 19101477, 38365584, 37560164, 18411915, 22766820, 43322743, 74724075, 47708850, 18663507, 41442762, 89811711, 77787724, 27041967, 88047921, 77898274, 24915585, 14363867, 99524975, 55247455, 86242799, 30218878, 99226875, 66663942, 56424103, 96193415, 62762857, 38256849, 50567636, 15536795, 40686254, 17081350, 82726008, 48774913, 96318094, 97641116, 6521313, 67124282, 56515456, 526217, 43501211, 54517921, 15148031, 32161669, 23848565, 22405120, 16405341, 83533741, 26139110, 45790169, 33431960, 47361209, 86001008, 61623915, 8055981, 45237957, 26776922, 30694952, 42947632, 55470718, 21919959, 27325428, 81677380, 68128438, 17857111, 24591705, 69605283, 53648154, 24826575, 5640302, 89217461, 86543538, 4204661, 11543098, 26063929, 415901, 49597667, 34295794, 39847321, 9175338, 48658605, 99690194, 77694700, 83747892, 67281495, 4508700, 29834512, 52204879, 57359924, 90004325, 4119747, 73786944, 60393039, 31491938, 66319530, 72357096, 34497327, 70420215, 26744917, 39986008, 12664567, 99515901, 76330843, 4064751, 45306070, 62693428, 77300457, 32274392, 44481640, 59109400, 13470059, 92787493, 45667668, 80251430, 3233084, 21001913, 35092039, 11923835, 42782652, 33628349, 9259676, 75052463, 70782102, 91990218, 6157724, 65454636, 51590803, 20836893, 67227442, 89499542, 62490109, 70004753, 65880522, 39801351, 62803858, 21289531, 29959549, 62740044, 72614359, 36845587, 62936963, 91957544, 29635537, 59177718, 33249630, 2204165, 71469330, 51464002, 7011964, 8791066, 5970607, 4091162, 20122224, 40781449, 74110882, 10264691, 83269727, 57240218, 10453030, 16380211, 44550764, 69136837, 92803766, 40677414, 90158785, 45407418, 58208470, 3880712, 30998561, 68110330, 38009615, 31733363, 47213183, 7300047, 65081429, 61741594, 7182642, 41481685, 36135, 82651278, 28851716, 33935899, 86798033, 43152977, 3294781, 24168411, 68939068, 67513640, 94595668, 22721500, 94516935, 29516592, 68102437, 14093520, 13819358, 94360702, 99333375, 14349098, 4985896, 321665, 45049308, 69848388, 6111563, 71522968, 18504197, 43933006, 82178706, 44473167, 13862149 +33237508, 3880712, 93566986, 66611759, 11161731, 90061527, 37501808, 1431742, 31727379, 33895336, 9259676, 17365188, 15902805, 92692283, 12348118, 55143724, 41481685, 67030811, 53888755, 56515456, 16387575, 31904591, 45407418, 21533347, 57961282, 3233084, 33304202, 65271999, 68816413, 54014062, 6157724, 81853704, 20681921, 73222868, 61728685, 37675718, 37183543, 7300047, 55189057, 52293995, 51507425, 32590267, 41380093, 37620363, 6808825, 56461322, 47090124, 59910624, 90654836, 37659250, 16684829, 23194618, 29994197, 66663942, 34497327, 86022504, 84127901, 45995325, 91255408, 69697787, 8696647, 88904910, 92530431, 84349107, 23432750, 83533741, 2891150, 18001617, 57241521, 13173644, 72019362, 61623915, 53084256, 63372756, 66045587, 49328608, 60430369, 81805959, 81274566, 14093520, 749283, 37957788, 34667286, 67227442, 65338021, 78884452, 69605283, 76170907, 67031644, 78561158, 92071990, 66116458, 31161687, 60581278, 39171895, 72793444, 36685023, 99729357, 61815223, 34295794, 39847321, 112651, 22766820, 43322743, 6505939, 7066775, 55960386, 4099191, 25636669, 32058615, 61859143, 19272365, 45919976, 10264691, 16567550, 60393039, 86242799, 14731700, 99226875, 96193415, 37166608, 39986008, 36396314, 40686254, 16380211, 34432810, 65851721, 61897582, 669105, 72278539, 11365791, 2917920, 321665, 89637706, 94911072, 61141874, 71083565, 15148031, 61859581, 9599614, 51588015, 93053405, 97281847, 33431960, 80251430, 17539625, 92215320, 83133790, 22450468, 93359396, 87160386, 78785507, 54762643, 58224549, 75229462, 14326617, 70782102, 70036438, 93515664, 15075176, 65454636, 32250045, 59614746, 3633375, 53547802, 45794415, 99604946, 64602895, 5482538, 61928316, 45990383, 52613508, 68875490, 38341669, 33123618, 1022677, 77301523, 42967683, 43933006, 17058722, 51149804, 13468268, 73617245, 30653863, 72614359, 19101477, 23110625, 15535065, 29188588, 29635537, 57163802, 92604458, 92998591, 42692881, 36808486, 83747892, 50007421, 56473732, 7011964, 39373729, 7182642, 20122224, 54232247, 71920426, 1204161, 65047700, 86798033, 63015256, 50668599, 29401781, 73786944, 31491938, 98948034, 30218878, 77072625, 9603598, 36930650, 29819940, 247198, 94076128, 97641116, 66832478, 6521313, 44550764, 74743862, 62452034, 99125126, 43501211, 74614639, 61960400, 83368048, 82339363, 44842615, 93057697, 36139942, 40677414, 13470059, 4095116, 70800879, 97940276, 66137019, 22435353, 45617087, 58208470, 3509435, 44348328, 88698958, 3487592, 7104732, 73235980, 44664587, 79657802, 53842979, 74137926, 26114953, 21919959, 95010552, 42237907, 57393458, 9829782, 67451935, 10309525, 62430984, 26734892, 24591705, 21070110, 53648154, 12416768, 64087743, 38061439, 44177011, 20867149, 83789391, 93790285, 8729146, 12571310, 75135448, 3235882, 69255765, 8129978, 63059024, 92554120, 42644903, 65275241, 62051033, 62803858, 29932657, 61263068, 77312810, 569864, 33553959, 93933709, 48088883, 66271566, 42307449, 63506281, 80555751, 92541302, 89214616, 99690194, 69786756, 63967300, 33249630, 66885828, 5073754, 27187213, 44889423, 47708850, 7646095, 93562257, 73109997, 63152504, 70541760, 8791066, 41442762, 89811711, 71316369, 52204879, 88047921, 85711894, 14045383, 97379790, 81774825, 90004325, 69355476, 68204242, 72238278, 43152977, 4668450, 6819644, 77284619, 59501487, 41092102, 24168411, 36780454, 99333375, 56955985, 26292919, 57803235, 7502255, 96318094, 59318837, 10597197, 50806615, 54987042, 4985896, 29065964, 77300457, 45075471, 26664538, 82327024, 69136837, 83210802, 76825057, 90158785, 72725103, 99549373, 9058407, 48673079, 27411561, 33797252, 97561745, 78218845, 19891772, 29510992, 69641513, 98462867, 86001008, 17957593, 21001913, 27185644, 17894977, 8099994, 90457870, 49882705, 88251446, 45237957, 57248122, 91802888, 6038457, 62115552, 23569917, 33628349, 38494874, 98943869, 91831487, 89419466, 88444207, 1788101, 26397786, 36580610, 47893286, 48675329, 4199704, 28734791, 59405277, 18466635, 81677380, 30764367, 84166196, 14723410, 69848388, 7919588, 32699744, 55770687, 38658347, 35456853, 22879907, 62232211, 22942635, 38009615, 54263880, 89046466, 70727211, 79738755, 15064655, 55753905, 59197747, 24826575, 48360198, 88130087, 64157906, 22108665, 44784505, 30569392, 95395112, 13819358, 55850790, 82886381, 17016156, 82979980, 94360702, 11543098, 85117092, 8913721, 76703609, 66322959, 874791, 84904436, 46851987, 4798568, 44847298, 36845587, 61741594, 37560164, 6793819, 9188443, 18411915, 98371444, 71300104, 42073124, 74724075, 29029316, 44245960, 71522968, 2204165, 51426311, 18783702, 86118021, 68644627, 95957797, 29466406, 27041967, 96726697, 16424199, 68316156, 77898274, 57359924, 4091162, 24915585, 27625735, 74110882, 91938191, 49271185, 14363867, 4069912, 87720882, 72777973, 72373496, 85571389, 95726235, 51466049, 83302115, 47298834, 1375023, 56484900, 40781854, 20002147, 66319530, 824872, 70420215, 62762857, 79322415, 97783876, 54606384, 87710366, 83269727, 89699445, 84187166, 26744917, 45996863, 8128637, 57240218, 96709982, 17385531, 1250437, 47738185, 30771409, 10453030, 32151165, 73031054, 82897371, 92353856, 72358170, 44627776, 95251277, 54517921, 79191827, 91240048, 92803766, 8115266, 14947650, 59371804, 68041839, 33336148, 30139692, 94090109, 47361209, 13231279, 43357947, 51715482, 84840549, 99658235, 28550822, 34698428, 48395186, 88653118, 62357986, 66250369, 36312813, 28787861, 42782652, 508198, 79922758, 55602660, 78602717, 20568363, 66903004, 42947632, 95290172, 36933038, 13862149, 22200378, 9860195, 53802686, 72495719, 34493392, 15163258, 94539824, 6525948, 17857111, 65715134, 24058273, 21993752, 81898046, 3773993, 19486173, 78589145, 76434144, 75407347, 73168565, 21289531, 42199455, 84293052, 55615885, 65081429, 48892201, 415901, 77620120, 9175338, 16099750, 48658605, 7685448, 96906410, 12303248, 77694700, 71469330, 67281495, 99965001, 33699435, 4508700, 34044787, 77787724, 71333116, 78766358, 16054533, 49641577, 82651278, 52060076, 40781449, 24314885, 18699206, 33935899, 66428795, 92033260, 50188404, 9257405, 18806856, 12024238, 37435892, 24953498, 74441448, 61982238, 56424103, 55669657, 50567636, 45381876, 12664567, 77413012, 52261574, 22129328, 4064751, 44060493, 54058788, 6871053, 97057187, 168541, 30163921, 44983451, 90090964, 45306070, 62693428, 65017137, 9886593, 91281584, 526217, 65038678, 32274392, 44481640, 24733232, 32161669, 10366309, 5267545, 19376156, 90310261, 38645117, 60176618, 4787945, 4806458, 92787493, 17764950, 41092172, 76540481, 85242963, 35996293, 45790169, 26493119, 90272749, 44473167, 81855445, 75820087, 84684495, 25842078, 38510840, 72274002, 76315420, 10961421, 8055981, 9623492, 93270084, 28796059, 26776922, 85023028, 59981773, 55470718, 83150534, 36468541, 26392416, 91990218, 30366150, 63628376, 37280276, 64848072, 51472275, 6497830, 44915235, 20885148, 97011160, 38008118, 54663246, 16595436, 19898053, 44844121, 40984766, 68110330, 33061250, 24619760, 30787683, 70004753, 176257, 40027975, 65880522, 13348726, 30090481, 98648327, 9575954, 7423788, 5640302, 92867155, 29959549, 89217461, 16097038, 3233569, 26275734, 89804152, 82052050, 30543215, 76671482, 34698463, 26063929, 16019925, 26507214, 78909561, 36505482, 7955293, 1239555, 83948335, 38365584, 73124510, 85116755, 54868730, 70372191, 59177718, 18663507, 79136082, 51464002, 23740167, 99297164, 22113133, 59329511, 29834512, 16971929, 18131876, 43376279, 74357852, 36135, 28851716, 72732205, 4119747, 79806380, 99021067, 94711842, 99524975, 55247455, 67474219, 20140249, 5822202, 77187825, 38256849, 33201905, 6986898, 82726008, 14349098, 46870723, 17727650, 34946859, 79821897, 40152546, 99971982, 14220886, 67124282, 57020481, 18833224, 23848565, 83651211, 35512853, 20642888, 22405120, 16405341, 26102057, 79942022, 26139110, 8373289, 8634541, 69579137, 96852321, 19939935, 90013093, 35092039, 16445503, 26998766, 68102437, 28928175, 40865610, 75153252, 11923835, 6221471, 3088684, 95581843, 54199160, 96735716, 96420429, 7229550, 8651647, 1936762, 9860968, 47792865, 61271144, 67084351, 84406788, 10358899, 49236559, 27325428, 77272628, 34236719, 53393358, 73392814, 8807940, 61373987, 1569515, 99861373, 68000591, 62552524, 47213183, 20765474, 23134715, 6111563, 4204661, 54427233, 80014588, 2331773, 62740044, 62936963, 49597667, 72738685, 75986488, 45428665, 32426752, 81172706, 58751351, 30811010, 92692978, 27665211, 7687278, 76960303, 40356877, 64055960, 83083131, 60955663, 73021291, 57658654, 62428472, 17037369, 68939068, 45848907, 50702367, 76330843, 99224392, 21397057, 71657078, 40534591, 37891451, 94595668, 22721500, 44846932, 19457589, 36753250, 39553046, 71965942, 17068582, 91141395, 4515343, 78549759, 36189527, 15948937, 14626618, 68128438, 32159704, 1197320, 6153406, 20486294, 82178706, 80246713, 31733363, 37224844, 86301513, 43045786, 75777973, 47887470, 86543538, 53666583, 98653983, 40197395, 79880247, 37739481, 30463802, 51047803, 18504197, 41245325, 38022834, 31126490, 65074535, 39201414, 20645197, 11757872, 74452589, 33565483, 37192445, 17081350, 82779622, 46540998, 86821229, 5111370, 36812683, 99917599, 91727510, 49598724, 45667668, 26863229, 66667729, 68702632, 82427263, 51315460, 60106217, 92398073, 22997281, 89996536, 64098930, 98130363, 15015906, 89499542, 21673260, 86460488, 30395570, 10649306, 58180653, 48260151, 84002370, 35192533, 70367851, 5970607, 91664334, 44479073, 56153345, 78300864, 72357096, 62496012, 67513640, 15536795, 89078848, 53632373, 17386996, 83155442, 5832946, 67793644, 48893685, 68694897, 43506672, 68824981, 59109400, 30998561, 3183975, 20836893, 61712234, 70596786, 62490109, 77377183, 60102965, 17146629, 2607799, 7517032, 82532312, 3294781, 11581181, 99515901, 48774913, 94516935, 80316608, 29516592, 75128745, 2208785, 75052463, 4978543, 17976208, 85695762, 98739783, 91957544, 8733068, 9398733, 56531125, 75543508, 4434662, 58749, 51590803, 39801351, 30694952, 30891921, 87598888, 2717150, 45049308 +60393039, 45381876, 26493119, 11161731, 72238278, 508198, 36505482, 31126490, 36780454, 67513640, 68694897, 88444207, 75135448, 39171895, 60102965, 63152504, 57359924, 92692978, 71920426, 22721500, 57020481, 60176618, 23432750, 26863229, 68702632, 49328608, 81805959, 67227442, 89217461, 82979980, 98653983, 74724075, 37501808, 41442762, 59329511, 2607799, 43376279, 55143724, 41481685, 74110882, 44479073, 56153345, 95726235, 86242799, 56955985, 53888755, 56515456, 62693428, 72358170, 24733232, 76825057, 98462867, 33304202, 28928175, 8055981, 93270084, 42782652, 78602717, 749283, 4199704, 98130363, 61712234, 3633375, 78884452, 99604946, 13819358, 31161687, 82886381, 47887470, 37675718, 569864, 16097038, 72793444, 30653863, 84002370, 34295794, 72738685, 75986488, 39847321, 16099750, 37620363, 51464002, 99965001, 22113133, 16971929, 27625735, 37659250, 54232247, 66428795, 73786944, 6819644, 77284619, 98948034, 5822202, 79322415, 55669657, 8128637, 57240218, 82726008, 7502255, 32151165, 6871053, 669105, 321665, 61141874, 77300457, 18833224, 54517921, 93566986, 32161669, 10366309, 3233084, 17957593, 35092039, 16445503, 87160386, 75128745, 65271999, 79657802, 96735716, 51315460, 70782102, 67084351, 54014062, 70036438, 15075176, 53802686, 27325428, 97011160, 68128438, 69605283, 82178706, 30395570, 61373987, 83789391, 8729146, 55753905, 63059024, 98739783, 92554120, 33553959, 42967683, 17058722, 58180653, 51149804, 44847298, 48658605, 43322743, 77694700, 29029316, 44889423, 18663507, 93562257, 51426311, 4099191, 25636669, 17146629, 89811711, 77787724, 88047921, 85711894, 28851716, 86798033, 65074535, 14363867, 50668599, 72373496, 83302115, 36930650, 83269727, 33565483, 17081350, 30771409, 94595668, 65851721, 73031054, 72278539, 82897371, 8696647, 65038678, 83368048, 9599614, 83210802, 4806458, 22405120, 35996293, 66137019, 2891150, 75543508, 22435353, 45617087, 44473167, 81855445, 57961282, 75820087, 71965942, 21001913, 28550822, 3487592, 45237957, 30998561, 62115552, 74137926, 98943869, 9860968, 91831487, 55470718, 83150534, 26392416, 49236559, 64848072, 9829782, 93515664, 28734791, 92398073, 15948937, 34667286, 22997281, 62430984, 32159704, 24591705, 65715134, 6153406, 19898053, 73392814, 89499542, 21673260, 40984766, 64087743, 44177011, 79738755, 59197747, 24826575, 48360198, 88130087, 30090481, 61928316, 44784505, 47213183, 78561158, 92071990, 68875490, 92867155, 93933709, 48088883, 52293995, 16019925, 13468268, 54427233, 73617245, 37739481, 77620120, 29188588, 57163802, 9175338, 7685448, 51047803, 32426752, 42692881, 44245960, 36808486, 58751351, 38022834, 71333116, 59910624, 32058615, 77898274, 52060076, 4091162, 20122224, 40781449, 27665211, 24314885, 49271185, 92033260, 39201414, 51466049, 20645197, 66319530, 3294781, 72357096, 66663942, 77187825, 68939068, 39986008, 37192445, 26292919, 53632373, 17386996, 40686254, 4064751, 45995325, 247198, 94076128, 61897582, 66832478, 2717150, 92353856, 5111370, 94911072, 99125126, 91281584, 43501211, 71083565, 68824981, 44842615, 69136837, 59109400, 8115266, 13470059, 45407418, 4095116, 59371804, 33797252, 49598724, 90272749, 8373289, 94090109, 58208470, 92215320, 13231279, 25842078, 83133790, 90457870, 75153252, 63372756, 54762643, 73235980, 9623492, 95581843, 66045587, 2208785, 96420429, 57248122, 79922758, 55602660, 7229550, 6038457, 9259676, 75052463, 8651647, 59981773, 95290172, 1788101, 48675329, 77272628, 34236719, 6525948, 69848388, 20486294, 22942635, 80246713, 12416768, 3773993, 99861373, 93790285, 5482538, 9575954, 3235882, 66116458, 8129978, 95395112, 42644903, 38341669, 1022677, 61263068, 23134715, 43933006, 7300047, 89804152, 34698463, 76703609, 84904436, 62740044, 72614359, 30463802, 91957544, 41380093, 92692283, 85116755, 12303248, 5073754, 47708850, 70367851, 71469330, 67281495, 7011964, 8791066, 29466406, 27041967, 91664334, 96726697, 78766358, 7182642, 16054533, 30811010, 33935899, 63015256, 72777973, 29994197, 45919976, 10264691, 31491938, 37435892, 24953498, 62496012, 74452589, 86022504, 89699445, 14349098, 17727650, 48774913, 5832946, 59318837, 168541, 30163921, 86821229, 6521313, 9398733, 65017137, 44846932, 44627776, 36812683, 29065964, 526217, 95251277, 32274392, 39553046, 31904591, 90158785, 83651211, 4787945, 17764950, 9058407, 97940276, 91727510, 80316608, 68041839, 97281847, 8634541, 3509435, 84684495, 19939935, 90013093, 76315420, 49882705, 40865610, 61623915, 34698428, 44664587, 33895336, 26776922, 38494874, 1936762, 75229462, 14326617, 61271144, 36580610, 84406788, 10358899, 37957788, 17976208, 20885148, 14626618, 89996536, 84166196, 64098930, 14723410, 26734892, 32699744, 53393358, 85695762, 21070110, 73222868, 30891921, 86460488, 38061439, 54263880, 30787683, 20867149, 87598888, 31733363, 40027975, 65880522, 76434144, 67031644, 22108665, 69255765, 30569392, 7423788, 65275241, 29932657, 29959549, 86543538, 6111563, 40197395, 66271566, 48260151, 65081429, 2331773, 61815223, 73124510, 29635537, 71300104, 92604458, 112651, 33249630, 2204165, 7646095, 6505939, 7066775, 55960386, 86118021, 47090124, 39373729, 34044787, 33237508, 5970607, 29834512, 74357852, 16424199, 82532312, 18699206, 68204242, 8733068, 99524975, 4668450, 67474219, 824872, 99226875, 96193415, 70420215, 77072625, 38256849, 41092102, 54606384, 24168411, 87710366, 33201905, 99333375, 15536795, 12664567, 57803235, 6986898, 46870723, 47738185, 44060493, 10453030, 37891451, 16380211, 10597197, 54987042, 91255408, 90090964, 48893685, 69697787, 45306070, 56531125, 89637706, 9886593, 88904910, 36753250, 92530431, 44481640, 26664538, 82327024, 61859581, 23848565, 91240048, 40677414, 90310261, 51588015, 16405341, 48673079, 76540481, 79942022, 94516935, 26139110, 93053405, 18001617, 30139692, 33431960, 13173644, 29510992, 69579137, 47361209, 96852321, 27185644, 17894977, 17068582, 72019362, 88251446, 11923835, 66611759, 36312813, 3880712, 82427263, 91802888, 26114953, 23569917, 33628349, 20568363, 85023028, 47792865, 42237907, 57393458, 51472275, 9860195, 59405277, 18466635, 32250045, 15163258, 94539824, 30764367, 38008118, 20836893, 81853704, 20681921, 16595436, 21993752, 22879907, 62232211, 38009615, 24619760, 70004753, 70727211, 68000591, 45990383, 75407347, 5640302, 61728685, 60581278, 17016156, 3233569, 53666583, 42199455, 55189057, 99729357, 26063929, 51507425, 79880247, 55615885, 26507214, 78909561, 35192533, 7955293, 19101477, 83948335, 49597667, 9188443, 59177718, 63967300, 92998591, 66885828, 27187213, 81172706, 73109997, 18504197, 23740167, 18783702, 99297164, 52204879, 18131876, 36135, 7517032, 61859143, 90004325, 4119747, 16684829, 87720882, 23194618, 29401781, 85571389, 43152977, 56484900, 55247455, 64055960, 83083131, 40781854, 61982238, 56424103, 34497327, 62762857, 62428472, 17037369, 1250437, 76330843, 99971982, 50806615, 44983451, 4985896, 44550764, 74743862, 11365791, 43506672, 74614639, 61960400, 45075471, 99917599, 82339363, 15148031, 93057697, 19376156, 38645117, 35512853, 99549373, 92787493, 85242963, 45790169, 45667668, 57241521, 86001008, 38510840, 8099994, 22450468, 93359396, 53084256, 10961421, 7104732, 58224549, 54199160, 30694952, 66903004, 81274566, 42947632, 14093520, 21919959, 36189527, 44915235, 67451935, 4978543, 6157724, 10309525, 51590803, 81677380, 34493392, 7919588, 15015906, 53547802, 45794415, 35456853, 53648154, 176257, 64602895, 62552524, 43045786, 62051033, 62803858, 55850790, 33123618, 77312810, 94360702, 26275734, 82052050, 30543215, 11543098, 63506281, 80014588, 36845587, 62936963, 61741594, 38365584, 92541302, 15535065, 45428665, 6793819, 18411915, 54868730, 99690194, 12348118, 71522968, 79136082, 6808825, 41245325, 68644627, 49641577, 68316156, 79806380, 76960303, 4069912, 50188404, 18806856, 78300864, 60955663, 20140249, 11757872, 37166608, 31727379, 84187166, 26744917, 45996863, 36396314, 50702367, 52261574, 22129328, 99224392, 46540998, 54058788, 14220886, 2917920, 19457589, 79191827, 92803766, 41092172, 33336148, 97561745, 29516592, 78218845, 21533347, 69641513, 43357947, 26998766, 68102437, 48395186, 88653118, 66250369, 28787861, 28796059, 53842979, 78549759, 68816413, 89419466, 36933038, 37280276, 47893286, 6497830, 54663246, 17857111, 65338021, 38658347, 12571310, 78589145, 13348726, 98648327, 10649306, 4204661, 36685023, 76671482, 8913721, 874791, 84293052, 46851987, 1239555, 37560164, 23110625, 89214616, 22766820, 70541760, 56461322, 4508700, 71316369, 24915585, 7687278, 99021067, 9257405, 94711842, 74441448, 14731700, 20002147, 67030811, 30218878, 73021291, 59501487, 11581181, 50567636, 45848907, 84127901, 82779622, 21397057, 71657078, 40534591, 40152546, 34432810, 67793644, 67124282, 62452034, 84349107, 72725103, 26102057, 14947650, 80251430, 19891772, 44348328, 88698958, 51715482, 78785507, 6221471, 3088684, 95010552, 26397786, 13862149, 63628376, 22200378, 90061527, 17365188, 59614746, 1197320, 24058273, 70596786, 8807940, 33061250, 1569515, 76170907, 19486173, 64157906, 77377183, 75777973, 73168565, 21289531, 37183543, 98371444, 70372191, 69786756, 42073124, 83747892, 56473732, 97379790, 81774825, 82651278, 90654836, 69355476, 91938191, 1204161, 40356877, 12024238, 47298834, 1375023, 57658654, 9603598, 97783876, 77413012, 89078848, 29819940, 96318094, 97057187, 16387575, 5267545, 36139942, 4434662, 17539625, 72274002, 58749, 91141395, 60106217, 30366150, 65454636, 72495719, 81898046, 44844121, 68110330, 52613508, 86301513, 39801351, 85117092, 42307449, 66322959, 415901, 32590267, 96906410, 50007421, 33699435, 14045383, 72732205, 65047700, 83155442, 96709982, 99515901, 34946859, 83533741, 66667729, 60430369, 3183975, 36468541, 91990218, 89046466, 15064655, 77301523, 4798568, 48892201, 19272365, 1431742, 17385531, 79821897, 97641116, 45049308, 27411561, 84840549, 99658235, 62357986, 4515343, 62490109, 37224844, 80555751, 95957797, 20642888, 70800879, 55770687, 16567550, 15902805, 20765474 +41092102, 83210802, 83533741, 51715482, 36312813, 53547802, 85695762, 55753905, 65880522, 64157906, 66116458, 55143724, 66319530, 99549373, 57961282, 81274566, 67451935, 62232211, 12571310, 68875490, 20765474, 11543098, 34295794, 66885828, 63152504, 55247455, 30218878, 66663942, 44060493, 46540998, 54058788, 9886593, 82339363, 4806458, 30139692, 17068582, 76315420, 6038457, 9259676, 83150534, 91990218, 14723410, 20836893, 20486294, 20867149, 59197747, 48360198, 86301513, 73168565, 61263068, 78909561, 415901, 32590267, 92692283, 71469330, 36808486, 51464002, 49641577, 71920426, 33935899, 39201414, 10264691, 60393039, 74452589, 86022504, 99224392, 669105, 6521313, 90090964, 44550764, 92353856, 56531125, 29065964, 18833224, 45075471, 38645117, 51588015, 27411561, 35996293, 69579137, 17539625, 98462867, 96852321, 22450468, 91141395, 93270084, 66250369, 30998561, 3183975, 47893286, 93515664, 4978543, 10309525, 22997281, 89996536, 83789391, 70727211, 79738755, 5482538, 22108665, 69255765, 5640302, 92867155, 82979980, 93933709, 86543538, 16097038, 48088883, 55189057, 66322959, 30653863, 37560164, 9188443, 44889423, 47708850, 71522968, 2204165, 73109997, 91664334, 36135, 82651278, 90654836, 72732205, 4119747, 65047700, 65074535, 56153345, 50668599, 29994197, 40356877, 77284619, 20140249, 57658654, 68939068, 39986008, 45381876, 40686254, 82726008, 71657078, 69697787, 68824981, 93057697, 60176618, 41092172, 22435353, 94090109, 19939935, 87160386, 78785507, 49882705, 75153252, 53084256, 6221471, 48395186, 54762643, 62357986, 73235980, 7229550, 20568363, 66903004, 21919959, 61271144, 42237907, 84406788, 10358899, 15075176, 65454636, 62430984, 84166196, 16595436, 65715134, 81898046, 68110330, 54263880, 30787683, 78589145, 61928316, 95395112, 31161687, 61728685, 21289531, 60102965, 4204661, 89804152, 4798568, 61741594, 29188588, 39847321, 18411915, 77694700, 29029316, 23740167, 59329511, 74357852, 16424199, 41481685, 61859143, 40781449, 92692978, 24314885, 7687278, 50188404, 31491938, 64055960, 83083131, 40781854, 5822202, 9603598, 55669657, 24168411, 87710366, 62428472, 99333375, 84187166, 67513640, 45996863, 17386996, 1250437, 22129328, 17727650, 82779622, 29819940, 5832946, 247198, 10597197, 44983451, 72278539, 22721500, 2717150, 74743862, 82897371, 68694897, 62693428, 99125126, 57020481, 61960400, 26664538, 23848565, 59109400, 76825057, 92787493, 9058407, 66137019, 2891150, 33336148, 45667668, 57241521, 33431960, 19891772, 29510992, 3233084, 25842078, 75128745, 7104732, 9623492, 55602660, 74137926, 51315460, 59981773, 36468541, 36933038, 30366150, 63628376, 64848072, 36189527, 44915235, 9860195, 53802686, 32250045, 27325428, 81677380, 34236719, 1197320, 26734892, 67227442, 3633375, 45794415, 53393358, 19898053, 73392814, 44844121, 15902805, 12416768, 99604946, 38061439, 33061250, 19486173, 67031644, 68000591, 77312810, 37183543, 42967683, 43933006, 7300047, 40197395, 76703609, 51507425, 36505482, 37739481, 35192533, 49597667, 92541302, 85116755, 98371444, 92604458, 54868730, 32426752, 92998591, 70541760, 7066775, 55960386, 56461322, 25636669, 4508700, 71316369, 68316156, 4091162, 20122224, 28851716, 69355476, 19272365, 66428795, 92033260, 72238278, 45919976, 83302115, 37435892, 20645197, 6819644, 67474219, 73021291, 3294781, 824872, 8128637, 83155442, 96709982, 52261574, 4064751, 32151165, 6871053, 97057187, 66832478, 86821229, 89637706, 61141874, 36753250, 77300457, 71083565, 15148031, 5267545, 91240048, 83651211, 45407418, 17764950, 20642888, 48673079, 4095116, 26102057, 91727510, 8373289, 3509435, 27185644, 33304202, 17894977, 43357947, 90457870, 10961421, 11923835, 34698428, 63372756, 65271999, 66611759, 8055981, 54199160, 26776922, 96735716, 2208785, 60430369, 62115552, 1936762, 42947632, 14093520, 9829782, 48675329, 70036438, 4199704, 11161731, 15948937, 34667286, 72495719, 30764367, 59614746, 38008118, 54663246, 6525948, 61712234, 69605283, 80246713, 8807940, 86460488, 3773993, 61373987, 87598888, 93790285, 37224844, 76170907, 75135448, 64602895, 30569392, 10649306, 62051033, 55850790, 47887470, 29959549, 89217461, 6111563, 3233569, 53666583, 26275734, 72793444, 98653983, 17058722, 30543215, 85117092, 73617245, 65081429, 72614359, 62936963, 7955293, 23110625, 72738685, 6793819, 57163802, 99690194, 70372191, 69786756, 42073124, 33249630, 12303248, 5073754, 12348118, 18663507, 37501808, 18783702, 86118021, 50007421, 33699435, 99297164, 39373729, 34044787, 16971929, 78766358, 7182642, 97379790, 32058615, 52060076, 49271185, 29401781, 72777973, 73786944, 8733068, 74441448, 78300864, 60955663, 4668450, 62496012, 99226875, 61982238, 56424103, 77072625, 79322415, 77187825, 33201905, 17037369, 50567636, 56955985, 37192445, 26292919, 77413012, 36396314, 17081350, 14349098, 48774913, 7502255, 10453030, 79821897, 16380211, 73031054, 91255408, 2917920, 44627776, 16387575, 65038678, 82327024, 61859581, 10366309, 19376156, 84349107, 4787945, 97940276, 59371804, 45790169, 93053405, 26493119, 45617087, 47361209, 58208470, 13231279, 75820087, 84684495, 26863229, 71965942, 35092039, 8099994, 28550822, 61623915, 45237957, 44664587, 3880712, 4515343, 57248122, 75052463, 38494874, 8651647, 98943869, 9860968, 91831487, 85023028, 89419466, 95290172, 36580610, 57393458, 13862149, 51472275, 6497830, 37957788, 90061527, 77272628, 34493392, 32159704, 69848388, 7919588, 24591705, 78884452, 89499542, 53648154, 73222868, 22879907, 38009615, 30395570, 44177011, 31733363, 15064655, 88130087, 13348726, 62552524, 45990383, 3235882, 47213183, 92071990, 8129978, 98739783, 42644903, 29932657, 82886381, 58180653, 42199455, 82052050, 76671482, 66271566, 26063929, 63506281, 13468268, 54427233, 79880247, 84002370, 46851987, 26507214, 44847298, 80555751, 61815223, 19101477, 83948335, 77620120, 51047803, 71300104, 43322743, 42692881, 27187213, 37620363, 99965001, 4099191, 56473732, 17146629, 58751351, 89811711, 22113133, 5970607, 71333116, 29834512, 27041967, 18131876, 96726697, 31126490, 59910624, 81774825, 7517032, 57359924, 24915585, 79806380, 18699206, 63015256, 76960303, 23194618, 18806856, 12024238, 43152977, 86242799, 20002147, 96193415, 54606384, 36780454, 45848907, 76330843, 46870723, 47738185, 30771409, 34946859, 59318837, 99971982, 65851721, 67793644, 168541, 4985896, 14220886, 56515456, 62452034, 65017137, 72358170, 44846932, 36812683, 45049308, 526217, 92530431, 69136837, 8115266, 90158785, 70800879, 4434662, 33797252, 49598724, 68041839, 97561745, 29516592, 8634541, 81855445, 92215320, 44348328, 17957593, 90013093, 68102437, 99658235, 28928175, 58749, 88653118, 66667729, 79657802, 53842979, 91802888, 78549759, 33628349, 68816413, 75229462, 26397786, 22200378, 749283, 28734791, 59405277, 20885148, 51590803, 17365188, 64098930, 98130363, 17857111, 15015906, 81853704, 20681921, 65338021, 55770687, 21993752, 24619760, 99861373, 24826575, 77377183, 9575954, 75407347, 39801351, 13819358, 65275241, 62803858, 60581278, 1022677, 77301523, 33553959, 52293995, 8913721, 84293052, 84904436, 30463802, 48892201, 38365584, 73124510, 45428665, 29635537, 59177718, 7646095, 6505939, 47090124, 7011964, 8791066, 33237508, 68644627, 38022834, 2607799, 88047921, 14045383, 16054533, 77898274, 90004325, 27625735, 74110882, 27665211, 91938191, 14363867, 4069912, 44479073, 68204242, 95726235, 47298834, 99524975, 14731700, 67030811, 98948034, 72357096, 70420215, 83269727, 31727379, 26744917, 53632373, 57240218, 50702367, 17385531, 21397057, 96318094, 45995325, 94076128, 97641116, 30163921, 5111370, 94911072, 88904910, 54517921, 99917599, 44481640, 40677414, 90310261, 13470059, 23432750, 22405120, 94516935, 14947650, 90272749, 80251430, 21533347, 69641513, 21001913, 72274002, 16445503, 72019362, 88251446, 3487592, 58224549, 3088684, 33895336, 66045587, 49328608, 508198, 82427263, 78602717, 81805959, 30694952, 60106217, 47792865, 14326617, 95010552, 70782102, 88444207, 67084351, 54014062, 49236559, 6157724, 14626618, 68128438, 15163258, 6153406, 35456853, 82178706, 22942635, 30891921, 76434144, 98648327, 52613508, 63059024, 569864, 23134715, 39171895, 51149804, 42307449, 16019925, 55615885, 62740044, 36845587, 91957544, 41380093, 96906410, 74724075, 44245960, 70367851, 79136082, 83747892, 18504197, 51426311, 41442762, 77787724, 52204879, 43376279, 54232247, 1204161, 9257405, 72373496, 51466049, 56484900, 1431742, 11757872, 38256849, 15536795, 57803235, 6986898, 40152546, 37891451, 50806615, 61897582, 53888755, 54987042, 11365791, 8696647, 45306070, 32274392, 39553046, 79191827, 32161669, 72725103, 35512853, 85242963, 79942022, 97281847, 44473167, 13173644, 88698958, 95581843, 28796059, 55470718, 26392416, 92398073, 94539824, 32699744, 24058273, 70596786, 40984766, 1569515, 44784505, 78561158, 7423788, 43045786, 17016156, 37675718, 94360702, 874791, 1239555, 15535065, 75986488, 16099750, 48658605, 7685448, 89214616, 63967300, 22766820, 6808825, 41245325, 67281495, 29466406, 37659250, 86798033, 87720882, 85571389, 16567550, 1375023, 59501487, 34497327, 36930650, 11581181, 33565483, 12664567, 84127901, 99515901, 48893685, 9398733, 67124282, 321665, 91281584, 43506672, 43501211, 31904591, 24733232, 76540481, 26139110, 75543508, 38510840, 68702632, 28787861, 42782652, 26114953, 23569917, 1788101, 17976208, 21070110, 62490109, 64087743, 89046466, 40027975, 8729146, 92554120, 75777973, 36685023, 48260151, 9175338, 81172706, 95957797, 85711894, 30811010, 82532312, 16684829, 99021067, 94711842, 62762857, 97783876, 37166608, 89078848, 94595668, 19457589, 95251277, 83368048, 93566986, 44842615, 36139942, 18001617, 78218845, 84840549, 26998766, 40865610, 96420429, 18466635, 97011160, 38658347, 21673260, 176257, 30090481, 33123618, 99729357, 2331773, 112651, 93562257, 24953498, 89699445, 40534591, 74614639, 9599614, 92803766, 86001008, 93359396, 37280276, 34698463, 80014588, 34432810, 16405341, 80316608, 79922758, 38341669, 83133790, 70004753 +168541, 71522968, 57393458, 60581278, 62428472, 4064751, 47738185, 40534591, 79191827, 44481640, 90013093, 6221471, 17365188, 24058273, 20765474, 66322959, 84002370, 71300104, 90654836, 51466049, 3294781, 46540998, 669105, 99125126, 45407418, 78218845, 69641513, 66250369, 21919959, 42237907, 10358899, 48675329, 55770687, 44844121, 19486173, 77377183, 33123618, 7300047, 72793444, 2331773, 62936963, 32590267, 6793819, 95957797, 16567550, 20002147, 66319530, 11581181, 44550764, 92353856, 526217, 19376156, 33336148, 57961282, 2208785, 4515343, 10309525, 22997281, 22879907, 62490109, 80246713, 13348726, 7423788, 31161687, 569864, 6111563, 16097038, 4204661, 3233569, 61741594, 38365584, 73124510, 98371444, 70372191, 29029316, 78766358, 16684829, 39201414, 73786944, 4668450, 30218878, 61982238, 37166608, 84187166, 45996863, 57240218, 5832946, 11365791, 19457589, 45075471, 26664538, 36139942, 92803766, 8115266, 90158785, 20642888, 83533741, 4434662, 33797252, 90272749, 94090109, 13173644, 92215320, 21001913, 11923835, 34698428, 66611759, 58224549, 3088684, 66667729, 53842979, 66045587, 96420429, 60430369, 7229550, 23569917, 1936762, 14093520, 95010552, 36468541, 1788101, 91990218, 30366150, 51590803, 61712234, 3633375, 73392814, 21673260, 99604946, 54263880, 44177011, 70727211, 24826575, 8129978, 73168565, 21289531, 86543538, 42199455, 11543098, 55615885, 80555751, 78909561, 48892201, 49597667, 15535065, 85116755, 16099750, 92604458, 96906410, 77694700, 74724075, 2204165, 38022834, 72373496, 40356877, 45919976, 18806856, 31491938, 78300864, 83083131, 824872, 96193415, 97783876, 41092102, 56955985, 96709982, 82726008, 96318094, 10597197, 90090964, 2917920, 9398733, 9886593, 39553046, 99917599, 24733232, 5267545, 84349107, 91240048, 83210802, 90310261, 27411561, 59371804, 22435353, 49598724, 30139692, 80251430, 84684495, 84840549, 28550822, 93270084, 3880712, 91831487, 85023028, 59981773, 84406788, 51472275, 70036438, 4199704, 36189527, 6157724, 18466635, 97011160, 15015906, 21070110, 38658347, 86460488, 3773993, 70004753, 12571310, 5482538, 67031644, 68000591, 61928316, 66116458, 86301513, 92554120, 47887470, 1022677, 60102965, 55189057, 82052050, 85117092, 26063929, 30653863, 65081429, 26507214, 19101477, 415901, 29188588, 29635537, 18411915, 112651, 12303248, 70367851, 7646095, 79136082, 6808825, 70541760, 7011964, 33699435, 71316369, 68644627, 52204879, 31126490, 16054533, 68316156, 54232247, 79806380, 68204242, 10264691, 47298834, 1375023, 99524975, 43152977, 1431742, 86242799, 56424103, 86022504, 24168411, 83269727, 31727379, 39986008, 77413012, 84127901, 76330843, 17727650, 30771409, 34946859, 16380211, 4985896, 22721500, 14220886, 72358170, 18833224, 15148031, 35512853, 99549373, 22405120, 70800879, 26102057, 35996293, 94516935, 75543508, 45617087, 18001617, 47361209, 17539625, 98462867, 19939935, 72274002, 90457870, 78785507, 58749, 91141395, 508198, 79922758, 62115552, 38494874, 98943869, 55470718, 75229462, 83150534, 36933038, 93515664, 15075176, 27325428, 30764367, 34236719, 38008118, 69605283, 35456853, 53648154, 22942635, 30891921, 40984766, 68110330, 38061439, 176257, 20867149, 31733363, 75135448, 65880522, 64157906, 44784505, 3235882, 95395112, 42644903, 38341669, 62051033, 92867155, 75777973, 61728685, 33553959, 94360702, 48088883, 26275734, 98653983, 40197395, 76671482, 52293995, 874791, 84904436, 46851987, 4798568, 35192533, 30463802, 66885828, 5073754, 27187213, 44245960, 47708850, 18663507, 51426311, 99965001, 4099191, 56473732, 47090124, 34044787, 89811711, 77787724, 71333116, 91664334, 88047921, 55143724, 41481685, 57359924, 72732205, 69355476, 27665211, 18699206, 91938191, 7687278, 86798033, 50188404, 9257405, 8733068, 12024238, 56484900, 60955663, 6819644, 99226875, 34497327, 9603598, 11757872, 62762857, 54606384, 36780454, 33201905, 45381876, 89078848, 36396314, 57803235, 40686254, 17385531, 44060493, 29819940, 54058788, 79821897, 37891451, 247198, 97641116, 53888755, 72278539, 69697787, 67124282, 56531125, 65017137, 29065964, 91281584, 36753250, 16387575, 71083565, 83368048, 82339363, 32161669, 51588015, 92787493, 17764950, 14947650, 45667668, 8634541, 57241521, 81855445, 13231279, 83133790, 17894977, 35092039, 17068582, 22450468, 26998766, 28928175, 75153252, 63372756, 65271999, 8055981, 9623492, 36312813, 28796059, 30998561, 96735716, 82427263, 26114953, 33628349, 26392416, 36580610, 13862149, 63628376, 37280276, 9829782, 4978543, 65454636, 11161731, 14626618, 68128438, 34493392, 15163258, 59614746, 84166196, 6525948, 14723410, 81853704, 67227442, 16595436, 32699744, 19898053, 15902805, 12416768, 30787683, 93790285, 40027975, 37224844, 75407347, 63059024, 10649306, 98739783, 13819358, 55850790, 29932657, 61263068, 37183543, 42967683, 66271566, 8913721, 99729357, 73617245, 72614359, 41380093, 92541302, 39847321, 57163802, 9188443, 99690194, 63967300, 33249630, 42692881, 37620363, 37501808, 93562257, 73109997, 18504197, 41245325, 67281495, 23740167, 55960386, 18783702, 50007421, 8791066, 99297164, 22113133, 59329511, 5970607, 2607799, 74357852, 36135, 7517032, 61859143, 27625735, 33935899, 65047700, 19272365, 14363867, 87720882, 94711842, 60393039, 55247455, 64055960, 67474219, 73021291, 72357096, 70420215, 57658654, 26744917, 50567636, 37192445, 8128637, 83155442, 7502255, 10453030, 32151165, 6871053, 94076128, 97057187, 73031054, 91255408, 2717150, 82897371, 48893685, 8696647, 62452034, 36812683, 65038678, 32274392, 10366309, 59109400, 85242963, 91727510, 93053405, 33431960, 3509435, 88698958, 26863229, 71965942, 38510840, 16445503, 93359396, 72019362, 61623915, 10961421, 28787861, 57248122, 3183975, 81805959, 78549759, 9259676, 8651647, 60106217, 95290172, 61271144, 44915235, 77272628, 81677380, 62430984, 69848388, 20681921, 65715134, 6153406, 45794415, 70596786, 89499542, 20486294, 82178706, 81898046, 24619760, 87598888, 15064655, 48360198, 76434144, 30090481, 98648327, 45990383, 47213183, 92071990, 69255765, 30569392, 43045786, 39801351, 17016156, 29959549, 89804152, 34698463, 76703609, 63506281, 62740044, 7955293, 1239555, 45428665, 9175338, 51047803, 32426752, 42073124, 92998591, 22766820, 63152504, 25636669, 17146629, 58751351, 43376279, 96726697, 7182642, 97379790, 16424199, 49641577, 82651278, 52060076, 20122224, 30811010, 92692978, 49271185, 44479073, 50668599, 83302115, 37435892, 24953498, 20645197, 74441448, 40781854, 14731700, 59501487, 5822202, 66663942, 36930650, 99333375, 17037369, 67513640, 45848907, 12664567, 53632373, 50702367, 17386996, 52261574, 99224392, 71657078, 34432810, 94595668, 50806615, 30163921, 6521313, 74743862, 68694897, 321665, 61141874, 61960400, 44842615, 38645117, 83651211, 72725103, 4787945, 4806458, 16405341, 9058407, 76540481, 2891150, 80316608, 29516592, 8373289, 3233084, 33304202, 43357947, 8099994, 51715482, 88251446, 99658235, 40865610, 53084256, 3487592, 7104732, 44664587, 95581843, 79657802, 33895336, 49328608, 42782652, 91802888, 6038457, 74137926, 51315460, 20568363, 75052463, 66903004, 81274566, 89419466, 88444207, 67084351, 47893286, 17976208, 9860195, 34667286, 54663246, 64098930, 98130363, 17857111, 7919588, 53547802, 53393358, 78884452, 73222868, 62232211, 8807940, 38009615, 30395570, 61373987, 83789391, 79738755, 8729146, 76170907, 55753905, 64602895, 78589145, 22108665, 9575954, 5640302, 82886381, 77301523, 93933709, 58180653, 30543215, 36685023, 84293052, 36505482, 37560164, 92692283, 7685448, 89214616, 69786756, 6505939, 83747892, 7066775, 86118021, 39373729, 29466406, 29834512, 27041967, 32058615, 77898274, 90004325, 4091162, 24915585, 82532312, 24314885, 1204161, 56153345, 85571389, 20140249, 74452589, 38256849, 87710366, 89699445, 33565483, 15536795, 1250437, 46870723, 82779622, 45995325, 40152546, 99971982, 67793644, 61897582, 56515456, 5111370, 44627776, 45049308, 43501211, 54517921, 92530431, 82327024, 93057697, 69136837, 60176618, 41092172, 48673079, 44473167, 69579137, 17957593, 27185644, 87160386, 48395186, 88653118, 68702632, 30694952, 42947632, 26397786, 749283, 28734791, 67451935, 37957788, 59405277, 20885148, 15948937, 32159704, 64087743, 89046466, 1569515, 88130087, 62552524, 52613508, 78561158, 37675718, 89217461, 82979980, 23134715, 43933006, 51149804, 42307449, 16019925, 54427233, 51507425, 80014588, 79880247, 36845587, 37739481, 83948335, 91957544, 23110625, 34295794, 75986488, 48658605, 59177718, 12348118, 36808486, 56461322, 33237508, 85711894, 14045383, 59910624, 81774825, 40781449, 28851716, 4119747, 71920426, 74110882, 66428795, 65074535, 63015256, 23194618, 29994197, 77284619, 62496012, 77072625, 79322415, 26292919, 22129328, 59318837, 65851721, 89637706, 43506672, 61859581, 9599614, 76825057, 13470059, 79942022, 97940276, 68041839, 97281847, 97561745, 19891772, 58208470, 75820087, 44348328, 96852321, 86001008, 25842078, 76315420, 68102437, 62357986, 45237957, 54199160, 26776922, 9860968, 47792865, 22200378, 32250045, 90061527, 65338021, 21993752, 68875490, 65275241, 62803858, 39171895, 53666583, 44847298, 61815223, 72738685, 54868730, 81172706, 71469330, 51464002, 4508700, 37659250, 76960303, 72238278, 17081350, 21397057, 66832478, 45306070, 62693428, 44846932, 88904910, 77300457, 95251277, 74614639, 31904591, 68824981, 40677414, 66137019, 45790169, 26493119, 29510992, 21533347, 54762643, 73235980, 55602660, 78602717, 68816413, 14326617, 6497830, 72495719, 89996536, 1197320, 20836893, 99861373, 59197747, 17058722, 77620120, 44889423, 18131876, 72777973, 95726235, 67030811, 77187825, 55669657, 99515901, 86821229, 44983451, 93566986, 23848565, 23432750, 49882705, 75128745, 54014062, 49236559, 64848072, 53802686, 94539824, 85695762, 33061250, 77312810, 48260151, 13468268, 43322743, 16971929, 99021067, 98948034, 68939068, 48774913, 54987042, 94911072, 57020481, 4095116, 26139110, 70782102, 26734892, 92033260, 4069912, 29401781, 6986898, 92398073, 24591705, 41442762, 14349098 +37560164, 65047700, 35512853, 79922758, 61928316, 23110625, 24733232, 4787945, 3509435, 40865610, 75052463, 95010552, 17976208, 8807940, 13348726, 78561158, 55189057, 73617245, 48892201, 92692283, 4099191, 38022834, 85711894, 69355476, 68204242, 51466049, 83302115, 12024238, 64055960, 33201905, 37192445, 54987042, 17764950, 49598724, 78785507, 26114953, 53802686, 34236719, 69605283, 44844121, 33061250, 37224844, 92554120, 77312810, 42199455, 37739481, 29188588, 98371444, 71333116, 63015256, 44479073, 94711842, 20645197, 73021291, 74452589, 86022504, 11581181, 45996863, 57803235, 96318094, 90090964, 69697787, 56531125, 44846932, 44627776, 59109400, 4806458, 35996293, 80316608, 90272749, 13173644, 29510992, 22450468, 54762643, 66250369, 82427263, 4515343, 81274566, 47792865, 63628376, 9860195, 92398073, 54663246, 73392814, 22879907, 21673260, 24619760, 64602895, 76434144, 67031644, 52613508, 92071990, 65275241, 39171895, 30543215, 52293995, 34698463, 16019925, 65081429, 78909561, 57163802, 12348118, 29029316, 37620363, 93562257, 56461322, 47090124, 17146629, 59329511, 29466406, 24915585, 54232247, 56153345, 72238278, 99524975, 24953498, 824872, 37166608, 62428472, 53632373, 17081350, 6871053, 10597197, 30163921, 82897371, 56515456, 72358170, 61141874, 39553046, 93057697, 91240048, 45407418, 70800879, 75543508, 45617087, 80251430, 69641513, 84684495, 49882705, 10961421, 3183975, 78602717, 23569917, 30694952, 61271144, 84406788, 48675329, 4199704, 36189527, 44915235, 67451935, 4978543, 15075176, 51590803, 38008118, 15015906, 67227442, 38658347, 73222868, 22942635, 54263880, 20867149, 55753905, 9575954, 44784505, 8129978, 5640302, 98739783, 20765474, 73168565, 21289531, 1022677, 37675718, 72793444, 98653983, 58180653, 76703609, 84904436, 84002370, 35192533, 19101477, 6793819, 9188443, 96906410, 12303248, 18663507, 36808486, 41245325, 51426311, 56473732, 25636669, 58751351, 39373729, 71316369, 52204879, 91664334, 31126490, 52060076, 30811010, 27625735, 71920426, 92033260, 76960303, 23194618, 40356877, 60955663, 67474219, 61982238, 34497327, 41092102, 50567636, 67513640, 45848907, 17386996, 48774913, 59318837, 37891451, 168541, 669105, 44983451, 6521313, 2917920, 94911072, 36812683, 43506672, 45075471, 82339363, 19376156, 92803766, 23432750, 92787493, 41092172, 79942022, 97940276, 66137019, 91727510, 93053405, 4434662, 68041839, 13231279, 75820087, 25842078, 90013093, 8099994, 16445503, 93359396, 72019362, 84840549, 26998766, 88251446, 28550822, 75153252, 65271999, 3088684, 95581843, 3880712, 508198, 96420429, 55602660, 62115552, 78549759, 20568363, 66903004, 60106217, 42947632, 1788101, 36580610, 57393458, 93515664, 28734791, 32250045, 72495719, 30764367, 98130363, 26734892, 16595436, 65715134, 3633375, 53547802, 70596786, 21993752, 35456853, 30891921, 40984766, 99604946, 89046466, 61373987, 87598888, 24826575, 5482538, 77377183, 75407347, 63059024, 31161687, 92867155, 93933709, 16097038, 17058722, 82052050, 76671482, 42307449, 63506281, 66322959, 874791, 79880247, 30653863, 26507214, 7955293, 83948335, 29635537, 9175338, 16099750, 51047803, 42073124, 44245960, 47708850, 81172706, 73109997, 63152504, 55960386, 33699435, 8791066, 29834512, 43376279, 74357852, 18699206, 66428795, 4069912, 39201414, 47298834, 55247455, 78300864, 83083131, 66319530, 77284619, 30218878, 77072625, 79322415, 77187825, 54606384, 24168411, 31727379, 56955985, 84127901, 96709982, 17385531, 99515901, 82726008, 99224392, 4064751, 5832946, 7502255, 46540998, 79821897, 97057187, 50806615, 66832478, 2717150, 74743862, 92353856, 67124282, 62693428, 65017137, 36753250, 18833224, 32274392, 79191827, 99917599, 26664538, 32161669, 10366309, 5267545, 84349107, 83210802, 60176618, 26102057, 27411561, 59371804, 22435353, 97281847, 44473167, 94090109, 47361209, 98462867, 26863229, 3233084, 76315420, 87160386, 61623915, 48395186, 73235980, 44664587, 93270084, 66667729, 36312813, 28796059, 53842979, 60430369, 81805959, 1936762, 89419466, 83150534, 88444207, 64848072, 15948937, 22997281, 34493392, 62430984, 89996536, 84166196, 20486294, 53648154, 81898046, 38061439, 30395570, 1569515, 176257, 76170907, 65880522, 64157906, 30090481, 68875490, 10649306, 42644903, 38341669, 62051033, 75777973, 48088883, 3233569, 26275734, 8913721, 80014588, 84293052, 62936963, 30463802, 92541302, 39847321, 71300104, 89214616, 99690194, 70372191, 43322743, 42692881, 2204165, 37501808, 99965001, 86118021, 33237508, 68644627, 77787724, 18131876, 55143724, 49641577, 81774825, 7517032, 82651278, 57359924, 4119747, 82532312, 27665211, 1204161, 65074535, 50668599, 16567550, 1431742, 4668450, 11757872, 55669657, 17037369, 36396314, 57240218, 76330843, 46870723, 29819940, 71657078, 99971982, 4985896, 11365791, 48893685, 9398733, 5111370, 91281584, 54517921, 83368048, 82327024, 69136837, 16405341, 14947650, 33431960, 21533347, 17957593, 71965942, 38510840, 21001913, 33304202, 35092039, 43357947, 53084256, 34698428, 7104732, 33895336, 42782652, 91802888, 74137926, 38494874, 98943869, 9860968, 85023028, 95290172, 36468541, 26397786, 26392416, 67084351, 91990218, 13862149, 22200378, 10309525, 18466635, 34667286, 27325428, 77272628, 81677380, 15163258, 32699744, 65338021, 45794415, 62232211, 62490109, 80246713, 30787683, 31733363, 99861373, 93790285, 12571310, 59197747, 19486173, 78589145, 60581278, 33123618, 77301523, 37183543, 42967683, 7300047, 53666583, 36685023, 66271566, 26063929, 13468268, 55615885, 46851987, 44847298, 38365584, 41380093, 77620120, 34295794, 66885828, 71469330, 23740167, 18783702, 99297164, 22113133, 27041967, 59910624, 36135, 37659250, 92692978, 24314885, 86798033, 16684829, 50188404, 72777973, 9257405, 45919976, 95726235, 60393039, 37435892, 86242799, 14731700, 67030811, 98948034, 56424103, 87710366, 36780454, 83269727, 89699445, 33565483, 12664567, 1250437, 52261574, 17727650, 82779622, 45995325, 94076128, 94595668, 65851721, 67793644, 72278539, 22721500, 14220886, 88904910, 45049308, 16387575, 23848565, 36139942, 8115266, 90158785, 83651211, 51588015, 76540481, 33336148, 26493119, 97561745, 29516592, 8373289, 57241521, 19891772, 81855445, 92215320, 96852321, 27185644, 17894977, 90457870, 68102437, 28928175, 91141395, 62357986, 58224549, 8055981, 45237957, 68702632, 79657802, 66045587, 6038457, 51315460, 59981773, 14326617, 70782102, 30366150, 10358899, 49236559, 20885148, 17365188, 97011160, 94539824, 6525948, 1197320, 7919588, 20836893, 24591705, 55770687, 85695762, 21070110, 38009615, 12416768, 86460488, 64087743, 70727211, 8729146, 22108665, 62552524, 45990383, 3235882, 43045786, 55850790, 61263068, 569864, 23134715, 86543538, 43933006, 60102965, 4204661, 40197395, 11543098, 48260151, 61815223, 61741594, 415901, 91957544, 32590267, 73124510, 72738685, 75986488, 7685448, 112651, 63967300, 74724075, 70367851, 7646095, 51464002, 6808825, 70541760, 89811711, 95957797, 16971929, 7182642, 16054533, 68316156, 90004325, 40781449, 74110882, 91938191, 19272365, 87720882, 18806856, 1375023, 74441448, 72357096, 20140249, 96193415, 70420215, 97783876, 99333375, 84187166, 68939068, 26744917, 45381876, 15536795, 89078848, 40686254, 22129328, 54058788, 32151165, 40152546, 34432810, 97641116, 86821229, 91255408, 89637706, 99125126, 19457589, 526217, 77300457, 95251277, 71083565, 92530431, 31904591, 15148031, 44842615, 9599614, 90310261, 38645117, 20642888, 48673079, 85242963, 2891150, 26139110, 69579137, 58208470, 44348328, 88698958, 86001008, 72274002, 99658235, 63372756, 6221471, 9623492, 28787861, 54199160, 30998561, 33628349, 8651647, 91831487, 14093520, 42237907, 47893286, 749283, 51472275, 59405277, 6157724, 11161731, 90061527, 68128438, 32159704, 14723410, 17857111, 61712234, 81853704, 78884452, 89499542, 44177011, 70004753, 79738755, 75135448, 48360198, 88130087, 47213183, 66116458, 39801351, 62803858, 29932657, 82886381, 54427233, 2331773, 72614359, 49597667, 15535065, 45428665, 92604458, 69786756, 92998591, 33249630, 22766820, 77694700, 6505939, 79136082, 18504197, 7066775, 7011964, 34044787, 96726697, 78766358, 88047921, 16424199, 4091162, 90654836, 79806380, 33935899, 7687278, 99021067, 72373496, 85571389, 56484900, 40781854, 62496012, 59501487, 66663942, 9603598, 38256849, 14349098, 34946859, 40534591, 247198, 16380211, 44550764, 321665, 29065964, 57020481, 65038678, 44481640, 61859581, 72725103, 22405120, 4095116, 94516935, 45790169, 8634541, 17539625, 17068582, 75128745, 3487592, 66611759, 88653118, 96735716, 7229550, 68816413, 55470718, 75229462, 21919959, 36933038, 70036438, 14626618, 69848388, 24058273, 19898053, 15902805, 3773993, 83789391, 40027975, 98648327, 68000591, 69255765, 30569392, 61728685, 89217461, 94360702, 51149804, 85117092, 51507425, 1239555, 18411915, 54868730, 32426752, 27187213, 83747892, 67281495, 50007421, 4508700, 5970607, 77898274, 49271185, 14363867, 29401781, 29994197, 73786944, 10264691, 8733068, 20002147, 6819644, 5822202, 57658654, 62762857, 77413012, 50702367, 47738185, 21397057, 44060493, 62452034, 9886593, 43501211, 61960400, 40677414, 9058407, 18001617, 30139692, 19939935, 83133790, 51715482, 57248122, 54014062, 9829782, 37957788, 59614746, 64098930, 53393358, 86301513, 7423788, 82979980, 6111563, 89804152, 99729357, 4798568, 36505482, 85116755, 48658605, 59177718, 44889423, 71522968, 14045383, 41481685, 61859143, 20122224, 72732205, 3294781, 99226875, 8128637, 26292919, 6986898, 30771409, 10453030, 53888755, 8696647, 74614639, 68824981, 83533741, 78218845, 58749, 9259676, 37280276, 6497830, 20681921, 6153406, 82178706, 95395112, 47887470, 33553959, 62740044, 36845587, 5073754, 41442762, 97379790, 32058615, 28851716, 36930650, 39986008, 73031054, 68694897, 45306070, 13470059, 33797252, 57961282, 26776922, 49328608, 15064655, 29959549, 2607799, 31491938, 43152977, 83155442, 61897582, 93566986, 76825057, 99549373, 11923835, 2208785, 65454636, 68110330, 13819358, 80555751, 17016156, 45667668 +93359396, 15075176, 65081429, 91255408, 67124282, 59371804, 18001617, 57248122, 38494874, 37957788, 31733363, 18131876, 30163921, 13470059, 27185644, 95010552, 27325428, 89046466, 92554120, 84904436, 37739481, 6505939, 22113133, 57359924, 71920426, 56955985, 6521313, 23432750, 17764950, 90457870, 40865610, 3633375, 93790285, 45990383, 33553959, 23134715, 98653983, 92604458, 71333116, 27041967, 74357852, 63015256, 39201414, 7502255, 99125126, 61141874, 61859581, 83210802, 72725103, 16405341, 26493119, 13231279, 25842078, 71965942, 72274002, 28928175, 53084256, 65271999, 45237957, 26114953, 6157724, 92398073, 15163258, 17857111, 80246713, 3773993, 30787683, 99861373, 3235882, 65275241, 86543538, 6111563, 4204661, 55189057, 13468268, 51507425, 1239555, 37560164, 41380093, 92692283, 6793819, 96906410, 5073754, 12348118, 74724075, 44245960, 81172706, 38022834, 7182642, 49641577, 27625735, 37659250, 50668599, 72238278, 1375023, 64055960, 67030811, 66663942, 61982238, 97783876, 24168411, 89699445, 33565483, 17037369, 37192445, 45996863, 84127901, 17081350, 46870723, 17727650, 30771409, 54987042, 44983451, 82897371, 44846932, 36812683, 77300457, 99917599, 15148031, 59109400, 76825057, 70800879, 57241521, 21533347, 43357947, 76315420, 28550822, 6221471, 28796059, 79657802, 42782652, 508198, 7229550, 1936762, 66903004, 14093520, 75229462, 36933038, 26397786, 64848072, 749283, 93515664, 34493392, 54663246, 7919588, 81853704, 53547802, 21673260, 44177011, 83789391, 5482538, 30090481, 78561158, 43045786, 98739783, 55850790, 82886381, 48088883, 89804152, 40197395, 874791, 30653863, 83948335, 71300104, 89214616, 112651, 92998591, 22766820, 77694700, 37620363, 18783702, 77787724, 29466406, 31126490, 77898274, 52060076, 92692978, 82532312, 27665211, 18699206, 14363867, 4069912, 99524975, 74441448, 40781854, 20002147, 41092102, 67513640, 57240218, 14349098, 47738185, 71657078, 37891451, 65851721, 73031054, 53888755, 72278539, 65017137, 45049308, 71083565, 92530431, 93566986, 92803766, 90310261, 38645117, 48673079, 68041839, 44473167, 8373289, 94090109, 47361209, 17957593, 38510840, 22450468, 26998766, 75153252, 73235980, 44664587, 23569917, 51315460, 68816413, 95290172, 70782102, 88444207, 67084351, 42237907, 84406788, 22200378, 4199704, 44915235, 67451935, 11161731, 72495719, 89996536, 59614746, 98130363, 26734892, 24591705, 16595436, 70596786, 69605283, 62232211, 61373987, 70004753, 70727211, 88130087, 76434144, 9575954, 92071990, 7423788, 13819358, 38341669, 1022677, 82979980, 16097038, 39171895, 60102965, 72793444, 52293995, 76703609, 99729357, 26063929, 55615885, 26507214, 44847298, 80555751, 91957544, 77620120, 23110625, 45428665, 29635537, 70372191, 18663507, 7646095, 79136082, 99965001, 50007421, 5970607, 91664334, 88047921, 55143724, 85711894, 14045383, 59910624, 32058615, 68316156, 82651278, 61859143, 4091162, 54232247, 33935899, 56153345, 29994197, 40356877, 45919976, 94711842, 47298834, 31491938, 1431742, 6819644, 3294781, 59501487, 96193415, 36930650, 62762857, 74452589, 54606384, 87710366, 31727379, 6986898, 83155442, 21397057, 6871053, 2717150, 2917920, 62693428, 94911072, 19457589, 526217, 95251277, 43506672, 32274392, 83368048, 44481640, 26664538, 32161669, 82327024, 44842615, 91240048, 26102057, 85242963, 27411561, 93053405, 33797252, 97561745, 90272749, 78218845, 19891772, 58208470, 3509435, 44348328, 69641513, 21001913, 88653118, 62357986, 8055981, 9623492, 28787861, 4515343, 60430369, 6038457, 3183975, 20568363, 47792865, 26392416, 30366150, 49236559, 9829782, 47893286, 28734791, 17976208, 32250045, 90061527, 84166196, 1197320, 24058273, 53393358, 38658347, 22879907, 99604946, 13348726, 68000591, 22108665, 86301513, 39801351, 20765474, 75777973, 60581278, 37675718, 42967683, 94360702, 3233569, 26275734, 17058722, 82052050, 76671482, 34698463, 16019925, 79880247, 2331773, 62936963, 36505482, 35192533, 7955293, 34295794, 85116755, 48658605, 54868730, 71469330, 70541760, 86118021, 56473732, 33699435, 17146629, 99297164, 39373729, 33237508, 95957797, 29834512, 16971929, 2607799, 96726697, 16054533, 49271185, 1204161, 19272365, 86798033, 66428795, 83302115, 83083131, 4668450, 98948034, 73021291, 70420215, 77072625, 36780454, 83269727, 11581181, 84187166, 45381876, 8128637, 77413012, 89078848, 40686254, 99515901, 52261574, 22129328, 82779622, 44060493, 79821897, 247198, 97057187, 67793644, 66832478, 669105, 11365791, 69697787, 5111370, 321665, 89637706, 62452034, 44627776, 18833224, 43501211, 54517921, 82339363, 24733232, 9599614, 84349107, 90158785, 83651211, 35512853, 51588015, 20642888, 75543508, 33336148, 45667668, 45617087, 30139692, 69579137, 17539625, 26863229, 86001008, 33304202, 35092039, 8099994, 87160386, 72019362, 75128745, 66667729, 3880712, 33895336, 66045587, 96735716, 96420429, 74137926, 75052463, 98943869, 81274566, 42947632, 61271144, 57393458, 54014062, 10358899, 63628376, 37280276, 70036438, 4978543, 9860195, 20885148, 10309525, 53802686, 18466635, 97011160, 68128438, 14723410, 20836893, 15015906, 89499542, 73222868, 8807940, 38009615, 86460488, 30395570, 54263880, 176257, 20867149, 64602895, 61928316, 62552524, 75407347, 66116458, 63059024, 68875490, 31161687, 92867155, 29932657, 21289531, 47887470, 569864, 7300047, 85117092, 48260151, 84293052, 84002370, 72614359, 36845587, 19101477, 38365584, 32590267, 29188588, 9175338, 9188443, 99690194, 63967300, 33249630, 43322743, 42692881, 27187213, 29029316, 44889423, 47708850, 70367851, 36808486, 23740167, 47090124, 4508700, 41442762, 71316369, 43376279, 41481685, 36135, 20122224, 24915585, 69355476, 76960303, 68204242, 29401781, 10264691, 95726235, 18806856, 56484900, 37435892, 20645197, 78300864, 14731700, 67474219, 20140249, 34497327, 9603598, 77187825, 33201905, 26744917, 36396314, 53632373, 96709982, 82726008, 4064751, 29819940, 46540998, 54058788, 32151165, 45995325, 168541, 86821229, 14220886, 44550764, 48893685, 8696647, 68694897, 56515456, 72358170, 88904910, 91281584, 57020481, 36753250, 61960400, 79191827, 68824981, 93057697, 23848565, 5267545, 36139942, 4787945, 4806458, 41092172, 76540481, 79942022, 35996293, 97940276, 66137019, 91727510, 26139110, 22435353, 49598724, 97281847, 57961282, 84684495, 88698958, 96852321, 3233084, 90013093, 17894977, 17068582, 16445503, 51715482, 49882705, 88251446, 11923835, 63372756, 66611759, 54762643, 58224549, 3088684, 54199160, 79922758, 91802888, 78602717, 78549759, 8651647, 9860968, 85023028, 60106217, 89419466, 36580610, 13862149, 6497830, 59405277, 81677380, 14626618, 32159704, 6525948, 61712234, 67227442, 45794415, 78884452, 21993752, 30891921, 40984766, 64087743, 24619760, 76170907, 55753905, 19486173, 24826575, 48360198, 64157906, 77377183, 52613508, 47213183, 69255765, 42644903, 73168565, 29959549, 37183543, 42199455, 51149804, 73617245, 46851987, 61741594, 415901, 49597667, 92541302, 15535065, 75986488, 39847321, 57163802, 18411915, 69786756, 42073124, 12303248, 66885828, 2204165, 37501808, 73109997, 63152504, 51464002, 7066775, 55960386, 8791066, 58751351, 90004325, 30811010, 28851716, 72732205, 74110882, 92033260, 23194618, 85571389, 16567550, 12024238, 55247455, 77284619, 5822202, 55669657, 38256849, 86022504, 68939068, 50567636, 39986008, 45848907, 50702367, 17385531, 48774913, 40534591, 96318094, 34432810, 50806615, 61897582, 74743862, 92353856, 9398733, 74614639, 39553046, 10366309, 60176618, 9058407, 94516935, 80316608, 4434662, 33431960, 61623915, 53842979, 49328608, 2208785, 81805959, 33628349, 9259676, 91831487, 36468541, 51472275, 77272628, 62430984, 94539824, 34236719, 38008118, 69848388, 65715134, 55770687, 85695762, 20486294, 38061439, 87598888, 79738755, 78589145, 44784505, 8129978, 62803858, 61728685, 61263068, 77301523, 93933709, 43933006, 30543215, 36685023, 66322959, 80014588, 62740044, 4798568, 30463802, 48892201, 73124510, 72738685, 16099750, 51047803, 93562257, 67281495, 51426311, 4099191, 25636669, 89811711, 68644627, 97379790, 16424199, 81774825, 90654836, 91938191, 65047700, 16684829, 50188404, 99021067, 72777973, 72373496, 60393039, 24953498, 86242799, 99226875, 56424103, 62428472, 99333375, 12664567, 57803235, 76330843, 99224392, 10453030, 94076128, 94595668, 99971982, 22721500, 56531125, 29065964, 16387575, 65038678, 31904591, 69136837, 40677414, 8115266, 45407418, 4095116, 14947650, 2891150, 80251430, 13173644, 92215320, 75820087, 98462867, 19939935, 83133790, 99658235, 3487592, 10961421, 91141395, 93270084, 66250369, 36312813, 95581843, 30694952, 55470718, 14326617, 21919959, 15948937, 22997281, 30764367, 20681921, 32699744, 82178706, 81898046, 22942635, 44844121, 68110330, 33061250, 1569515, 75135448, 65880522, 5640302, 62051033, 33123618, 53666583, 58180653, 66271566, 8913721, 42307449, 63506281, 61815223, 98371444, 6808825, 56461322, 59329511, 52204879, 78766358, 7517032, 4119747, 24314885, 65074535, 44479073, 73786944, 51466049, 30218878, 824872, 62496012, 11757872, 37166608, 15536795, 17386996, 1250437, 34946859, 59318837, 40152546, 16380211, 10597197, 97641116, 4985896, 9886593, 19376156, 99549373, 92787493, 83533741, 45790169, 29516592, 8634541, 78785507, 58749, 7104732, 68702632, 30998561, 55602660, 59981773, 83150534, 91990218, 48675329, 36189527, 65454636, 65338021, 6153406, 19898053, 53648154, 15902805, 12416768, 40027975, 37224844, 8729146, 12571310, 59197747, 67031644, 98648327, 30569392, 95395112, 89217461, 54427233, 7685448, 59177718, 32426752, 71522968, 41245325, 34044787, 79806380, 7687278, 9257405, 8733068, 43152977, 66319530, 57658654, 79322415, 5832946, 45306070, 45075471, 22405120, 68102437, 26776922, 82427263, 34667286, 21070110, 62490109, 15064655, 17016156, 11543098, 18504197, 40781449, 87720882, 60955663, 29510992, 84840549, 34698428, 48395186, 1788101, 51590803, 64098930, 73392814, 10649306, 77312810, 26292919, 81855445, 62115552, 17365188, 35456853, 78909561, 83747892, 7011964, 72357096, 90090964 +78785507, 54606384, 65275241, 3233569, 70372191, 78766358, 5832946, 54058788, 50806615, 9886593, 30139692, 40865610, 15163258, 85695762, 80246713, 52613508, 569864, 85116755, 48658605, 112651, 91664334, 16054533, 27665211, 11365791, 49598724, 8099994, 84840549, 63372756, 22997281, 34493392, 20486294, 176257, 78561158, 33123618, 54427233, 874791, 415901, 15535065, 71522968, 56461322, 31126490, 68316156, 7517032, 76960303, 45919976, 1375023, 20002147, 61982238, 36930650, 97783876, 26292919, 83155442, 17764950, 45667668, 17894977, 34698428, 28787861, 85023028, 27325428, 16595436, 19898053, 30891921, 99861373, 55753905, 77377183, 20765474, 77301523, 86543538, 39171895, 17058722, 42199455, 82052050, 48892201, 39847321, 89214616, 92998591, 29029316, 71333116, 82532312, 96193415, 62428472, 50702367, 48893685, 45306070, 89637706, 36812683, 19457589, 24733232, 23848565, 36139942, 19376156, 83210802, 83533741, 14947650, 91727510, 26139110, 29516592, 8373289, 78218845, 21001913, 62357986, 58224549, 3088684, 2208785, 7229550, 74137926, 55470718, 75229462, 83150534, 26397786, 17976208, 32250045, 72495719, 94539824, 34236719, 26734892, 65715134, 6153406, 55770687, 1569515, 5482538, 76434144, 64157906, 67031644, 13348726, 62552524, 8129978, 7423788, 68875490, 42644903, 29932657, 61263068, 77312810, 30543215, 66322959, 79880247, 84904436, 80555751, 1239555, 32590267, 45428665, 18663507, 79136082, 74357852, 32058615, 81774825, 79806380, 24314885, 85571389, 39201414, 95726235, 94711842, 47298834, 37435892, 55247455, 5822202, 62762857, 37166608, 99515901, 82726008, 46870723, 4064751, 47738185, 29819940, 40152546, 94595668, 73031054, 66832478, 44983451, 69697787, 8696647, 67124282, 44846932, 65038678, 45075471, 83368048, 99917599, 82339363, 68824981, 44481640, 15148031, 93057697, 4787945, 48673079, 33336148, 17539625, 19939935, 28928175, 28550822, 53084256, 91141395, 88653118, 45237957, 95581843, 30998561, 9259676, 60106217, 1788101, 30366150, 70036438, 15075176, 14626618, 81853704, 3633375, 12416768, 86460488, 64087743, 70004753, 15064655, 59197747, 64602895, 3235882, 92867155, 73168565, 33553959, 7300047, 4204661, 58180653, 85117092, 26063929, 80014588, 55615885, 46851987, 78909561, 61815223, 72738685, 75986488, 51047803, 99690194, 59177718, 32426752, 33249630, 42692881, 66885828, 77694700, 27187213, 71469330, 7066775, 18783702, 29466406, 16971929, 54232247, 71920426, 91938191, 49271185, 1204161, 86798033, 66428795, 14363867, 4069912, 16684829, 29994197, 16567550, 12024238, 14731700, 30218878, 66663942, 34497327, 57658654, 83269727, 33565483, 50567636, 67513640, 40686254, 76330843, 71657078, 45995325, 10597197, 82897371, 5111370, 56531125, 321665, 526217, 18833224, 61960400, 26664538, 9599614, 5267545, 91240048, 92803766, 40677414, 13470059, 99549373, 4806458, 22405120, 66137019, 33797252, 8634541, 33431960, 13173644, 19891772, 69579137, 13231279, 57961282, 3233084, 76315420, 16445503, 61623915, 75153252, 10961421, 66611759, 44664587, 68702632, 28796059, 53842979, 49328608, 82427263, 6038457, 81805959, 78549759, 8651647, 68816413, 42947632, 61271144, 88444207, 26392416, 42237907, 57393458, 37280276, 9829782, 4199704, 36189527, 44915235, 67451935, 9860195, 6157724, 77272628, 81677380, 59614746, 64098930, 24058273, 53393358, 73392814, 8807940, 68110330, 99604946, 38061439, 24619760, 54263880, 61373987, 20867149, 83789391, 79738755, 76170907, 75135448, 48360198, 65880522, 78589145, 22108665, 45990383, 92071990, 69255765, 43045786, 38341669, 1022677, 89217461, 51149804, 65081429, 37739481, 35192533, 38365584, 73124510, 29188588, 6793819, 9175338, 69786756, 63967300, 12303248, 12348118, 47708850, 2204165, 18504197, 67281495, 33699435, 99297164, 71316369, 18131876, 97379790, 16424199, 36135, 77898274, 82651278, 24915585, 37659250, 92692978, 40356877, 51466049, 18806856, 83302115, 8733068, 99524975, 1431742, 86242799, 4668450, 67474219, 38256849, 86022504, 87710366, 17037369, 37192445, 45848907, 45381876, 8128637, 57240218, 6986898, 1250437, 52261574, 99224392, 17727650, 82779622, 34946859, 79821897, 16380211, 67793644, 168541, 97641116, 61897582, 669105, 86821229, 68694897, 2917920, 9398733, 56515456, 94911072, 29065964, 91281584, 16387575, 77300457, 95251277, 43501211, 54517921, 39553046, 31904591, 82327024, 61859581, 44842615, 69136837, 90310261, 76825057, 35512853, 45407418, 41092172, 59371804, 80316608, 22435353, 4434662, 26493119, 97561745, 90272749, 57241521, 69641513, 88698958, 26863229, 86001008, 90013093, 72274002, 90457870, 99658235, 54199160, 96420429, 60430369, 57248122, 62115552, 26114953, 33628349, 1936762, 59981773, 22200378, 51472275, 48675329, 28734791, 65454636, 11161731, 92398073, 10309525, 15948937, 97011160, 68128438, 30764367, 54663246, 1197320, 53547802, 38658347, 62232211, 81898046, 44844121, 15902805, 30395570, 63059024, 13819358, 17016156, 29959549, 42967683, 94360702, 98653983, 76671482, 66271566, 8913721, 76703609, 13468268, 30653863, 2331773, 62740044, 72614359, 36845587, 62936963, 36505482, 30463802, 41380093, 77620120, 92541302, 16099750, 71300104, 92604458, 96906410, 22766820, 99965001, 17146629, 4508700, 58751351, 22113133, 77787724, 29834512, 96726697, 88047921, 57359924, 20122224, 30811010, 90654836, 27625735, 65047700, 72777973, 73786944, 10264691, 60393039, 43152977, 40781854, 66319530, 62496012, 77072625, 9603598, 11757872, 36780454, 68939068, 39986008, 45996863, 84127901, 57803235, 22129328, 44060493, 7502255, 32151165, 59318837, 6871053, 34432810, 97057187, 30163921, 2717150, 88904910, 44627776, 36753250, 93566986, 90158785, 23432750, 72725103, 20642888, 9058407, 70800879, 85242963, 94516935, 2891150, 75543508, 97281847, 47361209, 81855445, 44348328, 25842078, 71965942, 35092039, 93359396, 6221471, 7104732, 8055981, 9623492, 66250369, 33895336, 26776922, 66045587, 4515343, 79922758, 51315460, 75052463, 81274566, 21919959, 95290172, 95010552, 36933038, 36580610, 54014062, 10358899, 20885148, 51590803, 32159704, 38008118, 6525948, 69848388, 24591705, 61712234, 45794415, 78884452, 21993752, 73222868, 22879907, 38009615, 30787683, 70727211, 88130087, 61928316, 75407347, 30569392, 66116458, 10649306, 98739783, 39801351, 95395112, 31161687, 62051033, 55850790, 60581278, 21289531, 47887470, 6111563, 53666583, 36685023, 99729357, 84002370, 26507214, 83948335, 92692283, 7685448, 5073754, 7646095, 50007421, 47090124, 7011964, 2607799, 85711894, 14045383, 41481685, 90004325, 40781449, 72732205, 4119747, 65074535, 72238278, 56484900, 20645197, 67030811, 3294781, 72357096, 59501487, 99226875, 70420215, 41092102, 24168411, 33201905, 99333375, 84187166, 15536795, 77413012, 46540998, 40534591, 96318094, 37891451, 4985896, 90090964, 44550764, 74743862, 65017137, 99125126, 61141874, 74614639, 32274392, 92530431, 38645117, 51588015, 4095116, 76540481, 35996293, 97940276, 45790169, 93053405, 94090109, 84684495, 33304202, 51715482, 22450468, 87160386, 26998766, 3487592, 11923835, 65271999, 48395186, 93270084, 66667729, 36312813, 508198, 91802888, 3183975, 23569917, 89419466, 14326617, 36468541, 13862149, 6497830, 34667286, 84166196, 98130363, 20836893, 15015906, 89499542, 21070110, 82178706, 62490109, 89046466, 31733363, 93790285, 40027975, 19486173, 98648327, 86301513, 82886381, 43933006, 60102965, 26275734, 89804152, 72793444, 55189057, 40197395, 52293995, 34698463, 63506281, 16019925, 51507425, 73617245, 4798568, 44847298, 19101477, 49597667, 29635537, 98371444, 42073124, 44889423, 44245960, 37620363, 36808486, 51464002, 6808825, 41245325, 70541760, 23740167, 55960386, 86118021, 4099191, 56473732, 25636669, 34044787, 33237508, 59329511, 7182642, 55143724, 59910624, 49641577, 61859143, 52060076, 4091162, 28851716, 69355476, 18699206, 7687278, 92033260, 63015256, 50188404, 56153345, 50668599, 68204242, 72373496, 31491938, 24953498, 60955663, 73021291, 56955985, 89078848, 36396314, 53632373, 96709982, 17385531, 30771409, 94076128, 99971982, 65851721, 53888755, 91255408, 14220886, 62693428, 62452034, 45049308, 43506672, 32161669, 10366309, 8115266, 16405341, 26102057, 29510992, 27185644, 49882705, 58749, 73235980, 9860968, 91831487, 14093520, 47792865, 91990218, 63628376, 49236559, 47893286, 93515664, 4978543, 59405277, 53802686, 18466635, 89996536, 17857111, 7919588, 65338021, 70596786, 53648154, 44177011, 87598888, 37224844, 8729146, 12571310, 24826575, 9575954, 44784505, 5640302, 92554120, 61728685, 37675718, 82979980, 37183543, 16097038, 48088883, 48260151, 7955293, 91957544, 23110625, 34295794, 18411915, 70367851, 73109997, 63152504, 39373729, 89811711, 68644627, 5970607, 95957797, 43376279, 74110882, 33935899, 23194618, 29401781, 64055960, 78300864, 83083131, 6819644, 20140249, 74452589, 11581181, 31727379, 17386996, 21397057, 72278539, 59109400, 83651211, 92787493, 3509435, 98462867, 96852321, 83133790, 38510840, 72019362, 68102437, 75128745, 79657802, 55602660, 70782102, 84406788, 64848072, 37957788, 90061527, 17365188, 14723410, 69605283, 22942635, 21673260, 40984766, 33061250, 3773993, 47213183, 62803858, 75777973, 93933709, 11543098, 42307449, 61741594, 54868730, 81172706, 37501808, 93562257, 6505939, 51426311, 41442762, 38022834, 52204879, 19272365, 99021067, 9257405, 74441448, 824872, 77187825, 26744917, 12664567, 17081350, 48774913, 10453030, 247198, 54987042, 92353856, 57020481, 71083565, 79191827, 80251430, 21533347, 92215320, 17957593, 17068582, 96735716, 42782652, 78602717, 38494874, 98943869, 66903004, 67084351, 749283, 62430984, 67227442, 20681921, 32699744, 35456853, 30090481, 84293052, 37560164, 43322743, 74724075, 27041967, 44479073, 77284619, 56424103, 79322415, 6521313, 72358170, 79942022, 45617087, 18001617, 44473167, 58208470, 43357947, 3880712, 30694952, 20568363, 68000591, 23134715, 57163802, 9188443, 83747892, 98948034, 89699445, 14349098, 22721500, 84349107, 27411561, 75820087, 88251446, 54762643, 8791066, 55669657, 60176618, 68041839, 87720882 +43933006, 874791, 87720882, 8373289, 86301513, 67793644, 91255408, 43357947, 11923835, 66903004, 84406788, 59405277, 84904436, 52060076, 33201905, 83155442, 11365791, 83210802, 60176618, 90158785, 16405341, 9058407, 97561745, 9860968, 70782102, 7423788, 82979980, 80014588, 38365584, 23110625, 7646095, 17146629, 96726697, 14363867, 6819644, 77187825, 55669657, 15536795, 53632373, 65851721, 95251277, 43506672, 40677414, 59109400, 20642888, 68041839, 33431960, 38510840, 3487592, 62115552, 30694952, 75052463, 95290172, 65454636, 18466635, 27325428, 68128438, 17857111, 98648327, 63059024, 5640302, 13819358, 20765474, 31161687, 17016156, 51507425, 72614359, 44847298, 62936963, 45428665, 48658605, 44245960, 93562257, 99965001, 68316156, 61859143, 72732205, 4069912, 12024238, 47298834, 97783876, 40686254, 44550764, 92353856, 45306070, 67124282, 5111370, 65017137, 72358170, 43501211, 61960400, 32274392, 44481640, 24733232, 82327024, 13470059, 26139110, 80316608, 45617087, 80251430, 78218845, 3233084, 25842078, 72274002, 84840549, 26776922, 7229550, 26114953, 33628349, 85023028, 6497830, 28734791, 11161731, 15948937, 51590803, 14626618, 89996536, 30764367, 3633375, 89499542, 20486294, 81898046, 44844121, 80246713, 31733363, 75135448, 5482538, 62803858, 94360702, 39171895, 60102965, 17058722, 84293052, 73617245, 62740044, 37739481, 83948335, 49597667, 37560164, 51047803, 96906410, 70372191, 74724075, 73109997, 83747892, 67281495, 56473732, 58751351, 68644627, 27041967, 77898274, 57359924, 50188404, 72777973, 39201414, 20002147, 3294781, 5822202, 38256849, 77413012, 17727650, 82779622, 21397057, 54058788, 40152546, 2717150, 74743862, 36812683, 93566986, 15148031, 32161669, 93057697, 69136837, 72725103, 99549373, 22405120, 26102057, 27411561, 22435353, 49598724, 26493119, 47361209, 44348328, 27185644, 22450468, 93359396, 62357986, 2208785, 78549759, 20568363, 14093520, 14326617, 1788101, 26397786, 22200378, 34667286, 90061527, 72495719, 97011160, 22997281, 64098930, 98130363, 7919588, 69605283, 73222868, 20867149, 70727211, 15064655, 12571310, 24826575, 78589145, 44784505, 78561158, 98739783, 39801351, 21289531, 23134715, 6111563, 58180653, 30543215, 2331773, 61815223, 61741594, 91957544, 32590267, 72738685, 85116755, 71300104, 43322743, 5073754, 18663507, 51464002, 18504197, 23740167, 47090124, 4508700, 99297164, 89811711, 59329511, 77787724, 2607799, 55143724, 27625735, 37659250, 69355476, 74110882, 49271185, 65047700, 86798033, 65074535, 85571389, 10264691, 8733068, 43152977, 37435892, 20645197, 86242799, 59501487, 33565483, 84187166, 50702367, 14349098, 6871053, 168541, 30163921, 61897582, 86821229, 22721500, 68694897, 9398733, 56531125, 44627776, 91281584, 45049308, 61859581, 44842615, 84349107, 35512853, 4806458, 92787493, 85242963, 91727510, 29516592, 57241521, 21533347, 58208470, 75820087, 96852321, 17894977, 35092039, 26998766, 88251446, 58749, 45237957, 66667729, 95581843, 66045587, 60430369, 57248122, 91802888, 78602717, 81805959, 9259676, 47893286, 4199704, 36189527, 37957788, 92398073, 62430984, 84166196, 54663246, 69848388, 1197320, 15015906, 21673260, 86460488, 3773993, 30395570, 30787683, 87598888, 79738755, 88130087, 64157906, 22108665, 3235882, 52613508, 10649306, 55850790, 60581278, 82886381, 29959549, 1022677, 33553959, 3233569, 42199455, 36685023, 76671482, 11543098, 51149804, 34698463, 99729357, 65081429, 36505482, 19101477, 92541302, 29635537, 9175338, 69786756, 27187213, 12348118, 29029316, 44889423, 63152504, 33699435, 25636669, 41442762, 5970607, 29466406, 31126490, 49641577, 4091162, 20122224, 24915585, 30811010, 4119747, 92692978, 71920426, 24314885, 33935899, 91938191, 7687278, 63015256, 23194618, 9257405, 40356877, 45919976, 83083131, 77284619, 98948034, 72357096, 41092102, 24168411, 89699445, 99333375, 26744917, 50567636, 67513640, 39986008, 8128637, 89078848, 99224392, 46540998, 40534591, 96318094, 37891451, 94595668, 14220886, 6521313, 48893685, 94911072, 44846932, 526217, 79191827, 92530431, 19376156, 90310261, 76825057, 8115266, 23432750, 76540481, 70800879, 94516935, 97940276, 45790169, 93053405, 33336148, 30139692, 19891772, 81855445, 92215320, 57961282, 86001008, 19939935, 71965942, 21001913, 17068582, 87160386, 68102437, 28928175, 53084256, 63372756, 48395186, 73235980, 36312813, 28796059, 3880712, 82427263, 4515343, 55602660, 98943869, 91831487, 60106217, 59981773, 47792865, 55470718, 36468541, 67084351, 42237907, 91990218, 10358899, 51472275, 4978543, 15075176, 81677380, 59614746, 65338021, 70596786, 85695762, 62232211, 22942635, 62490109, 38009615, 61373987, 176257, 93790285, 13348726, 61928316, 30569392, 43045786, 92554120, 38341669, 75777973, 47887470, 89217461, 42967683, 86543538, 85117092, 48260151, 16019925, 13468268, 66322959, 54427233, 55615885, 30653863, 30463802, 77620120, 39847321, 6793819, 9188443, 92604458, 54868730, 59177718, 42073124, 12303248, 42692881, 71469330, 50007421, 71316369, 95957797, 91664334, 16054533, 59910624, 32058615, 82651278, 28851716, 79806380, 19272365, 51466049, 60393039, 1375023, 99524975, 1431742, 40781854, 4668450, 67030811, 73021291, 824872, 66663942, 61982238, 96193415, 77072625, 57658654, 62762857, 45848907, 45996863, 84127901, 6986898, 17081350, 82726008, 48774913, 34946859, 59318837, 16380211, 97057187, 97641116, 44983451, 72278539, 90090964, 69697787, 62693428, 89637706, 19457589, 36753250, 18833224, 39553046, 83368048, 68824981, 10366309, 92803766, 38645117, 4095116, 35996293, 75543508, 33797252, 90272749, 17539625, 3509435, 83133790, 8099994, 75153252, 10961421, 88653118, 7104732, 58224549, 44664587, 93270084, 28787861, 508198, 3183975, 81274566, 42947632, 75229462, 88444207, 26392416, 57393458, 13862149, 63628376, 49236559, 64848072, 17976208, 20885148, 15163258, 34236719, 38008118, 6525948, 14723410, 26734892, 20836893, 61712234, 20681921, 19898053, 55770687, 21993752, 21070110, 38658347, 53648154, 82178706, 30891921, 40984766, 24619760, 89046466, 1569515, 99861373, 8729146, 76170907, 59197747, 19486173, 48360198, 65880522, 64602895, 67031644, 77377183, 45990383, 47213183, 65275241, 62051033, 92867155, 73168565, 569864, 37183543, 93933709, 7300047, 48088883, 89804152, 52293995, 42307449, 26063929, 63506281, 46851987, 4798568, 80555751, 7955293, 1239555, 41380093, 34295794, 57163802, 7685448, 89214616, 32426752, 112651, 22766820, 66885828, 81172706, 37620363, 79136082, 18783702, 86118021, 8791066, 38022834, 16971929, 18131876, 7182642, 85711894, 14045383, 97379790, 36135, 81774825, 27665211, 76960303, 44479073, 68204242, 29401781, 73786944, 16567550, 94711842, 24953498, 60955663, 14731700, 30218878, 20140249, 62496012, 99226875, 9603598, 11757872, 36930650, 86022504, 11581181, 26292919, 57240218, 96709982, 1250437, 22129328, 44060493, 10453030, 247198, 34432810, 54987042, 669105, 4985896, 56515456, 321665, 57020481, 74614639, 71083565, 54517921, 9599614, 23848565, 4787945, 48673079, 2891150, 13231279, 69641513, 84684495, 88698958, 17957593, 33304202, 90013093, 16445503, 90457870, 51715482, 72019362, 75128745, 8055981, 3088684, 54199160, 49328608, 96735716, 96420429, 6038457, 8651647, 83150534, 95010552, 61271144, 36580610, 30366150, 9829782, 44915235, 93515664, 67451935, 10309525, 32250045, 94539824, 32159704, 24591705, 16595436, 24058273, 53393358, 73392814, 35456853, 68110330, 33061250, 40027975, 68000591, 9575954, 69255765, 42644903, 29932657, 77301523, 16097038, 72793444, 55189057, 82052050, 76703609, 79880247, 84002370, 48892201, 92692283, 75986488, 29188588, 18411915, 92998591, 77694700, 47708850, 2204165, 41245325, 70541760, 7066775, 51426311, 56461322, 7011964, 39373729, 29834512, 74357852, 78766358, 16424199, 41481685, 40781449, 54232247, 82532312, 18699206, 56153345, 99021067, 18806856, 83302115, 31491938, 56484900, 64055960, 67474219, 56424103, 79322415, 87710366, 31727379, 68939068, 56955985, 37192445, 45381876, 12664567, 36396314, 57803235, 47738185, 29819940, 30771409, 7502255, 71657078, 45995325, 94076128, 73031054, 50806615, 82897371, 2917920, 99125126, 88904910, 61141874, 77300457, 26664538, 5267545, 36139942, 83651211, 41092172, 66137019, 59371804, 4434662, 45667668, 97281847, 18001617, 29510992, 69579137, 98462867, 26863229, 76315420, 49882705, 91141395, 6221471, 9623492, 79657802, 79922758, 51315460, 38494874, 1936762, 37280276, 749283, 17365188, 81853704, 53547802, 78884452, 22879907, 15902805, 8807940, 99604946, 70004753, 83789391, 37224844, 62552524, 92071990, 66116458, 33123618, 61263068, 77312810, 53666583, 26275734, 66271566, 8913721, 36845587, 415901, 73124510, 99690194, 70367851, 37501808, 55960386, 34044787, 71333116, 88047921, 7517032, 1204161, 92033260, 16684829, 50668599, 72238278, 72373496, 29994197, 78300864, 70420215, 74452589, 54606384, 37166608, 36780454, 83269727, 62428472, 99515901, 76330843, 46870723, 32151165, 99971982, 53888755, 8696647, 62452034, 9886593, 29065964, 16387575, 65038678, 99917599, 31904591, 91240048, 51588015, 45407418, 17764950, 83533741, 79942022, 14947650, 8634541, 94090109, 99658235, 61623915, 66611759, 68702632, 53842979, 30998561, 74137926, 21919959, 54014062, 48675329, 53802686, 34493392, 65715134, 32699744, 6153406, 12416768, 38061439, 44177011, 55753905, 76434144, 95395112, 61728685, 15535065, 98371444, 6505939, 6808825, 33237508, 90004325, 55247455, 74441448, 66319530, 34497327, 17037369, 17386996, 4064751, 5832946, 79821897, 82339363, 44473167, 13173644, 40865610, 34698428, 66250369, 33895336, 42782652, 23569917, 68816413, 89419466, 36933038, 70036438, 9860195, 77272628, 64087743, 54263880, 75407347, 68875490, 37675718, 40197395, 26507214, 35192533, 63967300, 33249630, 36808486, 22113133, 90654836, 95726235, 52261574, 10597197, 78785507, 65271999, 54762643, 6157724, 45794415, 30090481, 4204661, 98653983, 16099750, 71522968, 4099191, 43376279, 66428795, 66832478, 8129978, 52204879, 17385531, 78909561, 45075471, 28550822, 67227442 +61373987, 7423788, 62762857, 69848388, 77301523, 83155442, 1250437, 53888755, 669105, 44983451, 9599614, 90013093, 75128745, 30366150, 12416768, 62552524, 92867155, 1239555, 48658605, 59329511, 78766358, 77284619, 34497327, 72278539, 44846932, 88904910, 45667668, 78785507, 68702632, 85023028, 42947632, 49236559, 37280276, 47893286, 4978543, 94539824, 82178706, 76170907, 98648327, 43933006, 52293995, 72738685, 73109997, 6505939, 18504197, 70541760, 22113133, 2607799, 32058615, 74110882, 82532312, 74441448, 98948034, 73021291, 62496012, 34432810, 50806615, 22721500, 44481640, 27411561, 91727510, 29510992, 17539625, 34698428, 33895336, 91802888, 51315460, 67084351, 42237907, 28734791, 14723410, 17857111, 21070110, 38658347, 30891921, 75135448, 64602895, 5482538, 76434144, 68875490, 61728685, 29932657, 37675718, 33553959, 39171895, 85117092, 73617245, 37739481, 73124510, 15535065, 112651, 12303248, 44889423, 18783702, 56461322, 41442762, 91664334, 31126490, 24953498, 1431742, 9603598, 36930650, 26744917, 45848907, 50702367, 52261574, 5832946, 34946859, 10453030, 97057187, 65851721, 54987042, 86821229, 4985896, 6521313, 67124282, 92530431, 5267545, 76540481, 70800879, 97940276, 80251430, 71965942, 38510840, 8099994, 26998766, 49882705, 10961421, 7104732, 66250369, 26776922, 4515343, 3183975, 8651647, 60106217, 70782102, 1788101, 4199704, 6497830, 20885148, 32250045, 81677380, 34493392, 20836893, 81853704, 53547802, 78884452, 62232211, 1569515, 68000591, 77377183, 44784505, 86301513, 39801351, 95395112, 65275241, 33123618, 7300047, 53666583, 26275734, 26063929, 16019925, 66322959, 54427233, 30653863, 62740044, 44847298, 92692283, 77620120, 57163802, 69786756, 5073754, 71469330, 67281495, 33237508, 29466406, 16424199, 81774825, 66428795, 65074535, 99021067, 29401781, 85571389, 73786944, 95726235, 60955663, 3294781, 20140249, 59501487, 97783876, 54606384, 89699445, 99515901, 71657078, 5111370, 93566986, 93057697, 10366309, 23848565, 84349107, 13470059, 83651211, 99549373, 9058407, 94516935, 4434662, 26493119, 33431960, 17894977, 72274002, 43357947, 91141395, 6221471, 88653118, 95581843, 79657802, 96735716, 6038457, 33628349, 68816413, 89419466, 55470718, 75229462, 749283, 44915235, 9860195, 65454636, 15948937, 77272628, 32159704, 64098930, 7919588, 15015906, 70596786, 89499542, 55770687, 85695762, 15902805, 40984766, 99604946, 30787683, 87598888, 37224844, 12571310, 47213183, 69255765, 75407347, 43045786, 5640302, 98739783, 13819358, 20765474, 38341669, 62803858, 17016156, 3233569, 72793444, 51149804, 99729357, 63506281, 4798568, 72614359, 36845587, 36505482, 61741594, 48892201, 415901, 96906410, 32426752, 63967300, 12348118, 18663507, 83747892, 51426311, 99965001, 86118021, 56473732, 8791066, 29834512, 16054533, 28851716, 72732205, 33935899, 49271185, 7687278, 50668599, 16567550, 51466049, 99524975, 37435892, 66319530, 67474219, 77072625, 57658654, 77187825, 38256849, 41092102, 37166608, 33201905, 17037369, 68939068, 39986008, 36396314, 57803235, 6986898, 99224392, 29819940, 6871053, 40152546, 99971982, 67793644, 66832478, 14220886, 74743862, 45306070, 56515456, 91281584, 57020481, 16387575, 95251277, 65038678, 18833224, 82339363, 61859581, 44842615, 19376156, 90310261, 22435353, 33797252, 97561745, 30139692, 44348328, 84684495, 96852321, 83133790, 35092039, 84840549, 68102437, 99658235, 40865610, 53084256, 11923835, 48395186, 45237957, 93270084, 36312813, 30998561, 42782652, 57248122, 75052463, 47792865, 21919959, 26397786, 91990218, 63628376, 70036438, 36189527, 67451935, 17365188, 97011160, 14626618, 59614746, 34236719, 38008118, 6525948, 1197320, 65715134, 24058273, 19898053, 53648154, 62490109, 3773993, 54263880, 44177011, 89046466, 99861373, 19486173, 88130087, 64157906, 45990383, 10649306, 29959549, 61263068, 77312810, 89217461, 82979980, 6111563, 48088883, 89804152, 98653983, 17058722, 42199455, 30543215, 8913721, 80014588, 2331773, 61815223, 19101477, 45428665, 39847321, 9175338, 54868730, 92998591, 42692881, 27187213, 81172706, 2204165, 41245325, 23740167, 16971929, 52204879, 18131876, 96726697, 49641577, 68316156, 7517032, 82651278, 52060076, 24915585, 24314885, 18699206, 1204161, 29994197, 39201414, 40356877, 18806856, 94711842, 43152977, 55247455, 83083131, 20002147, 6819644, 99226875, 74452589, 24168411, 37192445, 15536795, 84127901, 57240218, 96709982, 82726008, 76330843, 22129328, 21397057, 44060493, 30771409, 32151165, 59318837, 94076128, 10597197, 94595668, 29065964, 36753250, 45049308, 61960400, 54517921, 32274392, 68824981, 32161669, 40677414, 38645117, 90158785, 4787945, 17764950, 22405120, 48673079, 2891150, 93053405, 49598724, 29516592, 78218845, 69579137, 81855445, 92215320, 13231279, 86001008, 76315420, 16445503, 51715482, 87160386, 61623915, 65271999, 62357986, 58224549, 8055981, 44664587, 66667729, 28796059, 54199160, 96420429, 79922758, 55602660, 26114953, 38494874, 14326617, 95290172, 54014062, 51472275, 48675329, 93515664, 59405277, 30764367, 35456853, 73222868, 81898046, 30395570, 24619760, 176257, 20867149, 83789391, 15064655, 3235882, 52613508, 78561158, 66116458, 75777973, 60581278, 82886381, 16097038, 94360702, 40197395, 11543098, 76703609, 51507425, 84293052, 55615885, 80555751, 78909561, 7955293, 91957544, 38365584, 49597667, 9188443, 18411915, 71300104, 99690194, 22766820, 43322743, 77694700, 37620363, 79136082, 51464002, 6808825, 7011964, 25636669, 17146629, 58751351, 34044787, 5970607, 38022834, 27041967, 36135, 61859143, 54232247, 69355476, 71920426, 65047700, 19272365, 9257405, 83302115, 56484900, 40781854, 4668450, 72357096, 824872, 66663942, 96193415, 83269727, 62428472, 50567636, 8128637, 12664567, 89078848, 17385531, 14349098, 48774913, 82779622, 46540998, 45995325, 37891451, 73031054, 168541, 91255408, 2717150, 82897371, 9398733, 62693428, 62452034, 99125126, 9886593, 36812683, 77300457, 43501211, 39553046, 31904591, 24733232, 69136837, 60176618, 8115266, 23432750, 4806458, 41092172, 83533741, 79942022, 75543508, 80316608, 33336148, 97281847, 8634541, 19891772, 21533347, 75820087, 17957593, 27185644, 33304202, 88251446, 75153252, 58749, 73235980, 3088684, 28787861, 49328608, 508198, 62115552, 74137926, 78602717, 78549759, 30694952, 9259676, 20568363, 98943869, 9860968, 91831487, 83150534, 36468541, 61271144, 26392416, 13862149, 10358899, 64848072, 37957788, 17976208, 53802686, 34667286, 62430984, 84166196, 98130363, 24591705, 16595436, 6153406, 53393358, 21993752, 44844121, 33061250, 31733363, 40027975, 59197747, 9575954, 30569392, 63059024, 92554120, 31161687, 21289531, 569864, 23134715, 42967683, 86543538, 4204661, 76671482, 42307449, 48260151, 874791, 79880247, 46851987, 85116755, 16099750, 89214616, 70372191, 44245960, 71522968, 7066775, 99297164, 71316369, 77787724, 88047921, 7182642, 55143724, 14045383, 59910624, 97379790, 57359924, 30811010, 27665211, 79806380, 63015256, 4069912, 16684829, 87720882, 8733068, 47298834, 31491938, 86242799, 67030811, 5822202, 61982238, 79322415, 55669657, 11581181, 99333375, 31727379, 40686254, 17081350, 17727650, 7502255, 40534591, 54058788, 79821897, 16380211, 61897582, 69697787, 8696647, 526217, 43506672, 71083565, 45075471, 79191827, 99917599, 36139942, 59109400, 16405341, 4095116, 26102057, 66137019, 45790169, 8373289, 3509435, 69641513, 26863229, 19939935, 93359396, 72019362, 28928175, 66611759, 2208785, 60430369, 23569917, 66903004, 59981773, 36580610, 84406788, 22200378, 9829782, 15075176, 6157724, 92398073, 27325428, 51590803, 22997281, 89996536, 61712234, 67227442, 65338021, 73392814, 69605283, 80246713, 68110330, 70727211, 79738755, 93790285, 8729146, 48360198, 65880522, 78589145, 67031644, 30090481, 22108665, 42644903, 47887470, 93933709, 60102965, 82052050, 36685023, 34698463, 13468268, 84904436, 84002370, 26507214, 62936963, 35192533, 32590267, 37560164, 41380093, 92541302, 6793819, 98371444, 7685448, 51047803, 59177718, 42073124, 66885828, 7646095, 93562257, 4508700, 95957797, 71333116, 74357852, 85711894, 41481685, 77898274, 90004325, 4091162, 20122224, 40781449, 27625735, 92692978, 50188404, 72238278, 72373496, 20645197, 56424103, 45996863, 26292919, 77413012, 96318094, 44550764, 11365791, 92353856, 68694897, 56531125, 321665, 89637706, 94911072, 65017137, 61141874, 44627776, 19457589, 74614639, 26664538, 82327024, 91240048, 92803766, 72725103, 35512853, 85242963, 14947650, 26139110, 59371804, 18001617, 47361209, 57961282, 3233084, 17068582, 22450468, 28550822, 54762643, 9623492, 3880712, 82427263, 81274566, 14093520, 36933038, 68128438, 54663246, 26734892, 3633375, 20486294, 21673260, 38009615, 64087743, 38061439, 24826575, 61928316, 92071990, 8129978, 73168565, 55850790, 1022677, 58180653, 55189057, 66271566, 30463802, 83948335, 23110625, 34295794, 29188588, 92604458, 37501808, 63152504, 55960386, 4099191, 47090124, 89811711, 37659250, 4119747, 91938191, 86798033, 76960303, 56153345, 68204242, 12024238, 1375023, 78300864, 11757872, 84187166, 53632373, 17386996, 47738185, 97641116, 2917920, 72358170, 15148031, 76825057, 51588015, 92787493, 68041839, 45617087, 44473167, 57241521, 94090109, 13173644, 63372756, 53842979, 81805959, 95010552, 72495719, 45794415, 22879907, 22942635, 8807940, 70004753, 13348726, 37183543, 29635537, 33249630, 74724075, 47708850, 70367851, 50007421, 68644627, 90654836, 14363867, 23194618, 72777973, 60393039, 14731700, 87710366, 33565483, 56955985, 45381876, 46870723, 247198, 30163921, 90090964, 48893685, 83210802, 45407418, 90272749, 58208470, 98462867, 88698958, 25842078, 3487592, 66045587, 7229550, 1936762, 88444207, 10309525, 18466635, 86460488, 55753905, 62051033, 65081429, 75986488, 29029316, 33699435, 39373729, 92033260, 44479073, 45919976, 86022504, 36780454, 4064751, 83368048, 35996293, 21001913, 90457870, 11161731, 90061527, 43376279, 10264691, 70420215, 57393458, 32699744, 36808486, 30218878, 67513640, 20642888, 15163258, 64055960, 20681921 +77300457, 7104732, 44784505, 68875490, 13819358, 82886381, 96906410, 82651278, 63015256, 4985896, 54199160, 78766358, 7687278, 37891451, 94076128, 9398733, 90310261, 16405341, 48673079, 45790169, 79657802, 53842979, 53802686, 17857111, 68000591, 75407347, 7423788, 80555751, 88047921, 32058615, 11757872, 39986008, 34432810, 69697787, 45049308, 54517921, 99917599, 8115266, 72725103, 92787493, 75820087, 28928175, 66250369, 66667729, 54014062, 749283, 28734791, 32159704, 81853704, 48360198, 8129978, 65275241, 75777973, 55850790, 1022677, 72793444, 34698463, 54427233, 1239555, 41380093, 89214616, 22766820, 18663507, 83747892, 67281495, 86118021, 56461322, 33237508, 81774825, 37659250, 71920426, 19272365, 72373496, 30218878, 824872, 66663942, 11581181, 56955985, 53632373, 52261574, 46870723, 29819940, 59318837, 65851721, 54987042, 91255408, 6521313, 8696647, 321665, 89637706, 88904910, 36812683, 16387575, 526217, 79191827, 31904591, 15148031, 26664538, 10366309, 19376156, 91240048, 69136837, 76825057, 79942022, 94516935, 59371804, 33797252, 18001617, 29510992, 21001913, 6221471, 54762643, 58224549, 95581843, 42782652, 93515664, 20885148, 15948937, 38008118, 24591705, 38658347, 65880522, 3235882, 43045786, 77301523, 89804152, 55189057, 42307449, 48260151, 26063929, 63506281, 80014588, 84293052, 62740044, 72614359, 38365584, 34295794, 57163802, 63967300, 77694700, 70367851, 6808825, 18783702, 50007421, 58751351, 22113133, 71316369, 5970607, 18131876, 16424199, 24915585, 40781449, 90654836, 27625735, 72732205, 74110882, 79806380, 92033260, 8733068, 20645197, 40781854, 14731700, 73021291, 59501487, 36930650, 24168411, 17037369, 68939068, 45848907, 36396314, 17081350, 71657078, 10453030, 32151165, 96318094, 99971982, 50806615, 53888755, 72278539, 22721500, 56515456, 62452034, 29065964, 65038678, 32274392, 92530431, 68824981, 85242963, 75543508, 94090109, 3509435, 90457870, 93359396, 88251446, 10961421, 9623492, 3088684, 60430369, 57248122, 78549759, 85023028, 36468541, 88444207, 13862149, 51472275, 4199704, 65454636, 10309525, 34493392, 1197320, 65715134, 65338021, 89499542, 21993752, 12416768, 38061439, 30787683, 99861373, 93790285, 40027975, 67031644, 62552524, 9575954, 63059024, 20765474, 62051033, 33123618, 60102965, 66271566, 85117092, 66322959, 4798568, 45428665, 7685448, 92604458, 70372191, 42073124, 42692881, 5073754, 47708850, 71522968, 4508700, 39373729, 27041967, 74357852, 41481685, 36135, 30811010, 28851716, 82532312, 24314885, 50668599, 29401781, 18806856, 94711842, 56484900, 37435892, 78300864, 86242799, 3294781, 20140249, 62762857, 86022504, 87710366, 26744917, 37192445, 12664567, 77413012, 89078848, 6986898, 83155442, 96709982, 5832946, 30771409, 45995325, 97057187, 66832478, 669105, 86821229, 44983451, 44550764, 48893685, 67124282, 72358170, 91281584, 36753250, 93566986, 61859581, 84349107, 40677414, 13470059, 90158785, 4787945, 9058407, 83533741, 27411561, 26139110, 93053405, 68041839, 97281847, 80251430, 78218845, 69579137, 21533347, 58208470, 27185644, 43357947, 22450468, 40865610, 3487592, 34698428, 62357986, 93270084, 36312813, 3880712, 66045587, 49328608, 96420429, 79922758, 3183975, 33628349, 68816413, 14093520, 70782102, 37280276, 48675329, 44915235, 4978543, 15075176, 11161731, 92398073, 34667286, 17365188, 81677380, 14626618, 94539824, 59614746, 54663246, 32699744, 78884452, 20486294, 62232211, 30891921, 40984766, 3773993, 89046466, 61373987, 31733363, 8729146, 76170907, 64602895, 88130087, 64157906, 30090481, 98648327, 61928316, 47213183, 78561158, 66116458, 95395112, 92867155, 62803858, 61728685, 29959549, 37675718, 33553959, 23134715, 86543538, 48088883, 98653983, 82052050, 8913721, 76703609, 13468268, 51507425, 30653863, 65081429, 36845587, 37739481, 7955293, 19101477, 73124510, 92692283, 29635537, 9175338, 92998591, 44889423, 37620363, 23740167, 8791066, 17146629, 29466406, 16971929, 52204879, 14045383, 68316156, 49271185, 76960303, 4069912, 99021067, 87720882, 29994197, 77284619, 99226875, 61982238, 74452589, 89699445, 8128637, 1250437, 34946859, 6871053, 73031054, 97641116, 82339363, 44842615, 23848565, 41092172, 20642888, 45617087, 17539625, 33304202, 17068582, 72019362, 84840549, 26998766, 49882705, 28550822, 53084256, 75128745, 65271999, 66611759, 33895336, 96735716, 82427263, 2208785, 4515343, 6038457, 62115552, 23569917, 38494874, 81274566, 42947632, 47792865, 36933038, 67084351, 91990218, 30366150, 84406788, 49236559, 47893286, 36189527, 6497830, 97011160, 89996536, 69848388, 98130363, 26734892, 61712234, 15015906, 20681921, 45794415, 21070110, 73222868, 82178706, 8807940, 86460488, 33061250, 30395570, 24619760, 44177011, 83789391, 21289531, 82979980, 37183543, 26275734, 58180653, 42199455, 30543215, 16019925, 79880247, 55615885, 2331773, 44847298, 91957544, 23110625, 15535065, 29188588, 6793819, 18411915, 54868730, 69786756, 112651, 66885828, 74724075, 37501808, 79136082, 41245325, 70541760, 4099191, 33699435, 34044787, 59329511, 95957797, 43376279, 97379790, 90004325, 54232247, 1204161, 65047700, 66428795, 16684829, 68204242, 85571389, 95726235, 83302115, 47298834, 24953498, 55247455, 74441448, 67030811, 98948034, 62496012, 5822202, 56424103, 96193415, 57658654, 9603598, 38256849, 54606384, 83269727, 50567636, 45381876, 26292919, 57803235, 17385531, 82726008, 82779622, 21397057, 40534591, 79821897, 10597197, 94595668, 67793644, 168541, 14220886, 74743862, 11365791, 45306070, 5111370, 94911072, 61141874, 57020481, 18833224, 61960400, 71083565, 5267545, 83210802, 59109400, 60176618, 83651211, 35512853, 26102057, 14947650, 97940276, 22435353, 45667668, 30139692, 44473167, 8373289, 8634541, 57241521, 84684495, 96852321, 26863229, 3233084, 83133790, 71965942, 17894977, 51715482, 87160386, 68102437, 75153252, 508198, 74137926, 78602717, 30694952, 66903004, 9860968, 89419466, 75229462, 14326617, 83150534, 21919959, 26397786, 42237907, 22200378, 64848072, 67451935, 18466635, 72495719, 68128438, 84166196, 34236719, 20836893, 16595436, 3633375, 53547802, 55770687, 35456853, 22942635, 44844121, 21673260, 62490109, 68110330, 99604946, 54263880, 176257, 15064655, 75135448, 55753905, 19486173, 77377183, 92071990, 92554120, 38341669, 73168565, 47887470, 61263068, 89217461, 94360702, 4204661, 17058722, 99729357, 73617245, 84002370, 35192533, 61741594, 37560164, 72738685, 9188443, 33249630, 29029316, 73109997, 99965001, 56473732, 7011964, 25636669, 41442762, 68644627, 38022834, 71333116, 85711894, 7517032, 52060076, 69355476, 91938191, 44479073, 39201414, 1375023, 99524975, 64055960, 67474219, 70420215, 79322415, 77187825, 31727379, 33565483, 84187166, 84127901, 50702367, 99515901, 22129328, 14349098, 17727650, 48774913, 16380211, 68694897, 65017137, 9886593, 74614639, 32161669, 82327024, 9599614, 92803766, 38645117, 23432750, 4095116, 35996293, 66137019, 91727510, 2891150, 80316608, 90272749, 19891772, 81855445, 57961282, 88698958, 86001008, 35092039, 11923835, 91141395, 63372756, 48395186, 8055981, 28787861, 28796059, 26776922, 91802888, 55602660, 51315460, 20568363, 60106217, 26392416, 36580610, 9829782, 32250045, 27325428, 90061527, 51590803, 15163258, 30764367, 6525948, 14723410, 7919588, 6153406, 53393358, 53648154, 81898046, 15902805, 64087743, 1569515, 87598888, 79738755, 5482538, 78589145, 22108665, 45990383, 52613508, 69255765, 30569392, 5640302, 39801351, 29932657, 17016156, 569864, 6111563, 53666583, 36685023, 51149804, 52293995, 874791, 26507214, 62936963, 36505482, 30463802, 48892201, 415901, 77620120, 16099750, 12303248, 27187213, 12348118, 44245960, 81172706, 2204165, 7646095, 89811711, 77787724, 49641577, 77898274, 61859143, 20122224, 27665211, 65074535, 50188404, 56153345, 72777973, 73786944, 40356877, 45919976, 16567550, 12024238, 1431742, 4668450, 77072625, 33201905, 45996863, 15536795, 17386996, 47738185, 46540998, 54058788, 247198, 30163921, 61897582, 92353856, 62693428, 56531125, 99125126, 44627776, 95251277, 43501211, 83368048, 24733232, 4806458, 17764950, 76540481, 26493119, 97561745, 92215320, 13231279, 44348328, 90013093, 72274002, 76315420, 8099994, 78785507, 99658235, 61623915, 88653118, 73235980, 44664587, 30998561, 7229550, 26114953, 9259676, 98943869, 1936762, 91831487, 95290172, 95010552, 61271144, 1788101, 63628376, 17976208, 9860195, 59405277, 77272628, 67227442, 73392814, 69605283, 85695762, 22879907, 80246713, 38009615, 20867149, 10649306, 98739783, 42644903, 60581278, 42967683, 7300047, 40197395, 11543098, 84904436, 75986488, 85116755, 98371444, 71300104, 59177718, 32426752, 43322743, 36808486, 6505939, 51464002, 18504197, 99297164, 96726697, 31126490, 16054533, 59910624, 4091162, 4119747, 92692978, 86798033, 10264691, 51466049, 43152977, 83083131, 60955663, 6819644, 72357096, 34497327, 97783876, 37166608, 99333375, 99224392, 44060493, 7502255, 90090964, 44846932, 43506672, 45075471, 39553046, 93057697, 51588015, 45407418, 22405120, 70800879, 4434662, 49598724, 33336148, 33431960, 47361209, 98462867, 19939935, 17957593, 38510840, 16445503, 45237957, 68702632, 81805959, 59981773, 10358899, 6157724, 62430984, 24058273, 70596786, 12571310, 76434144, 13348726, 86301513, 77312810, 43933006, 76671482, 78909561, 61815223, 32590267, 49597667, 92541302, 99690194, 71469330, 93562257, 55960386, 29834512, 2607799, 91664334, 55143724, 18699206, 33935899, 14363867, 60393039, 31491938, 20002147, 66319530, 41092102, 40686254, 4064751, 82897371, 2917920, 44481640, 36139942, 69641513, 58749, 70036438, 37957788, 64098930, 19898053, 70004753, 70727211, 31161687, 93933709, 16097038, 39171895, 3233569, 46851987, 83948335, 39847321, 48658605, 7066775, 57359924, 57240218, 40152546, 2717150, 19457589, 99549373, 13173644, 75052463, 8651647, 55470718, 57393458, 59197747, 24826575, 63152504, 51426311, 47090124, 7182642, 23194618, 72238278, 62428472, 67513640, 29516592, 22997281, 37224844, 9257405, 36780454, 76330843, 55669657, 51047803, 25842078 +52613508, 45407418, 70036438, 44481640, 89078848, 84166196, 24058273, 84904436, 47090124, 5111370, 93566986, 4806458, 70800879, 27411561, 35996293, 61623915, 47792865, 30090481, 20765474, 94360702, 75986488, 18783702, 95957797, 2607799, 83302115, 41092102, 24168411, 99333375, 10453030, 16380211, 168541, 84349107, 83651211, 45617087, 16445503, 75229462, 57393458, 30366150, 9829782, 54663246, 7919588, 21070110, 65275241, 62803858, 75777973, 61263068, 33553959, 48088883, 63506281, 73617245, 73124510, 85116755, 32426752, 43322743, 71522968, 4099191, 56461322, 25636669, 68644627, 54232247, 18699206, 49271185, 9257405, 16567550, 1431742, 77187825, 67513640, 94595668, 73031054, 97641116, 45306070, 67124282, 91281584, 83368048, 92803766, 4095116, 49598724, 3509435, 69641513, 90457870, 48395186, 79657802, 4515343, 74137926, 67084351, 13862149, 6497830, 20885148, 6525948, 89499542, 30891921, 5482538, 88130087, 13348726, 68000591, 63059024, 10649306, 98739783, 4204661, 82052050, 72614359, 9175338, 18411915, 70372191, 92998591, 2204165, 83747892, 18504197, 55960386, 7011964, 4508700, 33237508, 43376279, 14045383, 49641577, 69355476, 1375023, 99524975, 55247455, 67474219, 96193415, 62762857, 76330843, 54058788, 61897582, 669105, 44983451, 22721500, 74743862, 2917920, 62693428, 62452034, 29065964, 71083565, 79191827, 8373289, 92215320, 43357947, 84840549, 88653118, 62357986, 60430369, 91802888, 10358899, 51472275, 6157724, 62430984, 89996536, 67227442, 53547802, 55770687, 44844121, 8807940, 176257, 99861373, 12571310, 75135448, 59197747, 19486173, 92071990, 86301513, 7423788, 31161687, 42967683, 39171895, 76671482, 66271566, 84293052, 2331773, 46851987, 4798568, 78909561, 19101477, 415901, 6793819, 51047803, 71300104, 89214616, 59177718, 93562257, 36808486, 58751351, 34044787, 89811711, 22113133, 29834512, 78766358, 81774825, 19272365, 14363867, 16684829, 50188404, 72373496, 73786944, 94711842, 60955663, 4668450, 20002147, 20140249, 77072625, 36780454, 11581181, 84187166, 57240218, 52261574, 46870723, 32151165, 94076128, 2717150, 92353856, 68694897, 99125126, 45075471, 15148031, 90310261, 51588015, 92787493, 17764950, 76540481, 97940276, 80316608, 33797252, 45667668, 97561745, 90272749, 80251430, 78218845, 81855445, 71965942, 38510840, 78785507, 34698428, 58224549, 8055981, 66250369, 96735716, 95290172, 36468541, 61271144, 26397786, 36580610, 48675329, 10309525, 15948937, 18466635, 90061527, 59614746, 34236719, 81853704, 65338021, 45794415, 69605283, 38658347, 99604946, 3773993, 70004753, 20867149, 76434144, 67031644, 69255765, 43045786, 13819358, 42644903, 82886381, 17016156, 37675718, 37183543, 60102965, 3233569, 85117092, 30653863, 44847298, 35192533, 30463802, 1239555, 83948335, 91957544, 32590267, 72738685, 29635537, 98371444, 92604458, 69786756, 29029316, 6505939, 51464002, 51426311, 99965001, 86118021, 16054533, 59910624, 97379790, 16424199, 27625735, 99021067, 85571389, 29994197, 43152977, 20645197, 74441448, 86242799, 6819644, 30218878, 86022504, 83269727, 50702367, 79821897, 40152546, 67793644, 82897371, 56531125, 321665, 526217, 77300457, 43506672, 43501211, 61960400, 60176618, 4787945, 35512853, 79942022, 66137019, 91727510, 33336148, 26493119, 44473167, 13173644, 57961282, 86001008, 27185644, 51715482, 49882705, 88251446, 99658235, 40865610, 10961421, 66611759, 54762643, 44664587, 3088684, 66667729, 36312813, 28787861, 26776922, 49328608, 508198, 96420429, 57248122, 62115552, 66903004, 85023028, 14093520, 55470718, 14326617, 83150534, 91990218, 28734791, 15075176, 59405277, 92398073, 53802686, 68128438, 15163258, 15015906, 65715134, 6153406, 73392814, 35456853, 53648154, 22879907, 21673260, 40984766, 38009615, 38061439, 33061250, 89046466, 70727211, 76170907, 98648327, 77377183, 9575954, 30569392, 5640302, 39801351, 38341669, 61728685, 55850790, 47887470, 89217461, 93933709, 7300047, 53666583, 17058722, 42199455, 76703609, 51507425, 874791, 26507214, 62740044, 48892201, 41380093, 92692283, 57163802, 7685448, 42692881, 47708850, 7646095, 79136082, 67281495, 56473732, 8791066, 38022834, 52204879, 96726697, 31126490, 7182642, 41481685, 36135, 32058615, 68316156, 30811010, 4119747, 71920426, 27665211, 1204161, 65047700, 92033260, 76960303, 56153345, 72777973, 39201414, 95726235, 18806856, 8733068, 12024238, 47298834, 40781854, 77284619, 59501487, 99226875, 57658654, 36930650, 37166608, 33201905, 33565483, 39986008, 56955985, 12664567, 53632373, 17386996, 4064751, 17727650, 47738185, 21397057, 5832946, 71657078, 40534591, 96318094, 59318837, 34432810, 97057187, 65851721, 66832478, 53888755, 54987042, 86821229, 14220886, 11365791, 48893685, 16387575, 65038678, 18833224, 39553046, 92530431, 9599614, 5267545, 36139942, 69136837, 76825057, 90158785, 99549373, 16405341, 9058407, 48673079, 14947650, 75543508, 22435353, 97281847, 47361209, 17539625, 75820087, 84684495, 26863229, 83133790, 17894977, 8099994, 58749, 11923835, 91141395, 63372756, 68702632, 54199160, 33895336, 66045587, 2208785, 79922758, 3183975, 8651647, 91831487, 68816413, 59981773, 42947632, 21919959, 26392416, 63628376, 49236559, 44915235, 93515664, 77272628, 97011160, 14626618, 64098930, 20681921, 16595436, 53393358, 15902805, 61373987, 1569515, 87598888, 40027975, 15064655, 8729146, 55753905, 24826575, 95395112, 60581278, 33123618, 77312810, 86543538, 6111563, 16097038, 43933006, 72793444, 55189057, 11543098, 52293995, 8913721, 54427233, 80014588, 84002370, 36845587, 62936963, 37739481, 61815223, 61741594, 49597667, 39847321, 9188443, 16099750, 96906410, 42073124, 112651, 33249630, 12303248, 66885828, 27187213, 18663507, 7066775, 33699435, 59329511, 5970607, 77787724, 71333116, 27041967, 18131876, 7517032, 57359924, 4091162, 20122224, 37659250, 74110882, 82532312, 65074535, 72238278, 83083131, 824872, 5822202, 61982238, 97783876, 38256849, 87710366, 89699445, 26744917, 37192445, 40686254, 17081350, 96709982, 17385531, 6871053, 45995325, 247198, 10597197, 50806615, 91255408, 4985896, 6521313, 56515456, 88904910, 36812683, 19457589, 57020481, 74614639, 54517921, 31904591, 82339363, 68824981, 26664538, 44842615, 91240048, 59109400, 83533741, 2891150, 93053405, 30139692, 8634541, 57241521, 21533347, 44348328, 25842078, 21001913, 90013093, 72274002, 22450468, 93359396, 26998766, 75128745, 6221471, 73235980, 45237957, 3880712, 82427263, 6038457, 81805959, 51315460, 20568363, 38494874, 1936762, 89419466, 95010552, 36933038, 42237907, 37280276, 34667286, 30764367, 38008118, 17857111, 70596786, 19898053, 78884452, 62232211, 22942635, 24619760, 31733363, 93790285, 37224844, 64602895, 22108665, 62552524, 8129978, 92554120, 77301523, 82979980, 26275734, 58180653, 40197395, 30543215, 36685023, 34698463, 26063929, 66322959, 36505482, 7955293, 23110625, 29188588, 48658605, 99690194, 22766820, 77694700, 12348118, 44245960, 71469330, 37620363, 63152504, 41245325, 23740167, 50007421, 41442762, 39373729, 55143724, 77898274, 61859143, 52060076, 40781449, 90654836, 79806380, 33935899, 91938191, 7687278, 66428795, 44479073, 45919976, 10264691, 51466049, 56484900, 67030811, 3294781, 66663942, 9603598, 55669657, 54606384, 17037369, 68939068, 50567636, 45996863, 8128637, 15536795, 6986898, 83155442, 1250437, 99515901, 82726008, 30771409, 34946859, 37891451, 90090964, 89637706, 61141874, 44627776, 36753250, 45049308, 24733232, 19376156, 13470059, 72725103, 41092172, 94516935, 94090109, 19891772, 13231279, 19939935, 17957593, 35092039, 76315420, 87160386, 68102437, 28928175, 65271999, 7104732, 93270084, 95581843, 30998561, 78602717, 26114953, 78549759, 33628349, 75052463, 98943869, 70782102, 1788101, 22200378, 749283, 36189527, 37957788, 65454636, 32250045, 27325428, 51590803, 34493392, 98130363, 1197320, 61712234, 21993752, 82178706, 80246713, 86460488, 30395570, 44177011, 3235882, 47213183, 78561158, 73168565, 21289531, 29959549, 1022677, 48260151, 65081429, 80555751, 77620120, 92541302, 15535065, 45428665, 5073754, 74724075, 44889423, 37501808, 73109997, 99297164, 29466406, 91664334, 85711894, 28851716, 72732205, 86798033, 4069912, 50668599, 68204242, 29401781, 64055960, 78300864, 56424103, 34497327, 70420215, 31727379, 45381876, 77413012, 36396314, 84127901, 57803235, 14349098, 82779622, 44060493, 29819940, 7502255, 72278539, 69697787, 94911072, 72358170, 9886593, 44846932, 95251277, 32161669, 82327024, 61859581, 10366309, 38645117, 8115266, 59371804, 45790169, 29516592, 33431960, 58208470, 98462867, 88698958, 17068582, 28550822, 3487592, 9623492, 53842979, 55602660, 23569917, 88444207, 84406788, 64848072, 67451935, 4978543, 17976208, 14723410, 69848388, 24591705, 62490109, 12416768, 54263880, 48360198, 65880522, 64157906, 45990383, 75407347, 66116458, 29932657, 569864, 23134715, 51149804, 99729357, 38365584, 54868730, 70367851, 6808825, 70541760, 16971929, 82651278, 90004325, 24915585, 23194618, 31491938, 24953498, 66319530, 98948034, 72357096, 74452589, 62428472, 45848907, 26292919, 22129328, 48774913, 46540998, 44550764, 32274392, 99917599, 93057697, 23848565, 83210802, 40677414, 23432750, 22405120, 26102057, 85242963, 96852321, 3233084, 72019362, 75153252, 53084256, 28796059, 42782652, 30694952, 81274566, 60106217, 54014062, 47893286, 11161731, 72495719, 17365188, 32699744, 20486294, 81898046, 30787683, 83789391, 79738755, 44784505, 68875490, 62051033, 92867155, 89804152, 98653983, 55615885, 34295794, 81172706, 17146629, 74357852, 24314885, 40356877, 60393039, 14731700, 11757872, 99971982, 30163921, 65017137, 26139110, 9259676, 9860195, 22997281, 94539824, 26734892, 20836893, 85695762, 68110330, 78589145, 61928316, 42307449, 16019925, 13468268, 88047921, 87720882, 37435892, 73021291, 79322415, 20642888, 4434662, 18001617, 69579137, 4199704, 81677380, 3633375, 73222868, 64087743, 63967300, 71316369, 99224392, 9398733, 68041839, 29510992, 33304202, 32159704, 79880247, 62496012, 63015256, 9860968, 37560164, 7229550, 92692978, 8696647 +31126490, 13173644, 4095116, 78766358, 5822202, 10597197, 24733232, 78602717, 67227442, 60102965, 36505482, 7011964, 25636669, 61960400, 84349107, 17764950, 2891150, 49882705, 83150534, 1788101, 6153406, 30395570, 12571310, 98648327, 9575954, 92071990, 7423788, 57163802, 9188443, 59177718, 32426752, 12348118, 18663507, 33237508, 71920426, 72373496, 56424103, 321665, 82327024, 14947650, 33797252, 29516592, 13231279, 3509435, 73235980, 95581843, 33895336, 59981773, 18466635, 30764367, 3633375, 21070110, 20867149, 55753905, 44784505, 63059024, 68875490, 82886381, 82979980, 51149804, 29188588, 48658605, 63967300, 2204165, 71469330, 63152504, 70541760, 99965001, 34044787, 59329511, 7517032, 65047700, 92033260, 76960303, 44479073, 40356877, 18806856, 40781854, 66319530, 20140249, 824872, 61982238, 59318837, 45995325, 53888755, 54987042, 669105, 14220886, 82897371, 56531125, 92530431, 26664538, 9599614, 8115266, 45407418, 48673079, 27411561, 35996293, 30139692, 88698958, 83133790, 11923835, 54762643, 93270084, 68702632, 26776922, 3183975, 81805959, 89419466, 14093520, 42237907, 48675329, 70036438, 93515664, 67451935, 22997281, 68128438, 15015906, 32699744, 82178706, 64087743, 87598888, 78589145, 5640302, 10649306, 65275241, 31161687, 569864, 42967683, 48088883, 26275734, 17058722, 63506281, 84002370, 44847298, 61815223, 73124510, 77620120, 75986488, 45428665, 85116755, 98371444, 42692881, 83747892, 8791066, 71333116, 7182642, 32058615, 81774825, 20122224, 37659250, 18699206, 73786944, 16567550, 83302115, 24953498, 86242799, 20002147, 67474219, 66663942, 79322415, 83269727, 45996863, 96709982, 5832946, 79821897, 40152546, 99971982, 97057187, 66832478, 44983451, 89637706, 88904910, 29065964, 31904591, 10366309, 76825057, 92787493, 16405341, 68041839, 97281847, 90272749, 8634541, 94090109, 80251430, 84684495, 76315420, 84840549, 88251446, 61623915, 63372756, 7104732, 66667729, 28787861, 28796059, 54199160, 79657802, 79922758, 7229550, 62115552, 74137926, 26114953, 21919959, 6497830, 28734791, 11161731, 27325428, 34236719, 38008118, 7919588, 65715134, 53547802, 44844121, 38009615, 86460488, 99604946, 99861373, 40027975, 75135448, 98739783, 95395112, 13819358, 42644903, 62803858, 21289531, 58180653, 66271566, 85117092, 99729357, 48260151, 84904436, 51047803, 54868730, 42073124, 51464002, 18783702, 4099191, 56461322, 33699435, 41442762, 29834512, 16971929, 55143724, 16424199, 4091162, 30811010, 27625735, 54232247, 27665211, 79806380, 49271185, 63015256, 16684829, 50188404, 72238278, 8733068, 37435892, 60955663, 14731700, 72357096, 55669657, 54606384, 86022504, 87710366, 36780454, 56955985, 45848907, 45381876, 57803235, 83155442, 30771409, 32151165, 50806615, 90090964, 92353856, 5111370, 62693428, 65017137, 9886593, 79191827, 99917599, 92803766, 40677414, 38645117, 59371804, 80316608, 22435353, 45667668, 45617087, 69579137, 96852321, 3233084, 17894977, 8099994, 28550822, 10961421, 58224549, 30998561, 82427263, 8651647, 66903004, 47792865, 55470718, 26397786, 36580610, 51472275, 44915235, 15075176, 10309525, 19898053, 78884452, 69605283, 20486294, 35456853, 22942635, 15902805, 54263880, 76170907, 19486173, 30090481, 66116458, 43045786, 38341669, 92867155, 61728685, 47887470, 29959549, 61263068, 89217461, 16097038, 43933006, 3233569, 55189057, 30543215, 8913721, 13468268, 874791, 73617245, 72614359, 37739481, 415901, 83948335, 92692283, 23110625, 9175338, 7685448, 71300104, 27187213, 47708850, 71522968, 7646095, 37620363, 73109997, 7066775, 29466406, 18131876, 88047921, 90004325, 40781449, 82532312, 66428795, 14363867, 85571389, 29994197, 39201414, 45919976, 47298834, 1431742, 73021291, 62496012, 36930650, 38256849, 41092102, 17037369, 84187166, 17386996, 71657078, 46540998, 40534591, 54058788, 247198, 94076128, 168541, 4985896, 22721500, 6521313, 44550764, 69697787, 68694897, 56515456, 18833224, 43501211, 54517921, 32274392, 5267545, 83210802, 90310261, 99549373, 22405120, 76540481, 94516935, 91727510, 26139110, 33431960, 47361209, 21533347, 58208470, 75820087, 90013093, 35092039, 16445503, 72019362, 78785507, 28928175, 3487592, 58749, 91141395, 6221471, 9623492, 45237957, 3880712, 66045587, 49328608, 23569917, 1936762, 9860968, 68816413, 42947632, 95290172, 36468541, 67084351, 30366150, 54014062, 9829782, 749283, 4199704, 9860195, 59405277, 65454636, 90061527, 17365188, 97011160, 14626618, 84166196, 54663246, 64098930, 30891921, 8807940, 12416768, 3773993, 24619760, 30787683, 1569515, 70727211, 79738755, 15064655, 59197747, 24826575, 65880522, 5482538, 13348726, 61928316, 52613508, 33553959, 93933709, 94360702, 7300047, 72793444, 98653983, 82052050, 34698463, 51507425, 80014588, 78909561, 35192533, 7955293, 61741594, 91957544, 72738685, 39847321, 70372191, 43322743, 66885828, 77694700, 81172706, 37501808, 93562257, 18504197, 41245325, 67281495, 55960386, 56473732, 47090124, 89811711, 71316369, 52204879, 2607799, 74357852, 14045383, 16054533, 49641577, 68316156, 57359924, 1204161, 4069912, 99021067, 95726235, 12024238, 60393039, 56484900, 78300864, 77284619, 3294781, 11757872, 74452589, 11581181, 31727379, 50567636, 39986008, 15536795, 26292919, 89078848, 57240218, 6986898, 50702367, 52261574, 46870723, 29819940, 96318094, 6871053, 34432810, 65851721, 73031054, 94911072, 44846932, 77300457, 43506672, 74614639, 45075471, 39553046, 83368048, 32161669, 36139942, 90158785, 23432750, 72725103, 51588015, 9058407, 85242963, 97940276, 66137019, 45790169, 49598724, 26493119, 81855445, 92215320, 57961282, 98462867, 17957593, 72274002, 90457870, 22450468, 40865610, 75128745, 34698428, 88653118, 44664587, 66250369, 508198, 2208785, 96420429, 91802888, 75052463, 91831487, 85023028, 36933038, 49236559, 37280276, 47893286, 36189527, 92398073, 34667286, 32250045, 77272628, 15163258, 89996536, 59614746, 69848388, 98130363, 73392814, 85695762, 21993752, 73222868, 22879907, 38061439, 44177011, 176257, 93790285, 8729146, 88130087, 64157906, 68000591, 75407347, 86301513, 92554120, 20765474, 55850790, 37675718, 37183543, 53666583, 89804152, 36685023, 11543098, 16019925, 66322959, 55615885, 26507214, 36845587, 19101477, 48892201, 34295794, 29635537, 16099750, 92604458, 99690194, 74724075, 44889423, 4508700, 99297164, 38022834, 27041967, 96726697, 77898274, 52060076, 90654836, 28851716, 92692978, 91938191, 19272365, 86798033, 50668599, 68204242, 9257405, 51466049, 83083131, 99226875, 96193415, 70420215, 77072625, 57658654, 62762857, 33201905, 89699445, 68939068, 26744917, 8128637, 53632373, 84127901, 40686254, 1250437, 99515901, 22129328, 14349098, 17727650, 7502255, 37891451, 16380211, 2717150, 67124282, 44627776, 91281584, 57020481, 36753250, 65038678, 71083565, 93566986, 44481640, 61859581, 44842615, 93057697, 19376156, 69136837, 59109400, 83651211, 4787945, 35512853, 4806458, 18001617, 97561745, 8373289, 57241521, 78218845, 19891772, 44348328, 69641513, 19939935, 71965942, 38510840, 21001913, 51715482, 93359396, 65271999, 42782652, 55602660, 6038457, 30694952, 20568363, 81274566, 75229462, 14326617, 95010552, 88444207, 26392416, 91990218, 57393458, 84406788, 10358899, 4978543, 34493392, 62430984, 32159704, 14723410, 61712234, 16595436, 24058273, 53393358, 38658347, 21673260, 80246713, 40984766, 33061250, 64602895, 67031644, 77377183, 62552524, 47213183, 78561158, 6111563, 4204661, 42307449, 26063929, 84293052, 65081429, 2331773, 80555751, 62936963, 30463802, 32590267, 41380093, 92541302, 18411915, 89214616, 69786756, 92998591, 22766820, 5073754, 29029316, 79136082, 6808825, 22113133, 5970607, 41481685, 82651278, 61859143, 24915585, 24314885, 87720882, 23194618, 72777973, 31491938, 6819644, 59501487, 34497327, 37166608, 62428472, 67513640, 77413012, 36396314, 76330843, 44060493, 94595668, 30163921, 61897582, 72278539, 48893685, 9398733, 72358170, 99125126, 61141874, 95251277, 82339363, 68824981, 15148031, 23848565, 60176618, 20642888, 26102057, 79942022, 29510992, 17539625, 26863229, 86001008, 33304202, 68102437, 62357986, 8055981, 36312813, 96735716, 4515343, 57248122, 98943869, 60106217, 61271144, 37957788, 17976208, 81677380, 94539824, 6525948, 1197320, 20836893, 81853704, 65338021, 81898046, 62490109, 68110330, 89046466, 48360198, 76434144, 22108665, 8129978, 75777973, 73168565, 60581278, 33123618, 17016156, 1022677, 77301523, 39171895, 42199455, 76671482, 52293995, 54427233, 46851987, 62740044, 49597667, 37560164, 15535065, 6793819, 112651, 44245960, 70367851, 51426311, 86118021, 58751351, 39373729, 77787724, 43376279, 85711894, 59910624, 97379790, 72732205, 69355476, 74110882, 33935899, 65074535, 56153345, 99524975, 55247455, 4668450, 98948034, 30218878, 9603598, 97783876, 24168411, 33565483, 17081350, 17385531, 82726008, 47738185, 48774913, 34946859, 67793644, 97641116, 86821229, 74743862, 8696647, 45306070, 19457589, 45049308, 16387575, 41092172, 83533741, 93053405, 4434662, 33336148, 44473167, 25842078, 27185644, 87160386, 75153252, 3088684, 53842979, 60430369, 78549759, 38494874, 70782102, 22200378, 20885148, 26734892, 45794415, 53648154, 83789391, 37224844, 3235882, 77312810, 23134715, 76703609, 79880247, 30653863, 38365584, 33249630, 50007421, 17146629, 68644627, 95957797, 91664334, 36135, 4119747, 7687278, 10264691, 43152977, 20645197, 64055960, 99333375, 37192445, 12664567, 10453030, 91255408, 526217, 91240048, 13470059, 75543508, 17068582, 43357947, 26998766, 66611759, 48395186, 13862149, 63628376, 6157724, 53802686, 72495719, 24591705, 20681921, 70596786, 89499542, 55770687, 62232211, 31733363, 45990383, 69255765, 86543538, 40197395, 12303248, 6505939, 23740167, 74441448, 77187825, 99224392, 21397057, 62452034, 36812683, 70800879, 99658235, 33628349, 51315460, 9259676, 51590803, 17857111, 61373987, 30569392, 39801351, 4798568, 1239555, 96906410, 36808486, 29401781, 94711842, 1375023, 4064751, 11365791, 2917920, 64848072, 70004753, 67030811, 82779622, 15948937, 62051033, 29932657, 53084256 +47792865, 72373496, 76330843, 68824981, 55960386, 58751351, 95957797, 98948034, 94595668, 35996293, 69579137, 87598888, 8129978, 57163802, 37620363, 6808825, 37659250, 19272365, 5832946, 90310261, 2891150, 96735716, 96420429, 82178706, 53666583, 79136082, 83747892, 5970607, 49641577, 7517032, 49271185, 18806856, 31491938, 99524975, 33201905, 26744917, 16380211, 66832478, 14220886, 68694897, 39553046, 19376156, 69136837, 14947650, 47361209, 68102437, 8055981, 9623492, 93270084, 26776922, 508198, 98943869, 59981773, 26392416, 6497830, 92398073, 78884452, 38658347, 44844121, 8729146, 98648327, 68000591, 10649306, 92867155, 30463802, 415901, 49597667, 39847321, 92604458, 70541760, 22113133, 78766358, 79806380, 72238278, 64055960, 60955663, 20140249, 96193415, 11757872, 45996863, 84127901, 17385531, 10453030, 96318094, 2717150, 69697787, 56515456, 65017137, 43501211, 93057697, 85242963, 75543508, 49598724, 21001913, 90013093, 72019362, 28928175, 40865610, 66045587, 30998561, 30694952, 36933038, 10358899, 17365188, 38008118, 45794415, 73392814, 89499542, 21993752, 8807940, 40984766, 68110330, 15064655, 61928316, 62552524, 95395112, 55850790, 61263068, 37675718, 4204661, 40197395, 54427233, 51507425, 73617245, 36845587, 62936963, 71300104, 22766820, 77694700, 12348118, 18663507, 86118021, 4508700, 34044787, 33237508, 52204879, 91664334, 92692978, 9257405, 95726235, 20645197, 78300864, 83083131, 40781854, 83269727, 12664567, 53632373, 99515901, 34432810, 97641116, 44983451, 22721500, 44846932, 57020481, 74614639, 32274392, 4787945, 76540481, 59371804, 44473167, 29510992, 58208470, 38510840, 16445503, 90457870, 93359396, 26998766, 58749, 34698428, 73235980, 28787861, 54199160, 3183975, 68816413, 13862149, 6157724, 15163258, 67227442, 20681921, 65715134, 24058273, 65338021, 21070110, 62232211, 86460488, 33061250, 30395570, 31733363, 76170907, 47213183, 69255765, 63059024, 7423788, 98739783, 13819358, 20765474, 65275241, 61728685, 60581278, 29959549, 569864, 33553959, 42967683, 17058722, 8913721, 30653863, 61815223, 19101477, 91957544, 38365584, 77620120, 85116755, 89214616, 33249630, 66885828, 44245960, 71522968, 56461322, 7011964, 25636669, 29834512, 43376279, 16424199, 77898274, 90004325, 69355476, 27665211, 18699206, 33935899, 4069912, 50668599, 23194618, 72777973, 85571389, 8733068, 24953498, 74441448, 4668450, 99226875, 61982238, 77072625, 55669657, 38256849, 37166608, 37192445, 89078848, 96709982, 22129328, 48774913, 40534591, 6871053, 67793644, 50806615, 90090964, 48893685, 45306070, 36812683, 77300457, 18833224, 54517921, 99917599, 31904591, 93566986, 32161669, 92803766, 35512853, 17764950, 22405120, 4095116, 91727510, 93053405, 29516592, 8634541, 33431960, 81855445, 69641513, 96852321, 71965942, 84840549, 88251446, 48395186, 44664587, 66250369, 74137926, 26114953, 91831487, 36468541, 88444207, 67084351, 91990218, 57393458, 84406788, 9829782, 48675329, 36189527, 28734791, 53802686, 90061527, 51590803, 97011160, 14626618, 34493392, 32699744, 53648154, 15902805, 38061439, 24619760, 61373987, 40027975, 30090481, 22108665, 9575954, 78561158, 5640302, 31161687, 62803858, 29932657, 89217461, 48088883, 26275734, 58180653, 82052050, 11543098, 76703609, 66322959, 80014588, 84904436, 84002370, 4798568, 44847298, 80555751, 78909561, 7955293, 83948335, 73124510, 72738685, 70372191, 59177718, 32426752, 42073124, 63967300, 42692881, 5073754, 70367851, 37501808, 67281495, 51426311, 47090124, 17146629, 41442762, 89811711, 59329511, 71333116, 29466406, 2607799, 85711894, 41481685, 82651278, 4091162, 24915585, 72732205, 54232247, 71920426, 7687278, 86798033, 68204242, 29994197, 39201414, 16567550, 1431742, 20002147, 67474219, 30218878, 3294781, 62496012, 59501487, 41092102, 89699445, 84187166, 50567636, 45848907, 17386996, 40686254, 52261574, 82726008, 99224392, 17727650, 47738185, 54058788, 37891451, 72278539, 92353856, 9398733, 5111370, 62693428, 91281584, 36753250, 16387575, 61960400, 71083565, 83368048, 82339363, 9599614, 84349107, 8115266, 4806458, 45407418, 26102057, 83533741, 66137019, 26139110, 45790169, 4434662, 33797252, 8373289, 94090109, 78218845, 3509435, 98462867, 25842078, 83133790, 33304202, 75128745, 88653118, 45237957, 95581843, 79657802, 53842979, 62115552, 78602717, 9259676, 8651647, 66903004, 85023028, 42947632, 14093520, 55470718, 75229462, 95290172, 70782102, 61271144, 54014062, 17976208, 9860195, 59405277, 11161731, 18466635, 22997281, 89996536, 84166196, 64098930, 6525948, 26734892, 6153406, 69605283, 30891921, 12416768, 99861373, 75135448, 48360198, 92071990, 66116458, 82886381, 21289531, 17016156, 77301523, 16097038, 43933006, 7300047, 99729357, 13468268, 65081429, 2331773, 46851987, 37739481, 1239555, 92541302, 18411915, 74724075, 44889423, 81172706, 2204165, 4099191, 33699435, 8791066, 71316369, 18131876, 7182642, 30811010, 82532312, 1204161, 65074535, 50188404, 87720882, 29401781, 73786944, 51466049, 83302115, 1375023, 73021291, 824872, 34497327, 57658654, 74452589, 86022504, 24168411, 11581181, 31727379, 39986008, 45381876, 8128637, 15536795, 6986898, 50702367, 17081350, 1250437, 32151165, 45995325, 40152546, 94076128, 10597197, 99971982, 97057187, 65851721, 321665, 62452034, 99125126, 29065964, 19457589, 45049308, 45075471, 26664538, 82327024, 90158785, 70800879, 22435353, 97281847, 80251430, 13173644, 21533347, 92215320, 75820087, 44348328, 26863229, 17957593, 17894977, 35092039, 22450468, 49882705, 99658235, 61623915, 53084256, 3487592, 11923835, 6221471, 36312813, 49328608, 79922758, 91802888, 6038457, 81805959, 23569917, 38494874, 14326617, 36580610, 70036438, 4978543, 15075176, 20885148, 34667286, 27325428, 68128438, 62430984, 32159704, 53547802, 53393358, 55770687, 85695762, 21673260, 3773993, 44177011, 89046466, 1569515, 70004753, 176257, 20867149, 70727211, 64602895, 75407347, 30569392, 68875490, 75777973, 6111563, 60102965, 76671482, 52293995, 34698463, 63506281, 26507214, 62740044, 72614359, 48892201, 15535065, 51047803, 112651, 12303248, 27187213, 7646095, 73109997, 36808486, 18783702, 56473732, 39373729, 68644627, 38022834, 77787724, 27041967, 74357852, 31126490, 81774825, 68316156, 57359924, 27625735, 24314885, 91938191, 16684829, 56153345, 60393039, 47298834, 37435892, 55247455, 67030811, 72357096, 70420215, 36930650, 79322415, 54606384, 33565483, 67513640, 57240218, 4064751, 29819940, 34946859, 73031054, 53888755, 4985896, 82897371, 2917920, 67124282, 56531125, 9886593, 61141874, 44627776, 95251277, 92530431, 44481640, 61859581, 44842615, 23848565, 72725103, 99549373, 92787493, 41092172, 16405341, 79942022, 45667668, 97561745, 57241521, 19891772, 57961282, 84684495, 88698958, 19939935, 17068582, 43357947, 8099994, 91141395, 66611759, 54762643, 3088684, 66667729, 68702632, 3880712, 7229550, 33628349, 51315460, 1936762, 81274566, 60106217, 21919959, 42237907, 63628376, 64848072, 4199704, 67451935, 81677380, 94539824, 54663246, 98130363, 17857111, 7919588, 61712234, 15015906, 81853704, 35456853, 22879907, 22942635, 62490109, 54263880, 83789391, 12571310, 19486173, 24826575, 5482538, 78589145, 44784505, 3235882, 52613508, 86301513, 39801351, 62051033, 33123618, 47887470, 77312810, 82979980, 93933709, 23134715, 86543538, 89804152, 72793444, 16019925, 79880247, 36505482, 61741594, 29635537, 9188443, 7685448, 69786756, 92998591, 43322743, 47708850, 51464002, 41245325, 16971929, 97379790, 36135, 52060076, 40781449, 28851716, 63015256, 14363867, 44479073, 99021067, 56484900, 86242799, 14731700, 6819644, 5822202, 56424103, 9603598, 62762857, 97783876, 77187825, 36780454, 17037369, 68939068, 14349098, 46870723, 21397057, 71657078, 79821897, 59318837, 247198, 86821229, 6521313, 44550764, 74743862, 8696647, 88904910, 526217, 65038678, 43506672, 79191827, 24733232, 10366309, 36139942, 91240048, 83210802, 40677414, 38645117, 59109400, 27411561, 94516935, 33336148, 26493119, 18001617, 86001008, 76315420, 78785507, 28550822, 65271999, 58224549, 4515343, 57248122, 55602660, 9860968, 89419466, 83150534, 95010552, 49236559, 749283, 51472275, 93515664, 37957788, 34236719, 1197320, 20836893, 24591705, 16595436, 3633375, 70596786, 19898053, 20486294, 81898046, 38009615, 64087743, 99604946, 30787683, 37224844, 67031644, 43045786, 42644903, 73168565, 94360702, 3233569, 55189057, 66271566, 42307449, 48260151, 26063929, 874791, 84293052, 35192533, 32590267, 41380093, 75986488, 6793819, 16099750, 98371444, 48658605, 54868730, 71469330, 18504197, 88047921, 55143724, 14045383, 16054533, 32058615, 74110882, 76960303, 45919976, 10264691, 66663942, 87710366, 56955985, 26292919, 57803235, 82779622, 168541, 669105, 91255408, 94911072, 15148031, 5267545, 51588015, 9058407, 97940276, 45617087, 17539625, 13231279, 3233084, 51715482, 87160386, 63372756, 33895336, 42782652, 2208785, 60430369, 78549759, 20568363, 75052463, 26397786, 30366150, 37280276, 47893286, 44915235, 65454636, 32250045, 59614746, 73222868, 55753905, 64157906, 45990383, 1022677, 36685023, 37560164, 29188588, 9175338, 96906410, 99690194, 93562257, 6505939, 7066775, 99965001, 99297164, 96726697, 59910624, 90654836, 4119747, 92033260, 94711842, 43152977, 77284619, 36396314, 30771409, 7502255, 54987042, 60176618, 13470059, 83651211, 20642888, 48673079, 75153252, 10961421, 7104732, 1788101, 10309525, 72495719, 77272628, 30764367, 69848388, 79738755, 59197747, 13348726, 98653983, 42199455, 51149804, 85117092, 55615885, 23110625, 34295794, 45428665, 50007421, 61859143, 20122224, 65047700, 66428795, 40356877, 66319530, 62428472, 77413012, 44060493, 46540998, 89637706, 23432750, 90272749, 30139692, 62357986, 82427263, 22200378, 15948937, 14723410, 93790285, 65880522, 88130087, 38341669, 37183543, 29029316, 63152504, 23740167, 12024238, 83155442, 72358170, 76825057, 80316608, 68041839, 80246713, 77377183, 92554120, 39171895, 30543215, 92692283, 99333375, 61897582, 11365791, 27185644, 30163921, 72274002, 28796059, 76434144 +6819644, 7502255, 86821229, 15148031, 13231279, 3633375, 79880247, 99965001, 74743862, 83368048, 4806458, 13819358, 61815223, 92692283, 36780454, 39986008, 97057187, 93566986, 57961282, 28928175, 28796059, 79657802, 11161731, 90061527, 84166196, 69605283, 35456853, 30787683, 5482538, 569864, 82979980, 39171895, 66322959, 18663507, 68316156, 30218878, 33565483, 67513640, 73031054, 67124282, 72358170, 60176618, 4787945, 27411561, 17894977, 17068582, 26998766, 57393458, 36189527, 10309525, 94539824, 30395570, 61373987, 77377183, 66116458, 43045786, 98739783, 92554120, 1022677, 86543538, 3233569, 40197395, 98371444, 12348118, 18783702, 56473732, 58751351, 71333116, 96726697, 59910624, 32058615, 77898274, 61859143, 24915585, 27665211, 65074535, 56153345, 73786944, 83302115, 60393039, 86242799, 20002147, 36930650, 97783876, 62428472, 99224392, 17727650, 34946859, 669105, 6521313, 44550764, 11365791, 8696647, 5111370, 56531125, 99125126, 32161669, 61859581, 91240048, 83210802, 35512853, 41092172, 68041839, 26493119, 97561745, 90272749, 8373289, 47361209, 87160386, 88653118, 66250369, 508198, 2208785, 96420429, 9860968, 81274566, 83150534, 95290172, 54014062, 47893286, 48675329, 15075176, 15948937, 77272628, 81677380, 54663246, 19898053, 78884452, 62232211, 99604946, 1569515, 99861373, 76434144, 64157906, 30090481, 3235882, 38341669, 29932657, 82886381, 21289531, 72793444, 42199455, 63506281, 874791, 84904436, 37739481, 37560164, 45428665, 77694700, 29029316, 37501808, 93562257, 47090124, 74357852, 49641577, 41481685, 28851716, 24314885, 1204161, 85571389, 29994197, 51466049, 94711842, 5822202, 66663942, 62762857, 41092102, 24168411, 33201905, 40686254, 82726008, 47738185, 82779622, 32151165, 6871053, 247198, 61897582, 91255408, 22721500, 82897371, 68694897, 44627776, 36812683, 91281584, 93057697, 90310261, 13470059, 83651211, 97940276, 33336148, 13173644, 98462867, 27185644, 28550822, 91141395, 63372756, 66611759, 9623492, 36312813, 3880712, 42782652, 6038457, 62115552, 81805959, 75052463, 38494874, 95010552, 70782102, 67084351, 42237907, 30366150, 4978543, 22997281, 6525948, 98130363, 26734892, 67227442, 53393358, 73392814, 89499542, 20486294, 73222868, 22879907, 44844121, 80246713, 64087743, 75135448, 59197747, 88130087, 22108665, 61928316, 62552524, 78561158, 5640302, 68875490, 95395112, 42644903, 65275241, 55850790, 47887470, 17016156, 16097038, 7300047, 53666583, 89804152, 55189057, 82052050, 99729357, 84293052, 26507214, 44847298, 80555751, 62936963, 78909561, 1239555, 83948335, 32590267, 72738685, 6793819, 85116755, 18411915, 51047803, 70372191, 92998591, 66885828, 74724075, 81172706, 70541760, 4099191, 8791066, 22113133, 71316369, 59329511, 16971929, 88047921, 55143724, 72732205, 69355476, 14363867, 50188404, 10264691, 8733068, 56484900, 55247455, 73021291, 61982238, 9603598, 68939068, 83155442, 46540998, 65851721, 88904910, 71083565, 54517921, 24733232, 51588015, 99549373, 35996293, 26139110, 22435353, 45617087, 57241521, 19891772, 81855445, 84684495, 96852321, 25842078, 90013093, 72274002, 43357947, 93359396, 75153252, 11923835, 34698428, 8055981, 66667729, 79922758, 91802888, 3183975, 23569917, 8651647, 1936762, 91831487, 42947632, 55470718, 21919959, 36933038, 1788101, 26397786, 36580610, 13862149, 49236559, 22200378, 64848072, 44915235, 72495719, 34236719, 14723410, 7919588, 70596786, 85695762, 38658347, 21673260, 38009615, 20867149, 70727211, 31733363, 93790285, 76170907, 12571310, 9575954, 69255765, 75407347, 39801351, 37675718, 33553959, 51149804, 85117092, 48260151, 30653863, 65081429, 2331773, 84002370, 48892201, 38365584, 39847321, 92604458, 112651, 43322743, 5073754, 27187213, 44889423, 44245960, 71522968, 7646095, 79136082, 51464002, 25636669, 39373729, 38022834, 29466406, 27041967, 2607799, 14045383, 16054533, 97379790, 82651278, 71920426, 65047700, 86798033, 87720882, 72238278, 72373496, 16567550, 18806856, 12024238, 83083131, 66319530, 99226875, 77072625, 89699445, 31727379, 17037369, 45996863, 77413012, 36396314, 96709982, 76330843, 4064751, 29819940, 79821897, 16380211, 10597197, 97641116, 53888755, 72278539, 62693428, 94911072, 65017137, 9886593, 45049308, 65038678, 61960400, 92530431, 68824981, 10366309, 36139942, 92803766, 59109400, 23432750, 48673079, 66137019, 2891150, 18001617, 8634541, 33431960, 19939935, 3233084, 38510840, 21001913, 16445503, 51715482, 22450468, 84840549, 75128745, 58749, 10961421, 65271999, 62357986, 73235980, 68702632, 53842979, 57248122, 74137926, 26114953, 98943869, 89419466, 75229462, 84406788, 70036438, 4199704, 37957788, 18466635, 32250045, 97011160, 68128438, 15163258, 38008118, 81853704, 53547802, 15902805, 8807940, 12416768, 70004753, 48360198, 67031644, 13348726, 98648327, 45990383, 92071990, 86301513, 63059024, 31161687, 60581278, 77312810, 89217461, 23134715, 94360702, 60102965, 26275734, 17058722, 30543215, 52293995, 76703609, 26063929, 16019925, 80014588, 73617245, 36505482, 30463802, 19101477, 91957544, 49597667, 77620120, 23110625, 16099750, 48658605, 42692881, 6808825, 41245325, 43376279, 31126490, 52060076, 54232247, 18699206, 19272365, 76960303, 16684829, 50668599, 9257405, 39201414, 1431742, 40781854, 67030811, 3294781, 20140249, 56424103, 74452589, 86022504, 87710366, 99333375, 45848907, 12664567, 6986898, 50702367, 17081350, 14349098, 46870723, 21397057, 44060493, 40534591, 40152546, 94076128, 50806615, 66832478, 4985896, 48893685, 9398733, 45306070, 56515456, 95251277, 18833224, 45075471, 82339363, 44481640, 69136837, 40677414, 90158785, 17764950, 20642888, 22405120, 4095116, 83533741, 94516935, 59371804, 93053405, 33797252, 49598724, 94090109, 80251430, 69579137, 58208470, 88698958, 71965942, 33304202, 35092039, 72019362, 40865610, 6221471, 45237957, 30998561, 4515343, 78549759, 30694952, 33628349, 51315460, 85023028, 60106217, 14326617, 61271144, 88444207, 91990218, 63628376, 93515664, 67451935, 17976208, 9860195, 65454636, 34493392, 32159704, 89996536, 64098930, 69848388, 24591705, 61712234, 15015906, 16595436, 6153406, 21070110, 82178706, 40984766, 86460488, 33061250, 3773993, 54263880, 79738755, 37224844, 55753905, 24826575, 65880522, 7423788, 20765474, 62803858, 61728685, 61263068, 37183543, 93933709, 42967683, 43933006, 98653983, 8913721, 13468268, 54427233, 36845587, 73124510, 41380093, 34295794, 75986488, 29188588, 9175338, 71300104, 69786756, 63967300, 47708850, 37620363, 63152504, 33699435, 17146629, 4508700, 89811711, 68644627, 95957797, 77787724, 29834512, 91664334, 85711894, 81774825, 7517032, 30811010, 37659250, 74110882, 33935899, 91938191, 66428795, 63015256, 72777973, 40356877, 95726235, 47298834, 20645197, 77284619, 72357096, 96193415, 34497327, 70420215, 57658654, 77187825, 84187166, 37192445, 45381876, 15536795, 1250437, 5832946, 71657078, 45995325, 37891451, 168541, 30163921, 2717150, 90090964, 44846932, 29065964, 19457589, 36753250, 77300457, 43501211, 32274392, 26664538, 23848565, 38645117, 92787493, 70800879, 91727510, 75820087, 3509435, 69641513, 26863229, 86001008, 83133790, 76315420, 49882705, 68102437, 61623915, 53084256, 3487592, 48395186, 54762643, 44664587, 93270084, 28787861, 33895336, 26776922, 49328608, 82427263, 60430369, 7229550, 14093520, 47792865, 10358899, 9829782, 59405277, 53802686, 14626618, 17857111, 24058273, 53648154, 81898046, 22942635, 62490109, 89046466, 176257, 8729146, 19486173, 44784505, 73168565, 33123618, 29959549, 77301523, 34698463, 4798568, 35192533, 89214616, 96906410, 32426752, 42073124, 70367851, 36808486, 6505939, 23740167, 51426311, 50007421, 56461322, 41442762, 33237508, 18131876, 7182642, 16424199, 4091162, 20122224, 40781449, 4119747, 49271185, 23194618, 29401781, 45919976, 31491938, 1375023, 43152977, 37435892, 64055960, 78300864, 60955663, 14731700, 67474219, 824872, 62496012, 11757872, 55669657, 54606384, 37166608, 11581181, 50567636, 52261574, 48774913, 30771409, 10453030, 94595668, 99971982, 54987042, 44983451, 14220886, 2917920, 57020481, 16387575, 526217, 99917599, 82327024, 9599614, 8115266, 16405341, 9058407, 45667668, 30139692, 92215320, 44348328, 17957593, 8099994, 7104732, 78602717, 9259676, 20568363, 66903004, 59981773, 28734791, 6157724, 92398073, 27325428, 30764367, 59614746, 1197320, 20836893, 20681921, 65338021, 21993752, 30891921, 68000591, 30569392, 10649306, 6111563, 48088883, 66271566, 11543098, 51507425, 72614359, 7955293, 92541302, 15535065, 29635537, 57163802, 54868730, 33249630, 22766820, 71469330, 67281495, 7066775, 86118021, 7011964, 52204879, 92692978, 82532312, 92033260, 4069912, 44479073, 68204242, 98948034, 83269727, 26744917, 56955985, 84127901, 57240218, 17386996, 17385531, 99515901, 22129328, 54058788, 59318837, 34432810, 321665, 62452034, 61141874, 43506672, 74614639, 39553046, 79191827, 44842615, 84349107, 76825057, 72725103, 76540481, 26102057, 85242963, 79942022, 45790169, 29516592, 78218845, 17539625, 90457870, 78785507, 99658235, 58224549, 3088684, 95581843, 54199160, 55602660, 68816413, 51472275, 6497830, 24619760, 44177011, 83789391, 64602895, 52613508, 47213183, 62051033, 92867155, 55615885, 415901, 7685448, 99690194, 59177718, 12303248, 2204165, 73109997, 83747892, 55960386, 5970607, 57359924, 90004325, 99021067, 74441448, 4668450, 59501487, 79322415, 8128637, 26292919, 89078848, 67793644, 92353856, 69697787, 89637706, 5267545, 19376156, 14947650, 75543508, 97281847, 29510992, 21533347, 36468541, 26392416, 37280276, 749283, 51590803, 17365188, 62430984, 65715134, 45794415, 68110330, 87598888, 78589145, 8129978, 75777973, 36685023, 76671482, 42307449, 62740044, 9188443, 18504197, 99297164, 34044787, 78766358, 7687278, 99524975, 24953498, 53632373, 57803235, 80316608, 4434662, 44473167, 88251446, 96735716, 20885148, 15064655, 4204661, 61741594, 36135, 90654836, 79806380, 96318094, 31904591, 32699744, 55770687, 38061439, 58180653, 46851987, 27625735, 38256849, 45407418, 66045587, 40027975, 34667286 +18001617, 92692283, 77620120, 54058788, 82897371, 36580610, 67451935, 40984766, 112651, 43376279, 20645197, 64055960, 9603598, 11581181, 14349098, 23432750, 17957593, 35092039, 93270084, 64848072, 30764367, 24591705, 70596786, 59197747, 92554120, 77312810, 82979980, 40197395, 36845587, 9175338, 98371444, 7685448, 63152504, 92692978, 82532312, 66428795, 55669657, 10453030, 61897582, 72278539, 8696647, 44481640, 38645117, 13470059, 26493119, 92215320, 88698958, 19939935, 83133790, 68102437, 7104732, 68702632, 81805959, 51315460, 75052463, 38494874, 34667286, 22942635, 64602895, 5482538, 45990383, 39801351, 62051033, 874791, 72614359, 72738685, 74724075, 41245325, 96726697, 50668599, 56484900, 6819644, 84187166, 26292919, 77413012, 84127901, 96709982, 48774913, 54987042, 69697787, 88904910, 19457589, 91281584, 74614639, 79942022, 97940276, 45790169, 3509435, 44348328, 99658235, 3487592, 58749, 53842979, 91802888, 78549759, 33628349, 9259676, 14326617, 10358899, 63628376, 47893286, 53802686, 15163258, 89996536, 19898053, 21993752, 89046466, 30090481, 47213183, 69255765, 5640302, 95395112, 42644903, 29932657, 29959549, 23134715, 94360702, 39171895, 76703609, 48260151, 84002370, 73124510, 37560164, 44245960, 47708850, 70367851, 73109997, 47090124, 8791066, 33237508, 89811711, 71316369, 95957797, 61859143, 52060076, 28851716, 91938191, 65074535, 99021067, 29994197, 95726235, 60393039, 24953498, 77072625, 74452589, 87710366, 37166608, 33565483, 57803235, 82779622, 21397057, 46540998, 97057187, 2717150, 11365791, 321665, 94911072, 36812683, 45049308, 71083565, 45075471, 83651211, 35996293, 75543508, 4434662, 33336148, 29510992, 13231279, 75820087, 98462867, 71965942, 21001913, 10961421, 65271999, 6221471, 54762643, 62357986, 508198, 9860968, 42947632, 47792865, 75229462, 83150534, 13862149, 17976208, 6157724, 72495719, 14626618, 6525948, 61712234, 32699744, 65338021, 89499542, 8807940, 3773993, 54263880, 76170907, 12571310, 55753905, 65880522, 68000591, 92867155, 60581278, 569864, 7300047, 26275734, 72793444, 82052050, 66271566, 51149804, 26063929, 16019925, 62740044, 78909561, 7955293, 19101477, 83948335, 91957544, 29188588, 39847321, 57163802, 99690194, 92998591, 22766820, 43322743, 27187213, 36808486, 99297164, 41442762, 39373729, 34044787, 29466406, 52204879, 31126490, 14045383, 49641577, 57359924, 4119747, 54232247, 92033260, 29401781, 9257405, 39201414, 8733068, 94711842, 12024238, 37435892, 83083131, 96193415, 31727379, 37192445, 53632373, 40686254, 30771409, 67793644, 168541, 91255408, 14220886, 9398733, 5111370, 62693428, 62452034, 72358170, 99125126, 43506672, 32161669, 82327024, 23848565, 90310261, 60176618, 72725103, 4806458, 76540481, 85242963, 14947650, 80316608, 45667668, 97561745, 8634541, 3233084, 25842078, 38510840, 27185644, 33304202, 72274002, 16445503, 87160386, 72019362, 26998766, 28550822, 66611759, 88653118, 73235980, 79657802, 96735716, 2208785, 79922758, 23569917, 91831487, 70782102, 67084351, 749283, 27325428, 90061527, 17365188, 62430984, 94539824, 73392814, 21070110, 21673260, 68110330, 33061250, 30787683, 93790285, 40027975, 37224844, 88130087, 13348726, 77377183, 30569392, 13819358, 62803858, 55850790, 1022677, 61263068, 13468268, 46851987, 4798568, 44847298, 30463802, 49597667, 41380093, 6793819, 16099750, 89214616, 59177718, 12303248, 66885828, 12348118, 29029316, 71469330, 6505939, 51464002, 51426311, 18783702, 86118021, 71333116, 16971929, 2607799, 74357852, 37659250, 24314885, 18699206, 33935899, 7687278, 1204161, 65047700, 14363867, 44479073, 85571389, 31491938, 1375023, 43152977, 78300864, 66663942, 70420215, 11757872, 62762857, 79322415, 26744917, 36396314, 82726008, 46870723, 5832946, 40534591, 32151165, 6871053, 37891451, 34432810, 86821229, 4985896, 92353856, 68694897, 89637706, 9886593, 29065964, 57020481, 16387575, 83368048, 82339363, 15148031, 9599614, 76825057, 90158785, 4787945, 51588015, 20642888, 83533741, 66137019, 26139110, 22435353, 8373289, 47361209, 21533347, 69641513, 96852321, 86001008, 17068582, 8099994, 51715482, 75153252, 11923835, 63372756, 45237957, 28796059, 33895336, 30998561, 49328608, 42782652, 82427263, 96420429, 78602717, 26114953, 20568363, 98943869, 85023028, 37280276, 36189527, 44915235, 93515664, 28734791, 15075176, 59405277, 92398073, 15948937, 51590803, 77272628, 97011160, 32159704, 84166196, 64098930, 26734892, 20681921, 44844121, 62490109, 64087743, 30395570, 83789391, 87598888, 31733363, 8729146, 19486173, 48360198, 98648327, 61928316, 44784505, 3235882, 92071990, 86301513, 8129978, 7423788, 43045786, 21289531, 47887470, 77301523, 89217461, 93933709, 98653983, 55189057, 36685023, 52293995, 99729357, 51507425, 79880247, 84293052, 73617245, 84904436, 36505482, 61741594, 415901, 1239555, 92541302, 34295794, 15535065, 75986488, 85116755, 32426752, 71522968, 6808825, 23740167, 7011964, 17146629, 4508700, 58751351, 77787724, 97379790, 32058615, 68316156, 7517032, 30811010, 27625735, 76960303, 16684829, 56153345, 73786944, 16567550, 74441448, 66319530, 67474219, 67030811, 72357096, 99226875, 56424103, 86022504, 36780454, 62428472, 68939068, 67513640, 45848907, 89078848, 6986898, 50702367, 99515901, 52261574, 17727650, 247198, 10597197, 73031054, 44983451, 6521313, 44550764, 65017137, 61141874, 36753250, 18833224, 61960400, 54517921, 79191827, 61859581, 44842615, 36139942, 84349107, 91240048, 92803766, 83210802, 8115266, 41092172, 16405341, 48673079, 4095116, 2891150, 45617087, 90272749, 44473167, 57241521, 33431960, 69579137, 58208470, 57961282, 84684495, 93359396, 88251446, 40865610, 53084256, 9623492, 66250369, 57248122, 30694952, 8651647, 68816413, 60106217, 89419466, 59981773, 21919959, 95290172, 36933038, 54014062, 51472275, 4199704, 37957788, 65454636, 11161731, 18466635, 32250045, 81677380, 22997281, 68128438, 38008118, 98130363, 7919588, 20836893, 15015906, 24058273, 53393358, 35456853, 53648154, 73222868, 12416768, 86460488, 24619760, 61373987, 70004753, 75135448, 9575954, 75407347, 20765474, 65275241, 38341669, 73168565, 86543538, 16097038, 60102965, 48088883, 4204661, 3233569, 53666583, 58180653, 76671482, 85117092, 80555751, 23110625, 29635537, 9188443, 96906410, 54868730, 42073124, 63967300, 2204165, 37620363, 93562257, 79136082, 18504197, 70541760, 7066775, 50007421, 4099191, 68644627, 29834512, 85711894, 36135, 4091162, 40781449, 63015256, 4069912, 50188404, 87720882, 23194618, 51466049, 83302115, 99524975, 60955663, 4668450, 98948034, 3294781, 62496012, 59501487, 34497327, 97783876, 38256849, 41092102, 89699445, 17037369, 8128637, 15536795, 12664567, 17386996, 76330843, 99224392, 4064751, 47738185, 29819940, 7502255, 59318837, 65851721, 67124282, 56531125, 77300457, 39553046, 93566986, 26664538, 69136837, 59109400, 45407418, 22405120, 9058407, 70800879, 94516935, 80251430, 19891772, 17539625, 43357947, 49882705, 8055981, 66667729, 28787861, 3880712, 26776922, 55602660, 62115552, 74137926, 81274566, 95010552, 61271144, 26392416, 42237907, 22200378, 48675329, 70036438, 6497830, 4978543, 20885148, 59614746, 54663246, 17857111, 67227442, 16595436, 53547802, 45794415, 20486294, 62232211, 81898046, 80246713, 38061439, 1569515, 70727211, 76434144, 64157906, 22108665, 31161687, 75777973, 33123618, 37675718, 33553959, 6111563, 89804152, 11543098, 34698463, 63506281, 80014588, 37739481, 35192533, 48892201, 45428665, 5073754, 83747892, 67281495, 27041967, 18131876, 7182642, 55143724, 16054533, 59910624, 41481685, 77898274, 82651278, 20122224, 90654836, 74110882, 19272365, 86798033, 40356877, 10264691, 18806856, 1431742, 30218878, 77187825, 24168411, 99333375, 57240218, 83155442, 22129328, 71657078, 96318094, 45995325, 94595668, 30163921, 53888755, 90090964, 48893685, 2917920, 56515456, 44627776, 526217, 65038678, 43501211, 68824981, 93057697, 19376156, 35512853, 91727510, 68041839, 78218845, 81855445, 26863229, 76315420, 90457870, 84840549, 61623915, 75128745, 91141395, 3088684, 95581843, 66045587, 7229550, 66903004, 34493392, 14723410, 81853704, 65715134, 3633375, 6153406, 78884452, 69605283, 38658347, 176257, 99861373, 79738755, 24826575, 78589145, 52613508, 78561158, 68875490, 10649306, 82886381, 37183543, 42967683, 17058722, 30543215, 8913721, 42307449, 66322959, 55615885, 62936963, 61815223, 32590267, 18411915, 48658605, 51047803, 71300104, 42692881, 44889423, 81172706, 25636669, 38022834, 91664334, 88047921, 81774825, 90004325, 72732205, 27665211, 79806380, 68204242, 47298834, 86242799, 40781854, 73021291, 5822202, 36930650, 54606384, 83269727, 50567636, 39986008, 56955985, 45381876, 17385531, 1250437, 99971982, 66832478, 74743862, 45306070, 95251277, 32274392, 92530431, 5267545, 40677414, 99549373, 17764950, 59371804, 30139692, 29516592, 22450468, 78785507, 48395186, 58224549, 44664587, 60430369, 14093520, 36468541, 88444207, 1788101, 30366150, 9860195, 10309525, 1197320, 55770687, 82178706, 15902805, 44177011, 20867149, 67031644, 62552524, 66116458, 98739783, 17016156, 43933006, 65081429, 2331773, 38365584, 70372191, 69786756, 33249630, 77694700, 18663507, 7646095, 37501808, 99965001, 56461322, 5970607, 24915585, 71920426, 49271185, 72777973, 72373496, 45919976, 55247455, 14731700, 20002147, 77284619, 57658654, 33201905, 45996863, 17081350, 44060493, 97641116, 22721500, 24733232, 10366309, 26102057, 27411561, 33797252, 97281847, 13173644, 17894977, 28928175, 34698428, 55470718, 49236559, 9829782, 69848388, 85695762, 22879907, 30891921, 38009615, 15064655, 42199455, 54427233, 30653863, 26507214, 92604458, 55960386, 22113133, 59329511, 78766358, 16424199, 69355476, 824872, 40152546, 94076128, 16380211, 50806615, 92787493, 93053405, 49598724, 94090109, 36312813, 6038457, 3183975, 91990218, 34236719, 61728685, 56473732, 33699435, 20140249, 61982238, 79821897, 44846932, 31904591, 84406788, 72238278, 34946859, 669105, 90013093, 54199160, 4515343, 1936762, 57393458, 99604946, 63059024, 99917599, 26397786 +15064655, 73124510, 95726235, 45407418, 89078848, 44842615, 1936762, 21993752, 52613508, 78909561, 97379790, 90004325, 79806380, 92033260, 17764950, 76540481, 19939935, 83133790, 78785507, 47893286, 36505482, 42073124, 36808486, 56461322, 82532312, 24314885, 55247455, 45381876, 53632373, 14349098, 40152546, 54517921, 44481640, 2891150, 13173644, 90013093, 35092039, 49328608, 17976208, 8729146, 64602895, 10649306, 95395112, 89217461, 3233569, 72793444, 51149804, 9175338, 89214616, 59177718, 42692881, 40781449, 76960303, 56153345, 12024238, 43152977, 98948034, 72357096, 99226875, 84187166, 99971982, 18833224, 31904591, 93566986, 61859581, 33431960, 81855445, 86001008, 17894977, 40865610, 73235980, 30998561, 7229550, 85023028, 70036438, 27325428, 14626618, 32159704, 54663246, 1197320, 17857111, 32699744, 21070110, 31733363, 12571310, 59197747, 62552524, 30569392, 63059024, 5640302, 42644903, 61728685, 29959549, 77301523, 82052050, 54427233, 2331773, 46851987, 37739481, 415901, 32426752, 18504197, 7011964, 29466406, 16971929, 2607799, 31126490, 32058615, 54232247, 66428795, 4069912, 18806856, 56484900, 59501487, 96193415, 26292919, 6986898, 17386996, 99515901, 96318094, 45995325, 50806615, 44983451, 321665, 9886593, 61960400, 45075471, 92530431, 9599614, 69136837, 40677414, 16405341, 66137019, 26139110, 75543508, 45790169, 45667668, 97281847, 57241521, 29510992, 57961282, 69641513, 87160386, 88653118, 7104732, 9623492, 74137926, 26114953, 9259676, 14093520, 26397786, 36580610, 91990218, 18466635, 34236719, 30891921, 8807940, 38061439, 87598888, 79738755, 40027975, 55753905, 5482538, 64157906, 78561158, 86301513, 31161687, 77312810, 569864, 93933709, 94360702, 66271566, 85117092, 84002370, 44847298, 61815223, 41380093, 39847321, 7685448, 96906410, 99690194, 47708850, 71522968, 18663507, 37620363, 51426311, 99297164, 34044787, 71316369, 29834512, 91664334, 20122224, 90654836, 92692978, 86798033, 65074535, 85571389, 73786944, 45919976, 8733068, 40781854, 14731700, 77284619, 34497327, 74452589, 54606384, 11581181, 68939068, 52261574, 54058788, 59318837, 97057187, 65851721, 67793644, 54987042, 11365791, 82897371, 8696647, 62452034, 29065964, 95251277, 43501211, 24733232, 82327024, 5267545, 76825057, 8115266, 90158785, 4787945, 4806458, 22405120, 4095116, 26102057, 85242963, 97940276, 33797252, 49598724, 33336148, 30139692, 44473167, 72274002, 93359396, 72019362, 88251446, 75153252, 10961421, 63372756, 58224549, 44664587, 28787861, 33895336, 66045587, 508198, 62115552, 55470718, 26392416, 9829782, 51472275, 4199704, 36189527, 22997281, 94539824, 98130363, 24591705, 61712234, 53547802, 6153406, 45794415, 38658347, 53648154, 62490109, 38009615, 99604946, 24619760, 83789391, 70727211, 65880522, 88130087, 68000591, 61928316, 45990383, 44784505, 92071990, 7423788, 68875490, 13819358, 65275241, 92867155, 33123618, 61263068, 37675718, 82979980, 37183543, 7300047, 48088883, 26275734, 58180653, 26063929, 16019925, 13468268, 51507425, 80014588, 83948335, 77620120, 112651, 71469330, 83747892, 7066775, 55960386, 33699435, 78766358, 49641577, 36135, 37659250, 71920426, 27665211, 44479073, 94711842, 60393039, 47298834, 60955663, 824872, 5822202, 57658654, 11757872, 36780454, 83269727, 67513640, 17385531, 5832946, 40534591, 32151165, 73031054, 92353856, 2917920, 67124282, 56531125, 44846932, 88904910, 19457589, 83368048, 99917599, 32161669, 83533741, 14947650, 80316608, 29516592, 8373289, 94090109, 25842078, 17957593, 21001913, 8099994, 51715482, 22450468, 28550822, 53084256, 75128745, 48395186, 45237957, 68702632, 95581843, 96735716, 6038457, 8651647, 9860968, 81274566, 68816413, 14326617, 83150534, 95290172, 70782102, 36933038, 88444207, 54014062, 51590803, 17365188, 62430984, 15015906, 3633375, 65338021, 55770687, 35456853, 44844121, 15902805, 86460488, 54263880, 44177011, 48360198, 78589145, 76434144, 67031644, 30090481, 22108665, 98739783, 20765474, 55850790, 47887470, 23134715, 86543538, 16097038, 30543215, 99729357, 84293052, 73617245, 4798568, 72614359, 80555751, 7955293, 48892201, 92692283, 15535065, 72738685, 98371444, 70372191, 92998591, 12303248, 12348118, 37501808, 51464002, 6808825, 41245325, 67281495, 23740167, 47090124, 41442762, 95957797, 43376279, 81774825, 28851716, 27625735, 74110882, 7687278, 63015256, 16684829, 72238278, 83302115, 20645197, 1431742, 36930650, 37166608, 62428472, 39986008, 50702367, 1250437, 44060493, 6871053, 94076128, 94595668, 61897582, 4985896, 90090964, 48893685, 68694897, 56515456, 5111370, 99125126, 61141874, 36753250, 16387575, 74614639, 82339363, 15148031, 10366309, 23848565, 92803766, 35996293, 91727510, 45617087, 90272749, 47361209, 17539625, 21533347, 26863229, 71965942, 16445503, 49882705, 28928175, 3487592, 91141395, 54762643, 3088684, 42782652, 2208785, 60430369, 91802888, 3183975, 81805959, 78549759, 51315460, 60106217, 21919959, 61271144, 42237907, 57393458, 10358899, 63628376, 49236559, 37280276, 48675329, 28734791, 9860195, 59405277, 65454636, 11161731, 53802686, 32250045, 72495719, 15163258, 89996536, 30764367, 64098930, 6525948, 26734892, 20836893, 81853704, 73222868, 22942635, 3773993, 30787683, 1569515, 99861373, 75135448, 19486173, 13348726, 69255765, 66116458, 8129978, 29932657, 1022677, 33553959, 42967683, 39171895, 60102965, 53666583, 36685023, 76671482, 11543098, 48260151, 63506281, 66322959, 36845587, 62936963, 35192533, 30463802, 38365584, 23110625, 45428665, 29635537, 57163802, 18411915, 51047803, 71300104, 92604458, 63967300, 22766820, 77694700, 27187213, 44889423, 2204165, 6505939, 86118021, 50007421, 58751351, 89811711, 59329511, 96726697, 14045383, 16424199, 68316156, 7517032, 82651278, 57359924, 24915585, 72732205, 4119747, 49271185, 68204242, 29994197, 40356877, 16567550, 1375023, 99524975, 24953498, 4668450, 6819644, 30218878, 3294781, 20140249, 62496012, 66663942, 61982238, 70420215, 77072625, 97783876, 38256849, 33201905, 99333375, 12664567, 36396314, 57803235, 83155442, 22129328, 99224392, 30771409, 71657078, 10453030, 37891451, 10597197, 34432810, 66832478, 14220886, 74743862, 69697787, 45306070, 94911072, 36812683, 57020481, 32274392, 39553046, 90310261, 72725103, 99549373, 92787493, 9058407, 48673079, 79942022, 59371804, 8634541, 78218845, 69579137, 58208470, 92215320, 96852321, 27185644, 90457870, 68102437, 99658235, 61623915, 58749, 34698428, 6221471, 66611759, 53842979, 26776922, 55602660, 78602717, 75052463, 66903004, 91831487, 42947632, 36468541, 1788101, 64848072, 34667286, 97011160, 34493392, 7919588, 16595436, 24058273, 53393358, 89499542, 69605283, 85695762, 22879907, 62232211, 81898046, 21673260, 12416768, 24826575, 98648327, 77377183, 9575954, 3235882, 75407347, 38341669, 62803858, 82886381, 43933006, 4204661, 8913721, 42307449, 76703609, 65081429, 26507214, 62740044, 91957544, 34295794, 6793819, 9188443, 16099750, 54868730, 69786756, 74724075, 81172706, 99965001, 18783702, 4099191, 25636669, 4508700, 33237508, 22113133, 27041967, 18131876, 7182642, 16054533, 52060076, 69355476, 91938191, 19272365, 29401781, 9257405, 72373496, 39201414, 51466049, 31491938, 64055960, 67474219, 73021291, 55669657, 41092102, 86022504, 87710366, 17037369, 56955985, 37192445, 15536795, 96709982, 76330843, 46870723, 21397057, 53888755, 669105, 72278539, 91255408, 6521313, 9398733, 89637706, 72358170, 44627776, 91281584, 77300457, 71083565, 79191827, 68824981, 19376156, 35512853, 20642888, 70800879, 93053405, 44348328, 84684495, 17068582, 76315420, 26998766, 65271999, 62357986, 8055981, 93270084, 79657802, 82427263, 79922758, 67451935, 37957788, 92398073, 15948937, 77272628, 81677380, 68128438, 67227442, 78884452, 20486294, 80246713, 40984766, 33061250, 89046466, 61373987, 70004753, 20867149, 93790285, 43045786, 62051033, 75777973, 17058722, 61741594, 1239555, 37560164, 92541302, 75986488, 85116755, 5073754, 73109997, 56473732, 38022834, 77787724, 71333116, 52204879, 88047921, 55143724, 77898274, 4091162, 33935899, 65047700, 50188404, 99021067, 87720882, 72777973, 10264691, 37435892, 86242799, 67030811, 56424103, 24168411, 31727379, 26744917, 50567636, 77413012, 57240218, 17081350, 82779622, 7502255, 34946859, 16380211, 2717150, 44550764, 65017137, 526217, 65038678, 26664538, 84349107, 91240048, 83210802, 38645117, 60176618, 83651211, 4434662, 3509435, 98462867, 33304202, 66250369, 28796059, 54199160, 57248122, 30694952, 33628349, 38494874, 47792865, 75229462, 22200378, 93515664, 6157724, 90061527, 59614746, 84166196, 38008118, 69848388, 73392814, 82178706, 68110330, 30395570, 176257, 47213183, 73168565, 21289531, 17016156, 6111563, 42199455, 52293995, 34698463, 874791, 84904436, 55615885, 66885828, 79136082, 39373729, 5970607, 85711894, 41481685, 30811010, 18699206, 1204161, 14363867, 50668599, 23194618, 89699445, 40686254, 82726008, 4064751, 48774913, 79821897, 247198, 168541, 22721500, 62693428, 45049308, 13470059, 23432750, 51588015, 41092172, 22435353, 26493119, 19891772, 88698958, 3233084, 36312813, 4515343, 96420429, 59981773, 84406788, 6497830, 44915235, 4978543, 15075176, 10309525, 14723410, 65715134, 19898053, 39801351, 60581278, 89804152, 98653983, 55189057, 19101477, 29188588, 48658605, 33249630, 43322743, 7646095, 93562257, 17146629, 68644627, 61859143, 83083131, 66319530, 9603598, 79322415, 77187825, 33565483, 45848907, 47738185, 29819940, 46540998, 86821229, 43506672, 36139942, 27411561, 68041839, 18001617, 97561745, 75820087, 11923835, 66667729, 3880712, 23569917, 20568363, 95010552, 13862149, 20681921, 70596786, 64087743, 37224844, 76170907, 92554120, 30653863, 49597667, 29029316, 74357852, 59910624, 20002147, 62762857, 45996863, 84127901, 59109400, 94516935, 80251430, 13231279, 38510840, 84840549, 67084351, 30366150, 749283, 20885148, 44245960, 70367851, 63152504, 70541760, 8791066, 74441448, 8128637, 97641116, 43357947, 98943869, 89419466, 30163921, 40197395, 79880247, 32590267, 78300864, 17727650, 93057697 +79880247, 26392416, 66116458, 61728685, 22766820, 79136082, 91664334, 4434662, 59981773, 70727211, 36845587, 49597667, 18411915, 42692881, 10264691, 78300864, 61982238, 4064751, 97057187, 67793644, 45306070, 69579137, 47361209, 63628376, 36189527, 27325428, 81677380, 38341669, 53666583, 2331773, 7066775, 71333116, 27041967, 43376279, 55143724, 92692978, 23194618, 72238278, 39201414, 55669657, 99333375, 67513640, 12664567, 54058788, 2717150, 67124282, 99125126, 20642888, 22435353, 93053405, 68041839, 26493119, 13231279, 33304202, 99658235, 6038457, 98943869, 47792865, 83150534, 84406788, 22200378, 9829782, 65454636, 59614746, 6525948, 67227442, 81898046, 68110330, 61373987, 31733363, 8129978, 13819358, 31161687, 62051033, 6111563, 7300047, 4204661, 58180653, 30463802, 61741594, 37560164, 34295794, 54868730, 43322743, 12348118, 44245960, 51464002, 22113133, 38022834, 91938191, 65047700, 50188404, 50668599, 72373496, 40356877, 12024238, 31491938, 17727650, 68694897, 44627776, 39553046, 70800879, 97561745, 29516592, 13173644, 57961282, 88698958, 3233084, 25842078, 90013093, 76315420, 66611759, 20568363, 91831487, 55470718, 95290172, 57393458, 10358899, 64848072, 17976208, 9860195, 84166196, 20681921, 3633375, 70596786, 22879907, 15902805, 64087743, 76170907, 5482538, 52613508, 47213183, 86301513, 77301523, 65081429, 84002370, 36505482, 48892201, 77620120, 66885828, 37501808, 71316369, 29466406, 29834512, 18131876, 31126490, 33935899, 83083131, 30218878, 73021291, 20140249, 70420215, 77187825, 41092102, 84187166, 15536795, 21397057, 40152546, 247198, 73031054, 6521313, 72358170, 36812683, 57020481, 61859581, 44842615, 93057697, 36139942, 38645117, 2891150, 59371804, 49598724, 57241521, 3509435, 98462867, 96852321, 26863229, 71965942, 21001913, 43357947, 3487592, 91141395, 8055981, 73235980, 49328608, 79922758, 7229550, 30694952, 9259676, 95010552, 88444207, 34667286, 51590803, 72495719, 22997281, 68128438, 89996536, 98130363, 16595436, 32699744, 45794415, 62232211, 40984766, 99604946, 30395570, 89046466, 30787683, 59197747, 24826575, 62552524, 92071990, 92554120, 62803858, 17016156, 23134715, 86543538, 89804152, 98653983, 42199455, 40197395, 11543098, 85117092, 99729357, 62936963, 78909561, 37739481, 415901, 98371444, 92604458, 42073124, 47708850, 7646095, 18504197, 47090124, 7011964, 41442762, 58751351, 77787724, 74357852, 54232247, 18699206, 49271185, 4069912, 16684829, 85571389, 16567550, 60393039, 4668450, 20002147, 67474219, 72357096, 11757872, 36780454, 83269727, 62428472, 26744917, 39986008, 57240218, 17386996, 96709982, 99515901, 48774913, 82779622, 5832946, 30163921, 66832478, 22721500, 45049308, 526217, 43501211, 74614639, 61960400, 54517921, 82339363, 15148031, 24733232, 26664538, 69136837, 76825057, 13470059, 72725103, 99549373, 4806458, 17764950, 41092172, 4095116, 76540481, 35996293, 66137019, 18001617, 29510992, 92215320, 75820087, 72274002, 8099994, 90457870, 93359396, 68102437, 28550822, 75153252, 58749, 34698428, 66667729, 36312813, 30998561, 508198, 78549759, 33628349, 68816413, 75229462, 14326617, 61271144, 36933038, 67084351, 13862149, 47893286, 4978543, 90061527, 14626618, 30764367, 38008118, 64098930, 20836893, 61712234, 53393358, 30891921, 44844121, 33061250, 24619760, 79738755, 93790285, 37224844, 55753905, 19486173, 48360198, 64602895, 78589145, 67031644, 3235882, 20765474, 92867155, 55850790, 60581278, 33123618, 1022677, 89217461, 37183543, 42967683, 43933006, 72793444, 55189057, 82052050, 52293995, 8913721, 874791, 46851987, 72614359, 7955293, 1239555, 38365584, 92541302, 75986488, 39847321, 7685448, 70372191, 59177718, 63967300, 33249630, 81172706, 70367851, 93562257, 73109997, 83747892, 23740167, 17146629, 4508700, 59329511, 95957797, 52204879, 88047921, 49641577, 77898274, 24915585, 90654836, 1204161, 19272365, 68204242, 73786944, 55247455, 96193415, 77072625, 57658654, 86022504, 89699445, 33565483, 17037369, 68939068, 45381876, 45996863, 84127901, 76330843, 99224392, 47738185, 10453030, 37891451, 44983451, 92353856, 69697787, 2917920, 56515456, 62693428, 9886593, 29065964, 95251277, 18833224, 32274392, 10366309, 92803766, 4787945, 35512853, 51588015, 22405120, 26102057, 97940276, 90272749, 8634541, 33431960, 19891772, 17539625, 17894977, 35092039, 16445503, 49882705, 88251446, 61623915, 48395186, 88653118, 62357986, 66250369, 54199160, 3880712, 42782652, 96420429, 60430369, 74137926, 81805959, 51315460, 75052463, 8651647, 66903004, 9860968, 70782102, 36580610, 749283, 48675329, 70036438, 6497830, 44915235, 67451935, 15075176, 10309525, 53802686, 54663246, 14723410, 1197320, 17857111, 7919588, 81853704, 19898053, 78884452, 73222868, 86460488, 54263880, 44177011, 20867149, 13348726, 10649306, 39801351, 95395112, 73168565, 47887470, 16097038, 48088883, 3233569, 36685023, 51149804, 51507425, 55615885, 30653863, 26507214, 83948335, 32590267, 15535065, 45428665, 29188588, 6793819, 29635537, 57163802, 16099750, 112651, 92998591, 12303248, 29029316, 70541760, 25636669, 2607799, 7182642, 85711894, 16424199, 36135, 81774825, 90004325, 4091162, 40781449, 72732205, 69355476, 82532312, 86798033, 65074535, 14363867, 29401781, 45919976, 18806856, 83302115, 1431742, 86242799, 60955663, 77284619, 62496012, 99226875, 36930650, 97783876, 38256849, 24168411, 33201905, 50567636, 26292919, 77413012, 36396314, 40686254, 17081350, 44060493, 7502255, 46540998, 59318837, 94076128, 16380211, 65851721, 669105, 11365791, 82897371, 56531125, 77300457, 71083565, 68824981, 32161669, 5267545, 8115266, 23432750, 45407418, 27411561, 91727510, 75543508, 33797252, 30139692, 44473167, 44348328, 69641513, 19939935, 38510840, 51715482, 72019362, 84840549, 28928175, 75128745, 10961421, 6221471, 9623492, 68702632, 82427263, 4515343, 62115552, 42947632, 42237907, 92398073, 77272628, 97011160, 62430984, 15163258, 32159704, 6153406, 20486294, 82178706, 22942635, 21673260, 62490109, 80246713, 3773993, 87598888, 40027975, 8729146, 22108665, 61928316, 69255765, 7423788, 5640302, 29932657, 82886381, 61263068, 77312810, 569864, 26275734, 66271566, 76703609, 48260151, 16019925, 80014588, 84904436, 44847298, 19101477, 91957544, 9175338, 89214616, 69786756, 5073754, 37620363, 6505939, 41245325, 51426311, 33699435, 16054533, 82651278, 61859143, 57359924, 20122224, 30811010, 27665211, 56153345, 99021067, 51466049, 8733068, 1375023, 43152977, 56484900, 37435892, 20645197, 64055960, 40781854, 98948034, 3294781, 824872, 59501487, 5822202, 34497327, 9603598, 54606384, 37166608, 31727379, 53632373, 17385531, 1250437, 30771409, 6871053, 94595668, 97641116, 61897582, 86821229, 72278539, 4985896, 14220886, 5111370, 91281584, 36753250, 65038678, 99917599, 93566986, 59109400, 90158785, 83651211, 48673079, 85242963, 83533741, 14947650, 26139110, 45617087, 78218845, 84684495, 86001008, 17957593, 27185644, 26998766, 11923835, 65271999, 58224549, 3088684, 28796059, 79657802, 33895336, 66045587, 91802888, 26114953, 1936762, 81274566, 26397786, 91990218, 6157724, 15948937, 18466635, 17365188, 94539824, 34236719, 65715134, 24058273, 89499542, 85695762, 53648154, 38009615, 38061439, 12571310, 75135448, 65880522, 64157906, 30090481, 98648327, 77377183, 45990383, 44784505, 30569392, 43045786, 33553959, 84293052, 62740044, 4798568, 80555751, 85116755, 9188443, 48658605, 51047803, 99690194, 77694700, 71522968, 18663507, 36808486, 55960386, 86118021, 4099191, 56473732, 8791066, 99297164, 34044787, 33237508, 89811711, 68644627, 5970607, 59910624, 97379790, 41481685, 32058615, 27625735, 71920426, 79806380, 7687278, 76960303, 44479073, 9257405, 95726235, 47298834, 24953498, 14731700, 6819644, 66663942, 79322415, 87710366, 37192445, 6986898, 50702367, 83155442, 52261574, 82726008, 14349098, 46870723, 40534591, 32151165, 79821897, 45995325, 99971982, 168541, 53888755, 54987042, 91255408, 90090964, 44550764, 74743862, 8696647, 9398733, 89637706, 62452034, 44846932, 88904910, 61141874, 45075471, 79191827, 44481640, 84349107, 40677414, 60176618, 9058407, 94516935, 80316608, 80251430, 58208470, 83133790, 17068582, 78785507, 53084256, 45237957, 26776922, 2208785, 57248122, 3183975, 23569917, 4199704, 28734791, 20885148, 69848388, 24591705, 65338021, 73392814, 38658347, 1569515, 70004753, 15064655, 9575954, 78561158, 75407347, 42644903, 82979980, 93933709, 94360702, 60102965, 17058722, 30543215, 76671482, 63506281, 13468268, 66322959, 73617245, 72738685, 74724075, 2204165, 71469330, 63152504, 6808825, 67281495, 16971929, 78766358, 92033260, 63015256, 87720882, 29994197, 99524975, 74441448, 66319530, 67030811, 8128637, 89078848, 22129328, 29819940, 71657078, 10597197, 50806615, 94911072, 65017137, 19457589, 92530431, 31904591, 82327024, 83210802, 90310261, 16405341, 33336148, 45667668, 8373289, 94090109, 81855445, 21533347, 22450468, 63372756, 93270084, 28787861, 53842979, 55602660, 78602717, 38494874, 60106217, 89419466, 14093520, 1788101, 30366150, 54014062, 37280276, 26734892, 55770687, 21070110, 176257, 99861373, 76434144, 63059024, 68875490, 98739783, 65275241, 37675718, 42307449, 26063929, 35192533, 61815223, 73124510, 92692283, 96906410, 44889423, 99965001, 50007421, 14045383, 68316156, 28851716, 37659250, 4119747, 74110882, 72777973, 94711842, 62762857, 11581181, 56955985, 34946859, 96318094, 48893685, 321665, 43506672, 83368048, 91240048, 79942022, 97281847, 87160386, 40865610, 44664587, 95581843, 96735716, 21919959, 49236559, 59405277, 32250045, 34493392, 15015906, 53547802, 69605283, 21993752, 35456853, 8807940, 83789391, 88130087, 68000591, 75777973, 21289531, 29959549, 39171895, 34698463, 23110625, 32426752, 27187213, 39373729, 7517032, 24314885, 56424103, 74452589, 16387575, 23848565, 92787493, 54762643, 7104732, 85023028, 36468541, 51472275, 11161731, 12416768, 71300104, 18783702, 96726697, 52060076, 66428795, 45848907, 57803235, 34432810, 9599614, 45790169, 37957788, 54427233, 41380093, 56461322, 19376156, 93515664 +22879907, 39801351, 11365791, 35996293, 21673260, 33553959, 65081429, 56473732, 77898274, 56424103, 36396314, 97641116, 83368048, 15148031, 87160386, 72495719, 63506281, 13468268, 55143724, 86242799, 83155442, 7502255, 168541, 86821229, 92803766, 40677414, 18001617, 19891772, 58224549, 3880712, 3633375, 38658347, 81898046, 94360702, 7300047, 42199455, 52293995, 80555751, 9175338, 7646095, 79136082, 16054533, 59910624, 68204242, 64055960, 66663942, 11757872, 97783876, 61960400, 24733232, 23848565, 36139942, 90158785, 48673079, 97561745, 21001913, 95581843, 53842979, 75052463, 10358899, 61712234, 40984766, 64087743, 176257, 45990383, 3235882, 30569392, 7423788, 31161687, 55850790, 60581278, 47887470, 6111563, 84002370, 46851987, 48658605, 70372191, 63967300, 25636669, 71316369, 52060076, 37659250, 69355476, 31491938, 30218878, 72357096, 37192445, 57803235, 4064751, 44060493, 71657078, 46540998, 40534591, 50806615, 61897582, 44550764, 5111370, 74614639, 82339363, 16405341, 27411561, 94516935, 14947650, 91727510, 96852321, 8099994, 28796059, 47792865, 1788101, 92398073, 10309525, 73392814, 62232211, 30395570, 20867149, 55753905, 19486173, 76434144, 75407347, 82886381, 82979980, 43933006, 3233569, 89804152, 99729357, 84293052, 84904436, 26507214, 36845587, 62936963, 19101477, 23110625, 85116755, 51047803, 77694700, 27187213, 81172706, 18663507, 93562257, 63152504, 2607799, 96726697, 88047921, 97379790, 90004325, 33935899, 86798033, 66428795, 87720882, 10264691, 1375023, 20002147, 5822202, 62428472, 77413012, 50702367, 14349098, 82779622, 6871053, 94595668, 30163921, 2717150, 72358170, 36812683, 526217, 43501211, 32274392, 79191827, 91240048, 26102057, 83533741, 79942022, 97940276, 80316608, 68041839, 90272749, 94090109, 47361209, 17539625, 17957593, 76315420, 28928175, 75153252, 3487592, 45237957, 49328608, 60430369, 79922758, 74137926, 9860968, 14326617, 57393458, 22200378, 36189527, 37957788, 9860195, 6157724, 27325428, 15163258, 59614746, 24058273, 53648154, 89046466, 99861373, 5482538, 13348726, 22108665, 78561158, 98739783, 92554120, 20765474, 38341669, 21289531, 16097038, 53666583, 55189057, 11543098, 48260151, 66322959, 874791, 2331773, 61741594, 6793819, 98371444, 66885828, 5073754, 44245960, 2204165, 99965001, 50007421, 8791066, 22113133, 77787724, 74357852, 41481685, 61859143, 57359924, 24915585, 72732205, 71920426, 49271185, 63015256, 56153345, 23194618, 18806856, 47298834, 67030811, 73021291, 24168411, 36780454, 89699445, 33565483, 17037369, 67513640, 46870723, 99224392, 17727650, 32151165, 97057187, 73031054, 54987042, 22721500, 6521313, 45306070, 321665, 65017137, 44627776, 44481640, 26664538, 32161669, 83210802, 76825057, 35512853, 92787493, 20642888, 9058407, 59371804, 4434662, 45617087, 80251430, 13173644, 21533347, 98462867, 88698958, 3233084, 38510840, 17894977, 72274002, 90457870, 51715482, 72019362, 61623915, 53084256, 91141395, 73235980, 79657802, 26776922, 30998561, 91802888, 62115552, 23569917, 38494874, 8651647, 91831487, 89419466, 26397786, 67084351, 13862149, 67451935, 18466635, 51590803, 77272628, 68128438, 94539824, 89996536, 34236719, 20836893, 69605283, 21070110, 35456853, 22942635, 62490109, 86460488, 30787683, 61373987, 70004753, 93790285, 37224844, 15064655, 78589145, 77377183, 75777973, 73168565, 17016156, 60102965, 4204661, 82052050, 76671482, 42307449, 26063929, 55615885, 62740044, 36505482, 61815223, 1239555, 37560164, 92541302, 34295794, 9188443, 18411915, 89214616, 42073124, 22766820, 12348118, 74724075, 71469330, 47090124, 33237508, 16971929, 18131876, 43376279, 91664334, 7182642, 36135, 68316156, 7517032, 30811010, 74110882, 82532312, 24314885, 65074535, 29994197, 73786944, 12024238, 55247455, 6819644, 67474219, 70420215, 62762857, 79322415, 37166608, 11581181, 56955985, 45996863, 57240218, 17081350, 96709982, 82726008, 29819940, 10453030, 40152546, 247198, 94076128, 669105, 91255408, 82897371, 48893685, 68694897, 62693428, 89637706, 62452034, 91281584, 45049308, 71083565, 92530431, 82327024, 10366309, 5267545, 19376156, 69136837, 90310261, 23432750, 72725103, 51588015, 4095116, 76540481, 75543508, 22435353, 26493119, 57241521, 78218845, 57961282, 75820087, 69641513, 84684495, 27185644, 90013093, 22450468, 84840549, 26998766, 78785507, 68102437, 99658235, 28550822, 58749, 34698428, 48395186, 62357986, 42782652, 508198, 4515343, 26114953, 30694952, 1936762, 81274566, 60106217, 83150534, 21919959, 84406788, 37280276, 6497830, 28734791, 59405277, 65454636, 11161731, 15948937, 97011160, 81677380, 34493392, 54663246, 98130363, 26734892, 65338021, 6153406, 45794415, 53393358, 70596786, 20486294, 30891921, 44844121, 80246713, 99604946, 3773993, 1569515, 70727211, 76170907, 12571310, 98648327, 62552524, 86301513, 68875490, 13819358, 42644903, 65275241, 62051033, 92867155, 1022677, 37675718, 26275734, 98653983, 76703609, 80014588, 79880247, 37739481, 30463802, 48892201, 91957544, 32590267, 77620120, 39847321, 57163802, 92604458, 33249630, 43322743, 42692881, 29029316, 71522968, 73109997, 55960386, 18783702, 99297164, 41442762, 58751351, 39373729, 68644627, 95957797, 29834512, 31126490, 85711894, 32058615, 82651278, 4091162, 27625735, 4119747, 54232247, 27665211, 65047700, 4069912, 44479073, 50188404, 40356877, 83302115, 8733068, 94711842, 60393039, 99524975, 24953498, 40781854, 98948034, 59501487, 9603598, 55669657, 86022504, 87710366, 31727379, 8128637, 6986898, 99515901, 47738185, 54058788, 16380211, 65851721, 53888755, 44983451, 14220886, 2917920, 56515456, 99125126, 9886593, 44846932, 43506672, 93566986, 38645117, 8115266, 13470059, 4787945, 99549373, 41092172, 85242963, 66137019, 49598724, 33336148, 30139692, 44473167, 8373289, 33431960, 69579137, 13231279, 83133790, 11923835, 44664587, 3088684, 66667729, 36312813, 28787861, 66045587, 82427263, 2208785, 6038457, 78602717, 81805959, 33628349, 98943869, 68816413, 14093520, 75229462, 36933038, 42237907, 36580610, 49236559, 64848072, 47893286, 48675329, 70036438, 93515664, 4978543, 15075176, 32250045, 22997281, 32159704, 14723410, 17857111, 16595436, 65715134, 44177011, 83789391, 64602895, 88130087, 9575954, 44784505, 52613508, 92071990, 8129978, 5640302, 93933709, 42967683, 86543538, 39171895, 72793444, 16019925, 30653863, 44847298, 35192533, 41380093, 15535065, 45428665, 29188588, 16099750, 71300104, 99690194, 112651, 92998591, 6808825, 70541760, 7066775, 33699435, 17146629, 38022834, 71333116, 29466406, 27041967, 52204879, 49641577, 81774825, 20122224, 19272365, 14363867, 50668599, 29401781, 72777973, 85571389, 39201414, 45919976, 16567550, 51466049, 43152977, 56484900, 78300864, 14731700, 66319530, 77284619, 96193415, 34497327, 77072625, 26292919, 89078848, 53632373, 76330843, 22129328, 48774913, 10597197, 74743862, 69697787, 9398733, 67124282, 56531125, 36753250, 16387575, 95251277, 65038678, 18833224, 61859581, 44842615, 9599614, 93057697, 59109400, 60176618, 4806458, 2891150, 93053405, 81855445, 26863229, 86001008, 35092039, 17068582, 93359396, 75128745, 10961421, 6221471, 54762643, 7104732, 9623492, 93270084, 96735716, 57248122, 78549759, 51315460, 20568363, 59981773, 42947632, 55470718, 70782102, 88444207, 30366150, 63628376, 9829782, 749283, 44915235, 90061527, 14626618, 6525948, 7919588, 24591705, 81853704, 67227442, 20681921, 32699744, 78884452, 85695762, 12416768, 24619760, 8729146, 75135448, 64157906, 30090481, 63059024, 10649306, 62803858, 29959549, 569864, 23134715, 17058722, 40197395, 30543215, 66271566, 51149804, 8913721, 51507425, 78909561, 83948335, 49597667, 73124510, 72738685, 75986488, 54868730, 69786756, 59177718, 12303248, 47708850, 36808486, 6505939, 41245325, 23740167, 51426311, 4099191, 4508700, 40781449, 90654836, 92692978, 18699206, 91938191, 1204161, 76960303, 72238278, 37435892, 1431742, 83083131, 4668450, 62496012, 61982238, 36930650, 41092102, 54606384, 33201905, 99333375, 45848907, 15536795, 52261574, 45995325, 4985896, 90090964, 8696647, 88904910, 84349107, 22405120, 45667668, 92215320, 25842078, 71965942, 16445503, 40865610, 63372756, 68702632, 95290172, 95010552, 36468541, 4199704, 20885148, 53802686, 38008118, 69848388, 55770687, 8807940, 24826575, 67031644, 61928316, 69255765, 66116458, 95395112, 33123618, 37183543, 58180653, 36685023, 85117092, 72614359, 7955293, 38365584, 37620363, 37501808, 51464002, 67281495, 56461322, 5970607, 16424199, 7687278, 16684829, 9257405, 74441448, 60955663, 20140249, 77187825, 68939068, 50567636, 39986008, 45381876, 40686254, 30771409, 79821897, 59318837, 67793644, 66832478, 72278539, 29065964, 19457589, 57020481, 39553046, 68824981, 45407418, 17764950, 33797252, 8634541, 29510992, 3509435, 44348328, 19939935, 33304202, 43357947, 49882705, 65271999, 88653118, 66250369, 96420429, 7229550, 9259676, 66903004, 85023028, 61271144, 26392416, 51472275, 17976208, 30764367, 84166196, 89499542, 82178706, 38009615, 38061439, 33061250, 54263880, 31733363, 79738755, 68000591, 43045786, 29932657, 77301523, 415901, 29635537, 7685448, 44889423, 83747892, 18504197, 86118021, 7011964, 34044787, 89811711, 14045383, 28851716, 99021067, 95726235, 74452589, 38256849, 83269727, 84187166, 17386996, 21397057, 37891451, 34432810, 99971982, 92353856, 54517921, 45075471, 83651211, 70800879, 26139110, 97281847, 29516592, 88251446, 66611759, 8055981, 91990218, 54014062, 17365188, 62430984, 64098930, 1197320, 15015906, 19898053, 68110330, 40027975, 59197747, 65880522, 47213183, 61728685, 89217461, 34698463, 73617245, 92692283, 96906410, 59329511, 79806380, 92033260, 72373496, 20645197, 3294781, 99226875, 26744917, 12664567, 17385531, 1250437, 5832946, 34946859, 96318094, 94911072, 61141874, 99917599, 31904591, 45790169, 58208470, 54199160, 3183975, 53547802, 21993752, 15902805, 87598888, 4798568, 32426752, 84127901, 55602660, 48360198, 77312810, 78766358, 77300457, 33895336, 34667286, 73222868, 48088883, 70367851, 824872, 57658654, 61263068, 54427233 +16684829, 86242799, 19891772, 10309525, 77272628, 68128438, 59614746, 20486294, 47887470, 48658605, 1250437, 16380211, 5111370, 30139692, 22450468, 79657802, 38658347, 38009615, 43045786, 13819358, 1022677, 52293995, 30653863, 15535065, 27187213, 2204165, 43152977, 66319530, 62762857, 76825057, 13470059, 23432750, 22405120, 49598724, 21001913, 17894977, 9259676, 75229462, 72495719, 64098930, 19898053, 64087743, 62740044, 48892201, 33249630, 7646095, 63152504, 79136082, 51426311, 55143724, 68316156, 40781449, 37659250, 63015256, 56424103, 11757872, 62428472, 50806615, 66832478, 77300457, 32274392, 82339363, 10366309, 97940276, 97281847, 92215320, 51715482, 40865610, 9623492, 26776922, 508198, 62115552, 26114953, 88444207, 15075176, 15163258, 69848388, 65715134, 24058273, 93790285, 88130087, 67031644, 78561158, 68875490, 65275241, 82886381, 33123618, 29959549, 37183543, 7300047, 60102965, 53666583, 98653983, 55615885, 46851987, 36505482, 1239555, 34295794, 89214616, 5073754, 44245960, 47708850, 59329511, 29466406, 88047921, 16054533, 4091162, 72777973, 73786944, 31491938, 1375023, 67030811, 70420215, 57658654, 36930650, 82726008, 71657078, 67793644, 53888755, 72278539, 8696647, 89637706, 18833224, 74614639, 61960400, 54517921, 83368048, 31904591, 93566986, 68824981, 44481640, 40677414, 48673079, 93053405, 4434662, 26493119, 78218845, 33304202, 90013093, 90457870, 99658235, 61623915, 34698428, 63372756, 66611759, 8055981, 44664587, 49328608, 91802888, 81805959, 75052463, 38494874, 89419466, 55470718, 42237907, 54014062, 37280276, 749283, 20885148, 97011160, 22997281, 32159704, 14723410, 26734892, 61712234, 81853704, 3633375, 78884452, 22879907, 30395570, 54263880, 30787683, 70004753, 20867149, 15064655, 45990383, 52613508, 30569392, 73168565, 61728685, 17016156, 17058722, 99729357, 44847298, 61815223, 83948335, 41380093, 23110625, 92541302, 72738685, 75986488, 29188588, 63967300, 71469330, 7066775, 4099191, 89811711, 77787724, 29834512, 16971929, 18131876, 43376279, 31126490, 7517032, 61859143, 27625735, 33935899, 19272365, 66428795, 29401781, 37435892, 54606384, 39986008, 45381876, 8128637, 36396314, 99224392, 21397057, 10453030, 30163921, 61897582, 48893685, 45306070, 9886593, 88904910, 44627776, 57020481, 16387575, 45075471, 39553046, 15148031, 44842615, 99549373, 22435353, 8634541, 81855445, 27185644, 28550822, 91141395, 3088684, 66667729, 53842979, 30998561, 2208785, 60430369, 6038457, 74137926, 60106217, 95010552, 26397786, 57393458, 30366150, 9829782, 6497830, 34667286, 90061527, 62430984, 7919588, 20836893, 16595436, 73392814, 55770687, 80246713, 8807940, 38061439, 176257, 83789391, 79738755, 59197747, 24826575, 78589145, 64157906, 30090481, 98648327, 39801351, 42644903, 92867155, 42967683, 89804152, 72793444, 66271566, 54427233, 84002370, 77620120, 45428665, 57163802, 85116755, 92998591, 77694700, 74724075, 29029316, 44889423, 71522968, 18663507, 83747892, 70541760, 67281495, 56473732, 25636669, 39373729, 22113133, 71333116, 52204879, 90654836, 49271185, 76960303, 14363867, 45919976, 16567550, 95726235, 60393039, 47298834, 30218878, 5822202, 86022504, 36780454, 45996863, 77413012, 96709982, 29819940, 7502255, 54058788, 45995325, 10597197, 97057187, 73031054, 168541, 97641116, 86821229, 6521313, 11365791, 92353856, 69697787, 68694897, 94911072, 72358170, 44846932, 36812683, 19457589, 95251277, 43501211, 99917599, 61859581, 93057697, 36139942, 60176618, 8115266, 51588015, 4806458, 20642888, 83533741, 79942022, 94516935, 68041839, 18001617, 29516592, 21533347, 58208470, 38510840, 87160386, 75153252, 11923835, 6221471, 88653118, 28787861, 7229550, 9860968, 81274566, 59981773, 42947632, 14326617, 83150534, 1788101, 22200378, 37957788, 9860195, 6157724, 65454636, 81677380, 98130363, 32699744, 45794415, 53393358, 44844121, 21673260, 40984766, 12416768, 76170907, 55753905, 5482538, 76434144, 3235882, 92071990, 92554120, 569864, 33553959, 86543538, 6111563, 3233569, 51149804, 34698463, 42307449, 76703609, 48260151, 16019925, 13468268, 72614359, 37739481, 61741594, 32590267, 70372191, 112651, 22766820, 43322743, 66885828, 81172706, 37620363, 6808825, 18504197, 17146629, 99297164, 41442762, 96726697, 78766358, 7182642, 85711894, 59910624, 97379790, 41481685, 36135, 32058615, 77898274, 24915585, 54232247, 92692978, 74110882, 91938191, 86798033, 87720882, 50668599, 23194618, 72373496, 40356877, 10264691, 18806856, 20645197, 4668450, 20002147, 77284619, 98948034, 72357096, 66663942, 61982238, 96193415, 34497327, 37166608, 83269727, 89699445, 31727379, 33565483, 50567636, 53632373, 57240218, 83155442, 17385531, 52261574, 4064751, 47738185, 44060493, 34946859, 40534591, 32151165, 6871053, 65851721, 321665, 65017137, 99125126, 24733232, 32161669, 5267545, 91240048, 35512853, 16405341, 9058407, 14947650, 26139110, 33797252, 90272749, 13231279, 44348328, 84684495, 98462867, 25842078, 35092039, 76315420, 16445503, 84840549, 49882705, 3487592, 28796059, 54199160, 66045587, 79922758, 78602717, 66903004, 21919959, 70782102, 13862149, 10358899, 49236559, 64848072, 47893286, 44915235, 93515664, 11161731, 92398073, 14626618, 34236719, 6525948, 1197320, 17857111, 24591705, 20681921, 85695762, 81898046, 30891921, 86460488, 44177011, 8729146, 12571310, 75135448, 48360198, 77377183, 44784505, 69255765, 8129978, 5640302, 20765474, 38341669, 75777973, 55850790, 29932657, 60581278, 23134715, 39171895, 4204661, 26275734, 55189057, 36685023, 85117092, 80555751, 62936963, 35192533, 19101477, 49597667, 39847321, 29635537, 9175338, 7685448, 92604458, 54868730, 69786756, 32426752, 42692881, 37501808, 99965001, 68644627, 57359924, 20122224, 82532312, 24314885, 7687278, 1204161, 72238278, 85571389, 29994197, 55247455, 14731700, 67474219, 59501487, 79322415, 97783876, 77187825, 24168411, 17037369, 37192445, 15536795, 6986898, 50702367, 22129328, 46870723, 17727650, 46540998, 59318837, 94595668, 669105, 14220886, 2717150, 90090964, 9398733, 92530431, 23848565, 19376156, 69136837, 92787493, 85242963, 2891150, 75543508, 80316608, 33336148, 8373289, 57241521, 69579137, 69641513, 26863229, 19939935, 83133790, 71965942, 8099994, 88251446, 28928175, 53084256, 48395186, 62357986, 7104732, 58224549, 93270084, 68702632, 3880712, 33895336, 42782652, 57248122, 78549759, 33628349, 8651647, 85023028, 68816413, 14093520, 36933038, 84406788, 63628376, 48675329, 4199704, 67451935, 53802686, 32250045, 94539824, 89996536, 84166196, 38008118, 67227442, 65338021, 6153406, 53648154, 82178706, 99604946, 61373987, 99861373, 40027975, 37224844, 64602895, 66116458, 10649306, 31161687, 62051033, 61263068, 43933006, 48088883, 42199455, 51507425, 65081429, 2331773, 26507214, 78909561, 73124510, 37560164, 6793819, 16099750, 51047803, 59177718, 42073124, 36808486, 6505939, 23740167, 47090124, 7011964, 33699435, 58751351, 34044787, 33237508, 71316369, 2607799, 16424199, 81774825, 82651278, 4119747, 27665211, 79806380, 99021067, 9257405, 51466049, 94711842, 56484900, 24953498, 60955663, 20140249, 99226875, 38256849, 99333375, 84187166, 67513640, 56955985, 17386996, 48774913, 82779622, 99971982, 91255408, 4985896, 44550764, 2917920, 67124282, 61141874, 91281584, 36753250, 65038678, 82327024, 9599614, 92803766, 83210802, 4787945, 4095116, 66137019, 91727510, 45790169, 97561745, 33431960, 94090109, 13173644, 47361209, 57961282, 86001008, 17957593, 72274002, 17068582, 43357947, 10961421, 73235980, 96420429, 55602660, 23569917, 20568363, 1936762, 91831487, 36468541, 67084351, 36580610, 51472275, 17976208, 59405277, 27325428, 15015906, 21070110, 73222868, 22942635, 62490109, 68110330, 24619760, 1569515, 70727211, 31733363, 65880522, 22108665, 62552524, 47213183, 86301513, 63059024, 62803858, 89217461, 76671482, 874791, 80014588, 79880247, 4798568, 30463802, 7955293, 38365584, 92692283, 18411915, 71300104, 96906410, 99690194, 12303248, 70367851, 93562257, 73109997, 51464002, 86118021, 50007421, 8791066, 4508700, 38022834, 27041967, 91664334, 14045383, 49641577, 28851716, 69355476, 18699206, 92033260, 65074535, 4069912, 44479073, 56153345, 39201414, 99524975, 1431742, 78300864, 83083131, 40781854, 3294781, 62496012, 41092102, 68939068, 45848907, 26292919, 84127901, 57803235, 40686254, 99515901, 76330843, 14349098, 5832946, 30771409, 79821897, 40152546, 247198, 54987042, 62452034, 45049308, 71083565, 84349107, 90158785, 83651211, 72725103, 41092172, 70800879, 26102057, 45617087, 29510992, 88698958, 96852321, 3233084, 78785507, 65271999, 54762643, 45237957, 95581843, 96735716, 82427263, 4515343, 30694952, 98943869, 47792865, 91990218, 70036438, 4978543, 34493392, 30764367, 70596786, 21993752, 35456853, 15902805, 13348726, 68000591, 9575954, 7423788, 21289531, 77301523, 77312810, 82979980, 93933709, 16097038, 94360702, 82052050, 8913721, 63506281, 36845587, 98371444, 12348118, 41245325, 55960386, 18783702, 56461322, 74357852, 30811010, 72732205, 65047700, 8733068, 74441448, 64055960, 9603598, 87710366, 34432810, 44983451, 22721500, 82897371, 56515456, 56531125, 43506672, 79191827, 90310261, 35996293, 45667668, 44473167, 80251430, 17539625, 75820087, 93359396, 72019362, 26998766, 75128745, 66250369, 36312813, 51315460, 28734791, 51590803, 17365188, 89499542, 69605283, 19486173, 61928316, 75407347, 95395112, 58180653, 40197395, 66322959, 73617245, 415901, 9188443, 95957797, 90004325, 50188404, 68204242, 83302115, 12024238, 6819644, 73021291, 74452589, 55669657, 33201905, 26744917, 12664567, 89078848, 17081350, 37891451, 94076128, 29065964, 526217, 38645117, 45407418, 27411561, 68102437, 3183975, 95290172, 61271144, 36189527, 15948937, 18466635, 54663246, 53547802, 62232211, 33061250, 3773993, 89046466, 87598888, 30543215, 11543098, 84293052, 91957544, 52060076, 824872, 77072625, 11581181, 96318094, 62693428, 26664538, 59109400, 17764950, 58749, 26392416, 98739783, 37675718, 26063929, 84904436, 71920426, 74743862, 59371804, 76540481, 3509435, 5970607 +55470718, 99729357, 80555751, 43152977, 58749, 39801351, 26063929, 415901, 69786756, 50007421, 76330843, 76540481, 69641513, 86001008, 20836893, 53648154, 38009615, 30569392, 72614359, 78909561, 32426752, 6505939, 90004325, 34497327, 36780454, 12664567, 97641116, 61897582, 93566986, 44481640, 36139942, 44473167, 57961282, 17957593, 26998766, 99658235, 26776922, 23569917, 8651647, 36933038, 36580610, 37280276, 35456853, 70004753, 63059024, 93933709, 94360702, 39171895, 45428665, 39847321, 9188443, 74724075, 58751351, 77787724, 7182642, 92033260, 18806856, 1375023, 56484900, 64055960, 11757872, 99515901, 79821897, 96318094, 48893685, 16387575, 45075471, 83368048, 5267545, 79942022, 14947650, 66137019, 94090109, 47361209, 63372756, 88653118, 58224549, 49328608, 9860968, 68816413, 69848388, 53547802, 24058273, 21673260, 75135448, 61728685, 29932657, 33123618, 53666583, 36685023, 63506281, 36845587, 51047803, 96906410, 81172706, 55960386, 34044787, 38022834, 52204879, 16054533, 59910624, 41481685, 50668599, 68204242, 9257405, 16567550, 51466049, 31491938, 99524975, 72357096, 20140249, 70420215, 62762857, 89078848, 14349098, 86821229, 8696647, 2917920, 54517921, 61859581, 92803766, 45407418, 41092172, 85242963, 97281847, 80251430, 84684495, 72019362, 78785507, 66611759, 54762643, 8055981, 9623492, 44664587, 3088684, 96735716, 82427263, 91802888, 81805959, 51315460, 1936762, 95010552, 57393458, 37957788, 6157724, 77272628, 94539824, 26734892, 55770687, 69605283, 82178706, 22879907, 62490109, 15902805, 68110330, 99861373, 15064655, 30090481, 68000591, 77377183, 17016156, 569864, 33553959, 82979980, 26275734, 72793444, 73617245, 2331773, 4798568, 15535065, 75986488, 48658605, 112651, 33249630, 5073754, 6808825, 51426311, 25636669, 8791066, 29466406, 97379790, 36135, 72732205, 24314885, 19272365, 66428795, 65074535, 76960303, 10264691, 83302115, 47298834, 1431742, 67030811, 56424103, 96193415, 9603598, 97783876, 24168411, 39986008, 77413012, 6986898, 7502255, 71657078, 10453030, 94595668, 91255408, 22721500, 82897371, 62693428, 56531125, 321665, 88904910, 29065964, 95251277, 61960400, 92530431, 44842615, 9599614, 23432750, 83651211, 99549373, 27411561, 45790169, 45667668, 18001617, 33431960, 17539625, 92215320, 13231279, 88698958, 38510840, 90013093, 17068582, 61623915, 75153252, 3487592, 91141395, 65271999, 28787861, 3880712, 4515343, 79922758, 6038457, 38494874, 42947632, 21919959, 70782102, 30366150, 47893286, 70036438, 81677380, 89996536, 34236719, 45794415, 53393358, 70596786, 73392814, 3773993, 1569515, 37224844, 76434144, 67031644, 9575954, 95395112, 62051033, 62803858, 29959549, 77312810, 89217461, 23134715, 42967683, 16097038, 7300047, 8913721, 48892201, 83948335, 91957544, 49597667, 92541302, 98371444, 7685448, 71300104, 89214616, 99690194, 70372191, 22766820, 27187213, 93562257, 36808486, 70541760, 4508700, 41442762, 22113133, 95957797, 14045383, 16424199, 90654836, 33935899, 49271185, 86798033, 16684829, 99021067, 87720882, 60393039, 14731700, 20002147, 66319530, 30218878, 3294781, 77072625, 77187825, 33565483, 67513640, 8128637, 84127901, 46870723, 99224392, 4064751, 21397057, 46540998, 34946859, 40534591, 16380211, 34432810, 14220886, 74743862, 45306070, 44627776, 91281584, 57020481, 43506672, 71083565, 31904591, 32161669, 10366309, 23848565, 91240048, 40677414, 90158785, 92787493, 17764950, 20642888, 22405120, 16405341, 4095116, 83533741, 35996293, 94516935, 97940276, 75543508, 59371804, 4434662, 30139692, 13173644, 78218845, 19891772, 58208470, 44348328, 96852321, 19939935, 27185644, 35092039, 72274002, 93359396, 87160386, 84840549, 48395186, 36312813, 95581843, 96420429, 55602660, 74137926, 75052463, 91831487, 60106217, 95290172, 36468541, 26392416, 67084351, 49236559, 18466635, 84166196, 14723410, 98130363, 24591705, 81853704, 65715134, 21993752, 21070110, 24619760, 44177011, 83789391, 76170907, 12571310, 59197747, 24826575, 88130087, 69255765, 86301513, 43045786, 37183543, 86543538, 89804152, 42199455, 82052050, 40197395, 66271566, 11543098, 52293995, 42307449, 874791, 30463802, 19101477, 1239555, 41380093, 23110625, 6793819, 85116755, 18411915, 12303248, 43322743, 42692881, 70367851, 71522968, 37501808, 83747892, 67281495, 56461322, 71333116, 16971929, 31126490, 7517032, 61859143, 57359924, 4119747, 91938191, 7687278, 72238278, 73786944, 55247455, 74441448, 77284619, 5822202, 66663942, 87710366, 11581181, 50567636, 45996863, 57240218, 83155442, 17385531, 52261574, 30771409, 40152546, 94076128, 10597197, 97057187, 50806615, 53888755, 4985896, 11365791, 5111370, 89637706, 62452034, 9886593, 61141874, 19457589, 65038678, 43501211, 82339363, 68824981, 93057697, 19376156, 69136837, 90310261, 13470059, 35512853, 51588015, 26102057, 33336148, 97561745, 90272749, 29516592, 8634541, 83133790, 68102437, 45237957, 66667729, 79657802, 53842979, 42782652, 26114953, 78549759, 33628349, 9259676, 98943869, 81274566, 1788101, 26397786, 42237907, 10358899, 63628376, 22200378, 64848072, 51472275, 67451935, 59405277, 65454636, 32250045, 51590803, 22997281, 62430984, 15163258, 38008118, 1197320, 17857111, 67227442, 38658347, 62232211, 80246713, 8807940, 12416768, 86460488, 64087743, 33061250, 93790285, 55753905, 19486173, 65880522, 5482538, 78589145, 62552524, 45990383, 52613508, 8129978, 7423788, 98739783, 20765474, 42644903, 38341669, 21289531, 47887470, 61263068, 37675718, 3233569, 85117092, 34698463, 16019925, 66322959, 84293052, 36505482, 35192533, 73124510, 77620120, 72738685, 29635537, 9175338, 92604458, 59177718, 77694700, 44889423, 71469330, 73109997, 63152504, 79136082, 18504197, 41245325, 86118021, 4099191, 56473732, 47090124, 7011964, 33699435, 99297164, 33237508, 5970607, 96726697, 52060076, 4091162, 54232247, 72373496, 85571389, 39201414, 8733068, 94711842, 86242799, 4668450, 62496012, 61982238, 36930650, 79322415, 55669657, 38256849, 54606384, 83269727, 84187166, 68939068, 26292919, 40686254, 1250437, 82726008, 47738185, 59318837, 99971982, 44550764, 69697787, 68694897, 94911072, 99125126, 526217, 77300457, 74614639, 15148031, 59109400, 76825057, 72725103, 48673079, 26139110, 33797252, 68041839, 45617087, 57241521, 69579137, 21533347, 3509435, 26863229, 71965942, 90457870, 28928175, 40865610, 28550822, 11923835, 62115552, 20568363, 89419466, 59981773, 14326617, 91990218, 54014062, 13862149, 48675329, 36189527, 11161731, 72495719, 97011160, 7919588, 65338021, 19898053, 89499542, 44844121, 40984766, 54263880, 89046466, 61373987, 176257, 40027975, 8729146, 98648327, 3235882, 78561158, 75407347, 5640302, 13819358, 92554120, 65275241, 60581278, 82886381, 77301523, 43933006, 60102965, 48088883, 55189057, 76671482, 48260151, 84904436, 84002370, 46851987, 26507214, 62936963, 61815223, 7955293, 32590267, 57163802, 63967300, 29029316, 2204165, 51464002, 23740167, 7066775, 18783702, 39373729, 89811711, 68644627, 59329511, 29834512, 27041967, 43376279, 78766358, 88047921, 55143724, 81774825, 68316156, 77898274, 82651278, 20122224, 40781449, 37659250, 82532312, 27665211, 79806380, 18699206, 1204161, 29401781, 72777973, 29994197, 95726235, 24953498, 40781854, 99226875, 57658654, 37166608, 31727379, 17037369, 26744917, 57803235, 17081350, 96709982, 22129328, 17727650, 48774913, 44060493, 32151165, 6871053, 37891451, 669105, 67124282, 65017137, 82327024, 8115266, 4787945, 4806458, 80316608, 93053405, 26493119, 29510992, 81855445, 75820087, 98462867, 33304202, 17894977, 22450468, 53084256, 7104732, 68702632, 28796059, 508198, 2208785, 57248122, 30694952, 85023028, 14093520, 61271144, 44915235, 93515664, 28734791, 17976208, 92398073, 20885148, 15948937, 27325428, 17365188, 34493392, 32159704, 30764367, 64098930, 61712234, 32699744, 6153406, 78884452, 85695762, 22942635, 30891921, 30787683, 79738755, 64157906, 13348726, 44784505, 47213183, 68875490, 92867155, 75777973, 6111563, 58180653, 51149804, 76703609, 13468268, 54427233, 30653863, 62740044, 44847298, 61741594, 37560164, 16099750, 42073124, 92998591, 47708850, 18663507, 37620363, 85711894, 49641577, 32058615, 28851716, 69355476, 4069912, 44479073, 50188404, 23194618, 45919976, 60955663, 98948034, 33201905, 62428472, 99333375, 45381876, 15536795, 36396314, 50702367, 29819940, 45995325, 65851721, 73031054, 168541, 44983451, 72278539, 90090964, 92353856, 44846932, 36753250, 18833224, 79191827, 99917599, 91727510, 21001913, 76315420, 51715482, 49882705, 75128745, 10961421, 34698428, 66250369, 54199160, 3183975, 47792865, 75229462, 83150534, 749283, 6497830, 4978543, 15075176, 9860195, 10309525, 53802686, 90061527, 68128438, 15015906, 20681921, 16595436, 30395570, 70727211, 48360198, 64602895, 22108665, 92071990, 66116458, 10649306, 73168565, 55850790, 4204661, 51507425, 80014588, 92692283, 29188588, 12348118, 7646095, 99965001, 17146629, 18131876, 2607799, 91664334, 74357852, 24915585, 30811010, 27625735, 74110882, 14363867, 12024238, 78300864, 59501487, 74452589, 86022504, 89699445, 37192445, 82779622, 54058788, 247198, 66832478, 56515456, 24733232, 26664538, 84349107, 83210802, 9058407, 70800879, 8373289, 3233084, 25842078, 43357947, 8099994, 88251446, 62357986, 93270084, 66045587, 7229550, 66903004, 9829782, 14626618, 59614746, 54663246, 3633375, 73222868, 81898046, 38061439, 31733363, 31161687, 17058722, 79880247, 55615885, 65081429, 71316369, 92692978, 71920426, 56153345, 37435892, 83083131, 6819644, 56955985, 45848907, 53632373, 30163921, 54987042, 2717150, 6521313, 72358170, 36812683, 45049308, 39553046, 38645117, 60176618, 2891150, 49598724, 16445503, 73235980, 33895336, 30998561, 60430369, 78602717, 34667286, 6525948, 20486294, 99604946, 20867149, 87598888, 61928316, 98653983, 38365584, 34295794, 65047700, 63015256, 40356877, 20645197, 17386996, 5832946, 67793644, 32274392, 6221471, 88444207, 84406788, 1022677, 30543215, 54868730, 67474219, 73021291, 9398733, 4199704, 37739481, 44245960, 824872, 41092102, 22435353, 66885828 +61623915, 29516592, 85695762, 82886381, 57393458, 30787683, 65047700, 168541, 44842615, 26114953, 98943869, 98130363, 13348726, 43045786, 39201414, 57240218, 59318837, 67793644, 669105, 22721500, 18833224, 35996293, 17894977, 91802888, 85023028, 14093520, 51472275, 10309525, 5640302, 63506281, 65081429, 2331773, 73124510, 9188443, 18411915, 42073124, 66885828, 83747892, 22113133, 5970607, 16684829, 40781854, 79322415, 45996863, 29819940, 54058788, 72358170, 4806458, 17764950, 48673079, 3509435, 86001008, 71965942, 90457870, 10961421, 34698428, 4515343, 96420429, 51315460, 36580610, 21993752, 20486294, 48360198, 52613508, 78561158, 65275241, 77312810, 51149804, 26507214, 71522968, 25636669, 91664334, 49641577, 86798033, 50188404, 83302115, 1250437, 52261574, 96318094, 44983451, 32274392, 82339363, 8115266, 99549373, 80316608, 94090109, 38510840, 22450468, 7104732, 33895336, 62115552, 3183975, 9259676, 30366150, 9829782, 70036438, 9860195, 14626618, 64098930, 35456853, 44844121, 20867149, 64157906, 67031644, 44784505, 10649306, 61263068, 89217461, 16097038, 60102965, 53666583, 17058722, 62936963, 48892201, 91957544, 29635537, 57163802, 92998591, 22766820, 42692881, 93562257, 33699435, 58751351, 27625735, 4069912, 72238278, 51466049, 86242799, 59501487, 77072625, 86022504, 39986008, 53632373, 83155442, 96709982, 17385531, 94076128, 2717150, 44550764, 45306070, 92530431, 31904591, 4787945, 45407418, 68041839, 97281847, 8373289, 8634541, 13173644, 81855445, 43357947, 99658235, 28550822, 58749, 66250369, 66667729, 68702632, 33628349, 66903004, 81274566, 68816413, 22200378, 44915235, 28734791, 67451935, 15075176, 11161731, 30764367, 1197320, 19898053, 55770687, 15902805, 38009615, 12416768, 44177011, 76170907, 55753905, 24826575, 5482538, 98739783, 31161687, 29959549, 77301523, 37183543, 7300047, 8913721, 76703609, 99729357, 73617245, 72614359, 7955293, 415901, 72738685, 70372191, 112651, 18663507, 23740167, 56473732, 7011964, 96726697, 52060076, 4091162, 28851716, 71920426, 82532312, 76960303, 14363867, 95726235, 37435892, 55247455, 74441448, 20002147, 67474219, 30218878, 3294781, 61982238, 96193415, 57658654, 77187825, 89699445, 31727379, 17037369, 15536795, 84127901, 76330843, 46540998, 40534591, 94595668, 50806615, 72278539, 56515456, 29065964, 91281584, 65038678, 43501211, 39553046, 83368048, 79191827, 26664538, 82327024, 19376156, 84349107, 69136837, 20642888, 9058407, 27411561, 93053405, 4434662, 49598724, 29510992, 26863229, 93359396, 84840549, 78785507, 88251446, 11923835, 63372756, 6221471, 48395186, 62357986, 58224549, 73235980, 9623492, 79657802, 96735716, 508198, 82427263, 60430369, 74137926, 23569917, 59981773, 55470718, 83150534, 88444207, 13862149, 63628376, 4199704, 36189527, 59405277, 51590803, 81677380, 62430984, 89996536, 84166196, 20836893, 15015906, 20681921, 3633375, 53547802, 45794415, 53393358, 78884452, 69605283, 21070110, 81898046, 30891921, 62490109, 80246713, 33061250, 79738755, 93790285, 12571310, 30090481, 22108665, 92071990, 8129978, 13819358, 38341669, 73168565, 33123618, 21289531, 47887470, 37675718, 569864, 42967683, 72793444, 58180653, 30543215, 54427233, 874791, 79880247, 84002370, 35192533, 19101477, 32590267, 75986488, 6793819, 85116755, 89214616, 43322743, 12348118, 44889423, 79136082, 51464002, 18504197, 4508700, 89811711, 71333116, 74357852, 78766358, 81774825, 30811010, 69355476, 74110882, 56153345, 40356877, 94711842, 12024238, 60393039, 99524975, 43152977, 78300864, 60955663, 67030811, 98948034, 20140249, 62762857, 41092102, 83269727, 26292919, 36396314, 6986898, 22129328, 99224392, 4064751, 17727650, 21397057, 79821897, 37891451, 10597197, 97057187, 61897582, 4985896, 90090964, 48893685, 67124282, 5111370, 88904910, 36753250, 45049308, 526217, 77300457, 45075471, 68824981, 32161669, 9599614, 92803766, 59109400, 76540481, 14947650, 91727510, 2891150, 26139110, 33336148, 80251430, 78218845, 17539625, 13231279, 57961282, 84684495, 96852321, 76315420, 75153252, 3487592, 95581843, 78602717, 81805959, 30694952, 38494874, 1936762, 91831487, 89419466, 42947632, 75229462, 14326617, 21919959, 95290172, 36933038, 1788101, 84406788, 17976208, 92398073, 34667286, 32250045, 27325428, 90061527, 17857111, 7919588, 24591705, 65715134, 6153406, 53648154, 62232211, 21673260, 68110330, 99604946, 38061439, 30395570, 24619760, 70727211, 31733363, 99861373, 75135448, 59197747, 76434144, 77377183, 75407347, 86301513, 7423788, 20765474, 92867155, 62803858, 1022677, 39171895, 98653983, 42199455, 82052050, 11543098, 13468268, 66322959, 51507425, 84904436, 46851987, 4798568, 44847298, 36845587, 37739481, 38365584, 49597667, 15535065, 9175338, 51047803, 71300104, 92604458, 96906410, 29029316, 7646095, 73109997, 7066775, 18783702, 86118021, 56461322, 47090124, 34044787, 71316369, 68644627, 38022834, 27041967, 52204879, 2607799, 31126490, 77898274, 57359924, 90004325, 72732205, 65074535, 68204242, 72777973, 72373496, 45919976, 10264691, 24953498, 64055960, 1431742, 66319530, 97783876, 24168411, 87710366, 37166608, 68939068, 50567636, 89078848, 40686254, 46870723, 71657078, 34432810, 65851721, 97641116, 14220886, 74743862, 82897371, 2917920, 56531125, 89637706, 94911072, 44846932, 36812683, 54517921, 93566986, 24733232, 61859581, 10366309, 40677414, 38645117, 13470059, 23432750, 35512853, 26102057, 85242963, 83533741, 75543508, 59371804, 18001617, 90272749, 30139692, 44473167, 92215320, 44348328, 27185644, 90013093, 17068582, 68102437, 40865610, 54762643, 88653118, 3088684, 28796059, 30998561, 49328608, 2208785, 7229550, 95010552, 37280276, 48675329, 37957788, 72495719, 77272628, 68128438, 34236719, 6525948, 14723410, 81853704, 32699744, 38658347, 73222868, 82178706, 22879907, 54263880, 61373987, 1569515, 40027975, 37224844, 8729146, 19486173, 78589145, 61928316, 45990383, 9575954, 68875490, 82979980, 6111563, 94360702, 48088883, 4204661, 3233569, 89804152, 55189057, 66271566, 52293995, 16019925, 55615885, 78909561, 37560164, 77620120, 34295794, 29188588, 48658605, 99690194, 32426752, 5073754, 74724075, 47708850, 6505939, 99965001, 8791066, 17146629, 59329511, 77787724, 29466406, 7182642, 85711894, 37659250, 4119747, 27665211, 7687278, 19272365, 44479073, 50668599, 23194618, 9257405, 85571389, 29994197, 73021291, 72357096, 824872, 66663942, 56424103, 54606384, 33201905, 11581181, 99333375, 67513640, 17386996, 82726008, 5832946, 10453030, 40152546, 16380211, 73031054, 86821229, 6521313, 92353856, 69697787, 62693428, 321665, 62452034, 9886593, 19457589, 43506672, 61960400, 15148031, 23848565, 60176618, 76825057, 90158785, 92787493, 22405120, 16405341, 70800879, 66137019, 33797252, 26493119, 45667668, 97561745, 33431960, 47361209, 75820087, 33304202, 51715482, 72019362, 28928175, 53084256, 65271999, 66611759, 54199160, 3880712, 26776922, 6038457, 36468541, 26392416, 67084351, 42237907, 54014062, 4978543, 65454636, 53802686, 15948937, 18466635, 32159704, 26734892, 67227442, 65338021, 70596786, 22942635, 8807940, 40984766, 3773993, 89046466, 83789391, 15064655, 98648327, 62552524, 47213183, 63059024, 95395112, 55850790, 60581278, 86543538, 26275734, 76671482, 85117092, 34698463, 42307449, 30653863, 80555751, 61741594, 1239555, 92692283, 23110625, 7685448, 54868730, 63967300, 12303248, 77694700, 44245960, 2204165, 36808486, 41245325, 51426311, 4099191, 99297164, 95957797, 29834512, 18131876, 16054533, 16424199, 41481685, 36135, 92692978, 24314885, 33935899, 91938191, 1204161, 66428795, 99021067, 18806856, 8733068, 47298834, 31491938, 56484900, 77284619, 5822202, 99226875, 34497327, 70420215, 11757872, 62428472, 26744917, 8128637, 12664567, 77413012, 44060493, 34946859, 6871053, 11365791, 9398733, 44627776, 16387575, 99917599, 44481640, 36139942, 72725103, 41092172, 4095116, 97940276, 45790169, 45617087, 57241521, 19891772, 88698958, 19939935, 72274002, 87160386, 26998766, 91141395, 8055981, 44664587, 93270084, 66045587, 75052463, 47792865, 70782102, 61271144, 64848072, 749283, 20885148, 97011160, 22997281, 34493392, 59614746, 38008118, 54663246, 69848388, 16595436, 24058273, 73392814, 64087743, 92554120, 75777973, 61728685, 17016156, 33553959, 48260151, 84293052, 62740044, 36505482, 30463802, 41380093, 45428665, 71469330, 37620363, 37501808, 63152504, 6808825, 55960386, 41442762, 43376279, 55143724, 14045383, 97379790, 32058615, 68316156, 82651278, 61859143, 20122224, 79806380, 92033260, 63015256, 16567550, 20645197, 4668450, 14731700, 6819644, 38256849, 36780454, 56955985, 37192445, 45848907, 45381876, 99515901, 47738185, 82779622, 54987042, 91255408, 8696647, 65017137, 99125126, 61141874, 57020481, 95251277, 5267545, 83210802, 90310261, 79942022, 69641513, 17957593, 83133790, 35092039, 8099994, 16445503, 49882705, 75128745, 45237957, 36312813, 79922758, 78549759, 20568363, 9860968, 26397786, 91990218, 47893286, 17365188, 61712234, 89499542, 70004753, 87598888, 65880522, 88130087, 42644903, 29932657, 40197395, 80014588, 92541302, 39847321, 16099750, 98371444, 70541760, 33237508, 40781449, 29401781, 73786944, 62496012, 9603598, 36930650, 74452589, 55669657, 33565483, 14349098, 7502255, 45995325, 99971982, 91240048, 94516935, 22435353, 69579137, 58208470, 3233084, 25842078, 21001913, 28787861, 55602660, 8651647, 60106217, 6497830, 6157724, 15163258, 94539824, 86460488, 3235882, 69255765, 30569392, 66116458, 39801351, 23134715, 26063929, 61815223, 83948335, 69786756, 33249630, 81172706, 67281495, 16971929, 7517032, 24915585, 54232247, 49271185, 87720882, 1375023, 84187166, 57803235, 48774913, 247198, 30163921, 66832478, 53888755, 68694897, 74614639, 71083565, 93057697, 21533347, 98462867, 42782652, 49236559, 93515664, 176257, 68000591, 93933709, 43933006, 36685023, 59177718, 27187213, 70367851, 50007421, 39373729, 88047921, 18699206, 83083131, 51588015, 53842979, 57248122, 64602895, 90654836, 50702367, 17081350, 83651211, 62051033, 59910624, 30771409, 10358899, 32151165 +86022504, 11923835, 70372191, 7646095, 4069912, 25842078, 54199160, 16595436, 3633375, 7423788, 52060076, 83302115, 94595668, 61960400, 68824981, 6221471, 68816413, 27325428, 98130363, 99861373, 64157906, 84904436, 77694700, 58751351, 18131876, 65047700, 72238278, 5822202, 24168411, 89637706, 92803766, 83210802, 40677414, 92787493, 17764950, 70800879, 88251446, 28928175, 53084256, 30694952, 47792865, 75229462, 59405277, 10309525, 54663246, 81853704, 24058273, 99604946, 52613508, 42644903, 29635537, 42073124, 66885828, 29834512, 16971929, 2607799, 32058615, 45919976, 14731700, 67474219, 67513640, 44060493, 29819940, 99971982, 66832478, 92353856, 56531125, 32274392, 69136837, 4787945, 78218845, 21533347, 98462867, 40865610, 62357986, 14093520, 84406788, 28734791, 92398073, 34236719, 89499542, 62232211, 81898046, 30395570, 15064655, 8729146, 55753905, 76434144, 98739783, 65275241, 82886381, 60102965, 55189057, 11543098, 8913721, 26063929, 63506281, 36505482, 415901, 91957544, 18411915, 48658605, 54868730, 29029316, 99965001, 25636669, 17146629, 91664334, 74357852, 16424199, 68316156, 30811010, 69355476, 71920426, 49271185, 92033260, 1375023, 66663942, 34497327, 41092102, 50567636, 56955985, 50702367, 83155442, 32151165, 59318837, 91255408, 90090964, 67124282, 526217, 18833224, 44481640, 61859581, 26102057, 66137019, 33797252, 97561745, 13173644, 58208470, 90457870, 75128745, 95581843, 96420429, 60430369, 3183975, 75052463, 38494874, 67084351, 10358899, 9829782, 48675329, 44915235, 18466635, 68128438, 15163258, 84166196, 38008118, 7919588, 61712234, 30891921, 62490109, 8807940, 86460488, 20867149, 70727211, 79738755, 78589145, 92071990, 75407347, 33553959, 43933006, 58180653, 48260151, 80014588, 30653863, 65081429, 84002370, 44847298, 37739481, 41380093, 45428665, 85116755, 9188443, 96906410, 33249630, 44889423, 73109997, 18783702, 56461322, 4508700, 41442762, 55143724, 74110882, 7687278, 19272365, 68204242, 29401781, 72777973, 85571389, 39201414, 99524975, 55247455, 86242799, 20002147, 77284619, 38256849, 17037369, 45381876, 99515901, 96318094, 45995325, 40152546, 34432810, 54987042, 86821229, 2717150, 48893685, 56515456, 321665, 9886593, 45049308, 43501211, 71083565, 15148031, 26664538, 10366309, 23848565, 19376156, 13470059, 99549373, 16405341, 9058407, 4095116, 27411561, 79942022, 35996293, 94516935, 68041839, 26493119, 45617087, 8373289, 94090109, 69579137, 47361209, 44348328, 96852321, 26863229, 3233084, 72274002, 93359396, 78785507, 49882705, 28550822, 3088684, 79657802, 42782652, 2208785, 4515343, 78602717, 66903004, 55470718, 95290172, 36468541, 13862149, 63628376, 67451935, 32250045, 90061527, 22997281, 6525948, 1197320, 17857111, 53547802, 65338021, 21993752, 61373987, 31733363, 59197747, 24826575, 5482538, 13348726, 68000591, 45990383, 9575954, 3235882, 78561158, 86301513, 63059024, 39801351, 95395112, 20765474, 62803858, 82979980, 42967683, 86543538, 4204661, 26275734, 82052050, 66271566, 51507425, 874791, 73617245, 55615885, 2331773, 26507214, 37560164, 23110625, 34295794, 72738685, 75986488, 57163802, 89214616, 99690194, 5073754, 71469330, 37620363, 86118021, 47090124, 99297164, 68644627, 5970607, 38022834, 77787724, 96726697, 78766358, 31126490, 97379790, 36135, 7517032, 57359924, 4091162, 27665211, 56153345, 50668599, 23194618, 10264691, 20645197, 40781854, 3294781, 824872, 99226875, 61982238, 11757872, 74452589, 77187825, 99333375, 68939068, 37192445, 45996863, 8128637, 12664567, 36396314, 17386996, 17081350, 52261574, 46870723, 4064751, 17727650, 7502255, 16380211, 67793644, 50806615, 30163921, 61897582, 669105, 44983451, 6521313, 74743862, 45306070, 5111370, 44846932, 44627776, 74614639, 39553046, 83368048, 79191827, 99917599, 24733232, 38645117, 90158785, 72725103, 4806458, 45407418, 48673079, 26139110, 22435353, 93053405, 49598724, 33336148, 30139692, 44473167, 33431960, 13231279, 57961282, 75820087, 3509435, 69641513, 84684495, 17894977, 17068582, 76315420, 8099994, 51715482, 61623915, 88653118, 44664587, 68702632, 26776922, 66045587, 30998561, 49328608, 79922758, 6038457, 81805959, 78549759, 20568363, 98943869, 91831487, 42947632, 30366150, 93515664, 15075176, 6157724, 15948937, 34667286, 81677380, 14626618, 32159704, 55770687, 69605283, 176257, 83789391, 87598888, 37224844, 64602895, 62552524, 69255765, 30569392, 66116458, 13819358, 92554120, 38341669, 31161687, 89804152, 76671482, 85117092, 13468268, 62740044, 72614359, 19101477, 32590267, 92692283, 29188588, 98371444, 43322743, 42692881, 36808486, 70541760, 55960386, 50007421, 33699435, 39373729, 22113133, 29466406, 43376279, 7182642, 16054533, 59910624, 81774825, 40781449, 28851716, 27625735, 72732205, 54232247, 82532312, 33935899, 44479073, 18806856, 24953498, 74441448, 60955663, 4668450, 73021291, 36930650, 55669657, 87710366, 33201905, 62428472, 84187166, 26292919, 57803235, 82726008, 76330843, 14349098, 48774913, 21397057, 10597197, 65851721, 97641116, 14220886, 11365791, 82897371, 62693428, 99125126, 61141874, 44842615, 9599614, 91240048, 59109400, 60176618, 23432750, 83651211, 51588015, 85242963, 97940276, 91727510, 2891150, 59371804, 45667668, 8634541, 19891772, 29510992, 81855445, 38510840, 35092039, 16445503, 22450468, 99658235, 10961421, 48395186, 8055981, 93270084, 66250369, 66667729, 36312813, 91802888, 55602660, 74137926, 33628349, 51315460, 1936762, 83150534, 21919959, 88444207, 26397786, 57393458, 49236559, 37280276, 4978543, 17976208, 53802686, 34493392, 89996536, 24591705, 15015906, 65715134, 6153406, 19898053, 38658347, 22942635, 80246713, 40984766, 30787683, 76170907, 19486173, 65880522, 30090481, 98648327, 5640302, 10649306, 75777973, 61728685, 55850790, 29932657, 21289531, 61263068, 48088883, 42199455, 52293995, 66322959, 54427233, 79880247, 46851987, 48892201, 15535065, 6793819, 47708850, 81172706, 18663507, 2204165, 7066775, 8791066, 95957797, 27041967, 85711894, 41481685, 77898274, 82651278, 20122224, 37659250, 65074535, 63015256, 72373496, 29994197, 16567550, 95726235, 47298834, 37435892, 83083131, 66319530, 67030811, 30218878, 62496012, 56424103, 70420215, 83269727, 11581181, 33565483, 6986898, 47738185, 5832946, 34946859, 6871053, 37891451, 247198, 73031054, 72278539, 68694897, 94911072, 62452034, 36812683, 19457589, 65038678, 82327024, 84349107, 90310261, 76825057, 41092172, 83533741, 4434662, 57241521, 80251430, 17539625, 19939935, 27185644, 72019362, 84840549, 75153252, 91141395, 58224549, 73235980, 96735716, 23569917, 89419466, 14326617, 95010552, 26392416, 22200378, 4199704, 36189527, 9860195, 11161731, 72495719, 97011160, 94539824, 59614746, 26734892, 20836893, 45794415, 78884452, 21070110, 22879907, 21673260, 12416768, 70004753, 93790285, 75135448, 88130087, 67031644, 44784505, 47213183, 8129978, 47887470, 569864, 37183543, 39171895, 40197395, 36685023, 34698463, 36845587, 61815223, 1239555, 92541302, 51047803, 71300104, 32426752, 63967300, 44245960, 63152504, 79136082, 51464002, 67281495, 33237508, 71316369, 90004325, 90654836, 4119747, 24314885, 86798033, 50188404, 40356877, 8733068, 12024238, 43152977, 64055960, 1431742, 78300864, 6819644, 20140249, 59501487, 57658654, 62762857, 79322415, 54606384, 89699445, 26744917, 89078848, 57240218, 22129328, 30771409, 10453030, 97057187, 53888755, 2917920, 29065964, 36753250, 77300457, 95251277, 45075471, 92530431, 31904591, 93566986, 82339363, 93057697, 5267545, 8115266, 76540481, 14947650, 75543508, 80316608, 97281847, 18001617, 83133790, 90013093, 43357947, 63372756, 54762643, 45237957, 53842979, 7229550, 62115552, 81274566, 61271144, 1788101, 54014062, 6497830, 37957788, 51590803, 77272628, 30764367, 67227442, 53393358, 53648154, 82178706, 15902805, 38061439, 48360198, 61928316, 62051033, 73168565, 60581278, 17016156, 77301523, 37675718, 93933709, 16097038, 53666583, 17058722, 30543215, 76703609, 7955293, 77620120, 9175338, 7685448, 92604458, 92998591, 74724075, 70367851, 93562257, 83747892, 6808825, 41245325, 23740167, 56473732, 71333116, 14045383, 49641577, 61859143, 24915585, 18699206, 66428795, 14363867, 87720882, 60393039, 98948034, 31727379, 77413012, 53632373, 82779622, 79821897, 94076128, 168541, 22721500, 69697787, 88904910, 16387575, 54517921, 35512853, 20642888, 22405120, 92215320, 33304202, 34698428, 65271999, 66611759, 9623492, 28787861, 3880712, 33895336, 508198, 82427263, 57248122, 26114953, 9259676, 85023028, 59981773, 70782102, 36933038, 64848072, 51472275, 65454636, 20885148, 17365188, 14723410, 20486294, 35456853, 68110330, 38009615, 1569515, 12571310, 77377183, 22108665, 43045786, 68875490, 92867155, 33123618, 6111563, 3233569, 98653983, 16019925, 84293052, 78909561, 61741594, 83948335, 38365584, 49597667, 16099750, 69786756, 59177718, 112651, 22766820, 71522968, 37501808, 4099191, 7011964, 89811711, 59329511, 92692978, 79806380, 91938191, 76960303, 16684829, 99021067, 73786944, 31491938, 56484900, 96193415, 9603598, 97783876, 36780454, 39986008, 45848907, 84127901, 17385531, 54058788, 4985896, 9398733, 72358170, 57020481, 36139942, 45790169, 29516592, 17957593, 87160386, 26998766, 68102437, 3487592, 58749, 8651647, 9860968, 42237907, 36580610, 91990218, 749283, 70036438, 62430984, 64098930, 20681921, 85695762, 73222868, 33061250, 3773993, 54263880, 44177011, 89046466, 29959549, 77312810, 89217461, 94360702, 7300047, 72793444, 51149804, 99729357, 80555751, 62936963, 35192533, 30463802, 73124510, 39847321, 12303248, 27187213, 6505939, 18504197, 1204161, 9257405, 51466049, 72357096, 37166608, 15536795, 40686254, 1250437, 99224392, 46540998, 40534591, 44550764, 65017137, 91281584, 32161669, 90272749, 88698958, 71965942, 21001913, 47893286, 69848388, 32699744, 64087743, 40027975, 1022677, 42307449, 4798568, 12348118, 51426311, 88047921, 94711842, 77072625, 96709982, 43506672, 7104732, 28796059, 44844121, 24619760, 23134715, 34044787, 52204879, 8696647, 70596786, 73392814, 71657078, 86001008, 60106217 +16019925, 44627776, 65851721, 89046466, 92554120, 6111563, 72357096, 97783876, 72358170, 95251277, 22405120, 8055981, 70782102, 70596786, 3235882, 23134715, 37560164, 51047803, 66319530, 7502255, 247198, 43506672, 10366309, 90272749, 91141395, 28787861, 28796059, 82427263, 4199704, 4978543, 17365188, 14723410, 70727211, 38341669, 3233569, 98653983, 79880247, 61815223, 7955293, 72738685, 16099750, 112651, 27187213, 81172706, 37501808, 18504197, 92692978, 12024238, 3294781, 55669657, 67513640, 57240218, 99224392, 4064751, 11365791, 67124282, 16405341, 4434662, 97281847, 26863229, 17957593, 21001913, 90013093, 87160386, 58749, 3880712, 508198, 30694952, 75052463, 9860968, 1788101, 91990218, 9829782, 69848388, 73222868, 44177011, 8729146, 60581278, 1022677, 84293052, 26507214, 62936963, 38365584, 69786756, 74724075, 2204165, 93562257, 79136082, 51464002, 38022834, 77898274, 82651278, 57359924, 1204161, 87720882, 86242799, 6819644, 79322415, 36780454, 89699445, 45848907, 45996863, 96709982, 82779622, 97057187, 30163921, 53888755, 44550764, 44846932, 39553046, 90158785, 94516935, 57241521, 47361209, 92215320, 98462867, 25842078, 83133790, 71965942, 72019362, 26998766, 68102437, 99658235, 34698428, 65271999, 66667729, 79922758, 7229550, 8651647, 1936762, 89419466, 26392416, 42237907, 84406788, 36189527, 37957788, 18466635, 51590803, 22997281, 94539824, 73392814, 21070110, 22942635, 80246713, 70004753, 93790285, 24826575, 65880522, 76434144, 45990383, 75777973, 17016156, 569864, 86543538, 16097038, 39171895, 4204661, 51149804, 44847298, 61741594, 45428665, 42073124, 63967300, 51426311, 89811711, 22113133, 59329511, 29466406, 91664334, 31126490, 90654836, 79806380, 33935899, 16684829, 56153345, 9257405, 51466049, 31491938, 56424103, 34497327, 36930650, 26744917, 84127901, 17081350, 48774913, 30771409, 71657078, 79821897, 168541, 4985896, 22721500, 82897371, 321665, 36812683, 24733232, 32161669, 36139942, 60176618, 27411561, 97940276, 2891150, 26493119, 18001617, 29516592, 44473167, 94090109, 58208470, 13231279, 57961282, 16445503, 3487592, 58224549, 73235980, 36312813, 42782652, 98943869, 91831487, 60106217, 14093520, 21919959, 95010552, 36933038, 88444207, 57393458, 37280276, 92398073, 68128438, 34236719, 38008118, 15015906, 67227442, 16595436, 32699744, 85695762, 82178706, 21673260, 99604946, 33061250, 3773993, 24619760, 87598888, 37224844, 59197747, 48360198, 77377183, 86301513, 63059024, 43045786, 61728685, 21289531, 37675718, 55189057, 80014588, 65081429, 84002370, 80555751, 49597667, 23110625, 92541302, 39847321, 6793819, 54868730, 12303248, 12348118, 83747892, 23740167, 7066775, 56473732, 52204879, 88047921, 7182642, 55143724, 85711894, 20122224, 40781449, 37659250, 4119747, 18699206, 91938191, 73786944, 40356877, 18806856, 60393039, 24953498, 78300864, 40781854, 98948034, 9603598, 77187825, 83269727, 62428472, 11581181, 99333375, 31727379, 45381876, 8128637, 15536795, 36396314, 40686254, 76330843, 46870723, 17727650, 40534591, 6871053, 73031054, 86821229, 91255408, 6521313, 8696647, 2917920, 9398733, 99125126, 71083565, 83368048, 99917599, 61859581, 44842615, 93057697, 91240048, 40677414, 90310261, 13470059, 4787945, 35512853, 20642888, 26139110, 49598724, 68041839, 78218845, 69579137, 17539625, 81855445, 88698958, 3233084, 27185644, 17068582, 76315420, 93359396, 66611759, 57248122, 55602660, 74137926, 81805959, 42947632, 55470718, 75229462, 14326617, 26397786, 70036438, 44915235, 77272628, 97011160, 34493392, 54663246, 1197320, 7919588, 53393358, 19898053, 78884452, 69605283, 38658347, 22879907, 62232211, 44844121, 62490109, 15902805, 38009615, 64087743, 54263880, 40027975, 76170907, 88130087, 78589145, 9575954, 30569392, 98739783, 62051033, 29932657, 82886381, 77301523, 89217461, 43933006, 89804152, 17058722, 40197395, 52293995, 85117092, 42307449, 874791, 55615885, 72614359, 36505482, 37739481, 83948335, 32590267, 85116755, 18411915, 98371444, 7685448, 71300104, 92998591, 22766820, 47708850, 71469330, 63152504, 6505939, 70541760, 8791066, 17146629, 34044787, 71316369, 71333116, 2607799, 43376279, 14045383, 16054533, 90004325, 4091162, 86798033, 65074535, 99021067, 50668599, 68204242, 23194618, 85571389, 39201414, 45919976, 8733068, 47298834, 43152977, 55247455, 4668450, 20002147, 30218878, 73021291, 62496012, 5822202, 61982238, 33201905, 33565483, 39986008, 53632373, 17385531, 99515901, 40152546, 94595668, 97641116, 669105, 72278539, 2717150, 65017137, 88904910, 61141874, 43501211, 54517921, 15148031, 26664538, 92803766, 72725103, 51588015, 26102057, 85242963, 75543508, 93053405, 45667668, 30139692, 8634541, 19891772, 86001008, 40865610, 28550822, 75128745, 11923835, 45237957, 44664587, 4515343, 96420429, 91802888, 62115552, 23569917, 20568363, 66903004, 81274566, 59981773, 61271144, 49236559, 22200378, 749283, 48675329, 9860195, 65454636, 11161731, 15948937, 59614746, 17857111, 20681921, 53547802, 6153406, 20486294, 35456853, 53648154, 38061439, 30787683, 20867149, 67031644, 13348726, 30090481, 61928316, 44784505, 47213183, 68875490, 39801351, 95395112, 47887470, 37183543, 93933709, 94360702, 53666583, 99729357, 13468268, 73617245, 84904436, 30653863, 2331773, 4798568, 78909561, 30463802, 1239555, 41380093, 92692283, 29188588, 9175338, 99690194, 43322743, 29029316, 7646095, 6808825, 55960386, 4099191, 47090124, 39373729, 68644627, 16971929, 18131876, 96726697, 36135, 52060076, 24915585, 30811010, 69355476, 66428795, 76960303, 29994197, 94711842, 99524975, 56484900, 37435892, 64055960, 83083131, 60955663, 77284619, 20140249, 59501487, 11757872, 74452589, 17037369, 12664567, 26292919, 77413012, 57803235, 14349098, 46540998, 45995325, 10597197, 61897582, 48893685, 68694897, 19457589, 91281584, 57020481, 18833224, 32274392, 92530431, 82339363, 83210802, 38645117, 23432750, 17764950, 83533741, 79942022, 45617087, 33431960, 80251430, 13173644, 69641513, 38510840, 33304202, 35092039, 72274002, 43357947, 63372756, 7104732, 66045587, 30998561, 6038457, 26114953, 85023028, 95290172, 47893286, 51472275, 93515664, 17976208, 15075176, 27325428, 62430984, 15163258, 32159704, 26734892, 45794415, 89499542, 55770687, 40984766, 68110330, 30395570, 61373987, 1569515, 176257, 83789391, 31733363, 99861373, 19486173, 64157906, 78561158, 7423788, 5640302, 29959549, 77312810, 82979980, 42967683, 7300047, 72793444, 36685023, 76671482, 34698463, 76703609, 26063929, 35192533, 73124510, 34295794, 15535065, 29635537, 57163802, 5073754, 44889423, 44245960, 67281495, 7011964, 33699435, 25636669, 95957797, 77787724, 29834512, 74357852, 59910624, 61859143, 28851716, 27625735, 72732205, 63015256, 44479073, 50188404, 72777973, 95726235, 14731700, 57658654, 38256849, 41092102, 37166608, 68939068, 50567636, 83155442, 1250437, 22129328, 47738185, 44060493, 29819940, 94076128, 67793644, 54987042, 62693428, 94911072, 29065964, 36753250, 16387575, 526217, 65038678, 74614639, 61960400, 68824981, 44481640, 82327024, 9599614, 23848565, 8115266, 83651211, 99549373, 9058407, 76540481, 35996293, 14947650, 33336148, 84684495, 19939935, 84840549, 48395186, 88653118, 9623492, 68702632, 26776922, 96735716, 2208785, 3183975, 78602717, 51315460, 9259676, 47792865, 54014062, 63628376, 6497830, 59405277, 20885148, 34667286, 89996536, 30764367, 6525948, 61712234, 81853704, 3633375, 86460488, 75135448, 68000591, 22108665, 62552524, 66116458, 8129978, 13819358, 31161687, 33123618, 61263068, 33553959, 48088883, 58180653, 42199455, 82052050, 11543098, 8913721, 51507425, 46851987, 48892201, 75986488, 9188443, 48658605, 92604458, 70367851, 18663507, 36808486, 50007421, 4508700, 99297164, 33237508, 5970607, 27041967, 16424199, 49641577, 68316156, 7517032, 54232247, 82532312, 49271185, 7687278, 14363867, 72373496, 10264691, 16567550, 1375023, 1431742, 99226875, 66663942, 96193415, 70420215, 62762857, 17386996, 52261574, 21397057, 34946859, 10453030, 34432810, 99971982, 66832478, 14220886, 90090964, 74743862, 92353856, 69697787, 45306070, 56515456, 89637706, 45049308, 45075471, 93566986, 84349107, 59109400, 92787493, 70800879, 59371804, 80316608, 22435353, 97561745, 29510992, 75820087, 96852321, 51715482, 61623915, 75153252, 53084256, 3088684, 54199160, 78549759, 33628349, 38494874, 68816413, 36580610, 30366150, 6157724, 90061527, 81677380, 64098930, 21993752, 81898046, 12416768, 79738755, 12571310, 55753905, 5482538, 92867155, 55850790, 26275734, 66271566, 66322959, 54427233, 91957544, 77620120, 70372191, 33249630, 71522968, 41245325, 99965001, 18783702, 78766358, 97379790, 41481685, 71920426, 24314885, 65047700, 19272365, 4069912, 67474219, 77072625, 54606384, 24168411, 84187166, 37192445, 89078848, 6986898, 5832946, 59318837, 37891451, 16380211, 50806615, 5111370, 62452034, 77300457, 79191827, 31904591, 5267545, 19376156, 76825057, 4806458, 41092172, 48673079, 4095116, 66137019, 45790169, 33797252, 8373289, 17894977, 8099994, 90457870, 49882705, 93270084, 66250369, 95581843, 33895336, 49328608, 83150534, 36468541, 10358899, 67451935, 53802686, 32250045, 72495719, 84166196, 98130363, 20836893, 24058273, 64602895, 98648327, 92071990, 69255765, 10649306, 20765474, 62803858, 73168565, 60102965, 30543215, 36845587, 415901, 59177718, 42692881, 66885828, 77694700, 37620363, 41442762, 81774825, 27665211, 92033260, 29401781, 72238278, 83302115, 74441448, 824872, 86022504, 82726008, 32151165, 96318094, 44983451, 56531125, 9886593, 69136837, 45407418, 21533347, 3509435, 10961421, 6221471, 54762643, 79657802, 60430369, 67084351, 13862149, 28734791, 24591705, 65715134, 65338021, 30891921, 8807940, 52613508, 42644903, 62740044, 19101477, 96906410, 32426752, 73109997, 86118021, 56461322, 58751351, 32058615, 74110882, 20645197, 67030811, 56955985, 50702367, 54058788, 91727510, 53842979, 64848072, 10309525, 15064655, 75407347, 65275241, 48260151, 89214616, 78785507, 88251446, 28928175, 62357986, 87710366, 44348328, 22450468, 14626618, 63506281 +3509435, 85117092, 38365584, 12303248, 59329511, 27041967, 8733068, 29516592, 69579137, 93270084, 66667729, 59981773, 26392416, 42237907, 10358899, 38008118, 62936963, 15535065, 57163802, 54868730, 33699435, 4985896, 36139942, 44915235, 20885148, 54263880, 48360198, 66116458, 6111563, 89804152, 34295794, 70367851, 49271185, 31491938, 79322415, 11581181, 26292919, 96709982, 71657078, 56531125, 26664538, 5267545, 84349107, 76825057, 90158785, 94516935, 98462867, 71965942, 43357947, 3880712, 37280276, 10309525, 17365188, 20486294, 80246713, 44177011, 89046466, 55850790, 60581278, 8913721, 16019925, 874791, 92541302, 51464002, 18504197, 17146629, 22113133, 29466406, 72732205, 7687278, 73021291, 3294781, 57658654, 38256849, 33201905, 26744917, 21397057, 168541, 22721500, 61859581, 23432750, 16405341, 26102057, 30139692, 80251430, 17957593, 35092039, 3487592, 36312813, 95581843, 96735716, 20568363, 8651647, 6497830, 15075176, 14626618, 73392814, 15902805, 3773993, 61373987, 55753905, 78589145, 13819358, 86543538, 26275734, 99729357, 13468268, 84002370, 62740044, 36505482, 37739481, 69786756, 44245960, 73109997, 70541760, 23740167, 52204879, 31126490, 92692978, 63015256, 16684829, 50188404, 99021067, 72373496, 39201414, 78300864, 6819644, 56955985, 4064751, 10453030, 32151165, 79821897, 37891451, 30163921, 14220886, 99125126, 29065964, 18833224, 99917599, 82339363, 24733232, 59109400, 60176618, 41092172, 22435353, 4434662, 68041839, 57241521, 29510992, 47361209, 88698958, 76315420, 65271999, 66250369, 28796059, 49328608, 7229550, 78602717, 81805959, 33628349, 66903004, 30366150, 63628376, 9829782, 4199704, 4978543, 17976208, 51590803, 32159704, 67227442, 55770687, 22879907, 44844121, 40984766, 93790285, 67031644, 77377183, 9575954, 44784505, 8129978, 43045786, 20765474, 38341669, 31161687, 17016156, 61263068, 77301523, 16097038, 48088883, 55189057, 82052050, 36685023, 4798568, 98371444, 42692881, 6505939, 89811711, 38022834, 14045383, 36135, 20122224, 79806380, 85571389, 12024238, 824872, 59501487, 61982238, 83269727, 45381876, 15536795, 17385531, 48774913, 46540998, 59318837, 247198, 97057187, 72278539, 2717150, 74743862, 2917920, 9886593, 43506672, 44842615, 10366309, 13470059, 72725103, 48673079, 4095116, 27411561, 97940276, 8634541, 94090109, 86001008, 17894977, 78785507, 88251446, 28550822, 54762643, 62357986, 7104732, 58224549, 33895336, 3183975, 23569917, 9860968, 36933038, 88444207, 91990218, 54014062, 34667286, 27325428, 90061527, 97011160, 1197320, 61712234, 81853704, 32699744, 78884452, 81898046, 22942635, 38061439, 33061250, 70727211, 31733363, 40027975, 5482538, 88130087, 22108665, 98739783, 61728685, 47887470, 569864, 3233569, 72793444, 98653983, 58180653, 80014588, 72614359, 44847298, 36845587, 78909561, 35192533, 30463802, 7955293, 1239555, 16099750, 18411915, 92604458, 42073124, 22766820, 66885828, 12348118, 74724075, 47708850, 79136082, 51426311, 56473732, 34044787, 95957797, 18131876, 43376279, 78766358, 16424199, 32058615, 61859143, 30811010, 90654836, 18699206, 91938191, 86798033, 50668599, 40356877, 37435892, 24953498, 20645197, 74441448, 14731700, 66319530, 70420215, 62762857, 99333375, 50567636, 39986008, 12664567, 53632373, 22129328, 47738185, 34946859, 6871053, 10597197, 99971982, 65851721, 73031054, 669105, 91255408, 6521313, 69697787, 62693428, 36753250, 45075471, 39553046, 15148031, 82327024, 83210802, 8115266, 51588015, 20642888, 22405120, 59371804, 18001617, 97561745, 17539625, 21533347, 75820087, 44348328, 21001913, 33304202, 90013093, 99658235, 8055981, 9623492, 3088684, 68702632, 26776922, 508198, 82427263, 60430369, 98943869, 47792865, 55470718, 75229462, 83150534, 95010552, 61271144, 1788101, 36580610, 47893286, 51472275, 48675329, 70036438, 36189527, 67451935, 59614746, 84166196, 14723410, 69848388, 98130363, 17857111, 7919588, 20681921, 16595436, 65715134, 24058273, 6153406, 53393358, 82178706, 86460488, 30395570, 30787683, 79738755, 65880522, 98648327, 62552524, 47213183, 78561158, 75407347, 86301513, 63059024, 95395112, 62051033, 43933006, 39171895, 60102965, 4204661, 17058722, 42199455, 66271566, 42307449, 76703609, 26063929, 51507425, 84293052, 73617245, 55615885, 2331773, 32590267, 49597667, 37560164, 45428665, 9175338, 48658605, 71300104, 89214616, 70372191, 63967300, 29029316, 7646095, 63152504, 7066775, 99965001, 86118021, 4099191, 7011964, 4508700, 71316369, 68644627, 74357852, 55143724, 68316156, 52060076, 40781449, 28851716, 69355476, 82532312, 1204161, 65047700, 19272365, 65074535, 72777973, 45919976, 18806856, 86242799, 4668450, 67474219, 5822202, 77072625, 9603598, 11757872, 86022504, 17037369, 84187166, 84127901, 57240218, 83155442, 14349098, 46870723, 30771409, 40534591, 54058788, 90090964, 8696647, 67124282, 88904910, 91281584, 526217, 74614639, 9599614, 99549373, 4806458, 85242963, 2891150, 93053405, 49598724, 33336148, 45667668, 45617087, 58208470, 57961282, 69641513, 27185644, 72274002, 16445503, 93359396, 84840549, 49882705, 11923835, 6221471, 48395186, 28787861, 54199160, 79657802, 30998561, 42782652, 79922758, 55602660, 51315460, 75052463, 68816413, 60106217, 89419466, 42947632, 21919959, 36468541, 749283, 65454636, 77272628, 22997281, 34493392, 62430984, 15163258, 89996536, 30764367, 64098930, 6525948, 26734892, 20836893, 70596786, 21070110, 62490109, 8807940, 37224844, 8729146, 76170907, 19486173, 64157906, 61928316, 3235882, 7423788, 68875490, 92554120, 42644903, 33123618, 21289531, 77312810, 23134715, 7300047, 51149804, 34698463, 79880247, 84904436, 61741594, 73124510, 75986488, 39847321, 29635537, 7685448, 59177718, 33249630, 5073754, 77694700, 81172706, 71522968, 71469330, 37501808, 41245325, 67281495, 47090124, 58751351, 77787724, 71333116, 16054533, 7517032, 77898274, 82651278, 37659250, 24314885, 33935899, 87720882, 95726235, 51466049, 60393039, 47298834, 43152977, 98948034, 62496012, 96193415, 97783876, 36780454, 31727379, 45996863, 8128637, 1250437, 52261574, 82779622, 44060493, 5832946, 16380211, 34432810, 67793644, 66832478, 53888755, 321665, 65017137, 44627776, 36812683, 45049308, 43501211, 61960400, 71083565, 54517921, 79191827, 93566986, 68824981, 91240048, 92803766, 40677414, 90310261, 83651211, 35512853, 92787493, 83533741, 14947650, 91727510, 75543508, 33797252, 26493119, 90272749, 33431960, 13173644, 92215320, 13231279, 19939935, 83133790, 17068582, 75128745, 10961421, 91141395, 66611759, 4515343, 62115552, 30694952, 1936762, 95290172, 70782102, 26397786, 67084351, 28734791, 9860195, 53547802, 19898053, 89499542, 69605283, 73222868, 62232211, 30891921, 21673260, 87598888, 75135448, 64602895, 76434144, 68000591, 30569392, 10649306, 75777973, 29932657, 1022677, 89217461, 82979980, 42967683, 76671482, 11543098, 66322959, 30653863, 65081429, 80555751, 19101477, 48892201, 415901, 83948335, 92692283, 29188588, 6793819, 96906410, 99690194, 92998591, 18663507, 2204165, 83747892, 50007421, 25636669, 99297164, 41442762, 39373729, 29834512, 7182642, 59910624, 57359924, 27625735, 4119747, 74110882, 27665211, 66428795, 76960303, 56153345, 68204242, 23194618, 29994197, 20002147, 66663942, 36930650, 74452589, 41092102, 33565483, 37192445, 77413012, 89078848, 36396314, 57803235, 17386996, 17081350, 99515901, 17727650, 29819940, 40152546, 94076128, 86821229, 44550764, 92353856, 48893685, 45306070, 56515456, 89637706, 72358170, 16387575, 77300457, 95251277, 92530431, 19376156, 38645117, 4787945, 17764950, 19891772, 84684495, 3233084, 25842078, 8099994, 51715482, 72019362, 61623915, 58749, 34698428, 63372756, 88653118, 73235980, 45237957, 66045587, 57248122, 6038457, 78549759, 91831487, 81274566, 14326617, 37957788, 59405277, 6157724, 11161731, 53802686, 72495719, 94539824, 15015906, 3633375, 21993752, 38658347, 53648154, 68110330, 38009615, 1569515, 83789391, 59197747, 13348726, 30090481, 45990383, 52613508, 69255765, 92867155, 62803858, 73168565, 82886381, 29959549, 40197395, 46851987, 77620120, 23110625, 72738685, 85116755, 37620363, 93562257, 18783702, 56461322, 8791066, 2607799, 91664334, 88047921, 49641577, 41481685, 81774825, 24915585, 92033260, 44479073, 29401781, 9257405, 73786944, 16567550, 1375023, 99524975, 1431742, 83083131, 60955663, 40781854, 77284619, 20140249, 87710366, 68939068, 67513640, 50702367, 99224392, 94595668, 82897371, 68694897, 9398733, 5111370, 57020481, 65038678, 32274392, 83368048, 44481640, 32161669, 93057697, 69136837, 79942022, 66137019, 80316608, 97281847, 38510840, 90457870, 26998766, 68102437, 28928175, 75153252, 53084256, 44664587, 2208785, 96420429, 9259676, 14093520, 57393458, 13862149, 84406788, 49236559, 15948937, 81677380, 45794415, 64087743, 24619760, 15064655, 24826575, 93933709, 94360702, 48260151, 63506281, 54427233, 91957544, 112651, 27187213, 36808486, 33237508, 96726697, 85711894, 71920426, 14363867, 4069912, 10264691, 30218878, 72357096, 99226875, 77187825, 55669657, 54606384, 24168411, 37166608, 6986898, 40686254, 82726008, 45995325, 97641116, 62452034, 19457589, 45407418, 9058407, 76540481, 45790169, 44473167, 8373289, 81855445, 22450468, 74137926, 85023028, 22200378, 32250045, 68128438, 34236719, 54663246, 24591705, 65338021, 85695762, 99604946, 92071990, 5640302, 39801351, 37675718, 33553959, 37183543, 52293995, 26507214, 61815223, 41380093, 9188443, 51047803, 32426752, 44889423, 55960386, 4091162, 83302115, 56484900, 56424103, 34497327, 89699445, 50806615, 44983451, 94911072, 44846932, 31904591, 70800879, 26139110, 96852321, 26863229, 87160386, 40865610, 53842979, 91802888, 26114953, 64848072, 93515664, 18466635, 70004753, 176257, 99861373, 12571310, 30543215, 6808825, 5970607, 55247455, 45848907, 61897582, 11365791, 35996293, 38494874, 35456853, 12416768, 20867149, 53666583, 97379790, 90004325, 72238278, 64055960, 67030811, 76330843, 7502255, 96318094, 54987042, 61141874, 23848565, 78218845, 65275241, 43322743, 16971929, 54232247, 94711842, 62428472, 92398073 +14349098, 23432750, 9860968, 54987042, 26734892, 20836893, 70596786, 21289531, 13468268, 89811711, 29466406, 56424103, 97783876, 33565483, 86821229, 33431960, 19891772, 17068582, 45237957, 14326617, 4199704, 36845587, 61741594, 43322743, 63152504, 8791066, 88047921, 85711894, 77898274, 72357096, 8128637, 48774913, 94911072, 72358170, 44627776, 43506672, 38645117, 4095116, 94516935, 18001617, 27185644, 87160386, 75153252, 3880712, 79657802, 57248122, 47792865, 37957788, 89996536, 69848388, 20486294, 63059024, 6111563, 89804152, 17058722, 69786756, 27041967, 74357852, 96726697, 82651278, 61859143, 24314885, 68204242, 40356877, 6986898, 99224392, 21397057, 65851721, 22721500, 11365791, 65017137, 95251277, 65038678, 32161669, 40677414, 20642888, 68041839, 45617087, 57241521, 47361209, 88698958, 25842078, 17957593, 83133790, 38510840, 76315420, 26998766, 62357986, 26776922, 78549759, 8651647, 81274566, 95290172, 88444207, 42237907, 84406788, 47893286, 67451935, 24058273, 82178706, 64087743, 93790285, 76170907, 76434144, 3235882, 37675718, 77312810, 82979980, 23134715, 86543538, 30543215, 42307449, 84293052, 78909561, 23110625, 51047803, 63967300, 81172706, 37501808, 41442762, 55143724, 14045383, 59910624, 41481685, 20122224, 28851716, 92692978, 33935899, 14363867, 87720882, 40781854, 20140249, 66663942, 11757872, 79322415, 74452589, 68939068, 67513640, 46870723, 79821897, 247198, 6521313, 44550764, 2917920, 99125126, 88904910, 36753250, 74614639, 61960400, 71083565, 15148031, 24733232, 82327024, 93057697, 23848565, 92803766, 90310261, 59109400, 4787945, 92787493, 16405341, 35996293, 81855445, 75820087, 96852321, 33304202, 68102437, 53084256, 6221471, 28796059, 49328608, 7229550, 75052463, 38494874, 91831487, 68816413, 60106217, 42947632, 70782102, 49236559, 22200378, 10309525, 72495719, 54663246, 14723410, 32699744, 85695762, 53648154, 73222868, 37224844, 12571310, 55753905, 19486173, 17016156, 33553959, 11543098, 48260151, 55615885, 19101477, 37560164, 92692283, 48658605, 12348118, 7646095, 71469330, 70541760, 67281495, 56473732, 68644627, 16971929, 7182642, 57359924, 90004325, 30811010, 72732205, 66428795, 65074535, 63015256, 99021067, 23194618, 72238278, 73786944, 51466049, 31491938, 24953498, 67030811, 30218878, 37166608, 36780454, 84187166, 26292919, 77413012, 40686254, 30771409, 71657078, 40534591, 6871053, 168541, 97641116, 30163921, 91255408, 45306070, 89637706, 44846932, 61141874, 77300457, 72725103, 41092172, 70800879, 85242963, 27411561, 80316608, 45667668, 44473167, 94090109, 13231279, 57961282, 8099994, 54762643, 44664587, 28787861, 79922758, 81805959, 26114953, 89419466, 67084351, 93515664, 28734791, 27325428, 90061527, 77272628, 34493392, 15163258, 32159704, 38008118, 1197320, 35456853, 62232211, 81898046, 89046466, 83789391, 22108665, 39801351, 75777973, 60581278, 94360702, 43933006, 39171895, 60102965, 4204661, 40197395, 51149804, 874791, 80014588, 84904436, 46851987, 30463802, 32590267, 34295794, 75986488, 6793819, 9175338, 7685448, 92604458, 92998591, 22766820, 27187213, 74724075, 44245960, 71522968, 2204165, 36808486, 7066775, 50007421, 56461322, 33699435, 38022834, 77787724, 29834512, 32058615, 4091162, 90654836, 37659250, 4119747, 4069912, 29401781, 60393039, 99524975, 55247455, 74441448, 1431742, 6819644, 96193415, 9603598, 62762857, 38256849, 24168411, 87710366, 26744917, 84127901, 57240218, 96709982, 22129328, 4064751, 44060493, 7502255, 34946859, 10597197, 50806615, 72278539, 74743862, 48893685, 8696647, 9398733, 67124282, 62452034, 91281584, 57020481, 45049308, 43501211, 79191827, 61859581, 9599614, 83210802, 60176618, 35512853, 48673079, 79942022, 97940276, 29516592, 8634541, 58208470, 92215320, 98462867, 3233084, 43357947, 51715482, 11923835, 65271999, 9623492, 95581843, 82427263, 62115552, 23569917, 66903004, 64848072, 6497830, 9860195, 6157724, 92398073, 15948937, 97011160, 81677380, 22997281, 68128438, 59614746, 24591705, 20681921, 53547802, 6153406, 15902805, 12416768, 3773993, 70727211, 31733363, 40027975, 48360198, 13348726, 45990383, 78561158, 98739783, 92554120, 31161687, 73168565, 29959549, 93933709, 7300047, 3233569, 26275734, 66322959, 79880247, 30653863, 415901, 1239555, 49597667, 92541302, 45428665, 29188588, 9188443, 71300104, 112651, 42692881, 5073754, 93562257, 6505939, 79136082, 51464002, 18504197, 4099191, 47090124, 7011964, 58751351, 71316369, 18131876, 16424199, 36135, 7517032, 52060076, 40781449, 69355476, 74110882, 82532312, 91938191, 1204161, 9257405, 16567550, 8733068, 37435892, 64055960, 78300864, 66319530, 77284619, 3294781, 77072625, 57658654, 36930650, 54606384, 33201905, 89699445, 12664567, 36396314, 17386996, 17081350, 52261574, 29819940, 54058788, 37891451, 97057187, 61897582, 66832478, 53888755, 4985896, 69697787, 68694897, 56515456, 36812683, 32274392, 83368048, 5267545, 76825057, 83651211, 76540481, 14947650, 66137019, 26493119, 97561745, 90272749, 80251430, 13173644, 78218845, 84684495, 17894977, 22450468, 84840549, 78785507, 99658235, 63372756, 88653118, 73235980, 36312813, 68702632, 96735716, 508198, 2208785, 55602660, 9259676, 59981773, 21919959, 36468541, 91990218, 30366150, 10358899, 37280276, 9829782, 32250045, 62430984, 65715134, 45794415, 78884452, 22879907, 22942635, 21673260, 80246713, 68110330, 86460488, 33061250, 30395570, 30787683, 70004753, 79738755, 15064655, 65880522, 9575954, 44784505, 86301513, 8129978, 43045786, 68875490, 13819358, 62051033, 82886381, 47887470, 1022677, 98653983, 55189057, 76671482, 52293995, 8913721, 76703609, 99729357, 51507425, 65081429, 80555751, 37739481, 61815223, 83948335, 91957544, 29635537, 16099750, 89214616, 42073124, 12303248, 18663507, 23740167, 51426311, 86118021, 25636669, 17146629, 4508700, 34044787, 33237508, 97379790, 24915585, 18699206, 49271185, 76960303, 44479073, 50668599, 72777973, 85571389, 29994197, 45919976, 47298834, 86242799, 14731700, 98948034, 73021291, 62496012, 5822202, 83269727, 15536795, 83155442, 1250437, 82726008, 76330843, 17727650, 82779622, 46540998, 10453030, 96318094, 73031054, 669105, 2717150, 82897371, 321665, 29065964, 31904591, 82339363, 84349107, 90158785, 51588015, 99549373, 75543508, 59371804, 22435353, 45790169, 4434662, 97281847, 30139692, 29510992, 17539625, 21533347, 44348328, 26863229, 93359396, 72019362, 28550822, 75128745, 48395186, 7104732, 8055981, 66667729, 53842979, 66045587, 4515343, 78602717, 85023028, 14093520, 36933038, 1788101, 26397786, 36580610, 749283, 59405277, 65454636, 11161731, 34236719, 61712234, 15015906, 16595436, 73392814, 38658347, 30891921, 44844121, 62490109, 38009615, 99604946, 87598888, 99861373, 78589145, 30090481, 98648327, 68000591, 77377183, 75407347, 30569392, 20765474, 38341669, 55850790, 33123618, 569864, 37183543, 42967683, 16097038, 72793444, 36685023, 66271566, 34698463, 72614359, 7955293, 48892201, 85116755, 18411915, 59177718, 29029316, 47708850, 70367851, 73109997, 55960386, 99297164, 59329511, 5970607, 43376279, 49641577, 81774825, 27625735, 71920426, 27665211, 86798033, 50188404, 56153345, 39201414, 83302115, 94711842, 43152977, 61982238, 70420215, 11581181, 31727379, 17037369, 39986008, 56955985, 53632373, 32151165, 94076128, 16380211, 67793644, 526217, 54517921, 93566986, 68824981, 44842615, 10366309, 36139942, 19376156, 69136837, 13470059, 45407418, 22405120, 9058407, 26102057, 83533741, 91727510, 26139110, 93053405, 33336148, 8373289, 86001008, 35092039, 72274002, 90457870, 49882705, 66611759, 3088684, 66250369, 54199160, 42782652, 60430369, 6038457, 74137926, 51315460, 98943869, 1936762, 83150534, 61271144, 57393458, 54014062, 70036438, 36189527, 17365188, 94539824, 6525948, 98130363, 17857111, 7919588, 67227442, 53393358, 19898053, 21070110, 38061439, 24619760, 54263880, 44177011, 176257, 64602895, 88130087, 67031644, 62552524, 66116458, 7423788, 5640302, 58180653, 42199455, 85117092, 63506281, 16019925, 84002370, 26507214, 44847298, 62936963, 36505482, 35192533, 38365584, 41380093, 72738685, 39847321, 98371444, 54868730, 99690194, 70372191, 33249630, 66885828, 83747892, 99965001, 39373729, 22113133, 95957797, 71333116, 16054533, 79806380, 7687278, 19272365, 92033260, 12024238, 1375023, 56484900, 20645197, 83083131, 60955663, 67474219, 59501487, 34497327, 77187825, 55669657, 89078848, 50702367, 59318837, 40152546, 44983451, 14220886, 90090964, 92353856, 39553046, 92530431, 44481640, 8115266, 4806458, 2891150, 69641513, 19939935, 16445503, 28928175, 40865610, 61623915, 58749, 10961421, 34698428, 58224549, 93270084, 96420429, 91802888, 30694952, 75229462, 95010552, 13862149, 63628376, 48675329, 4978543, 17976208, 15075176, 20885148, 53802686, 51590803, 30764367, 84166196, 3633375, 65338021, 89499542, 69605283, 21993752, 8807940, 40984766, 61373987, 1569515, 20867149, 75135448, 59197747, 24826575, 64157906, 52613508, 47213183, 92071990, 42644903, 61728685, 29932657, 48088883, 82052050, 26063929, 77620120, 15535065, 57163802, 77694700, 37620363, 41245325, 68316156, 65047700, 16684829, 72373496, 10264691, 20002147, 824872, 45848907, 45381876, 45996863, 17385531, 99515901, 47738185, 5832946, 94595668, 5111370, 62693428, 56531125, 9886593, 26664538, 17764950, 69579137, 21001913, 88251446, 3487592, 3183975, 33628349, 20568363, 51472275, 44915235, 81853704, 55770687, 8729146, 5482538, 10649306, 92867155, 62803858, 53666583, 4798568, 96906410, 32426752, 44889423, 6808825, 2607799, 91664334, 78766358, 95726235, 18806856, 86022504, 99333375, 37192445, 57803235, 45995325, 34432810, 16387575, 18833224, 33797252, 49598724, 3509435, 71965942, 90013093, 91141395, 55470718, 64098930, 61928316, 69255765, 95395112, 89217461, 54427233, 73124510, 18783702, 52204879, 54232247, 4668450, 99226875, 62428472, 99971982, 19457589, 91240048, 33895336, 30998561, 26392416, 18466635, 14626618, 65275241, 73617245, 2331773, 31126490, 41092102, 45075471, 34667286, 77301523, 62740044, 50567636, 99917599, 61263068 +66250369, 54427233, 66885828, 41481685, 62452034, 44627776, 94090109, 81855445, 22450468, 28928175, 11923835, 6221471, 14093520, 36468541, 93515664, 37675718, 33553959, 4798568, 80555751, 18663507, 5970607, 97379790, 99226875, 11757872, 99224392, 321665, 68824981, 99549373, 44473167, 26863229, 57248122, 74137926, 78549759, 13862149, 49236559, 70036438, 35456853, 48360198, 88130087, 65275241, 47887470, 70372191, 90654836, 72777973, 29994197, 40781854, 56955985, 26292919, 7502255, 44550764, 48893685, 9886593, 526217, 32274392, 15148031, 69136837, 40677414, 8115266, 70800879, 59371804, 17894977, 42782652, 82427263, 1936762, 68816413, 21919959, 84406788, 59405277, 11161731, 32699744, 85695762, 68110330, 99604946, 70004753, 31733363, 29959549, 37183543, 16097038, 4204661, 2331773, 54868730, 71522968, 4099191, 56461322, 68644627, 2607799, 91664334, 69355476, 44479073, 64055960, 86242799, 14731700, 72357096, 62428472, 45381876, 17386996, 83155442, 17081350, 52261574, 4064751, 17727650, 29819940, 94076128, 66832478, 669105, 8696647, 45306070, 44842615, 76825057, 9058407, 26139110, 93053405, 58208470, 69641513, 88251446, 28550822, 58224549, 9623492, 66045587, 6038457, 57393458, 51472275, 65454636, 53802686, 62430984, 15015906, 86460488, 83789391, 15064655, 8729146, 30090481, 7423788, 68875490, 95395112, 62803858, 82886381, 1022677, 6111563, 66271566, 11543098, 8913721, 66322959, 46851987, 19101477, 61741594, 29635537, 32426752, 42073124, 33249630, 22766820, 42692881, 7646095, 37501808, 55960386, 56473732, 41442762, 16971929, 90004325, 20122224, 1204161, 56153345, 39201414, 10264691, 55247455, 30218878, 3294781, 824872, 5822202, 66663942, 34497327, 87710366, 17385531, 53888755, 4985896, 90090964, 19457589, 65038678, 31904591, 32161669, 90310261, 17764950, 85242963, 83533741, 35996293, 33797252, 45617087, 8373289, 29510992, 90013093, 34698428, 79657802, 2208785, 4515343, 55602660, 33628349, 36933038, 26392416, 42237907, 64848072, 749283, 4199704, 27325428, 17365188, 81677380, 84166196, 14723410, 1197320, 20681921, 16595436, 20486294, 12416768, 76434144, 64157906, 67031644, 47213183, 75407347, 30569392, 98739783, 13819358, 20765474, 55850790, 569864, 86543538, 26275734, 72793444, 55189057, 30543215, 42307449, 84293052, 30653863, 415901, 91957544, 32590267, 9188443, 99690194, 112651, 29029316, 47708850, 37620363, 36808486, 83747892, 18504197, 18783702, 50007421, 17146629, 4508700, 43376279, 74357852, 88047921, 36135, 81774825, 77898274, 82651278, 27625735, 54232247, 24314885, 86798033, 14363867, 95726235, 8733068, 99524975, 43152977, 78300864, 62762857, 41092102, 89078848, 1250437, 30771409, 32151165, 45995325, 73031054, 54987042, 6521313, 82897371, 9398733, 89637706, 65017137, 72358170, 91281584, 45049308, 77300457, 74614639, 99917599, 24733232, 10366309, 23848565, 4806458, 4095116, 97940276, 80316608, 33431960, 78218845, 98462867, 19939935, 21001913, 17068582, 43357947, 72019362, 49882705, 40865610, 53084256, 58749, 63372756, 48395186, 53842979, 60430369, 7229550, 51315460, 38494874, 85023028, 47792865, 48675329, 28734791, 67451935, 15075176, 6157724, 92398073, 20885148, 10309525, 59614746, 65715134, 45794415, 89499542, 69605283, 82178706, 15902805, 8807940, 38061439, 24619760, 30787683, 40027975, 76170907, 12571310, 19486173, 98648327, 77377183, 61928316, 62552524, 42644903, 62051033, 60581278, 61263068, 77301523, 82979980, 60102965, 3233569, 53666583, 82052050, 76671482, 48260151, 26063929, 51507425, 79880247, 84002370, 26507214, 36505482, 23110625, 6793819, 9175338, 85116755, 89214616, 74724075, 44889423, 67281495, 99965001, 7011964, 89811711, 71316369, 77787724, 29834512, 78766358, 57359924, 4091162, 63015256, 4069912, 87720882, 72373496, 18806856, 83302115, 1375023, 20645197, 60955663, 66319530, 67474219, 77284619, 59501487, 61982238, 56424103, 97783876, 54606384, 24168411, 83269727, 31727379, 84187166, 68939068, 39986008, 45996863, 8128637, 36396314, 57240218, 50702367, 40686254, 22129328, 47738185, 44060493, 34946859, 50806615, 97641116, 44983451, 72278539, 2717150, 11365791, 68694897, 56515456, 62693428, 56531125, 44846932, 16387575, 95251277, 18833224, 79191827, 93566986, 82339363, 19376156, 92803766, 60176618, 13470059, 83651211, 4787945, 20642888, 22405120, 48673079, 26102057, 27411561, 79942022, 2891150, 22435353, 49598724, 68041839, 97281847, 30139692, 13173644, 57961282, 3509435, 17957593, 83133790, 33304202, 51715482, 68102437, 75128745, 10961421, 73235980, 54199160, 30998561, 96420429, 3183975, 78602717, 9259676, 8651647, 81274566, 75229462, 1788101, 30366150, 9829782, 4978543, 9860195, 18466635, 32250045, 34236719, 98130363, 7919588, 3633375, 65338021, 19898053, 78884452, 55770687, 21993752, 62232211, 38009615, 3773993, 20867149, 70727211, 93790285, 24826575, 13348726, 3235882, 52613508, 92071990, 8129978, 43045786, 5640302, 75777973, 73168565, 33123618, 21289531, 23134715, 94360702, 48088883, 89804152, 98653983, 42199455, 40197395, 63506281, 65081429, 44847298, 35192533, 61815223, 48892201, 38365584, 92692283, 18411915, 7685448, 71300104, 92604458, 43322743, 2204165, 79136082, 86118021, 58751351, 95957797, 16054533, 59910624, 16424199, 32058615, 24915585, 72732205, 71920426, 74110882, 19272365, 92033260, 16684829, 68204242, 29401781, 85571389, 73786944, 60393039, 74441448, 83083131, 6819644, 98948034, 96193415, 57658654, 36930650, 74452589, 77187825, 38256849, 89699445, 17037369, 12664567, 82779622, 46540998, 79821897, 37891451, 65851721, 168541, 86821229, 91255408, 22721500, 92353856, 57020481, 36753250, 71083565, 39553046, 26664538, 61859581, 9599614, 93057697, 91240048, 83210802, 38645117, 51588015, 94516935, 66137019, 91727510, 33336148, 45667668, 18001617, 97561745, 69579137, 75820087, 86001008, 90457870, 87160386, 78785507, 61623915, 91141395, 66611759, 54762643, 44664587, 96735716, 98943869, 91831487, 89419466, 55470718, 14326617, 88444207, 67084351, 54014062, 47893286, 15948937, 97011160, 89996536, 30764367, 54663246, 24591705, 61712234, 81853704, 67227442, 24058273, 70596786, 73392814, 38658347, 44844121, 21673260, 80246713, 33061250, 54263880, 1569515, 79738755, 65880522, 68000591, 22108665, 45990383, 44784505, 69255765, 10649306, 29932657, 42967683, 58180653, 51149804, 76703609, 80014588, 84904436, 36845587, 62936963, 78909561, 37739481, 83948335, 73124510, 41380093, 16099750, 59177718, 63967300, 5073754, 77694700, 81172706, 93562257, 6808825, 33699435, 8791066, 39373729, 33237508, 59329511, 38022834, 71333116, 18131876, 96726697, 7182642, 85711894, 68316156, 52060076, 40781449, 30811010, 28851716, 79806380, 33935899, 50188404, 72238278, 40356877, 45919976, 16567550, 94711842, 73021291, 20140249, 77072625, 37166608, 45848907, 53632373, 96709982, 82726008, 46870723, 40534591, 96318094, 40152546, 16380211, 94595668, 14220886, 69697787, 94911072, 99125126, 29065964, 61960400, 83368048, 44481640, 59109400, 35512853, 14947650, 45790169, 4434662, 8634541, 92215320, 96852321, 25842078, 38510840, 35092039, 93359396, 75153252, 62357986, 7104732, 8055981, 3088684, 66667729, 36312813, 95581843, 28787861, 508198, 91802888, 26114953, 95290172, 91990218, 36189527, 6497830, 37957788, 90061527, 51590803, 72495719, 77272628, 14626618, 68128438, 34493392, 6525948, 17857111, 53547802, 6153406, 21070110, 53648154, 30891921, 62490109, 40984766, 64087743, 44177011, 89046466, 61373987, 99861373, 9575954, 78561158, 66116458, 63059024, 17016156, 93933709, 34698463, 99729357, 62740044, 7955293, 92541302, 34295794, 72738685, 70367851, 63152504, 70541760, 34044787, 27041967, 55143724, 82532312, 27665211, 7687278, 65047700, 65074535, 37435892, 4668450, 20002147, 67030811, 70420215, 9603598, 86022504, 11581181, 50567636, 67513640, 37192445, 77413012, 57803235, 6986898, 99515901, 76330843, 5832946, 71657078, 54058788, 61897582, 74743862, 43501211, 54517921, 45075471, 92530431, 82327024, 5267545, 84349107, 45407418, 92787493, 41092172, 16405341, 75543508, 26493119, 17539625, 44348328, 3233084, 71965942, 27185644, 72274002, 8099994, 45237957, 3880712, 33895336, 26776922, 49328608, 30694952, 9860968, 59981773, 83150534, 61271144, 26397786, 22200378, 44915235, 17976208, 22997281, 94539824, 26734892, 20836893, 176257, 55753905, 64602895, 78589145, 77312810, 89217461, 43933006, 52293995, 85117092, 13468268, 874791, 55615885, 72614359, 1239555, 49597667, 77620120, 75986488, 29188588, 98371444, 48658605, 96906410, 69786756, 27187213, 44245960, 73109997, 41245325, 23740167, 25636669, 99297164, 14045383, 49641577, 7517032, 61859143, 4119747, 92692978, 91938191, 49271185, 66428795, 76960303, 99021067, 50668599, 23194618, 9257405, 51466049, 12024238, 31491938, 56484900, 36780454, 99333375, 59318837, 6871053, 99971982, 67124282, 88904910, 61141874, 36139942, 90158785, 29516592, 57241521, 88698958, 16445503, 65271999, 88653118, 93270084, 79922758, 62115552, 75052463, 66903004, 36580610, 10358899, 34667286, 15163258, 32159704, 38008118, 53393358, 73222868, 22879907, 81898046, 87598888, 37224844, 75135448, 38341669, 92867155, 61728685, 7300047, 17058722, 15535065, 92998591, 71469330, 6505939, 7066775, 51426311, 18699206, 84127901, 14349098, 21397057, 10453030, 34432810, 67793644, 30163921, 5111370, 76540481, 80251430, 19891772, 47361209, 84684495, 76315420, 26998766, 99658235, 68702632, 28796059, 23569917, 20568363, 60106217, 70782102, 63628376, 69848388, 22942635, 59197747, 5482538, 39801351, 31161687, 36685023, 30463802, 51047803, 12303248, 51464002, 47090124, 22113133, 52204879, 37659250, 79322415, 26744917, 48774913, 247198, 10597197, 2917920, 43506672, 72725103, 21533347, 13231279, 84840549, 81805959, 95010552, 37280276, 30395570, 92554120, 39171895, 16019925, 39847321, 12348118, 29466406, 31126490, 47298834, 24953498, 1431742, 55669657, 33565483, 97057187, 36812683, 23432750, 90272749, 3487592, 42947632, 86301513, 73617245, 37560164, 57163802, 62496012, 15536795, 64098930, 45428665, 33201905 +82052050, 72725103, 85242963, 7104732, 30694952, 24314885, 65047700, 4806458, 70782102, 1569515, 31733363, 44784505, 29959549, 49597667, 26744917, 73031054, 90158785, 97281847, 32699744, 61263068, 62936963, 23740167, 71316369, 95957797, 16424199, 65074535, 44479073, 71657078, 10453030, 247198, 34432810, 74743862, 45075471, 59109400, 4434662, 26493119, 98462867, 88698958, 71965942, 78785507, 68102437, 54762643, 89419466, 88444207, 38061439, 48360198, 77377183, 95395112, 17016156, 33553959, 51149804, 26063929, 16019925, 4798568, 35192533, 1239555, 6793819, 57163802, 7685448, 92998591, 43322743, 66885828, 93562257, 67281495, 86118021, 4119747, 69355476, 92033260, 45919976, 4668450, 98948034, 99333375, 50567636, 17386996, 48774913, 32151165, 96318094, 59318837, 8696647, 68694897, 65038678, 18833224, 83210802, 49598724, 90272749, 26863229, 25842078, 93359396, 49882705, 8055981, 53842979, 508198, 85023028, 26397786, 84406788, 17365188, 89996536, 38008118, 70596786, 12416768, 54263880, 87598888, 29932657, 60581278, 33123618, 86543538, 52293995, 2331773, 84002370, 36505482, 72738685, 16099750, 33249630, 77694700, 44889423, 18663507, 34044787, 16971929, 78766358, 20122224, 54232247, 27665211, 18699206, 91938191, 7687278, 1204161, 72373496, 8733068, 73021291, 59501487, 5822202, 61982238, 62762857, 74452589, 37166608, 68939068, 15536795, 89078848, 5832946, 79821897, 44983451, 4985896, 11365791, 9886593, 29065964, 91281584, 57020481, 82339363, 99549373, 14947650, 2891150, 75543508, 80316608, 45790169, 27185644, 90013093, 26998766, 34698428, 9623492, 93270084, 66045587, 79922758, 91831487, 59981773, 61271144, 37280276, 9829782, 53802686, 27325428, 61712234, 67227442, 55770687, 69605283, 30891921, 62490109, 15064655, 59197747, 19486173, 98648327, 62552524, 45990383, 47213183, 30569392, 1022677, 569864, 89217461, 42199455, 36685023, 11543098, 30653863, 80555751, 30463802, 38365584, 29188588, 44245960, 51426311, 4099191, 4508700, 29834512, 74357852, 36135, 77898274, 61859143, 92692978, 33935899, 40356877, 94711842, 83083131, 66319530, 67030811, 20140249, 97783876, 77187825, 83269727, 33565483, 37192445, 45381876, 8128637, 45995325, 37891451, 99971982, 30163921, 54987042, 44550764, 2917920, 9398733, 56515456, 62693428, 88904910, 19457589, 74614639, 39553046, 79191827, 44481640, 19376156, 51588015, 30139692, 33431960, 80251430, 13173644, 96852321, 22450468, 65271999, 73235980, 3880712, 4515343, 91802888, 81805959, 78549759, 14326617, 1788101, 10358899, 64848072, 36189527, 17976208, 15948937, 32250045, 51590803, 64098930, 69848388, 15015906, 20681921, 65715134, 81898046, 40984766, 3773993, 44177011, 79738755, 65880522, 5482538, 13348726, 22108665, 69255765, 98739783, 92554120, 38341669, 92867155, 61728685, 77301523, 82979980, 23134715, 94360702, 43933006, 4204661, 98653983, 76703609, 61741594, 83948335, 73124510, 29635537, 18411915, 51047803, 71522968, 37620363, 63152504, 18504197, 41245325, 18783702, 56473732, 99297164, 52204879, 31126490, 88047921, 14045383, 32058615, 52060076, 24915585, 40781449, 27625735, 82532312, 66428795, 56153345, 68204242, 10264691, 95726235, 47298834, 37435892, 70420215, 57658654, 36930650, 84187166, 67513640, 39986008, 26292919, 6986898, 96709982, 82726008, 76330843, 30771409, 10597197, 94595668, 61897582, 2717150, 6521313, 90090964, 45306070, 44846932, 36812683, 16387575, 526217, 77300457, 54517921, 9599614, 93057697, 10366309, 8115266, 13470059, 45407418, 9058407, 70800879, 83533741, 33336148, 8373289, 69579137, 3233084, 76315420, 16445503, 90457870, 72019362, 99658235, 61623915, 10961421, 48395186, 3088684, 66667729, 68702632, 54199160, 26776922, 30998561, 23569917, 51315460, 75052463, 38494874, 42947632, 47792865, 37957788, 15075176, 9860195, 65454636, 92398073, 20885148, 10309525, 18466635, 14626618, 15163258, 30764367, 84166196, 98130363, 24591705, 16595436, 45794415, 53393358, 73392814, 78884452, 89499542, 22942635, 80246713, 89046466, 30787683, 20867149, 70727211, 93790285, 37224844, 8729146, 75135448, 76434144, 61928316, 92071990, 8129978, 68875490, 10649306, 39801351, 62051033, 62803858, 82886381, 37675718, 37183543, 39171895, 72793444, 17058722, 8913721, 63506281, 66322959, 84293052, 37739481, 39847321, 9175338, 48658605, 71300104, 89214616, 96906410, 12303248, 74724075, 73109997, 41442762, 58751351, 71333116, 91664334, 85711894, 59910624, 82651278, 90004325, 72732205, 76960303, 14363867, 99021067, 23194618, 83302115, 12024238, 99524975, 43152977, 78300864, 30218878, 824872, 62496012, 66663942, 96193415, 34497327, 79322415, 54606384, 83155442, 1250437, 34946859, 6871053, 97641116, 82897371, 92353856, 5111370, 94911072, 65017137, 99125126, 61141874, 83368048, 92530431, 68824981, 36139942, 69136837, 60176618, 76825057, 23432750, 4787945, 41092172, 4095116, 79942022, 97940276, 44473167, 57241521, 19891772, 29510992, 13231279, 3509435, 84684495, 17957593, 35092039, 43357947, 8099994, 28550822, 75153252, 53084256, 66611759, 36312813, 28787861, 33895336, 49328608, 96735716, 82427263, 55602660, 6038457, 26114953, 81274566, 60106217, 36580610, 47893286, 51472275, 6497830, 67451935, 72495719, 77272628, 32159704, 65338021, 6153406, 19898053, 62232211, 68110330, 99604946, 24619760, 61373987, 83789391, 12571310, 64602895, 88130087, 67031644, 9575954, 78561158, 43045786, 13819358, 77312810, 26275734, 58180653, 40197395, 30543215, 80014588, 55615885, 46851987, 26507214, 62740044, 41380093, 23110625, 75986488, 45428665, 9188443, 98371444, 92604458, 59177718, 32426752, 112651, 12348118, 29029316, 70367851, 7646095, 6808825, 55960386, 47090124, 8791066, 27041967, 43376279, 7182642, 16054533, 81774825, 7517032, 57359924, 4091162, 90654836, 28851716, 79806380, 4069912, 16684829, 73786944, 51466049, 60393039, 55247455, 64055960, 9603598, 55669657, 33201905, 62428472, 45848907, 12664567, 57803235, 50702367, 47738185, 82779622, 21397057, 7502255, 54058788, 16380211, 65851721, 67793644, 50806615, 66832478, 669105, 86821229, 91255408, 22721500, 48893685, 67124282, 72358170, 36753250, 61960400, 32274392, 31904591, 24733232, 32161669, 61859581, 23848565, 91240048, 91727510, 59371804, 45667668, 8634541, 94090109, 81855445, 57961282, 44348328, 69641513, 86001008, 19939935, 83133790, 21001913, 33304202, 72274002, 17068582, 87160386, 40865610, 62357986, 58224549, 44664587, 28796059, 42782652, 60430369, 74137926, 33628349, 9259676, 9860968, 14093520, 83150534, 95010552, 36933038, 91990218, 30366150, 49236559, 28734791, 34667286, 81677380, 22997281, 59614746, 6525948, 26734892, 7919588, 3633375, 21070110, 44844121, 86460488, 33061250, 70004753, 176257, 75407347, 42644903, 73168565, 47887470, 93933709, 16097038, 55189057, 76671482, 48260151, 54427233, 79880247, 44847298, 36845587, 78909561, 415901, 91957544, 92692283, 92541302, 34295794, 54868730, 42073124, 63967300, 22766820, 47708850, 6505939, 33699435, 17146629, 39373729, 5970607, 41481685, 71920426, 19272365, 63015256, 87720882, 50668599, 85571389, 29994197, 16567550, 18806856, 1375023, 56484900, 74441448, 1431742, 60955663, 40781854, 77284619, 3294781, 72357096, 99226875, 36780454, 11581181, 56955985, 77413012, 36396314, 84127901, 99515901, 52261574, 22129328, 46870723, 99224392, 4064751, 168541, 321665, 89637706, 62452034, 45049308, 95251277, 43506672, 99917599, 82327024, 38645117, 35512853, 16405341, 26102057, 94516935, 26139110, 33797252, 45617087, 47361209, 17539625, 75128745, 88653118, 45237957, 66250369, 2208785, 57248122, 62115552, 3183975, 20568363, 8651647, 66903004, 26392416, 67084351, 63628376, 48675329, 44915235, 93515664, 6157724, 11161731, 90061527, 97011160, 34236719, 14723410, 85695762, 21993752, 35456853, 73222868, 21673260, 15902805, 30395570, 99861373, 76170907, 78589145, 30090481, 52613508, 86301513, 75777973, 42967683, 6111563, 7300047, 60102965, 48088883, 66271566, 85117092, 99729357, 874791, 73617245, 61815223, 7955293, 48892201, 85116755, 81172706, 36808486, 51464002, 50007421, 7011964, 68644627, 59329511, 18131876, 37659250, 74110882, 29401781, 72777973, 9257405, 14731700, 41092102, 87710366, 31727379, 53632373, 57240218, 17385531, 44060493, 46540998, 94076128, 97057187, 53888755, 69697787, 44627776, 5267545, 40677414, 48673079, 22435353, 93053405, 97561745, 58208470, 92215320, 75820087, 38510840, 3487592, 91141395, 79657802, 78602717, 21919959, 42237907, 749283, 68128438, 62430984, 94539824, 54663246, 1197320, 38658347, 53648154, 82178706, 8807940, 55753905, 24826575, 68000591, 63059024, 7423788, 20765474, 65275241, 55850790, 21289531, 53666583, 42307449, 51507425, 84904436, 65081429, 19101477, 37560164, 77620120, 15535065, 99690194, 69786756, 5073754, 70541760, 7066775, 99965001, 25636669, 89811711, 22113133, 77787724, 96726697, 97379790, 49641577, 68316156, 30811010, 49271185, 50188404, 24953498, 20645197, 56424103, 24168411, 17037369, 45996863, 40686254, 14349098, 29819940, 72278539, 56531125, 71083565, 15148031, 84349107, 92803766, 90310261, 92787493, 17764950, 76540481, 66137019, 68041839, 29516592, 84840549, 63372756, 98943869, 1936762, 55470718, 36468541, 57393458, 70036438, 4978543, 17857111, 20836893, 53547802, 20486294, 22879907, 38009615, 40027975, 3235882, 66116458, 31161687, 3233569, 89804152, 34698463, 71469330, 37501808, 38022834, 2607799, 72238278, 39201414, 31491938, 20002147, 6819644, 77072625, 86022504, 89699445, 17727650, 40152546, 14220886, 43501211, 93566986, 44842615, 22405120, 35996293, 18001617, 78218845, 51715482, 88251446, 28928175, 11923835, 6221471, 95581843, 7229550, 68816413, 75229462, 95290172, 54014062, 13862149, 4199704, 59405277, 34493392, 81853704, 24058273, 5640302, 13468268, 72614359, 42692881, 27187213, 2204165, 83747892, 86798033, 86242799, 67474219, 17081350, 40534591, 26664538, 83651211, 20642888, 27411561, 22200378, 64087743, 64157906, 79136082, 56461322, 11757872, 38256849, 21533347, 58749, 96420429, 32590267, 70372191, 33237508, 29466406, 55143724, 17894977 +24591705, 45794415, 37183543, 52060076, 17976208, 53666583, 22766820, 66611759, 62115552, 15064655, 8913721, 84904436, 2331773, 29635537, 42073124, 23740167, 83302115, 47298834, 96318094, 94595668, 2917920, 26102057, 26863229, 86001008, 17068582, 78785507, 48395186, 30694952, 70782102, 15948937, 62232211, 52613508, 55189057, 52293995, 49597667, 70367851, 94711842, 67030811, 61982238, 79322415, 67793644, 168541, 72278539, 48893685, 56515456, 82339363, 85242963, 35996293, 28787861, 33628349, 51315460, 68816413, 47792865, 14326617, 36580610, 92398073, 15163258, 1197320, 7919588, 22942635, 92071990, 5640302, 61728685, 82886381, 16097038, 76703609, 80555751, 19101477, 18411915, 7685448, 29029316, 18783702, 14045383, 90004325, 30811010, 54232247, 4069912, 10264691, 45996863, 89078848, 17385531, 14349098, 48774913, 10453030, 61897582, 91255408, 11365791, 92353856, 8696647, 45306070, 44842615, 23848565, 4787945, 16405341, 49598724, 45617087, 33431960, 80251430, 81855445, 87160386, 72019362, 61623915, 3487592, 65271999, 66045587, 26397786, 22200378, 72495719, 14626618, 84166196, 61712234, 20681921, 55770687, 62490109, 40027975, 48360198, 67031644, 13348726, 30090481, 68875490, 569864, 82979980, 93933709, 92541302, 83747892, 55960386, 47090124, 4508700, 68644627, 36135, 77898274, 90654836, 82532312, 92033260, 99021067, 23194618, 31491938, 64055960, 70420215, 77072625, 77187825, 33201905, 96709982, 1250437, 99515901, 45995325, 50806615, 97641116, 82897371, 43501211, 38645117, 13470059, 23432750, 4806458, 93053405, 13231279, 11923835, 7104732, 3088684, 33895336, 60430369, 91802888, 81805959, 26114953, 85023028, 81274566, 95290172, 88444207, 57393458, 63628376, 70036438, 34667286, 90061527, 38008118, 98130363, 20836893, 65715134, 89499542, 24619760, 76170907, 59197747, 5482538, 73168565, 29932657, 17016156, 77312810, 23134715, 94360702, 39171895, 11543098, 63506281, 874791, 44847298, 48892201, 1239555, 23110625, 29188588, 69786756, 112651, 66885828, 74724075, 7646095, 93562257, 73109997, 36808486, 79136082, 50007421, 56473732, 58751351, 89811711, 22113133, 52204879, 91664334, 85711894, 16424199, 49641577, 61859143, 57359924, 19272365, 16684829, 50668599, 51466049, 1375023, 43152977, 20645197, 5822202, 34497327, 62762857, 97783876, 86022504, 24168411, 11581181, 31727379, 33565483, 36396314, 53632373, 40686254, 17727650, 247198, 34432810, 73031054, 22721500, 2717150, 5111370, 61141874, 19457589, 71083565, 93566986, 93057697, 83210802, 41092172, 48673079, 14947650, 33336148, 44473167, 8373289, 29510992, 47361209, 17539625, 58208470, 75820087, 3509435, 88698958, 27185644, 43357947, 22450468, 93359396, 68102437, 73235980, 45237957, 66250369, 30998561, 508198, 96420429, 74137926, 9259676, 66903004, 75229462, 9829782, 47893286, 67451935, 37957788, 51590803, 17365188, 97011160, 81677380, 54663246, 64098930, 32699744, 65338021, 19898053, 73392814, 38658347, 81898046, 21673260, 40984766, 38009615, 99604946, 33061250, 44177011, 89046466, 1569515, 176257, 79738755, 12571310, 19486173, 24826575, 64602895, 76434144, 64157906, 98648327, 68000591, 77377183, 61928316, 47213183, 78561158, 86301513, 7423788, 92554120, 38341669, 62051033, 92867155, 1022677, 61263068, 86543538, 4204661, 72793444, 51149804, 26063929, 26507214, 36845587, 30463802, 91957544, 73124510, 37560164, 34295794, 15535065, 45428665, 6793819, 48658605, 89214616, 96906410, 54868730, 70372191, 92998591, 43322743, 77694700, 12348118, 44245960, 47708850, 6808825, 7011964, 99297164, 71333116, 16971929, 27041967, 78766358, 59910624, 7517032, 4119747, 74110882, 91938191, 1204161, 76960303, 50188404, 68204242, 72238278, 73786944, 1431742, 60955663, 14731700, 98948034, 30218878, 9603598, 62428472, 26744917, 50567636, 56955985, 37192445, 26292919, 77413012, 84127901, 83155442, 52261574, 46870723, 71657078, 94076128, 30163921, 74743862, 68694897, 9398733, 67124282, 99125126, 88904910, 91281584, 526217, 43506672, 39553046, 92530431, 15148031, 26664538, 69136837, 59109400, 83651211, 35512853, 9058407, 76540481, 70800879, 27411561, 79942022, 97940276, 26139110, 75543508, 4434662, 45667668, 97281847, 29516592, 19891772, 92215320, 69641513, 98462867, 96852321, 71965942, 38510840, 90013093, 17894977, 35092039, 90457870, 88251446, 99658235, 10961421, 54762643, 62357986, 8055981, 3880712, 53842979, 57248122, 55602660, 78602717, 20568363, 75052463, 38494874, 1936762, 14093520, 83150534, 61271144, 36933038, 26392416, 48675329, 28734791, 6157724, 10309525, 32250045, 30764367, 26734892, 17857111, 70596786, 78884452, 21070110, 20486294, 35456853, 68110330, 38061439, 30395570, 70004753, 93790285, 75135448, 55753905, 30569392, 39801351, 65275241, 29959549, 37675718, 33553959, 42967683, 43933006, 89804152, 58180653, 40197395, 66271566, 34698463, 16019925, 13468268, 55615885, 65081429, 84002370, 415901, 38365584, 77620120, 57163802, 85116755, 98371444, 42692881, 81172706, 37620363, 37501808, 63152504, 67281495, 17146629, 39373729, 59329511, 5970607, 38022834, 55143724, 97379790, 41481685, 68316156, 33935899, 49271185, 7687278, 86798033, 66428795, 63015256, 9257405, 45919976, 8733068, 12024238, 37435892, 86242799, 59501487, 11757872, 74452589, 54606384, 89699445, 84187166, 12664567, 57240218, 57803235, 6986898, 82779622, 21397057, 29819940, 30771409, 54058788, 99971982, 65851721, 86821229, 69697787, 89637706, 72358170, 44627776, 36812683, 36753250, 45049308, 77300457, 95251277, 65038678, 32274392, 45075471, 31904591, 32161669, 82327024, 36139942, 92803766, 40677414, 72725103, 45407418, 4095116, 33797252, 26493119, 90272749, 8634541, 57241521, 94090109, 84684495, 21001913, 40865610, 28550822, 75153252, 53084256, 44664587, 36312813, 95581843, 54199160, 79657802, 96735716, 42782652, 2208785, 4515343, 78549759, 59981773, 21919959, 67084351, 91990218, 13862149, 36189527, 6497830, 44915235, 4978543, 9860195, 65454636, 53802686, 22997281, 62430984, 89996536, 81853704, 16595436, 6153406, 53393358, 85695762, 21993752, 53648154, 22879907, 30891921, 44844121, 15902805, 86460488, 3773993, 54263880, 70727211, 31733363, 37224844, 9575954, 69255765, 75407347, 62803858, 33123618, 47887470, 6111563, 7300047, 82052050, 99729357, 48260151, 54427233, 80014588, 84293052, 73617245, 46851987, 62936963, 61741594, 92692283, 39847321, 71300104, 32426752, 41245325, 86118021, 4099191, 41442762, 34044787, 29466406, 43376279, 96726697, 31126490, 16054533, 81774825, 4091162, 20122224, 40781449, 28851716, 71920426, 24314885, 44479073, 29401781, 72777973, 29994197, 60393039, 24953498, 55247455, 67474219, 77284619, 73021291, 824872, 99226875, 56424103, 57658654, 36930650, 41092102, 37166608, 17037369, 67513640, 45381876, 8128637, 17386996, 17081350, 82726008, 99224392, 4064751, 5832946, 79821897, 59318837, 40152546, 16380211, 10597197, 4985896, 14220886, 6521313, 94911072, 57020481, 16387575, 18833224, 74614639, 61960400, 54517921, 44481640, 61859581, 9599614, 76825057, 90158785, 59371804, 80316608, 45790169, 18001617, 30139692, 78218845, 57961282, 19939935, 25842078, 17957593, 33304202, 84840549, 49882705, 28928175, 63372756, 6221471, 66667729, 26776922, 79922758, 23569917, 98943869, 60106217, 89419466, 95010552, 42237907, 49236559, 64848072, 59405277, 11161731, 77272628, 14723410, 69848388, 53547802, 24058273, 73222868, 82178706, 8807940, 30787683, 61373987, 99861373, 88130087, 3235882, 66116458, 20765474, 42644903, 75777973, 21289531, 77301523, 60102965, 36685023, 42307449, 66322959, 51507425, 79880247, 62740044, 72614359, 78909561, 7955293, 41380093, 72738685, 9188443, 12303248, 5073754, 71469330, 6505939, 7066775, 99965001, 33699435, 71316369, 95957797, 77787724, 32058615, 72732205, 37659250, 92692978, 27665211, 18699206, 65047700, 72373496, 78300864, 40781854, 66319530, 72357096, 55669657, 87710366, 68939068, 39986008, 15536795, 76330843, 22129328, 46540998, 65017137, 9886593, 79191827, 68824981, 5267545, 19376156, 84349107, 91240048, 90310261, 8115266, 17764950, 20642888, 22405120, 94516935, 97561745, 13173644, 69579137, 72274002, 8099994, 51715482, 26998766, 58749, 91141395, 34698428, 88653118, 58224549, 49328608, 82427263, 3183975, 8651647, 42947632, 55470718, 54014062, 10358899, 749283, 20885148, 18466635, 27325428, 68128438, 94539824, 59614746, 67227442, 3633375, 69605283, 8729146, 22108665, 62552524, 45990383, 43045786, 95395112, 55850790, 89217461, 48088883, 26275734, 17058722, 30653863, 37739481, 61815223, 9175338, 51047803, 99690194, 18663507, 2204165, 25636669, 18131876, 82651278, 69355476, 65074535, 56153345, 87720882, 39201414, 16567550, 95726235, 99524975, 56484900, 83083131, 20002147, 20140249, 62496012, 66663942, 38256849, 50702367, 44060493, 34946859, 97057187, 66832478, 53888755, 44550764, 56531125, 321665, 44846932, 83368048, 10366309, 99549373, 91727510, 2891150, 22435353, 21533347, 3233084, 83133790, 75128745, 9623492, 6038457, 91831487, 36468541, 30366150, 84406788, 51472275, 93515664, 34493392, 34236719, 6525948, 12416768, 78589145, 63059024, 10649306, 98739783, 13819358, 3233569, 30543215, 4798568, 35192533, 83948335, 32590267, 75986488, 92604458, 71522968, 51464002, 18504197, 51426311, 56461322, 7182642, 24915585, 79806380, 85571389, 18806856, 6819644, 3294781, 96193415, 83269727, 99333375, 47738185, 40534591, 32151165, 6871053, 54987042, 44983451, 90090964, 62693428, 62452034, 29065964, 51588015, 83533741, 68041839, 44348328, 68702632, 7229550, 37280276, 4199704, 15075176, 15015906, 80246713, 64087743, 83789391, 44784505, 8129978, 31161687, 42199455, 85117092, 36505482, 63967300, 70541760, 33237508, 29834512, 2607799, 27625735, 40356877, 74441448, 36780454, 45848907, 7502255, 37891451, 669105, 99917599, 60176618, 92787493, 16445503, 93270084, 28796059, 9860968, 32159704, 20867149, 87598888, 60581278, 98653983, 76671482, 33249630, 8791066, 88047921, 14363867, 24733232, 66137019, 65880522, 16099750, 59177718, 44889423, 74357852, 76315420, 1788101, 4668450, 27187213 +23432750, 84187166, 68939068, 67513640, 30139692, 21919959, 48360198, 61928316, 48260151, 75986488, 71316369, 59910624, 54987042, 82897371, 94911072, 8099994, 75153252, 81274566, 89996536, 77377183, 82532312, 63015256, 11581181, 77413012, 17386996, 4787945, 63372756, 53842979, 68816413, 95010552, 67451935, 37957788, 15948937, 84166196, 53547802, 65880522, 68875490, 92554120, 8913721, 84293052, 55615885, 92692283, 85116755, 41245325, 70541760, 68644627, 16971929, 28851716, 24314885, 91938191, 7687278, 92033260, 85571389, 8733068, 62762857, 54606384, 82726008, 66832478, 92353856, 99125126, 88904910, 91240048, 22405120, 70800879, 69579137, 51715482, 93359396, 66611759, 62357986, 26776922, 23569917, 75052463, 93515664, 10309525, 24591705, 67227442, 32699744, 176257, 55753905, 76434144, 64157906, 68000591, 63059024, 73168565, 569864, 37183543, 93933709, 94360702, 39171895, 63506281, 72614359, 415901, 23110625, 34295794, 69786756, 59177718, 43322743, 29029316, 44889423, 47708850, 71522968, 71469330, 50007421, 4099191, 58751351, 29466406, 18131876, 97379790, 61859143, 49271185, 65047700, 76960303, 33565483, 26744917, 39986008, 84127901, 46870723, 4064751, 79821897, 37891451, 97641116, 669105, 69697787, 2917920, 67124282, 72358170, 61141874, 45049308, 74614639, 71083565, 54517921, 45075471, 44481640, 15148031, 92803766, 83210802, 76825057, 4806458, 45407418, 92787493, 16405341, 33336148, 45667668, 8634541, 57241521, 80251430, 98462867, 88698958, 26863229, 54762643, 54199160, 57248122, 79922758, 81805959, 42947632, 9829782, 90061527, 54663246, 1197320, 19898053, 8807940, 1569515, 99861373, 12571310, 64602895, 22108665, 52613508, 62051033, 77312810, 58180653, 66271566, 874791, 4798568, 36845587, 19101477, 15535065, 29188588, 29635537, 70367851, 36808486, 63152504, 51464002, 56461322, 85711894, 32058615, 68316156, 30811010, 71920426, 79806380, 1204161, 50668599, 16567550, 43152977, 1431742, 66319530, 72357096, 66663942, 70420215, 9603598, 36930650, 97783876, 38256849, 56955985, 8128637, 26292919, 30771409, 7502255, 32151165, 34432810, 61897582, 91255408, 6521313, 44550764, 89637706, 62452034, 19457589, 16387575, 65038678, 23848565, 5267545, 14947650, 26139110, 59371804, 97561745, 44473167, 94090109, 58208470, 87160386, 26998766, 78785507, 3880712, 49328608, 82427263, 33628349, 9860968, 89419466, 70782102, 67084351, 30366150, 54014062, 64848072, 47893286, 17976208, 15075176, 18466635, 27325428, 72495719, 68128438, 15163258, 69848388, 26734892, 20836893, 61712234, 24058273, 70596786, 69605283, 38061439, 93790285, 15064655, 76170907, 88130087, 13348726, 47213183, 78561158, 42644903, 38341669, 75777973, 1022677, 33553959, 82979980, 53666583, 55189057, 42307449, 66322959, 84002370, 36505482, 91957544, 77620120, 92541302, 45428665, 7685448, 71300104, 33249630, 66885828, 99965001, 56473732, 47090124, 34044787, 27041967, 31126490, 14045383, 16054533, 16424199, 41481685, 81774825, 4119747, 69355476, 18699206, 86798033, 66428795, 72238278, 40356877, 45919976, 83302115, 47298834, 1375023, 99524975, 56484900, 74441448, 4668450, 14731700, 67030811, 99226875, 34497327, 86022504, 87710366, 33201905, 99333375, 17037369, 5832946, 40534591, 247198, 73031054, 168541, 30163921, 4985896, 8696647, 62693428, 9886593, 61960400, 90310261, 60176618, 8115266, 13470059, 83651211, 27411561, 26493119, 45617087, 47361209, 13231279, 19939935, 17957593, 17894977, 76315420, 16445503, 68102437, 40865610, 28550822, 53084256, 88653118, 45237957, 44664587, 36312813, 28796059, 66045587, 78549759, 9259676, 36580610, 37280276, 65454636, 11161731, 22997281, 30764367, 59614746, 14723410, 81853704, 20486294, 35456853, 53648154, 82178706, 15902805, 38009615, 3773993, 89046466, 30787683, 20867149, 5482538, 75407347, 30569392, 47887470, 37675718, 43933006, 89804152, 82052050, 40197395, 30543215, 51149804, 26063929, 79880247, 61815223, 48658605, 112651, 92998591, 12303248, 42692881, 12348118, 2204165, 37620363, 37501808, 73109997, 18504197, 23740167, 18783702, 25636669, 17146629, 99297164, 33237508, 89811711, 96726697, 88047921, 7517032, 82651278, 57359924, 4091162, 20122224, 40781449, 90654836, 27625735, 19272365, 14363867, 50188404, 99021067, 23194618, 73786944, 18806856, 55247455, 83083131, 6819644, 30218878, 3294781, 20140249, 62496012, 56424103, 74452589, 24168411, 36780454, 62428472, 50567636, 45381876, 12664567, 36396314, 57803235, 6986898, 76330843, 14349098, 29819940, 59318837, 94076128, 53888755, 86821229, 11365791, 68694897, 321665, 65017137, 44627776, 29065964, 91281584, 526217, 77300457, 18833224, 83368048, 79191827, 93566986, 24733232, 32161669, 82327024, 9599614, 93057697, 36139942, 40677414, 59109400, 99549373, 20642888, 85242963, 83533741, 94516935, 93053405, 18001617, 90272749, 13173644, 19891772, 17539625, 57961282, 96852321, 35092039, 84840549, 99658235, 3487592, 11923835, 34698428, 65271999, 7104732, 93270084, 30998561, 96735716, 55602660, 62115552, 8651647, 85023028, 75229462, 36468541, 63628376, 49236559, 44915235, 6157724, 34667286, 77272628, 94539824, 32159704, 38008118, 16595436, 3633375, 73392814, 89499542, 21993752, 21070110, 62232211, 62490109, 80246713, 40984766, 68110330, 12416768, 54263880, 70004753, 83789391, 59197747, 67031644, 30090481, 45990383, 44784505, 3235882, 92071990, 69255765, 66116458, 7423788, 39801351, 29932657, 60581278, 33123618, 21289531, 17016156, 61263068, 23134715, 42967683, 7300047, 60102965, 48088883, 4204661, 72793444, 98653983, 76671482, 11543098, 85117092, 76703609, 99729357, 54427233, 80014588, 73617245, 84904436, 61741594, 48892201, 83948335, 38365584, 41380093, 39847321, 6793819, 9175338, 89214616, 96906410, 99690194, 32426752, 63967300, 27187213, 44245960, 7646095, 6505939, 79136082, 83747892, 67281495, 86118021, 71333116, 43376279, 78766358, 77898274, 90004325, 37659250, 54232247, 27665211, 33935899, 44479073, 68204242, 29401781, 72373496, 95726235, 51466049, 94711842, 24953498, 64055960, 73021291, 5822202, 96193415, 11757872, 41092102, 83269727, 53632373, 57240218, 83155442, 17081350, 17727650, 47738185, 82779622, 96318094, 6871053, 45995325, 10597197, 97057187, 65851721, 44983451, 14220886, 2717150, 48893685, 56515456, 5111370, 56531125, 36812683, 57020481, 43506672, 68824981, 61859581, 44842615, 10366309, 84349107, 9058407, 48673079, 4095116, 79942022, 66137019, 80316608, 45790169, 97281847, 8373289, 33431960, 75820087, 84684495, 3233084, 25842078, 83133790, 27185644, 33304202, 72274002, 17068582, 90457870, 10961421, 3088684, 66250369, 66667729, 28787861, 33895336, 42782652, 508198, 2208785, 96420429, 7229550, 78602717, 98943869, 60106217, 47792865, 14326617, 83150534, 26397786, 26392416, 91990218, 4199704, 6497830, 9860195, 92398073, 81677380, 15015906, 65715134, 65338021, 6153406, 45794415, 30891921, 44844121, 86460488, 33061250, 87598888, 79738755, 40027975, 24826575, 78589145, 9575954, 98739783, 95395112, 20765474, 65275241, 92867155, 86543538, 6111563, 26275734, 17058722, 51507425, 30653863, 62936963, 78909561, 37739481, 30463802, 7955293, 1239555, 73124510, 9188443, 51047803, 22766820, 81172706, 18663507, 7066775, 51426311, 55960386, 7011964, 8791066, 39373729, 38022834, 95957797, 29834512, 91664334, 74357852, 7182642, 49641577, 72732205, 74110882, 65074535, 4069912, 16684829, 29994197, 39201414, 12024238, 31491938, 59501487, 77072625, 79322415, 31727379, 45848907, 89078848, 50702367, 40686254, 96709982, 99515901, 99224392, 72278539, 90090964, 9398733, 43501211, 38645117, 72725103, 35512853, 51588015, 35996293, 97940276, 22435353, 49598724, 68041839, 21533347, 44348328, 69641513, 86001008, 71965942, 43357947, 28928175, 61623915, 75128745, 91141395, 58224549, 95581843, 79657802, 4515343, 60430369, 6038457, 74137926, 30694952, 38494874, 1936762, 14093520, 1788101, 13862149, 84406788, 10358899, 22200378, 70036438, 36189527, 20885148, 32250045, 17365188, 64098930, 6525948, 20681921, 78884452, 64087743, 70727211, 31733363, 19486173, 8129978, 5640302, 62803858, 61728685, 55850790, 36685023, 52293995, 62740044, 44847298, 80555751, 32590267, 37560164, 98371444, 5073754, 77694700, 93562257, 4508700, 77787724, 55143724, 52060076, 24915585, 56153345, 9257405, 10264691, 37435892, 40781854, 67474219, 77284619, 57658654, 37166608, 37192445, 15536795, 17385531, 1250437, 52261574, 22129328, 48774913, 34946859, 54058788, 10453030, 16380211, 99971982, 50806615, 22721500, 74743862, 44846932, 36753250, 95251277, 31904591, 26664538, 19376156, 69136837, 17764950, 76540481, 26102057, 91727510, 75543508, 4434662, 33797252, 78218845, 81855445, 92215320, 3509435, 38510840, 21001913, 72019362, 8055981, 51315460, 66903004, 91831487, 59981773, 95290172, 88444207, 42237907, 749283, 48675329, 4978543, 53802686, 51590803, 97011160, 14626618, 34236719, 98130363, 7919588, 55770687, 81898046, 22942635, 21673260, 24619760, 37224844, 8729146, 10649306, 13819358, 31161687, 29959549, 3233569, 42199455, 16019925, 13468268, 65081429, 2331773, 26507214, 49597667, 57163802, 16099750, 92604458, 70372191, 42073124, 74724075, 6808825, 33699435, 41442762, 22113133, 52204879, 87720882, 72777973, 78300864, 86242799, 55669657, 44060493, 71657078, 46540998, 40152546, 45306070, 39553046, 92530431, 6221471, 48395186, 73235980, 9623492, 68702632, 26114953, 20568363, 55470718, 36933038, 51472275, 28734791, 34493392, 17857111, 53393358, 22879907, 99604946, 30395570, 61373987, 75135448, 98648327, 62552524, 86301513, 77301523, 35192533, 18411915, 54868730, 36135, 60393039, 20645197, 824872, 61982238, 89699445, 45996863, 21397057, 67793644, 32274392, 99917599, 82339363, 90158785, 41092172, 2891150, 90013093, 22450468, 49882705, 88251446, 61271144, 59405277, 62430984, 73222868, 44177011, 43045786, 82886381, 89217461, 16097038, 72738685, 59329511, 60955663, 77187825, 94595668, 29516592, 58749, 91802888, 85695762, 38658347, 34698463, 2607799, 92692978, 20002147, 29510992, 3183975, 46851987, 5970607, 98948034, 57393458 +48658605, 47090124, 68644627, 91938191, 96318094, 14093520, 67031644, 42967683, 55189057, 71469330, 7011964, 95957797, 4119747, 68204242, 64055960, 34497327, 11581181, 7502255, 6521313, 48893685, 71083565, 44842615, 85242963, 44664587, 63628376, 78561158, 43933006, 92541302, 29635537, 85116755, 71300104, 66885828, 45919976, 51466049, 20140249, 55669657, 77413012, 96709982, 99515901, 91255408, 32274392, 23432750, 79942022, 80251430, 47361209, 28550822, 9623492, 53842979, 26776922, 42947632, 49236559, 27325428, 20836893, 78884452, 80246713, 48360198, 13819358, 65275241, 86543538, 26063929, 2331773, 9188443, 12348118, 74724075, 27041967, 41481685, 69355476, 86242799, 67030811, 62496012, 76330843, 14349098, 40534591, 79821897, 61859581, 33797252, 69579137, 98462867, 17068582, 61623915, 54199160, 81805959, 21919959, 95010552, 64848072, 9829782, 69605283, 38658347, 73222868, 21673260, 8807940, 87598888, 37224844, 15064655, 68000591, 92554120, 60581278, 65081429, 61815223, 43322743, 18663507, 70541760, 56473732, 33699435, 99297164, 58751351, 16054533, 61859143, 52060076, 4091162, 18699206, 65047700, 14363867, 56153345, 72373496, 16567550, 1375023, 99524975, 61982238, 56424103, 70420215, 54606384, 33201905, 6871053, 94595668, 97641116, 14220886, 65017137, 61141874, 44627776, 36753250, 18833224, 31904591, 84349107, 92803766, 27411561, 35996293, 14947650, 68041839, 45617087, 13173644, 58208470, 26863229, 17957593, 72274002, 93359396, 88251446, 40865610, 53084256, 58749, 11923835, 34698428, 45237957, 3880712, 82427263, 30694952, 75052463, 89419466, 36933038, 30366150, 51472275, 10309525, 18466635, 34493392, 89996536, 67227442, 24058273, 6153406, 21993752, 82178706, 22879907, 30891921, 70004753, 8729146, 77377183, 22108665, 61928316, 44784505, 63059024, 98739783, 569864, 33553959, 94360702, 26275734, 36685023, 26507214, 44847298, 80555751, 415901, 34295794, 45428665, 54868730, 81172706, 37620363, 93562257, 99965001, 86118021, 38022834, 77787724, 18131876, 68316156, 57359924, 82532312, 33935899, 72238278, 39201414, 43152977, 30218878, 73021291, 66663942, 41092102, 68939068, 67513640, 44060493, 59318837, 247198, 10597197, 97057187, 669105, 44550764, 11365791, 92353856, 8696647, 67124282, 321665, 74614639, 61960400, 44481640, 15148031, 24733232, 82327024, 93057697, 69136837, 83210802, 76825057, 13470059, 4806458, 45407418, 92787493, 41092172, 91727510, 26139110, 59371804, 45667668, 90272749, 44473167, 57241521, 94090109, 78218845, 19891772, 81855445, 86001008, 25842078, 71965942, 38510840, 21001913, 84840549, 3487592, 10961421, 93270084, 42782652, 79922758, 91802888, 26114953, 23569917, 33628349, 51315460, 91831487, 47792865, 95290172, 36468541, 70782102, 26397786, 67084351, 57393458, 10358899, 47893286, 67451935, 37957788, 51590803, 68128438, 15163258, 30764367, 38008118, 14723410, 3633375, 53547802, 65338021, 55770687, 21070110, 62232211, 40984766, 12416768, 86460488, 30395570, 83789391, 19486173, 30090481, 52613508, 5640302, 95395112, 62803858, 75777973, 21289531, 23134715, 3233569, 99729357, 16019925, 73617245, 78909561, 83948335, 23110625, 72738685, 6793819, 57163802, 32426752, 77694700, 70367851, 2204165, 7646095, 73109997, 23740167, 50007421, 33237508, 91664334, 77898274, 30811010, 86798033, 44479073, 50188404, 10264691, 83302115, 24953498, 96193415, 62762857, 24168411, 87710366, 84187166, 39986008, 8128637, 36396314, 34946859, 67793644, 90090964, 74743862, 82897371, 68694897, 62693428, 56531125, 44846932, 43501211, 93566986, 82339363, 68824981, 26664538, 4787945, 35512853, 20642888, 16405341, 83533741, 66137019, 75543508, 80316608, 8634541, 33431960, 29510992, 21533347, 84684495, 3233084, 83133790, 90013093, 78785507, 7104732, 8055981, 95581843, 28787861, 4515343, 57248122, 3183975, 74137926, 78602717, 9259676, 38494874, 98943869, 1936762, 1788101, 11161731, 72495719, 1197320, 61712234, 15015906, 20681921, 65715134, 19898053, 73392814, 35456853, 81898046, 44844121, 62490109, 24619760, 70727211, 5482538, 88130087, 76434144, 13348726, 30569392, 86301513, 7423788, 10649306, 38341669, 33123618, 47887470, 7300047, 60102965, 4204661, 53666583, 11543098, 34698463, 63506281, 13468268, 54427233, 874791, 84904436, 84002370, 46851987, 62936963, 38365584, 37560164, 92692283, 98371444, 92998591, 33249630, 47708850, 51464002, 18504197, 7066775, 55960386, 41442762, 71333116, 29466406, 55143724, 85711894, 14045383, 97379790, 90004325, 92692978, 79806380, 65074535, 23194618, 1431742, 20002147, 5822202, 57658654, 97783876, 83269727, 31727379, 50567636, 45996863, 57240218, 17385531, 17727650, 48774913, 71657078, 61897582, 54987042, 22721500, 72358170, 29065964, 43506672, 54517921, 45075471, 79191827, 5267545, 90310261, 59109400, 60176618, 17764950, 48673079, 76540481, 94516935, 45790169, 93053405, 33336148, 30139692, 8373289, 75820087, 33304202, 17894977, 90457870, 51715482, 87160386, 99658235, 63372756, 62357986, 73235980, 49328608, 96420429, 6038457, 66903004, 9860968, 68816413, 42237907, 91990218, 84406788, 6497830, 28734791, 9860195, 59405277, 32250045, 17365188, 22997281, 14626618, 59614746, 34236719, 69848388, 98130363, 32699744, 70596786, 85695762, 20486294, 15902805, 38009615, 44177011, 30787683, 99861373, 79738755, 12571310, 75135448, 78589145, 45990383, 92071990, 66116458, 39801351, 82886381, 82979980, 37183543, 89804152, 72793444, 85117092, 8913721, 66322959, 84293052, 36845587, 19101477, 91957544, 32590267, 29188588, 39847321, 51047803, 92604458, 99690194, 63967300, 5073754, 44245960, 71522968, 37501808, 36808486, 63152504, 83747892, 25636669, 39373729, 89811711, 71316369, 59329511, 29834512, 43376279, 96726697, 88047921, 16424199, 49641577, 7517032, 82651278, 20122224, 40781449, 37659250, 71920426, 27665211, 7687278, 92033260, 16684829, 87720882, 50668599, 9257405, 85571389, 29994197, 95726235, 8733068, 94711842, 60393039, 31491938, 4668450, 14731700, 66319530, 3294781, 9603598, 86022504, 62428472, 84127901, 6986898, 83155442, 4064751, 30771409, 37891451, 34432810, 65851721, 73031054, 53888755, 86821229, 4985896, 2917920, 9398733, 45306070, 19457589, 91281584, 45049308, 39553046, 10366309, 19376156, 38645117, 8115266, 90158785, 72725103, 51588015, 99549373, 22405120, 4095116, 70800879, 26102057, 2891150, 4434662, 18001617, 29516592, 69641513, 88698958, 96852321, 27185644, 35092039, 76315420, 72019362, 28928175, 65271999, 58224549, 3088684, 36312813, 96735716, 508198, 7229550, 8651647, 81274566, 14326617, 13862149, 36189527, 17976208, 94539824, 84166196, 54663246, 6525948, 24591705, 16595436, 89499542, 99604946, 54263880, 20867149, 31733363, 76170907, 59197747, 62552524, 9575954, 20765474, 31161687, 55850790, 29932657, 29959549, 1022677, 61263068, 37675718, 77312810, 93933709, 6111563, 48088883, 40197395, 51149804, 48260151, 80014588, 79880247, 72614359, 36505482, 61741594, 73124510, 41380093, 77620120, 15535065, 96906410, 70372191, 12303248, 44889423, 41245325, 18783702, 4099191, 17146629, 4508700, 5970607, 52204879, 31126490, 7182642, 72732205, 24314885, 49271185, 1204161, 19272365, 66428795, 63015256, 99021067, 74441448, 72357096, 824872, 59501487, 99226875, 99333375, 33565483, 26292919, 17386996, 40686254, 22129328, 99224392, 47738185, 29819940, 99971982, 50806615, 2717150, 89637706, 88904910, 57020481, 65038678, 92530431, 99917599, 23848565, 91240048, 40677414, 9058407, 97561745, 22450468, 75153252, 75128745, 91141395, 66250369, 66667729, 28796059, 79657802, 60106217, 55470718, 75229462, 83150534, 88444207, 26392416, 48675329, 70036438, 4199704, 92398073, 15948937, 90061527, 77272628, 97011160, 81677380, 62430984, 32159704, 64098930, 17857111, 7919588, 64087743, 38061439, 33061250, 176257, 93790285, 98648327, 47213183, 8129978, 68875490, 42644903, 17016156, 39171895, 98653983, 58180653, 76671482, 66271566, 52293995, 76703609, 55615885, 30653863, 35192533, 48892201, 7685448, 89214616, 69786756, 42073124, 112651, 29029316, 6505939, 79136082, 51426311, 59910624, 28851716, 27625735, 54232247, 4069912, 18806856, 47298834, 40781854, 79322415, 38256849, 37166608, 36780454, 45381876, 12664567, 21397057, 5832946, 46540998, 10453030, 94076128, 16380211, 168541, 30163921, 66832478, 69697787, 56515456, 94911072, 99125126, 526217, 95251277, 83368048, 32161669, 9599614, 36139942, 22435353, 97281847, 3509435, 19939935, 8099994, 16445503, 49882705, 66611759, 54762643, 88653118, 33895336, 66045587, 2208785, 55602660, 59981773, 36580610, 54014062, 37280276, 22200378, 15075176, 6157724, 20885148, 34667286, 26734892, 53393358, 22942635, 68110330, 3773993, 55753905, 24826575, 65880522, 64157906, 69255765, 75407347, 92867155, 73168565, 61728685, 89217461, 16097038, 17058722, 30543215, 42307449, 51507425, 49597667, 75986488, 59177718, 42692881, 27187213, 56461322, 8791066, 22113133, 16971929, 24915585, 29401781, 72777973, 73786944, 40356877, 56484900, 55247455, 20645197, 83083131, 6819644, 98948034, 74452589, 17037369, 26744917, 37192445, 15536795, 89078848, 52261574, 82726008, 32151165, 40152546, 5111370, 9886593, 16387575, 77300457, 97940276, 17539625, 92215320, 57961282, 26998766, 68102437, 48395186, 60430369, 62115552, 78549759, 20568363, 93515664, 4978543, 65454636, 89046466, 61373987, 3235882, 82052050, 62740044, 4798568, 37739481, 7955293, 9175338, 18411915, 67281495, 74357852, 78766358, 32058615, 81774825, 90654836, 60955663, 77284619, 11757872, 36930650, 89699445, 56955985, 45848907, 57803235, 1250437, 46870723, 44983451, 62452034, 49598724, 13231279, 6221471, 68702632, 85023028, 61271144, 749283, 81853704, 45794415, 1569515, 40027975, 64602895, 43045786, 62051033, 42199455, 30463802, 22766820, 6808825, 34044787, 2607799, 74110882, 12024238, 78300864, 77072625, 77187825, 50702367, 45995325, 83651211, 44348328, 43357947, 44915235, 53802686, 53648154, 1239555, 16099750, 36135, 37435892, 17081350, 82779622, 54058788, 72278539, 36812683, 26493119, 30998561, 77301523, 76960303, 67474219, 53632373 +36505482, 75052463, 42967683, 72738685, 70596786, 37224844, 39171895, 74357852, 86821229, 53084256, 15015906, 76434144, 44784505, 75407347, 77301523, 16019925, 23110625, 23740167, 95726235, 12024238, 62762857, 37192445, 67793644, 5111370, 9886593, 36139942, 4434662, 26493119, 76315420, 9259676, 55470718, 9860195, 68128438, 55770687, 43045786, 92554120, 38341669, 1022677, 52293995, 48892201, 9175338, 51426311, 4091162, 69355476, 1204161, 65047700, 20002147, 77187825, 62428472, 15536795, 59318837, 97057187, 95251277, 83368048, 16405341, 27185644, 78785507, 28550822, 79922758, 1788101, 36189527, 17857111, 3633375, 21070110, 20486294, 80246713, 54263880, 1569515, 13348726, 29959549, 61263068, 37183543, 4204661, 26275734, 17058722, 76703609, 54427233, 84002370, 4798568, 57163802, 4099191, 92692978, 7687278, 66428795, 24953498, 66319530, 96709982, 1250437, 44060493, 6871053, 168541, 72278539, 11365791, 88904910, 61960400, 82339363, 15148031, 61859581, 13470059, 26102057, 49598724, 30139692, 71965942, 38510840, 90013093, 75153252, 10961421, 34698428, 66667729, 66045587, 82427263, 81805959, 42947632, 77272628, 94539824, 59614746, 98130363, 7919588, 20836893, 73392814, 81898046, 61373987, 93790285, 76170907, 67031644, 78561158, 92071990, 5640302, 98653983, 42307449, 80014588, 44847298, 35192533, 37560164, 15535065, 29188588, 54868730, 70372191, 66885828, 7646095, 33699435, 18131876, 91664334, 31126490, 81774825, 57359924, 76960303, 29994197, 98948034, 83269727, 50567636, 82779622, 32151165, 34432810, 94911072, 44627776, 32274392, 31904591, 68824981, 32161669, 44842615, 94516935, 14947650, 97940276, 97281847, 29516592, 29510992, 17539625, 21533347, 88698958, 26863229, 25842078, 93359396, 87160386, 49882705, 99658235, 40865610, 61623915, 7104732, 4515343, 60430369, 91802888, 26114953, 60106217, 14093520, 75229462, 83150534, 70782102, 15075176, 17365188, 14723410, 69848388, 62490109, 55753905, 48360198, 5482538, 78589145, 77377183, 61928316, 45990383, 9575954, 98739783, 42644903, 77312810, 93933709, 51149804, 874791, 79880247, 73617245, 30653863, 46851987, 26507214, 51047803, 112651, 92998591, 12303248, 27187213, 29029316, 71469330, 37620363, 70541760, 8791066, 71316369, 29466406, 43376279, 68316156, 40781449, 27625735, 4119747, 18699206, 33935899, 65074535, 87720882, 85571389, 45919976, 18806856, 83302115, 99524975, 40781854, 72357096, 34497327, 86022504, 36780454, 39986008, 45848907, 57803235, 83155442, 4064751, 21397057, 46540998, 40152546, 16380211, 94595668, 30163921, 91255408, 2917920, 72358170, 16387575, 43506672, 44481640, 9599614, 10366309, 91240048, 40677414, 4787945, 99549373, 4806458, 41092172, 22405120, 48673079, 79942022, 26139110, 80316608, 68041839, 78218845, 13231279, 98462867, 17957593, 72274002, 48395186, 58224549, 8055981, 44664587, 93270084, 66250369, 36312813, 28787861, 3880712, 96735716, 51315460, 38494874, 8651647, 66903004, 59981773, 47792865, 26397786, 30366150, 49236559, 37280276, 749283, 67451935, 37957788, 10309525, 32250045, 14626618, 32159704, 30764367, 26734892, 81853704, 16595436, 65715134, 32699744, 38658347, 22879907, 30891921, 12416768, 20867149, 64602895, 88130087, 30569392, 39801351, 92867155, 62803858, 33123618, 37675718, 23134715, 7300047, 3233569, 72793444, 42199455, 55189057, 76671482, 8913721, 34698463, 48260151, 2331773, 62740044, 72614359, 34295794, 75986488, 45428665, 29635537, 48658605, 89214616, 33249630, 5073754, 81172706, 73109997, 86118021, 50007421, 56473732, 47090124, 25636669, 59329511, 2607799, 85711894, 97379790, 72732205, 82532312, 91938191, 92033260, 63015256, 56153345, 43152977, 14731700, 59501487, 57658654, 79322415, 97783876, 89699445, 99333375, 17037369, 68939068, 67513640, 77413012, 36396314, 6986898, 50702367, 52261574, 22129328, 99224392, 34946859, 37891451, 97641116, 66832478, 54987042, 44983451, 14220886, 82897371, 67124282, 89637706, 44846932, 36812683, 45049308, 526217, 65038678, 54517921, 92530431, 24733232, 26664538, 23848565, 84349107, 23432750, 70800879, 2891150, 93053405, 8634541, 57241521, 33431960, 80251430, 81855445, 92215320, 86001008, 35092039, 26998766, 68102437, 75128745, 58749, 26776922, 30998561, 74137926, 95010552, 9829782, 44915235, 72495719, 89996536, 54663246, 24591705, 61712234, 20681921, 53547802, 24058273, 65338021, 45794415, 53393358, 85695762, 21993752, 21673260, 8807940, 40984766, 99604946, 89046466, 70004753, 176257, 70727211, 79738755, 52613508, 8129978, 65275241, 31161687, 62051033, 29932657, 60581278, 89217461, 86543538, 43933006, 26063929, 65081429, 62936963, 37739481, 61815223, 19101477, 1239555, 38365584, 73124510, 41380093, 39847321, 16099750, 96906410, 43322743, 44245960, 63152504, 79136082, 18504197, 7066775, 55960386, 18783702, 7011964, 17146629, 68644627, 77787724, 29834512, 16971929, 96726697, 78766358, 7182642, 16424199, 27665211, 49271185, 86798033, 14363867, 16684829, 50668599, 68204242, 94711842, 60393039, 47298834, 37435892, 55247455, 86242799, 4668450, 30218878, 56424103, 96193415, 77072625, 11757872, 55669657, 26292919, 84127901, 76330843, 46870723, 48774913, 29819940, 79821897, 10597197, 99971982, 50806615, 22721500, 6521313, 48893685, 56515456, 62693428, 74614639, 79191827, 5267545, 19376156, 59109400, 60176618, 51588015, 92787493, 85242963, 66137019, 91727510, 33336148, 45667668, 13173644, 19891772, 21001913, 16445503, 22450468, 84840549, 62357986, 9623492, 28796059, 54199160, 33895336, 508198, 55602660, 6038457, 78602717, 30694952, 98943869, 68816413, 89419466, 36933038, 26392416, 84406788, 22200378, 4199704, 4978543, 17976208, 59405277, 65454636, 34667286, 27325428, 90061527, 34236719, 1197320, 19898053, 69605283, 15902805, 44177011, 87598888, 99861373, 12571310, 75135448, 59197747, 62552524, 47213183, 69255765, 68875490, 73168565, 55850790, 47887470, 569864, 6111563, 82052050, 36685023, 85117092, 99729357, 51507425, 84293052, 415901, 32590267, 92692283, 77620120, 6793819, 98371444, 71300104, 99690194, 69786756, 42073124, 63967300, 77694700, 44889423, 47708850, 2204165, 6808825, 58751351, 39373729, 33237508, 22113133, 52204879, 16054533, 20122224, 4069912, 23194618, 9257405, 16567550, 1375023, 74441448, 64055960, 1431742, 78300864, 67474219, 3294781, 61982238, 9603598, 37166608, 11581181, 45996863, 8128637, 17386996, 99515901, 71657078, 45995325, 247198, 73031054, 53888755, 2717150, 74743862, 68694897, 99125126, 91281584, 36753250, 43501211, 71083565, 83210802, 8115266, 35512853, 83533741, 27411561, 18001617, 90272749, 44473167, 8373289, 44348328, 69641513, 84684495, 19939935, 83133790, 33304202, 17894977, 43357947, 8099994, 90457870, 3487592, 63372756, 65271999, 6221471, 66611759, 95581843, 49328608, 62115552, 3183975, 78549759, 23569917, 33628349, 9860968, 91831487, 81274566, 14326617, 61271144, 88444207, 63628376, 64848072, 47893286, 51472275, 48675329, 28734791, 20885148, 53802686, 15948937, 51590803, 22997281, 62430984, 64098930, 6525948, 78884452, 53648154, 62232211, 86460488, 64087743, 15064655, 19486173, 24826575, 65880522, 3235882, 86301513, 63059024, 10649306, 75777973, 21289531, 33553959, 16097038, 48088883, 89804152, 40197395, 30543215, 13468268, 55615885, 36845587, 80555751, 61741594, 18411915, 32426752, 12348118, 93562257, 83747892, 41245325, 56461322, 99297164, 41442762, 34044787, 89811711, 71333116, 88047921, 55143724, 36135, 32058615, 7517032, 61859143, 52060076, 54232247, 24314885, 19272365, 44479073, 72238278, 31491938, 6819644, 67030811, 73021291, 62496012, 5822202, 66663942, 70420215, 74452589, 41092102, 33565483, 26744917, 12664567, 10453030, 96318094, 65851721, 61897582, 90090964, 44550764, 8696647, 9398733, 56531125, 321665, 29065964, 19457589, 77300457, 18833224, 45075471, 99917599, 82327024, 90310261, 38645117, 83651211, 72725103, 9058407, 35996293, 75543508, 22435353, 97561745, 94090109, 69579137, 47361209, 3509435, 3233084, 51715482, 72019362, 88251446, 91141395, 88653118, 3088684, 79657802, 42782652, 1936762, 21919959, 42237907, 36580610, 57393458, 54014062, 6497830, 92398073, 97011160, 38008118, 6153406, 89499542, 73222868, 68110330, 38009615, 30787683, 83789391, 31733363, 40027975, 68000591, 66116458, 95395112, 20765474, 61728685, 82886381, 60102965, 53666583, 66271566, 11543098, 66322959, 84904436, 30463802, 9188443, 7685448, 92604458, 42692881, 74724075, 37501808, 6505939, 51464002, 67281495, 99965001, 5970607, 38022834, 95957797, 27041967, 59910624, 41481685, 82651278, 24915585, 37659250, 99021067, 72777973, 39201414, 40356877, 10264691, 51466049, 8733068, 83083131, 77284619, 824872, 99226875, 38256849, 33201905, 84187166, 45381876, 57240218, 17385531, 82726008, 17727650, 5832946, 40534591, 94076128, 669105, 4985896, 61141874, 39553046, 93566986, 92803766, 90158785, 45407418, 17764950, 45617087, 58208470, 57961282, 11923835, 54762643, 68702632, 53842979, 20568363, 85023028, 13862149, 70036438, 11161731, 18466635, 81677380, 35456853, 22942635, 38061439, 33061250, 3773993, 30395570, 8729146, 64157906, 7423788, 94360702, 58180653, 63506281, 7955293, 83948335, 91957544, 49597667, 92541302, 85116755, 22766820, 71522968, 14045383, 90004325, 71920426, 79806380, 50188404, 29401781, 73786944, 56484900, 60955663, 36930650, 24168411, 87710366, 31727379, 17081350, 47738185, 30771409, 54058788, 69697787, 45306070, 62452034, 65017137, 57020481, 76825057, 20642888, 4095116, 76540481, 28928175, 45237957, 2208785, 96420429, 7229550, 67084351, 91990218, 93515664, 34493392, 84166196, 82178706, 24619760, 98648327, 22108665, 17016156, 82979980, 59177718, 70367851, 18663507, 72373496, 20645197, 54606384, 89078848, 53632373, 14349098, 7502255, 92353856, 93057697, 69136837, 59371804, 33797252, 75820087, 96852321, 73235980, 95290172, 36468541, 6157724, 44844121, 78909561, 4508700, 30811010, 90654836, 28851716, 56955985, 40686254, 45790169, 10358899, 15163258, 67227442, 30090481, 36808486, 49641577, 77898274, 74110882, 57248122, 13819358, 20140249, 17068582 +6038457, 91727510, 77272628, 91664334, 13819358, 37183543, 41481685, 91938191, 83651211, 28550822, 52613508, 47213183, 61263068, 7685448, 7646095, 8115266, 66611759, 9623492, 4515343, 74137926, 91990218, 49236559, 47893286, 44177011, 70004753, 64602895, 68000591, 42644903, 92867155, 1022677, 92692283, 29188588, 29029316, 37501808, 50668599, 1375023, 4668450, 57658654, 39986008, 96709982, 92353856, 36139942, 22435353, 8373289, 44348328, 33304202, 34698428, 96420429, 57248122, 8651647, 36933038, 67084351, 9860195, 6157724, 59614746, 6525948, 65715134, 80246713, 99604946, 176257, 93790285, 76434144, 8129978, 92554120, 55850790, 29932657, 72793444, 35192533, 16099750, 12303248, 4091162, 54232247, 10264691, 83083131, 66319530, 824872, 41092102, 33565483, 50702367, 37891451, 6521313, 90090964, 94911072, 77300457, 32161669, 93057697, 69136837, 93053405, 30139692, 71965942, 38510840, 8055981, 53842979, 60430369, 91802888, 51315460, 9259676, 59981773, 42237907, 36580610, 64848072, 22997281, 62430984, 84166196, 1197320, 20836893, 61712234, 32699744, 24058273, 53393358, 73222868, 44844121, 12416768, 86460488, 77377183, 43045786, 68875490, 65275241, 73168565, 77301523, 569864, 33553959, 48088883, 40197395, 54427233, 79880247, 80555751, 48892201, 1239555, 83948335, 77620120, 15535065, 70372191, 43322743, 66885828, 74724075, 18663507, 37620363, 7066775, 4099191, 17146629, 5970607, 77787724, 14363867, 50188404, 9257405, 83302115, 20645197, 5822202, 34497327, 77072625, 77413012, 1250437, 6871053, 97057187, 73031054, 30163921, 2717150, 44550764, 11365791, 9398733, 36753250, 65038678, 32274392, 31904591, 82339363, 38645117, 59371804, 80316608, 49598724, 45617087, 84684495, 19939935, 83133790, 21001913, 22450468, 78785507, 88251446, 40865610, 6221471, 44664587, 66250369, 79657802, 96735716, 79922758, 7229550, 62115552, 78549759, 60106217, 55470718, 83150534, 61271144, 13862149, 97011160, 14626618, 68128438, 45794415, 38658347, 64087743, 3773993, 92071990, 31161687, 33123618, 47887470, 17016156, 93933709, 23134715, 7300047, 30543215, 76671482, 52293995, 34698463, 16019925, 62740044, 6793819, 42692881, 77694700, 79136082, 18783702, 86118021, 7011964, 33237508, 59329511, 71333116, 52204879, 18131876, 96726697, 31126490, 88047921, 90654836, 76960303, 60393039, 43152977, 56484900, 64055960, 73021291, 3294781, 66663942, 61982238, 56424103, 77187825, 54606384, 62428472, 45848907, 45381876, 12664567, 82726008, 22129328, 4064751, 82779622, 34946859, 16380211, 72278539, 8696647, 68694897, 61141874, 71083565, 99917599, 44481640, 26664538, 60176618, 4787945, 35512853, 51588015, 4806458, 41092172, 70800879, 97940276, 4434662, 90272749, 29510992, 27185644, 90013093, 35092039, 72274002, 17068582, 51715482, 49882705, 75128745, 58749, 91141395, 66667729, 36312813, 30998561, 49328608, 2208785, 55602660, 23569917, 38494874, 9860968, 95290172, 95010552, 36468541, 1788101, 57393458, 84406788, 10358899, 51472275, 4199704, 67451935, 92398073, 20885148, 53802686, 89996536, 14723410, 69848388, 24591705, 15015906, 20681921, 70596786, 19898053, 76170907, 75135448, 48360198, 62552524, 9575954, 30569392, 62051033, 61728685, 42967683, 6111563, 89804152, 98653983, 42199455, 36685023, 8913721, 48260151, 30653863, 2331773, 46851987, 26507214, 78909561, 37739481, 38365584, 32590267, 39847321, 9188443, 71300104, 96906410, 112651, 63967300, 33249630, 22766820, 27187213, 70367851, 36808486, 63152504, 51464002, 99965001, 47090124, 58751351, 39373729, 89811711, 2607799, 43376279, 55143724, 85711894, 20122224, 40781449, 69355476, 27665211, 19272365, 29994197, 51466049, 94711842, 14731700, 77284619, 62496012, 96193415, 86022504, 24168411, 87710366, 83269727, 89699445, 17037369, 84187166, 50567636, 8128637, 17386996, 40686254, 17727650, 47738185, 29819940, 5832946, 30771409, 54058788, 168541, 66832478, 54987042, 22721500, 2917920, 19457589, 57020481, 45049308, 43501211, 45075471, 84349107, 91240048, 76825057, 13470059, 99549373, 20642888, 22405120, 29516592, 80251430, 78218845, 81855445, 92215320, 98462867, 17894977, 43357947, 87160386, 53084256, 10961421, 11923835, 63372756, 3088684, 28796059, 33895336, 26114953, 98943869, 75229462, 21919959, 22200378, 749283, 48675329, 44915235, 93515664, 4978543, 65454636, 11161731, 18466635, 34667286, 32250045, 34493392, 15163258, 54663246, 64098930, 7919588, 16595436, 3633375, 73392814, 89499542, 20486294, 30891921, 68110330, 54263880, 15064655, 19486173, 65880522, 67031644, 61928316, 75407347, 66116458, 98739783, 95395112, 20765474, 60581278, 82886381, 37675718, 3233569, 26275734, 58180653, 55189057, 11543098, 85117092, 76703609, 61741594, 415901, 91957544, 37560164, 29635537, 98371444, 89214616, 54868730, 32426752, 42073124, 47708850, 81172706, 71522968, 6505939, 67281495, 23740167, 56473732, 25636669, 22113133, 29834512, 14045383, 16054533, 16424199, 32058615, 77898274, 66428795, 92033260, 16684829, 72777973, 72238278, 72373496, 39201414, 73786944, 40356877, 16567550, 74441448, 1431742, 20002147, 6819644, 30218878, 72357096, 9603598, 36930650, 62762857, 99333375, 31727379, 45996863, 57803235, 46870723, 50806615, 86821229, 44983451, 91255408, 4985896, 14220886, 74743862, 5111370, 62693428, 62452034, 72358170, 9886593, 526217, 43506672, 39553046, 82327024, 61859581, 83210802, 90310261, 72725103, 48673079, 94516935, 44473167, 8634541, 57241521, 13231279, 57961282, 75820087, 88698958, 26863229, 8099994, 28928175, 65271999, 88653118, 68702632, 28787861, 82427263, 30694952, 75052463, 91831487, 85023028, 89419466, 42947632, 26392416, 30366150, 54014062, 70036438, 59405277, 10309525, 15948937, 51590803, 94539824, 32159704, 98130363, 26734892, 81853704, 67227442, 55770687, 8807940, 40984766, 30787683, 70727211, 31733363, 40027975, 5482538, 88130087, 64157906, 30090481, 98648327, 69255765, 29959549, 86543538, 16097038, 60102965, 17058722, 82052050, 66271566, 84293052, 4798568, 44847298, 62936963, 61815223, 73124510, 34295794, 99690194, 59177718, 44245960, 2204165, 73109997, 41245325, 50007421, 68644627, 38022834, 95957797, 29466406, 27041967, 74357852, 68316156, 82651278, 4119747, 79806380, 24314885, 7687278, 65047700, 65074535, 56153345, 99021067, 68204242, 23194618, 45919976, 95726235, 99524975, 78300864, 60955663, 40781854, 67474219, 59501487, 99226875, 70420215, 36780454, 37192445, 36396314, 84127901, 6986898, 17081350, 99515901, 48774913, 21397057, 40534591, 32151165, 79821897, 40152546, 99971982, 56515456, 56531125, 44846932, 88904910, 54517921, 93566986, 68824981, 9599614, 23432750, 16405341, 26102057, 79942022, 35996293, 26139110, 75543508, 33797252, 18001617, 33431960, 94090109, 47361209, 17539625, 96852321, 86001008, 76315420, 68102437, 75153252, 3487592, 54762643, 58224549, 73235980, 93270084, 54199160, 3183975, 33628349, 68816413, 14326617, 63628376, 36189527, 15075176, 30764367, 34236719, 53547802, 35456853, 82178706, 38061439, 30395570, 24619760, 61373987, 20867149, 79738755, 55753905, 13348726, 22108665, 78561158, 63059024, 38341669, 75777973, 21289531, 89217461, 53666583, 99729357, 63506281, 66322959, 55615885, 84002370, 36845587, 30463802, 19101477, 49597667, 9175338, 48658605, 92604458, 44889423, 71469330, 6808825, 70541760, 8791066, 41442762, 16971929, 78766358, 49641577, 81774825, 7517032, 61859143, 52060076, 57359924, 30811010, 27625735, 72732205, 37659250, 92692978, 18699206, 85571389, 37435892, 86242799, 67030811, 11757872, 97783876, 74452589, 37166608, 33201905, 11581181, 68939068, 56955985, 53632373, 52261574, 99224392, 71657078, 10453030, 96318094, 247198, 94076128, 94595668, 67793644, 53888755, 669105, 48893685, 69697787, 45306070, 99125126, 91281584, 92530431, 24733232, 44842615, 5267545, 17764950, 9058407, 83533741, 68041839, 45667668, 97281847, 3509435, 25842078, 17957593, 26998766, 45237957, 26776922, 66045587, 42782652, 20568363, 1936762, 66903004, 14093520, 26397786, 37957788, 27325428, 17365188, 65338021, 78884452, 85695762, 22879907, 81898046, 21673260, 62490109, 89046466, 1569515, 83789391, 87598888, 37224844, 12571310, 24826575, 78589145, 45990383, 86301513, 7423788, 5640302, 39801351, 77312810, 82979980, 43933006, 39171895, 4204661, 42307449, 26063929, 13468268, 73617245, 84904436, 36505482, 41380093, 23110625, 85116755, 69786756, 92998591, 18504197, 51426311, 56461322, 71316369, 97379790, 90004325, 71920426, 82532312, 1204161, 63015256, 29401781, 18806856, 8733068, 98948034, 38256849, 26744917, 67513640, 17385531, 44060493, 46540998, 67124282, 321665, 89637706, 29065964, 16387575, 18833224, 61960400, 83368048, 79191827, 15148031, 10366309, 19376156, 85242963, 66137019, 45790169, 26493119, 58208470, 69641513, 93359396, 72019362, 84840549, 61623915, 48395186, 508198, 70782102, 9829782, 6497830, 17976208, 38008118, 17857111, 62232211, 38009615, 8729146, 10649306, 51149804, 51507425, 874791, 65081429, 72614359, 72738685, 75986488, 45428665, 57163802, 5073754, 93562257, 83747892, 55960386, 4508700, 99297164, 24915585, 28851716, 74110882, 4069912, 44479073, 87720882, 47298834, 55247455, 15536795, 89078848, 57240218, 83155442, 14349098, 7502255, 59318837, 45995325, 10597197, 97641116, 82897371, 65017137, 44627776, 95251277, 23848565, 45407418, 27411561, 14947650, 21533347, 3233084, 16445503, 90457870, 99658235, 62357986, 7104732, 95581843, 3880712, 81805959, 88444207, 37280276, 28734791, 90061527, 81677380, 69605283, 21993752, 21070110, 53648154, 22942635, 15902805, 99861373, 3235882, 92541302, 51047803, 33699435, 34044787, 7182642, 33935899, 49271185, 12024238, 20140249, 55669657, 34432810, 65851721, 36812683, 74614639, 59109400, 90158785, 92787493, 4095116, 2891150, 33336148, 13173644, 69579137, 78602717, 81274566, 47792865, 6153406, 94360702, 7955293, 18411915, 59910624, 86798033, 79322415, 26292919, 92803766, 76540481, 33061250, 59197747, 44784505, 62803858, 12348118, 24953498, 76330843, 61897582, 40677414, 97561745, 72495719, 80014588, 36135, 31491938, 19891772 +17857111, 86022504, 91255408, 30139692, 1788101, 3633375, 99604946, 42967683, 74357852, 71920426, 5822202, 29065964, 9599614, 33431960, 28550822, 34698428, 74137926, 38494874, 15075176, 80246713, 22108665, 75407347, 77301523, 63506281, 37739481, 71316369, 45919976, 40781854, 89699445, 52261574, 67124282, 57961282, 27185644, 85023028, 83150534, 61928316, 98739783, 1022677, 37675718, 36505482, 29188588, 32426752, 92998591, 18663507, 25636669, 17146629, 55143724, 69355476, 50188404, 40356877, 95726235, 18806856, 1431742, 6871053, 37891451, 73031054, 44550764, 89637706, 36812683, 36753250, 95251277, 71083565, 83368048, 72725103, 17539625, 58224549, 54199160, 14093520, 57393458, 9829782, 89996536, 32699744, 53393358, 44177011, 20867149, 31733363, 76434144, 95395112, 82979980, 42199455, 51507425, 46851987, 32590267, 57163802, 70372191, 5073754, 63152504, 88047921, 27625735, 4119747, 94711842, 74441448, 56424103, 97783876, 99333375, 50702367, 7502255, 61897582, 669105, 65038678, 31904591, 24733232, 82327024, 19376156, 13470059, 41092172, 35996293, 2891150, 22435353, 68041839, 26493119, 8634541, 71965942, 95581843, 3880712, 26776922, 4515343, 66903004, 84406788, 49236559, 4199704, 69848388, 81853704, 1569515, 93790285, 76170907, 48360198, 45990383, 92071990, 55850790, 60581278, 33553959, 4204661, 53666583, 98653983, 73124510, 72738685, 6793819, 7685448, 92604458, 5970607, 27041967, 16424199, 54232247, 56153345, 29994197, 6819644, 62762857, 83269727, 39986008, 56955985, 44060493, 5832946, 247198, 54987042, 86821229, 5111370, 99125126, 57020481, 61960400, 15148031, 26664538, 44842615, 76825057, 48673079, 4434662, 97281847, 90272749, 29510992, 13231279, 93359396, 26998766, 65271999, 6221471, 7104732, 93270084, 33895336, 91802888, 55602660, 6038457, 54014062, 70036438, 93515664, 67451935, 59405277, 17365188, 59614746, 6525948, 20681921, 19898053, 21993752, 40984766, 24619760, 89046466, 30787683, 61373987, 75135448, 55753905, 69255765, 66116458, 86301513, 65275241, 38341669, 92867155, 29932657, 82886381, 89217461, 6111563, 39171895, 17058722, 30543215, 52293995, 99729357, 65081429, 62740044, 4798568, 44847298, 80555751, 75986488, 99690194, 44245960, 67281495, 4099191, 77787724, 71333116, 85711894, 27665211, 7687278, 65047700, 86798033, 39201414, 60393039, 43152977, 37435892, 14731700, 66319530, 67030811, 98948034, 30218878, 66663942, 96193415, 37166608, 45848907, 83155442, 99224392, 65851721, 168541, 53888755, 22721500, 14220886, 11365791, 62452034, 72358170, 9886593, 16387575, 54517921, 32274392, 79191827, 23848565, 83210802, 76540481, 26102057, 91727510, 93053405, 78218845, 88698958, 96852321, 86001008, 22450468, 49882705, 53084256, 88653118, 44664587, 49328608, 7229550, 78602717, 42947632, 55470718, 88444207, 30366150, 63628376, 749283, 36189527, 37957788, 14626618, 34236719, 20836893, 61712234, 65715134, 6153406, 70596786, 55770687, 21070110, 20486294, 35456853, 73222868, 82178706, 62232211, 15902805, 12416768, 70004753, 65880522, 77377183, 62552524, 78561158, 68875490, 10649306, 13819358, 31161687, 569864, 93933709, 23134715, 43933006, 3233569, 82052050, 76671482, 51149804, 48260151, 26063929, 16019925, 66322959, 30653863, 26507214, 62936963, 61815223, 48892201, 415901, 1239555, 77620120, 15535065, 45428665, 9175338, 85116755, 16099750, 48658605, 71300104, 54868730, 112651, 63967300, 66885828, 71469330, 6505939, 83747892, 7066775, 47090124, 89811711, 95957797, 18131876, 96726697, 14045383, 59910624, 41481685, 32058615, 52060076, 40781449, 90654836, 72732205, 74110882, 33935899, 44479073, 50668599, 72777973, 8733068, 12024238, 56484900, 78300864, 20002147, 77284619, 73021291, 72357096, 20140249, 62496012, 9603598, 55669657, 38256849, 33565483, 17037369, 8128637, 84127901, 96709982, 82726008, 29819940, 30771409, 71657078, 46540998, 34946859, 59318837, 97057187, 50806615, 97641116, 4985896, 61141874, 44627776, 526217, 43506672, 92530431, 40677414, 90158785, 4787945, 35512853, 92787493, 4095116, 66137019, 80316608, 13173644, 26863229, 19939935, 17957593, 21001913, 17894977, 35092039, 43357947, 87160386, 78785507, 99658235, 75128745, 45237957, 36312813, 57248122, 26114953, 91831487, 81274566, 68816413, 59981773, 36468541, 67084351, 4978543, 92398073, 10309525, 27325428, 68128438, 34493392, 15163258, 84166196, 38008118, 14723410, 78884452, 89499542, 69605283, 68110330, 70727211, 99861373, 40027975, 8729146, 12571310, 19486173, 24826575, 78589145, 9575954, 44784505, 7423788, 92554120, 62803858, 17016156, 61263068, 86543538, 94360702, 60102965, 26275734, 66271566, 34698463, 42307449, 76703609, 874791, 79880247, 84293052, 84904436, 2331773, 61741594, 38365584, 49597667, 42692881, 77694700, 27187213, 12348118, 44889423, 81172706, 71522968, 2204165, 7646095, 73109997, 18504197, 41245325, 51426311, 56473732, 4508700, 59329511, 16971929, 91664334, 49641577, 77898274, 57359924, 90004325, 37659250, 79806380, 18699206, 91938191, 72373496, 86242799, 4668450, 67474219, 824872, 59501487, 99226875, 36930650, 41092102, 50567636, 37192445, 45996863, 12664567, 36396314, 57240218, 17386996, 22129328, 17727650, 10453030, 67793644, 2717150, 45306070, 65017137, 39553046, 99917599, 44481640, 93057697, 45407418, 83533741, 27411561, 94516935, 59371804, 18001617, 69579137, 81855445, 38510840, 90013093, 72274002, 8099994, 72019362, 40865610, 61623915, 10961421, 66611759, 73235980, 28796059, 42782652, 60430369, 79922758, 33628349, 8651647, 21919959, 22200378, 64848072, 51472275, 48675329, 6497830, 65454636, 34667286, 97011160, 32159704, 98130363, 81898046, 44844121, 21673260, 54263880, 59197747, 5482538, 67031644, 30090481, 3235882, 47213183, 63059024, 5640302, 75777973, 47887470, 16097038, 48088883, 89804152, 58180653, 55189057, 40197395, 36685023, 85117092, 8913721, 55615885, 35192533, 83948335, 41380093, 9188443, 18411915, 98371444, 69786756, 33249630, 43322743, 29029316, 37620363, 93562257, 18783702, 86118021, 50007421, 56461322, 33699435, 8791066, 41442762, 33237508, 68644627, 29466406, 29834512, 2607799, 78766358, 31126490, 16054533, 81774825, 68316156, 7517032, 61859143, 82532312, 65074535, 76960303, 14363867, 4069912, 68204242, 83302115, 61982238, 77072625, 79322415, 74452589, 77187825, 87710366, 26744917, 77413012, 17081350, 4064751, 47738185, 48774913, 82779622, 40534591, 79821897, 45995325, 94076128, 16380211, 34432810, 99971982, 30163921, 44983451, 90090964, 92353856, 8696647, 44846932, 88904910, 77300457, 43501211, 93566986, 36139942, 91240048, 69136837, 90310261, 38645117, 51588015, 99549373, 4806458, 17764950, 85242963, 97940276, 75543508, 97561745, 44473167, 94090109, 19891772, 75820087, 44348328, 3233084, 83133790, 33304202, 76315420, 90457870, 51715482, 84840549, 28928175, 75153252, 58749, 91141395, 8055981, 3088684, 66667729, 68702632, 66045587, 96735716, 508198, 2208785, 96420429, 78549759, 9259676, 75052463, 9860968, 14326617, 26397786, 42237907, 47893286, 44915235, 28734791, 20885148, 18466635, 32250045, 51590803, 72495719, 77272628, 62430984, 54663246, 1197320, 7919588, 15015906, 67227442, 24058273, 85695762, 38658347, 22879907, 62490109, 38061439, 3773993, 83789391, 87598888, 37224844, 88130087, 43045786, 73168565, 33123618, 7300047, 72793444, 13468268, 54427233, 80014588, 36845587, 91957544, 89214616, 59177718, 74724075, 37501808, 51464002, 55960386, 38022834, 97379790, 36135, 82651278, 4091162, 24915585, 28851716, 92692978, 66428795, 63015256, 99021067, 87720882, 72238278, 85571389, 73786944, 31491938, 1375023, 99524975, 55247455, 20645197, 83083131, 3294781, 34497327, 70420215, 62428472, 11581181, 31727379, 68939068, 89078848, 53632373, 1250437, 46870723, 54058788, 32151165, 94595668, 66832478, 6521313, 82897371, 48893685, 2917920, 56515456, 56531125, 321665, 94911072, 45049308, 18833224, 10366309, 84349107, 59109400, 60176618, 8115266, 23432750, 83651211, 20642888, 14947650, 45790169, 33797252, 45667668, 47361209, 21533347, 3509435, 98462867, 3487592, 63372756, 54762643, 9623492, 53842979, 82427263, 23569917, 30694952, 1936762, 89419466, 70782102, 26392416, 36580610, 91990218, 37280276, 11161731, 15948937, 94539824, 64098930, 24591705, 16595436, 30891921, 8807940, 86460488, 64087743, 33061250, 30395570, 176257, 64602895, 8129978, 20765474, 29959549, 37183543, 73617245, 84002370, 72614359, 78909561, 30463802, 19101477, 37560164, 92541302, 34295794, 29635537, 51047803, 42073124, 12303248, 22766820, 70367851, 36808486, 79136082, 6808825, 70541760, 23740167, 39373729, 34044787, 22113133, 52204879, 20122224, 1204161, 92033260, 16567550, 47298834, 60955663, 57658654, 54606384, 24168411, 36780454, 67513640, 6986898, 40686254, 17385531, 76330843, 74743862, 68694897, 9398733, 62693428, 91281584, 82339363, 68824981, 32161669, 92803766, 16405341, 9058407, 70800879, 79942022, 49598724, 45617087, 58208470, 92215320, 84684495, 17068582, 11923835, 48395186, 66250369, 30998561, 62115552, 47792865, 95290172, 95010552, 61271144, 13862149, 53802686, 30764367, 26734892, 53547802, 53648154, 38009615, 79738755, 15064655, 64157906, 13348726, 68000591, 30569392, 39801351, 62051033, 61728685, 21289531, 7955293, 92692283, 39847321, 96906410, 47708850, 99965001, 99297164, 58751351, 7182642, 30811010, 24314885, 49271185, 23194618, 29401781, 9257405, 10264691, 51466049, 24953498, 11757872, 21397057, 96318094, 72278539, 69697787, 22405120, 26139110, 29516592, 8373289, 57241521, 80251430, 69641513, 16445503, 68102437, 88251446, 79657802, 81805959, 51315460, 98943869, 75229462, 10358899, 90061527, 73392814, 22942635, 52613508, 42644903, 11543098, 19272365, 33201905, 15536795, 26292919, 57803235, 19457589, 74614639, 45075471, 61859581, 5267545, 25842078, 28787861, 20568363, 60106217, 36933038, 9860195, 6157724, 22997281, 77312810, 23110625, 7011964, 43376279, 64055960, 84187166, 45381876, 99515901, 14349098, 40152546, 33336148, 62357986, 3183975, 81677380, 65338021, 98648327, 16684829, 17976208, 45794415, 10597197 +44784505, 7104732, 81898046, 5482538, 67031644, 86798033, 14220886, 54517921, 45407418, 45237957, 28734791, 20867149, 99861373, 86301513, 77301523, 4508700, 31126490, 7182642, 97783876, 17727650, 10453030, 44983451, 72278539, 14947650, 3509435, 88698958, 72019362, 21919959, 13862149, 30764367, 65338021, 69605283, 62552524, 92071990, 73617245, 65081429, 69786756, 59177718, 18504197, 8791066, 95957797, 16424199, 12024238, 40781854, 33565483, 46540998, 77300457, 13470059, 51588015, 92787493, 97561745, 57241521, 29510992, 49882705, 63372756, 48395186, 91831487, 64848072, 65454636, 34236719, 21673260, 88130087, 66116458, 7423788, 95395112, 62803858, 89217461, 33553959, 43933006, 48088883, 76671482, 32590267, 6793819, 85116755, 92604458, 12303248, 43322743, 82651278, 28851716, 27625735, 79806380, 65047700, 74441448, 4668450, 77072625, 55669657, 12664567, 57240218, 76330843, 79821897, 59318837, 54987042, 45306070, 89637706, 92530431, 93566986, 82339363, 75543508, 44473167, 17539625, 58208470, 13231279, 78785507, 61623915, 34698428, 79657802, 96735716, 57248122, 6038457, 26392416, 22200378, 47893286, 92398073, 15015906, 67227442, 6153406, 8807940, 78589145, 10649306, 37183543, 7300047, 42199455, 36685023, 26063929, 44847298, 36845587, 9175338, 51047803, 5073754, 71522968, 99297164, 33237508, 74357852, 88047921, 36135, 32058615, 81774825, 52060076, 57359924, 90004325, 4091162, 4119747, 27665211, 18699206, 33935899, 92033260, 76960303, 14731700, 62496012, 9603598, 99333375, 89078848, 40686254, 34946859, 247198, 94595668, 86821229, 44550764, 74743862, 69697787, 99125126, 88904910, 93057697, 5267545, 84349107, 91240048, 99549373, 20642888, 83533741, 27411561, 35996293, 33336148, 26493119, 80251430, 21533347, 21001913, 26998766, 88251446, 75128745, 91141395, 65271999, 58224549, 28787861, 3183975, 51315460, 38494874, 55470718, 36933038, 57393458, 49236559, 70036438, 93515664, 34667286, 77272628, 62430984, 94539824, 17857111, 20836893, 85695762, 35456853, 80246713, 24619760, 87598888, 79738755, 76170907, 19486173, 65880522, 22108665, 9575954, 30569392, 63059024, 92554120, 65275241, 29959549, 77312810, 94360702, 82052050, 42307449, 72614359, 78909561, 35192533, 61815223, 19101477, 1239555, 92692283, 92541302, 29188588, 48658605, 96906410, 92998591, 66885828, 70367851, 93562257, 79136082, 67281495, 51426311, 18783702, 41442762, 68644627, 91664334, 14045383, 54232247, 92692978, 69355476, 99021067, 29994197, 73786944, 18806856, 78300864, 98948034, 34497327, 62762857, 74452589, 38256849, 87710366, 83269727, 11581181, 26744917, 45848907, 14349098, 54058788, 96318094, 94076128, 34432810, 50806615, 669105, 2717150, 6521313, 67124282, 56515456, 5111370, 62452034, 16387575, 65038678, 71083565, 39553046, 31904591, 44481640, 61859581, 9599614, 36139942, 90158785, 17764950, 4095116, 76540481, 26102057, 79942022, 2891150, 59371804, 4434662, 33797252, 49598724, 45667668, 18001617, 86001008, 3233084, 38510840, 33304202, 53084256, 10961421, 54762643, 88653118, 8055981, 3880712, 33895336, 42782652, 91802888, 81805959, 23569917, 1936762, 68816413, 95010552, 61271144, 30366150, 10358899, 63628376, 37957788, 4978543, 9860195, 6157724, 53802686, 72495719, 22997281, 6525948, 7919588, 24591705, 81853704, 16595436, 3633375, 70596786, 19898053, 53648154, 22879907, 15902805, 30395570, 31733363, 12571310, 48360198, 64602895, 30090481, 68000591, 77377183, 45990383, 5640302, 86543538, 16097038, 39171895, 4204661, 3233569, 17058722, 58180653, 55189057, 99729357, 54427233, 30653863, 62740044, 62936963, 61741594, 83948335, 38365584, 49597667, 75986488, 99690194, 32426752, 63967300, 22766820, 44889423, 36808486, 63152504, 6505939, 83747892, 86118021, 4099191, 56461322, 47090124, 5970607, 85711894, 16054533, 49641577, 61859143, 91938191, 7687278, 1204161, 14363867, 4069912, 72373496, 16567550, 8733068, 94711842, 60393039, 37435892, 64055960, 1431742, 67030811, 99226875, 66663942, 41092102, 89699445, 39986008, 15536795, 84127901, 83155442, 17081350, 52261574, 47738185, 29819940, 5832946, 7502255, 37891451, 61897582, 53888755, 4985896, 48893685, 68694897, 2917920, 62693428, 56531125, 72358170, 91281584, 57020481, 526217, 95251277, 74614639, 32274392, 24733232, 26664538, 40677414, 76825057, 66137019, 30139692, 19891772, 17957593, 8099994, 40865610, 28550822, 75153252, 58749, 62357986, 73235980, 3088684, 54199160, 26776922, 66045587, 55602660, 78549759, 33628349, 85023028, 14326617, 95290172, 88444207, 67084351, 42237907, 36580610, 749283, 36189527, 67451935, 90061527, 81677380, 68128438, 59614746, 38008118, 64098930, 1197320, 21993752, 21070110, 20486294, 73222868, 62490109, 44177011, 1569515, 37224844, 15064655, 8729146, 75135448, 24826575, 61928316, 98739783, 42644903, 60581278, 17016156, 93933709, 23134715, 42967683, 60102965, 89804152, 52293995, 874791, 80014588, 79880247, 84293052, 84904436, 46851987, 80555751, 36505482, 415901, 37560164, 41380093, 29635537, 9188443, 7685448, 71300104, 112651, 12348118, 81172706, 2204165, 37620363, 70541760, 23740167, 99965001, 56473732, 33699435, 34044787, 89811711, 71316369, 71333116, 27041967, 43376279, 55143724, 59910624, 20122224, 37659250, 71920426, 19272365, 66428795, 16684829, 50668599, 72238278, 9257405, 51466049, 31491938, 99524975, 83083131, 60955663, 59501487, 61982238, 96193415, 86022504, 36780454, 17037369, 50567636, 56955985, 77413012, 57803235, 96709982, 17385531, 21397057, 44060493, 45995325, 65851721, 91255408, 9886593, 61141874, 44627776, 18833224, 43501211, 44842615, 69136837, 23432750, 83651211, 4787945, 9058407, 70800879, 80316608, 22435353, 45617087, 33431960, 69579137, 47361209, 57961282, 69641513, 26863229, 25842078, 71965942, 43357947, 51715482, 87160386, 99658235, 28928175, 11923835, 66250369, 66667729, 82427263, 96420429, 26114953, 98943869, 9860968, 47792865, 75229462, 26397786, 84406788, 51472275, 10309525, 15948937, 27325428, 51590803, 17365188, 14626618, 32159704, 89996536, 84166196, 54663246, 14723410, 69848388, 98130363, 78884452, 55770687, 82178706, 62232211, 44844121, 40984766, 12416768, 33061250, 3773993, 30787683, 93790285, 59197747, 3235882, 52613508, 75407347, 8129978, 68875490, 39801351, 13819358, 92867155, 75777973, 33123618, 61263068, 37675718, 53666583, 72793444, 98653983, 30543215, 11543098, 85117092, 63506281, 55615885, 26507214, 4798568, 30463802, 48892201, 91957544, 39847321, 57163802, 16099750, 18411915, 70372191, 33249630, 42692881, 47708850, 71469330, 41245325, 50007421, 7011964, 22113133, 38022834, 52204879, 78766358, 97379790, 41481685, 68316156, 24314885, 44479073, 50188404, 87720882, 85571389, 39201414, 83302115, 24953498, 55247455, 6819644, 3294781, 72357096, 20140249, 77187825, 54606384, 24168411, 62428472, 68939068, 37192445, 53632373, 48774913, 30771409, 97641116, 82897371, 94911072, 65017137, 44846932, 36812683, 29065964, 43506672, 68824981, 23848565, 92803766, 38645117, 60176618, 8115266, 4806458, 85242963, 94516935, 97940276, 45790169, 90272749, 94090109, 92215320, 44348328, 98462867, 96852321, 19939935, 27185644, 90013093, 35092039, 72274002, 22450468, 93359396, 44664587, 28796059, 53842979, 30998561, 79922758, 74137926, 30694952, 9259676, 75052463, 60106217, 59981773, 83150534, 91990218, 54014062, 9829782, 4199704, 18466635, 32250045, 97011160, 34493392, 53547802, 24058273, 38658347, 64087743, 54263880, 70004753, 83789391, 76434144, 13348726, 43045786, 20765474, 31161687, 55850790, 29932657, 21289531, 47887470, 40197395, 66271566, 16019925, 13468268, 66322959, 51507425, 37739481, 73124510, 77620120, 72738685, 89214616, 74724075, 44245960, 37501808, 51464002, 17146629, 29834512, 30811010, 49271185, 65074535, 56153345, 23194618, 29401781, 45919976, 95726235, 20645197, 77284619, 30218878, 824872, 79322415, 67513640, 45996863, 8128637, 36396314, 6986898, 50702367, 17386996, 99515901, 46870723, 71657078, 32151165, 10597197, 97057187, 67793644, 66832478, 90090964, 11365791, 92353856, 321665, 45049308, 61960400, 45075471, 79191827, 99917599, 15148031, 59109400, 72725103, 41092172, 91727510, 68041839, 97281847, 83133790, 6221471, 66611759, 93270084, 508198, 78602717, 66903004, 42947632, 14093520, 70782102, 44915235, 20885148, 15163258, 65715134, 45794415, 22942635, 30891921, 68110330, 61373987, 55753905, 64157906, 78561158, 69255765, 38341669, 73168565, 26275734, 8913721, 7955293, 23110625, 34295794, 15535065, 98371444, 54868730, 29029316, 18663507, 7646095, 55960386, 58751351, 59329511, 77787724, 16971929, 18131876, 2607799, 40781449, 90654836, 72777973, 10264691, 43152977, 56484900, 20002147, 66319530, 67474219, 56424103, 70420215, 33201905, 82726008, 4064751, 82779622, 40152546, 16380211, 73031054, 22721500, 19457589, 83368048, 32161669, 82327024, 19376156, 83210802, 90310261, 35512853, 16405341, 48673079, 26139110, 29516592, 75820087, 84684495, 76315420, 16445503, 90457870, 9623492, 36312813, 68702632, 95581843, 2208785, 60430369, 62115552, 20568363, 81274566, 1788101, 37280276, 48675329, 6497830, 59405277, 20681921, 73392814, 89499542, 38061439, 89046466, 98648327, 62051033, 61728685, 82886381, 1022677, 569864, 82979980, 51149804, 34698463, 48260151, 77694700, 73109997, 6808825, 25636669, 96726697, 7517032, 24915585, 68204242, 40356877, 1375023, 86242799, 73021291, 5822202, 37166608, 31727379, 26292919, 6871053, 99971982, 168541, 8696647, 9398733, 36753250, 10366309, 93053405, 81855445, 17894977, 84840549, 68102437, 3487592, 49328608, 17976208, 15075176, 11161731, 61712234, 32699744, 38009615, 86460488, 70727211, 6111563, 2331773, 84002370, 45428665, 42073124, 27187213, 7066775, 29466406, 72732205, 82532312, 63015256, 47298834, 57658654, 36930650, 84187166, 1250437, 22129328, 99224392, 40534591, 30163921, 22405120, 8634541, 13173644, 78218845, 4515343, 26734892, 53393358, 99604946, 39373729, 11757872, 45381876, 8373289, 17068582, 7229550, 8651647, 89419466, 36468541, 40027975, 47213183, 77898274, 74110882, 176257, 76703609 diff --git a/src/test/resources/data/test_ground_truth_l2_100.csv b/src/test/resources/data/test_ground_truth_l2_100.csv new file mode 100644 index 000000000..c1986261f --- /dev/null +++ b/src/test/resources/data/test_ground_truth_l2_100.csv @@ -0,0 +1,100 @@ +76960303,76703609,62740044,29188588,66428795,37435892,60106217,71469330,9575954,6986898,2607799,66045587,39847321,48774913,16097038,61263068,75229462,39801351,247198,44627776,7182642,40677414,29834512,95957797,30771409,84293052,16099750,38658347,86001008,76434144,9860968,94076128,77187825,67793644,30998561,50806615,96193415,42967683,4095116,16595436,4099191,54987042,56424103,61271144,74137926,2208785,24915585,43376279,92033260,21993752,70596786,24058273,71083565,9623492,6808825,7687278,51472275,35192533,97011160,38645117,34236719,86460488,56153345,61859143,43933006,99224392,17081350,7646095,37675718,66667729,8733068,32250045,35456853,44889423,14947650,96726697,62452034,83210802,86022504,79136082,73235980,1569515,62496012,22108665,92803766,7919588,92787493,40865610,72738685,75407347,63967300,23848565,33237508,51047803,6153406,69786756,55247455,24826575,6793819,5111370,63059024,64157906,42307449,3233084,75986488,38061439,80014588,72373496,14349098,72357096,20885148,53842979,23740167,17764950,32161669,98943869,33935899,65851721,43506672,54199160,7423788,16971929,60176618,9599614,36753250,9860195,97783876,83948335,53632373,79922758,94711842,176257,86301513,4064751,26863229,5640302,79821897,17385531,68694897,94539824,57241521,40781854,29994197,8634541,60430369,19376156,30218878,7685448,58749,57163802,60955663,44847298,30543215,19939935,75777973,26493119,62693428,39171895,20002147,92692283,57248122,70372191,40781449,78549759,22879907,62232211,73786944,14731700,3233569,72274002,41092172,76315420,72725103,85116755,9603598,93933709,22942635,1431742,46870723,57359924,15015906,20568363,77620120,44177011,17386996,749283,40984766,76671482,21001913,49236559,36396314,33061250,76540481,54427233,44550764,54762643,88653118,95581843,31904591,48893685,78589145,27625735,36780454,17037369,63152504,47792865,2917920,84840549,39553046,21070110,4668450,36812683,32426752,45848907,81853704,29029316,73392814,37620363,50702367,34698463,26664538,77413012,8129978,59371804,84904436,61373987,27665211,16019925,54663246,68939068,48675329,80316608,70004753,53802686,1788101,87720882,37891451,64602895,74110882,53666583,18131876,71316369,13231279,16054533,66271566,29466406,55189057,12024238,96735716,19101477,42199455,64848072,45794415,15064655,68000591,78602717,3487592,68875490,67030811,85242963,26998766,77272628,18699206,99515901,18783702,55960386,89811711,13468268,19486173,66903004,41481685,93270084,90272749,1197320,91802888,73617245,17146629,15536795,18806856,17957593,89214616,28796059,23569917,29819940,71300104,72777973,50668599,36685023,48260151,93515664,3509435,2204165,37192445,24168411,11365791,99690194,3773993,36312813,8807940,77284619,82779622,91240048,15075176,49882705,74452589,61960400,5267545,12571310,90457870,16387575,99333375,86821229,36505482,36933038,508198,569864,33797252,70036438,65880522,62803858,32159704,69136837,21289531,7066775,73124510,16380211,49598724,8913721,9175338,55602660,63372756,14326617,91664334,54058788,16405341,62430984,52060076,38365584,54517921,8651647,7011964,30569392,35092039,71657078,59981773,99658235,68644627,19457589,90004325,78218845,29510992,92692978,24591705,83302115,7955293,51464002,9058407,91990218,874791,56484900,62051033,39986008,88047921,57803235,45996863,53547802,36845587,83533741,82427263,9188443,23194618,45995325,27185644,85571389,91957544,51149804,4508700,31126490,27325428,42073124,6505939,30764367,415901,34497327,12348118,67451935,29959549,77694700,26744917,40027975,29932657,48892201,41245325,69355476,22129328,38341669,68824981,55669657,9398733,3880712,26392416,34698428,52261574,41092102,43357947,526217,20140249,36189527,45075471,3294781,66250369,112651,11543098,44915235,89217461,38009615,79880247,40534591,45237957,26507214,44060493,87710366,80555751,33565483,92071990,83150534,59910624,15948937,57961282,61859581,34946859,2891150,25636669,49271185,84187166,19272365,17365188,81805959,96420429,97057187,92604458,87598888,62936963,20122224,40152546,2717150,97281847,66832478,15535065,83269727,28787861,17539625,95251277,10358899,99604946,82178706,13470059,68204242,84127901,36930650,77377183,6497830,3235882,36580610,92554120,30090481,4204661,87160386,34044787,30787683,36808486,168541,669105,38494874,90013093,34295794,73222868,91141395,99971982,90061527,1022677,55753905,68102437,4434662,10366309,69641513,43322743,33304202,45790169,17068582,55143724,98948034,18833224,64087743,20642888,9257405,79806380,8128637,97940276,4787945,11757872,30811010,75135448,74743862,32274392,99549373,78909561,16684829,30891921,82897371,37224844,77300457,45919976,98739783,59177718,99917599,92867155,62490109,72019362,45049308,99021067,10961421,61741594,24953498,94516935,18001617,58208470,4119747,61982238,25842078,47708850,59501487,45667668,41442762,53648154,83368048,96852321,57240218,37501808,44348328,68316156,67281495,5970607,65338021,56955985,58180653,20836893,3633375,11923835,64055960,40686254,79322415,68110330,66663942,44983451,19891772,77072625,99861373,89046466,90654836,66319530,79191827,86242799,7517032,83133790,11581181,8791066,59614746,13348726,92541302,47361209,26139110,13173644,39373729,44846932,65081429,45407418,84684495,37183543,89419466,60102965,97641116,49641577,56515456,90310261,33249630,71333116,46851987,78766358,61141874,54014062,21919959,99297164,9886593,31161687,50007421,82532312,43501211,23110625,99965001,30694952,78884452,98371444,93057697,65454636,26292919,6871053,95010552,67513640,76170907,30139692,53084256,92215320,44784505,22450468,28928175,33628349,91727510,58751351,5832946,44479073,18411915,77312810,12664567,71920426,94911072,82726008,94360702,30463802,16445503,82651278,48673079,81898046,51426311,24314885,37659250,6038457,58224549,91281584,79942022,1204161,93566986,20486294,27041967,39201414,73031054,32590267,94090109,30163921,50188404,57020481,35996293,33123618,15148031,74357852,4806458,76330843,30395570,37739481,31491938,70420215,70367851,1250437,26114953,26776922,80246713,88698958,56531125,67227442,48088883,52613508,75153252,4515343,42644903,73168565,77301523,26102057,32699744,7104732,7300047,6521313,9829782,40197395,6111563,75128745,2331773,57658654,56461322,78785507,26063929,83083131,45381876,82886381,8373289,22721500,92398073,91255408,321665,42947632,51715482,68128438,83747892,17727650,10649306,83155442,72278539,83789391,82339363,49597667,73021291,36135,63015256,43045786,16424199,27187213,60581278,5073754,21533347,82979980,33895336,55470718,93359396,68041839,72495719,52204879,47887470,72614359,86118021,74724075,74614639,14093520,22997281,54868730,88904910,79657802,67474219,95290172,22766820,6819644,72238278,51507425,38022834,51588015,94595668,3183975,75543508,98462867,61728685,99524975,27411561,65275241,66611759,45617087,97379790,83651211,63506281,66885828,47213183,53393358,22435353,96709982,18663507,53888755,81774825,28851716,20765474,29065964,4091162,36139942,8099994,35512853,52293995,4069912,61897582,81677380,44473167,47893286,59109400,89699445,7502255,42782652,84406788,65715134,97561745,59197747,12416768,6157724,18504197,69579137,33699435,26734892,4798568,51466049,1936762,24619760,22200378,61815223,82327024,86543538,43152977,68702632,85711894,33201905,37560164,26397786,86798033,65271999,48658605,55770687,65047700,19898053,28734791,36468541,92998591,72793444,72358170,15902805,61623915,93790285,98130363,81274566,32058615,12303248,14220886,10597197,84002370,21673260,88444207,89499542,11161731,33431960,34493392,42237907,75052463,82052050,93562257,98653983,37957788,70727211,6221471,47298834,74441448,93053405,15163258,34432810,49328608,81172706,65074535,88251446,48395186,46540998,70800879,40356877,54232247,14626618,29635537,5822202,4985896,59329511,66322959,21397057,8115266,66116458,33553959,4199704,59318837,54263880,76825057,90090964,38510840,33336148,95726235,20645197,62762857,63628376,89637706,9259676,31727379,98648327,78300864,95395112,44844121,44664587,44245960,73109997,89996536,13862149,45990383,77787724,44481640,32151165,78561158,62357986,34667286,92530431,85023028,18466635,57393458,99226875,55615885,70782102,29401781,89078848,75820087,45428665,30653863,68816413,51590803,77898274,81855445,38008118,23134715,28550822,64098930,5482538,6525948,8696647,71522968,10309525,30366150,50567636,31733363,84349107,61928316,69848388,4978543,41380093,17016156,8729146,56473732,38256849,62552524,47090124,26275734,44842615,17058722,47738185,69605283,17894977,91938191,67031644,3088684,65017137,71965942,62428472,14723410,20867149,17976208,65038678,89804152,10264691,10453030,85117092,99729357,60393039,42692881,62115552,51315460,48360198,45306070,91831487,92353856,8055981,23432750,70541760,29516592,69697787,7229550,69255765,16567550,55850790,72732205,61712234,17857111,14363867,96318094,88130087,37166608,13819358,66137019,85695762,14045383,67084351,54606384,1375023,824872,67124282,84166196,90158785,20681921,22405120,22113133,99125126,24733232,79738755,37280276,80251430,1239555,59405277,96906410 +84840549,73124510,14326617,99658235,9623492,89214616,68702632,30787683,97940276,5267545,76434144,35092039,40534591,19376156,23569917,29819940,5111370,80316608,98943869,83368048,1788101,41245325,15536795,6986898,37620363,80251430,22942635,64087743,42644903,36930650,13173644,18663507,63967300,72495719,247198,21070110,16684829,30694952,79136082,45996863,17016156,74614639,93053405,18466635,17764950,36685023,80555751,27665211,48774913,76703609,36505482,79922758,18131876,94076128,18783702,40027975,22879907,72373496,64098930,38061439,30543215,1022677,1431742,43501211,9886593,32250045,62936963,60955663,99917599,20642888,44889423,72738685,44348328,40865610,16424199,73235980,65047700,13468268,66885828,38365584,45995325,16097038,97783876,73392814,63372756,33304202,39553046,168541,83302115,6808825,47213183,13231279,15535065,31904591,62115552,65074535,3509435,10961421,16445503,81677380,81898046,7502255,26744917,33123618,40152546,62496012,77312810,96726697,35456853,93933709,11365791,36780454,37659250,39986008,92033260,43933006,78766358,34432810,61263068,82052050,9603598,68204242,56515456,34295794,7919588,48675329,29029316,66045587,38658347,82897371,61373987,78884452,48260151,10366309,53842979,54762643,32161669,51715482,73031054,2917920,40677414,92787493,55770687,90272749,71469330,17727650,71657078,63628376,57803235,55753905,37166608,96193415,34497327,79322415,56484900,43376279,84684495,75986488,669105,30139692,15902805,43045786,90090964,45075471,23194618,83150534,62490109,50668599,23848565,47738185,71333116,72777973,4668450,8115266,19891772,69136837,54199160,16054533,3294781,3233084,24591705,7646095,62803858,65338021,33628349,68041839,78602717,51047803,91664334,91802888,19898053,36139942,6157724,92803766,2717150,10358899,62693428,7517032,90457870,24915585,76960303,98371444,7300047,69355476,40984766,22766820,70004753,77377183,112651,98130363,29959549,29994197,38645117,51149804,63506281,33249630,52293995,61897582,79880247,14731700,4099191,7687278,41481685,6153406,59197747,16380211,20486294,55615885,75229462,86022504,34698428,8634541,17894977,26292919,14626618,88047921,4434662,60430369,62357986,39373729,99515901,30163921,24953498,874791,29188588,78909561,43322743,92215320,30366150,83083131,95581843,29466406,3233569,19457589,87710366,30463802,73222868,95726235,74137926,83210802,19486173,10597197,87720882,12348118,39801351,62452034,22129328,60102965,92398073,18833224,66663942,97057187,569864,50806615,17068582,24058273,85242963,12416768,29065964,36753250,5822202,8696647,48893685,85571389,78300864,26392416,33895336,26114953,17957593,66322959,59981773,88698958,85116755,21533347,88653118,89419466,48892201,36933038,93562257,30764367,22108665,98653983,43152977,77272628,66271566,50188404,6521313,10309525,45667668,4064751,42073124,73168565,95957797,25636669,11543098,78589145,61271144,72274002,36580610,47298834,52613508,79657802,42307449,82726008,68816413,77187825,75777973,44844121,66428795,55143724,37435892,56955985,30090481,50702367,67030811,70596786,9398733,67227442,96852321,33061250,16099750,31727379,49328608,6871053,26507214,47361209,62552524,99297164,45919976,69255765,69641513,61728685,53802686,60581278,44550764,2891150,37739481,76315420,95395112,99226875,30998561,46851987,99971982,9188443,53393358,14093520,98462867,46870723,65851721,70727211,29834512,4985896,36812683,28796059,56473732,75153252,32590267,8913721,53084256,89699445,16387575,86001008,42967683,26139110,35192533,16971929,20122224,13348726,66667729,68316156,99021067,74110882,74743862,38008118,28851716,58751351,82532312,79191827,11161731,30891921,39171895,64157906,65017137,81805959,21993752,72732205,70541760,91727510,99333375,45237957,61141874,16567550,36845587,71316369,66611759,61982238,68644627,99965001,37183543,49598724,57658654,81172706,44245960,4806458,97641116,8791066,67281495,98648327,22997281,7955293,20568363,45306070,44983451,526217,59501487,40781449,57163802,18411915,61623915,56531125,53547802,41442762,3487592,70036438,92692978,37192445,83533741,84293052,48673079,54058788,15064655,98948034,1569515,94516935,78549759,321665,73021291,9860968,59371804,92692283,69848388,54014062,37224844,508198,99729357,30811010,14947650,82339363,7066775,43506672,22435353,64055960,47090124,2607799,30395570,97561745,9860195,36312813,3633375,28550822,96420429,89217461,92071990,3183975,74357852,39847321,30218878,81274566,22450468,95290172,8651647,99549373,37675718,17976208,33797252,3880712,8733068,4095116,64602895,67793644,12571310,89637706,27187213,60106217,49641577,92604458,34493392,64848072,36808486,99690194,1197320,51466049,53666583,9599614,47708850,19939935,19272365,68694897,44915235,66903004,62740044,24619760,65454636,86242799,42947632,20002147,42782652,66319530,65715134,3773993,40356877,34946859,9257405,51588015,93566986,17386996,13470059,7423788,55247455,21001913,83155442,20765474,415901,36396314,61928316,50007421,16595436,72725103,50567636,71300104,83133790,66250369,59177718,26397786,72019362,18001617,61859143,44481640,30569392,30771409,62428472,2208785,12024238,20885148,33553959,2204165,99524975,87598888,33201905,60393039,79821897,63015256,14723410,86460488,76170907,38009615,81853704,6793819,15015906,91831487,47792865,67084351,44177011,85117092,76540481,40197395,79942022,44060493,70800879,749283,29635537,8729146,91141395,77413012,45848907,78785507,75128745,77898274,72358170,8373289,91957544,4787945,52060076,75543508,49271185,3088684,55470718,66832478,77284619,5640302,48658605,62762857,90310261,56424103,67451935,44842615,27185644,7685448,19101477,24314885,55189057,9058407,77072625,4119747,42199455,12303248,38341669,57248122,75135448,67124282,62051033,70420215,82886381,57020481,77620120,99604946,23740167,83789391,68102437,24826575,45049308,18504197,73786944,93270084,59614746,26734892,5832946,40686254,31161687,68875490,8129978,89046466,55602660,91281584,97011160,54987042,94711842,94911072,70372191,29932657,44664587,9175338,95010552,69786756,45990383,84349107,33237508,38494874,94595668,21673260,60176618,92554120,92541302,76330843,54427233,89811711,31126490,63059024,59910624,61960400,98739783,55669657,24168411,77301523,93057697,71920426,88904910,51472275,17037369,63152504,90654836,18699206,76671482,91990218,22200378,44479073,32159704,54868730,7182642,39201414,10649306,61859581,80014588,96906410,24733232,59109400,49236559,9259676,81855445,69605283,57961282,88444207,76825057,17365188,53648154,90061527,56153345,78218845,97379790,8055981,62232211,86821229,35512853,10264691,65038678,18806856,7229550,84187166,83948335,14349098,62430984,53632373,57359924,58224549,6525948,85711894,32274392,83651211,65081429,26063929,72357096,94090109,6497830,41092172,54606384,75407347,49882705,84904436,34236719,6505939,37280276,52204879,69579137,33336148,51507425,30653863,9575954,86301513,79806380,5073754,72278539,66137019,57241521,70782102,20645197,59318837,32426752,23432750,84166196,13862149,4069912,51464002,37501808,56461322,57240218,52261574,93359396,99861373,87160386,16019925,15948937,8807940,93790285,72614359,7011964,94539824,23110625,26493119,84002370,25842078,6038457,21919959,58749,34044787,37560164,16405341,55850790,34698463,48088883,8099994,47887470,78561158,11923835,38510840,8128637,77694700,65271999,57393458,48395186,27325428,74724075,74441448,45428665,22721500,96709982,49597667,35996293,15075176,17385531,1204161,36468541,45794415,51590803,82779622,26275734,73617245,84127901,13819358,71522968,824872,34667286,22113133,28928175,44473167,90013093,36189527,17146629,29516592,4508700,51426311,91240048,26998766,22405120,91938191,54517921,44784505,4515343,54663246,92867155,83747892,68000591,67513640,86118021,61712234,9829782,44627776,43357947,33935899,75052463,85023028,7104732,17058722,94360702,41092102,48360198,46540998,92998591,95251277,1375023,38256849,82979980,44847298,2331773,67474219,31491938,86798033,74452589,17539625,90004325,32058615,26664538,59329511,6819644,33565483,37891451,65275241,68824981,72793444,14045383,15148031,11581181,75820087,21397057,97281847,54232247,29510992,1250437,82178706,27411561,71083565,5482538,54263880,96318094,93515664,61741594,99125126,4798568,38022834,82427263,27625735,44846932,33699435,58180653,45381876,99224392,32151165,36135,33431960,28734791,67031644,6221471,14363867,20867149,61815223,96735716,89078848,45617087,45790169,4199704,71965942,29401781,17081350,3235882,45407418,17857111,176257,88251446,82651278,6111563,4978543,68128438,20681921,77300457,69697787,79738755,58208470,26102057,26863229,14220886,53888755,65880522,82327024,32699744,83269727,88130087,70367851,66116458,15163258,10453030,77787724,73109997,21289531,55960386,90158785,80246713,41380093,42692881,27041967,4091162,86543538,68110330,26776922,20140249,81774825,72238278,89804152,4204661,5970607,31733363,85695762,37957788,11757872,92353856,47893286,20836893,42237907,23134715,40781854,1936762,91255408,51315460,59405277,89499542,1239555,68939068,92530431,28787861,12664567,89996536,84406788 +56461322,63015256,74614639,48260151,91664334,54987042,92803766,4119747,6153406,12348118,33237508,7011964,75153252,88047921,33553959,9398733,72777973,4069912,67793644,89214616,62452034,15064655,19891772,21070110,73168565,97641116,34493392,30218878,87710366,3294781,80316608,62496012,68644627,669105,26114953,68204242,45306070,62936963,44846932,42967683,51507425,35092039,415901,62428472,62115552,54199160,73392814,57240218,45237957,22450468,55143724,54427233,65715134,1431742,65454636,48675329,13468268,92692978,68816413,81855445,59177718,99965001,70541760,29188588,48893685,34236719,29466406,9175338,78549759,26776922,97783876,93933709,45995325,99690194,83368048,99297164,90061527,57163802,79806380,6986898,93566986,5832946,10961421,95290172,874791,29819940,60430369,76671482,31904591,47738185,77377183,79136082,88653118,99226875,70004753,78785507,49598724,32161669,36468541,19272365,19376156,33628349,56473732,33797252,77284619,40781449,38658347,26139110,44983451,37183543,14093520,36812683,4787945,76540481,50806615,4099191,68316156,34698428,14326617,35192533,45919976,14731700,55753905,60102965,66137019,20765474,92071990,96193415,40152546,40677414,47708850,53084256,48673079,72238278,64087743,16445503,6793819,90654836,9886593,8807940,66322959,44889423,27665211,70372191,59318837,47792865,30891921,15015906,8913721,16097038,61263068,90090964,71469330,74357852,11161731,38061439,91281584,22129328,93053405,24733232,94911072,36808486,31161687,3233084,7687278,60955663,96726697,21289531,20486294,43045786,749283,17058722,77072625,12571310,68824981,62430984,92787493,46851987,32426752,70596786,62232211,85116755,54606384,67030811,63967300,24619760,9623492,81172706,79821897,32699744,9188443,45407418,81853704,30139692,94076128,51047803,36753250,43501211,91727510,22108665,17386996,4806458,63059024,66832478,50702367,40356877,48395186,83210802,68694897,22879907,3633375,8099994,508198,80246713,81274566,96420429,33249630,5267545,30395570,28550822,21673260,99917599,17957593,72738685,54663246,72278539,75135448,13231279,73617245,76330843,7517032,81898046,38009615,12024238,95395112,72373496,23569917,18131876,44550764,26102057,32590267,78909561,77620120,73031054,71657078,42644903,78602717,76960303,7919588,44245960,80555751,55615885,18783702,82327024,53842979,569864,83302115,56424103,57803235,7229550,9860968,83083131,48088883,37739481,168541,88444207,92033260,49271185,28928175,69641513,34497327,30787683,42307449,36396314,16595436,86022504,59197747,35996293,22200378,33123618,92604458,70800879,42199455,49641577,14626618,63506281,16424199,78218845,92554120,90272749,112651,59614746,36580610,56531125,74137926,77312810,16971929,61271144,71316369,53632373,82532312,7423788,81677380,28851716,34295794,247198,29029316,98371444,16380211,12416768,59501487,67281495,33304202,7502255,78589145,66663942,5970607,36505482,43506672,37620363,43933006,23110625,32274392,29994197,50007421,5111370,40027975,20140249,36189527,74441448,8373289,20122224,89811711,30543215,66611759,63628376,18699206,5640302,3235882,51590803,43322743,86821229,18466635,70420215,37166608,2891150,72357096,16019925,95957797,2917920,41481685,45996863,83150534,27625735,69355476,51472275,94360702,89637706,66271566,65047700,30569392,25842078,10649306,93515664,1204161,30764367,38645117,6871053,49328608,57020481,55602660,78561158,79657802,20885148,86242799,54058788,3233569,38008118,53547802,48360198,89078848,22942635,64157906,92215320,50188404,38494874,39847321,83789391,3509435,75986488,78766358,29510992,56515456,7104732,82052050,18663507,1375023,85242963,37501808,76434144,67474219,76703609,36930650,4985896,99333375,7955293,33895336,59910624,99658235,73124510,7300047,39171895,17037369,54762643,57241521,72019362,33431960,4668450,40781854,92353856,98739783,43152977,61623915,65275241,61741594,96318094,9860195,40686254,7685448,24953498,55770687,80014588,51715482,15163258,50668599,71333116,40865610,97940276,48892201,61897582,99861373,1022677,65017137,88130087,73235980,76170907,66903004,59371804,75543508,83948335,82779622,51588015,61859581,24168411,92998591,42692881,37659250,77413012,79191827,35456853,6808825,63372756,33201905,26063929,43376279,92692283,72732205,84187166,81774825,13173644,24591705,70727211,3487592,75052463,99549373,22997281,89419466,71300104,64602895,37957788,8696647,37675718,1250437,45667668,74743862,30653863,72495719,67031644,50567636,64098930,24915585,17764950,3183975,20836893,99524975,14947650,35512853,17385531,31491938,86301513,71522968,70036438,2208785,82726008,36685023,95726235,9603598,16099750,6221471,32159704,65338021,57658654,79922758,52613508,17539625,15148031,10597197,10358899,34432810,59109400,48658605,15948937,72274002,11365791,92398073,68102437,27325428,42237907,15535065,36845587,29959549,93359396,23432750,321665,25636669,45428665,75777973,61859143,94539824,24058273,23740167,66116458,77694700,69136837,55247455,36780454,96852321,98130363,19101477,21993752,73222868,41092102,74110882,97561745,91957544,8634541,77300457,26998766,8651647,82339363,8733068,62740044,23194618,99021067,52060076,98462867,54014062,8129978,86543538,49236559,3880712,87160386,39986008,66319530,20002147,33699435,67227442,13348726,86001008,85023028,62803858,68939068,44842615,1197320,73786944,4798568,20568363,2204165,42073124,79738755,176257,19486173,55189057,23848565,59981773,79880247,7066775,83155442,94711842,15536795,90013093,17146629,42947632,45794415,88698958,84293052,45617087,37891451,11581181,47298834,30998561,45848907,67084351,73021291,19898053,13862149,66250369,47361209,45790169,83651211,39553046,64848072,93562257,56955985,41245325,65271999,82178706,17068582,65880522,68875490,29834512,10366309,99125126,40534591,26493119,53666583,61728685,16567550,83533741,14045383,29635537,91240048,87720882,97011160,97057187,26292919,60581278,85711894,84904436,34698463,4434662,24314885,17976208,20642888,85571389,47090124,4091162,65074535,44664587,17016156,16684829,11543098,33935899,86798033,9575954,61141874,54868730,83747892,62357986,4064751,89046466,11923835,69697787,2717150,32250045,68128438,69786756,63152504,32151165,91802888,21533347,61928316,38365584,71920426,4095116,26734892,99224392,84349107,40197395,18411915,526217,26275734,20681921,6525948,27411561,95581843,61815223,96735716,80251430,84127901,62693428,15075176,89699445,61982238,47213183,77301523,52261574,8791066,9829782,61373987,48774913,17894977,23134715,17727650,75407347,21001913,58751351,61960400,24826575,84002370,38256849,90310261,10309525,58180653,44060493,46870723,82651278,96709982,22435353,99971982,84406788,45381876,58749,19457589,77187825,52293995,21919959,37435892,67124282,41092172,1936762,4199704,6819644,4515343,19939935,31733363,61712234,41380093,68041839,81805959,30771409,77787724,31126490,14220886,97379790,62490109,27041967,30163921,30463802,51466049,34946859,51464002,44847298,59405277,29065964,85117092,7646095,44784505,72358170,26392416,89804152,28734791,91938191,37192445,68702632,74452589,75128745,26863229,56153345,82427263,47887470,42782652,44348328,66045587,65038678,66885828,43357947,26397786,70367851,14349098,92541302,30366150,83133790,26507214,15902805,83269727,30811010,98648327,8115266,99604946,95251277,86460488,29932657,84684495,44177011,22721500,39373729,85695762,18504197,69605283,14723410,72793444,75229462,4204661,29516592,39201414,69579137,53648154,57248122,72725103,52204879,67451935,4508700,5822202,54517921,6497830,22766820,18806856,58208470,90004325,55669657,4978543,44481640,59329511,69848388,28796059,44627776,57393458,84840549,44915235,91990218,17857111,18001617,82897371,17081350,39801351,99729357,6505939,88904910,9058407,46540998,6038457,79942022,91831487,30694952,76315420,87598888,54232247,82886381,27187213,9599614,69255765,79322415,37224844,6521313,94516935,75820087,38022834,9257405,88251446,40984766,98948034,62051033,16054533,6157724,62552524,73109997,66667729,93057697,5482538,89217461,92867155,36139942,57359924,74724075,56484900,51426311,90158785,86118021,14363867,55850790,90457870,98653983,78884452,5073754,60176618,20645197,76825057,44844121,66428795,51315460,91141395,55470718,53802686,28787861,92530431,18833224,38510840,8128637,33336148,10264691,33565483,41442762,93270084,49597667,20867149,97281847,71083565,65851721,3088684,7182642,12303248,94090109,34667286,11757872,72614359,93790285,68000591,16405341,82979980,68110330,26664538,10453030,45075471,94595668,44479073,16387575,1788101,57961282,26744917,2607799,98943869,51149804,31727379,36135,22405120,55960386,45990383,34044787,78300864,22113133,36312813,27185644,77272628,3773993,60106217,17365188,2331773,30090481,89996536,824872,9259676,32058615,91255408,77898274,58224549,65081429,29401781,37560164,44473167,70782102,64055960,49882705,6111563,13470059,89499542,71965942,12664567,99515901,54263880,47893286,96906410,62762857,1569515,13819358,95010552,8055981,8729146,45049308,53888755,67513640,36933038,84166196,38341669,60393039,1239555,33061250,37280276,53393358,21397057 +9623492,75153252,19101477,39201414,14326617,3183975,63967300,38658347,33304202,22108665,6497830,70596786,68702632,35456853,67030811,6793819,24591705,30463802,69136837,12348118,74441448,73168565,10366309,44245960,89699445,34698428,68102437,76540481,20836893,39986008,17957593,10961421,71333116,2717150,59501487,67793644,78884452,31904591,49882705,71657078,9058407,10358899,99125126,8115266,97057187,3487592,62430984,5832946,60430369,30543215,20002147,6871053,51047803,569864,48892201,77072625,91664334,13231279,8099994,98371444,76170907,61373987,55143724,40152546,51472275,83302115,86821229,47361209,77312810,69355476,91802888,112651,3233084,22450468,59109400,70420215,91141395,93933709,96318094,38494874,85116755,98739783,70800879,3509435,99549373,8129978,43322743,49597667,37675718,11543098,20122224,47090124,27411561,70727211,5970607,38510840,96420429,37620363,93562257,35092039,62232211,83083131,415901,96193415,9603598,31733363,77620120,9175338,10649306,12024238,92692283,36396314,38645117,7300047,20885148,99658235,72738685,36812683,6819644,90272749,53084256,52293995,23194618,61982238,26275734,29994197,44348328,96709982,40984766,36933038,63628376,23569917,67451935,9860968,37183543,12571310,33628349,12416768,35996293,99604946,27665211,98130363,85242963,2891150,61859581,59910624,72732205,37166608,19939935,30569392,65851721,53393358,66885828,70541760,508198,73786944,33699435,26493119,44983451,73031054,21673260,61263068,60581278,86022504,87598888,29819940,73222868,66250369,55753905,526217,7502255,95957797,44550764,73235980,18833224,48360198,72725103,22129328,80316608,48774913,90061527,9188443,82339363,33061250,40197395,7646095,29510992,80555751,42237907,78300864,25842078,59197747,15535065,79657802,45990383,28796059,49641577,4515343,45049308,61741594,4668450,99297164,45790169,62452034,91281584,3088684,65047700,6521313,32426752,54663246,89419466,36580610,39847321,81677380,47213183,55247455,6153406,6986898,61859143,9259676,57803235,89214616,90004325,33553959,89811711,7011964,23848565,66663942,74137926,97940276,32159704,71469330,60955663,51315460,36685023,16380211,68644627,20642888,36753250,43357947,41092102,37224844,99690194,5111370,79880247,26292919,14626618,54232247,92398073,26063929,98948034,86543538,57020481,81898046,8373289,83210802,81172706,54199160,64087743,30787683,91727510,51464002,49236559,55960386,29959549,87710366,17727650,75543508,72019362,99333375,52261574,65038678,50188404,61623915,91831487,15536795,4787945,77284619,99021067,16019925,93053405,45237957,39171895,31491938,77301523,44844121,1250437,62803858,9886593,44479073,18001617,62428472,66611759,57248122,4099191,68110330,45996863,64098930,33935899,17058722,13468268,1431742,54868730,14349098,36808486,43045786,40781854,73124510,35192533,33123618,56153345,30163921,93057697,7955293,55189057,55770687,59371804,64848072,43376279,32250045,92998591,92692978,98462867,45794415,82178706,1569515,30090481,69579137,42782652,72357096,33797252,33895336,84840549,53648154,33565483,73109997,88698958,68939068,54058788,94090109,77413012,33201905,5267545,62936963,57359924,42967683,37560164,9860195,56531125,66322959,2917920,17764950,1204161,16567550,29466406,31161687,65017137,53666583,98648327,29029316,56473732,65074535,72373496,84684495,99515901,33237508,89046466,20867149,75229462,72495719,75135448,27041967,67031644,86301513,80251430,321665,20140249,78602717,29834512,3235882,12303248,88047921,70004753,95395112,44842615,20681921,22942635,22997281,24058273,6038457,57163802,61728685,66116458,51588015,8696647,92541302,94516935,24619760,824872,4978543,72274002,17068582,22721500,33249630,16684829,40865610,77787724,26664538,62552524,56515456,78909561,84187166,16445503,52204879,1022677,16097038,76671482,92071990,54606384,22435353,7423788,85023028,44915235,57658654,87720882,78785507,30694952,36189527,74357852,95010552,14723410,83133790,82897371,89078848,17365188,72777973,30764367,45075471,8634541,14220886,28851716,86001008,82052050,81853704,37501808,66137019,57240218,65081429,48893685,7182642,75777973,37435892,2208785,2204165,29932657,97783876,57393458,95581843,34667286,59177718,19272365,8055981,3633375,46851987,4119747,57961282,53842979,4095116,73021291,81855445,96735716,68204242,82726008,34236719,24915585,68875490,61271144,91957544,84166196,43152977,874791,89804152,99965001,62496012,33336148,77187825,27325428,59614746,42307449,99226875,168541,96852321,84127901,67124282,5482538,24826575,49328608,44473167,7919588,34698463,15948937,45919976,82779622,6808825,36468541,82427263,55669657,15064655,44481640,22879907,39801351,28734791,44177011,59329511,42644903,24168411,82327024,56424103,38365584,72278539,83533741,61815223,32161669,30218878,55850790,15015906,92554120,52060076,65338021,79922758,16387575,21993752,30395570,66832478,38008118,40027975,11161731,4798568,69255765,69786756,56484900,22766820,20486294,11923835,91938191,61960400,18806856,30998561,67084351,15075176,30366150,4064751,247198,3880712,38061439,66903004,40686254,13862149,14093520,26507214,65275241,44060493,68041839,10309525,84349107,37739481,59981773,30653863,26102057,53888755,4985896,90013093,99917599,90457870,3773993,47792865,10453030,9575954,94539824,4508700,67227442,84406788,36505482,34044787,99524975,65715134,34295794,80014588,18783702,23110625,9398733,17976208,28550822,76434144,32590267,42692881,4806458,18504197,68824981,13173644,76330843,22200378,9257405,68816413,68316156,26734892,26863229,77300457,90090964,37659250,41481685,58749,99861373,44889423,12664567,72614359,69641513,71083565,51715482,71300104,72358170,21533347,44846932,30139692,93790285,26114953,62490109,669105,88653118,79136082,46870723,95290172,54427233,19376156,40677414,20765474,17037369,79738755,73392814,97011160,92604458,87160386,16971929,34946859,31126490,34432810,45848907,14363867,10597197,18466635,36312813,45617087,92033260,50567636,75128745,16595436,45407418,98943869,23134715,34497327,42199455,97561745,94076128,7685448,76315420,42073124,8729146,1197320,83368048,95726235,86460488,17146629,66428795,6221471,47887470,51426311,8791066,17386996,24733232,64055960,83150534,92803766,63506281,44784505,62357986,74743862,50702367,82979980,93566986,1375023,53632373,26392416,3233569,36780454,65880522,43506672,6505939,31727379,22405120,26397786,74110882,37192445,84293052,98653983,85117092,13470059,33431960,13819358,44627776,18663507,78766358,17385531,5822202,21001913,46540998,15902805,4204661,81274566,94711842,89996536,88251446,11581181,29516592,62115552,22113133,83155442,89637706,38341669,19486173,14045383,75986488,4199704,78549759,80246713,26744917,2607799,77272628,14731700,70036438,89499542,99224392,1788101,7104732,27625735,48658605,17857111,61897582,5073754,88444207,18411915,97641116,45306070,63059024,65454636,15148031,50668599,749283,70367851,73617245,69848388,75820087,8733068,48675329,75407347,78589145,51507425,21397057,18131876,57241521,38022834,67281495,72793444,54014062,52613508,43933006,96726697,90158785,35512853,4069912,30891921,7229550,91990218,26998766,54762643,81805959,64157906,68694897,7517032,51149804,77898274,4434662,15163258,67474219,48395186,29188588,63152504,77694700,72238278,77377183,37891451,8807940,79806380,47893286,45428665,4091162,47738185,47708850,32699744,83948335,37957788,10264691,78218845,50007421,41092172,40781449,60106217,92215320,58751351,61712234,48673079,55470718,55602660,84904436,34493392,88904910,91255408,58208470,49271185,36845587,42947632,79322415,45667668,39553046,79942022,7687278,17016156,53802686,17081350,84002370,71965942,68128438,54987042,20568363,62740044,8651647,40534591,43501211,30771409,26139110,16405341,16054533,53547802,94360702,11757872,51466049,83269727,76703609,70782102,58180653,19891772,27185644,1239555,6111563,63015256,8913721,23740167,89217461,41442762,83651211,29401781,92353856,74724075,71920426,51590803,93359396,66667729,44847298,70372191,11365791,65271999,36135,79821897,9829782,48088883,90654836,19457589,5640302,54263880,69605283,6525948,18699206,92787493,93270084,83789391,66319530,21919959,92867155,64602895,32274392,17539625,40356877,48260151,85571389,76825057,16099750,21070110,71316369,17894977,8128637,81774825,93515664,62693428,95251277,50806615,6157724,61928316,97281847,90310261,97379790,86798033,3294781,45381876,14947650,62051033,85711894,63372756,56955985,23432750,59318837,82886381,60102965,25636669,66271566,99971982,58224549,86118021,26776922,74614639,69697787,7066775,79191827,45995325,16424199,28928175,29065964,68000591,47298834,82532312,86242799,54517921,59405277,60176618,92530431,24953498,66045587,78561158,74452589,44664587,2331773,55615885,41245325,71522968,29635537,76960303,60393039,85695762,99729357,32151165,20645197,13348726,36930650,94911072,82651278,21289531,83747892,61141874,24314885,56461322,37280276,28787861,38009615,19898053,94595668,62762857,41380093,49598724,96906410,27187213,9599614,30811010,75052463,176257,38256849,32058615,36139942,39373729,91240048,67513640,88130087,1936762 +73031054,81853704,39171895,65851721,26998766,26863229,92692283,10366309,98739783,23110625,54427233,54663246,38061439,6793819,4985896,22108665,29932657,16971929,61271144,40677414,38008118,77284619,15535065,56473732,33628349,87710366,12348118,44889423,68702632,17957593,2717150,30543215,55602660,24591705,58180653,75153252,60176618,70727211,83210802,36505482,23569917,3633375,98462867,52293995,17146629,56484900,84187166,63967300,3509435,77377183,37560164,15948937,96193415,38341669,32250045,25636669,54014062,8807940,28851716,53084256,29819940,8129978,44348328,59109400,77312810,77272628,52060076,17068582,20122224,94911072,61263068,66611759,74110882,63015256,20765474,29510992,77187825,37739481,25842078,81274566,88047921,11161731,93270084,29834512,45990383,16019925,48088883,26493119,29188588,83651211,55770687,91664334,16445503,22450468,49882705,72777973,81898046,15075176,66832478,9398733,57803235,13231279,54987042,89811711,72495719,97940276,18833224,61373987,29029316,30653863,9623492,72732205,35456853,78561158,2891150,16099750,7423788,48360198,35192533,68041839,55143724,89046466,43933006,32159704,79821897,43322743,34432810,29466406,92541302,3773993,4069912,74452589,36685023,14723410,17037369,74724075,99965001,66319530,98130363,4099191,62232211,92215320,55247455,95290172,55669657,75820087,17764950,37675718,4668450,61897582,72793444,86821229,44060493,72738685,64848072,42947632,8115266,91990218,91240048,36468541,68816413,38365584,64602895,79136082,22435353,79191827,43376279,69136837,17386996,19376156,68824981,10358899,24915585,16424199,78549759,24058273,26102057,66663942,99333375,1431742,31161687,57248122,69355476,59197747,14093520,85023028,19891772,29065964,81805959,7955293,64087743,22942635,61928316,9860968,51472275,38494874,67793644,12024238,35092039,23194618,69848388,49236559,47298834,85571389,87598888,37891451,73222868,98948034,508198,68875490,99917599,64157906,89419466,66250369,1022677,89214616,62430984,27665211,72274002,112651,33304202,3880712,68204242,14626618,62936963,87720882,61859143,62496012,55753905,10597197,168541,85242963,54762643,66903004,91281584,23848565,68644627,1788101,2204165,51588015,86022504,99658235,47090124,3088684,31727379,50567636,84406788,40152546,67084351,30694952,12303248,6505939,79880247,60955663,4787945,61815223,74137926,7646095,4119747,92554120,26776922,71300104,15015906,28787861,8696647,48892201,7300047,57359924,38658347,51715482,75229462,61141874,67030811,82979980,16387575,83789391,96906410,66137019,99524975,51149804,24733232,69697787,35512853,84002370,19457589,16097038,32590267,71316369,8733068,96726697,96852321,30366150,92398073,9603598,81172706,70596786,14349098,91938191,39373729,526217,63059024,33201905,10961421,45995325,83155442,8128637,17539625,17058722,38256849,44473167,95395112,62115552,33237508,13468268,44842615,40865610,86001008,86460488,24826575,16595436,19939935,74743862,50702367,40781449,95581843,6153406,47708850,17894977,99297164,13173644,50188404,47361209,96709982,49271185,91802888,95726235,40356877,48774913,3294781,3183975,2917920,63628376,415901,3233084,46870723,93790285,16054533,76540481,71333116,88444207,32161669,70004753,6521313,46851987,93933709,62452034,33431960,6525948,11581181,33336148,37183543,76703609,7687278,75407347,47792865,41245325,33797252,77898274,65038678,30569392,92071990,85116755,30771409,10309525,45075471,48673079,16380211,42692881,30163921,824872,38645117,3487592,54058788,6819644,65074535,27625735,22879907,6871053,74357852,61859581,90061527,99549373,9058407,99971982,96318094,44550764,36580610,62740044,73235980,41481685,72373496,36396314,81855445,4806458,94539824,30395570,11365791,669105,77301523,66116458,42782652,99729357,49641577,6808825,72278539,75986488,23432750,37501808,67451935,69786756,42307449,1204161,99224392,23740167,39201414,92033260,30090481,68102437,90158785,34667286,66322959,30787683,45428665,6986898,33123618,70800879,82886381,21289531,77413012,247198,99604946,50668599,40686254,61741594,31733363,21533347,12416768,48675329,20645197,65715134,33553959,16684829,3233569,87160386,51426311,44479073,78602717,73021291,72725103,28796059,93515664,8099994,62552524,34698428,59910624,4095116,80014588,49597667,9259676,31904591,37280276,40197395,60430369,37435892,84293052,75777973,18001617,88130087,97379790,23134715,61728685,5640302,92803766,62357986,59371804,20885148,45790169,27325428,874791,68939068,15536795,99226875,38510840,97641116,17976208,27041967,43506672,44847298,60102965,39986008,79942022,56531125,76825057,53632373,78589145,66045587,9886593,93053405,14947650,73392814,33895336,45848907,45381876,84840549,5111370,67227442,97561745,70372191,7517032,14326617,44177011,37957788,17016156,42644903,92998591,82052050,26275734,36312813,80246713,20867149,66428795,30218878,8913721,91255408,93057697,78300864,37166608,83368048,27185644,95010552,8791066,62693428,42237907,26664538,63506281,73168565,32426752,76960303,55189057,56461322,9188443,59405277,57240218,93359396,7685448,65047700,34236719,22129328,77072625,10649306,99515901,36753250,95957797,33249630,79922758,70036438,67124282,37620363,84684495,47213183,90457870,34493392,321665,79806380,90090964,40027975,83133790,55470718,89699445,55960386,2208785,69641513,51464002,29401781,90013093,9257405,76330843,83150534,80251430,18131876,57241521,21993752,94090109,17727650,18411915,26744917,56515456,98371444,76434144,74614639,51047803,40984766,9175338,36780454,51466049,39553046,18663507,53842979,15148031,38022834,66271566,65454636,78785507,36845587,19101477,80316608,99861373,47738185,34497327,83302115,44844121,4199704,20836893,11543098,42199455,81677380,43045786,27411561,75052463,17365188,82339363,75135448,66667729,30764367,86798033,36812683,77300457,6221471,22721500,34295794,65275241,44245960,45617087,73617245,60106217,33935899,83533741,71657078,45919976,61960400,99021067,36189527,5832946,62803858,65017137,26063929,7011964,30998561,83083131,82897371,45667668,73124510,65338021,4798568,33061250,61623915,92604458,56153345,97783876,60393039,65880522,89499542,18699206,32058615,34698463,1569515,20642888,44627776,68316156,13348726,68128438,9575954,44915235,41442762,45237957,57658654,9599614,92353856,60581278,89996536,44784505,94076128,20568363,64098930,36930650,59501487,79657802,37224844,76671482,45306070,53393358,44983451,1197320,75128745,43501211,83948335,34946859,77620120,91831487,70367851,1375023,18466635,68000591,8373289,16405341,92692978,53648154,37192445,78884452,85711894,29994197,45996863,5267545,30891921,22997281,4091162,89804152,73109997,75543508,5482538,32151165,30463802,19486173,7502255,82327024,10453030,96420429,12571310,42073124,96735716,15064655,95251277,49598724,70541760,68110330,73786944,89637706,93562257,74441448,41380093,36808486,52613508,24168411,5073754,88653118,39801351,80555751,12664567,1250437,56424103,51507425,21001913,49328608,91727510,18783702,43357947,59614746,86242799,65271999,4508700,70782102,55615885,26507214,52204879,31126490,6038457,17081350,82726008,28734791,42967683,92787493,85117092,30811010,6497830,18504197,82651278,53547802,47893286,48260151,41092172,52261574,86543538,7066775,58208470,76170907,59318837,76315420,48658605,67281495,7182642,26734892,97057187,9829782,54199160,54868730,4515343,50806615,71469330,63372756,90272749,16567550,749283,39847321,61982238,72357096,86118021,2607799,24953498,62051033,14731700,82532312,26139110,36139942,1239555,569864,82427263,43152977,54232247,91957544,3235882,54517921,11757872,4434662,35996293,22405120,9860195,33699435,90654836,99125126,26392416,33565483,58224549,28928175,56955985,79738755,24314885,93566986,22766820,20140249,40781854,63152504,17857111,13470059,20486294,70420215,17385531,45407418,51315460,94360702,22113133,78218845,7919588,69579137,84349107,4064751,48893685,14045383,26114953,27187213,31491938,7104732,36933038,90310261,8651647,30139692,71522968,88698958,57393458,71083565,54263880,28550822,4978543,53802686,78766358,45794415,22200378,67474219,18806856,69255765,57163802,34044787,26397786,26292919,58751351,13819358,50007421,77694700,21070110,59329511,82178706,6111563,83269727,44846932,91141395,72358170,94516935,19272365,84127901,62762857,89078848,57961282,79322415,88904910,88251446,58749,13862149,54606384,99690194,32699744,71920426,98943869,97281847,57020481,38009615,44664587,68694897,86301513,59177718,89217461,97011160,62490109,8055981,7229550,14220886,84904436,15902805,29959549,85695762,78909561,41092102,21397057,53666583,62428472,11923835,92530431,8729146,84166196,36135,29516592,5822202,21919959,37659250,92867155,72019362,46540998,51590803,59981773,44481640,15163258,69605283,176257,94595668,45049308,98653983,53888755,66885828,24619760,67513640,40534591,47887470,6157724,1936762,90004325,19898053,20002147,64055960,61712234,98648327,55850790,82779622,77787724,94711842,32274392,8634541,29635537,48395186,20681921,5970607,10264691,14363867,4204661,67031644,71965942,72614359,72238278,65081429,21673260,81774825,83747892,2331773 +33565483,33304202,92692978,44983451,62740044,874791,26292919,68939068,7300047,57803235,51047803,67793644,40677414,55247455,33336148,7229550,66045587,72614359,12348118,12024238,36189527,8115266,30998561,89214616,40356877,66832478,73021291,65338021,40152546,60430369,7423788,62430984,22200378,9886593,64602895,59197747,60106217,3487592,80014588,54663246,46870723,569864,96735716,76540481,94539824,65851721,63967300,112651,37183543,78589145,62452034,526217,13468268,54058788,68102437,37435892,54517921,57241521,65454636,62357986,3233569,77284619,1197320,99021067,9175338,37560164,26392416,92692283,37620363,99297164,16445503,68875490,63015256,22766820,79880247,34493392,3235882,70372191,55615885,56484900,31126490,36685023,18411915,7955293,47361209,27187213,73617245,64157906,66611759,96193415,44784505,9860195,83210802,26063929,3773993,66667729,97011160,18833224,1788101,44889423,81898046,87720882,15535065,70004753,71920426,29834512,38645117,72274002,45428665,98948034,20122224,71316369,83533741,5640302,45381876,92554120,89217461,54427233,22450468,87598888,35092039,72357096,34667286,11365791,14349098,11923835,1239555,94911072,65275241,41380093,3509435,98943869,4199704,29994197,73124510,42307449,8696647,39847321,38365584,85117092,60955663,31491938,14045383,89699445,43501211,55602660,62232211,2208785,83789391,83155442,13470059,75543508,81677380,99515901,23740167,92033260,47213183,20568363,58224549,69579137,99224392,79738755,45237957,2917920,89046466,6986898,83083131,53842979,1022677,97940276,22405120,8128637,50702367,70800879,23848565,29188588,81172706,9058407,30653863,81853704,57240218,39201414,21993752,34295794,13819358,78549759,38510840,38658347,76960303,31161687,65880522,16595436,61373987,7919588,72793444,17068582,36312813,72278539,84127901,62496012,57359924,9259676,40027975,22435353,90013093,98130363,6808825,27665211,88251446,99226875,66271566,44627776,86001008,9398733,21001913,53802686,43933006,16405341,67281495,46540998,65271999,15015906,168541,69697787,76671482,49597667,2717150,51464002,82779622,30787683,67451935,59501487,19939935,75153252,30771409,20002147,33895336,93933709,21289531,99549373,89078848,75128745,9860968,29029316,61928316,20642888,36780454,30764367,13348726,15148031,1204161,81855445,86460488,4069912,77301523,4064751,82532312,43376279,8807940,95010552,36505482,29401781,21533347,93270084,61741594,62051033,9188443,88904910,49641577,68316156,47298834,20867149,55143724,95290172,72725103,88653118,56153345,30463802,86301513,26664538,18783702,16097038,17764950,19376156,9575954,43045786,72777973,61263068,83948335,3233084,22997281,73392814,26114953,25842078,34698463,14326617,34044787,68816413,29959549,66322959,36753250,32590267,9603598,36808486,17365188,36580610,321665,26493119,2204165,29819940,38022834,62490109,92541302,52261574,57961282,55753905,74357852,27325428,54232247,78766358,20486294,24168411,77620120,6505939,44550764,31904591,48658605,44060493,61859581,65074535,60176618,99861373,98371444,669105,4668450,33628349,8129978,53632373,52613508,37739481,91240048,61271144,18663507,14220886,5832946,20140249,99125126,55470718,59177718,98739783,11581181,38009615,9257405,99965001,99917599,45667668,85023028,30543215,97783876,58749,50806615,63059024,40534591,70596786,5970607,82726008,98648327,29510992,56461322,8099994,82979980,10366309,53393358,56515456,55960386,68702632,64848072,72238278,77300457,17037369,14626618,37659250,1569515,45995325,85242963,8373289,68128438,66885828,77312810,53648154,94516935,749283,68824981,22108665,5267545,10453030,37501808,78602717,45075471,91802888,19898053,74137926,75777973,52204879,47887470,51149804,96726697,45049308,54762643,58208470,54014062,87160386,45617087,4798568,55189057,94595668,15948937,76170907,56955985,20765474,40686254,44847298,24915585,56531125,1431742,96906410,72738685,75986488,48088883,27625735,16019925,74724075,77413012,97379790,62936963,82651278,53084256,70420215,75229462,83302115,57248122,28796059,61982238,53666583,89637706,26734892,15064655,16099750,24826575,6521313,50188404,75820087,64087743,49882705,6793819,81274566,93790285,64098930,65081429,65017137,17976208,38256849,92867155,66116458,82427263,3880712,34497327,99690194,28851716,65715134,17894977,51426311,65047700,74452589,2891150,28928175,67124282,61815223,34236719,71333116,69255765,24591705,47090124,1250437,66663942,6038457,26275734,14731700,4204661,38341669,31733363,66903004,44846932,44915235,3088684,85571389,56473732,5111370,77187825,95726235,19101477,13862149,94076128,35192533,90457870,52060076,38061439,24058273,90158785,86798033,21673260,39171895,63372756,38494874,19486173,83651211,27411561,33431960,54868730,17081350,43506672,88130087,247198,44842615,35512853,23569917,48360198,88444207,40781854,18806856,18131876,78218845,74743862,85116755,28787861,68694897,37675718,6221471,44348328,87710366,40197395,32161669,77694700,97561745,84840549,99971982,69848388,8729146,44844121,508198,4978543,10358899,41481685,53547802,51466049,63628376,91727510,42782652,28550822,36933038,10961421,30366150,77272628,4787945,51315460,33201905,15536795,91831487,91664334,59318837,36135,16684829,93057697,96420429,55770687,55669657,66250369,22129328,73222868,17727650,96852321,21919959,78300864,29466406,54199160,73786944,11543098,42237907,41442762,50668599,92353856,7646095,7104732,45790169,26507214,29516592,84904436,57163802,84187166,70541760,6497830,12571310,92530431,76703609,8634541,54987042,76315420,6819644,51715482,75135448,19457589,95395112,17957593,68000591,3294781,77072625,22942635,91255408,40865610,49271185,44481640,91141395,55850790,33237508,24619760,11161731,90004325,45848907,33797252,99658235,78909561,18699206,70782102,65038678,30891921,17539625,80555751,16971929,29635537,18504197,43322743,73235980,85695762,72495719,9623492,96318094,67030811,39801351,16387575,98462867,6153406,42073124,59614746,14947650,60102965,90272749,23134715,79136082,4091162,37166608,32699744,32159704,23194618,14093520,5822202,7011964,84406788,86821229,92398073,78561158,34698428,7687278,33123618,20645197,4099191,93562257,45794415,66137019,10264691,84684495,29932657,92215320,48395186,69355476,47708850,20885148,13231279,18466635,71300104,45996863,61728685,48675329,6525948,51472275,69605283,62803858,66428795,89499542,38008118,82052050,71657078,44177011,17146629,4508700,53888755,57020481,33699435,91990218,30139692,26744917,10597197,30569392,68110330,16054533,46851987,86242799,73168565,93515664,61897582,88047921,61623915,98653983,97057187,89996536,71469330,12303248,15163258,92998591,30694952,60581278,15075176,77787724,82339363,3183975,52293995,84349107,40781449,61141874,93566986,69641513,30163921,39553046,89811711,48673079,44473167,69786756,92787493,5482538,29065964,41245325,45990383,15902805,36812683,4119747,35996293,58180653,44479073,97281847,8651647,17385531,47738185,50567636,81805959,76330843,24953498,16567550,7502255,76434144,78785507,2607799,7066775,40984766,88698958,62552524,68041839,94090109,42199455,30395570,90310261,82897371,19272365,4434662,80316608,42967683,67474219,24314885,85711894,83747892,48774913,35456853,61859143,79922758,47893286,20836893,59405277,68644627,95581843,73031054,26102057,63152504,95251277,39986008,31727379,11757872,80246713,19891772,10309525,89419466,26139110,82886381,7685448,63506281,84293052,45407418,27185644,57658654,415901,7182642,78884452,21070110,79806380,50007421,93359396,36930650,26998766,56424103,59981773,22879907,61712234,33935899,66319530,824872,4985896,2331773,86022504,51507425,70727211,45306070,81774825,5073754,58751351,61960400,34946859,59910624,48260151,54606384,21397057,26863229,71965942,90090964,91281584,43357947,99729357,4515343,74110882,59371804,62115552,90061527,54263880,83368048,64055960,17386996,92604458,14363867,75052463,59109400,93053405,26397786,74614639,69136837,10649306,36396314,51590803,41092102,41092172,3633375,49598724,48892201,83133790,8791066,62693428,70367851,82327024,33249630,4095116,99524975,22721500,36468541,76825057,44245960,86543538,30811010,32274392,71083565,24733232,73109997,96709982,72358170,72019362,7517032,23432750,28734791,4806458,95957797,36139942,77377183,90654836,14723410,47792865,23110625,18001617,79191827,17857111,37280276,12416768,57393458,79657802,89804152,42644903,60393039,42947632,70036438,6871053,32058615,32151165,37957788,9829782,72732205,8055981,51588015,48893685,33061250,26776922,67084351,77898274,17016156,62762857,30218878,27041967,34432810,79821897,92071990,94711842,1936762,22113133,44664587,74441448,1375023,92803766,37891451,67031644,37192445,30090481,49328608,91957544,32250045,72373496,80251430,6157724,13173644,49236559,62428472,45919976,83150534,39373729,8733068,16380211,37224844,6111563,9599614,67513640,99604946,68204242,17058722,20681921,82178706,42692881,94360702,16424199,33553959,71522968,43152977,84166196,36845587,99333375,84002370,83269727,59329511,91938191,79942022,12664567,8913721,25636669,79322415,67227442,32426752,97641116,75407347,86118021,176257 +54663246,63967300,62452034,34295794,75543508,6525948,19457589,11581181,99965001,60430369,66045587,44784505,99524975,98948034,40781449,89078848,30764367,62740044,6808825,26507214,87720882,9398733,43376279,17764950,33797252,168541,48675329,16019925,14947650,73392814,5111370,75777973,9829782,26392416,14093520,35092039,78549759,90158785,90457870,97011160,2717150,98130363,81805959,67281495,67793644,57803235,38645117,88047921,98462867,95726235,31904591,32250045,34667286,6793819,8807940,28550822,70596786,22721500,80316608,77413012,14220886,81855445,18466635,44481640,526217,93270084,48260151,16405341,99226875,69355476,31161687,18663507,4064751,19486173,96735716,44842615,50702367,16445503,10366309,89214616,64087743,71333116,48893685,73031054,74110882,39373729,2891150,45428665,22129328,10358899,37659250,33201905,36808486,30998561,22942635,7011964,81853704,18504197,26664538,17365188,44889423,38365584,72793444,9257405,79880247,70727211,35456853,40152546,37620363,60102965,27665211,66667729,8696647,60955663,32161669,93515664,76540481,31491938,26863229,14626618,92215320,30811010,5640302,28787861,18833224,51426311,20002147,37891451,6153406,80014588,98371444,65454636,13468268,37183543,30891921,5832946,61263068,57241521,96193415,26292919,43501211,63628376,66903004,85117092,73235980,34698463,53648154,112651,15148031,93933709,54427233,66885828,61373987,45995325,67474219,40197395,62428472,39801351,45990383,22200378,91831487,84684495,68128438,874791,66250369,73124510,58749,61712234,72274002,92554120,49328608,78884452,91240048,72373496,508198,70372191,28734791,30694952,247198,47361209,94076128,65038678,89499542,36780454,99658235,78561158,48360198,9886593,16971929,82979980,93562257,64848072,2208785,84840549,4099191,72278539,56515456,57240218,749283,4204661,29819940,85116755,30163921,33249630,69255765,40686254,51047803,47738185,44060493,99297164,76703609,2607799,33237508,21001913,40677414,98653983,86242799,11923835,83150534,75229462,81172706,11365791,79942022,70004753,65074535,62936963,47213183,92692283,41481685,47090124,55143724,62490109,44245960,78589145,81774825,3880712,19939935,92530431,53842979,84904436,2331773,77694700,18699206,68824981,2917920,85571389,78766358,83533741,51507425,15536795,24733232,99515901,6221471,68102437,16054533,70036438,12571310,32699744,38061439,35192533,7646095,81677380,92033260,48774913,45919976,92398073,36845587,87598888,79191827,62051033,42307449,89419466,83133790,47887470,53666583,94911072,19101477,69605283,3773993,9058407,47298834,36812683,94360702,29834512,83302115,7300047,74614639,93053405,63015256,54014062,15902805,42947632,88904910,91957544,8129978,77300457,68702632,34236719,74724075,88698958,31126490,55247455,98648327,55615885,7955293,83155442,51590803,13173644,75153252,10309525,72614359,59177718,33123618,91990218,9175338,37192445,70782102,49598724,22879907,49641577,38022834,56153345,7423788,77377183,3088684,1022677,21673260,16380211,54517921,80555751,9860195,64055960,36505482,55189057,17068582,16567550,14349098,37501808,94516935,91664334,69579137,43933006,40356877,99021067,38658347,81898046,72019362,30771409,569864,6521313,75820087,44479073,47792865,71965942,8099994,85242963,63059024,44983451,29510992,66832478,18783702,84187166,45667668,64098930,53632373,27041967,39986008,92803766,7182642,13862149,21993752,24619760,3294781,41380093,65081429,11161731,99971982,45790169,9623492,77284619,24058273,59614746,55960386,20645197,52293995,45049308,10649306,54762643,62357986,36685023,65338021,92692978,669105,40781854,24168411,17957593,33565483,36753250,45794415,22108665,6497830,86821229,20568363,70420215,43152977,30395570,40027975,91727510,26275734,59318837,61623915,13231279,55753905,61741594,29029316,89637706,28796059,55770687,68939068,28851716,29466406,58751351,4668450,824872,62430984,91141395,95957797,8634541,23740167,23432750,22766820,12416768,19376156,3509435,89811711,5267545,73786944,66611759,18131876,59109400,24591705,51149804,56473732,18411915,35996293,4985896,70367851,53888755,9188443,69697787,56531125,54058788,51464002,36312813,96906410,77272628,76825057,84166196,26998766,10264691,77620120,29188588,17857111,29959549,24915585,20140249,22450468,96726697,58180653,26776922,54987042,19272365,36396314,58208470,54199160,86798033,55470718,33628349,65275241,21289531,96709982,88251446,97379790,86022504,15075176,17976208,73168565,23569917,77187825,23194618,79806380,33304202,20642888,68644627,61928316,56955985,95290172,98739783,54868730,50188404,77312810,16099750,20486294,27187213,14045383,30090481,94595668,8913721,99333375,49271185,79657802,10453030,66322959,26734892,84293052,86001008,8791066,68000591,56484900,92604458,99604946,45075471,12024238,79738755,3233084,72357096,44844121,39847321,89699445,76330843,5073754,62552524,61815223,14731700,74137926,82726008,43357947,82532312,1569515,20765474,83368048,24826575,13470059,29635537,45617087,61982238,23110625,75135448,87710366,2204165,79136082,8115266,71300104,92353856,12348118,31727379,3233569,80246713,73222868,4069912,72495719,95395112,20122224,68316156,56424103,47708850,29994197,30218878,99690194,90272749,70800879,72725103,22405120,82427263,20836893,15064655,66137019,55669657,77301523,78602717,30366150,57359924,72738685,17727650,76671482,73021291,48892201,1788101,72238278,65880522,44550764,71522968,17894977,57658654,9575954,5970607,26063929,32159704,43506672,92787493,97641116,84127901,33895336,51466049,42644903,4515343,98943869,30569392,36930650,33935899,51588015,30653863,37435892,4787945,39553046,67451935,21533347,86543538,89046466,45407418,67513640,71469330,92998591,20885148,52613508,34497327,53393358,85023028,42782652,1204161,83789391,71083565,15948937,64602895,49236559,321665,29516592,61960400,79922758,31733363,45381876,1375023,34698428,38008118,55602660,14363867,40984766,30543215,8373289,81274566,61271144,11543098,54232247,32151165,23848565,42967683,37560164,90310261,59501487,45306070,38009615,62803858,27185644,27325428,415901,16595436,83651211,17539625,6986898,53802686,69641513,66271566,50806615,91802888,93566986,4508700,16684829,83210802,57163802,72777973,66428795,75407347,25842078,1250437,24953498,13819358,32590267,52204879,74743862,99917599,29932657,42692881,70541760,32274392,68694897,49882705,61728685,66319530,82886381,90090964,99125126,62693428,38341669,1197320,74357852,7919588,26102057,95010552,52261574,68875490,6505939,41245325,80251430,42237907,32058615,26114953,37166608,28928175,84406788,17081350,46870723,15015906,76170907,51715482,48673079,33336148,67084351,45996863,6157724,10597197,96852321,34493392,34946859,21070110,17385531,69136837,33553959,4798568,73617245,97057187,94090109,3487592,40534591,7502255,13348726,7685448,18001617,57961282,7229550,63506281,16097038,85711894,77787724,61859581,59981773,65851721,83269727,71657078,69848388,48395186,33699435,32426752,59329511,77898274,51315460,54606384,65271999,53547802,87160386,8729146,55850790,1936762,7104732,36468541,50567636,6871053,8055981,83948335,75052463,78300864,62496012,71316369,67030811,66116458,68816413,68041839,37739481,43322743,48088883,82327024,21919959,26139110,43045786,60393039,19891772,67031644,9259676,99549373,76434144,1431742,68204242,97940276,65047700,97281847,17037369,66663942,61897582,60106217,39201414,85695762,75128745,99861373,3235882,90061527,50007421,46851987,16424199,42073124,15535065,14723410,4095116,45848907,94539824,76960303,50668599,51472275,35512853,36135,62762857,91938191,52060076,37675718,83083131,22997281,77072625,40865610,60176618,60581278,59197747,97783876,46540998,45237957,36189527,4806458,88444207,56461322,3633375,82897371,53084256,90013093,74452589,30463802,27625735,4091162,44627776,82779622,38510840,29401781,10961421,89217461,82178706,29065964,59371804,59910624,75986488,27411561,8651647,34432810,176257,5482538,90654836,69786756,22435353,39171895,30787683,67227442,38494874,37957788,15163258,96420429,44473167,96318094,4199704,26744917,76315420,41092172,65017137,14326617,33431960,78218845,20681921,99729357,86460488,93057697,74441448,36580610,71920426,78785507,44846932,26493119,4119747,44177011,57393458,25636669,48658605,61141874,9603598,4434662,8128637,6819644,58224549,33061250,86301513,44664587,16387575,5822202,62115552,44348328,92867155,6038457,88653118,62232211,41092102,86118021,57248122,9860968,42199455,4978543,34044787,12664567,79821897,1239555,73109997,94711842,82339363,90004325,68110330,23134715,7517032,63372756,82651278,21397057,54263880,7687278,93359396,20867149,91281584,82052050,72358170,3183975,7066775,37280276,61859143,36139942,8733068,91255408,72732205,95581843,41442762,63152504,12303248,49597667,89804152,84349107,78909561,64157906,99224392,59405277,44915235,9599614,18806856,97561745,22113133,65715134,37224844,83747892,6111563,26397786,95251277,47893286,17058722,84002370,88130087,79322415,44847298,67124282,17386996,30139692,57020481,89996536,24314885,92541302,11757872,93790285,36933038,17016156,17146629,38256849,19898053,92071990 +62496012,74357852,19891772,29819940,95010552,83210802,88047921,49641577,34493392,50702367,92787493,21533347,66832478,45848907,36753250,70541760,17764950,40356877,16099750,37620363,62803858,62693428,44481640,12348118,93515664,13468268,3233084,45990383,63967300,66137019,77284619,87160386,95957797,61928316,41245325,65271999,96193415,2917920,18783702,16019925,68000591,55247455,68204242,71920426,63152504,64848072,58749,6808825,64055960,71522968,18699206,88653118,18001617,8129978,45407418,86022504,37739481,35996293,9623492,71333116,17727650,89078848,84840549,8115266,62428472,86543538,9257405,75153252,1788101,37166608,97940276,41092102,57241521,26114953,83789391,17539625,21993752,44784505,66045587,61859581,83368048,44060493,36780454,28550822,92803766,77413012,9603598,55143724,51047803,96726697,94090109,88904910,3880712,42782652,6221471,74452589,99524975,30787683,53547802,39801351,99224392,92554120,50668599,91255408,91240048,8807940,4204661,27625735,32590267,44889423,53842979,45996863,79821897,37957788,57163802,47893286,22108665,73124510,93933709,26998766,71469330,7687278,36685023,73031054,85117092,83651211,66885828,98371444,54987042,52261574,16387575,45428665,17857111,34236719,64602895,5111370,749283,67451935,7502255,64087743,6505939,53802686,6793819,55189057,93270084,39847321,78300864,69641513,40686254,24733232,6986898,27325428,61815223,79136082,36580610,26863229,32159704,65275241,33237508,81898046,68816413,30764367,50188404,45667668,70800879,98943869,33628349,42307449,34295794,4119747,99515901,62357986,14947650,69579137,59197747,50007421,51472275,61859143,37192445,38494874,93053405,78218845,68128438,67124282,67793644,3294781,79657802,65047700,71316369,51426311,8128637,81853704,75135448,29834512,28796059,60955663,47738185,40027975,62936963,20867149,43501211,38061439,23569917,72793444,82726008,7182642,8696647,54762643,44177011,90090964,97783876,83533741,84127901,97561745,67030811,53084256,4787945,34044787,63506281,43376279,78602717,62452034,61141874,55753905,52293995,54263880,95581843,60102965,79942022,59177718,68316156,9575954,15015906,76671482,32161669,76434144,28928175,10358899,78766358,14220886,29065964,61271144,10961421,48673079,19898053,94539824,99861373,4099191,70727211,61897582,33336148,22450468,9860195,66611759,36808486,20642888,91664334,70004753,17037369,73392814,84904436,9259676,44245960,96852321,23194618,36312813,19486173,247198,76960303,58751351,38022834,89699445,90272749,95395112,45995325,65880522,45919976,74614639,45075471,29188588,68824981,57803235,63372756,11365791,68041839,82779622,34432810,669105,27665211,99604946,58224549,83948335,4668450,39986008,30366150,99297164,22942635,4064751,43933006,3487592,76315420,41442762,83155442,48675329,46540998,75777973,26392416,95290172,33935899,99917599,3233569,92692978,6521313,31126490,47361209,15075176,20568363,93057697,86001008,40197395,72495719,26664538,59614746,18466635,20486294,9829782,73617245,33201905,70036438,14045383,16097038,76540481,26507214,63059024,47090124,11543098,19272365,55770687,98948034,23110625,5267545,66322959,4434662,9188443,11581181,72274002,21070110,20002147,37891451,66271566,23848565,90457870,36505482,19457589,69697787,874791,80555751,38365584,30543215,73168565,89046466,27187213,7423788,33304202,75543508,48658605,508198,91727510,66663942,62740044,55615885,10366309,84684495,48088883,48260151,40865610,70596786,89996536,92867155,42947632,47708850,18663507,54427233,65715134,33895336,3773993,78589145,83083131,15535065,24619760,7955293,1431742,66667729,68644627,38645117,85695762,74110882,87598888,81172706,5073754,13348726,56461322,78561158,88698958,51315460,62051033,22879907,98462867,23740167,68939068,70372191,2717150,96709982,82979980,66116458,49236559,80246713,65338021,82897371,44844121,73235980,39171895,27411561,48892201,53393358,56531125,92692283,69355476,84293052,44842615,92998591,72358170,56473732,90013093,5832946,45237957,80316608,79738755,2204165,10597197,4798568,83133790,89419466,95726235,11161731,15163258,56484900,9599614,55669657,92071990,72614359,40152546,22435353,26063929,54014062,5640302,73222868,66428795,42073124,94711842,53648154,74441448,44479073,526217,36189527,28734791,99965001,25636669,42644903,61741594,74743862,47792865,82651278,59910624,9886593,38658347,59109400,56424103,12024238,92604458,79191827,79806380,43045786,53888755,90310261,168541,14731700,40984766,92033260,76330843,83150534,18806856,39201414,93562257,30139692,62430984,86301513,99729357,98653983,81677380,16971929,57359924,81274566,7104732,94911072,29994197,58180653,24915585,82427263,66250369,60581278,18504197,98739783,91831487,4199704,24058273,69605283,3183975,29029316,71657078,24168411,33797252,7300047,8913721,46851987,14093520,30771409,57020481,16567550,34946859,59318837,86798033,93359396,16405341,36139942,62490109,77377183,99125126,72357096,44664587,30163921,81805959,45790169,85571389,77072625,46870723,36930650,55470718,36812683,77312810,65081429,92541302,72019362,44550764,37560164,20140249,51466049,20885148,5482538,34698463,40677414,61728685,69848388,64157906,94360702,87720882,33249630,30694952,77694700,69786756,99658235,77272628,75407347,30891921,77620120,43322743,55960386,1375023,57248122,14723410,64098930,61982238,86821229,9398733,43357947,32250045,35512853,17068582,91141395,35192533,81855445,99549373,59501487,6525948,60106217,1569515,54606384,55602660,1204161,31491938,20765474,112651,47213183,35092039,56153345,72238278,67227442,16595436,8634541,53666583,16380211,3235882,52060076,2891150,415901,67474219,29932657,21289531,45617087,89214616,2607799,96906410,97057187,31161687,93790285,85116755,62762857,7011964,19376156,32058615,33061250,50567636,16054533,21919959,40534591,36468541,34497327,61263068,82339363,70367851,30218878,92398073,13231279,30653863,3633375,31727379,3088684,37501808,92353856,17957593,24314885,37224844,176257,18833224,62232211,53632373,44983451,26275734,72777973,26493119,77301523,32151165,17081350,4515343,54199160,12571310,74724075,82532312,23134715,96318094,65454636,73786944,18131876,15536795,17385531,60430369,8099994,72373496,82052050,54663246,71965942,37435892,57658654,49271185,16445503,61623915,69255765,39553046,50806615,4069912,57240218,18411915,36396314,21673260,31904591,11923835,76170907,30998561,28851716,824872,7229550,66903004,13470059,37675718,78785507,26139110,29401781,89811711,25842078,79922758,38341669,36933038,86242799,13173644,84349107,17058722,51464002,38009615,37183543,22200378,89637706,59371804,87710366,29959549,63015256,30569392,79880247,14363867,47298834,82886381,57961282,65038678,88444207,52613508,3509435,81774825,24953498,54517921,24826575,6153406,56955985,91802888,66319530,36135,54232247,44915235,76703609,35456853,85711894,85023028,83302115,41092172,91281584,1022677,89804152,49598724,96420429,51507425,62552524,22405120,90004325,34698428,96735716,60176618,72725103,99690194,76825057,78909561,7517032,88130087,8373289,15148031,70420215,57393458,80251430,10309525,39373729,29510992,77187825,93566986,56515456,75052463,19939935,15064655,34667286,12416768,7066775,28787861,9058407,67513640,31733363,14349098,37659250,71300104,14626618,85242963,40781854,80014588,51715482,67281495,91938191,33123618,65017137,32699744,68875490,26734892,26292919,19101477,98130363,51588015,38008118,26776922,30090481,23432750,22129328,29466406,10649306,42199455,90061527,8791066,4091162,30811010,9860968,24591705,48360198,82178706,51149804,4508700,51590803,62115552,8733068,30395570,48893685,89217461,42967683,1936762,68102437,22766820,52204879,58208470,15948937,82327024,60393039,4095116,49882705,65851721,84187166,47887470,90158785,97281847,48774913,44473167,92215320,92530431,75229462,44846932,7646095,6497830,6157724,99971982,71083565,17365188,26397786,30463802,55850790,8651647,97379790,91990218,45049308,45306070,15902805,68110330,54868730,12303248,99333375,74137926,2331773,32426752,43506672,88251446,7685448,86460488,33553959,6038457,61960400,72278539,75986488,94076128,6871053,40781449,98648327,72732205,29635537,45794415,89499542,5970607,59329511,49328608,63628376,14326617,33565483,99226875,33699435,97011160,91957544,4806458,6819644,10453030,20645197,7919588,67031644,20122224,41481685,26744917,27185644,99021067,54058788,42692881,65074535,38256849,1197320,59405277,68702632,77787724,73109997,9175338,94595668,61373987,42237907,77898274,22997281,26102057,17976208,86118021,83269727,90654836,20836893,16424199,59981773,77300457,10264691,68694897,2208785,20681921,4985896,43152977,72738685,49597667,70782102,37280276,21001913,17016156,1250437,569864,44627776,41380093,75128745,321665,44348328,73021291,13862149,17146629,44847298,78549759,36845587,97641116,95251277,67084351,17894977,38510840,84166196,75820087,78884452,33431960,79322415,45381876,8729146,22113133,84002370,17386996,69136837,22721500,83747892,48395186,11757872,16684829,1239555,21397057,61712234,27041967,94516935,6111563,5822202,4978543,84406788,8055981,13819358,32274392,29516592,12664567 +45381876,96735716,66322959,3509435,81853704,57803235,64157906,33895336,35192533,29188588,23569917,69355476,63506281,56531125,65271999,6808825,44915235,26392416,52204879,62693428,415901,65275241,29029316,86001008,168541,48088883,66250369,70004753,95010552,35092039,99965001,67030811,48675329,8807940,9188443,70372191,10309525,1022677,66319530,7423788,66271566,63372756,22997281,80246713,17957593,53842979,73617245,62936963,37739481,28928175,65715134,24826575,84904436,9257405,66045587,22129328,62430984,41092102,54232247,569864,34295794,11161731,76170907,99690194,29994197,30366150,63967300,31126490,91240048,44983451,20002147,4668450,26863229,7646095,15535065,66667729,33304202,61928316,51047803,62232211,4515343,97011160,16097038,8129978,96709982,83651211,18663507,10264691,69641513,38494874,59981773,72274002,1431742,13231279,27411561,60102965,53666583,50806615,71333116,9886593,40197395,50188404,52613508,66832478,68816413,75135448,84127901,61263068,61815223,72278539,55189057,65338021,56424103,67474219,92554120,93933709,75986488,25842078,54517921,9575954,66611759,86301513,30787683,55247455,84166196,71920426,62740044,85116755,9860195,73124510,42307449,176257,19272365,47738185,61982238,83210802,62051033,39847321,97561745,62452034,43501211,63015256,71316369,24168411,36780454,33565483,99549373,88130087,89699445,92692283,43045786,7182642,112651,76330843,16054533,19376156,29819940,7955293,26114953,34698463,44479073,31727379,41380093,36505482,60430369,34493392,32426752,24619760,44348328,54263880,97940276,19939935,56473732,77377183,96193415,68875490,14731700,95395112,13348726,12348118,48892201,3773993,70800879,65454636,8696647,20642888,99226875,72793444,51466049,6221471,51464002,30764367,44177011,17365188,83948335,73031054,69255765,98648327,93790285,3233569,20765474,36930650,16099750,2204165,65017137,29466406,47887470,66137019,66116458,86242799,64087743,41481685,68102437,22879907,60106217,6038457,36685023,7685448,78589145,67084351,1788101,64602895,1569515,9623492,98739783,45075471,8115266,749283,44889423,33201905,30569392,52060076,77694700,30998561,67031644,38645117,2208785,17727650,15075176,49597667,5111370,38365584,76703609,67451935,59910624,16595436,874791,86022504,35512853,61373987,26507214,76434144,73021291,89214616,26275734,61623915,99515901,15902805,36396314,59177718,50007421,12416768,75229462,99729357,99125126,17037369,86460488,33123618,37183543,47090124,18783702,58224549,43376279,3294781,87710366,36753250,72373496,39553046,17081350,17386996,76540481,3633375,81172706,68128438,46540998,32161669,21533347,61141874,89046466,526217,45848907,53802686,51472275,59371804,78766358,3088684,55470718,11923835,99604946,82178706,90272749,57240218,22435353,40781449,77284619,98130363,6505939,33249630,33797252,30090481,77312810,37435892,90013093,26664538,15163258,18833224,4508700,17068582,98371444,39171895,92033260,54762643,3880712,669105,23194618,91802888,71657078,26998766,82979980,6793819,57248122,18466635,42967683,17539625,51507425,59197747,78785507,52293995,37620363,56955985,91664334,77301523,36189527,20645197,32151165,83150534,9259676,79136082,72725103,94911072,13862149,45617087,83302115,6986898,82726008,13173644,89637706,22200378,45306070,61741594,60955663,30139692,37957788,18806856,96726697,8651647,98943869,48360198,68694897,508198,68939068,77072625,824872,42237907,49641577,40677414,34497327,7300047,2717150,16019925,94360702,24733232,35456853,84187166,92541302,68644627,54663246,83083131,21993752,10366309,37501808,54014062,44842615,33237508,54427233,38658347,4119747,83155442,63152504,72358170,19101477,70036438,55850790,5970607,78602717,49598724,53547802,45996863,56153345,55753905,72777973,7919588,42692881,78909561,19457589,10453030,83789391,14947650,90654836,92787493,99861373,68041839,30653863,33628349,48893685,92867155,69697787,84349107,76671482,7229550,16380211,92353856,94539824,39801351,54058788,44473167,19486173,62496012,44847298,38061439,22942635,51715482,89419466,57241521,20681921,24591705,99224392,1250437,68204242,29834512,28550822,27325428,77187825,17894977,55143724,61271144,6157724,64098930,9829782,3233084,42199455,66663942,1239555,14220886,7687278,33061250,17146629,41245325,52261574,79880247,90090964,38341669,14093520,51588015,57961282,85571389,321665,75153252,40356877,77413012,51426311,40865610,10358899,70727211,76960303,98948034,50668599,36933038,37675718,4099191,13470059,44627776,48260151,99971982,73168565,75543508,29635537,80555751,22405120,95726235,34044787,83269727,38022834,32699744,35996293,1197320,8373289,59318837,81677380,64055960,47298834,38009615,9599614,22108665,43506672,9398733,57393458,44784505,58749,16971929,36808486,89811711,10597197,65851721,40534591,73222868,26102057,69786756,86798033,27665211,69605283,78561158,92692978,5482538,97379790,68000591,30771409,70420215,49328608,36580610,58180653,77272628,4806458,61859581,64848072,21070110,94076128,22450468,99297164,47361209,16445503,62428472,83533741,43322743,49271185,39201414,71083565,54199160,29959549,88904910,1204161,77620120,57359924,4091162,15148031,15015906,36312813,51149804,95290172,2607799,84002370,71469330,75820087,55615885,61859143,92398073,26139110,44844121,8913721,90061527,30395570,65038678,16567550,84684495,6819644,81274566,44060493,60393039,73235980,11581181,45995325,42782652,47708850,79821897,30218878,94090109,88251446,74724075,63628376,96420429,20122224,54606384,49882705,16424199,9058407,89078848,30543215,99917599,20486294,76825057,56515456,48673079,14349098,9860968,39986008,59329511,89499542,28787861,5073754,9175338,20885148,74137926,7011964,93270084,53888755,79806380,81855445,5267545,78549759,82532312,23848565,82886381,4798568,79191827,85242963,14626618,88047921,98462867,8634541,32590267,34667286,10649306,44481640,30891921,46870723,47792865,2891150,67793644,54987042,73392814,84406788,74614639,91727510,71522968,77300457,91831487,17764950,34946859,7104732,44550764,67227442,33336148,4787945,83368048,53632373,48395186,18411915,68824981,96318094,69579137,12571310,93053405,45237957,71300104,59501487,62357986,61728685,21001913,72495719,2917920,36139942,83133790,38256849,96852321,70596786,74357852,20140249,18131876,72614359,17976208,50567636,86821229,31491938,31904591,78218845,24058273,13468268,87160386,2331773,65074535,82052050,73109997,68316156,82339363,38510840,47213183,34432810,79657802,40152546,72357096,90457870,26292919,65047700,24915585,40984766,79322415,29065964,29516592,91957544,55669657,65880522,31161687,66885828,36468541,32250045,11757872,29510992,7517032,53648154,60581278,67281495,91990218,37560164,27625735,74452589,8128637,51315460,66903004,62762857,9603598,67124282,30463802,1375023,20836893,12024238,14363867,72238278,3487592,74743862,55960386,60176618,38008118,4064751,33553959,36845587,82897371,93359396,21919959,29932657,48658605,6525948,21673260,70367851,92604458,5640302,8729146,18001617,247198,19891772,70541760,88653118,54868730,82327024,77787724,15064655,91141395,66428795,92071990,11543098,45790169,7502255,40686254,89217461,81774825,53084256,42073124,94516935,97057187,55770687,45407418,93566986,39373729,4095116,4069912,30694952,62552524,45428665,56484900,11365791,93057697,45049308,90004325,26776922,32058615,61897582,78884452,93515664,57163802,37659250,85711894,69136837,43152977,59109400,22766820,48774913,79942022,87720882,15948937,23110625,34236719,23432750,14326617,28796059,34698428,18699206,95581843,92215320,69848388,80014588,8099994,55602660,99524975,42947632,4985896,8791066,3235882,62803858,91255408,90310261,46851987,23740167,85023028,45919976,19898053,3183975,26744917,26063929,79922758,73786944,28734791,16387575,88444207,30163921,27185644,45794415,99333375,15536795,12664567,92998591,74110882,82427263,6871053,28851716,43357947,37891451,32274392,8733068,4978543,20867149,31733363,1936762,43933006,33699435,62490109,99658235,17058722,7066775,32159704,20568363,8055981,96906410,84840549,97281847,30811010,84293052,61712234,22721500,14723410,68702632,49236559,94711842,77898274,95957797,91281584,92803766,10961421,75407347,12303248,26493119,6521313,98653983,17385531,72019362,25636669,97783876,75777973,99021067,21289531,6153406,50702367,59405277,87598888,5832946,18504197,85695762,71965942,80251430,13819358,45667668,36812683,81898046,82779622,93562257,37166608,36135,57658654,85117092,72732205,4199704,5822202,40027975,14045383,56461322,62115552,4204661,16684829,6111563,26397786,27041967,33935899,79738755,65081429,67513640,81805959,63059024,6497830,44664587,16405341,41092172,88698958,94595668,86118021,59614746,42644903,37192445,40781854,58208470,53393358,86543538,91938191,92530431,58751351,27187213,51590803,72738685,33431960,75052463,26734892,90158785,22113133,24314885,61960400,80316608,37224844,44846932,44245960,89804152,82651278,78300864,95251277,29401781,68110330,83747892,89996536,47893286,4434662,37280276,17016156,23134715,70782102,74441448,75128745,45990383,24953498,57020481,76315420,41442762,17857111,97641116,21397057 +77312810,96726697,61741594,4064751,51047803,112651,17365188,63967300,53084256,66319530,44177011,17957593,31727379,71083565,7423788,55247455,57803235,69605283,81853704,62452034,23569917,40984766,7182642,22108665,44348328,168541,4099191,51464002,77272628,99333375,62936963,98371444,10358899,69355476,94076128,16387575,44915235,48892201,96193415,35192533,88904910,49641577,91990218,23432750,21533347,44481640,66250369,10366309,30395570,55770687,7646095,89419466,34698428,9829782,76671482,61271144,60581278,53648154,55143724,95581843,39553046,77787724,90272749,93270084,14220886,16097038,56473732,39847321,63152504,15535065,9860195,37675718,84293052,27411561,3880712,54762643,29959549,50188404,79136082,4095116,84904436,22129328,26392416,9886593,19939935,66832478,9599614,27041967,73617245,24733232,76170907,95957797,91802888,8115266,14093520,39171895,83083131,95010552,82726008,67281495,18663507,74357852,59177718,84684495,81898046,10309525,26664538,70004753,30771409,57240218,34946859,34432810,73031054,73786944,6819644,45617087,53666583,29819940,70036438,75229462,40865610,14731700,33201905,44842615,94516935,9603598,51588015,27665211,31126490,18001617,89046466,26998766,99965001,53632373,31733363,77284619,77301523,176257,73168565,62490109,33304202,69641513,33895336,56515456,7685448,12571310,42692881,69786756,11581181,89637706,40686254,59371804,26114953,93790285,27625735,79922758,64087743,14349098,3294781,51315460,96420429,29994197,50702367,56424103,6793819,60106217,91240048,247198,3088684,18699206,72373496,8651647,57163802,29932657,99658235,8913721,79322415,64157906,38494874,24591705,33565483,8733068,8634541,93562257,12348118,15075176,83533741,17037369,51472275,62740044,20645197,36780454,41092102,82779622,10649306,58751351,33061250,75543508,73021291,18806856,43933006,68702632,65038678,16684829,89217461,76540481,97011160,75986488,21070110,37501808,33431960,38022834,34044787,96735716,34236719,59501487,29188588,7919588,55753905,6157724,72238278,508198,83368048,61263068,29834512,78549759,44784505,20885148,4199704,44550764,54232247,58208470,35456853,73124510,65851721,3487592,26292919,59109400,48675329,415901,97561745,29510992,90090964,749283,20867149,72357096,824872,78602717,37739481,12416768,669105,8128637,51426311,27185644,33797252,8807940,42967683,34698463,98943869,56484900,70727211,37891451,9257405,59329511,6497830,51466049,16595436,5267545,33123618,52060076,59910624,13470059,28796059,88047921,74441448,58749,11923835,89078848,60430369,65271999,99021067,54014062,22997281,37957788,70596786,86022504,54987042,17894977,93053405,65081429,75153252,4434662,67227442,66885828,43357947,32159704,75135448,58180653,65715134,32161669,33237508,6221471,49882705,8129978,48088883,21993752,98739783,68128438,83269727,12664567,67451935,68644627,16019925,98648327,17146629,9259676,63372756,9398733,5970607,60102965,64848072,39201414,88653118,71657078,97783876,38658347,86301513,45995325,4798568,45794415,79806380,19101477,23194618,93933709,24826575,99861373,88698958,40027975,42237907,42199455,30653863,81805959,24168411,73392814,42307449,68102437,37435892,36845587,37280276,45790169,98462867,43506672,82979980,4668450,3233084,59981773,31904591,86821229,30463802,66667729,86798033,65047700,94360702,78218845,38061439,65880522,52293995,54517921,44245960,36580610,1431742,81172706,9623492,99604946,62693428,9860968,87160386,53842979,30543215,25842078,26776922,30218878,3773993,5111370,26863229,95395112,30764367,90654836,76434144,14045383,51507425,65338021,75820087,3509435,66428795,55470718,2717150,92530431,66045587,43376279,1204161,56531125,24058273,1569515,49271185,55189057,63506281,6153406,77072625,39986008,60393039,25636669,17058722,73235980,86001008,40534591,42644903,47090124,90457870,36753250,84349107,84127901,68939068,5482538,76703609,6986898,54606384,71965942,6505939,67474219,29466406,97057187,84187166,22450468,50007421,68816413,54058788,67793644,17385531,15015906,55669657,16054533,34295794,1239555,44844121,30787683,37620363,65454636,1022677,77620120,90013093,61712234,28787861,70372191,44473167,30366150,81274566,7011964,11543098,77694700,5832946,92541302,23848565,75052463,36312813,21673260,70800879,20681921,53802686,64602895,6525948,16099750,24619760,63628376,43045786,37659250,61982238,72793444,47708850,6808825,2204165,77300457,82339363,40781854,84406788,76330843,13231279,46540998,38341669,99524975,55960386,13468268,90004325,51149804,45848907,26734892,47887470,87710366,72019362,96709982,42782652,43152977,77377183,43322743,526217,36808486,82052050,52204879,44983451,40197395,26063929,86543538,76315420,57961282,80316608,321665,75407347,49598724,67084351,68875490,92692283,62496012,85571389,36505482,78561158,4508700,62430984,14723410,16405341,91957544,72614359,4091162,22435353,20836893,61859143,13173644,7104732,70367851,95251277,14947650,9575954,19272365,12024238,77187825,74452589,37183543,89214616,98653983,40356877,74110882,53393358,91664334,61960400,874791,1250437,57359924,9188443,92604458,13862149,26493119,63015256,78589145,7229550,78909561,32058615,20140249,61623915,53888755,11757872,48360198,62762857,18131876,20122224,8696647,69697787,26744917,92554120,45075471,16445503,90310261,72738685,44846932,79191827,98948034,70541760,77413012,15148031,99549373,7300047,62803858,69136837,14326617,46851987,7955293,61373987,55850790,78785507,83302115,92803766,33249630,10597197,2331773,82886381,88444207,49236559,87598888,68041839,66322959,71469330,92353856,60955663,72278539,72274002,69848388,47361209,28851716,54427233,29029316,89499542,19486173,68204242,16380211,5073754,97379790,61728685,45919976,33628349,36930650,30090481,79942022,74724075,4806458,89811711,2917920,6871053,39801351,62051033,32274392,99690194,569864,30569392,18833224,32250045,49597667,87720882,82427263,20486294,20002147,1375023,2891150,6111563,21001913,62428472,22113133,37192445,68000591,56153345,17764950,17016156,6038457,56461322,54663246,61859581,42947632,36812683,35092039,8729146,30163921,27325428,4985896,18466635,3233569,64098930,4515343,51715482,50806615,71300104,14363867,30998561,99917599,71333116,37224844,38256849,65074535,97940276,44847298,91831487,32699744,30694952,41481685,46870723,73222868,17081350,8373289,84840549,38645117,57241521,60176618,20765474,91141395,35996293,91281584,8099994,51590803,7687278,40152546,89699445,47738185,85116755,2607799,92787493,85023028,36189527,22721500,99297164,80251430,1936762,82178706,90158785,40677414,21289531,15536795,28550822,99515901,1788101,93359396,17539625,35512853,66663942,82897371,22942635,61815223,93515664,95726235,15902805,34493392,45407418,78766358,33699435,71522968,44479073,92867155,11365791,33935899,71316369,83150534,74743862,17386996,16567550,52261574,55615885,48673079,71920426,59318837,99125126,54263880,99224392,57248122,15064655,2208785,4978543,80555751,26507214,8055981,18783702,33553959,42073124,52613508,48893685,22766820,36933038,90061527,85242963,9175338,70420215,17068582,92071990,48658605,12303248,29065964,65275241,80014588,93566986,45996863,24314885,94539824,80246713,41380093,30139692,39373729,48774913,1197320,40781449,54868730,10264691,31161687,88251446,29516592,26275734,83210802,4787945,92033260,84002370,4069912,10453030,21397057,38510840,44060493,18504197,78300864,32426752,17976208,91255408,47792865,14626618,17727650,43501211,7502255,36468541,16424199,89996536,95290172,56955985,22405120,83133790,37166608,8791066,45667668,34497327,24915585,92692978,88130087,7517032,37560164,91727510,19891772,3183975,3235882,34667286,19457589,41442762,24953498,81855445,11161731,68824981,50567636,97281847,82327024,72725103,69255765,20642888,57658654,75777973,94711842,99226875,13819358,5640302,47213183,59197747,59614746,9058407,44889423,54199160,3633375,83789391,57393458,94911072,68694897,92398073,83651211,79880247,94595668,89804152,86460488,79657802,45428665,92215320,28734791,94090109,69579137,85711894,17857111,97641116,29635537,30811010,47298834,62115552,74614639,45049308,45237957,21919959,45381876,57020481,61141874,66137019,72358170,67030811,82651278,75128745,98130363,77898274,33336148,50668599,74137926,19376156,72777973,13348726,53547802,36139942,66903004,48395186,15163258,45306070,76960303,93057697,38009615,83948335,67513640,7066775,86118021,67124282,18411915,68316156,85117092,30891921,45990383,48260151,68110330,72495719,26102057,62552524,96852321,22879907,64055960,15948937,79821897,47893286,4119747,41245325,36685023,99729357,79738755,20568363,66271566,26397786,16971929,63059024,38365584,83747892,6521313,32590267,61897582,91938191,85695762,58224549,99971982,29401781,67031644,44627776,41092172,28928175,31491938,92998591,49328608,55602660,65017137,66611759,96318094,81677380,23740167,38008118,23110625,27187213,23134715,36396314,86242799,5822202,26139110,78884452,4204661,83155442,96906410,22200378,62232211,32151165,72732205,36135,44664587,70782102,62357986,61928316,59405277,66116458,19898053,84166196,82532312,81774825,10961421,73109997,76825057 +72274002,89214616,59197747,67793644,112651,77284619,45428665,95290172,60430369,44348328,20765474,70420215,81898046,99658235,50668599,55753905,16387575,84840549,96906410,99549373,19939935,29029316,83150534,12024238,30395570,29834512,66250369,54058788,93933709,74137926,99971982,53547802,3633375,3233084,55470718,69255765,20568363,66322959,55247455,14626618,13862149,71333116,17068582,50702367,47708850,24058273,95957797,6793819,6808825,71316369,72725103,9259676,81853704,15536795,6525948,8129978,82339363,54663246,53802686,30998561,5640302,4508700,33797252,98371444,78589145,63506281,40781449,43152977,77620120,62452034,10961421,74724075,44481640,15535065,86543538,37739481,97379790,37166608,39847321,64087743,2717150,8807940,38645117,65047700,73124510,49882705,54014062,29188588,20486294,33935899,62496012,29819940,415901,29065964,9603598,63628376,7687278,36189527,27411561,40356877,54868730,51047803,3088684,54427233,37620363,26744917,15015906,96193415,86301513,66832478,7919588,65338021,84002370,18699206,35192533,16019925,18411915,92033260,73222868,3183975,75407347,3233569,62490109,19891772,65275241,69136837,72278539,31904591,69848388,34236719,88653118,33201905,84684495,6221471,9257405,96318094,20642888,42967683,14326617,47090124,35092039,98648327,83083131,77272628,68102437,40865610,1431742,48892201,6871053,79806380,37560164,61728685,91664334,26275734,98130363,4119747,32590267,24915585,21533347,7685448,40677414,61263068,96726697,54263880,92803766,9623492,9886593,90013093,86460488,6153406,34044787,66137019,62740044,26493119,62430984,62357986,824872,10309525,25842078,72777973,46851987,38008118,19376156,30366150,22108665,19486173,36812683,26114953,74452589,32159704,42073124,14093520,42199455,92692978,51590803,34493392,61960400,83533741,5111370,61897582,99226875,44889423,53084256,91938191,14947650,66611759,29510992,76170907,67124282,94516935,5832946,68816413,65715134,24826575,33431960,16445503,7300047,12348118,73617245,8651647,73235980,31161687,44060493,92353856,57163802,29466406,31126490,64602895,58224549,36753250,33237508,22766820,65851721,72738685,55189057,67030811,66663942,23848565,99515901,9188443,34698463,30764367,508198,73109997,39373729,16567550,65454636,82052050,50567636,16380211,49328608,59177718,43376279,73168565,12416768,85116755,91281584,61982238,37891451,89046466,14220886,13231279,61271144,72732205,7011964,64098930,95395112,45306070,37183543,79738755,3509435,5970607,38658347,17976208,22450468,3294781,30787683,39171895,65038678,75229462,85023028,55770687,50007421,72358170,40686254,75052463,69641513,16971929,95581843,26734892,84127901,77694700,56484900,22942635,51472275,68204242,94595668,17385531,95726235,23569917,40197395,61373987,26139110,51588015,48260151,27325428,41245325,30891921,27665211,40984766,56531125,91831487,83948335,526217,34497327,40781854,55669657,9575954,44842615,45848907,45049308,12664567,43933006,30139692,86022504,11543098,66271566,54199160,78218845,36580610,99917599,99297164,80014588,23194618,66116458,93053405,16595436,15148031,56461322,7955293,60106217,14349098,41092102,91957544,82327024,35996293,48675329,57803235,13173644,22879907,72495719,22129328,4668450,18806856,5073754,17037369,65017137,49641577,15163258,47792865,83210802,57658654,70372191,39553046,97057187,88904910,11365791,42692881,48774913,16099750,29516592,11161731,16684829,69355476,59501487,10366309,33628349,61141874,10453030,69579137,32426752,44245960,81677380,30694952,2891150,67281495,29994197,42307449,24591705,41092172,20836893,85242963,45407418,34295794,78785507,58749,38061439,7423788,83651211,59371804,33123618,39801351,34667286,84187166,60581278,11581181,62803858,45237957,38494874,99604946,64157906,96420429,33249630,90061527,86821229,55850790,74743862,83269727,90090964,36139942,72373496,3880712,52261574,62552524,4099191,73031054,71657078,57248122,44983451,8733068,71083565,92692283,57020481,75777973,80555751,43045786,68824981,49598724,76330843,43501211,4787945,84406788,79821897,51507425,51715482,26063929,30771409,51464002,17365188,17857111,12303248,39201414,85571389,54987042,62232211,26102057,45990383,62051033,1204161,70596786,4064751,6986898,46540998,92998591,79880247,36396314,36685023,73786944,97561745,3487592,57961282,4515343,7229550,60955663,26507214,70541760,33304202,18504197,38365584,68041839,92787493,84904436,89637706,20002147,99965001,98462867,77301523,86001008,15075176,44479073,82897371,54517921,54606384,77787724,9398733,17146629,10649306,50806615,60102965,36468541,99224392,98739783,20122224,77187825,75543508,76315420,44177011,47738185,68000591,59614746,21993752,6505939,50188404,22200378,28734791,37224844,20140249,37501808,26664538,7517032,20645197,18833224,99690194,10358899,48088883,92604458,30569392,16405341,26392416,47361209,19272365,66428795,77312810,44473167,63967300,1250437,87598888,88047921,47298834,48395186,82979980,97940276,16054533,7182642,44844121,39986008,78602717,66667729,2208785,70367851,33699435,61859581,83368048,63152504,17764950,67474219,11757872,45794415,63015256,77413012,1197320,38256849,29959549,17957593,53632373,23134715,19101477,81855445,1022677,18783702,98653983,51426311,87720882,67031644,75153252,45790169,72614359,321665,76540481,44550764,96709982,70727211,36505482,92215320,34432810,62428472,5822202,53842979,3235882,8729146,4204661,8696647,35456853,10597197,55143724,75128745,32058615,32161669,75986488,53666583,82726008,66045587,74357852,77377183,88251446,45617087,26776922,23740167,61741594,34946859,86118021,669105,42782652,40152546,78561158,90272749,99861373,92071990,45995325,31491938,15902805,176257,91990218,1569515,70004753,7646095,41380093,28796059,61623915,89811711,65074535,96735716,28928175,17386996,34698428,83133790,5482538,93790285,56153345,91255408,36780454,62693428,53648154,31727379,2917920,94911072,36312813,71469330,72019362,57359924,78909561,87710366,1239555,53393358,52613508,67084351,59109400,18663507,2607799,26998766,22997281,89499542,89078848,21001913,26397786,52293995,88130087,68644627,569864,44784505,15948937,59329511,92554120,30653863,47213183,16424199,99021067,78766358,79136082,89804152,22405120,17081350,48893685,4798568,84166196,9058407,37435892,8055981,14731700,58751351,8913721,99333375,16097038,51466049,2204165,44847298,33895336,18131876,94076128,60176618,97641116,32151165,30543215,83155442,21070110,42947632,61712234,78300864,91727510,58208470,10264691,77300457,30218878,14045383,59405277,78549759,22113133,87160386,44664587,9860195,76960303,93515664,18466635,54232247,1375023,70800879,17727650,12571310,22435353,55602660,45075471,27187213,86798033,75135448,74110882,68316156,168541,63372756,72357096,7066775,8115266,45667668,24168411,36808486,68110330,24953498,83302115,29932657,68939068,36135,8099994,13348726,42644903,62936963,51149804,91802888,71920426,20867149,80251430,56515456,28550822,4434662,13819358,4978543,79191827,72793444,43322743,18001617,11923835,84349107,93057697,20885148,95251277,4095116,97011160,17894977,81172706,65271999,89419466,32699744,63059024,47887470,45381876,69697787,97783876,55615885,80316608,52060076,79657802,56955985,33565483,59910624,71522968,6038457,49271185,78884452,57393458,61859143,65880522,17539625,49236559,73392814,74614639,13468268,38510840,83789391,47893286,68702632,97281847,35512853,8373289,58180653,749283,4069912,89699445,88698958,64848072,59318837,90310261,90457870,93359396,98943869,32250045,24314885,64055960,1788101,80246713,38009615,48360198,96852321,52204879,92541302,37659250,9175338,29401781,75820087,79922758,31733363,88444207,76434144,92867155,85117092,44627776,60393039,49597667,92398073,33336148,44915235,27625735,62762857,94711842,4806458,66319530,99524975,8791066,30463802,9599614,6521313,61815223,69786756,73021291,40027975,54762643,38341669,20681921,48673079,76825057,8634541,45919976,29635537,21919959,74441448,37957788,82532312,90004325,28787861,82178706,37280276,26292919,22721500,21397057,93566986,56424103,93270084,6111563,66903004,89217461,95010552,69605283,7104732,77072625,36930650,68694897,55960386,874791,5267545,57241521,24619760,6497830,19898053,247198,28851716,7502255,82779622,14723410,3773993,81805959,21289531,61928316,71300104,4199704,23110625,76671482,68128438,99729357,26863229,89996536,17058722,84293052,66885828,94539824,59981773,81774825,48658605,45996863,46870723,30090481,57240218,9860968,70036438,40534591,65081429,33061250,44846932,85695762,81274566,43506672,83747892,99125126,24733232,98948034,14363867,13470059,67451935,27041967,36933038,30163921,6157724,82886381,91240048,27185644,72238278,91141395,6819644,67513640,8128637,94090109,42237907,90654836,30811010,86242799,9829782,94360702,21673260,2331773,4985896,67227442,43357947,32274392,23432750,90158785,1936762,25636669,4091162,19457589,33553959,37675718,15064655,37192445,38022834,85711894,41442762,68875490,82651278,79322415,17016156,62115552,76703609,93562257,41481685,77898274,79942022,53888755,92530431,82427263,56473732,71965942,36845587,51315460,70782102 +99861373,62740044,36580610,38494874,96318094,77620120,40984766,99524975,53648154,67281495,54517921,1022677,65880522,5970607,61373987,40197395,37659250,50806615,91990218,3088684,12416768,112651,62452034,93057697,8791066,19939935,84406788,97783876,61141874,29834512,7955293,526217,74441448,26664538,48360198,55470718,91802888,85242963,34698428,70004753,19101477,39986008,82651278,44481640,50188404,4978543,84840549,19376156,415901,46870723,27665211,37435892,15535065,6808825,49641577,78589145,89637706,49882705,99515901,30543215,30771409,39553046,9860195,35092039,7182642,43933006,5267545,68102437,59501487,1431742,79880247,1204161,29932657,66045587,69355476,18783702,37675718,64848072,14220886,94516935,56515456,38061439,63967300,29994197,24058273,87598888,95957797,2204165,1788101,96726697,26392416,29029316,55602660,39847321,69136837,64098930,57359924,29959549,4668450,12571310,91727510,10366309,52060076,20002147,25636669,54762643,42782652,75135448,92554120,89811711,99965001,28550822,91831487,53842979,99658235,22200378,34698463,6111563,68939068,33565483,45075471,36845587,35456853,29510992,66611759,60955663,38008118,66663942,57163802,80251430,83210802,44844121,95581843,36780454,99604946,71657078,99917599,45919976,64087743,24591705,96735716,5822202,569864,2917920,9188443,60106217,20140249,52293995,8696647,20765474,95726235,62496012,44915235,92541302,12664567,42199455,88047921,27411561,508198,28851716,44479073,18504197,42237907,50702367,65715134,77312810,91957544,93933709,88653118,77272628,64602895,13470059,33249630,83269727,37183543,72278539,15015906,92692283,321665,98462867,66428795,7685448,77413012,53632373,26998766,31733363,78884452,3233084,8055981,71083565,26744917,84127901,34295794,4199704,4204661,45790169,39801351,27185644,51426311,33237508,17146629,5111370,99549373,14947650,49597667,7423788,48892201,60430369,44177011,10358899,19272365,56424103,17081350,3235882,53802686,47213183,79657802,63015256,48774913,32274392,80014588,22405120,85711894,30998561,66667729,81172706,57240218,26776922,39201414,94595668,32250045,27041967,44348328,65074535,83083131,76703609,67451935,44784505,30569392,26275734,33431960,98371444,71300104,30764367,35192533,68204242,8128637,77284619,76330843,71522968,37192445,70372191,9259676,2717150,70367851,11365791,66832478,41481685,54058788,91240048,57803235,85023028,84904436,20681921,31904591,21397057,36312813,16971929,97011160,68000591,76315420,37620363,65038678,70596786,54868730,6038457,66250369,84349107,45428665,34946859,24826575,82726008,30653863,35996293,8129978,99021067,84187166,44842615,23134715,9257405,17068582,21001913,96906410,5482538,7300047,57961282,87160386,80555751,1239555,55753905,69579137,4434662,13231279,63628376,23740167,61741594,53666583,669105,96193415,80316608,49236559,36808486,33123618,61859143,96420429,74110882,68824981,20836893,65851721,86821229,35512853,60581278,99333375,75543508,76540481,61960400,53393358,62490109,33797252,61263068,60102965,45996863,10649306,92787493,51590803,18806856,21993752,30139692,37891451,72725103,82339363,40781854,2891150,82886381,86001008,30395570,76671482,77898274,57658654,30463802,36505482,44473167,21919959,90004325,15148031,12348118,74357852,93562257,93790285,65081429,90090964,48893685,17365188,92033260,95395112,81677380,67084351,73222868,78300864,40027975,86460488,77300457,4099191,7919588,47090124,74743862,50668599,9398733,33201905,247198,69786756,70800879,6525948,26063929,73021291,47708850,40686254,3880712,6153406,9058407,84684495,16380211,65338021,55770687,22129328,18833224,23569917,9599614,20867149,29065964,4091162,81898046,9603598,13173644,18411915,54199160,14326617,82979980,73786944,38365584,22997281,32590267,92215320,65047700,62803858,45848907,34493392,90457870,97940276,6505939,79191827,81853704,62693428,70420215,51149804,23848565,52204879,9575954,93515664,48675329,6157724,47298834,18663507,82052050,73124510,4985896,3294781,8807940,29819940,77187825,75820087,4508700,4064751,62552524,11923835,61982238,54606384,59109400,42947632,66137019,53084256,20885148,21289531,9886593,24915585,32161669,68128438,66903004,43501211,97641116,98943869,14731700,19898053,67793644,54427233,23194618,72793444,89214616,40677414,21673260,61623915,89419466,98653983,40356877,77301523,67474219,98130363,30218878,13468268,16054533,75986488,56484900,51047803,45407418,17857111,7104732,36930650,4515343,18131876,45049308,91664334,33061250,30163921,34497327,36685023,22721500,95010552,65017137,45990383,94076128,89078848,3633375,22450468,52613508,72357096,29188588,39171895,40152546,78549759,15064655,62430984,73617245,22766820,24733232,43322743,19457589,78561158,8115266,17727650,20486294,168541,39373729,72373496,79942022,49328608,53888755,77787724,32699744,33304202,26734892,5073754,3509435,83368048,65271999,41380093,75407347,72732205,63059024,27325428,89804152,77377183,37957788,24619760,88444207,20642888,28787861,13862149,78218845,5832946,59910624,82427263,57393458,26493119,66885828,89046466,11581181,44060493,85117092,55189057,17037369,8634541,67031644,11543098,52261574,58224549,31727379,48395186,33935899,69641513,48673079,20645197,31161687,79922758,56461322,3183975,96852321,46851987,93270084,92604458,75229462,30811010,13348726,7517032,6986898,48088883,20122224,36753250,92867155,22108665,36139942,91141395,69255765,2607799,99224392,50007421,26507214,83150534,91255408,44245960,40865610,21533347,4119747,72777973,62936963,74724075,76170907,22879907,14363867,90158785,75777973,57248122,98739783,73031054,74137926,68041839,1197320,47887470,99971982,33628349,38022834,7687278,89699445,1250437,31126490,58751351,9175338,88251446,79136082,36189527,61728685,68702632,30891921,4787945,43506672,58180653,55247455,45794415,6871053,63372756,69848388,16387575,51466049,6521313,80246713,44550764,16424199,99297164,88904910,38510840,19486173,76434144,72358170,44846932,63506281,45306070,4798568,42692881,65454636,47361209,72738685,30694952,36933038,38256849,68644627,56153345,16445503,68875490,68816413,97561745,17016156,34432810,61271144,15948937,29466406,30366150,56473732,69697787,61815223,32151165,34236719,83533741,46540998,73168565,86118021,41245325,10597197,44983451,59329511,25842078,98948034,14093520,54663246,17385531,75153252,18001617,94711842,17764950,32159704,90654836,36468541,6221471,8099994,73109997,53547802,14349098,44664587,92998591,2331773,51507425,72274002,3233569,59318837,36135,97281847,42073124,66319530,59371804,92398073,26114953,62762857,98648327,17957593,99125126,61928316,6793819,17539625,86301513,79821897,71469330,56955985,55960386,8651647,71920426,82178706,70727211,749283,42967683,90272749,73235980,83651211,9623492,14045383,75128745,1569515,67030811,78766358,51715482,34044787,79738755,85116755,90013093,22435353,86543538,26102057,86022504,17894977,93053405,84002370,58749,63152504,62051033,68110330,71333116,16099750,824872,14626618,51464002,31491938,26292919,30787683,77072625,37280276,33336148,32426752,69605283,10309525,38645117,64157906,55143724,42307449,94360702,83155442,72238278,5640302,92530431,9860968,22113133,44889423,47738185,28796059,62115552,36396314,15902805,8733068,26139110,95251277,82897371,62357986,21070110,78602717,41442762,26863229,55669657,34667286,60176618,28734791,32058615,58208470,24953498,72019362,16405341,23432750,15536795,18699206,33553959,29401781,36812683,41092102,41092172,97057187,70036438,54232247,14723410,45237957,11757872,55615885,85695762,38658347,59197747,3487592,16097038,51588015,67227442,90310261,92071990,92353856,85571389,84293052,3773993,7502255,51315460,48260151,94911072,17976208,60393039,99729357,72495719,2208785,40534591,37739481,43045786,94090109,16019925,77694700,45667668,37224844,17058722,18466635,76960303,64055960,94539824,89499542,10961421,12024238,86242799,33699435,93359396,92803766,50567636,83789391,93566986,57241521,28928175,15075176,71316369,79322415,65275241,13819358,51472275,47893286,45617087,874791,42644903,89996536,24168411,8373289,37560164,78909561,59614746,176257,6497830,96709982,43152977,88698958,83948335,56531125,86798033,90061527,17386996,43376279,62232211,83302115,73392814,66322959,59981773,62428472,29516592,92692978,91938191,4069912,61712234,66116458,71965942,45995325,55850790,70541760,61859581,74452589,10264691,89217461,44847298,87720882,87710366,83747892,7011964,95290172,76825057,59177718,4095116,54014062,7066775,49271185,12303248,72614359,37501808,81774825,6819644,20568363,16595436,79806380,8729146,45381876,23110625,82532312,38009615,4806458,37166608,61897582,68694897,74614639,81805959,9829782,1375023,81274566,27187213,38341669,29635537,7229550,83133790,15163258,57020481,11161731,10453030,78785507,54263880,7646095,8913721,44627776,19891772,40781449,88130087,97379790,67124282,16684829,30090481,22942635,91281584,33895336,67513640,99690194,75052463,27625735,99226875,84166196,48658605,26397786,54987042,1936762,49598724,82327024,59405277,16567550,68316156,24314885,47792865,66271566,70782102,81855445,82779622,43357947 +91957544,60102965,44842615,56424103,66250369,39801351,62803858,29959549,73168565,53666583,97379790,85023028,35192533,88047921,14093520,24619760,95957797,38061439,89637706,98648327,59177718,25842078,81172706,415901,16971929,74452589,99524975,84349107,4204661,34044787,62496012,14349098,45848907,39553046,89078848,47708850,23134715,40197395,19939935,6153406,90004325,12416768,9886593,74110882,8807940,71083565,42644903,72357096,77620120,35996293,70596786,4099191,59910624,67474219,77284619,9575954,82979980,30395570,84904436,33237508,93933709,20140249,33201905,10649306,29819940,46540998,7685448,55143724,6221471,24733232,42782652,20765474,66832478,68000591,65454636,48260151,55753905,14220886,79657802,89804152,51588015,15075176,20681921,78218845,22942635,77300457,16097038,26776922,2717150,19891772,96726697,58180653,18001617,19272365,95581843,49641577,94516935,90457870,8651647,55669657,74357852,89419466,73031054,6525948,75986488,19486173,58208470,53888755,93515664,60955663,3235882,92215320,34295794,40781854,57248122,14947650,77312810,32426752,16380211,99965001,81853704,76330843,13862149,43322743,9599614,18699206,6111563,75777973,42073124,64087743,34698463,24591705,34698428,71522968,70036438,90310261,18411915,89046466,76170907,44983451,99658235,59318837,34946859,12664567,61741594,8913721,176257,8733068,79821897,72274002,8055981,32699744,2208785,3773993,99861373,90090964,66428795,29994197,749283,8099994,91831487,40027975,29466406,7517032,67227442,43045786,85571389,53648154,21533347,8791066,88653118,73222868,83150534,10309525,78602717,69697787,82897371,4798568,36780454,75543508,58749,31904591,67281495,36505482,112651,7955293,70727211,85242963,36808486,62452034,37891451,4119747,39201414,26507214,44481640,7011964,41442762,30998561,28734791,74614639,36753250,7229550,12571310,56531125,73235980,76540481,54663246,50188404,5832946,35456853,32274392,11543098,76671482,69641513,22405120,83269727,18466635,30811010,38494874,45995325,45667668,97561745,30090481,6497830,3233084,508198,49236559,68644627,92604458,29065964,61271144,34236719,6986898,73786944,51315460,9257405,5111370,5970607,93053405,7182642,64848072,54517921,44348328,77272628,33797252,45790169,3183975,59371804,17386996,94360702,80316608,54868730,95290172,17058722,26114953,50668599,94711842,29834512,37957788,98653983,87710366,97783876,45919976,17037369,247198,50702367,61960400,66319530,30764367,24826575,89699445,51047803,95395112,27625735,82178706,44177011,2204165,98948034,28550822,89214616,98943869,70367851,84187166,40356877,81677380,82052050,92787493,14326617,36845587,26392416,4806458,20486294,30891921,63015256,8696647,29510992,27411561,47738185,83747892,32161669,13470059,81898046,48893685,9058407,73124510,49597667,46870723,23848565,68102437,20836893,526217,83651211,69255765,4091162,16387575,20568363,72278539,51507425,45049308,17539625,824872,31733363,28851716,49882705,15536795,68702632,62490109,45306070,89996536,67793644,18783702,30139692,55602660,33249630,23569917,40686254,68204242,74441448,44245960,80555751,43933006,37183543,18504197,47298834,40781449,45407418,66322959,70420215,17857111,669105,33304202,75229462,37739481,20642888,50007421,72725103,93566986,99224392,92803766,42692881,30771409,10961421,94076128,57020481,59329511,92398073,55850790,26493119,78561158,26744917,30543215,13231279,87598888,61859143,86543538,90272749,31126490,55247455,54762643,34493392,48774913,36580610,10358899,92530431,71657078,83789391,68041839,50806615,6505939,75820087,38008118,92692283,57359924,70004753,62693428,6871053,68824981,72777973,75153252,84406788,99549373,51426311,91664334,98739783,17016156,6157724,24058273,27185644,7502255,74724075,36812683,2607799,88904910,86001008,1204161,71333116,91802888,55470718,30163921,6808825,9860195,70541760,15064655,1375023,98371444,33553959,99125126,21397057,23110625,12348118,27325428,84684495,55615885,44844121,72495719,49271185,27041967,59501487,27665211,48360198,33431960,40984766,9603598,43501211,86118021,63372756,79191827,62740044,16595436,92692978,76315420,78589145,74743862,40677414,68816413,57658654,94090109,38645117,17764950,32590267,9398733,29029316,52204879,5267545,48892201,20885148,4434662,66663942,69786756,22721500,63506281,60176618,321665,53632373,168541,83155442,3233569,9623492,1250437,62051033,86242799,41380093,91938191,70800879,90061527,16405341,61712234,72019362,11757872,17957593,26734892,1936762,23194618,74137926,1431742,44915235,83083131,17894977,22766820,79806380,44889423,54987042,85117092,85116755,53802686,51472275,4095116,82886381,99333375,78300864,86821229,3633375,97281847,47213183,31727379,3880712,66045587,13819358,54014062,48673079,11161731,4199704,66137019,26102057,99604946,3294781,67084351,55189057,52293995,68694897,61373987,30653863,88698958,37435892,21070110,92033260,45428665,99971982,43357947,4668450,77787724,6793819,66667729,49328608,99729357,38009615,39986008,54606384,57240218,87720882,37280276,18833224,15163258,39847321,80014588,1022677,26292919,17068582,63967300,93562257,16684829,88251446,33123618,62430984,44784505,51715482,9175338,60430369,48675329,28796059,47361209,48088883,42967683,36312813,54199160,45996863,35092039,77072625,62936963,43152977,67451935,79942022,44473167,4978543,72358170,8729146,44479073,86301513,82726008,71920426,20122224,33628349,42307449,9829782,70372191,53547802,91141395,78766358,65880522,37675718,54263880,63152504,54427233,72238278,41245325,84293052,26863229,4064751,26998766,97057187,4985896,65017137,99021067,10366309,66903004,13173644,45617087,83133790,77377183,43376279,59109400,4508700,48395186,99917599,16445503,90013093,85711894,37192445,24915585,38256849,30569392,65074535,68110330,33061250,43506672,64098930,96193415,80246713,77301523,47887470,22200378,15148031,45237957,61982238,35512853,92998591,71300104,85695762,44627776,89499542,71469330,14626618,17727650,36933038,31161687,7919588,24953498,28787861,30218878,46851987,82327024,17081350,42237907,16019925,99690194,20645197,45381876,59197747,9188443,26139110,94539824,30366150,45075471,61263068,89811711,99226875,26664538,12024238,73392814,83948335,37659250,32159704,64055960,84166196,8128637,45794415,20867149,95726235,29635537,82779622,60106217,2331773,63059024,38341669,10453030,51590803,81855445,55770687,79136082,29188588,47792865,76434144,61859581,18663507,11923835,84127901,36135,92353856,66116458,2917920,22435353,16424199,36685023,61141874,88444207,91990218,62552524,67031644,76960303,72373496,72614359,77898274,31491938,22108665,19101477,50567636,52261574,60581278,61728685,18131876,36396314,82339363,5822202,75407347,63628376,90654836,92554120,5073754,34667286,54058788,4069912,44846932,38022834,19457589,33699435,1197320,97011160,87160386,17365188,39373729,98462867,48658605,56515456,37501808,40152546,22450468,68939068,22129328,39171895,36930650,5482538,65081429,41481685,18806856,86460488,69848388,51464002,44550764,29401781,65275241,99297164,82532312,65715134,96318094,14723410,17385531,67124282,17976208,6038457,64602895,77187825,21673260,9259676,7687278,33935899,14045383,84840549,96906410,78909561,4515343,21993752,10597197,59614746,75052463,42199455,1239555,82427263,56955985,96709982,34432810,51466049,1569515,83210802,14363867,93270084,56473732,15015906,29516592,73617245,62115552,21289531,22879907,22113133,66611759,84002370,72738685,81805959,16567550,99515901,30463802,59405277,76703609,15535065,96852321,7646095,15948937,58751351,83368048,21001913,72732205,77413012,67513640,86022504,83533741,47090124,7104732,8115266,49598724,36468541,11581181,41092102,65038678,97940276,57803235,69355476,92541302,68128438,56461322,44060493,89217461,76825057,3088684,77694700,61815223,78884452,66885828,33895336,69605283,75135448,79738755,95251277,52613508,26397786,91281584,71316369,67030811,93057697,91240048,32058615,72793444,56484900,57961282,79922758,56153345,20002147,25636669,96420429,41092172,2891150,5640302,14731700,37560164,88130087,16099750,82651278,55960386,33565483,30787683,15902805,1788101,7423788,6521313,60393039,3509435,93790285,3487592,29932657,86798033,47893286,17146629,96735716,6819644,57241521,91255408,8634541,874791,52060076,57393458,62762857,80251430,9860968,94911072,92071990,61897582,97641116,65271999,69136837,36139942,19898053,44664587,79322415,53084256,65047700,54232247,28928175,53842979,32250045,79880247,94595668,8129978,95010552,90158785,37166608,30694952,98130363,13348726,62428472,91727510,21919959,32151165,37620363,26275734,19376156,81274566,81774825,24168411,37224844,23432750,59981773,64157906,26063929,40865610,36189527,68875490,7300047,569864,51149804,65851721,69579137,13468268,75128745,66271566,11365791,23740167,57163802,38658347,22997281,44847298,65338021,27187213,58224549,78549759,10264691,16054533,62232211,83302115,42947632,61623915,62357986,92867155,45990383,34497327,93359396,7066775,8373289,68316156,40534591,24314885,38510840,33336148,73021291,4787945,53393358,12303248,78785507,71965942,73109997,38365584,61928316,70782102 +3233084,112651,42199455,53084256,81853704,33304202,97561745,24733232,99965001,3235882,56515456,38061439,26063929,29188588,75135448,31161687,73222868,45407418,5267545,38645117,6221471,1022677,55143724,76671482,30653863,62496012,59109400,92554120,38256849,321665,51047803,669105,17146629,63628376,74357852,15535065,92692978,86022504,44060493,45790169,33895336,33237508,89637706,96193415,65047700,60430369,67793644,42967683,42782652,60581278,8129978,30366150,62232211,30543215,37675718,29466406,26664538,7011964,10358899,59981773,83789391,92692283,37183543,81677380,15015906,55753905,33249630,12024238,61741594,8115266,45381876,21001913,75229462,44983451,33123618,526217,57961282,73617245,88653118,4199704,168541,7423788,72725103,54232247,85117092,50188404,1431742,29029316,6808825,13173644,84349107,13470059,34295794,63967300,10597197,824872,99658235,569864,68939068,67281495,36580610,16595436,29834512,3633375,80246713,81898046,83210802,66319530,7646095,57803235,33565483,66832478,7229550,30139692,55770687,35192533,3088684,69697787,68204242,62452034,98462867,66116458,76315420,51472275,72614359,65271999,44889423,44550764,31126490,78218845,83302115,41380093,83533741,99690194,93270084,48658605,24953498,84904436,39847321,98130363,17365188,72274002,72738685,28550822,43933006,15163258,37659250,36685023,36812683,55615885,56531125,14731700,415901,66611759,77787724,23194618,53842979,63015256,85571389,18806856,78884452,89214616,26998766,59318837,36505482,38658347,749283,77312810,92071990,39201414,4099191,7300047,92604458,54058788,43045786,95581843,1788101,92787493,86301513,65454636,32590267,22435353,77694700,61141874,43501211,88444207,29994197,16097038,34698428,76170907,70004753,10961421,4064751,30395570,55247455,54199160,66667729,82726008,27185644,16971929,72278539,96709982,16424199,37435892,63152504,36930650,39171895,18131876,3509435,17894977,52613508,45617087,61263068,70420215,1204161,70036438,66045587,99549373,61623915,82339363,94516935,1250437,70800879,82178706,46540998,9860968,62357986,93933709,69641513,14093520,22942635,57240218,16445503,80316608,61271144,88047921,65851721,62740044,93057697,68041839,77300457,80251430,10366309,72373496,50567636,9398733,50668599,98371444,17727650,78549759,4069912,5482538,59197747,9058407,90457870,16684829,23432750,19101477,47090124,32159704,24058273,69355476,54517921,4508700,65715134,9886593,36189527,77284619,20002147,68644627,90013093,33797252,17957593,98653983,12303248,2891150,30569392,83269727,19939935,9175338,96726697,29065964,30218878,57241521,61728685,91831487,44847298,12348118,54014062,72495719,7066775,75407347,64602895,78589145,44784505,45306070,55189057,26102057,71333116,25842078,70596786,49236559,75153252,4985896,55470718,91802888,22450468,40152546,84684495,78909561,24619760,8634541,18699206,66428795,93053405,66137019,90654836,20642888,40534591,22108665,34044787,84187166,11365791,33336148,247198,38008118,36780454,34493392,21070110,45237957,75986488,17539625,49271185,15148031,4668450,69786756,39986008,85116755,48673079,35092039,8696647,13231279,77072625,32274392,4119747,36753250,40356877,44627776,874791,8807940,82979980,46851987,38365584,33628349,90272749,37739481,52060076,27411561,43322743,87710366,9623492,14363867,64848072,57359924,51466049,29819940,56424103,61373987,21533347,9259676,74110882,53648154,83150534,14626618,21993752,27665211,92998591,54868730,13468268,15064655,14723410,68694897,66885828,30998561,23848565,22129328,48088883,63059024,35996293,37891451,15536795,22997281,40677414,63372756,45667668,58224549,56461322,60102965,90061527,24915585,508198,99917599,48774913,68875490,50806615,13819358,3880712,22113133,17058722,93790285,77620120,42237907,70372191,8128637,50702367,61859143,79657802,70541760,68000591,3773993,37166608,30463802,62936963,15075176,62430984,68702632,91664334,34236719,7955293,37620363,34946859,65038678,49641577,27325428,22879907,67031644,24591705,22200378,40865610,99226875,49597667,99125126,79191827,74743862,86001008,79922758,27625735,97011160,61982238,62693428,21289531,97940276,5111370,67227442,88251446,10309525,95290172,99297164,28851716,82427263,18504197,82651278,31733363,70727211,31904591,62552524,20122224,66250369,76434144,55602660,78300864,19891772,71469330,6793819,40197395,20867149,92541302,47738185,2204165,12416768,79738755,1239555,69136837,7502255,26114953,44481640,14349098,61859581,79821897,5970607,96735716,38341669,72777973,34497327,13862149,29510992,76540481,59614746,12664567,65338021,33201905,59405277,83133790,19376156,57163802,95010552,74724075,83083131,94595668,17016156,36135,50007421,18663507,86460488,73124510,6986898,1197320,92803766,62428472,95395112,41481685,86821229,44245960,92215320,26776922,4095116,48360198,67451935,73168565,73021291,26292919,36845587,16099750,99861373,64157906,64087743,59177718,9188443,20568363,44842615,86798033,41092172,66663942,30163921,8099994,26392416,45075471,61897582,23569917,63506281,73031054,78785507,51426311,24168411,98739783,91727510,56955985,26493119,9860195,72732205,4091162,84127901,85711894,65275241,94090109,54987042,48675329,87598888,38494874,20645197,59910624,46870723,19272365,54663246,36468541,41092102,6111563,52261574,65081429,22721500,37501808,57658654,59329511,54427233,44348328,45919976,33431960,26275734,8055981,20486294,36312813,40686254,53547802,82052050,99604946,40781449,69848388,8791066,67084351,66271566,45848907,10649306,51507425,77301523,99971982,11923835,16019925,29401781,34432810,30811010,73786944,12571310,65880522,39553046,14045383,43152977,69579137,52293995,24826575,57248122,45996863,59501487,6038457,9603598,36808486,74452589,44915235,90090964,14326617,3487592,82532312,99515901,94076128,71657078,29932657,6153406,59371804,95726235,92033260,60393039,40027975,61815223,53888755,88698958,17037369,78602717,54762643,78766358,16380211,71300104,13348726,95957797,47213183,43376279,76330843,28787861,18783702,18411915,60955663,34667286,25636669,73392814,75820087,7919588,47708850,14220886,93566986,97057187,44846932,32161669,97783876,93515664,85023028,35512853,91990218,20836893,3294781,62051033,55669657,68816413,18833224,72358170,15948937,28734791,44479073,96318094,28928175,19486173,53632373,86543538,47361209,71316369,88904910,28796059,52204879,98943869,55960386,6157724,31491938,51588015,89217461,81805959,99224392,51464002,58749,83368048,36396314,81855445,77187825,17386996,37192445,83651211,74614639,61928316,37560164,56484900,7182642,80014588,2917920,95251277,176257,87720882,7104732,66322959,30764367,77272628,89811711,86242799,1375023,70367851,74137926,72793444,37280276,84406788,26744917,45995325,6521313,32426752,69255765,6871053,33553959,62803858,81274566,84840549,5822202,43506672,99021067,42947632,94539824,89046466,86118021,17764950,89078848,7687278,68824981,7685448,76703609,33699435,69605283,89419466,62490109,5832946,74441448,6497830,68128438,18001617,91281584,4787945,44177011,91255408,34698463,17385531,81774825,4515343,3233569,65017137,49598724,40984766,32699744,96906410,9599614,58180653,2208785,17068582,93359396,20681921,1569515,71083565,99524975,43357947,5073754,20765474,16387575,30787683,8373289,76825057,72238278,97281847,16405341,73235980,8651647,6819644,30771409,4806458,47298834,4434662,89699445,42307449,53802686,48892201,4978543,45428665,67474219,49882705,81172706,87160386,96420429,94911072,23110625,68102437,39801351,83155442,7517032,48893685,38022834,48395186,73109997,15902805,23740167,19898053,11543098,79322415,2717150,26139110,64098930,91240048,44473167,66903004,3183975,8729146,60176618,36933038,10264691,26863229,83747892,99333375,27187213,38009615,77377183,85242963,22405120,90310261,77413012,9829782,45990383,98648327,62762857,75777973,35456853,96852321,67030811,11161731,20885148,78561158,47792865,16567550,37224844,72357096,22766820,51149804,91938191,31727379,94360702,58751351,4798568,29516592,29635537,45794415,92867155,27041967,49328608,54263880,40781854,92530431,39373729,93562257,94711842,71965942,14947650,70782102,85695762,41442762,82886381,82327024,32250045,51315460,29959549,30891921,5640302,56473732,57020481,92353856,54606384,6505939,6525948,26397786,42073124,10453030,9257405,20140249,76960303,90004325,92398073,33061250,56153345,84002370,26734892,4204661,36139942,71522968,44664587,21919959,75543508,65074535,26507214,62115552,84293052,17976208,18466635,42644903,23134715,11581181,51590803,2607799,79880247,33935899,19457589,98948034,57393458,16054533,71920426,21673260,2331773,42692881,60106217,24314885,67124282,51715482,68316156,97641116,97379790,89804152,21397057,83948335,88130087,91957544,79136082,55850790,41245325,99729357,79942022,17857111,37957788,90158785,91141395,64055960,17081350,80555751,47887470,53393358,9575954,68110330,1936762,32058615,38510840,75052463,89499542,8913721,82779622,44844121,72019362,58208470,30694952,8733068,47893286,61960400,32151165,79806380,53666583,11757872,77898274,82897371,61712234,75128745,30090481,89996536,48260151,84166196,45049308,67513640 +20002147,99965001,51047803,37183543,60581278,96193415,59177718,36780454,45617087,38645117,61982238,98462867,4064751,60430369,9623492,53084256,57961282,74357852,72274002,7300047,36753250,27665211,85116755,29834512,34236719,72725103,21993752,1431742,82979980,43376279,10358899,45407418,6793819,90090964,17764950,98371444,99549373,88047921,14626618,88698958,33249630,77072625,44983451,58751351,31126490,22450468,17068582,39847321,98130363,97011160,65338021,79922758,83210802,2917920,29029316,67793644,40356877,14220886,45237957,22129328,77787724,95290172,44550764,92554120,55189057,86022504,5970607,72238278,63059024,64848072,94090109,3233084,40984766,57803235,14045383,38022834,47090124,75543508,36505482,38658347,45428665,91802888,83269727,73786944,22997281,12664567,15015906,36580610,4069912,14731700,40152546,59371804,65271999,73222868,81898046,53842979,56153345,67281495,96852321,31904591,40781449,7229550,84684495,45794415,20642888,28796059,30395570,99524975,93270084,54058788,8807940,37659250,55753905,50668599,83789391,64087743,13173644,82726008,44479073,15148031,93057697,36312813,14947650,34698428,72732205,569864,66667729,49236559,99515901,55850790,57393458,24058273,62496012,7646095,78602717,14363867,96906410,10309525,27411561,72357096,16567550,88653118,50702367,8099994,37501808,63967300,77272628,67451935,25636669,9603598,92353856,12348118,24168411,30764367,62740044,22435353,68875490,30218878,20568363,15535065,76671482,32590267,6986898,23194618,52060076,92803766,59614746,59109400,72373496,30463802,18699206,87720882,13468268,49641577,21289531,415901,96735716,68204242,53648154,4806458,82339363,62936963,39171895,70004753,18131876,91831487,19939935,18806856,44889423,10264691,56461322,61728685,96318094,37435892,96726697,62430984,81274566,30771409,29188588,89214616,69697787,30569392,65047700,54014062,8129978,41092102,95957797,37620363,65715134,76315420,30653863,99971982,824872,16445503,38365584,93562257,27041967,32161669,7919588,34493392,71333116,53802686,16595436,73617245,61623915,77694700,68041839,11923835,40534591,73031054,61263068,51472275,69641513,84840549,22200378,3088684,48260151,15163258,81677380,69355476,9058407,71965942,91990218,43152977,89078848,66271566,46870723,38061439,1197320,30163921,33797252,63152504,44844121,33431960,56473732,9257405,32250045,42644903,10366309,37166608,80316608,90457870,20122224,75153252,77284619,44348328,33123618,51590803,3487592,36812683,34497327,93053405,176257,55143724,63628376,27185644,35192533,669105,70367851,62452034,65275241,61859581,43322743,51588015,66885828,96420429,45995325,6808825,91664334,66832478,35092039,13819358,45381876,66903004,29959549,3233569,42967683,1936762,79738755,70727211,84904436,57241521,56515456,4099191,20645197,49598724,12416768,63015256,74110882,94516935,7011964,89804152,5832946,55615885,78561158,93933709,70036438,85117092,99917599,54232247,112651,9886593,7182642,72793444,92215320,99297164,26998766,39553046,55470718,83150534,8373289,14093520,7423788,3880712,4095116,91727510,20140249,33304202,24314885,19101477,20486294,46851987,29994197,1204161,77312810,72278539,61815223,18466635,64602895,41481685,23432750,40197395,8115266,74743862,74614639,87710366,65038678,80251430,75135448,23569917,9188443,73021291,66250369,54987042,70420215,54606384,21001913,37891451,43933006,90310261,96709982,91255408,78218845,11161731,39373729,19486173,28550822,7502255,90272749,33336148,97783876,32159704,33201905,77413012,15536795,2204165,42199455,68644627,74724075,4434662,93515664,44481640,26292919,47738185,92033260,44784505,6521313,99604946,78589145,526217,54663246,4668450,42782652,15064655,6157724,90013093,43045786,66663942,33553959,78909561,26063929,74441448,39986008,92604458,85242963,59318837,48893685,14326617,36685023,42237907,71083565,59981773,321665,11365791,42073124,66322959,71316369,31733363,66428795,65081429,92398073,10597197,44664587,26139110,67474219,80014588,40781854,54517921,57248122,16684829,6221471,8128637,5073754,53632373,86460488,16405341,78884452,6819644,29932657,83533741,2717150,68000591,17365188,78785507,11543098,70800879,16099750,31161687,59501487,20765474,89419466,16971929,33237508,16019925,84406788,8791066,21673260,33565483,40686254,45306070,61859143,51464002,83083131,79821897,52613508,22108665,92692978,84349107,17539625,70596786,16380211,40027975,24619760,84293052,6497830,56531125,51466049,38510840,45990383,33699435,17016156,76960303,90061527,87598888,49271185,9860968,26392416,28928175,508198,44245960,27187213,42947632,83302115,26275734,34946859,85711894,53547802,84127901,82427263,44060493,45996863,47361209,36808486,48892201,50567636,67084351,50188404,77620120,69605283,1250437,69848388,29819940,49328608,72019362,12024238,79657802,77377183,75229462,3294781,16097038,13231279,92787493,16424199,5267545,44846932,99333375,53393358,82052050,73235980,68694897,6153406,65017137,48673079,45790169,26397786,76434144,71657078,15902805,30694952,55770687,26493119,36135,76703609,30543215,82178706,44842615,18663507,66137019,85571389,53888755,7955293,20867149,43501211,71300104,56424103,89217461,66045587,16387575,58224549,60393039,54762643,22721500,81853704,24591705,50007421,62693428,61897582,26664538,31491938,97561745,62552524,37560164,73168565,92541302,80555751,76540481,68702632,42307449,30139692,38008118,61741594,5111370,65851721,95395112,89637706,17727650,35996293,78549759,22766820,4515343,48088883,12571310,82886381,47893286,99861373,8696647,1788101,43357947,88251446,95010552,24733232,32058615,23110625,90654836,26776922,247198,30787683,69579137,4787945,72777973,86242799,61271144,14723410,97057187,4204661,97641116,40865610,32274392,37192445,83368048,82779622,68816413,75407347,98648327,91141395,60102965,69136837,8651647,22113133,17385531,874791,16054533,86543538,24826575,36930650,59197747,48774913,72738685,9259676,62762857,3509435,8634541,98739783,43506672,19376156,41092172,55247455,44627776,9829782,42692881,20885148,62428472,75777973,68128438,26114953,88444207,71469330,168541,58208470,31727379,62115552,85695762,34432810,61373987,70541760,3773993,94076128,36933038,83651211,61141874,69786756,99125126,94595668,45848907,66116458,1022677,58180653,68939068,18504197,22879907,57240218,29466406,4119747,24915585,70372191,81805959,56484900,2891150,88904910,36189527,82651278,57163802,92071990,81172706,5482538,44177011,54199160,3633375,18833224,5640302,17976208,77300457,7066775,1239555,84187166,20681921,55960386,64055960,79136082,92692283,45919976,26102057,92530431,40677414,39201414,13348726,17957593,92867155,57658654,62051033,75128745,67030811,71920426,36468541,9398733,86798033,82897371,12303248,8733068,25842078,64098930,17894977,21070110,8729146,86001008,50806615,41245325,38494874,71522968,79880247,48658605,92998591,26507214,74137926,90004325,29516592,1375023,52261574,99690194,27625735,4091162,19891772,62490109,36396314,5822202,89996536,36139942,79806380,94911072,9175338,81855445,79942022,2607799,78766358,2208785,28734791,6505939,63372756,76825057,21397057,26734892,99226875,89046466,93566986,35512853,99021067,41380093,94539824,46540998,32699744,8055981,58749,21533347,77898274,68110330,86118021,64157906,54427233,73124510,7685448,55602660,30891921,14349098,89811711,37739481,78300864,32426752,17857111,6038457,17386996,91281584,10649306,32151165,9575954,83948335,3183975,29065964,66611759,4199704,1569515,65880522,91240048,68824981,60955663,73392814,61960400,44473167,94711842,7517032,66319530,88130087,35456853,22405120,98943869,47887470,13470059,99224392,47298834,56955985,17146629,68102437,65454636,79191827,77187825,9860195,83155442,17081350,76170907,11581181,23848565,72358170,89699445,91938191,97940276,26863229,30366150,80246713,45075471,30811010,38341669,75820087,27325428,6525948,30998561,72614359,75986488,6871053,62803858,34698463,81774825,93359396,83133790,20836893,57020481,13862149,82327024,18783702,99658235,17058722,86301513,49597667,15075176,51426311,62232211,22942635,3235882,34667286,60106217,95581843,79322415,90158785,73109997,93790285,48675329,11757872,63506281,7104732,29635537,74452589,2331773,59910624,749283,19457589,86821229,4508700,29401781,61928316,52293995,33895336,34295794,62357986,29510992,23134715,49882705,37957788,84002370,45667668,69255765,38009615,97379790,19272365,98653983,26744917,47792865,18001617,18411915,94360702,65074535,59329511,53666583,85023028,67227442,9599614,61712234,41442762,15948937,95251277,76330843,68316156,10453030,34044787,28787861,75052463,77301523,38256849,4985896,84166196,52204879,70782102,48360198,72495719,33628349,67513640,37224844,89499542,47213183,59405277,36845587,39801351,7687278,87160386,67124282,37675718,47708850,97281847,99729357,24953498,10961421,23740167,95726235,51507425,28851716,51715482,67031644,33935899,55669657,44847298,17037369,8913721,37280276,4798568,57359924,54868730,60176618,48395186,51149804,19898053,98948034,82532312,54263880,4978543,33061250,44915235,91957544,6111563,30090481,45049308,21919959,83747892,51315460 +21993752,99515901,14731700,47361209,37620363,77312810,14220886,6986898,55247455,32159704,66832478,55753905,10309525,59197747,52261574,86543538,76960303,66250369,15015906,1569515,96193415,26139110,62552524,53666583,67793644,45237957,9886593,14626618,29834512,83150534,77284619,15902805,17764950,78602717,1204161,4064751,48892201,67281495,77413012,14947650,51047803,24619760,62496012,50702367,30463802,55470718,54606384,59177718,92033260,9257405,93933709,27665211,68939068,67124282,48260151,40865610,91664334,83651211,75543508,669105,18504197,70420215,54014062,82178706,48675329,99524975,20486294,35192533,77072625,41245325,42692881,80014588,6808825,96726697,33935899,12571310,23848565,95726235,84684495,73786944,33249630,44842615,31904591,92787493,79821897,45848907,66322959,45381876,54199160,99604946,1197320,62936963,98130363,72373496,82726008,38494874,3487592,96420429,66045587,17068582,69355476,16405341,56153345,42644903,55850790,26292919,85242963,48893685,66137019,49641577,12664567,36930650,10366309,47090124,78909561,81677380,70036438,65038678,7011964,50007421,53632373,53802686,73124510,69848388,89214616,74137926,77272628,88653118,78766358,40781854,3233569,90272749,34497327,36753250,7300047,569864,38009615,36780454,30764367,16595436,88904910,75128745,32151165,81855445,20002147,32161669,88047921,24915585,89217461,14349098,68816413,71316369,63372756,11757872,26998766,44177011,17386996,45428665,60430369,74614639,22997281,20122224,96735716,29959549,81274566,59910624,85116755,16445503,51464002,45617087,68875490,24058273,99917599,74357852,4119747,16387575,35092039,44889423,76330843,40356877,64848072,37501808,21533347,57248122,33061250,42307449,74110882,98371444,66116458,37224844,92692978,50567636,99965001,51426311,75153252,22766820,53393358,95251277,79136082,72357096,6819644,415901,53084256,7182642,66667729,44983451,62428472,43376279,1250437,44847298,247198,94090109,46870723,21673260,18699206,71333116,69786756,8634541,78589145,64087743,78884452,36808486,6221471,86460488,19101477,99125126,60102965,42199455,28550822,12024238,93053405,44784505,37675718,68644627,176257,3235882,61728685,65715134,4508700,38658347,5832946,74724075,20140249,8807940,69255765,60106217,37183543,29932657,58751351,65017137,87598888,44479073,14093520,90310261,90013093,44473167,3294781,83948335,62452034,26863229,44481640,22942635,53648154,82339363,84127901,38061439,526217,17385531,64602895,99021067,20645197,24591705,56515456,22450468,17365188,82052050,68694897,77300457,34044787,72614359,84840549,6793819,10358899,88444207,13348726,37891451,62803858,40534591,61859581,36312813,79922758,50806615,30163921,92541302,3233084,28796059,17081350,86001008,42967683,39171895,94911072,93566986,27187213,29065964,51466049,54663246,31733363,66428795,39553046,73168565,62051033,49328608,68102437,21289531,61373987,57803235,18466635,9398733,63015256,85571389,30395570,824872,34236719,55143724,34493392,38645117,76315420,40781449,63059024,34946859,73235980,26734892,508198,73031054,65851721,78218845,2204165,57241521,89046466,69641513,19939935,22129328,66319530,8729146,68000591,54427233,32058615,99226875,99224392,92353856,33201905,7104732,4099191,11543098,4515343,54517921,65271999,43152977,16971929,40677414,26102057,55189057,13173644,82897371,97783876,39801351,42237907,48774913,74452589,5111370,17894977,5073754,92071990,321665,43501211,50188404,35512853,26275734,70596786,59371804,61982238,96318094,49236559,40197395,99861373,8651647,6871053,32699744,63967300,86301513,9259676,80316608,30366150,99549373,61263068,42073124,26392416,9603598,76170907,66903004,94711842,95290172,31491938,36505482,1431742,62430984,15535065,75777973,30139692,1936762,75229462,8696647,2891150,82979980,91802888,74743862,66611759,6505939,78561158,67451935,68702632,97011160,29029316,112651,55960386,33304202,6153406,69579137,23194618,92215320,76434144,81898046,76703609,76540481,73222868,22405120,56531125,64157906,39847321,16567550,81805959,24826575,45407418,22113133,73392814,42782652,62693428,30891921,77620120,18411915,26776922,30653863,4069912,57961282,57393458,2917920,68316156,57240218,56461322,53547802,49271185,79657802,40027975,75986488,44060493,29819940,40152546,85711894,77694700,30694952,45075471,33628349,24953498,14045383,5970607,47792865,26397786,32426752,30787683,57020481,48360198,83789391,80555751,69697787,16684829,46851987,94516935,44627776,83747892,8733068,85023028,44550764,68824981,59981773,29994197,12348118,47738185,31126490,19898053,82651278,16054533,45306070,20836893,58749,63506281,4091162,72274002,64098930,7517032,7687278,3880712,91831487,70541760,18806856,70800879,14723410,24168411,98739783,62232211,9623492,48395186,91240048,14363867,70727211,17957593,29188588,8373289,98648327,58208470,75135448,27041967,72732205,89637706,12416768,72495719,38022834,66271566,3183975,33699435,57658654,36812683,49598724,41092172,40686254,97057187,37659250,82327024,9829782,20568363,90654836,18833224,28928175,96906410,71657078,7919588,15163258,90090964,69136837,61141874,10649306,60581278,20765474,76671482,46540998,36933038,15948937,6038457,71920426,99297164,34295794,8115266,73021291,32250045,4204661,35456853,44846932,91990218,30998561,16019925,24314885,36139942,2331773,58180653,27411561,35996293,72793444,4095116,9175338,17727650,4434662,92998591,7229550,72019362,68128438,34698463,92554120,83210802,4798568,57359924,22200378,90457870,71469330,81853704,18131876,86118021,61712234,1788101,92692283,94539824,56424103,26507214,19376156,1022677,79880247,48088883,37560164,54232247,49882705,83368048,60176618,47298834,18783702,95395112,79806380,39373729,44844121,93057697,22435353,89499542,92604458,15148031,7423788,56484900,68041839,50668599,8099994,51149804,13819358,72238278,44664587,58224549,42947632,8129978,38365584,80246713,45990383,25842078,99690194,16097038,72738685,61271144,67030811,99971982,21070110,37166608,70004753,17857111,69605283,2208785,30811010,84349107,30543215,2717150,4787945,61623915,83155442,31161687,32590267,84293052,41442762,29516592,83533741,55770687,77787724,36135,52204879,68110330,3088684,7685448,19486173,19272365,79322415,26114953,70372191,59501487,7502255,79738755,71522968,4668450,91141395,47893286,34698428,94360702,16380211,37739481,73617245,16099750,17037369,18663507,47708850,13468268,90004325,87720882,3633375,89804152,71965942,87160386,52293995,15075176,65275241,48658605,8913721,67474219,72358170,3509435,5482538,20867149,84166196,97281847,168541,7646095,10961421,45794415,61741594,37435892,33431960,98462867,37957788,98653983,9575954,11161731,6497830,65338021,84904436,92398073,75407347,19891772,89419466,84187166,49597667,17976208,89811711,61928316,92530431,38256849,72777973,97561745,26063929,97940276,29510992,27625735,36580610,68204242,65081429,89996536,36845587,99658235,93515664,23134715,85117092,89078848,51715482,55615885,45049308,98948034,11365791,71300104,45996863,31727379,18001617,65454636,87710366,45995325,27185644,61815223,30090481,77301523,43045786,8128637,4806458,59614746,63152504,95581843,30569392,33123618,59109400,56473732,86022504,33565483,97641116,96852321,39986008,66885828,96709982,13862149,38341669,17016156,84002370,86242799,78549759,86798033,7955293,90061527,29401781,95957797,62740044,36468541,47887470,17058722,5267545,23432750,17146629,51472275,21919959,52613508,53842979,14326617,40984766,88698958,93790285,15064655,45667668,86821229,30771409,84406788,27325428,77377183,43357947,65880522,98943869,24733232,67084351,26493119,22721500,52060076,60955663,63628376,11581181,79942022,59405277,91727510,65047700,20642888,39201414,83083131,59318837,26744917,99333375,23110625,93270084,6157724,54987042,89699445,81172706,9860968,33336148,95010552,67227442,91938191,82886381,72725103,13231279,51590803,79191827,91957544,33797252,66663942,82779622,62357986,54263880,62762857,54058788,57163802,94595668,38008118,33895336,55669657,22879907,51507425,10597197,45790169,67513640,51588015,23569917,53888755,85695762,41481685,78300864,92803766,29466406,65074535,93359396,19457589,77187825,61960400,36685023,82427263,15536795,54868730,47213183,9058407,56955985,78785507,61859143,83302115,7066775,59329511,71083565,20885148,48673079,70367851,10453030,72278539,28734791,874791,6525948,43506672,9599614,41092102,43933006,97379790,5640302,94076128,75052463,28851716,6111563,33237508,74441448,30218878,55602660,22108665,37192445,80251430,62115552,9188443,44348328,43322743,16424199,749283,8055981,28787861,83269727,11923835,23740167,20681921,26664538,81774825,88251446,76825057,91255408,36396314,13470059,5822202,34667286,82532312,60393039,44245960,29635537,44915235,4985896,37280276,12303248,34432810,9860195,36189527,21397057,33553959,2607799,75820087,83133790,25636669,4199704,10264691,3773993,1239555,45919976,54762643,88130087,73109997,21001913,93562257,17539625,91281584,4978543,90158785,70782102,64055960,32274392,8791066,61897582,67031644,41380093,92867155,77898274,99729357,1375023,38510840,6521313,51315460,62490109 +6505939,68816413,40197395,89214616,78766358,34493392,35092039,61728685,71920426,67084351,99515901,22879907,36930650,99917599,26397786,48260151,67124282,85242963,17016156,83789391,65017137,70004753,68000591,27665211,62496012,20140249,28851716,13173644,74110882,12348118,99965001,53842979,36753250,50806615,83210802,9603598,75128745,57020481,59371804,20122224,57248122,73124510,8651647,14326617,97783876,44177011,69579137,18783702,98943869,11161731,10366309,47298834,86118021,72495719,73031054,59910624,35512853,45667668,72725103,96852321,92998591,6986898,96906410,26275734,29994197,23848565,66322959,51047803,53547802,62936963,669105,19457589,67030811,63506281,9623492,46870723,54517921,50007421,99524975,98739783,84840549,90457870,67281495,5073754,33123618,8696647,39171895,32151165,74441448,44842615,74614639,42073124,62430984,60102965,91281584,33201905,55247455,65715134,415901,7687278,71657078,95957797,6808825,75777973,33249630,50188404,85711894,51464002,62693428,247198,5111370,9188443,95395112,61859143,41092172,99604946,89419466,79880247,64098930,82897371,17058722,22766820,34295794,92787493,2331773,526217,14045383,82979980,10597197,53802686,9398733,6153406,48893685,56955985,88904910,13468268,86460488,76330843,61141874,88653118,63372756,95010552,1197320,55753905,30366150,3294781,13231279,44473167,66428795,14947650,69848388,59501487,30771409,88047921,3233084,45919976,89078848,90310261,75543508,54014062,24591705,36780454,99549373,29834512,70036438,49328608,45996863,32274392,45995325,54606384,89804152,76434144,64087743,37739481,8913721,16380211,19272365,6521313,9575954,82886381,3183975,74137926,30090481,26744917,43933006,64602895,99861373,92692978,7502255,49597667,33895336,508198,33553959,99971982,75986488,5822202,95581843,63967300,27185644,54199160,22129328,77312810,88444207,40356877,26114953,19376156,62232211,47738185,68875490,56515456,33797252,59197747,36505482,30395570,61982238,67227442,8634541,18411915,66903004,84293052,83155442,6221471,44479073,79821897,99125126,33237508,18466635,55615885,44889423,68644627,12571310,39553046,31126490,38008118,17764950,36812683,66045587,50702367,80555751,18131876,24826575,51149804,31727379,18699206,48673079,66611759,76540481,35192533,83747892,69786756,83651211,3233569,45407418,43357947,9599614,99658235,79738755,70367851,54762643,75820087,96735716,44060493,78218845,90272749,4069912,10961421,6111563,67793644,569864,21919959,70596786,16405341,62452034,92398073,39801351,70420215,26392416,14349098,92215320,76671482,6871053,53666583,62803858,22942635,61897582,40534591,18663507,23194618,37620363,37957788,23432750,22997281,11757872,91664334,81677380,1431742,97940276,72777973,17068582,7423788,71316369,1022677,37183543,84349107,20642888,29819940,24058273,92071990,49271185,16971929,55189057,61960400,749283,45790169,80251430,90090964,40865610,71300104,45428665,26507214,55602660,44983451,30998561,59177718,26998766,51715482,85571389,68204242,48774913,92530431,70727211,43376279,81172706,94090109,30811010,62357986,95290172,83368048,50668599,14093520,68694897,4119747,83269727,70782102,48088883,86001008,72732205,76960303,32058615,73617245,38022834,176257,66137019,56153345,49641577,52293995,78884452,91831487,84904436,42307449,41442762,59109400,21070110,77377183,22108665,72373496,9175338,69255765,73235980,37166608,95726235,17976208,21533347,22113133,3880712,79322415,46851987,70800879,10309525,71522968,54263880,61623915,82532312,7517032,874791,62051033,61373987,99297164,38494874,30569392,64055960,36135,91957544,74357852,42967683,43506672,98653983,19939935,68102437,81774825,2607799,19486173,7300047,85116755,49236559,66271566,39373729,13348726,20486294,49882705,77620120,9058407,38341669,16445503,72274002,44245960,43045786,98948034,63059024,92604458,65271999,68824981,43322743,5267545,53084256,3509435,98130363,75407347,57241521,74724075,53632373,77187825,55850790,32161669,53648154,34698428,29959549,7955293,23569917,52261574,29466406,31904591,90004325,77413012,28928175,2204165,56424103,73021291,23134715,97057187,61815223,53888755,42782652,5970607,81274566,60955663,56473732,40686254,85023028,16019925,65338021,89046466,79191827,47213183,68041839,21993752,36685023,36845587,94711842,51507425,37675718,30891921,45848907,65038678,30463802,45306070,39847321,29188588,92353856,78602717,26493119,54868730,4091162,168541,38009615,17727650,29401781,43501211,99224392,97011160,61712234,88698958,19898053,91990218,20002147,34698463,1204161,79136082,4787945,26292919,82178706,16097038,35456853,92541302,47792865,73168565,91240048,73222868,27041967,40781854,23740167,60106217,47887470,48360198,1788101,33565483,99021067,57803235,8791066,76170907,26102057,18833224,30543215,93566986,17957593,66832478,86301513,77694700,48675329,66250369,27187213,20681921,56484900,89811711,84187166,39986008,94076128,6157724,69605283,33628349,3235882,33304202,69641513,2917920,88130087,99729357,40152546,79657802,8099994,30139692,14220886,15064655,4515343,54663246,36312813,24953498,42237907,9257405,17081350,7182642,98648327,321665,17857111,26664538,29635537,41481685,67031644,15148031,44550764,80316608,72238278,84127901,2208785,91802888,45237957,87598888,37280276,90013093,28550822,90654836,82339363,22200378,66667729,38365584,19891772,20765474,17037369,29065964,30787683,40781449,82726008,4095116,38061439,20885148,96193415,22450468,44348328,77898274,37224844,42199455,69697787,99226875,92033260,65275241,96726697,15163258,30694952,36139942,93790285,87160386,78589145,65851721,31491938,97561745,66319530,62740044,94595668,52613508,13470059,92554120,26063929,85695762,85117092,84684495,9860195,7685448,26139110,31161687,6819644,89217461,30764367,15015906,23110625,71083565,93515664,32250045,78300864,93053405,68939068,96318094,77072625,78909561,18806856,70541760,84002370,61271144,24168411,29932657,18001617,72357096,92692283,65047700,1375023,24733232,77272628,24619760,13819358,62552524,55470718,36396314,73392814,22721500,95251277,96420429,27411561,67513640,9259676,37659250,82052050,68316156,40984766,56531125,74743862,11581181,93933709,65880522,76703609,55143724,36933038,7919588,44784505,19101477,44915235,14626618,41092102,51426311,1250437,15535065,30163921,15948937,71333116,34497327,7229550,89637706,93562257,47708850,38645117,61928316,67474219,77300457,6497830,83150534,45381876,57393458,69355476,8373289,29029316,40677414,33061250,72019362,21001913,72793444,37501808,17385531,28796059,43152977,94360702,69136837,11543098,7104732,67451935,18504197,82327024,79806380,65074535,37192445,60430369,28787861,1239555,92867155,5832946,89499542,44846932,16054533,66885828,56461322,65081429,74452589,99333375,45075471,32699744,58751351,63015256,41245325,50567636,45990383,82651278,4204661,78561158,6793819,824872,45617087,10453030,47090124,11365791,21289531,86821229,80014588,35996293,45049308,97379790,83533741,44481640,84166196,17386996,65454636,8733068,59405277,83948335,7011964,81898046,31733363,92803766,76825057,52060076,36468541,64848072,75229462,75135448,21673260,26734892,47893286,3773993,33935899,32426752,8729146,57240218,90061527,1936762,57163802,15902805,21397057,98371444,94516935,36808486,8807940,10649306,86543538,42644903,112651,15075176,4806458,36189527,41380093,76315420,83133790,34432810,14363867,72614359,26863229,37435892,16387575,5640302,83083131,4508700,91938191,48658605,4798568,49598724,72278539,10358899,6525948,48892201,42692881,51588015,16567550,77787724,34044787,86798033,7646095,34946859,47361209,81805959,87710366,58224549,91255408,51472275,55669657,42947632,14731700,44844121,93359396,79942022,8129978,62115552,53393358,63628376,62762857,97641116,44847298,73109997,99690194,17539625,12416768,61859581,37891451,68702632,72738685,54427233,20836893,37560164,30653863,16099750,3633375,40027975,33336148,5482538,59981773,4668450,57961282,72358170,66116458,55960386,48395186,64157906,17365188,81855445,54058788,2717150,4434662,77284619,71469330,16595436,38256849,32590267,25842078,71965942,3487592,51466049,51590803,58749,34236719,8128637,86242799,12664567,27325428,54987042,89699445,84406788,86022504,39201414,44627776,3088684,8055981,75153252,13862149,61263068,66663942,63152504,10264691,14723410,60581278,91727510,32159704,27625735,20645197,78549759,51315460,97281847,52204879,75052463,83302115,4064751,15536795,89996536,4099191,2891150,20568363,6038457,29516592,44664587,36580610,58208470,60393039,24314885,9886593,45794415,12024238,78785507,59614746,28734791,11923835,68110330,81853704,33699435,94539824,54232247,88251446,16684829,94911072,4985896,9829782,8115266,20867149,17146629,93057697,25636669,87720882,68128438,93270084,46540998,1569515,4199704,17894977,79922758,16424199,24915585,90158785,7066775,73786944,22405120,59318837,57658654,59329511,22435353,29510992,57359924,96709982,61741594,12303248,70372191,82779622,38658347,77301523,38510840,55770687,30218878,80246713,91141395,33431960,58180653,82427263,26776922,98462867,62490109,4978543,60176618,9860968,34667286,62428472 +83789391,7687278,3235882,32159704,76540481,29466406,24058273,10366309,20885148,46870723,38061439,70596786,33336148,99658235,68000591,38658347,65074535,36812683,3773993,33628349,6871053,72495719,61373987,4119747,66271566,67793644,63059024,81853704,42782652,85116755,39847321,45848907,76434144,63967300,75229462,39201414,60430369,79942022,48893685,36396314,53084256,36312813,61263068,81805959,68644627,84904436,66045587,37659250,48360198,3487592,9058407,95957797,94711842,96193415,44627776,40984766,3233084,83533741,17058722,72732205,74357852,34432810,52261574,59197747,168541,24591705,45667668,2917920,16971929,33201905,9175338,24619760,60106217,80014588,70420215,33061250,96726697,29932657,29029316,74441448,93057697,99524975,4798568,62051033,60581278,66667729,78766358,15015906,75153252,62452034,86118021,39801351,88047921,31491938,62936963,12348118,76315420,69355476,35092039,93933709,70800879,23848565,9603598,95581843,59109400,89699445,71316369,55247455,69697787,8729146,70727211,17539625,44842615,15535065,14626618,27185644,72019362,72614359,69579137,76671482,72738685,75777973,77272628,85023028,55770687,44177011,66428795,8129978,19272365,10358899,36753250,99125126,65851721,36808486,36685023,26392416,44060493,62496012,66250369,6819644,88653118,13470059,71657078,15163258,26292919,63628376,247198,7229550,81274566,17068582,25842078,62803858,40677414,73222868,6793819,7955293,71469330,19939935,33565483,20002147,68316156,82427263,92787493,19891772,45407418,86821229,91255408,46851987,32151165,99515901,71083565,33935899,49328608,54663246,874791,48774913,9623492,50188404,2717150,77284619,83210802,55753905,53802686,75543508,19376156,88444207,80246713,72274002,70367851,30463802,68816413,82339363,93359396,415901,36135,68824981,2204165,89419466,53547802,89214616,91802888,34493392,72725103,16405341,5073754,70541760,97940276,72777973,62357986,4064751,59177718,45919976,77620120,15148031,82726008,4668450,9259676,45790169,31727379,81677380,28796059,15948937,44889423,40865610,22108665,27325428,29834512,85711894,5970607,23569917,89046466,6808825,29994197,37560164,569864,47792865,49236559,112651,40197395,37620363,30569392,98371444,7502255,89078848,58749,90457870,8128637,66663942,45990383,98739783,76703609,17365188,34295794,67031644,77312810,21993752,51047803,11543098,43376279,92692978,65338021,16387575,99021067,23194618,39553046,21533347,82897371,73124510,32590267,16019925,92998591,99297164,57359924,22721500,17957593,74743862,95010552,4099191,25636669,53666583,71333116,73617245,39171895,70782102,45075471,87720882,36468541,38365584,20765474,44847298,57803235,16097038,54199160,42307449,77413012,47213183,11365791,27665211,91831487,4199704,78909561,8733068,66611759,61859581,78300864,86022504,53632373,18806856,94090109,6497830,61859143,40152546,92803766,1204161,26998766,17081350,21070110,92554120,66137019,37224844,18504197,2607799,1431742,84840549,84293052,40534591,35192533,37183543,35512853,49641577,86460488,71920426,50702367,28851716,56531125,4515343,1022677,15075176,59318837,84127901,8099994,5111370,17764950,47361209,72278539,92604458,17857111,36933038,5267545,36580610,61982238,6986898,321665,93515664,81855445,41092172,68204242,30366150,4204661,48673079,43322743,73109997,21001913,14326617,30218878,91727510,53842979,50567636,14093520,40027975,18783702,17894977,11581181,37192445,83133790,55850790,90061527,34698463,37675718,90310261,17146629,82532312,92541302,6153406,87160386,30787683,64848072,98130363,83155442,96318094,7423788,50668599,30811010,61712234,30163921,31733363,84187166,67124282,97641116,44664587,34698428,56153345,9575954,87598888,15064655,43501211,79922758,51472275,59981773,67030811,70372191,66885828,80316608,4069912,49271185,94360702,54606384,20486294,18466635,54014062,26275734,99224392,38008118,96709982,5832946,61928316,18833224,22435353,98948034,19457589,7919588,36189527,37166608,30653863,44983451,29959549,20836893,75128745,77377183,16445503,80555751,64602895,73168565,29065964,51315460,26493119,73235980,42947632,34946859,38494874,75820087,79821897,26734892,34497327,73786944,43152977,95726235,69786756,26863229,63015256,3088684,67474219,77301523,26063929,93053405,73392814,99690194,59329511,95251277,71300104,526217,66903004,79136082,92215320,3183975,10961421,51590803,14045383,33249630,78884452,24168411,92692283,70036438,9188443,51464002,55669657,33123618,50007421,68875490,99549373,72357096,65880522,66832478,56424103,45049308,79191827,3880712,30139692,84349107,98462867,97379790,33237508,23432750,8651647,669105,29819940,85117092,31904591,94516935,97561745,46540998,13173644,52060076,70004753,43933006,83302115,61741594,1375023,67451935,37501808,86301513,29635537,78602717,9599614,35996293,60102965,32250045,55189057,83651211,82779622,3633375,75986488,52293995,83083131,508198,39986008,13468268,76960303,90654836,61815223,6221471,17037369,82979980,9257405,14349098,45237957,35456853,33431960,92033260,2891150,82651278,87710366,85242963,77072625,16380211,4091162,48088883,51149804,76330843,93270084,53888755,97057187,1569515,16595436,40356877,65271999,41481685,17385531,68041839,93790285,99333375,49597667,4787945,86543538,58224549,19101477,78561158,93562257,40686254,37739481,22129328,61271144,54427233,15536795,69848388,8373289,56484900,23110625,3294781,9886593,57241521,69641513,77300457,1788101,1197320,27187213,69255765,28928175,68110330,21919959,36505482,22942635,96735716,64157906,68702632,7685448,42692881,65715134,10453030,30771409,61960400,44479073,20568363,62740044,99861373,7104732,17976208,40781449,96906410,86242799,77787724,43357947,91938191,37891451,78785507,61141874,14220886,83150534,60176618,2208785,44550764,44245960,92398073,18131876,9398733,26776922,62762857,53393358,81898046,55602660,44915235,59501487,8696647,99604946,26139110,7011964,43506672,78218845,22405120,9860968,91664334,19486173,11161731,57020481,44481640,824872,21673260,45996863,68694897,4434662,62693428,28550822,88698958,54263880,33797252,51426311,79806380,20140249,22450468,51507425,44784505,33895336,59371804,24953498,11757872,10649306,74137926,7646095,53648154,30764367,34236719,47887470,84684495,48892201,44846932,9829782,12303248,38256849,65017137,72373496,79880247,68102437,94911072,23134715,29188588,29401781,74110882,89811711,61728685,69136837,89217461,94539824,67227442,75052463,49598724,20642888,62232211,47893286,91990218,52613508,6111563,27625735,9860195,11923835,33553959,32426752,42199455,37957788,45428665,85571389,58208470,31126490,5482538,55960386,89804152,59614746,55143724,65047700,77187825,38645117,28734791,90004325,14731700,24915585,16054533,56955985,18699206,22766820,43045786,57658654,7517032,34044787,64055960,90013093,41245325,91281584,26744917,20867149,3509435,8807940,68939068,30543215,1250437,80251430,6038457,48260151,14363867,84406788,24733232,85695762,36780454,42967683,38022834,31161687,23740167,44844121,33304202,97011160,22997281,24826575,55470718,3233569,19898053,50806615,99226875,47298834,62430984,93566986,89637706,10597197,82052050,176257,66319530,68128438,26397786,56461322,30891921,97783876,12664567,5822202,16684829,21289531,8634541,57240218,81172706,88904910,89996536,76170907,74614639,58751351,59910624,91957544,97281847,57163802,17016156,14723410,20645197,48658605,59405277,45306070,63152504,18001617,49882705,27411561,62490109,84166196,5640302,82178706,29516592,67084351,33699435,749283,30395570,78589145,32161669,38341669,94595668,41442762,26114953,22200378,47090124,4978543,36930650,4985896,6525948,6521313,30694952,26507214,77694700,7182642,88130087,92353856,95395112,21397057,89499542,66322959,54987042,52204879,74724075,75407347,12024238,1239555,67281495,41092102,36139942,54868730,88251446,86001008,57961282,99917599,42073124,32699744,96420429,54232247,83368048,30090481,79657802,8791066,26102057,73031054,20681921,54762643,57248122,74452589,82327024,26664538,72358170,29510992,24314885,54517921,48395186,56515456,61623915,37435892,6505939,65275241,16567550,98943869,44348328,63506281,64087743,99965001,86798033,61897582,16099750,40781854,18411915,96852321,60955663,36845587,10309525,28787861,75135448,71965942,17727650,90272749,22879907,42644903,14947650,16424199,62552524,95290172,90158785,79322415,44473167,83948335,81774825,4095116,41380093,39373729,7066775,12416768,13862149,62428472,56473732,79738755,47738185,45995325,48675329,45794415,51715482,22113133,20122224,15902805,54058788,8055981,77898274,27041967,65081429,12571310,2331773,63372756,55615885,78549759,58180653,8913721,51588015,57393458,45381876,13231279,98648327,62115552,67513640,4508700,65038678,65454636,32058615,32274392,7300047,34667286,6157724,38009615,66116458,82886381,72793444,30998561,90090964,42237907,76825057,91141395,84002370,92867155,69605283,8115266,92071990,51466049,47708850,60393039,83269727,73021291,91240048,4806458,99971982,45617087,64098930,71522968,94076128,18663507,83747892,1936762,13348726,98653983,92530431,38510840,37280276,10264691,17386996,13819358,99729357,72238278 +45990383,21993752,16019925,30543215,90272749,59371804,63059024,91664334,45237957,27665211,77413012,16099750,73031054,68110330,3233084,45428665,3487592,44889423,66137019,51472275,75153252,62496012,39986008,45919976,62936963,68204242,53393358,26863229,92398073,85116755,70004753,87598888,59177718,86001008,90090964,61928316,6793819,68041839,53547802,79821897,22108665,30764367,69848388,88653118,77301523,12348118,2917920,55247455,11543098,99333375,47738185,14947650,36812683,7687278,7502255,63967300,2891150,6153406,86022504,50567636,29188588,54427233,16054533,74110882,89699445,13468268,62490109,66611759,89804152,42782652,98130363,29819940,19891772,4119747,40865610,17857111,7955293,62452034,44784505,3088684,4787945,73235980,26139110,61815223,33237508,77284619,99658235,99917599,9575954,50188404,86543538,69579137,13231279,18466635,4204661,31733363,36685023,60955663,31126490,45407418,45667668,30771409,95290172,61859581,64848072,74614639,17764950,97783876,54014062,55753905,57240218,99861373,15015906,53648154,82327024,72357096,10366309,34236719,93933709,36780454,96726697,3183975,20140249,78300864,53666583,71469330,47361209,61271144,33935899,62428472,23194618,88047921,7182642,65047700,25636669,78589145,6986898,37435892,62051033,18504197,28550822,89811711,91990218,43501211,8733068,55143724,65271999,16445503,91255408,35092039,749283,23848565,93053405,415901,8696647,2717150,3294781,60581278,45848907,41092102,40197395,17068582,61728685,20867149,6505939,82979980,9886593,71316369,12416768,20486294,42967683,56153345,8128637,27411561,8807940,80555751,10649306,32590267,72274002,22942635,91141395,40356877,84904436,45996863,99224392,81898046,18833224,39847321,95726235,96193415,67793644,66903004,47893286,93562257,6525948,48675329,77187825,18783702,15902805,3880712,34493392,32250045,68644627,32159704,4798568,40152546,89046466,29834512,71333116,17058722,77072625,31904591,35456853,57393458,17539625,6808825,99524975,66832478,49641577,3233569,508198,92554120,38022834,65454636,89217461,70596786,49328608,81774825,34295794,31491938,86242799,92998591,78602717,83651211,82726008,49236559,59318837,24058273,85242963,14045383,22997281,32161669,79136082,71920426,70541760,48088883,72358170,91957544,26744917,89214616,32426752,39171895,82532312,19898053,57163802,9603598,88698958,72019362,12024238,57961282,48774913,14626618,99515901,82339363,80014588,14220886,91240048,55470718,83789391,62803858,73168565,97379790,51507425,26292919,75777973,9829782,92033260,57241521,23110625,37957788,37501808,20122224,5970607,98948034,42073124,82427263,74357852,26507214,44550764,65074535,9398733,19457589,73786944,84406788,67451935,63628376,1788101,37166608,22766820,61373987,36312813,91938191,11161731,11581181,247198,4985896,37620363,96318094,73222868,669105,20765474,96852321,14093520,68102437,57803235,59197747,66663942,29065964,38365584,44481640,92692978,51047803,18699206,569864,92867155,86460488,37183543,33797252,83269727,44177011,54663246,52060076,36933038,36930650,93057697,29510992,26063929,82178706,36753250,30694952,26776922,96420429,79880247,30163921,83533741,4434662,65851721,80316608,33628349,8634541,77620120,38494874,18001617,75135448,76540481,30787683,89637706,66667729,62232211,86821229,97641116,27325428,5482538,83210802,44479073,34698463,55189057,38008118,17727650,30998561,90004325,61263068,51590803,15075176,19939935,89419466,24591705,83155442,82651278,40686254,27625735,2607799,90457870,78766358,40677414,54762643,93515664,68875490,69786756,51588015,112651,77312810,84684495,5832946,43045786,99297164,64098930,26664538,8115266,47298834,44842615,82897371,63372756,55669657,55602660,9257405,84293052,93359396,84127901,74441448,98462867,47792865,40781449,85023028,54199160,67030811,43152977,95957797,99125126,38061439,8129978,37739481,48260151,9188443,79738755,26392416,71657078,51715482,68816413,36189527,87160386,65081429,82779622,57658654,16424199,6221471,4064751,40781854,48360198,5822202,47708850,70367851,10961421,73021291,26998766,7229550,66322959,24826575,70727211,89078848,7011964,45075471,1569515,68824981,79322415,81855445,20642888,22129328,36505482,39553046,9623492,82886381,58749,72238278,33201905,98371444,4806458,321665,37560164,9058407,64157906,28928175,79942022,81172706,17957593,34698428,34667286,35192533,16387575,5073754,45306070,67124282,40027975,94516935,66116458,24915585,56473732,4508700,90654836,78549759,4668450,63015256,40984766,67227442,59614746,69255765,58180653,47887470,61982238,85571389,99604946,52261574,65017137,17081350,63506281,3633375,76825057,68000591,58751351,92787493,68702632,84840549,77377183,81677380,57020481,72777973,94539824,19486173,95581843,4515343,95010552,99549373,76330843,5111370,23569917,73617245,76315420,51464002,6497830,54987042,76960303,79806380,29466406,65880522,85695762,85117092,17365188,43376279,526217,4099191,36468541,27041967,44473167,24733232,72725103,99965001,30218878,18806856,11923835,70420215,16097038,43506672,97940276,89996536,9860968,23134715,29932657,30891921,34497327,16595436,20568363,6819644,14723410,38645117,62552524,1197320,36808486,29994197,83368048,874791,10358899,54058788,45790169,17386996,50668599,81805959,57359924,50007421,84002370,21289531,53632373,26493119,59501487,37891451,59109400,42692881,33061250,14349098,76434144,69136837,67513640,88904910,62740044,92803766,6111563,36135,1250437,87720882,88444207,7423788,50702367,30139692,97281847,43322743,35996293,44844121,43933006,42644903,74137926,45995325,64602895,94090109,99971982,97057187,86798033,72373496,66250369,28796059,51466049,49597667,80251430,51149804,92541302,44915235,44664587,1936762,63152504,57248122,58224549,83150534,29029316,65038678,95251277,99729357,38658347,6038457,99021067,17037369,68694897,94911072,78218845,14363867,19376156,13173644,78561158,96709982,84187166,8913721,72732205,30569392,78909561,15163258,824872,38009615,49271185,91802888,24314885,9259676,68316156,66885828,82052050,17146629,4069912,7104732,44060493,74452589,54868730,54606384,71522968,39801351,13470059,68128438,1431742,37675718,16380211,66428795,14326617,42307449,11365791,28851716,30090481,28734791,17385531,73124510,33699435,56424103,14731700,34044787,48892201,21673260,28787861,62430984,43357947,24619760,17976208,18663507,26114953,64087743,75407347,75128745,70372191,69605283,69697787,73392814,44627776,77787724,33431960,98653983,24168411,9860195,67474219,9599614,78785507,7685448,98739783,2204165,86118021,93270084,70036438,22200378,48893685,93790285,97561745,83302115,36580610,37224844,37192445,7300047,94076128,6871053,30366150,1022677,65275241,53842979,81274566,41245325,88130087,76671482,20885148,91727510,97011160,176257,33895336,90158785,29959549,95395112,66271566,36139942,69355476,99690194,86301513,20836893,1375023,26734892,22450468,76170907,3773993,61960400,30395570,47213183,55770687,56461322,94360702,45617087,10309525,15536795,61623915,68939068,51315460,90013093,22721500,74743862,67281495,8729146,20681921,45794415,44348328,32699744,30811010,92604458,15535065,33249630,92692283,79657802,47090124,4091162,56531125,9175338,3235882,72278539,96735716,17016156,11757872,61859143,56484900,89499542,16405341,4095116,60176618,92215320,81853704,54517921,42199455,99226875,59910624,12571310,20002147,77272628,26102057,61141874,83083131,83948335,60102965,75543508,55960386,42947632,71083565,32151165,54263880,24953498,3509435,65715134,94711842,55615885,77694700,22405120,23740167,59981773,31161687,44846932,41092172,32058615,53802686,77898274,60430369,44983451,15948937,16567550,44245960,61712234,49598724,4978543,62115552,12664567,8099994,16971929,58208470,33565483,70800879,37659250,48658605,168541,72793444,30653863,32274392,27187213,2208785,25842078,74724075,53888755,54232247,50806615,33336148,18411915,34432810,8651647,55850790,36396314,64055960,38256849,92530431,13348726,61741594,48673079,29516592,62693428,70782102,66319530,92353856,66045587,96906410,22879907,79191827,5640302,10597197,23432750,46851987,30463802,7066775,10453030,53084256,69641513,72614359,71300104,21533347,41481685,67084351,34946859,75986488,21919959,61897582,87710366,77300457,59405277,80246713,48395186,75820087,71965942,75229462,39373729,44847298,83133790,45049308,56955985,21001913,67031644,45381876,91281584,85711894,7517032,51426311,10264691,15064655,15148031,2331773,35512853,12303248,41380093,84349107,98943869,26275734,40534591,62762857,78884452,1204161,26397786,8373289,90061527,22435353,7646095,8791066,38510840,13862149,19272365,73109997,52293995,72738685,22113133,41442762,91831487,65338021,76703609,83747892,27185644,42237907,39201414,33553959,8055981,31727379,33123618,56515456,6521313,38341669,21070110,79922758,92071990,20645197,94595668,62357986,46870723,6157724,46540998,37280276,33304202,1239555,36845587,17894977,29635537,84166196,60393039,59329511,7919588,75052463,49882705,93566986,72495719,21397057,5267545,19101477,29401781,60106217,52204879,13819358,98648327,88251446,90310261,4199704,18131876,52613508,16684829 +17764950,4099191,90013093,55189057,60430369,38061439,34497327,66667729,26998766,16405341,49236559,22405120,68041839,14093520,18806856,29834512,11923835,79821897,76540481,3509435,20765474,72274002,61859581,68204242,15535065,91240048,51472275,50702367,97561745,95581843,28928175,52204879,2331773,20002147,40197395,2208785,2607799,96735716,72357096,8099994,16097038,99965001,51047803,45428665,66045587,71300104,44627776,9058407,59501487,22721500,35512853,73392814,80316608,80246713,90310261,87598888,39553046,49271185,71920426,34236719,98130363,415901,69355476,99226875,57803235,89046466,99515901,7646095,38658347,87720882,6497830,61271144,30366150,55247455,73617245,8733068,20140249,68000591,58749,18466635,33336148,46540998,94090109,33895336,65271999,99971982,4515343,70800879,38365584,47361209,54663246,62936963,36753250,36780454,73235980,44784505,54987042,82178706,76170907,99524975,29029316,73168565,62803858,40356877,53632373,9623492,37192445,91664334,72793444,36312813,2717150,96906410,34432810,89214616,76703609,50188404,98943869,33061250,78589145,99917599,99224392,30764367,10358899,65074535,3183975,20642888,37739481,14349098,94539824,76671482,29188588,44842615,82651278,18783702,45794415,62740044,99549373,4668450,9599614,44844121,96193415,60176618,38645117,26493119,26664538,68644627,89996536,93562257,17539625,36933038,34493392,10597197,73124510,32274392,49641577,84002370,16445503,47893286,30463802,85242963,95957797,86460488,62496012,90158785,53547802,28796059,25636669,78602717,76434144,78909561,6521313,95726235,46870723,61373987,96852321,35996293,51466049,526217,43152977,66322959,73021291,59371804,77272628,7955293,14220886,85116755,81898046,21993752,67793644,40781449,36505482,27325428,3487592,39201414,16099750,79922758,63506281,70372191,42073124,84904436,89078848,85117092,18131876,1204161,168541,99297164,18833224,64602895,38022834,33201905,7687278,77620120,77413012,78218845,17081350,73786944,508198,93933709,44177011,34946859,92803766,92215320,80014588,95395112,1431742,35192533,26744917,59109400,81853704,33249630,44889423,83651211,85571389,824872,38341669,10366309,77694700,68102437,66250369,29994197,61263068,73222868,44481640,86118021,82979980,18001617,84187166,37501808,43376279,20486294,31161687,34044787,45848907,26507214,32161669,31126490,84684495,42692881,70596786,9603598,4798568,67451935,22113133,53666583,62232211,66885828,8373289,22450468,61623915,26397786,26275734,74357852,83083131,16567550,55470718,29065964,22942635,7502255,98371444,59981773,69641513,26863229,45996863,4119747,5267545,63967300,5482538,77072625,50806615,67474219,26139110,54014062,71333116,65047700,83133790,56531125,48088883,3633375,23569917,19486173,72725103,88130087,19272365,24058273,79136082,26102057,44479073,71965942,90457870,17727650,29959549,67124282,36468541,19376156,24619760,30787683,12024238,26392416,42199455,5970607,44983451,33699435,75543508,29819940,61741594,91281584,14626618,1569515,52261574,75777973,91831487,16054533,8651647,79322415,70004753,61728685,27411561,28851716,11581181,21289531,98948034,93057697,32151165,95010552,59197747,21070110,61982238,97281847,37620363,30163921,10309525,9575954,29635537,3880712,18411915,56955985,55770687,81855445,18504197,76315420,62490109,4199704,63059024,82897371,7229550,51590803,38256849,62452034,33797252,8791066,11757872,57248122,5111370,53802686,77312810,96318094,43045786,97783876,58180653,9829782,89699445,37675718,14947650,74743862,3088684,22879907,9398733,17146629,36580610,92604458,68816413,9860968,68316156,42782652,72732205,86242799,45075471,16595436,45617087,37891451,6505939,42237907,77284619,61815223,9257405,72738685,14326617,45407418,96726697,91802888,39171895,67030811,98648327,83789391,37183543,86798033,57240218,40152546,6153406,13231279,8807940,70727211,77377183,19457589,37560164,51464002,67227442,17016156,85023028,88251446,30543215,16971929,17957593,1197320,45667668,89217461,62693428,70036438,44915235,61960400,874791,36396314,99861373,92692978,56461322,15536795,49328608,40027975,93270084,88904910,10264691,13468268,41442762,99658235,11543098,56424103,19939935,61928316,32590267,82339363,55850790,6221471,4787945,19101477,32426752,58751351,98462867,86022504,49598724,17068582,41481685,7011964,6819644,24168411,12348118,91141395,83302115,6871053,1936762,92554120,17386996,6793819,33237508,57658654,12303248,15148031,81805959,18663507,8129978,60581278,54427233,4806458,53648154,83155442,23134715,40984766,64055960,26063929,27041967,73031054,4091162,69605283,89804152,75128745,96420429,66428795,9175338,14723410,22997281,74452589,7919588,48360198,96709982,56515456,2891150,43501211,3235882,92787493,4064751,87710366,43357947,20122224,19891772,41245325,31904591,78884452,1022677,2917920,92398073,50567636,55753905,8913721,4434662,63015256,65081429,72373496,40865610,92033260,4985896,61712234,35092039,36189527,18699206,30811010,31491938,67084351,112651,57241521,23110625,84840549,39801351,81677380,91990218,69786756,54606384,69255765,75052463,33628349,52060076,82427263,38008118,71083565,99729357,74724075,62051033,97940276,9188443,13862149,13470059,92071990,55960386,1788101,20885148,30139692,31733363,36135,74137926,95290172,55602660,59177718,79191827,29466406,48675329,34698463,1250437,36808486,30771409,59405277,55669657,33304202,569864,90090964,8055981,88653118,19898053,2204165,83210802,59910624,92353856,30653863,92998591,47792865,60955663,49597667,70420215,48260151,62552524,30998561,77187825,86001008,60102965,66137019,39847321,17037369,60106217,20568363,9886593,13173644,1375023,27665211,3773993,81172706,30569392,94516935,98739783,65715134,58224549,16684829,38009615,29932657,7423788,23194618,27625735,15163258,26114953,32250045,68875490,66116458,50668599,53842979,45381876,70541760,32159704,93359396,15015906,63628376,93790285,75407347,247198,72278539,669105,10453030,38494874,93053405,57393458,30891921,44847298,36139942,78785507,54232247,64848072,68702632,48892201,82886381,68939068,65017137,38510840,84293052,21673260,22766820,22108665,76960303,37435892,99604946,41092172,71316369,16387575,93515664,36930650,71469330,84349107,80555751,12416768,72358170,20645197,33123618,54058788,81274566,5832946,3294781,57163802,57961282,66319530,64098930,74614639,66903004,321665,72019362,65338021,36812683,54263880,41092102,62762857,23740167,99333375,27187213,5640302,54199160,86301513,71657078,93566986,82532312,15075176,94076128,77898274,44060493,67513640,54868730,11161731,15064655,37166608,44846932,59318837,39986008,92692283,6986898,62430984,72614359,92541302,66832478,45995325,29510992,3233084,30090481,44664587,69136837,51507425,6038457,16019925,74110882,66271566,47090124,72495719,47738185,78766358,37659250,40686254,53084256,12664567,82726008,55143724,21533347,3233569,83150534,89419466,34698428,69579137,54762643,48774913,48673079,24314885,72777973,97379790,99690194,63372756,94595668,45237957,13348726,90061527,31727379,4069912,75229462,88047921,30694952,6111563,88698958,97057187,56153345,24915585,90654836,176257,56473732,49882705,26292919,17365188,52613508,36685023,16380211,83368048,45919976,62115552,64157906,40781854,91957544,32699744,40534591,58208470,8696647,51315460,5822202,17894977,8115266,97011160,46851987,89499542,54517921,91727510,71522968,34667286,86543538,6525948,75135448,23848565,7182642,75820087,43322743,15902805,80251430,28734791,69848388,8634541,34295794,7517032,20836893,22129328,44550764,79806380,10649306,77787724,33553959,86821229,26734892,42307449,69697787,83747892,43506672,87160386,48893685,79657802,4978543,41380093,53888755,84127901,6808825,72238278,4204661,11365791,44245960,68128438,65275241,81774825,7066775,25842078,78561158,97641116,45790169,8729146,4095116,42644903,30395570,76330843,24733232,40677414,51426311,39373729,61859143,8128637,47213183,57359924,42967683,57020481,15948937,99021067,75986488,99125126,44473167,7685448,65454636,79880247,56484900,47298834,82052050,90004325,35456853,91255408,20867149,94360702,48658605,9860195,68824981,83533741,47708850,29401781,36845587,51715482,75153252,4508700,24953498,17385531,84166196,17857111,37280276,21919959,84406788,22200378,67281495,45990383,37224844,66611759,10961421,59614746,94911072,43933006,27185644,66663942,82779622,17976208,65880522,59329511,70367851,14045383,16424199,32058615,28550822,48395186,26776922,12571310,44348328,5073754,33431960,24591705,78300864,42947632,89637706,14731700,749283,67031644,29516592,14363867,62428472,45306070,89811711,79942022,74441448,60393039,13819358,65038678,68694897,63152504,68110330,33935899,51588015,76825057,78549759,7300047,94711842,33565483,22435353,7104732,52293995,77300457,90272749,21397057,50007421,28787861,21001913,45049308,20681921,30218878,91938191,73109997,88444207,61897582,62357986,83948335,77301523,85711894,61141874,24826575,53393358,1239555,92530431,98653983,95251277,51149804,17058722,47887470,79738755,92867155,85695762,55615885,64087743,6157724,65851721,37957788,70782102,83269727,9259676,82327024,23432750 +4099191,14626618,72373496,89214616,27665211,75543508,65074535,50567636,81855445,7687278,9623492,526217,73124510,60430369,508198,66250369,83302115,35092039,29994197,54199160,61263068,38061439,30090481,44481640,51047803,8913721,15535065,29029316,63628376,86821229,68644627,17764950,168541,5832946,95726235,61373987,70800879,19939935,32250045,82052050,47090124,6871053,89078848,10366309,9886593,59371804,98739783,66137019,4064751,42967683,67793644,43501211,55247455,61271144,99658235,63506281,13173644,72614359,56515456,30891921,10649306,93933709,47792865,70036438,15536795,68824981,43376279,16424199,7011964,97783876,96318094,34432810,91664334,80316608,31904591,74137926,6986898,55770687,26744917,26292919,79922758,33123618,16445503,68204242,26392416,34698428,70372191,247198,2917920,24591705,54663246,50188404,68694897,24058273,71657078,13470059,78766358,16971929,54606384,20122224,69355476,95290172,33797252,10358899,93053405,6153406,45075471,99965001,74743862,62452034,22108665,11365791,93562257,10309525,81805959,65038678,4668450,47708850,34295794,32161669,5111370,34946859,75229462,74724075,39986008,29065964,90457870,7517032,17385531,21993752,3633375,34497327,4515343,43322743,53666583,14093520,1569515,87598888,63059024,76703609,59318837,33201905,92554120,18833224,22879907,98371444,1022677,26139110,95395112,54868730,52613508,6793819,35192533,63967300,26734892,8634541,7229550,15064655,59109400,36812683,75777973,30395570,18466635,99861373,31161687,37659250,58751351,47213183,18783702,26063929,98130363,42947632,33304202,8651647,68702632,66667729,16567550,36930650,85117092,37675718,61728685,78785507,95957797,44784505,30139692,8733068,32159704,79738755,11581181,824872,46870723,60102965,25842078,90013093,11923835,57240218,79821897,39171895,42782652,91957544,1431742,32699744,9575954,88047921,70367851,3509435,81898046,26863229,40865610,45995325,67281495,56484900,26507214,94711842,98462867,321665,72019362,1204161,47738185,84904436,66611759,96193415,8696647,40781449,72274002,96420429,41380093,76540481,67227442,90272749,75986488,19101477,8373289,20002147,20885148,43152977,44889423,30764367,61960400,51149804,70420215,73031054,37183543,55753905,70004753,59177718,62740044,99297164,85116755,38341669,75153252,112651,63372756,4787945,81853704,18411915,93359396,7646095,48774913,85023028,33565483,19272365,42644903,38494874,69848388,569864,77377183,55143724,62803858,48260151,84840549,7423788,4119747,18131876,36780454,82339363,54987042,44847298,99226875,45407418,48892201,44842615,94516935,77312810,17016156,14363867,54014062,79191827,92033260,669105,83368048,56531125,415901,18699206,2331773,30366150,29834512,33553959,36580610,85242963,8055981,98948034,62936963,76170907,49328608,72725103,93566986,8729146,80014588,57658654,75407347,49236559,94076128,81677380,9398733,66885828,59197747,44245960,17068582,28796059,91240048,6808825,45794415,28851716,74614639,56153345,77300457,39201414,34236719,95010552,86301513,93790285,30998561,31733363,45049308,55470718,28928175,27185644,88653118,57163802,18806856,28734791,99604946,29819940,27041967,67513640,3773993,30787683,99333375,69579137,6497830,34698463,33249630,73168565,76671482,50702367,6525948,97940276,14326617,49641577,23569917,86460488,57803235,21673260,61982238,68939068,30218878,92692978,37192445,82886381,90004325,53802686,3294781,98648327,96906410,43045786,71300104,98943869,17957593,38658347,47361209,53648154,77787724,35456853,8807940,24314885,55960386,74110882,29959549,7919588,92215320,45667668,23848565,45919976,36753250,74357852,65271999,66903004,99125126,33061250,62693428,46851987,19891772,87710366,36312813,88444207,69605283,65338021,36808486,67031644,69136837,77694700,53547802,66045587,9829782,29635537,51472275,79136082,86001008,91831487,65715134,33895336,72495719,86543538,37891451,29510992,84684495,32426752,6819644,4798568,56955985,15948937,62490109,51588015,22450468,37739481,17365188,37957788,54232247,39553046,16380211,62357986,15075176,12571310,16019925,97281847,24733232,17146629,73222868,72738685,19486173,99524975,52204879,83150534,36505482,66663942,57248122,32058615,67124282,22997281,62115552,24619760,82897371,11161731,8129978,72238278,17727650,57359924,45617087,77284619,18663507,40027975,91281584,3235882,92398073,44348328,44479073,16595436,48893685,92998591,73392814,2607799,30694952,42199455,16097038,16054533,80251430,60955663,49597667,54058788,53632373,17539625,68000591,17976208,31126490,71333116,37166608,30569392,12416768,4806458,40152546,7685448,66319530,54427233,84166196,48360198,26114953,3088684,88904910,20836893,77620120,7955293,34044787,78884452,37620363,5822202,91938191,77072625,4985896,48088883,59501487,42307449,74441448,99224392,34493392,85571389,22721500,70596786,53842979,50668599,15902805,61859581,91990218,47887470,40534591,69255765,40197395,12664567,61859143,18001617,22129328,92803766,49598724,16387575,14349098,62496012,20681921,82726008,36685023,72732205,56424103,41092102,17386996,78561158,30463802,12348118,33336148,32274392,97379790,30543215,15148031,41481685,67084351,38008118,35996293,58224549,80246713,5970607,76330843,52293995,99515901,29466406,61623915,51466049,99021067,62430984,80555751,3487592,40984766,20765474,37501808,6221471,13862149,92071990,83651211,97561745,99917599,84187166,11543098,68816413,79942022,63015256,38365584,10961421,92604458,2208785,92530431,86118021,36396314,21533347,1936762,92692283,84349107,61141874,81274566,9599614,45237957,44177011,26397786,62051033,89499542,26664538,48658605,19376156,69641513,17894977,84127901,23194618,26998766,40781854,44550764,4434662,3183975,67030811,10597197,92787493,78909561,73617245,40677414,8128637,93515664,29516592,22405120,54762643,33628349,33699435,31727379,86022504,4204661,71920426,57241521,51464002,55189057,49271185,96726697,21070110,78549759,1788101,3880712,93270084,5267545,12024238,65081429,73021291,13231279,79880247,96735716,60106217,19457589,7066775,22200378,43357947,20486294,68128438,23740167,22766820,41092172,87720882,45381876,30653863,23432750,65047700,9603598,29932657,66428795,79657802,82979980,20568363,76960303,84293052,36139942,2717150,95581843,82532312,18504197,27325428,60176618,9188443,39801351,22942635,77413012,91727510,64098930,30771409,90310261,28550822,88251446,20642888,73235980,44473167,54263880,42073124,78218845,14731700,39847321,85711894,55602660,3233084,89699445,50806615,98653983,20140249,53084256,7300047,59910624,64157906,60581278,71316369,77272628,24168411,99549373,4978543,51315460,37435892,70727211,83083131,94090109,44915235,66271566,65017137,7502255,51590803,41442762,37224844,65275241,9257405,66832478,7104732,4199704,66322959,79806380,72777973,59329511,68102437,17058722,75128745,97057187,94360702,45996863,95251277,24953498,76434144,97641116,78589145,82178706,9058407,51426311,71083565,94539824,82427263,58180653,67474219,45428665,77187825,33431960,30811010,26102057,89637706,99690194,13348726,25636669,26493119,14723410,22113133,89419466,83269727,44664587,86242799,94595668,44983451,58749,84002370,51507425,72358170,1197320,14045383,89217461,16684829,83789391,36933038,44846932,15163258,47893286,2891150,8099994,90061527,71965942,71522968,30163921,4091162,78602717,99971982,28787861,48395186,27625735,23134715,89996536,6505939,94911072,50007421,68875490,56461322,9259676,10264691,1375023,73109997,17081350,58208470,93057697,84406788,33237508,81774825,4069912,64087743,73786944,9175338,45848907,77898274,89046466,32151165,5482538,68316156,40686254,57961282,26776922,66116458,55615885,52261574,26275734,14947650,83533741,62232211,27187213,42692881,35512853,56473732,45306070,61712234,75820087,20867149,16405341,14220886,21919959,62552524,176257,13819358,8115266,29188588,32590267,83210802,9860968,71469330,89811711,90654836,19898053,76315420,9860195,874791,57020481,4095116,23110625,48675329,38645117,38009615,4508700,74452589,64602895,36845587,61897582,62428472,70541760,68041839,3233569,6038457,11757872,43933006,10453030,36468541,38256849,5640302,20645197,96709982,86798033,44627776,17037369,39373729,31491938,65454636,1250437,54517921,16099750,22435353,91141395,82779622,60393039,53888755,90090964,78300864,72357096,82651278,57393458,43506672,59981773,6157724,63152504,44060493,53393358,59405277,52060076,48673079,37560164,40356877,6521313,34667286,65851721,97011160,5073754,24915585,59614746,72793444,21397057,49882705,87160386,24826575,92353856,55669657,88130087,15015906,29401781,62762857,70782102,83948335,92867155,42237907,88698958,83155442,36189527,45790169,749283,45990383,83747892,44844121,8791066,77301523,41245325,51715482,61928316,72278539,67451935,64055960,69697787,91802888,12303248,75052463,75135448,81172706,47298834,64848072,7182642,92541302,90158785,65880522,79322415,17857111,38022834,33935899,37280276,83133790,6111563,1239555,82327024,89804152,21289531,13468268,27411561,69786756,36135,61815223,76825057,68110330,99729357,21001913,46540998,96852321,2204165,55850790,61741594,38510840,91255408,85695762 +14220886,24058273,17068582,82327024,67793644,49236559,66250369,96193415,44889423,21993752,29834512,66832478,96735716,78785507,95957797,93933709,1197320,22108665,16595436,4204661,81172706,44784505,66116458,6986898,74743862,29819940,60430369,37620363,29510992,74724075,44348328,6793819,50806615,96726697,67030811,26292919,78561158,20568363,68000591,14626618,58180653,55770687,69355476,78218845,57241521,5073754,19457589,15015906,53084256,89214616,9599614,54427233,10358899,29029316,66045587,7646095,74110882,16971929,92787493,33628349,14947650,71300104,73124510,45237957,16054533,16019925,72274002,72777973,35092039,6871053,99604946,55247455,86543538,81853704,99524975,55470718,26114953,69136837,99549373,20122224,93515664,75229462,38658347,47213183,30764367,76434144,32250045,74357852,17764950,21289531,23194618,32151165,61982238,44481640,17539625,55753905,6505939,37891451,73235980,36808486,36753250,35192533,65715134,55850790,72019362,88047921,6525948,39171895,60581278,66428795,91255408,94090109,77284619,98130363,14326617,4064751,29994197,77312810,93270084,1431742,2204165,13468268,8807940,4515343,73222868,98739783,62452034,52261574,5111370,41092172,415901,9175338,9575954,2717150,45790169,26139110,62430984,20140249,50702367,8733068,92554120,5832946,29466406,72738685,28928175,70372191,39201414,67451935,6038457,40197395,12416768,11543098,11581181,8129978,12303248,53648154,29959549,90310261,73617245,53802686,57359924,6153406,92604458,16099750,91938191,48360198,29188588,43501211,70596786,6808825,80555751,33201905,12348118,29635537,33431960,24168411,45407418,4787945,9886593,19101477,56153345,7502255,84406788,4099191,59910624,83155442,83269727,42237907,34236719,66663942,62232211,36468541,79821897,72725103,34497327,79191827,33336148,40152546,58749,17365188,64602895,55960386,67281495,32159704,78909561,16097038,31126490,4806458,27665211,68824981,85242963,79922758,39801351,59501487,25842078,68816413,89804152,20885148,33935899,97783876,44842615,58208470,44479073,27625735,26998766,51426311,34946859,80014588,77272628,76960303,61960400,91990218,33797252,32058615,55602660,99515901,9860195,23848565,23569917,59371804,91727510,41245325,3294781,92033260,22450468,59405277,31491938,72373496,93566986,30090481,87598888,18833224,17037369,30543215,15902805,44177011,55669657,15535065,74452589,8099994,92803766,94595668,34044787,77413012,4668450,40781854,65271999,10309525,58224549,84127901,82651278,26863229,8729146,3088684,9623492,69641513,57658654,4091162,36685023,66667729,78884452,28734791,508198,71920426,78589145,18504197,17957593,54868730,44983451,4508700,64848072,37435892,79942022,90158785,43376279,48088883,98948034,1204161,86118021,51047803,50567636,38494874,94911072,569864,66885828,31161687,38645117,48673079,36312813,83533741,7423788,72357096,99125126,49271185,96420429,90457870,89996536,32161669,70800879,56955985,77694700,61263068,90013093,80246713,60176618,85023028,28787861,40984766,45919976,63059024,66903004,40677414,45990383,62740044,7300047,32590267,44847298,82339363,86242799,65338021,99965001,13862149,1569515,83789391,22942635,32426752,68644627,64055960,61712234,76540481,2917920,18699206,71522968,45428665,42644903,78602717,57240218,83210802,19939935,18411915,54263880,22405120,34667286,9259676,31904591,50668599,15075176,72495719,168541,15948937,72278539,51588015,57803235,33699435,5970607,99226875,78766358,48774913,3509435,86001008,22129328,12664567,26734892,66611759,99021067,31733363,51464002,82979980,96906410,9058407,41442762,36505482,92398073,4069912,40356877,61859581,37192445,4434662,63967300,52204879,74137926,70004753,94711842,80316608,65275241,4798568,321665,45848907,19376156,60106217,77898274,3880712,39373729,44060493,81898046,44844121,74441448,90061527,36396314,92215320,34432810,16405341,20002147,95581843,1788101,91664334,6819644,61373987,47893286,82897371,40686254,34698428,69697787,14363867,77377183,73031054,41481685,17081350,824872,30653863,11161731,46540998,8128637,66271566,1936762,30787683,19891772,68875490,26392416,24619760,91802888,82427263,68102437,56461322,20867149,22113133,61271144,84349107,89046466,46851987,26102057,96318094,56531125,96852321,47738185,62051033,72732205,54517921,16380211,68204242,1250437,87710366,44473167,98943869,54663246,75543508,669105,66319530,52060076,26063929,8791066,86022504,93359396,92692978,43152977,94539824,8373289,14093520,24953498,3183975,71316369,38061439,3233569,2208785,29932657,49328608,7182642,27041967,68939068,14723410,37957788,37659250,48893685,33061250,16684829,70367851,57393458,86821229,33304202,47361209,16445503,11757872,75777973,10453030,89811711,7687278,53547802,15148031,75153252,14731700,4119747,14349098,36580610,17386996,30998561,77301523,9257405,62490109,83150534,91831487,84684495,92692283,34698463,85695762,90004325,61859143,90090964,57163802,44846932,65017137,28550822,71965942,37183543,749283,526217,17976208,36933038,28796059,54987042,7011964,2891150,53842979,48675329,8651647,37224844,82178706,42199455,95290172,65038678,69786756,40865610,50007421,82726008,54762643,59197747,36139942,43322743,84166196,3233084,51472275,92353856,98371444,63015256,94360702,62693428,45794415,81677380,11923835,49598724,62936963,86301513,9188443,47887470,11365791,22435353,26776922,45381876,47792865,95726235,89419466,30366150,91957544,97379790,58751351,77072625,45667668,66322959,85571389,20765474,74614639,59109400,44627776,39553046,38341669,26744917,66137019,98462867,65880522,79880247,22997281,22766820,14045383,30139692,76703609,57020481,19486173,69579137,17727650,22721500,48260151,3487592,30463802,84904436,23134715,61728685,94516935,21673260,68128438,82779622,39847321,86460488,68110330,10597197,26275734,67124282,62762857,51315460,54058788,78549759,99971982,60102965,1239555,70782102,43357947,84293052,70541760,13819358,99690194,20836893,82052050,9603598,84840549,75820087,88251446,76315420,53632373,18001617,15163258,32274392,39986008,54014062,49641577,73109997,47708850,9860968,97561745,75135448,33237508,59318837,65851721,4199704,76671482,59329511,2607799,874791,76330843,12571310,79806380,71083565,99658235,83651211,89499542,72238278,37675718,36812683,89078848,65081429,35512853,88130087,81274566,68316156,13348726,61928316,73392814,62552524,87160386,82886381,13173644,64087743,13470059,10366309,19272365,83368048,92530431,64157906,87720882,40781449,75986488,30891921,3633375,16387575,68041839,89217461,27185644,37280276,53666583,3235882,38256849,37166608,46870723,7229550,35456853,53888755,29516592,48892201,93057697,10264691,26493119,68694897,54606384,62357986,91240048,42782652,72793444,1022677,15536795,86798033,97011160,61141874,42967683,99861373,7919588,56484900,4095116,5267545,6221471,7685448,24733232,90272749,67474219,88444207,27325428,60955663,63628376,26664538,38365584,18783702,20681921,57248122,51466049,88904910,33553959,99333375,89637706,18131876,23110625,5482538,67084351,36135,61623915,71469330,43933006,76170907,15064655,21919959,81805959,51507425,30395570,17016156,63372756,16424199,10649306,45306070,24591705,42073124,88653118,43045786,81855445,44245960,30811010,30163921,63152504,247198,50188404,29401781,84002370,45075471,51590803,95395112,75128745,79657802,92998591,70036438,77620120,98653983,83133790,3773993,59614746,69605283,34493392,55189057,65074535,55143724,48658605,49882705,30771409,32699744,27187213,17385531,77187825,41092102,36845587,6497830,1375023,89699445,17894977,49597667,36930650,85116755,21533347,38022834,77787724,98648327,67031644,21397057,33565483,28851716,12024238,52613508,37560164,42947632,18663507,7955293,33249630,62496012,97057187,44550764,47298834,83083131,44664587,8696647,79136082,7517032,24314885,65454636,61897582,29065964,59177718,99297164,92541302,42692881,23740167,62803858,92867155,24915585,76825057,70420215,92071990,91141395,65047700,44915235,30694952,16567550,18466635,36780454,20642888,99224392,37739481,17146629,9829782,19898053,8913721,94076128,72614359,54232247,56515456,40027975,47090124,24826575,60393039,13231279,99917599,97940276,176257,35996293,84187166,91281584,38510840,20486294,51149804,93562257,61815223,37501808,45617087,22200378,69848388,97281847,77300457,27411561,40534591,30569392,71333116,52293995,81774825,69255765,6521313,34295794,56473732,112651,95251277,9398733,4978543,23432750,83948335,54199160,96709982,8115266,17857111,5640302,73786944,73168565,55615885,83302115,42307449,2331773,30218878,48395186,38009615,21070110,38008118,45995325,36189527,20645197,82532312,63506281,21001913,25636669,7104732,41380093,71657078,61741594,75407347,33123618,8055981,26507214,45049308,88698958,4985896,22879907,93053405,95010552,99729357,45996863,56424103,51715482,79322415,43506672,68702632,93790285,10961421,67513640,73021291,85711894,8634541,97641116,59981773,53393358,31727379,80251430,17058722,62428472,70727211,85117092,79738755,62115552,78300864,75052463,6157724,5822202,57961282,72358170,33895336,67227442,6111563,18806856,64098930,7066775,26397786,90654836,83747892 +37183543,24591705,69786756,53842979,73168565,30366150,99965001,29819940,92787493,27185644,20642888,9398733,73617245,59177718,60430369,97561745,14045383,65047700,19101477,61859143,39801351,92554120,26664538,76671482,50188404,34493392,1022677,64602895,28787861,78218845,16595436,66322959,57803235,8913721,62740044,39847321,32161669,874791,68128438,67084351,3880712,526217,34295794,1431742,40152546,99297164,77413012,55770687,49641577,112651,38061439,40197395,14093520,84187166,51715482,65338021,77272628,62936963,73222868,1204161,92215320,28550822,79821897,176257,42782652,60955663,29188588,5482538,48360198,17016156,36780454,36468541,63015256,62693428,89214616,84840549,50702367,18131876,21289531,1375023,15535065,18001617,28851716,80014588,27041967,98130363,53648154,21533347,15064655,10264691,45667668,66903004,51047803,47298834,75407347,86022504,41092172,96906410,89046466,39553046,79922758,65454636,43933006,92803766,62496012,4091162,18783702,68000591,70596786,45790169,23569917,29029316,44983451,11365791,36685023,70367851,89419466,66832478,62357986,22997281,77620120,36580610,43501211,99861373,69355476,68939068,67031644,168541,83083131,74441448,86821229,31126490,38365584,53084256,85242963,37560164,6521313,6986898,56473732,71083565,92033260,6153406,92541302,6793819,9058407,7955293,27411561,77300457,61728685,24168411,45995325,33123618,46870723,37435892,98462867,33249630,66116458,96852321,66045587,96709982,72793444,42073124,26998766,19891772,10358899,75229462,90061527,26397786,41481685,68204242,16971929,45996863,33237508,82979980,30694952,38645117,81172706,14349098,3509435,67474219,36753250,23110625,4099191,54762643,67793644,88653118,55189057,89078848,44550764,62051033,71300104,36135,43322743,30163921,3235882,36139942,77284619,67281495,72278539,4806458,4787945,71333116,40781449,88904910,95957797,19939935,63967300,33201905,69697787,83368048,11923835,90090964,66271566,38341669,8099994,37192445,96735716,33565483,83651211,77694700,91957544,13862149,34698428,42199455,90457870,36505482,91990218,20765474,24058273,3233084,29932657,38022834,3183975,2208785,9623492,99658235,17539625,18806856,78589145,68816413,2917920,99515901,88698958,247198,82339363,94911072,76960303,51472275,78561158,84904436,51464002,29635537,28796059,89811711,50007421,33336148,24619760,52204879,62490109,20681921,84349107,72357096,82886381,7919588,35192533,53666583,23432750,27665211,25842078,26114953,14947650,82726008,13348726,93933709,72725103,4069912,60106217,55615885,23194618,80251430,38658347,90272749,62452034,26063929,19486173,43152977,49597667,16684829,20002147,16099750,19272365,42644903,89804152,63506281,99524975,34432810,16387575,73021291,39373729,91938191,30395570,38008118,52261574,86118021,7011964,64098930,44479073,90310261,50668599,72495719,85711894,66319530,40534591,70420215,60102965,92998591,95010552,29834512,66250369,22200378,8373289,12348118,91802888,14220886,27625735,93790285,47090124,8807940,74357852,97011160,99333375,91664334,66667729,76434144,20122224,43506672,61815223,99917599,78549759,73392814,12024238,6808825,21001913,65271999,33304202,70004753,3088684,76540481,3487592,67030811,52613508,98648327,74614639,24826575,7646095,88130087,49236559,1197320,70800879,45617087,82779622,32274392,32250045,77072625,29959549,85117092,12571310,57241521,33628349,54014062,77312810,36845587,81274566,9603598,18699206,14326617,96420429,5970607,59910624,54058788,61741594,13468268,70727211,61982238,8055981,10309525,44177011,75153252,59614746,2717150,31904591,38256849,40677414,70372191,35996293,55143724,61263068,73031054,82651278,38494874,99971982,86001008,26863229,20486294,9599614,55753905,37739481,59109400,45075471,15015906,3294781,15948937,7229550,88047921,14731700,2204165,99549373,91141395,43045786,48673079,68875490,64087743,4064751,42947632,67227442,415901,16380211,54606384,53888755,79191827,96726697,30653863,40356877,30139692,43376279,47708850,61623915,98943869,1239555,81898046,95395112,52060076,44842615,22405120,72274002,17146629,20867149,61897582,52293995,56424103,15536795,94539824,17081350,68316156,22129328,16405341,53632373,92692283,20140249,97641116,54427233,68102437,8634541,7687278,92398073,46540998,34946859,22879907,4119747,21070110,63628376,44889423,77377183,6111563,29401781,51466049,83789391,22721500,28928175,97783876,20836893,83210802,70541760,10366309,94516935,94595668,42237907,22108665,29994197,36312813,37659250,58751351,64848072,92353856,76330843,99224392,93515664,23848565,20645197,23740167,63059024,81677380,26776922,86242799,48088883,79136082,91240048,9257405,10649306,30811010,4508700,59371804,75135448,5073754,79806380,89996536,60581278,47738185,25636669,57961282,32151165,72777973,22942635,44915235,9860968,63372756,55247455,36812683,51149804,26102057,47893286,84293052,45428665,54199160,44844121,12416768,48893685,71469330,45237957,74452589,26744917,37891451,91255408,47361209,58749,92530431,7066775,45306070,8696647,65880522,39171895,37675718,73786944,50806615,13231279,34698463,94090109,57240218,15163258,17037369,80316608,83302115,79322415,93270084,31733363,87710366,10453030,44245960,87160386,6505939,71920426,19898053,62803858,50567636,55850790,51590803,68824981,96193415,16054533,19376156,88251446,92604458,9575954,99226875,5111370,41380093,68644627,56461322,55602660,67451935,97281847,37501808,95581843,59981773,59501487,62115552,93053405,57248122,5267545,81774825,32159704,17386996,824872,85023028,49271185,84002370,4095116,7423788,17068582,69641513,58208470,19457589,57658654,11543098,61859581,3633375,89637706,49598724,77187825,72732205,49882705,64157906,79880247,99021067,69136837,17058722,77787724,79657802,6871053,26734892,34236719,6157724,34667286,62430984,91831487,81805959,34497327,51588015,76170907,93562257,4434662,87598888,45848907,85116755,40686254,64055960,26493119,90004325,75820087,40984766,65017137,7104732,76703609,42307449,54868730,22435353,93566986,45990383,86301513,4798568,35456853,35512853,66611759,54663246,80246713,33797252,38510840,34044787,22766820,40027975,9860195,68041839,44481640,92692978,71316369,21919959,86460488,10597197,61960400,99604946,33895336,90158785,40865610,17727650,7502255,55669657,22450468,89699445,7300047,87720882,85571389,79738755,8129978,73235980,57020481,86543538,44784505,4199704,31161687,33699435,99125126,15148031,98371444,66885828,76315420,6497830,30764367,1569515,61141874,32699744,569864,84684495,56515456,8791066,75777973,43357947,16445503,74743862,4668450,75543508,94360702,20885148,46851987,48774913,74110882,4204661,16424199,88444207,36396314,65851721,48395186,65275241,48675329,6221471,53393358,16097038,41442762,8128637,78785507,65715134,82178706,31491938,62232211,48892201,54263880,13470059,59318837,26392416,70036438,7685448,80555751,21993752,48658605,24953498,44060493,44348328,17385531,56531125,69848388,18411915,1936762,73109997,45919976,61373987,22113133,83269727,59197747,4985896,12303248,44627776,11161731,81853704,54987042,37957788,76825057,75052463,61928316,37620363,97379790,95251277,321665,90654836,45794415,99690194,21673260,30771409,29466406,71657078,26139110,24915585,94076128,7517032,56153345,26275734,78766358,92071990,44846932,83133790,9188443,55470718,60176618,37280276,12664567,15902805,58180653,45407418,42692881,96318094,36808486,86798033,98948034,9886593,36933038,30463802,32590267,1788101,71522968,30998561,66137019,30543215,35092039,23134715,69255765,83155442,51507425,30218878,77898274,83533741,669105,30569392,37166608,9259676,17365188,13173644,66663942,55960386,94711842,39986008,53802686,65038678,33061250,78300864,57393458,5640302,36930650,10961421,93057697,81855445,24733232,29510992,62762857,53547802,9175338,4515343,18466635,91281584,61271144,65081429,95290172,18833224,3233569,69579137,68110330,31727379,16567550,65074535,17957593,83747892,3773993,8733068,91727510,97940276,66428795,48260151,8651647,69605283,1250437,14723410,28734791,508198,72614359,79942022,54517921,32426752,11581181,62428472,7182642,77301523,5822202,49328608,47887470,83150534,14626618,2891150,18663507,56484900,15075176,72373496,29516592,78884452,82427263,30891921,67124282,89499542,82052050,16019925,41245325,90013093,73124510,68702632,26507214,67513640,84127901,44473167,68694897,17857111,75128745,32058615,13819358,63152504,72358170,8115266,27325428,36189527,33935899,6038457,44847298,2607799,17976208,2331773,99729357,30787683,6819644,54232247,40781854,82532312,59329511,38009615,74137926,37224844,92867155,57359924,30090481,11757872,82897371,72738685,47213183,9829782,51315460,26292919,61712234,33431960,72019362,5832946,56955985,62552524,33553959,42967683,97057187,75986488,27187213,17894977,60393039,72238278,20568363,39201414,59405277,93359396,21397057,17764950,70782102,74724075,4978543,47792865,749283,29065964,78909561,84406788,71965942,78602717,83948335,14363867,57163802,44664587,45049308,24314885,51426311,84166196,89217461,98739783,6525948,58224549,95726235,45381876,41092102,85695762,82327024,8729146,18504197,98653983 +61859143,17037369,43376279,66832478,8913721,64055960,12348118,3233084,4119747,89214616,44889423,16097038,16595436,19891772,95957797,15015906,76540481,99224392,34497327,62693428,30787683,37957788,32161669,98948034,9623492,83789391,88653118,92803766,18699206,62496012,86001008,65454636,62740044,92998591,7502255,63967300,65275241,83948335,55247455,92692978,70596786,19457589,34698428,40197395,68939068,57803235,28851716,62936963,669105,36580610,14349098,96735716,75153252,19486173,81677380,22942635,60955663,43506672,45848907,29959549,61741594,42782652,38022834,38658347,54427233,89046466,99226875,33553959,39801351,20140249,76434144,73235980,93359396,39847321,44550764,68644627,45790169,41481685,27665211,62357986,21993752,5970607,6808825,59614746,17385531,89078848,749283,508198,29188588,70800879,12571310,43357947,2891150,9188443,49641577,34493392,53842979,70004753,9860195,18001617,96709982,47792865,46851987,26063929,36753250,49236559,52261574,34698463,27325428,33797252,62803858,15064655,9398733,20642888,68816413,56515456,37739481,57248122,70367851,64848072,26114953,30694952,42307449,14947650,61897582,22766820,92604458,40677414,20885148,26998766,9603598,23848565,76960303,97940276,77620120,33336148,74614639,50188404,88047921,18783702,2717150,7687278,36845587,26392416,77301523,62430984,51715482,43045786,65851721,37435892,4515343,72019362,47708850,24591705,65338021,59177718,1431742,37620363,90272749,91255408,50702367,1204161,86543538,9860968,21533347,6986898,3294781,82779622,99515901,30569392,92554120,51047803,75407347,33201905,95395112,33935899,29994197,62232211,33237508,79821897,30163921,96193415,35996293,36812683,74452589,66250369,1375023,84187166,50668599,70036438,44060493,54663246,66903004,4099191,71920426,23569917,92541302,3183975,99524975,23740167,6505939,68824981,72725103,28734791,65074535,42199455,54987042,31733363,99965001,13231279,26275734,45049308,77284619,48088883,67031644,83083131,54762643,96318094,76671482,79136082,20486294,1022677,38365584,94539824,26493119,39171895,15536795,58749,93566986,71083565,33628349,62452034,72777973,61982238,56424103,44245960,25842078,52060076,38341669,75777973,8696647,30395570,11543098,11161731,50806615,5267545,98371444,22879907,69786756,97561745,52293995,97281847,79191827,54199160,168541,33249630,10366309,51472275,83302115,6153406,91664334,72793444,97783876,36468541,55189057,64087743,67281495,29834512,44177011,61859581,66116458,86798033,43933006,46540998,7104732,48260151,30543215,81853704,16387575,90013093,63059024,73617245,66885828,63372756,78589145,78300864,11923835,89699445,45237957,36135,47361209,65880522,71333116,85571389,45995325,7955293,74441448,112651,39201414,17068582,3633375,57241521,50007421,874791,1250437,23194618,71316369,30771409,78549759,86821229,24915585,22450468,51588015,91281584,68102437,9575954,4508700,3509435,8055981,79657802,77413012,28796059,32250045,82427263,68694897,66271566,24733232,4798568,48360198,23134715,8115266,24826575,57359924,73031054,38061439,29819940,57393458,16019925,17365188,56153345,5640302,94090109,57020481,20122224,85711894,13470059,22108665,59371804,98130363,48673079,247198,28550822,70541760,32426752,17058722,18806856,38494874,72274002,83533741,51464002,3773993,14093520,10961421,53802686,88444207,96852321,40686254,66663942,83155442,73124510,61271144,38008118,66137019,33565483,83210802,6793819,36933038,22129328,7919588,4204661,6111563,13348726,17764950,26102057,98648327,36505482,74724075,45996863,98462867,36780454,93933709,55143724,45667668,69641513,39373729,68316156,51590803,34236719,84904436,91727510,44983451,9175338,35092039,36189527,38510840,87160386,92692283,90310261,8791066,74357852,98739783,45990383,36930650,61815223,44473167,33304202,3487592,78766358,39553046,77377183,67474219,60581278,51426311,99658235,71657078,87720882,35456853,87710366,88130087,85242963,81172706,91831487,38645117,58208470,29932657,67084351,37183543,40152546,21397057,80014588,93270084,29466406,69579137,92033260,54517921,44915235,19376156,34295794,42237907,6221471,74137926,86301513,77312810,53888755,68128438,42073124,76703609,30090481,55602660,82979980,82532312,7517032,72373496,73392814,66045587,12024238,83368048,48774913,29029316,68204242,92787493,9829782,7066775,16099750,78602717,57961282,45306070,45075471,99549373,21001913,19101477,8733068,18833224,8634541,38009615,66611759,73786944,97057187,90654836,8807940,79880247,44348328,68875490,2607799,97011160,415901,57240218,84293052,80555751,8128637,67793644,40984766,33895336,34946859,9599614,91957544,41442762,59197747,51507425,27625735,56531125,39986008,84840549,89811711,83651211,53547802,61623915,40781854,40356877,59109400,61373987,69255765,4095116,55669657,26776922,1569515,45407418,59501487,16380211,85116755,6521313,54014062,33123618,37192445,4668450,31904591,20568363,569864,7300047,7423788,99729357,75052463,65047700,14326617,45428665,45919976,55470718,44784505,3880712,92215320,64098930,4069912,99917599,63506281,72614359,37675718,71469330,14723410,67030811,79942022,91802888,62051033,70420215,56473732,79806380,76315420,77187825,24058273,77272628,44627776,94711842,94911072,29635537,9058407,69697787,61728685,85695762,27185644,78218845,17957593,84127901,92867155,57163802,59981773,98943869,30139692,97641116,19272365,63152504,80316608,17146629,41092172,15163258,33431960,53632373,4434662,53084256,24168411,99861373,4806458,26863229,46870723,73222868,21070110,82651278,99971982,74110882,59910624,49271185,89804152,6497830,14045383,29510992,93515664,20681921,90457870,99297164,75543508,10358899,29401781,2208785,96726697,62552524,9886593,76330843,66322959,30463802,30366150,71522968,68702632,17727650,83133790,83269727,33061250,58751351,32699744,37166608,24314885,55615885,55960386,95251277,16971929,63015256,82886381,26139110,48395186,27411561,77694700,28928175,60176618,15948937,75986488,47298834,67124282,20765474,19939935,42967683,36685023,43322743,54868730,13468268,34432810,5482538,77300457,42947632,57658654,65715134,70727211,24619760,15075176,28787861,43501211,36139942,5073754,40781449,69355476,75229462,99125126,84406788,88904910,26507214,72495719,71300104,44842615,1788101,48892201,26734892,3233569,35192533,95290172,68000591,80246713,67451935,7182642,41092102,15535065,12416768,41380093,4985896,53666583,93053405,91240048,51466049,51315460,47213183,85023028,99333375,96420429,82339363,11365791,4787945,17386996,82052050,24953498,89996536,68041839,10453030,49597667,4064751,60106217,94076128,93562257,30218878,58224549,99604946,21289531,53648154,62490109,95726235,81855445,78884452,19898053,84002370,72357096,72732205,9259676,32274392,1239555,94516935,47090124,77787724,65271999,8651647,15148031,48675329,52613508,13862149,47738185,74743862,65017137,93790285,75135448,44479073,40865610,8129978,48893685,73021291,6157724,11757872,91141395,14731700,32159704,7011964,35512853,58180653,61263068,49882705,31126490,82726008,87598888,62428472,59318837,526217,21673260,99690194,60430369,16054533,29065964,26744917,67227442,83150534,62762857,54606384,88251446,26397786,36808486,94360702,72738685,7229550,14363867,69605283,78909561,18466635,49598724,76170907,70372191,37280276,10649306,60102965,55753905,30764367,22721500,824872,37224844,97379790,89419466,18663507,36396314,8099994,92398073,38256849,30998561,18131876,20002147,67513640,1936762,20867149,93057697,92530431,17081350,16684829,45794415,17976208,80251430,44846932,72278539,78561158,54232247,82178706,20645197,91938191,92071990,90061527,90090964,37501808,31491938,75128745,22435353,55770687,66319530,36312813,69848388,61928316,56461322,40027975,6525948,47887470,51149804,99021067,54263880,66428795,63628376,95010552,54058788,5832946,88698958,7685448,53393358,23110625,77072625,86460488,17016156,89637706,8373289,44664587,56484900,10309525,30653863,81805959,3235882,23432750,16445503,69136837,4199704,79922758,9257405,72358170,52204879,37659250,41245325,2917920,2204165,16567550,84349107,44847298,33699435,6038457,25636669,89499542,31161687,50567636,12664567,62115552,30811010,34044787,20836893,17539625,73168565,7646095,48658605,79738755,26292919,5111370,16424199,42692881,27041967,32151165,1197320,72238278,6871053,94595668,21919959,66667729,81898046,44844121,90004325,79322415,64602895,30891921,17857111,61141874,37891451,78785507,86022504,40534591,70782102,17894977,75820087,14626618,47893286,22200378,32590267,83747892,71965942,44481640,12303248,84684495,64157906,68110330,92353856,18411915,26664538,82897371,15902805,76825057,86242799,321665,91990218,65081429,59329511,6819644,45617087,13173644,59405277,14220886,77898274,16405341,37560164,22997281,27187213,86118021,96906410,85117092,81774825,55850790,29516592,49328608,81274566,176257,10597197,22405120,5822202,42644903,98653983,60393039,84166196,11581181,8729146,89217461,73109997,45381876,56955985,43152977,32058615,82327024,18504197,4978543,90158785,22113133,4091162,95581843,13819358,3088684,34667286,31727379,2331773,61960400,61712234,10264691,65038678 +66045587,70420215,2717150,77284619,54199160,30998561,82339363,27665211,10309525,62740044,88653118,55470718,33201905,89214616,93933709,62552524,35092039,37620363,37183543,16445503,47090124,54663246,26392416,20765474,13862149,77272628,71333116,81898046,15535065,79657802,17764950,29834512,13173644,67793644,49641577,89637706,43501211,64087743,9257405,10366309,27325428,61263068,20122224,63059024,21533347,73786944,89804152,80014588,76960303,1197320,62357986,39847321,50668599,84840549,49328608,89078848,31904591,14626618,84187166,74137926,40356877,8807940,5111370,1431742,92787493,44983451,27411561,92033260,55753905,10453030,91957544,93566986,99515901,53802686,5822202,30764367,54762643,18504197,83651211,71657078,45428665,6525948,64098930,24915585,39373729,63967300,27041967,75777973,36580610,18833224,94516935,37739481,49271185,3509435,72274002,60430369,64602895,91664334,67474219,65081429,20836893,67451935,34044787,40677414,9860195,67030811,39801351,49882705,20486294,65851721,33431960,94595668,52613508,75986488,12348118,24058273,17081350,7919588,84904436,82726008,54606384,85116755,59329511,22997281,75820087,321665,32590267,96318094,59177718,69136837,31161687,92604458,9259676,35192533,54014062,2917920,26507214,66250369,34698463,36780454,29994197,55247455,66903004,88047921,16054533,22108665,50702367,4204661,53666583,3880712,78785507,44844121,99021067,30163921,37501808,57803235,66667729,40152546,66116458,508198,247198,62051033,3487592,51464002,53547802,3233084,4099191,526217,87160386,66271566,8129978,45049308,47792865,47887470,43376279,68816413,19376156,19891772,77787724,99549373,92398073,70372191,44481640,52204879,14349098,71316369,14326617,12571310,42199455,5482538,96193415,76825057,10649306,3088684,62490109,85117092,60106217,38061439,49598724,73235980,33935899,65038678,36808486,29516592,61960400,65338021,65275241,2204165,45794415,42782652,7229550,66428795,77620120,56461322,78589145,19101477,10961421,47361209,88251446,16097038,43322743,56515456,68000591,48260151,96735716,80316608,69848388,68644627,18783702,74110882,99917599,58224549,60176618,33237508,4064751,46851987,6986898,45306070,30366150,50567636,73124510,89046466,99971982,66611759,62452034,4787945,73168565,19486173,4119747,20140249,14045383,30811010,94360702,72373496,65715134,18663507,90457870,16387575,69355476,4095116,8651647,28734791,18131876,1569515,95290172,43933006,60102965,51149804,1239555,46870723,84406788,72357096,9886593,45407418,55615885,73617245,59910624,61271144,66832478,70800879,99965001,30891921,99658235,91727510,30395570,40027975,43357947,30090481,669105,20002147,78602717,29065964,33699435,44479073,95726235,14220886,44842615,5832946,76315420,9058407,75135448,99125126,90004325,51047803,18806856,84684495,35996293,35456853,22766820,55143724,84002370,26397786,16380211,45075471,68939068,30139692,7300047,28928175,29029316,61373987,82897371,9575954,72278539,98648327,22200378,12664567,4515343,89699445,14947650,53393358,38022834,6793819,80251430,51507425,9175338,22879907,3633375,7685448,22405120,92998591,62693428,45237957,56484900,57240218,62496012,23848565,81853704,17037369,15536795,14731700,59109400,67031644,83150534,8696647,32151165,75543508,80555751,20867149,79806380,18411915,91938191,63628376,22450468,93057697,7687278,61141874,8733068,15148031,4668450,22113133,16567550,98130363,40865610,32161669,61712234,77694700,86022504,34497327,53648154,61741594,70727211,5073754,50188404,22721500,75153252,3233569,8115266,36139942,40781854,46540998,52261574,30787683,97940276,69786756,37891451,37659250,77300457,32159704,72238278,33565483,79136082,27625735,22435353,2891150,68204242,68875490,98653983,44784505,31126490,79738755,89996536,824872,90013093,19939935,86242799,71920426,40984766,99729357,34295794,26139110,88444207,86001008,33797252,82886381,96726697,83210802,89419466,83155442,54058788,77898274,38256849,16099750,6808825,51426311,87598888,20568363,99226875,16405341,42692881,25636669,6505939,76170907,56955985,39986008,62803858,6153406,17068582,65880522,4508700,79821897,47213183,89217461,16019925,6521313,569864,36753250,42073124,59371804,2208785,41245325,45617087,40781449,36312813,82052050,62936963,9599614,30771409,94911072,15902805,63506281,81172706,51588015,48892201,98371444,7182642,74441448,62430984,17386996,96906410,5640302,6157724,29466406,23569917,45996863,24826575,39553046,61982238,32699744,5970607,81677380,74743862,81805959,95957797,92554120,5267545,58208470,38008118,33249630,84166196,84127901,168541,75128745,58180653,99604946,99524975,17385531,86118021,48395186,91831487,41380093,68128438,76330843,31491938,70367851,88904910,67281495,17016156,13348726,44889423,52293995,36135,72358170,21070110,78300864,66137019,48088883,15015906,72725103,42644903,61623915,26102057,4985896,94090109,37560164,47708850,63015256,61859581,71469330,42237907,90061527,17976208,83789391,48774913,72614359,30543215,26275734,1788101,112651,12416768,3183975,55189057,54427233,42307449,67227442,19272365,18699206,92692283,85571389,1204161,71083565,79880247,47738185,44664587,45848907,76434144,57163802,44473167,62762857,55770687,76540481,32058615,86460488,33061250,28851716,26493119,26292919,7066775,95010552,95251277,99297164,92803766,97057187,86821229,53632373,86798033,47298834,45990383,29932657,1250437,81855445,92692978,78218845,54517921,34698428,70004753,31733363,38658347,79922758,23194618,7646095,55602660,9188443,68702632,93053405,24619760,77187825,92071990,30653863,59197747,16595436,32250045,93790285,99861373,11543098,7955293,83747892,50007421,18001617,41092102,16971929,69579137,36930650,33304202,57248122,73109997,66663942,84349107,20885148,33628349,51590803,8099994,97011160,59318837,56473732,8913721,60955663,67124282,30463802,93562257,78561158,63372756,49597667,6111563,57241521,45381876,75229462,61859143,82979980,83083131,71522968,64848072,44348328,77312810,16684829,91990218,44846932,44847298,98943869,49236559,81774825,78909561,43506672,69697787,88130087,64157906,72777973,55669657,77413012,14363867,95581843,25842078,36396314,43045786,62115552,24314885,68694897,85023028,176257,65047700,29635537,85242963,56424103,69641513,21397057,8729146,4434662,7011964,48673079,74614639,77072625,66322959,36685023,94711842,57020481,68041839,69605283,40197395,78884452,24168411,7517032,40686254,99333375,92541302,38009615,37957788,37435892,90272749,71300104,59501487,75052463,10597197,22942635,37192445,21919959,60581278,70036438,34432810,36812683,61728685,37675718,95395112,97641116,34493392,58751351,99224392,65017137,17539625,69255765,50806615,26998766,73031054,93359396,24733232,89811711,86301513,2607799,36505482,43152977,53888755,874791,54868730,83368048,54263880,86543538,35512853,23740167,57393458,61928316,2331773,59405277,12303248,38365584,26744917,7423788,24591705,44245960,11757872,17857111,29188588,44177011,65074535,83302115,72495719,97379790,97561745,11581181,62232211,88698958,92215320,56531125,53842979,78766358,36468541,17058722,44060493,70541760,6221471,29819940,26114953,90090964,19898053,87710366,87720882,42947632,29401781,82327024,4091162,17727650,48675329,15948937,67513640,28796059,57359924,82532312,21993752,68824981,30694952,51715482,48360198,749283,82651278,41442762,67084351,4069912,82178706,74724075,21673260,38494874,19457589,39171895,6038457,85695762,9603598,57658654,26863229,47893286,10358899,59614746,8373289,15163258,76671482,28787861,28550822,23432750,75407347,54987042,3773993,36189527,17894977,22129328,90310261,82427263,4199704,63152504,36933038,74357852,26776922,65271999,38645117,13819358,79191827,97281847,13468268,26734892,91802888,20642888,61815223,93515664,6871053,72732205,15064655,92867155,37224844,68102437,37166608,24953498,23134715,91281584,98462867,23110625,32274392,55960386,44550764,55850790,9398733,44915235,16424199,56153345,1936762,59981773,68110330,96852321,70596786,42967683,83133790,37280276,73021291,33336148,8128637,10264691,415901,15075176,20681921,77301523,68316156,89499542,13231279,4806458,38341669,85711894,34236719,77377183,98948034,9623492,60393039,33895336,79322415,45995325,18466635,14093520,34667286,97783876,92530431,40534591,82779622,48658605,79942022,21001913,29510992,12024238,92353856,34946859,52060076,33553959,39201414,30218878,54232247,17957593,83948335,36845587,4978543,66319530,72019362,20645197,78549759,73392814,91141395,81274566,83533741,99690194,94076128,11365791,91255408,29959549,61897582,1375023,11161731,41481685,72793444,26063929,17365188,98739783,31727379,48893685,57961282,17146629,96420429,64055960,4798568,26664538,7104732,83269727,3235882,65454636,30569392,74452589,3294781,7502255,27185644,44627776,1022677,51466049,8055981,45667668,94539824,41092172,8791066,33123618,90654836,53084256,21289531,32426752,93270084,84293052,80246713,58749,96709982,45919976,51472275,66885828,91240048,9829782,71965942,9860968,11923835,90158785,76703609,13470059,45790169,73222868,27187213,6497830,72738685,8634541,14723410,6819644,70782102,62428472,51315460,38510840 +168541,61741594,83789391,90457870,50188404,44177011,55189057,84349107,14093520,88444207,73168565,20486294,4069912,46870723,4119747,24591705,91240048,38061439,669105,36753250,39171895,29466406,22435353,48360198,57803235,2208785,30694952,94090109,53632373,75543508,31904591,40027975,42692881,29188588,1197320,81677380,69355476,71333116,67793644,8099994,62936963,53842979,51047803,7104732,5832946,33201905,24058273,12348118,66667729,96735716,66250369,56424103,34295794,68644627,57240218,8128637,48673079,67451935,51590803,33304202,5970607,48088883,70727211,30163921,19376156,76703609,11161731,48892201,15535065,61859143,9058407,41481685,43376279,81805959,26863229,43501211,96709982,89214616,63967300,62232211,28734791,57393458,4806458,56473732,7229550,34044787,93053405,66045587,66832478,94711842,46851987,29029316,67474219,22108665,99524975,52060076,66611759,19457589,65454636,18833224,61712234,60106217,30463802,21533347,44983451,63015256,14349098,30998561,84293052,86242799,78218845,22766820,55247455,13468268,74357852,49598724,21993752,30139692,79191827,90310261,66319530,66903004,38494874,37183543,77312810,15064655,38341669,85711894,60581278,73031054,62430984,29819940,36812683,20765474,10366309,47887470,6819644,99604946,37192445,40534591,10309525,17081350,6808825,39553046,32274392,80316608,75153252,52204879,37739481,72725103,84406788,1239555,247198,85117092,66885828,92398073,71920426,26392416,16097038,27665211,44627776,18411915,91727510,96318094,66271566,2717150,53666583,76330843,59318837,7423788,36312813,35192533,3880712,89804152,50668599,37659250,40781854,68939068,16971929,60430369,81898046,31126490,68875490,71965942,38022834,55753905,52293995,38658347,92692978,4204661,47361209,48774913,20645197,24826575,92033260,98371444,6153406,37620363,56515456,75820087,73235980,70372191,79821897,47090124,60102965,70004753,53084256,96193415,59501487,112651,44847298,55850790,19486173,4668450,33249630,81855445,92554120,56153345,32161669,99965001,69697787,8115266,51472275,91802888,17539625,62496012,71300104,17037369,92353856,77694700,11365791,87710366,83302115,65074535,54199160,4515343,92787493,7011964,50567636,45919976,44915235,98130363,6221471,59614746,508198,67513640,76315420,64602895,19891772,40984766,7955293,15148031,82726008,51464002,44842615,35996293,61373987,73021291,72495719,2204165,61960400,76434144,56531125,77300457,23110625,44245960,43357947,29959549,97641116,55770687,74110882,77620120,76671482,84002370,526217,5482538,40152546,80014588,99333375,38365584,20140249,13348726,99549373,22942635,46540998,57658654,91957544,71316369,62740044,89046466,98648327,321665,89078848,27625735,84840549,43322743,44473167,68000591,90004325,87598888,95010552,90090964,91831487,84166196,35512853,84684495,24733232,72614359,4091162,2917920,52261574,29994197,34698428,26664538,21001913,81274566,30787683,8696647,34493392,29834512,68816413,47213183,93790285,45848907,9829782,30891921,26114953,40356877,14220886,43045786,83651211,45049308,79942022,80555751,98943869,23134715,27325428,3233084,98739783,83210802,18783702,30218878,42782652,65271999,33237508,43506672,31733363,82886381,7687278,72274002,72357096,45794415,45407418,44784505,75229462,91664334,8729146,53648154,3487592,6986898,3294781,29516592,71083565,77284619,24619760,1022677,32250045,63059024,62693428,23569917,17727650,17058722,73124510,30395570,81172706,68204242,85023028,68316156,87720882,15075176,36505482,9175338,61928316,83133790,52613508,72732205,82532312,54058788,26292919,76960303,5267545,874791,12416768,14731700,67281495,39847321,57241521,55602660,39801351,17365188,34667286,17957593,9603598,51507425,7919588,44846932,12024238,37675718,54263880,75777973,70420215,64087743,61815223,3773993,44889423,32159704,86460488,44060493,77187825,19272365,66322959,5822202,70800879,33431960,65715134,27041967,9886593,59981773,3633375,21673260,6871053,85242963,569864,6525948,89419466,415901,18131876,89499542,57359924,82339363,84904436,99515901,9398733,18466635,85116755,36396314,70596786,30811010,37560164,1936762,13470059,34432810,22997281,4199704,14947650,77272628,79136082,34698463,54606384,30569392,1431742,38645117,9599614,3088684,54232247,9575954,34236719,62051033,14626618,82052050,26102057,45790169,88047921,79806380,86301513,40686254,70367851,5073754,27185644,78909561,81853704,44844121,77301523,72793444,35092039,87160386,22405120,42644903,78589145,20642888,93359396,17386996,6497830,59177718,57248122,68694897,16405341,37435892,53802686,17894977,99125126,9860195,10649306,18663507,75128745,20885148,59329511,34497327,18504197,94076128,80251430,1375023,92541302,72278539,63628376,26998766,20867149,96852321,26275734,89811711,23848565,29635537,67031644,94516935,20002147,8634541,26397786,31161687,16595436,65047700,7646095,83083131,49641577,4099191,91990218,17764950,1788101,15948937,48260151,74724075,75986488,824872,77377183,42073124,96906410,45237957,92215320,54427233,16684829,64157906,16445503,60176618,90158785,92867155,58208470,55615885,94360702,9623492,6038457,83155442,24915585,47792865,23740167,99224392,97281847,83150534,28796059,39201414,2331773,6111563,90654836,73392814,86001008,62490109,30764367,11923835,66663942,61263068,19101477,99297164,50007421,13819358,94539824,33336148,18806856,28928175,94911072,47708850,95395112,54987042,78766358,12571310,72777973,89699445,62803858,83533741,24953498,28550822,60393039,17068582,21289531,64055960,7300047,64848072,65081429,30653863,176257,47298834,26139110,38009615,93933709,58224549,71657078,65338021,37891451,22129328,3235882,41442762,99226875,8733068,78561158,74137926,8807940,69848388,1204161,68041839,8129978,45075471,96726697,78300864,33895336,7502255,40781449,20568363,36808486,32699744,83747892,57961282,95957797,19939935,68128438,70036438,65851721,39986008,48893685,17146629,10961421,91141395,55143724,45667668,93562257,98948034,8791066,21070110,33797252,86022504,61728685,9188443,17976208,73786944,65017137,3509435,37957788,59197747,59371804,64098930,53393358,99658235,74441448,58180653,68702632,97379790,40197395,7517032,79922758,92692283,68102437,19898053,77072625,89637706,23194618,97057187,82779622,72373496,8373289,54663246,15015906,90013093,78785507,74743862,55669657,75135448,82651278,76540481,92071990,49597667,79738755,65275241,65880522,45306070,62115552,31727379,49271185,50806615,36933038,32426752,33628349,45995325,92803766,5111370,49328608,45996863,6505939,85695762,86821229,86543538,44550764,63152504,36139942,4434662,1250437,55960386,51315460,62552524,25636669,25842078,69136837,4798568,2891150,10358899,95726235,38008118,88130087,92998591,73222868,20836893,47738185,36468541,59109400,86798033,72238278,23432750,88251446,36780454,60955663,82897371,33061250,6793819,93566986,78602717,33565483,14723410,77898274,14045383,57020481,49236559,749283,26507214,56955985,63506281,93515664,51466049,29401781,92530431,34946859,20681921,42967683,54517921,91938191,68824981,93270084,67030811,79657802,96420429,82178706,28851716,18001617,7066775,58749,98462867,17857111,40865610,61271144,12664567,29932657,40677414,73617245,71522968,61982238,79880247,68110330,4787945,37224844,67084351,76170907,3183975,84127901,17385531,49882705,61623915,61141874,90272749,83269727,80246713,45617087,39373729,61897582,44481640,30543215,11581181,99917599,16099750,59910624,78884452,63372756,51149804,41380093,9860968,2607799,16387575,36580610,85571389,45381876,26063929,9257405,77787724,43152977,13862149,10597197,36135,22450468,75407347,33935899,66137019,33699435,42307449,8651647,58751351,69605283,95581843,88653118,8913721,55470718,31491938,98653983,30366150,26493119,43933006,66428795,83368048,3233569,82427263,26734892,82327024,38256849,89217461,22879907,7685448,41245325,69641513,99021067,82979980,17016156,20122224,84187166,29065964,91281584,16019925,28787861,32151165,8055981,42947632,54868730,88904910,67124282,95290172,61859581,62357986,72358170,94595668,22113133,51426311,41092102,92604458,77413012,72738685,45990383,97011160,4095116,72019362,6521313,30090481,70782102,36930650,42199455,99861373,74452589,93057697,10453030,16424199,47893286,65038678,67227442,48658605,70541760,71469330,13173644,35456853,56461322,4978543,62452034,33553959,15163258,54762643,69579137,51715482,99690194,95251277,4985896,59405277,27187213,89996536,81774825,36845587,86118021,75052463,78549759,37280276,44479073,24168411,97561745,27411561,14326617,41092172,26744917,9259676,22200378,15902805,97940276,53888755,14363867,42237907,18699206,91255408,33123618,30771409,88698958,50702367,26776922,66116458,79322415,76825057,16380211,36189527,21919959,51588015,62428472,54014062,15536795,13231279,24314885,16054533,32590267,99971982,53547802,4064751,29510992,97783876,83948335,56484900,11543098,44664587,21397057,69255765,45428665,1569515,36685023,44348328,11757872,22721500,4508700,48395186,74614639,90061527,99729357,73109997,16567550,48675329,5640302,38510840,37501808,6157724,57163802,12303248,69786756,32058615,37166608,10264691,62762857,7182642 +4515343,61373987,17764950,29065964,9623492,74357852,93933709,55247455,39201414,24058273,11923835,66885828,68644627,97783876,60430369,75777973,96193415,29994197,77312810,20885148,44983451,75543508,20002147,14626618,40197395,69355476,415901,55753905,44915235,67451935,43357947,75986488,6497830,29029316,9603598,77787724,10309525,16445503,95957797,321665,168541,34698463,70596786,98371444,20140249,17727650,4099191,70004753,4668450,62430984,88653118,46870723,10366309,93057697,83533741,81853704,70800879,48892201,39553046,34698428,9886593,71657078,7517032,79821897,53648154,96726697,65275241,38494874,5832946,67793644,44784505,86022504,44844121,2717150,31904591,99658235,7955293,76434144,65047700,66667729,29834512,89046466,57240218,73222868,92604458,20765474,9188443,37224844,53547802,7502255,50806615,90457870,72725103,71965942,66428795,74110882,15015906,34044787,99861373,72373496,77620120,3088684,15148031,35192533,1431742,77272628,82897371,45996863,16595436,76960303,37620363,99690194,4787945,72738685,97057187,54663246,32590267,57163802,27665211,69786756,65715134,43045786,56531125,61982238,44473167,6221471,2204165,85116755,96318094,31126490,40027975,47792865,52261574,86001008,30463802,39986008,40865610,1569515,36685023,76703609,93515664,30090481,74441448,14363867,77284619,82052050,62452034,60581278,19101477,32426752,89499542,18833224,30163921,86543538,98648327,78909561,4064751,49882705,91141395,98739783,42073124,67084351,34946859,59371804,95010552,5970607,80316608,8651647,3509435,56424103,71522968,24168411,17539625,54199160,31733363,89214616,749283,94090109,85023028,50567636,80246713,8128637,23848565,18411915,59501487,4434662,99549373,45794415,38658347,55470718,99917599,4204661,8634541,28796059,66319530,94711842,1197320,32250045,66137019,27187213,49271185,18131876,33061250,82651278,54987042,79191827,37183543,26493119,30543215,6505939,5111370,14093520,6793819,35092039,54058788,73124510,63059024,17957593,67124282,8129978,93270084,874791,2607799,67474219,93562257,22405120,29510992,99021067,30771409,8807940,21993752,34236719,45790169,37739481,569864,96420429,39171895,36468541,68000591,8791066,72777973,57658654,2208785,83150534,66832478,58208470,38022834,26664538,45049308,84406788,98653983,47361209,91802888,96906410,26863229,69641513,82979980,3294781,54868730,30787683,68102437,84684495,22721500,18504197,50702367,63628376,38645117,67281495,75229462,26063929,26998766,36505482,87710366,66663942,35996293,29466406,40781854,73168565,92398073,14326617,44348328,3183975,45407418,11581181,27325428,669105,51047803,36812683,78766358,83368048,84349107,44177011,70036438,36312813,66903004,9575954,56153345,7687278,63152504,78602717,42237907,28734791,68702632,22113133,91831487,59197747,30139692,76170907,83210802,508198,50668599,33797252,66611759,35456853,82427263,61263068,31161687,8729146,68204242,8055981,47213183,62490109,63372756,58180653,4798568,4119747,16380211,44842615,20867149,30366150,1788101,89637706,89078848,50188404,12348118,30891921,42782652,69136837,15163258,76315420,15075176,43376279,16097038,92353856,65074535,89419466,7646095,72357096,49236559,29516592,25636669,84840549,99125126,90272749,40686254,6038457,29819940,92803766,26507214,8099994,10597197,87598888,824872,82726008,43506672,1204161,57803235,24619760,96735716,48893685,78218845,41092102,51315460,44481640,57241521,9058407,42967683,22450468,94539824,20122224,27411561,91938191,72274002,13231279,52204879,80014588,48774913,44479073,59318837,98130363,33431960,45919976,86301513,84127901,61960400,23569917,96709982,62762857,25842078,88047921,38008118,51472275,44846932,36396314,84293052,83302115,53666583,74137926,24733232,1250437,37192445,91727510,53888755,33628349,83083131,86821229,21397057,21289531,70372191,15535065,44889423,56484900,33237508,84904436,18806856,8733068,24953498,11543098,19486173,95581843,29188588,64157906,82886381,38061439,15536795,6153406,78589145,61623915,89699445,59405277,10961421,55770687,68824981,61712234,36780454,69579137,43322743,58749,66250369,26139110,16684829,73786944,14731700,52613508,92071990,45428665,1375023,90158785,92692283,18663507,76671482,6525948,40781449,17037369,81677380,79922758,60102965,11757872,41092172,58224549,44550764,39373729,14723410,59109400,10358899,36930650,33553959,48360198,72614359,61859143,36753250,55189057,79738755,37675718,26114953,19376156,68041839,72238278,12571310,94516935,36933038,72495719,61141874,20681921,62936963,33895336,51590803,15902805,40534591,65880522,99297164,71333116,92998591,47738185,4508700,99524975,526217,80555751,58751351,9829782,14045383,57248122,87720882,48088883,74743862,40984766,59177718,98948034,33249630,65271999,7229550,21070110,98462867,20645197,61741594,34497327,16424199,63967300,96852321,89811711,14947650,13173644,92554120,5267545,30764367,3233084,43933006,99604946,61271144,90310261,68110330,91957544,90654836,12664567,22200378,16971929,91990218,16567550,42644903,62232211,6871053,53802686,30218878,9257405,62552524,67513640,34432810,83651211,90061527,59614746,33699435,2917920,95726235,37166608,77187825,37891451,89996536,17385531,33201905,16405341,17068582,20486294,91664334,95290172,17081350,9599614,19939935,72019362,62740044,67030811,78300864,46851987,71300104,24826575,29959549,26292919,47090124,46540998,56955985,43152977,14220886,40152546,85242963,97641116,95395112,79806380,92215320,76540481,4978543,44060493,99515901,91255408,77898274,6986898,91240048,54517921,19272365,18783702,32274392,51588015,22942635,88251446,26392416,45848907,28928175,6111563,61897582,44245960,54606384,66116458,76330843,66271566,83269727,64087743,19457589,59910624,30811010,54762643,5482538,72732205,78561158,17976208,33565483,65338021,65038678,73392814,21673260,37435892,75052463,64848072,26744917,93053405,62693428,3235882,83789391,68816413,27185644,81172706,73031054,70727211,22879907,39801351,8373289,18699206,49328608,59329511,82339363,93790285,77072625,85571389,26776922,38510840,56461322,99965001,80251430,10649306,45075471,77413012,38256849,74724075,83133790,20836893,70541760,38341669,99971982,22766820,6808825,81774825,86460488,30569392,4199704,68316156,34667286,20568363,40356877,27625735,79657802,79942022,7919588,18466635,34493392,12416768,73617245,37957788,28550822,81898046,32699744,88130087,112651,1022677,85117092,37280276,99333375,97379790,49641577,62051033,98943869,79322415,41442762,32159704,66322959,16054533,51464002,64098930,2331773,54263880,75407347,41481685,84187166,90013093,33304202,26734892,79880247,71920426,42947632,62496012,45237957,37560164,36189527,41245325,5822202,62803858,75128745,21919959,56473732,48260151,65017137,7423788,60955663,41380093,17386996,26275734,7104732,57961282,38009615,34295794,78549759,37659250,45667668,8696647,82327024,79136082,16019925,75153252,44664587,90090964,28851716,29635537,1239555,82178706,70367851,24915585,69605283,57393458,4069912,5640302,64602895,84002370,97281847,36808486,55850790,99224392,13862149,57359924,82779622,78884452,31727379,77694700,4095116,97561745,23194618,11365791,55960386,81805959,3880712,97011160,7011964,49598724,61728685,52060076,36580610,30998561,4806458,23110625,30395570,78785507,56515456,92787493,9175338,45617087,60106217,16099750,71083565,54427233,17016156,73235980,16387575,61859581,94911072,17058722,57020481,21533347,92692978,3233569,26397786,81855445,4985896,42307449,64055960,55602660,17365188,68939068,48395186,45990383,47708850,30653863,32151165,13468268,13470059,53393358,5073754,71469330,65851721,3487592,15064655,54014062,32161669,97940276,22435353,7685448,44847298,23740167,26102057,7182642,52293995,1936762,2891150,55143724,94076128,72793444,6521313,84166196,33123618,67227442,19898053,86118021,77377183,10453030,13348726,69848388,23134715,24591705,33935899,63015256,76825057,69697787,51426311,88444207,55615885,74452589,8115266,94360702,20642888,44627776,75820087,65454636,4091162,60176618,92541302,68875490,27041967,65081429,69255765,45995325,53084256,72358170,29401781,19891772,88698958,38365584,42199455,247198,22129328,30694952,33336148,42692881,6819644,48673079,83155442,73109997,176257,85695762,7300047,66045587,49597667,22997281,15948937,48658605,9259676,77301523,9860195,47298834,39847321,90004325,17894977,81274566,70420215,22108665,53632373,93566986,40677414,9398733,50007421,99226875,62357986,35512853,67031644,94595668,59981773,17857111,47887470,12024238,68694897,92033260,51715482,93359396,3773993,8913721,61815223,31491938,82532312,45306070,29932657,83948335,51507425,77300457,6157724,73021291,43501211,9860968,62115552,51149804,23432750,63506281,47893286,91281584,92867155,89217461,17146629,21001913,62428472,87160386,71316369,53842979,86798033,92530431,83747892,7066775,51466049,86242799,54232247,75135448,48675329,24314885,13819358,55669657,3633375,95251277,89804152,36139942,74614639,68128438,10264691,11161731,12303248,18001617,36135,45381876,88904910,60393039,37501808,36845587,32058615,28787861,14349098,61928316,70782102,99729357,85711894,72278539 +37183543,24619760,29994197,89214616,92033260,5267545,45306070,88653118,99690194,17764950,9188443,91664334,90090964,9886593,80316608,68644627,30569392,57803235,89078848,90310261,81853704,72777973,35092039,76671482,78602717,69641513,8807940,36808486,96726697,3487592,59177718,29029316,18466635,47738185,58224549,21993752,34493392,38061439,96735716,60430369,98371444,44983451,51047803,83789391,36753250,54663246,82979980,22942635,57961282,33249630,68204242,4119747,526217,45848907,62936963,83150534,84904436,70036438,24058273,27665211,66250369,36312813,36468541,91802888,29819940,55753905,70420215,55615885,30653863,16097038,45428665,43501211,14947650,8099994,40677414,62740044,86001008,10358899,20568363,15163258,99549373,33797252,75543508,4064751,72274002,6221471,72373496,83155442,7300047,55602660,58749,61263068,99965001,97281847,65715134,96193415,168541,23194618,17539625,39847321,78589145,34236719,8733068,44889423,93933709,874791,4668450,36812683,39553046,20002147,72732205,28796059,6808825,16595436,5832946,22450468,8115266,3233084,50702367,59981773,45919976,62051033,56531125,99917599,68000591,30218878,67451935,56424103,55850790,30998561,11543098,80555751,38009615,32590267,99524975,18699206,90272749,95957797,96852321,9257405,67474219,44627776,17037369,61271144,75777973,29188588,20885148,22129328,92215320,22435353,415901,73392814,89804152,7517032,63015256,63967300,14326617,13173644,35512853,55189057,42644903,59371804,76434144,669105,16445503,15064655,98462867,12024238,90654836,36135,20765474,99515901,51588015,62496012,72725103,94711842,67793644,45617087,56515456,38022834,36780454,74614639,32161669,34044787,2208785,1197320,48893685,61728685,66271566,61815223,21001913,98948034,45407418,16387575,18783702,31161687,49271185,11161731,569864,72019362,85571389,66045587,44481640,98739783,16019925,33123618,19376156,45996863,2891150,77312810,54762643,508198,73124510,40356877,53802686,1788101,32699744,26392416,92692978,72614359,78909561,20486294,71333116,41245325,44550764,53842979,48260151,4204661,31904591,38658347,65454636,73031054,26139110,41092102,97379790,70372191,19939935,65338021,7104732,43376279,87720882,7502255,44177011,33336148,30764367,70004753,4806458,26292919,16567550,31491938,6819644,89046466,36505482,10366309,23848565,2204165,69848388,45794415,11365791,74357852,4099191,8651647,52261574,3235882,2717150,76540481,45075471,29635537,77694700,42237907,73617245,48673079,6986898,70800879,29834512,9575954,57393458,97057187,88047921,83533741,14220886,85023028,41092172,78766358,72738685,56473732,66667729,14626618,40781449,7646095,17068582,11923835,10649306,93057697,47361209,66832478,48892201,99224392,30787683,37620363,99729357,66663942,76703609,82052050,6497830,42967683,68816413,96318094,12664567,14349098,77377183,1375023,55470718,9623492,50567636,74724075,7229550,55143724,17976208,78884452,69255765,32426752,77072625,24826575,36580610,12348118,43506672,57248122,85242963,5970607,59197747,86460488,99021067,48395186,9175338,20645197,17365188,92803766,15535065,69786756,176257,80246713,36930650,6505939,5111370,62430984,84293052,13862149,80014588,62115552,74110882,22766820,33553959,38365584,99125126,59501487,68694897,90004325,27185644,93515664,47213183,30811010,83948335,19457589,48774913,55960386,19486173,50188404,5073754,8055981,53547802,59405277,29401781,89699445,40027975,52204879,76170907,8696647,43357947,51590803,77301523,42199455,18833224,7011964,1250437,43045786,23134715,97940276,18663507,51472275,56461322,45237957,14093520,79191827,23569917,65275241,84349107,79880247,97011160,19272365,45790169,89637706,1431742,70541760,4515343,42073124,82427263,50668599,37739481,35456853,98943869,30395570,67281495,40534591,5822202,40197395,9603598,34698428,14731700,3509435,34497327,73222868,87598888,85116755,95395112,4069912,2917920,99971982,13470059,64602895,71920426,62452034,30463802,68875490,40781854,89217461,47298834,83368048,39201414,54606384,14045383,87710366,38256849,4508700,36685023,81855445,49641577,45381876,40152546,84840549,58180653,81898046,54427233,79136082,29959549,26102057,9829782,1936762,66137019,6157724,96420429,21070110,3183975,30771409,90457870,7685448,33628349,74441448,58208470,47090124,61982238,78218845,66611759,77187825,45995325,24733232,66428795,73168565,46851987,37957788,15075176,36139942,90061527,83210802,52613508,26734892,83651211,29932657,96906410,70596786,15015906,84127901,92998591,6153406,30139692,3088684,10453030,30163921,16405341,82779622,3880712,9398733,36396314,64848072,94911072,37166608,27041967,53648154,13819358,78785507,69697787,92530431,62232211,7182642,95251277,43933006,19101477,11757872,65271999,20681921,73021291,83269727,56153345,98648327,58751351,26063929,39801351,6793819,13348726,85117092,53084256,41380093,79922758,49236559,62803858,42782652,9058407,28550822,20642888,22879907,247198,33201905,82327024,1022677,21289531,91240048,77284619,16971929,86798033,99861373,63059024,82726008,60176618,78300864,82339363,35996293,77898274,55247455,15536795,21673260,10961421,68316156,45667668,62552524,49597667,24915585,66903004,6038457,18806856,10309525,89811711,63628376,62490109,76330843,50007421,77413012,24314885,2331773,93053405,27625735,64157906,71083565,8913721,17957593,37891451,92604458,71965942,63372756,3233569,7423788,62693428,72238278,81677380,30891921,12303248,55770687,92554120,97783876,95290172,16099750,72357096,54232247,74137926,22200378,93270084,71316369,98653983,35192533,61928316,23740167,65081429,91727510,83747892,38645117,94076128,92353856,76960303,77272628,17146629,44784505,48675329,67084351,40865610,34946859,91255408,34667286,63506281,25636669,61859143,7687278,57020481,15148031,59318837,79806380,33431960,37192445,8634541,53666583,59109400,18411915,19898053,61712234,72495719,64087743,68824981,18504197,22997281,51507425,95726235,12416768,16054533,4798568,98130363,92787493,17058722,1569515,99604946,3773993,30694952,97561745,86301513,61897582,36933038,80251430,61373987,65017137,7955293,46870723,16684829,65880522,75153252,92398073,824872,4434662,29466406,23110625,24168411,7919588,29065964,47792865,37435892,16380211,34295794,52060076,86022504,46540998,66322959,69355476,82178706,31733363,48658605,54014062,40984766,57658654,47887470,3294781,8729146,54058788,65074535,13468268,72278539,29510992,29516592,83302115,94090109,48360198,1204161,43152977,85711894,33237508,30366150,4985896,40686254,84684495,9599614,44060493,78549759,11581181,4095116,20122224,81774825,37501808,44348328,33895336,66116458,65047700,28928175,9860968,47708850,36189527,68702632,84187166,77787724,60581278,24953498,39171895,82532312,4199704,83133790,69579137,93562257,51426311,96709982,50806615,87160386,91957544,64098930,99658235,18131876,27187213,49328608,15902805,66885828,44844121,68102437,71300104,44842615,9860195,22721500,41481685,30543215,83083131,19891772,93566986,26776922,85695762,2607799,73235980,8791066,75407347,54199160,79738755,99226875,62357986,81172706,57241521,88130087,20867149,17894977,39986008,16424199,64055960,97641116,32151165,749283,34432810,8373289,49598724,72358170,112651,53632373,26744917,75229462,79657802,53393358,68041839,92692283,59910624,79821897,39373729,76315420,31126490,13231279,61859581,56484900,63152504,52293995,26664538,99333375,26275734,61741594,4091162,88698958,45990383,3633375,8128637,17727650,57359924,76825057,59614746,95581843,77620120,44846932,94516935,74743862,42307449,17385531,33935899,66319530,33699435,75052463,91938191,26493119,22108665,82897371,37280276,57240218,71469330,42692881,90013093,26397786,91831487,75128745,82886381,37659250,92541302,68110330,68128438,4787945,27325428,26114953,82651278,7066775,5482538,89419466,86543538,75135448,38008118,43322743,32250045,26998766,62428472,81274566,51715482,72793444,17016156,67030811,14723410,88251446,32274392,24591705,91281584,86242799,67124282,37675718,69136837,54987042,1239555,70367851,60393039,26507214,57163802,73109997,44479073,12571310,6111563,17857111,65038678,91141395,33304202,54517921,73786944,26863229,8129978,55669657,28734791,5640302,33565483,89996536,38510840,21533347,84406788,321665,71657078,75820087,92071990,14363867,92867155,44245960,94595668,88444207,37224844,86118021,56955985,79942022,38494874,44473167,34698463,25842078,70727211,74452589,91990218,75986488,77300457,10264691,17386996,78561158,48088883,61960400,86821229,94539824,6871053,38341669,65851721,21397057,6525948,61623915,9259676,79322415,60106217,51464002,27411561,60955663,22405120,30090481,36845587,31727379,32159704,20836893,15948937,44847298,20140249,81805959,61141874,42947632,44664587,18001617,68939068,44915235,10597197,4978543,32058615,93790285,95010552,60102965,45049308,54868730,99297164,69605283,51315460,28851716,93359396,67031644,51466049,90158785,89499542,59329511,84166196,67227442,71522968,84002370,53888755,21919959,49882705,22113133,47893286,94360702,67513640,70782102,28787861,37560164,23432750,33061250,62762857,88904910,17081350,6521313,51149804,41442762,54263880 +99297164,70541760,3294781,20642888,70596786,53084256,20486294,51047803,19486173,78589145,75543508,40152546,90310261,34698428,33304202,62496012,64848072,34493392,68816413,6521313,79806380,59177718,60430369,32161669,83083131,8115266,68644627,45667668,15535065,46870723,61741594,67793644,92541302,15536795,75777973,43376279,79136082,72357096,74357852,39201414,13468268,37620363,68000591,73124510,669105,14731700,30787683,874791,72278539,7687278,11923835,81853704,64055960,59614746,42307449,33336148,54427233,65275241,18131876,36753250,14045383,62430984,77620120,30395570,5640302,21533347,83651211,61859143,49641577,40356877,32159704,70800879,44842615,749283,65017137,19891772,7423788,22766820,12348118,3233084,92692978,9623492,2717150,26392416,66271566,75229462,7502255,87160386,94090109,62803858,3183975,89046466,66250369,72495719,96318094,54058788,63967300,10358899,8373289,55247455,26507214,23569917,56424103,99524975,9188443,53842979,15015906,28734791,20867149,38645117,23134715,55189057,40197395,40984766,67281495,44550764,77072625,73168565,7011964,62936963,88047921,46851987,68939068,57803235,168541,22997281,81855445,42782652,83789391,78602717,38658347,12416768,96420429,57359924,64087743,1375023,70004753,66832478,89637706,96906410,98943869,55143724,4119747,4204661,9886593,824872,99965001,16097038,59501487,45237957,17037369,4434662,98462867,52261574,68316156,99515901,79657802,22879907,29819940,14626618,44060493,21673260,55753905,94539824,6153406,45996863,16405341,8099994,37957788,14349098,26102057,71316369,80246713,85116755,86798033,53547802,21001913,7300047,58749,75128745,23848565,97057187,71469330,50007421,40027975,74614639,39847321,44481640,91831487,54987042,71522968,30366150,22405120,62740044,74441448,14093520,99861373,19939935,75153252,18001617,30764367,24953498,82897371,44844121,34236719,45790169,84684495,36580610,93053405,8128637,55615885,19101477,96726697,99226875,29029316,54606384,82178706,92353856,27411561,26139110,36505482,81898046,99549373,46540998,89214616,66885828,40677414,18699206,83533741,77413012,57241521,81677380,59109400,19272365,26063929,65338021,91664334,45049308,20836893,60106217,65715134,1197320,62357986,80316608,63372756,20122224,1204161,40781449,30891921,30463802,96193415,51715482,88653118,82339363,82532312,95957797,16567550,15064655,26114953,17727650,68824981,77312810,33628349,87720882,31727379,40686254,20765474,8913721,93515664,48260151,33895336,92803766,57248122,95290172,50702367,89078848,44245960,44784505,4515343,79738755,53802686,6793819,98648327,35092039,72732205,48893685,93057697,25842078,32274392,96709982,69641513,20002147,91255408,68128438,72019362,29510992,59371804,36812683,86460488,39171895,95395112,4064751,55850790,13470059,6986898,26664538,44889423,30139692,91938191,53632373,78218845,34698463,30694952,54263880,54199160,5970607,84904436,50806615,44983451,29834512,37224844,26734892,9860968,99917599,14220886,98130363,14947650,31126490,40865610,89804152,67030811,7229550,9603598,10264691,72274002,64157906,78884452,42073124,80555751,38494874,61897582,73617245,17539625,26292919,76825057,77284619,16387575,5482538,47361209,76540481,14326617,82726008,39801351,82779622,6808825,37192445,5267545,1431742,45428665,69355476,77377183,65047700,21070110,18466635,73031054,10309525,78766358,41245325,45794415,94911072,86543538,84840549,9860195,97940276,63059024,7646095,27625735,58751351,45407418,95581843,4985896,33237508,24168411,68694897,15163258,86301513,94516935,65038678,37659250,33797252,23432750,9259676,73786944,60581278,61712234,13862149,83210802,21289531,29466406,36396314,6505939,26863229,83133790,14723410,40534591,4798568,6871053,36845587,18783702,60102965,53888755,92787493,66116458,68204242,88130087,26275734,79821897,74110882,96735716,87710366,29188588,61859581,90013093,97561745,76170907,69786756,81172706,8791066,9398733,6221471,61623915,37560164,7517032,43501211,3233569,63015256,28550822,66322959,95726235,44177011,76330843,88251446,2208785,112651,24058273,17764950,62232211,8807940,99690194,54014062,74724075,65454636,84293052,32590267,57020481,56473732,33553959,4668450,48673079,44348328,94076128,97011160,29959549,75407347,84349107,49271185,39986008,38510840,43933006,415901,30163921,27665211,53666583,9058407,85242963,8634541,28851716,35996293,55470718,4787945,22129328,70420215,26397786,41442762,95010552,41092102,34432810,92604458,17058722,43045786,3235882,62428472,10961421,50668599,59318837,9829782,43322743,14363867,62693428,24619760,63152504,64098930,92554120,66428795,33201905,29994197,81805959,49236559,94360702,28928175,32151165,19376156,2607799,67451935,16054533,85711894,69579137,22200378,30653863,90090964,34497327,43152977,9175338,85695762,62452034,48774913,35192533,54663246,34946859,99021067,4099191,68102437,38256849,72358170,88904910,16595436,38061439,42644903,6819644,22450468,83150534,73392814,51590803,44846932,7104732,8696647,96852321,36685023,30811010,58208470,61960400,93566986,92867155,47708850,36933038,5832946,93933709,44473167,57240218,51588015,38365584,2204165,88698958,77272628,33123618,7919588,13231279,51464002,56515456,72793444,92692283,43506672,42199455,16971929,21993752,526217,50567636,20140249,75052463,66045587,51472275,76315420,37435892,33935899,29401781,4069912,11161731,48658605,47213183,36930650,47090124,81274566,33249630,56461322,31904591,6157724,98371444,78561158,8651647,67124282,76960303,83948335,65074535,33699435,31733363,17365188,72614359,13348726,3880712,20568363,39553046,17146629,10453030,12571310,15148031,11543098,87598888,18663507,11581181,4508700,93270084,56955985,91240048,76671482,17068582,44664587,98739783,37891451,69605283,76434144,31491938,61982238,41481685,91281584,176257,86022504,80251430,71920426,36780454,54868730,47887470,27325428,70367851,97783876,44479073,79880247,19457589,30998561,18504197,70727211,89699445,12024238,56531125,61815223,89996536,1250437,10597197,79922758,569864,69697787,24591705,52293995,36189527,77787724,45381876,65081429,20885148,38341669,6497830,58180653,67474219,41380093,49598724,26493119,98653983,79942022,16019925,7955293,36312813,9257405,53393358,77301523,99658235,22435353,61728685,84002370,5073754,92998591,62490109,61373987,59405277,4199704,27187213,23194618,68875490,69255765,38022834,30543215,17857111,67031644,69848388,7066775,52060076,82979980,51149804,50188404,45995325,74452589,92071990,17976208,68041839,62051033,74137926,36135,49328608,2891150,247198,91802888,79191827,32250045,72238278,29065964,82427263,84187166,18411915,93790285,64602895,22113133,86242799,59197747,62762857,71333116,89217461,59981773,8129978,33061250,42947632,86821229,61141874,66137019,86001008,76703609,66663942,71300104,68702632,17385531,65851721,80014588,34295794,62552524,57961282,16380211,53648154,33565483,67513640,3773993,17894977,66903004,20645197,77694700,97379790,78785507,89419466,47792865,78549759,1788101,19898053,61263068,12664567,42967683,22108665,73222868,1022677,44915235,92398073,7685448,57658654,91141395,27185644,71965942,11365791,40781854,93562257,17016156,62115552,72738685,30569392,37183543,90061527,30771409,58224549,82327024,91727510,83155442,5111370,3509435,61928316,48088883,86118021,90457870,99224392,45990383,89499542,47298834,9575954,88444207,48395186,85023028,90272749,85571389,45919976,82886381,36468541,16684829,29635537,66611759,48892201,45848907,72777973,56153345,13819358,24733232,75135448,32699744,65880522,3633375,37166608,26998766,4091162,15948937,73109997,73235980,12303248,92215320,6038457,74743862,47893286,39373729,321665,60955663,57163802,11757872,4978543,24826575,44847298,18833224,78909561,65271999,55960386,9599614,17957593,28796059,37739481,84127901,83302115,63628376,42692881,44627776,84166196,10649306,52613508,52204879,60393039,54517921,79322415,99729357,78300864,85117092,30090481,55770687,1569515,70036438,51315460,31161687,23110625,94595668,90654836,24314885,21919959,71657078,70372191,72725103,3487592,18806856,99971982,99125126,37501808,36808486,90004325,32058615,51426311,92033260,17081350,55602660,37280276,45617087,20681921,92530431,63506281,34044787,66667729,77300457,29516592,59910624,15902805,67227442,10366309,42237907,67084351,49597667,91957544,59329511,8733068,48360198,35512853,2917920,508198,71083565,1239555,54762643,7182642,57393458,77898274,69136837,8055981,35456853,82651278,16099750,22942635,8729146,54232247,43357947,3088684,33431960,6525948,66319530,29932657,16424199,51507425,16445503,77187825,37675718,91990218,49882705,6111563,93359396,99604946,61271144,1936762,94711842,83747892,45075471,28787861,51466049,75820087,98948034,81774825,26776922,47738185,48675329,26744917,99333375,4095116,27041967,17386996,82052050,34667286,5822202,30218878,23740167,83368048,60176618,21397057,24915585,83269727,73021291,36139942,89811711,55669657,70782102,38009615,38008118,75986488,84406788,45306070,56484900,41092172,32426752,22721500,68110330,90158785,72373496,15075176,2331773,97641116,4806458,95251277,13173644,25636669,97281847 +71920426,33628349,77413012,62740044,62496012,81677380,34493392,1250437,30787683,39986008,89811711,96735716,5970607,36505482,55470718,59177718,99729357,98462867,35092039,79136082,36933038,14947650,49236559,30569392,44842615,44889423,4119747,7955293,42782652,78602717,85116755,2607799,17037369,79657802,64848072,75986488,94090109,3487592,99524975,60955663,9575954,37435892,23194618,89699445,11161731,62762857,97011160,18783702,65017137,5073754,29819940,13468268,23848565,42947632,66832478,45848907,8791066,8696647,95581843,53547802,61859581,22942635,94595668,44177011,15015906,78589145,40197395,73617245,13231279,93053405,99861373,22721500,55753905,48673079,50668599,63967300,66137019,65271999,13173644,45306070,68041839,92692978,13470059,7229550,16019925,415901,48675329,34295794,92554120,15535065,45237957,54517921,54762643,61928316,91664334,44479073,65715134,34698463,81853704,65081429,9175338,71469330,8634541,52261574,41481685,9188443,88653118,47090124,18806856,50188404,68128438,12303248,45919976,40677414,17146629,14349098,63059024,47361209,77300457,9257405,1431742,77187825,91727510,36396314,17058722,74137926,26776922,26139110,4668450,37501808,11543098,43501211,58749,48260151,28928175,17068582,61373987,67124282,86022504,49328608,54014062,51315460,94516935,62115552,21993752,70036438,29834512,33123618,83155442,78300864,64087743,508198,68816413,80246713,23569917,50702367,3294781,86798033,30764367,5111370,4204661,72357096,77620120,95726235,53888755,45996863,24591705,29959549,66663942,33797252,49271185,42307449,30218878,669105,6871053,99549373,61982238,92787493,26392416,72777973,36580610,96726697,78218845,53393358,7182642,29466406,48360198,57163802,20885148,3183975,65880522,58180653,28851716,65275241,77694700,51466049,14093520,20002147,78766358,9886593,92803766,77284619,73222868,70541760,25636669,89214616,66428795,98943869,4806458,21673260,79322415,10366309,63506281,83789391,56424103,3633375,31491938,38008118,92215320,68000591,24826575,68204242,84127901,76170907,93057697,38022834,63372756,37183543,45407418,38061439,33237508,63152504,4064751,38645117,30163921,72793444,36753250,72278539,39847321,27665211,89637706,57803235,19272365,9398733,68824981,32161669,27325428,56515456,98948034,99515901,32250045,20765474,21070110,1788101,82178706,44473167,38494874,99690194,78909561,13819358,57359924,36780454,38256849,75135448,91255408,20486294,62430984,16380211,82532312,62803858,44983451,29994197,749283,13348726,37675718,53842979,11923835,55602660,46851987,70367851,77072625,59197747,9259676,17957593,7687278,72732205,19376156,24619760,59981773,6505939,54199160,76540481,3235882,6221471,48892201,86001008,93515664,16099750,95290172,29635537,94539824,92604458,37659250,55143724,29065964,22766820,91802888,14326617,42692881,41380093,6986898,39801351,112651,69579137,72373496,61623915,30139692,84904436,49598724,7011964,51590803,7300047,67031644,59614746,26493119,40686254,55850790,65047700,57393458,8913721,4434662,61263068,569864,82979980,33553959,15064655,36468541,82327024,92867155,99125126,3233569,90310261,48088883,26998766,15163258,19101477,22435353,66322959,82339363,69697787,28550822,40865610,29188588,83210802,98371444,68939068,55669657,43045786,60102965,8129978,68644627,26102057,34497327,6808825,40781449,83269727,17894977,35996293,4099191,8099994,33336148,20867149,55189057,6111563,33201905,68702632,58224549,5267545,96709982,4095116,19898053,27411561,30653863,68102437,68875490,8115266,4515343,18001617,76825057,39201414,63628376,62232211,42073124,57658654,88251446,76671482,65851721,95395112,39171895,67227442,28796059,8651647,74110882,83133790,86118021,20642888,45990383,44664587,75407347,90457870,66250369,15075176,43933006,85571389,47738185,79821897,35512853,96193415,51507425,82726008,51472275,9860968,44847298,1022677,77377183,30771409,7685448,29029316,92692283,82651278,68110330,69136837,80251430,89996536,9599614,57248122,44550764,83302115,92071990,38365584,69255765,53632373,78884452,70596786,11757872,36845587,83368048,55960386,67451935,9623492,96852321,52613508,92541302,4069912,66116458,83533741,99917599,80014588,51464002,66885828,92353856,62936963,49641577,3233084,29510992,42237907,47887470,70727211,6793819,93933709,70800879,45995325,88047921,50567636,60430369,92033260,88698958,17081350,83150534,42644903,9058407,40152546,14220886,36312813,35456853,10453030,70372191,87710366,2204165,55247455,72274002,32590267,47213183,48893685,53666583,59371804,38341669,65454636,70004753,21533347,34698428,64157906,52293995,17539625,44245960,64055960,18504197,17857111,84684495,9603598,56484900,76960303,247198,37620363,71083565,51047803,99965001,74357852,97281847,83747892,70420215,93562257,36930650,7104732,6153406,59318837,30395570,2717150,86242799,24058273,15902805,38510840,74724075,22405120,44784505,66611759,22200378,86301513,15148031,26114953,76330843,95957797,97940276,27625735,78561158,44060493,42967683,78785507,33249630,38658347,89419466,63015256,81274566,87720882,82779622,97057187,81172706,45381876,67084351,76434144,52060076,54427233,50007421,54987042,71333116,6157724,321665,79922758,74441448,71300104,99658235,17764950,53802686,62693428,4978543,18411915,47792865,16054533,8807940,72358170,44481640,61815223,90004325,57240218,62452034,67281495,26063929,12348118,66271566,7423788,32159704,53084256,98130363,85711894,12416768,22129328,2208785,43376279,97561745,62428472,72725103,40984766,30090481,43322743,23110625,10264691,4508700,73786944,19457589,88444207,82427263,88130087,84349107,3880712,7919588,72614359,168541,99333375,73031054,99226875,99224392,97783876,1197320,19939935,45075471,4091162,73392814,30998561,44348328,31126490,44844121,56153345,74614639,36135,83651211,4798568,77787724,41245325,75543508,8373289,30891921,26292919,23432750,33431960,12664567,33565483,4199704,67030811,6525948,60581278,69605283,9829782,79880247,50806615,13862149,26507214,67793644,31727379,72738685,49597667,1204161,8128637,66903004,94076128,18466635,91957544,14731700,67474219,62552524,10961421,17727650,10358899,95010552,10309525,86460488,61859143,87160386,90090964,39553046,30463802,6497830,42199455,72019362,41092102,61141874,3509435,56955985,82052050,69641513,93270084,6521313,59501487,8055981,37957788,24953498,20140249,77272628,58751351,71657078,29401781,18833224,36189527,5822202,90013093,62490109,81898046,33304202,24915585,65038678,33699435,30694952,64098930,37280276,526217,75128745,5640302,35192533,18131876,84166196,30543215,28734791,86543538,33895336,31904591,24168411,20681921,88904910,55615885,47708850,32274392,72495719,16445503,15536795,83083131,45428665,92530431,98648327,91831487,34946859,39373729,24733232,16971929,96906410,22108665,90061527,16595436,73124510,74743862,73109997,92398073,62051033,90272749,60176618,29932657,7502255,57961282,85695762,32699744,14045383,37739481,19891772,21001913,22997281,59329511,44627776,91240048,75052463,53648154,75229462,38009615,51426311,79942022,68316156,89217461,27041967,40781854,51149804,95251277,37192445,96420429,19486173,33935899,2891150,26744917,26734892,7646095,31161687,37166608,66667729,20568363,36808486,59910624,98739783,56531125,97641116,73168565,98653983,32151165,21397057,85242963,89804152,51715482,47298834,89046466,17976208,72238278,1239555,80555751,37891451,69786756,73235980,21919959,18663507,56473732,82897371,89078848,56461322,9860195,99971982,4985896,83948335,17386996,48774913,58208470,57241521,54232247,46540998,85117092,44915235,70782102,34044787,2331773,2917920,87598888,48395186,71522968,93359396,84002370,40027975,45667668,41092172,30366150,6038457,7066775,99604946,80316608,66319530,61960400,99297164,93566986,78549759,57020481,29516592,60106217,7517032,84840549,8733068,75820087,176257,874791,26664538,14626618,96318094,33061250,26275734,82886381,61897582,12024238,45617087,32426752,86821229,36812683,71316369,20122224,45790169,69355476,61728685,30811010,75153252,16097038,3088684,49882705,48658605,40534591,65338021,17385531,54606384,69848388,20645197,8729146,43506672,10649306,59109400,54868730,1569515,3773993,34432810,14363867,36139942,44846932,1936762,91281584,51588015,55770687,61271144,34667286,77312810,90654836,5832946,93790285,23134715,84406788,34236719,91141395,17365188,16387575,22450468,77301523,15948937,77898274,18699206,21289531,40356877,26863229,76315420,36685023,43152977,45794415,75777973,81805959,24314885,10597197,84293052,46870723,81774825,65074535,11365791,32058615,31733363,84187166,20836893,16567550,66045587,76703609,43357947,68694897,12571310,17016156,5482538,4787945,64602895,22113133,16405341,45049308,60393039,92998591,14723410,73021291,54663246,94711842,79806380,23740167,94911072,28787861,99021067,54263880,91990218,16684829,67513640,47893286,824872,27185644,22879907,89499542,91938191,61741594,52204879,41442762,11581181,81855445,59405277,79191827,90158785,71965942,16424199,26397786,74452589,85023028,1375023,54058788,37224844,97379790,6819644,61712234,25842078,62357986,79738755,27187213,94360702,37560164 +93933709,70004753,10366309,5073754,68041839,99658235,64602895,88047921,92787493,19891772,9623492,71083565,63059024,72373496,38022834,42692881,61271144,36685023,55247455,69848388,89811711,20140249,16424199,75986488,36312813,67227442,47738185,57163802,15148031,5111370,62430984,10309525,89214616,48360198,30787683,78766358,54014062,72274002,32590267,29188588,42967683,33237508,749283,20122224,83789391,56153345,34946859,31904591,33201905,87720882,75153252,37739481,96193415,72777973,168541,59371804,37501808,37620363,81677380,29466406,90272749,68644627,74110882,4668450,40197395,97783876,34493392,89419466,34295794,63152504,77187825,23569917,95010552,48260151,85571389,77787724,94516935,27665211,36780454,81853704,15535065,38061439,87598888,17068582,44889423,50188404,64848072,44784505,70800879,30764367,45428665,36753250,77272628,54762643,6793819,4099191,53648154,73222868,43045786,63506281,30395570,66250369,73031054,77312810,88904910,60102965,2204165,95395112,71657078,16099750,55753905,30139692,79657802,28851716,65715134,7517032,54427233,22942635,92398073,92998591,22108665,526217,77284619,17386996,24058273,78602717,3088684,42782652,22879907,14326617,65074535,35092039,66137019,98371444,25842078,91281584,30366150,15075176,18466635,95290172,99861373,32151165,83651211,33797252,59109400,9575954,93359396,34698463,62452034,55470718,70036438,95957797,48892201,71920426,96726697,99125126,2917920,82886381,43501211,49328608,321665,20642888,72725103,19101477,27625735,63015256,40865610,53547802,79821897,16445503,99524975,20486294,61859581,44473167,7955293,75543508,55770687,13862149,49641577,80251430,63967300,43357947,4095116,83269727,62496012,88653118,48675329,51588015,98739783,83210802,13468268,47792865,19898053,24733232,16567550,93790285,93515664,71522968,17764950,54663246,49236559,83150534,74357852,25636669,33304202,48658605,7646095,66667729,67084351,10358899,27187213,6525948,66832478,37183543,5482538,44245960,57658654,66611759,86001008,45617087,26493119,74441448,8129978,24591705,9603598,36505482,89637706,60581278,14093520,4798568,44177011,76170907,29834512,824872,44348328,57241521,12664567,82427263,72732205,72358170,93562257,61728685,73235980,32161669,42307449,48893685,79136082,24314885,97940276,4508700,54199160,6808825,80014588,45075471,17081350,17016156,508198,5832946,77072625,40677414,43933006,60176618,32426752,8696647,47298834,13231279,8791066,3509435,6505939,32058615,86022504,669105,19376156,72614359,16405341,20885148,95581843,40027975,10453030,59177718,59197747,98130363,44479073,10597197,38008118,13173644,47361209,92803766,14626618,74743862,30090481,28796059,96852321,66903004,38658347,17957593,50668599,4119747,3233084,21533347,53888755,39373729,27041967,43322743,96735716,44983451,16595436,3487592,61960400,8128637,3294781,6871053,90654836,97057187,43376279,69605283,86821229,63628376,30543215,19939935,9188443,61373987,6038457,45407418,8807940,29819940,10961421,61897582,28928175,62936963,70367851,56484900,41481685,16684829,18833224,79942022,8733068,24915585,84684495,91990218,68875490,82726008,61815223,56955985,32159704,9398733,67124282,7687278,44481640,77300457,18663507,54263880,45667668,68824981,6153406,47887470,31126490,1250437,29994197,18001617,37659250,58208470,68702632,11923835,56515456,37435892,53802686,64087743,26998766,79322415,23194618,49271185,74724075,88698958,26114953,76315420,415901,6521313,3633375,21993752,29932657,68816413,66319530,70372191,4064751,16387575,75777973,66663942,35456853,16380211,92604458,22721500,50702367,54606384,31161687,874791,7300047,11161731,27185644,90310261,45237957,74452589,68204242,68110330,12416768,30463802,16019925,80555751,33565483,60955663,96709982,83368048,61982238,59910624,3880712,65454636,78561158,84904436,99917599,75135448,86543538,69355476,57020481,69136837,82651278,51590803,27411561,41092172,13819358,30771409,1431742,49598724,6986898,78300864,54058788,72019362,62051033,92554120,69579137,62693428,7502255,58224549,39201414,87710366,50806615,73021291,8099994,17727650,73168565,14349098,56531125,55143724,3235882,70596786,91831487,30811010,38009615,36812683,78589145,2208785,78909561,81898046,39801351,89078848,33699435,30218878,67281495,11757872,65271999,69641513,1569515,99965001,26776922,71333116,61928316,33431960,83533741,67474219,35192533,26292919,18699206,1375023,74137926,39986008,23432750,67513640,51472275,94911072,64157906,14723410,91255408,88251446,9599614,66322959,88444207,45790169,14947650,30653863,67031644,71316369,7919588,77620120,52613508,99729357,18131876,6221471,54987042,51047803,24168411,66271566,72357096,65038678,7229550,39171895,6111563,7685448,76960303,92692978,33553959,86118021,30569392,2717150,9829782,93057697,39553046,247198,60393039,90061527,12348118,73617245,65338021,9259676,22200378,40781854,37560164,16097038,20002147,37166608,1239555,8651647,87160386,66428795,90090964,22435353,67030811,91957544,92353856,51464002,78884452,1197320,29401781,68939068,97011160,20765474,91802888,43506672,52261574,38365584,72793444,20568363,57248122,82178706,30163921,22129328,50007421,18806856,46540998,36396314,94090109,9058407,65851721,65047700,11581181,73786944,31491938,77301523,34497327,99021067,89996536,74614639,36139942,48088883,31733363,9257405,84002370,99297164,4069912,17365188,84187166,72238278,9886593,61741594,94076128,82897371,84293052,40534591,84840549,95251277,44915235,77694700,51315460,24826575,26744917,21397057,66885828,86242799,44664587,88130087,99333375,92033260,70782102,57393458,76434144,41092102,45848907,569864,68102437,55189057,11365791,57803235,84127901,59318837,44060493,62232211,47708850,34236719,30998561,85116755,80316608,77413012,97641116,22113133,17037369,17857111,9175338,99549373,59614746,83302115,70541760,51507425,61263068,34698428,26275734,79880247,11543098,62740044,60430369,5267545,67793644,35996293,34432810,7011964,53842979,36933038,82979980,78785507,20681921,86798033,40152546,16054533,33061250,33628349,18504197,66045587,9860968,89217461,27325428,44844121,52060076,50567636,8913721,45306070,40781449,94539824,20836893,98948034,14220886,38494874,82052050,19486173,41442762,91141395,91240048,34667286,79738755,99604946,73124510,78549759,85242963,40356877,14045383,81855445,85117092,45995325,42237907,26734892,45919976,36468541,56461322,29065964,48673079,79191827,22450468,72738685,56473732,91938191,95726235,15163258,10649306,86460488,29516592,4985896,38645117,82779622,22997281,43152977,47893286,20645197,59501487,62357986,2607799,90004325,37192445,47213183,36930650,29635537,84406788,62803858,26102057,28734791,97281847,81805959,12571310,52204879,92215320,38510840,30891921,29510992,8055981,57961282,59981773,45794415,82327024,81774825,1022677,93270084,76703609,92071990,99226875,83155442,46870723,75820087,96420429,44627776,51715482,5970607,37891451,38341669,37957788,90457870,61712234,98462867,93053405,76330843,13348726,3183975,7423788,75128745,23848565,53084256,15536795,67451935,61141874,82339363,59329511,16971929,81274566,29029316,22405120,97561745,98653983,76671482,24953498,30694952,3233569,37675718,36135,65275241,33935899,55850790,48774913,77377183,70420215,65880522,68128438,71300104,26863229,55602660,6497830,97379790,2891150,73392814,69786756,89499542,68000591,22766820,84349107,19272365,37224844,59405277,61859143,41245325,71469330,58751351,62762857,89804152,23110625,17058722,14731700,54868730,54232247,29959549,26139110,75052463,6819644,32250045,18783702,1788101,8634541,38256849,42644903,1204161,4091162,17146629,89699445,44847298,35512853,4787945,85695762,36808486,94711842,3773993,34044787,15015906,8373289,54517921,69697787,40686254,78218845,56424103,32699744,86301513,83133790,5822202,96906410,55669657,31727379,58180653,14363867,26392416,98648327,57359924,85023028,70727211,65017137,7182642,21289531,1936762,64098930,33123618,96318094,53632373,66116458,92692283,68694897,26507214,19457589,51426311,71965942,23134715,92867155,72495719,99224392,84166196,90013093,9860195,8115266,42947632,53393358,13470059,53666583,49882705,15902805,93566986,33336148,26397786,81172706,94360702,7104732,45381876,21001913,90158785,99690194,7066775,47090124,18411915,21673260,79806380,5640302,62552524,26063929,33249630,15948937,63372756,20867149,17385531,46851987,21919959,83948335,91727510,92541302,69255765,45049308,21070110,17894977,45996863,79922758,2331773,62115552,91664334,75407347,15064655,4199704,39847321,40984766,76540481,45990383,36580610,99515901,36189527,62490109,37280276,28550822,60106217,4806458,98943869,44842615,89046466,28787861,83747892,42199455,42073124,49597667,80246713,62428472,12303248,41380093,76825057,51149804,52293995,4515343,55960386,33895336,8729146,36845587,82532312,48395186,17976208,61623915,44550764,23740167,17539625,92530431,72278539,73109997,24619760,112651,55615885,4204661,58749,68316156,94595668,57240218,83083131,99971982,4978543,85711894,26664538,64055960,51466049,176257,75229462,10264691,65081429,77898274,12024238,4434662,44846932,32274392,6157724 +96193415,58749,37891451,98371444,18699206,64087743,95957797,33249630,50188404,51047803,30218878,5970607,10358899,97641116,81274566,35996293,49328608,47090124,44889423,83651211,68204242,80316608,20002147,31904591,44245960,26998766,62496012,50702367,40197395,29834512,15064655,74743862,66137019,31161687,88047921,46851987,66611759,77413012,24733232,4515343,7502255,61623915,44550764,1431742,74357852,14093520,247198,45919976,48673079,78589145,67281495,61859143,82327024,42199455,28734791,70036438,86022504,48260151,99524975,27325428,86301513,35092039,70727211,57020481,6505939,33553959,30463802,59177718,43376279,82726008,8696647,62452034,40356877,45996863,77620120,19101477,38494874,60430369,15535065,56424103,73392814,84840549,71333116,79191827,83210802,39847321,29188588,76540481,89214616,4787945,86543538,65454636,55143724,84684495,53648154,8634541,15015906,26392416,38061439,50668599,55753905,44473167,61141874,18783702,79657802,73168565,4434662,57240218,83150534,81677380,57359924,56531125,55669657,27665211,34698428,1788101,30569392,79821897,53842979,55470718,77312810,12348118,22108665,17727650,4668450,22435353,78549759,69355476,81898046,28928175,40152546,62803858,72777973,45407418,43501211,4099191,40686254,42782652,92803766,94595668,33628349,26507214,44177011,56473732,41092172,76330843,87160386,8913721,13231279,71300104,96726697,32699744,32250045,22879907,76960303,83302115,9188443,89078848,76434144,65715134,92398073,29994197,19486173,3880712,35192533,46870723,98739783,61897582,51464002,75153252,45990383,75543508,54987042,16099750,70800879,39801351,34295794,24619760,62740044,8651647,13173644,65047700,51426311,53666583,70596786,61960400,99333375,5111370,20486294,20885148,76671482,98948034,47361209,28550822,874791,36753250,77377183,93566986,50806615,81805959,99965001,39171895,59614746,43357947,63506281,82779622,26063929,42967683,19272365,75407347,9623492,4119747,77072625,48360198,92604458,66116458,36580610,8373289,99604946,3633375,65271999,89811711,55247455,85711894,37957788,42692881,57393458,88653118,86118021,37659250,62051033,94090109,96852321,45306070,77284619,6808825,50007421,25636669,65038678,24915585,62490109,18001617,29819940,79136082,51590803,57241521,70004753,36312813,43506672,86821229,59910624,92530431,90457870,66319530,21993752,71920426,81855445,56515456,62936963,63967300,97783876,36139942,20836893,112651,91727510,60581278,26664538,26139110,6793819,99861373,89046466,73617245,23194618,7104732,53888755,10961421,37183543,58751351,68644627,21533347,99125126,57961282,98648327,8791066,51588015,40677414,33304202,53084256,52261574,6157724,36930650,9603598,43933006,83789391,52293995,99549373,68939068,36780454,26102057,48892201,74110882,30395570,2917920,93515664,10264691,7011964,22200378,53632373,3294781,96906410,83083131,64848072,14349098,2717150,15948937,29959549,44983451,69605283,47708850,57163802,54663246,18411915,55189057,20642888,12024238,89996536,69579137,32159704,67227442,21070110,80251430,91802888,669105,92215320,49641577,32161669,415901,18806856,55602660,66903004,97561745,82886381,3233084,84293052,56484900,68816413,48774913,30764367,44481640,24591705,62693428,51472275,91831487,33237508,9860195,17016156,92692283,24058273,1569515,99515901,61263068,6153406,77694700,43322743,42644903,39373729,69255765,17764950,4064751,47792865,12571310,41481685,89804152,17058722,37620363,38341669,44842615,61373987,6221471,55960386,41245325,33201905,21001913,63152504,73031054,93057697,45848907,58180653,22942635,84349107,85116755,91957544,11365791,11161731,17539625,95395112,1204161,83269727,39201414,44060493,68102437,91664334,72274002,13348726,75229462,30163921,9886593,77300457,57658654,8099994,31491938,47213183,89499542,94711842,51507425,54014062,36808486,28851716,65081429,84166196,6871053,68824981,68316156,27041967,26776922,14220886,83368048,99658235,68000591,51315460,16380211,44846932,80555751,95290172,35456853,78218845,7685448,9599614,80246713,33123618,16019925,45428665,74137926,20568363,14045383,33565483,79922758,89637706,32274392,83133790,69641513,81172706,21289531,66045587,64098930,91240048,89419466,70372191,99690194,36812683,9860968,60102965,67793644,70541760,99021067,508198,64055960,34497327,68041839,17146629,8129978,45617087,69136837,50567636,15536795,84187166,72614359,9829782,15148031,74724075,83533741,8115266,81853704,7687278,90158785,38008118,26734892,70782102,83155442,59981773,63372756,98462867,4806458,56461322,42947632,57803235,19939935,47738185,30694952,49597667,67084351,60393039,54868730,88698958,16971929,79880247,29029316,66428795,53547802,37166608,62428472,36468541,24826575,4069912,14731700,29466406,92071990,824872,66271566,7919588,37192445,3773993,98130363,41092102,82178706,19898053,66885828,82052050,69848388,26493119,17081350,73124510,88444207,96709982,31126490,16054533,13468268,40781449,74614639,9257405,36685023,92692978,1022677,4204661,11581181,22450468,55850790,52613508,29510992,38645117,75986488,75777973,68128438,93790285,48675329,91281584,77787724,61741594,45790169,27187213,18131876,71083565,23569917,90272749,72725103,16387575,74441448,56153345,23848565,94911072,79806380,36845587,66667729,13470059,8807940,7300047,69786756,77272628,72495719,19891772,59318837,4091162,14947650,99297164,7646095,28796059,95581843,34493392,93270084,29401781,23110625,61982238,5832946,36505482,23134715,93053405,34236719,99224392,40781854,96735716,71316369,45995325,71657078,29065964,92554120,94516935,59197747,66250369,60106217,85571389,16405341,37675718,70420215,39986008,22405120,59501487,6986898,11757872,82339363,94360702,54517921,176257,93359396,85242963,22766820,92033260,91255408,9175338,38365584,14626618,61712234,41442762,33895336,66322959,90654836,67031644,31733363,79738755,21673260,72019362,46540998,168541,49271185,20140249,36135,63059024,59109400,33699435,22997281,92787493,79942022,93933709,77898274,53802686,569864,41380093,37224844,67030811,33797252,87720882,60955663,72373496,45237957,59371804,7066775,526217,55770687,15075176,62232211,26275734,20681921,749283,91938191,70367851,44627776,90004325,32590267,76703609,37280276,36189527,30139692,72793444,17857111,89217461,93562257,67451935,10366309,98653983,63628376,53393358,62552524,20765474,73222868,7423788,55615885,19457589,38009615,49598724,10597197,44844121,75135448,37560164,80014588,94076128,98943869,23432750,35512853,16567550,20122224,6525948,40534591,62430984,86798033,96318094,49236559,6819644,30771409,15902805,52204879,17037369,16595436,88251446,66832478,12303248,61728685,67124282,54606384,18663507,5822202,77301523,82979980,84406788,37739481,29932657,12416768,82651278,65275241,17068582,81774825,84904436,90310261,99917599,26114953,90090964,34698463,30891921,85117092,30998561,51466049,72357096,44479073,68702632,18504197,9398733,44348328,3183975,36396314,78300864,6038457,76170907,97281847,2204165,16097038,77187825,4985896,8128637,75052463,95726235,9575954,62357986,48395186,43045786,11543098,321665,52060076,54232247,61271144,61815223,18466635,22129328,65017137,72278539,71965942,91141395,7229550,42307449,6497830,65851721,22721500,2331773,78766358,37435892,5267545,72238278,30366150,75820087,99729357,90061527,72732205,38256849,10309525,2891150,71522968,14723410,54199160,54762643,44784505,61859581,57248122,30543215,20867149,6111563,4508700,5073754,92867155,34667286,2607799,34432810,86001008,92998591,68694897,73786944,59405277,48893685,4095116,31727379,21919959,3235882,83948335,15163258,78909561,42073124,17385531,99226875,61928316,56955985,38022834,37501808,26863229,76315420,27625735,10649306,84002370,12664567,48658605,85023028,82427263,19376156,65074535,48088883,64157906,87710366,45667668,6521313,17957593,47298834,2208785,84127901,90013093,73235980,30653863,1197320,4798568,76825057,26292919,9259676,32151165,78561158,43152977,33431960,8733068,92353856,95010552,58208470,71469330,27185644,99971982,62762857,45794415,3509435,13862149,8055981,40027975,40865610,54058788,96420429,3233569,72358170,24168411,34044787,13819358,42237907,86460488,16445503,51715482,17894977,38658347,1239555,3487592,7955293,23740167,97940276,65880522,67513640,28787861,33935899,39553046,3088684,65338021,30787683,5482538,30811010,78602717,60176618,94539824,11923835,24953498,40984766,66663942,82532312,68110330,69697787,10453030,26744917,44847298,75128745,14363867,33336148,7182642,47893286,44664587,33061250,14326617,95251277,4199704,29516592,1250437,59329511,72738685,83747892,25842078,78785507,86242799,64602895,67474219,5640302,30090481,27411561,51149804,1375023,73021291,82897371,54427233,45075471,32058615,92541302,79322415,74452589,78884452,97011160,1936762,17365188,62115552,34946859,9058407,7517032,68875490,87598888,73109997,36933038,29635537,17386996,91990218,49882705,16424199,47887470,32426752,17976208,58224549,22113133,24314885,16684829,89699445,38510840,88130087,63015256,8729146,20645197,97379790,26397786,88904910,21397057,85695762,54263880,45049308,97057187,4978543,45381876,44915235,18833224 +10358899,99021067,33797252,15535065,54014062,62490109,92803766,30463802,26292919,24058273,19376156,95957797,62452034,84840549,38658347,31904591,89078848,37659250,40197395,55770687,33304202,96193415,93933709,44245960,18806856,48360198,62740044,72777973,34946859,63967300,92787493,62496012,5267545,9599614,27665211,41245325,66045587,43501211,61741594,2717150,78602717,66832478,89637706,46851987,14349098,48892201,26744917,14220886,36685023,28928175,11923835,23569917,96726697,45306070,3088684,17081350,49271185,94516935,50007421,14731700,17068582,22129328,27411561,38365584,70004753,99549373,29834512,67793644,40677414,33201905,72373496,4508700,44889423,3487592,60581278,8733068,72274002,77284619,55470718,9398733,44847298,51472275,569864,6153406,4064751,65271999,5111370,24953498,16097038,67281495,247198,64098930,20568363,78589145,88653118,5073754,80251430,32590267,65715134,92604458,22942635,12664567,20885148,99658235,69355476,36753250,80014588,72738685,8099994,58751351,68824981,36468541,35092039,36580610,31491938,29466406,47361209,61815223,28796059,77787724,1431742,59981773,38061439,92692978,77312810,61897582,80316608,17037369,33431960,51590803,52060076,42782652,33628349,78909561,73617245,20486294,82339363,34698463,22108665,1788101,71083565,40534591,38256849,19898053,8128637,59614746,61373987,28851716,20642888,17365188,40152546,77694700,58749,57020481,81853704,77272628,19939935,61712234,37620363,7687278,42967683,80555751,81898046,72732205,17016156,14045383,20002147,83150534,60430369,66250369,56473732,77301523,91957544,98739783,48893685,56515456,22435353,84293052,61960400,23194618,36312813,61982238,47792865,50702367,30366150,29994197,24591705,69786756,68816413,5970607,98462867,44473167,38022834,82178706,76330843,10366309,71300104,21001913,16387575,55850790,83789391,63059024,2917920,36808486,13862149,53084256,168541,34044787,89214616,73235980,70367851,16595436,88698958,22997281,321665,68000591,40027975,68694897,54199160,60106217,98371444,33237508,91802888,72357096,79657802,39801351,49598724,35456853,21070110,38645117,87720882,37183543,9886593,68644627,4099191,35192533,19891772,44844121,16424199,4434662,83651211,68102437,84904436,33699435,1250437,91727510,36812683,90310261,6819644,53648154,67030811,79136082,4668450,3294781,30569392,74110882,63152504,54427233,75128745,43322743,34497327,70420215,81677380,59501487,4095116,90090964,78785507,72725103,7919588,57240218,47090124,70596786,3880712,8129978,47213183,27325428,61859143,69136837,65338021,43506672,18783702,6808825,44983451,66611759,14626618,21397057,8651647,36505482,22766820,16099750,45790169,45237957,26392416,86301513,43376279,90272749,7955293,83083131,13468268,29932657,82327024,37891451,74614639,30395570,88047921,97011160,81172706,12303248,68316156,17539625,29819940,17386996,63628376,37675718,8807940,16445503,51047803,62232211,13470059,20765474,41481685,66903004,57359924,66428795,93566986,92398073,874791,98948034,51588015,34432810,58224549,82726008,55753905,81805959,99861373,19486173,55247455,76703609,70541760,56461322,32159704,55189057,99515901,76434144,61263068,92554120,11161731,26275734,30787683,56153345,24915585,43152977,63506281,77413012,85571389,30090481,14326617,88444207,85711894,48395186,9603598,41092172,14093520,49236559,59910624,45428665,30653863,26493119,44060493,6871053,8634541,86001008,17146629,98943869,44664587,40781449,53393358,112651,62430984,59109400,8729146,8115266,61271144,3233084,65454636,33565483,93053405,26998766,6038457,15015906,98648327,415901,26102057,43357947,91664334,29401781,65851721,23432750,42237907,5482538,79191827,1239555,8696647,39847321,17957593,75407347,36396314,83533741,6986898,30998561,7502255,62936963,84349107,34698428,71469330,78884452,67084351,94911072,1197320,50668599,36139942,72278539,97641116,7646095,76315420,4985896,7517032,99965001,96709982,50806615,56955985,86543538,79942022,72019362,59197747,73222868,8791066,9058407,99125126,15064655,84684495,49328608,59371804,11581181,14947650,28550822,98130363,79821897,23848565,62762857,68702632,16405341,93790285,87160386,8913721,97561745,20122224,86821229,73786944,55669657,83133790,73392814,10309525,62051033,78218845,77072625,70372191,15536795,75135448,32250045,7011964,91281584,17764950,48673079,59177718,43933006,47887470,45667668,42199455,526217,84166196,91255408,45919976,73031054,61623915,83210802,89804152,10453030,824872,24733232,86118021,94711842,96735716,90061527,34493392,79922758,46540998,44177011,39171895,54868730,93359396,70036438,50188404,86022504,39553046,21993752,25636669,72614359,39201414,2607799,6793819,56531125,57658654,70800879,65081429,48774913,7685448,75229462,36930650,19101477,11543098,58208470,44481640,76540481,57393458,4978543,20140249,9860195,44627776,7423788,44348328,4119747,40356877,97783876,60102965,24826575,10961421,16684829,30771409,16054533,38008118,67474219,89217461,73124510,74357852,50567636,30163921,85242963,29188588,26734892,53842979,68110330,33061250,26664538,68939068,3235882,92033260,89811711,29510992,69848388,508198,9829782,12348118,94595668,7229550,36135,68041839,71965942,33336148,45075471,93057697,69641513,55602660,35996293,15148031,13173644,749283,3233569,32426752,18001617,95581843,55615885,82532312,1375023,82779622,75153252,89419466,45990383,2204165,24168411,6111563,74743862,1569515,3509435,27625735,94090109,64087743,54762643,21919959,86460488,70727211,7066775,93515664,34295794,9860968,12416768,45407418,82052050,26139110,69579137,33249630,4798568,12571310,30694952,66137019,96852321,26063929,42692881,77300457,66885828,44842615,51464002,99524975,42644903,62803858,2208785,6157724,59318837,71657078,91990218,64157906,44479073,6525948,56484900,9575954,86242799,52261574,30218878,89996536,61859581,96420429,48088883,44784505,36189527,59329511,20836893,54606384,83155442,45794415,91938191,74441448,5832946,40984766,9623492,59405277,15902805,75543508,49641577,75986488,68875490,99971982,29959549,6497830,76671482,32151165,84406788,7182642,16380211,61728685,60955663,17727650,77377183,68204242,78300864,66667729,67227442,65074535,4515343,79806380,37739481,37192445,21533347,83302115,4204661,74452589,64848072,48658605,81855445,40686254,82886381,52204879,3773993,42073124,38510840,32161669,77898274,57803235,58180653,1204161,669105,93270084,44550764,95395112,97057187,57163802,19272365,86798033,96318094,62693428,19457589,69605283,51507425,30764367,92215320,43045786,2891150,53547802,42947632,18504197,48260151,83269727,17894977,99729357,41092102,66271566,14723410,92998591,47738185,79322415,30811010,29065964,66663942,67031644,54263880,4806458,40781854,75820087,34667286,79880247,10649306,92541302,31161687,51715482,18131876,69255765,62357986,99690194,57961282,23740167,85116755,22405120,26776922,84127901,4787945,41442762,47708850,91831487,6505939,21289531,3183975,94539824,37224844,84002370,89046466,65047700,32058615,24314885,48675329,75777973,63015256,90004325,53802686,36845587,40865610,92353856,31727379,6221471,22200378,33935899,84187166,24619760,73021291,78549759,91240048,36933038,64602895,34236719,62552524,97281847,22113133,9175338,87598888,72495719,99224392,36780454,38009615,82651278,74137926,28734791,47298834,37435892,82979980,83747892,95010552,37957788,27041967,77620120,18663507,99333375,53888755,97940276,71333116,73168565,91141395,38341669,30139692,45995325,45996863,45049308,55143724,13819358,10597197,65880522,85117092,83368048,37166608,71316369,65017137,4069912,18411915,30543215,89499542,76960303,16019925,39373729,54232247,31126490,32699744,20681921,81274566,18699206,78766358,65038678,15948937,13348726,67451935,99917599,49597667,9188443,9257405,29516592,26114953,46870723,22721500,99226875,12024238,31733363,56424103,11757872,82427263,39986008,72238278,2331773,72358170,20645197,4091162,63372756,85023028,37501808,15163258,88251446,92692283,61141874,32274392,7300047,45617087,29635537,37280276,81774825,3633375,38494874,64055960,15075176,96906410,54663246,52613508,16567550,33895336,29029316,26863229,1022677,9259676,94360702,66116458,57241521,90158785,17058722,28787861,16971929,93562257,95290172,44846932,26397786,18466635,90654836,87710366,7104732,33123618,11365791,60176618,53666583,30891921,69697787,99604946,95251277,90013093,27187213,54058788,66322959,66319530,55960386,67124282,41380093,51466049,98653983,51315460,89699445,8055981,70782102,80246713,27185644,76825057,176257,5822202,18833224,73109997,25842078,99297164,4199704,78561158,51149804,42307449,77187825,13231279,45848907,53632373,33553959,22450468,51426311,6521313,79738755,94076128,82897371,17976208,26507214,23134715,35512853,71522968,88904910,71920426,14363867,44915235,54987042,85695762,88130087,95726235,75052463,1936762,17385531,62428472,22879907,5640302,21673260,92071990,97379790,68128438,52293995,90457870,49882705,47893286,61928316,83948335,54517921,65275241,62115552,76170907,60393039,92867155,72793444,74724075,67513640,8373289,17857111,10264691,45381876,23110625,37560164,92530431,20867149,57248122 +44481640,36780454,16097038,83210802,88653118,77072625,66667729,16099750,61263068,53393358,69641513,36933038,247198,96193415,77312810,16445503,68000591,35456853,53547802,90457870,55189057,39801351,83651211,40027975,2917920,38645117,95726235,29834512,36312813,15535065,61141874,6986898,9257405,60106217,96735716,79942022,99549373,78766358,7919588,99515901,26114953,64087743,49641577,68875490,32590267,66250369,43376279,6808825,73124510,6505939,92554120,64602895,40865610,63967300,33237508,64848072,95957797,62051033,19376156,96726697,18783702,88444207,63059024,81805959,79136082,99524975,56484900,49236559,2204165,70596786,85116755,56531125,70004753,9575954,39373729,36505482,92692283,17764950,7955293,37192445,68110330,75543508,29932657,31126490,4668450,8129978,12348118,31904591,85117092,55470718,59501487,86022504,5111370,58751351,10309525,36808486,37620363,61271144,59109400,54762643,32159704,38365584,5267545,73786944,9259676,18504197,22997281,63015256,45237957,48892201,16054533,90013093,33895336,51047803,18699206,54263880,82339363,40152546,44664587,76434144,61373987,13231279,76960303,67451935,87710366,33699435,52261574,79657802,54427233,5482538,9603598,26507214,66428795,59177718,7300047,55669657,8696647,39847321,59371804,98943869,22942635,61859143,53632373,55753905,17857111,94539824,99729357,86001008,70036438,82897371,62430984,5640302,30366150,22766820,74743862,47738185,61960400,65038678,81898046,16567550,45794415,34044787,45407418,29959549,94360702,48088883,14947650,84127901,78785507,89499542,72357096,50668599,96906410,9886593,43322743,30771409,54014062,67793644,88904910,32250045,98648327,51464002,92033260,61859581,8807940,72238278,68316156,68644627,8651647,1431742,16387575,49328608,55770687,81274566,66271566,93933709,4806458,95290172,90310261,76703609,44842615,60393039,3509435,81677380,27665211,88698958,57803235,44784505,10358899,33249630,62496012,73392814,53888755,30764367,31727379,8128637,84187166,70367851,83150534,78602717,35192533,94911072,62452034,4798568,4099191,37501808,18131876,74110882,62552524,44983451,21533347,73031054,51149804,24058273,27411561,38061439,65338021,40984766,22113133,41092102,93270084,77300457,28928175,34493392,26493119,68702632,77187825,61928316,20002147,94090109,84349107,47887470,97940276,30891921,41442762,10597197,77377183,19939935,88130087,92541302,7011964,53084256,32161669,93057697,62693428,17068582,3880712,95010552,42947632,18663507,26392416,71333116,19457589,39986008,21070110,74357852,6157724,72495719,65275241,66045587,874791,24915585,4064751,72738685,22108665,91664334,66832478,66903004,40686254,21919959,22435353,51426311,52060076,61623915,72278539,36753250,22405120,71657078,72373496,53666583,78300864,84684495,29466406,51507425,45919976,68204242,43045786,26292919,65081429,94076128,44889423,415901,20486294,29994197,39171895,14220886,33797252,19891772,50702367,52204879,98130363,97011160,30543215,33201905,91802888,14723410,168541,62936963,321665,38008118,65715134,78218845,83789391,64055960,78589145,17957593,65851721,95581843,44348328,6221471,71469330,60430369,54517921,62803858,48893685,37166608,92867155,76315420,31491938,2331773,11161731,38022834,23848565,41245325,22450468,69355476,19101477,80014588,62740044,69697787,65017137,97561745,526217,16019925,13173644,749283,26139110,36189527,75153252,74452589,22129328,9188443,42644903,82726008,18833224,17727650,68816413,14093520,37659250,35092039,26998766,20765474,25636669,79821897,2607799,72777973,29065964,19272365,81774825,72274002,66611759,37224844,97379790,17539625,34295794,17365188,45990383,75986488,69848388,69255765,37280276,99297164,72793444,90004325,47090124,38494874,71522968,44245960,84293052,17016156,30787683,83533741,99658235,80316608,82052050,40356877,62490109,90090964,41481685,97783876,64157906,93053405,71300104,18001617,64098930,99971982,12416768,23432750,85695762,17386996,77284619,29819940,57658654,89217461,83302115,91255408,69136837,89214616,15075176,86118021,87160386,60955663,10366309,73235980,11543098,32426752,19898053,60102965,40197395,3633375,50806615,5832946,11581181,49598724,37739481,99226875,56424103,112651,29029316,669105,86460488,49882705,95395112,30218878,3088684,43506672,89078848,14045383,19486173,16380211,9860195,47213183,1197320,29401781,82327024,23569917,17976208,1250437,48260151,26397786,41380093,33628349,51315460,75135448,89419466,85242963,98462867,54199160,68694897,79191827,20885148,40781449,20568363,57359924,54058788,37675718,79880247,70541760,37957788,9599614,71965942,77301523,13348726,10453030,67227442,49597667,1239555,92787493,63372756,56515456,34698428,52293995,84904436,45996863,36139942,37183543,59329511,94516935,13468268,14626618,77787724,77272628,4069912,45667668,7182642,65271999,67030811,81853704,14326617,44844121,80555751,63152504,30163921,4787945,44473167,28796059,68041839,81172706,99917599,76671482,91957544,21397057,75407347,27625735,43933006,91990218,56461322,42307449,78561158,30998561,77898274,44060493,18411915,54987042,57961282,55960386,94711842,98948034,44177011,70800879,36930650,76330843,20645197,61741594,89637706,99604946,2891150,53802686,87598888,51472275,29188588,49271185,7517032,66322959,71920426,57248122,47893286,34236719,5970607,73222868,92998591,66137019,76825057,46851987,7104732,75128745,33431960,99965001,88047921,91240048,92071990,82886381,8373289,3233084,9175338,45075471,72019362,7685448,20836893,91281584,63628376,8099994,5073754,13862149,85023028,99861373,26863229,34667286,26275734,48673079,38510840,43501211,16971929,65880522,5822202,91141395,2208785,68824981,20140249,90272749,569864,21673260,48675329,40534591,54663246,93566986,6793819,59910624,77620120,4095116,55247455,14349098,58208470,54606384,8115266,56955985,32151165,58749,55615885,84840549,91727510,82178706,75229462,67281495,50007421,26664538,17081350,42692881,61815223,31733363,79922758,37435892,88251446,76540481,57393458,46540998,28787861,45995325,15015906,98739783,80246713,59197747,66663942,52613508,48774913,54232247,86821229,8729146,7502255,72725103,10961421,12303248,14731700,62762857,26776922,58180653,44915235,86242799,98371444,61982238,2717150,87720882,57241521,21993752,11757872,92604458,15902805,96420429,51715482,99125126,1788101,7229550,35512853,83368048,60581278,96709982,62428472,48360198,30090481,62232211,59614746,31161687,24314885,61897582,176257,48658605,1936762,24591705,6525948,42967683,46870723,26734892,99224392,77413012,65074535,36812683,71083565,50188404,53648154,33935899,44847298,80251430,3294781,53842979,75777973,73168565,6153406,33336148,20867149,79806380,30139692,14363867,28851716,28550822,16405341,25842078,33304202,44479073,9623492,29635537,95251277,34497327,28734791,8634541,71316369,55602660,18806856,7687278,74441448,29510992,38009615,27187213,94595668,17058722,16595436,56153345,93790285,34946859,78909561,60176618,56473732,18466635,70727211,15536795,3773993,1375023,51466049,1204161,89046466,73109997,34432810,82979980,17385531,39553046,3233569,20122224,43152977,4091162,72614359,24826575,4204661,38341669,4434662,22721500,7646095,37560164,47298834,33061250,33123618,17894977,36845587,26063929,45848907,74614639,44550764,4119747,84166196,45381876,36685023,92215320,50567636,67474219,97641116,66319530,12664567,22200378,67031644,16424199,69579137,27041967,6521313,61712234,35996293,10649306,92530431,92803766,93562257,32058615,45790169,75052463,27325428,4515343,55850790,8733068,45617087,34698463,92398073,15148031,82779622,83269727,70372191,30694952,37891451,10264691,72732205,82651278,83133790,57240218,17037369,20642888,74137926,78549759,24168411,6497830,6819644,89811711,30653863,41092172,3183975,99333375,84002370,47361209,96852321,70420215,47792865,93359396,92692978,508198,6038457,77694700,23740167,12571310,68939068,68102437,51590803,99690194,1569515,7066775,59318837,42782652,45428665,15163258,13470059,38256849,24953498,44846932,73617245,42073124,16684829,36580610,69605283,85571389,3487592,90061527,81855445,93515664,36135,66885828,38658347,82427263,27185644,23110625,11365791,83948335,62115552,824872,51588015,82532312,86301513,26744917,47708850,9829782,59405277,39201414,67124282,67513640,92353856,20681921,23134715,79738755,6871053,86543538,83155442,30463802,9058407,30811010,57020481,99021067,21001913,98653983,89996536,83083131,76170907,24733232,30395570,55143724,30569392,74724075,63506281,48395186,9398733,33565483,45049308,69786756,8791066,65047700,86798033,12024238,66116458,91938191,4508700,36468541,89804152,57163802,8913721,40781854,96318094,15064655,43357947,97281847,26102057,75820087,13819358,90654836,22879907,15948937,33553959,61728685,70782102,4985896,11923835,7423788,29516592,97057187,83747892,23194618,40677414,32699744,36396314,4978543,54868730,91831487,24619760,58224549,85711894,73021291,68128438,90158785,45306070,72358170,32274392,62357986,1022677,84406788,42199455,65454636,89699445,59981773,79322415,78884452,9860968,44627776,21289531,17146629,3235882,42237907,6111563,8055981,67084351,4199704 +88653118,66832478,1431742,24058273,669105,19101477,7919588,54199160,50668599,36753250,76960303,4099191,88047921,30139692,75986488,52613508,84349107,77620120,74137926,29029316,77284619,36580610,92787493,29834512,66667729,27665211,73222868,6986898,19939935,31904591,7517032,72614359,112651,51047803,80316608,67793644,43322743,20568363,40865610,5111370,60430369,65275241,60102965,95581843,29994197,74357852,8129978,67281495,16971929,37620363,48260151,17081350,48673079,39171895,5832946,38061439,19891772,62496012,17727650,27625735,15948937,24619760,91957544,16099750,99658235,40534591,36808486,40027975,72777973,29466406,30163921,34493392,11365791,90457870,37192445,27187213,84904436,53666583,53547802,5482538,81677380,38341669,71333116,59318837,44889423,74441448,76540481,64087743,90310261,40197395,66045587,321665,62803858,72238278,44983451,86001008,7011964,8128637,59329511,25636669,26392416,3294781,93566986,78218845,38645117,3509435,46870723,1788101,99224392,16595436,58751351,17894977,78884452,94711842,73168565,80246713,57020481,36812683,9623492,176257,66250369,20002147,77272628,52261574,71657078,38658347,16684829,21533347,75135448,97561745,34236719,37891451,66903004,55753905,50702367,63059024,39553046,63152504,5267545,45995325,9599614,37166608,57248122,95957797,84840549,8807940,12664567,26493119,57359924,99604946,71469330,72373496,65715134,22435353,38494874,15535065,44842615,84127901,21070110,45790169,26139110,98653983,4787945,33628349,63372756,83651211,43501211,8099994,29065964,16424199,23848565,29188588,18783702,89214616,39801351,22108665,3233084,34432810,48892201,20486294,92554120,4119747,66137019,68204242,34295794,93359396,95290172,97783876,41442762,34044787,18131876,76315420,40984766,34946859,59614746,87598888,77300457,61859143,62693428,73786944,55247455,37659250,54517921,32590267,76434144,32161669,13862149,59910624,98943869,40686254,70372191,51472275,64602895,82427263,18001617,83302115,86242799,45075471,37675718,8634541,77413012,58224549,9058407,78549759,89637706,70596786,43376279,247198,26114953,14220886,65017137,46540998,34497327,53842979,33304202,79657802,66611759,62430984,81853704,83789391,6153406,49328608,2208785,69136837,68644627,67227442,91727510,94090109,93933709,87160386,81898046,36505482,28550822,33237508,54987042,65880522,30395570,83210802,93270084,13468268,7687278,39201414,72274002,8696647,55770687,45237957,44481640,97281847,15148031,20885148,82897371,74743862,4069912,93515664,42692881,55470718,28734791,10961421,99861373,92692283,62452034,3633375,30787683,37739481,57240218,85571389,64848072,57961282,30543215,73124510,96709982,18699206,93053405,73617245,24953498,80014588,29819940,33201905,30811010,83083131,16097038,53648154,61141874,4199704,96726697,12348118,13470059,43506672,82726008,79191827,4204661,90090964,29932657,37435892,72738685,98130363,17385531,69255765,47708850,76170907,22879907,79922758,749283,92033260,70420215,94516935,22766820,2204165,55143724,68816413,3773993,45848907,45428665,22405120,26734892,508198,77694700,7502255,22942635,36780454,12571310,23134715,42199455,51464002,99021067,88251446,50806615,7685448,67451935,89419466,99549373,1204161,27185644,74110882,53888755,33431960,12416768,3487592,34698463,71300104,7229550,82052050,65338021,42782652,30463802,10366309,33797252,78602717,43933006,60581278,24733232,14731700,2917920,38008118,39847321,62740044,76703609,26664538,4064751,28928175,50188404,85116755,75153252,58749,32159704,33061250,16445503,6505939,55189057,72358170,33249630,98371444,30366150,48360198,86543538,54014062,30891921,17037369,85242963,31161687,3880712,61373987,44846932,47090124,54762643,61960400,65851721,86821229,70004753,79136082,72725103,40677414,26507214,22200378,45919976,17068582,60176618,37183543,97641116,49641577,41245325,77312810,66428795,82979980,62051033,4091162,19486173,92604458,91802888,72495719,874791,95395112,78766358,99524975,93057697,6525948,36933038,28796059,10597197,96735716,40356877,20140249,61897582,54868730,7646095,56424103,22721500,26063929,36312813,72357096,89811711,526217,35512853,91281584,20645197,61271144,59405277,59177718,69355476,47738185,57163802,6871053,36189527,35192533,9886593,28851716,1022677,56531125,1375023,68939068,93790285,83155442,52204879,71920426,2717150,34698428,7955293,7066775,24591705,18466635,96193415,88904910,3235882,10358899,6038457,60955663,62232211,77072625,86022504,99965001,18411915,22450468,44348328,16405341,36139942,4668450,11543098,4798568,17386996,37501808,18806856,6793819,42237907,49236559,27325428,9257405,92803766,47361209,54427233,26776922,73235980,4806458,30998561,46851987,55602660,30218878,38022834,62552524,99690194,415901,84684495,84166196,32426752,66271566,34667286,63506281,70541760,82779622,75820087,69697787,78909561,41481685,61741594,21993752,92541302,87710366,15075176,39986008,92692978,75543508,44915235,19457589,53084256,68824981,75229462,20642888,53632373,1197320,6221471,65271999,57393458,79806380,45306070,15536795,56461322,81855445,95010552,19898053,63628376,59981773,62762857,99917599,65038678,24915585,9860968,63967300,54663246,42967683,88444207,85117092,51507425,74724075,69786756,50007421,85711894,91938191,55669657,36685023,64098930,77187825,14363867,94911072,83150534,9175338,84293052,6808825,96906410,53802686,33699435,67513640,44847298,29401781,94076128,56955985,44479073,84187166,31126490,9860195,70036438,44473167,68000591,65074535,92215320,7423788,45407418,35092039,56515456,92398073,3088684,569864,17146629,19376156,9603598,80555751,35996293,8733068,8791066,51590803,89804152,8729146,75407347,66663942,14626618,21673260,49882705,83368048,16019925,57241521,66319530,96852321,76671482,20765474,61712234,4515343,8651647,55850790,86118021,44844121,23432750,25842078,54606384,60106217,87720882,8115266,44784505,96318094,21289531,23110625,55615885,79942022,68875490,2607799,70800879,6497830,81805959,14947650,14326617,69848388,75777973,68041839,36930650,39373729,85023028,4095116,29959549,59197747,84406788,57803235,44245960,61982238,91664334,71083565,92071990,43045786,7300047,81172706,1569515,42947632,92998591,16380211,42644903,44627776,94595668,168541,33565483,86301513,38009615,76825057,72278539,29635537,90272749,33553959,99297164,52060076,17016156,52293995,91831487,21919959,16387575,17764950,77787724,69641513,61928316,35456853,45617087,54058788,98739783,99515901,68102437,18663507,68694897,96420429,5970607,42307449,40781449,9398733,11757872,76330843,97940276,21397057,1250437,36468541,91240048,17539625,40781854,36135,45996863,78561158,45990383,74452589,20122224,56153345,23569917,67031644,99125126,5073754,70727211,9188443,59109400,33895336,48658605,98648327,71316369,95726235,824872,48088883,65454636,61859581,41380093,26744917,62115552,92867155,27041967,62490109,13819358,51466049,44177011,67084351,89078848,81274566,24168411,62357986,20867149,18504197,47213183,30569392,63015256,69605283,49271185,89699445,43357947,10309525,48774913,82327024,70367851,30653863,48675329,9575954,64157906,37957788,90013093,71522968,48893685,82532312,78589145,44664587,14093520,15064655,57658654,59501487,26292919,20836893,73392814,89499542,67474219,36396314,19272365,41092172,82178706,17857111,10649306,4985896,26998766,8913721,44060493,93562257,38365584,40152546,7104732,6111563,91990218,51715482,17957593,32274392,3233569,13231279,32250045,22997281,18833224,11923835,26275734,89996536,45381876,67030811,43152977,44550764,83948335,82886381,58208470,79821897,72019362,21001913,33935899,14349098,68702632,6157724,56484900,45667668,26863229,55960386,88698958,51588015,54263880,75052463,78785507,85695762,28787861,30764367,90654836,49598724,91255408,90004325,86798033,13173644,62936963,89046466,14723410,14045383,61728685,23194618,73109997,64055960,95251277,4508700,59371804,60393039,89217461,82651278,11161731,83133790,20681921,66885828,42073124,54232247,47792865,97379790,27411561,16054533,8055981,15015906,26397786,61263068,36845587,37280276,53393358,65047700,24826575,66116458,51426311,23740167,83269727,37224844,32699744,97057187,78300864,15902805,79738755,5822202,74614639,15163258,56473732,99226875,72793444,68128438,70782102,67124282,73031054,45049308,80251430,99729357,83747892,97011160,31727379,84002370,22113133,94539824,13348726,17058722,16567550,32058615,24314885,94360702,77377183,99333375,22129328,38510840,99971982,41092102,12024238,2331773,72732205,11581181,33123618,62428472,91141395,82339363,9259676,29510992,38256849,6521313,90061527,61815223,3183975,58180653,4434662,68110330,30090481,37560164,50567636,51315460,98948034,12303248,86460488,88130087,61623915,51149804,6819644,4978543,79880247,98462867,31733363,1239555,47893286,45794415,8373289,65081429,66322959,30771409,17365188,5640302,69579137,92353856,29516592,2891150,81774825,49597667,71965942,68316156,32151165,47887470,30694952,9829782,33336148,73021291,47298834,17976208,48395186,7182642,1936762,92530431,77898274,79322415,26102057,75128745,83533741,31491938,77301523,10453030,10264691,90158785 +89214616,77301523,92033260,43501211,21993752,66611759,72373496,19376156,55470718,8099994,5970607,3294781,61263068,50188404,1197320,17037369,9175338,96735716,91727510,669105,22129328,45237957,50567636,22766820,6808825,75128745,35092039,37183543,72777973,66667729,99515901,40152546,44177011,62430984,11161731,19898053,31904591,56515456,42692881,90272749,57240218,65715134,415901,70372191,52261574,62936963,56461322,24591705,63059024,61373987,44983451,88653118,36468541,72614359,54199160,48360198,67084351,63015256,49328608,51590803,6153406,24953498,76540481,24619760,6038457,36139942,24058273,78602717,72019362,56153345,73124510,8729146,9886593,10649306,96193415,34698428,76960303,247198,36753250,20486294,41481685,23134715,35192533,99917599,66903004,34698463,96318094,13862149,34497327,8055981,94516935,48395186,68816413,78589145,91664334,81853704,7011964,77300457,4668450,62740044,48892201,44889423,78766358,9575954,99965001,55753905,54606384,36930650,57241521,47361209,57393458,59371804,94911072,16445503,63967300,48675329,68875490,59177718,4119747,98130363,20645197,7300047,22997281,34493392,42967683,8634541,61859581,37435892,20885148,5267545,8696647,62552524,53084256,4069912,45306070,77787724,91957544,8733068,80555751,68644627,92554120,26292919,46870723,29932657,31161687,10366309,67281495,32250045,24826575,26139110,16595436,52613508,76434144,34295794,91802888,99524975,43045786,40865610,17068582,99125126,90310261,30366150,77312810,87598888,26114953,48673079,1250437,569864,61982238,68939068,15535065,62496012,6793819,17764950,40677414,83269727,92215320,76671482,67031644,51047803,78785507,45407418,11365791,55770687,40534591,26392416,68204242,82532312,874791,92692978,43506672,1431742,70367851,57658654,2917920,99604946,6505939,39847321,37620363,33431960,27665211,77072625,77620120,47213183,168541,20002147,49271185,5073754,68702632,33237508,66250369,93933709,1204161,14731700,79136082,38009615,57803235,9259676,59197747,82651278,76330843,95290172,6986898,1788101,81677380,1022677,53632373,83789391,28734791,47738185,17058722,26734892,7423788,4787945,38008118,27041967,3509435,66045587,92541302,58749,73617245,8128637,97641116,45428665,8651647,30395570,40027975,17386996,29959549,46851987,85242963,4508700,53802686,73392814,97783876,80251430,21001913,68824981,95726235,38658347,9188443,14093520,38022834,14045383,59614746,82327024,96420429,36312813,50806615,97057187,44473167,55602660,19939935,33201905,66137019,30891921,83651211,4515343,7229550,38256849,83155442,87710366,14626618,47887470,18833224,23848565,33249630,17365188,7955293,15902805,29188588,17539625,80316608,28550822,8807940,29516592,19457589,71083565,94076128,78561158,79806380,54427233,51507425,44348328,90457870,5832946,39553046,3487592,16380211,62803858,82427263,3233569,17146629,99021067,75820087,75543508,75986488,79922758,14220886,9623492,38061439,89811711,3633375,75153252,51315460,33565483,92787493,45667668,55615885,27325428,76703609,526217,47708850,2891150,53393358,53648154,65338021,51464002,28928175,30764367,74614639,7687278,13348726,7919588,31491938,62357986,28796059,31733363,55247455,71965942,60176618,36580610,54058788,79821897,79657802,66319530,63372756,1936762,49236559,72732205,14723410,18783702,84349107,95395112,7685448,77187825,47792865,85695762,96906410,82779622,22435353,50668599,32161669,79942022,10453030,64157906,81855445,15015906,29466406,76315420,89804152,31126490,67793644,32151165,62693428,44847298,52204879,22942635,84840549,10961421,36505482,27185644,30543215,21070110,26776922,55189057,65275241,82886381,94360702,32426752,98653983,72738685,91938191,88444207,29065964,36808486,80014588,71333116,94595668,71920426,60430369,78300864,15148031,37675718,22113133,2204165,99690194,42237907,12348118,98739783,17976208,24733232,19891772,68694897,69848388,73109997,20765474,74743862,98371444,99333375,40781854,29635537,16684829,86001008,79191827,57020481,62051033,55960386,98943869,85116755,30694952,57961282,71657078,72357096,54014062,37659250,65851721,68128438,39171895,58180653,26275734,67474219,13468268,83210802,66116458,3233084,54868730,41092172,83302115,508198,99729357,26102057,99861373,14349098,65454636,8129978,93359396,62232211,92071990,33304202,65271999,29994197,83150534,37957788,48893685,60106217,4434662,112651,3088684,66832478,43376279,48088883,73222868,61859143,16054533,43933006,70036438,50007421,32058615,70420215,6525948,78218845,20122224,30998561,66271566,77284619,2717150,83533741,39986008,59329511,70541760,93566986,82052050,94711842,63628376,9398733,1375023,18699206,39201414,321665,33336148,10358899,96726697,77694700,69579137,78884452,72278539,21673260,44842615,70004753,18411915,56531125,90004325,21397057,69355476,7104732,72725103,77272628,81774825,59501487,78909561,39801351,74441448,75229462,4064751,17957593,13470059,38365584,30787683,84684495,9829782,30139692,34236719,2208785,45919976,90654836,19101477,45848907,49641577,88251446,43357947,53547802,35456853,4806458,15075176,88047921,15064655,83747892,72793444,54663246,37280276,62452034,74724075,18806856,5822202,33935899,84904436,3183975,51466049,51472275,34432810,18131876,99658235,61141874,16567550,69136837,61623915,88130087,98948034,6819644,30771409,42782652,61728685,66663942,44481640,30163921,94090109,74357852,6871053,23194618,66322959,26744917,34946859,74137926,92398073,43152977,18466635,83948335,92530431,48774913,86543538,70596786,40197395,30811010,12664567,71316369,86460488,14363867,99226875,26063929,90013093,77898274,36685023,79880247,88904910,90090964,47090124,37501808,29819940,97281847,75135448,13819358,44784505,70800879,69697787,13173644,65074535,16424199,25636669,32159704,40356877,25842078,60102965,17894977,84187166,95957797,58224549,176257,42307449,72274002,73021291,60581278,21533347,34044787,89419466,36933038,16097038,68000591,85117092,99549373,22108665,89637706,5111370,9603598,36812683,59109400,38510840,92998591,84406788,82339363,40984766,68316156,23569917,91831487,81172706,76170907,34667286,67513640,30463802,8115266,89217461,4798568,29029316,35512853,29834512,49598724,11581181,36845587,67227442,78549759,83368048,93270084,54263880,57163802,16971929,20681921,61271144,40686254,18001617,20140249,88698958,51588015,18663507,67451935,46540998,59981773,14947650,26397786,18504197,22200378,55669657,56424103,50702367,53666583,54232247,91990218,92803766,73786944,6221471,55143724,7502255,95251277,89046466,36780454,85023028,30218878,45996863,92604458,62115552,36135,86301513,77413012,42644903,33123618,89699445,19272365,2331773,90061527,17385531,59318837,80246713,17016156,23740167,64848072,53888755,15948937,38494874,10309525,14326617,54517921,72238278,48260151,13231279,64087743,33699435,84127901,66885828,85711894,44550764,61928316,33797252,45794415,21919959,44627776,82178706,9860195,4204661,81805959,92692283,61897582,4199704,97011160,98462867,89078848,45790169,1239555,39373729,9599614,22450468,71522968,65017137,4099191,16405341,56484900,37739481,11923835,20568363,45995325,45075471,27625735,56473732,8373289,93562257,98648327,92353856,49597667,6157724,57248122,86242799,74110882,84293052,15536795,59910624,19486173,91240048,40781449,32590267,51715482,42947632,20642888,20836893,749283,30569392,42073124,53842979,69641513,70727211,12571310,30090481,94539824,93053405,16099750,58208470,69255765,38645117,3235882,26998766,43322743,96709982,33061250,72358170,77377183,33628349,75052463,12416768,86798033,75777973,62490109,72495719,52060076,68110330,1569515,85571389,4091162,41092102,66428795,32699744,3880712,12303248,73168565,97940276,824872,92867155,64098930,15163258,65081429,82979980,61712234,58751351,73031054,55850790,73235980,82897371,68102437,6111563,59405277,97379790,67124282,86118021,12024238,45990383,24168411,32274392,35996293,81898046,67030811,11543098,68041839,23432750,60393039,9058407,29401781,37192445,42199455,22405120,26507214,47298834,65038678,71300104,4978543,7517032,22721500,69786756,21289531,5482538,29510992,44060493,97561745,33895336,60955663,38341669,49882705,51149804,71469330,86821229,61741594,84166196,3773993,8913721,54987042,17857111,93790285,30653863,4985896,44915235,99224392,96852321,54762643,2607799,84002370,24915585,99971982,51426311,79322415,6497830,79738755,17081350,26664538,28787861,89996536,95010552,93057697,48658605,82726008,61960400,17727650,27187213,52293995,63506281,16387575,87720882,20867149,4095116,37166608,37224844,69605283,57359924,44664587,65047700,44846932,7646095,89499542,91141395,16019925,36189527,41245325,83133790,62762857,56955985,7066775,33553959,65880522,22879907,87160386,5640302,95581843,93515664,81274566,75407347,64602895,44844121,76825057,24314885,41380093,28851716,83083131,86022504,91255408,27411561,7182642,8791066,37891451,11757872,44245960,10597197,45381876,70782102,36396314,90158785,9860968,41442762,47893286,26863229,45617087,9257405,45049308,63152504,74452589,23110625,10264691,31727379,64055960,62428472,99297164,44479073,26493119,61815223,6521313,91281584,37560164 +98371444,55960386,99333375,91990218,53842979,89214616,59109400,41092172,33201905,7011964,35192533,98462867,72495719,30395570,415901,6871053,94516935,10358899,73124510,6793819,24058273,50188404,15535065,72738685,42782652,77413012,51047803,77694700,10366309,66663942,60430369,74724075,57658654,94076128,23848565,22129328,15536795,15015906,47792865,78589145,44550764,23569917,29065964,6153406,29029316,35092039,68824981,77187825,30694952,84684495,56531125,73168565,48893685,95581843,61960400,62740044,32250045,68644627,55753905,26392416,63015256,47298834,92033260,17146629,43152977,92803766,51472275,18131876,508198,84002370,14326617,79136082,17068582,65074535,16097038,35996293,9886593,29994197,9188443,54868730,43376279,39847321,9603598,50567636,11581181,73392814,70367851,21993752,53648154,99297164,84293052,96193415,84840549,40152546,87710366,14220886,40984766,62693428,39801351,53084256,93933709,34698428,92692978,4668450,26063929,58749,98943869,45790169,17365188,67793644,13348726,16054533,97641116,35456853,38658347,63628376,20765474,26734892,86460488,96420429,48892201,5111370,38061439,66250369,23134715,5267545,36580610,16387575,67281495,33628349,70420215,112651,43501211,7646095,44245960,8634541,3509435,85242963,80316608,3183975,9623492,33249630,6521313,89046466,84187166,69136837,91831487,80251430,21673260,31727379,62490109,99524975,51464002,83150534,77272628,42967683,99658235,20568363,93270084,75229462,34698463,76671482,26139110,80014588,87598888,33336148,7423788,63506281,51149804,46851987,59177718,65017137,6808825,57803235,99021067,51590803,42307449,11923835,31161687,4064751,83368048,22997281,59981773,24591705,33237508,2717150,4508700,75543508,39986008,74441448,168541,92787493,72019362,37891451,69355476,72725103,17957593,81853704,60955663,17385531,37659250,72793444,77284619,96726697,95010552,27041967,93053405,44177011,7687278,29819940,49641577,57241521,91957544,83533741,98130363,22108665,54762643,90272749,73235980,40865610,18466635,64055960,1022677,14093520,44348328,14626618,36505482,15075176,79821897,4099191,13470059,40677414,99515901,86301513,30764367,62452034,23194618,97379790,24168411,30366150,60102965,96852321,89078848,76170907,47213183,4985896,68041839,18699206,55189057,77301523,89996536,34432810,8913721,44481640,71333116,75777973,26998766,8055981,61623915,21533347,39171895,3088684,669105,33797252,3487592,75407347,9860968,16445503,71657078,29466406,14045383,69579137,79657802,48360198,3233084,30569392,30787683,63967300,65047700,20642888,72777973,44889423,56484900,18783702,13819358,86798033,92530431,45848907,8733068,55850790,17016156,26664538,32161669,20885148,56515456,55470718,70541760,59371804,78549759,48774913,95395112,82532312,17539625,68816413,36753250,4434662,50668599,6986898,17976208,44842615,81172706,26776922,68204242,61859143,67513640,69255765,72614359,12664567,95957797,14349098,5073754,3880712,18663507,45237957,13173644,47090124,65715134,53666583,72373496,247198,88444207,45049308,69848388,38365584,52060076,39373729,30218878,73031054,37435892,43933006,7300047,47738185,92215320,54058788,8791066,22450468,57163802,76825057,569864,51507425,99965001,76330843,8807940,68702632,99917599,16380211,23740167,21001913,94595668,22766820,9575954,1197320,29188588,26397786,53802686,10649306,38256849,88130087,27665211,65454636,20867149,92398073,81274566,90158785,97561745,31491938,72274002,12024238,95726235,77377183,526217,79806380,4091162,7955293,32590267,70372191,99549373,40534591,54427233,34295794,75153252,55669657,36468541,80555751,29510992,7685448,91255408,27411561,15064655,72732205,64087743,4798568,44847298,7502255,72278539,60581278,5970607,62430984,53632373,78218845,22113133,61859581,16424199,31904591,19376156,19101477,47893286,54606384,82726008,55770687,81898046,76960303,19939935,36312813,83302115,49598724,19891772,56424103,49328608,45919976,6111563,83651211,66137019,16405341,68102437,31126490,13862149,48395186,65081429,42692881,30463802,91938191,33565483,25842078,39201414,17894977,12348118,47708850,36812683,74614639,92604458,93057697,61263068,67084351,73617245,56153345,54663246,37166608,70036438,93515664,96906410,44915235,50007421,26863229,22942635,81855445,47887470,55247455,2917920,10961421,17058722,51588015,90457870,4095116,67474219,36933038,60106217,20836893,50702367,34044787,89811711,21397057,13231279,81805959,9257405,14947650,68000591,5832946,5822202,26275734,86821229,91141395,33431960,33123618,74137926,51715482,16019925,39553046,45990383,4978543,24733232,78300864,98739783,28787861,4806458,64157906,82886381,38008118,36780454,61728685,86118021,59197747,93790285,18833224,45428665,61815223,38022834,66667729,26114953,66885828,56461322,40781854,22879907,47361209,42947632,32274392,44784505,8651647,84904436,64098930,4204661,67227442,37957788,75820087,18504197,99226875,68939068,19272365,4069912,96318094,30998561,13468268,62115552,76703609,43506672,3294781,5482538,321665,36930650,61741594,2891150,4787945,28734791,40027975,53393358,70596786,38494874,9175338,34236719,26292919,61982238,92692283,78909561,31733363,15148031,40686254,95290172,54014062,79322415,69605283,10309525,33304202,6525948,94539824,91240048,48088883,79880247,30139692,41380093,77312810,749283,71469330,65851721,65038678,26744917,12303248,89419466,91281584,83269727,92554120,45996863,3633375,90090964,62496012,71300104,99729357,3235882,63059024,70004753,44627776,49236559,54232247,54263880,42199455,97940276,49597667,85116755,45075471,59329511,11161731,89699445,21070110,33895336,55143724,9398733,1250437,36685023,60176618,90061527,34497327,40781449,73222868,61141874,61373987,67124282,52261574,59405277,91664334,94911072,82327024,49271185,36845587,18411915,87720882,56473732,62051033,51315460,10264691,37675718,1431742,58751351,1936762,5640302,89804152,11365791,72358170,99971982,64602895,94360702,91727510,62232211,2607799,52613508,82651278,89217461,44983451,85117092,65880522,46870723,76540481,61897582,34493392,8373289,54199160,90654836,18806856,64848072,14731700,21289531,9829782,44479073,11543098,61271144,19486173,92541302,1204161,62936963,59501487,69641513,84406788,30771409,57359924,33061250,88698958,1788101,28928175,77620120,65338021,8129978,57248122,45794415,79942022,75128745,79738755,7104732,52204879,43045786,77072625,9058407,66045587,66322959,73786944,37280276,42644903,8696647,81677380,20486294,57393458,37192445,6819644,70727211,97783876,35512853,86543538,54987042,23110625,824872,93562257,68128438,23432750,20122224,17386996,54517921,78602717,66271566,82979980,59318837,77787724,22405120,44664587,92998591,29635537,82339363,57961282,2331773,53547802,96709982,79191827,82052050,14363867,37560164,4515343,63372756,80246713,74357852,94711842,83133790,92353856,66832478,32159704,37183543,45667668,8099994,99690194,37620363,19457589,62803858,86242799,45407418,55602660,8115266,77898274,28550822,61928316,9599614,73109997,16684829,83210802,97281847,24953498,57240218,33935899,12571310,68316156,29959549,65271999,75135448,69786756,70800879,99125126,15902805,20002147,20645197,99604946,68694897,10597197,86001008,24826575,29834512,71316369,48260151,72357096,40197395,98648327,16971929,36139942,30090481,58208470,97057187,14723410,11757872,98948034,88047921,86022504,88904910,27185644,56955985,44846932,96735716,36135,34946859,1569515,874791,28796059,44473167,88653118,83083131,45995325,26507214,45306070,82897371,57020481,58180653,41481685,78884452,7517032,37739481,41245325,83948335,28851716,88251446,24619760,93566986,98653983,22721500,48673079,71920426,26102057,67030811,94090109,68110330,7919588,52293995,38009615,34667286,99861373,71965942,20681921,89637706,90004325,60393039,84166196,33699435,30891921,44844121,38341669,74110882,91802888,1239555,17037369,43322743,7066775,6505939,45617087,17764950,75986488,17727650,30811010,79922758,66116458,75052463,71083565,37501808,93359396,42073124,76315420,82427263,49882705,8128637,90310261,6497830,67031644,84349107,24314885,41092102,59614746,66319530,30653863,22435353,9860195,1375023,51426311,27325428,29932657,27187213,36189527,78785507,9259676,66903004,50806615,32151165,62552524,29516592,51466049,22200378,3773993,7182642,12416768,20140249,83747892,89499542,85711894,66428795,74743862,78561158,63152504,15163258,76434144,87160386,69697787,78766358,62762857,32058615,82178706,4119747,10453030,65275241,32699744,17081350,36396314,67451935,41442762,48675329,32426752,45381876,7229550,73021291,15948937,83789391,90013093,92071990,58224549,99224392,19898053,62428472,40356877,66611759,30543215,85023028,18001617,37224844,74452589,61712234,2208785,77300457,59910624,6221471,3233569,16567550,26493119,17857111,55615885,92867155,62357986,6038457,25636669,21919959,29401781,4199704,30163921,85571389,24915585,16595436,71522968,95251277,8729146,48658605,38510840,70782102,82779622,44060493,2204165,83155442,46540998,38645117,53888755,42237907,36808486,97011160,16099750,85695762,72238278,68875490,81774825,6157724,176257,27625735,43357947,33553959,84127901 +61373987,44348328,65851721,112651,3509435,31733363,29959549,25842078,8733068,98462867,8055981,98739783,62693428,15536795,22997281,10358899,30395570,97561745,66322959,53084256,49641577,27325428,3233084,33304202,12416768,81853704,96193415,526217,48774913,54058788,59109400,85023028,99549373,63059024,54427233,3183975,19939935,99965001,4508700,415901,91938191,4985896,79922758,95581843,27411561,83210802,43933006,2917920,8807940,6793819,35092039,72274002,35192533,36505482,23569917,12348118,12664567,72725103,13231279,56531125,23848565,22108665,17146629,19101477,69355476,27041967,55189057,4095116,92033260,60581278,33201905,36580610,55753905,62936963,59371804,18833224,73031054,80251430,40984766,66250369,45996863,9188443,4668450,9575954,30218878,3294781,5822202,65074535,91957544,321665,29029316,20765474,51472275,39553046,61263068,68204242,63628376,4806458,45237957,62740044,79806380,4199704,84187166,61859143,65038678,62496012,10366309,22721500,7011964,34698428,92398073,90090964,89637706,51466049,9886593,71920426,71333116,84002370,569864,99333375,94516935,44846932,82886381,24591705,13173644,14093520,73109997,38494874,66663942,26776922,17365188,24168411,93562257,50668599,83269727,2204165,77312810,93270084,3880712,10309525,38061439,75543508,78884452,13862149,30366150,26063929,82979980,19486173,17957593,26292919,59197747,6153406,26744917,8115266,17068582,50567636,30139692,1204161,91802888,10453030,9623492,45790169,508198,62232211,51047803,54762643,33431960,43322743,42073124,30543215,55602660,27665211,92604458,78561158,65047700,60430369,4978543,79880247,11161731,61982238,87598888,95290172,6111563,50188404,99524975,63967300,98943869,72738685,65715134,57240218,85116755,85242963,44550764,669105,27185644,68041839,26664538,14045383,60106217,70800879,16595436,37435892,14326617,39986008,6521313,38008118,36312813,81172706,68102437,34295794,74743862,72278539,17058722,43501211,41380093,65454636,77300457,14626618,15015906,62452034,1431742,51715482,18411915,69136837,71657078,55143724,33628349,7300047,68128438,34236719,20122224,68702632,67793644,73786944,89214616,12303248,2891150,7687278,68816413,60102965,40152546,24826575,4119747,68644627,37891451,80014588,69579137,48360198,48893685,7423788,72777973,37675718,69786756,33797252,21001913,91255408,3088684,66116458,42967683,3235882,44983451,8129978,77284619,44842615,22129328,61859581,33237508,39373729,54199160,6986898,76330843,70541760,77620120,67031644,30787683,84840549,99604946,64848072,39847321,59501487,86460488,4064751,11543098,24058273,96726697,45306070,14731700,32590267,6497830,72373496,30653863,24915585,90272749,70004753,37739481,34432810,75229462,7646095,45794415,61741594,74357852,91990218,55470718,1569515,65017137,96318094,20486294,58751351,65271999,73168565,54014062,68824981,10961421,16424199,20681921,72358170,92803766,68694897,68000591,18783702,44844121,33699435,30694952,95957797,78589145,99658235,84349107,21993752,83302115,5111370,9058407,75153252,96735716,78218845,92554120,15535065,45428665,37183543,34044787,44784505,47792865,13470059,80555751,36780454,92541302,37501808,2717150,874791,93933709,84904436,40781449,76170907,29834512,67474219,66832478,24733232,97011160,57658654,87710366,5970607,1022677,66428795,33123618,81677380,37957788,6525948,86543538,77694700,40197395,17976208,51588015,66137019,168541,52060076,92215320,76540481,30891921,36845587,36930650,16380211,89046466,49597667,20645197,95395112,6038457,56153345,47090124,4099191,23194618,13348726,52293995,67084351,4787945,43045786,89419466,39201414,99224392,28928175,35996293,55669657,824872,42947632,62803858,86821229,77187825,60176618,69605283,76671482,26392416,57393458,17081350,61960400,9860968,19376156,79821897,75128745,31126490,33336148,92692283,73235980,82726008,62490109,29819940,68316156,40677414,32159704,26139110,77072625,8651647,93053405,78785507,71083565,64087743,10649306,10597197,51464002,41481685,64157906,62051033,77787724,66319530,67513640,66667729,81898046,64055960,29466406,26998766,67281495,57961282,36753250,5482538,8128637,43506672,82327024,15075176,53842979,92998591,92692978,1250437,56424103,24314885,60955663,91664334,11923835,247198,62357986,54663246,42782652,99861373,40356877,18806856,68875490,78602717,84127901,93057697,47298834,97940276,84406788,9398733,10264691,90061527,85695762,90004325,8373289,23432750,48673079,69848388,38645117,78549759,68939068,44481640,36468541,7919588,89699445,53648154,47213183,8696647,38022834,45848907,95726235,86301513,93566986,94360702,65338021,59981773,1788101,44479073,82052050,34497327,89078848,82339363,71522968,57241521,78300864,16099750,20867149,18663507,98648327,18131876,55850790,67227442,53802686,44245960,8791066,14363867,15902805,20836893,13819358,65081429,69641513,20002147,31727379,6871053,54868730,49236559,32058615,32426752,45381876,9599614,16684829,86242799,67451935,42644903,4798568,46870723,29510992,1197320,29635537,98130363,59318837,78909561,9259676,57803235,16097038,79136082,35456853,75777973,83533741,32161669,84684495,89804152,30771409,22435353,32250045,73222868,71300104,42237907,72793444,29932657,33249630,58208470,47361209,31904591,53547802,97641116,71469330,61141874,55770687,62430984,47887470,66903004,29188588,38365584,28851716,85711894,15948937,72732205,79657802,9257405,88444207,29516592,25636669,99297164,89811711,26114953,41092172,29994197,9603598,99515901,16971929,1239555,26863229,99729357,46540998,96709982,1936762,99125126,33565483,6819644,74441448,70367851,22113133,70372191,47708850,83083131,57248122,40865610,73617245,80246713,59910624,44177011,33061250,61815223,82897371,71316369,51149804,70727211,88251446,73021291,99021067,67030811,38658347,90013093,41092102,55960386,68110330,38256849,74110882,59329511,28550822,16019925,30764367,58180653,51590803,86118021,53393358,45617087,63015256,77377183,9829782,90310261,34667286,33895336,66611759,30090481,24619760,82651278,15148031,90158785,8729146,44473167,83651211,89499542,36812683,53666583,70596786,27625735,48892201,17857111,51507425,26275734,20642888,72614359,53888755,7955293,9175338,17386996,7685448,7517032,91831487,81274566,87720882,26102057,64098930,6157724,56461322,1375023,39171895,28734791,17727650,7229550,88698958,89217461,32151165,92787493,75407347,82532312,34493392,3633375,72357096,63372756,72238278,91281584,44847298,52261574,37192445,86001008,30998561,96420429,61623915,94539824,84166196,29401781,77301523,40686254,82178706,39801351,81855445,19891772,24953498,66045587,99690194,7066775,54987042,77898274,83150534,49328608,82427263,21533347,72019362,46851987,28787861,47738185,75052463,76315420,83789391,36135,7502255,11581181,52204879,38009615,96906410,45995325,61928316,17764950,45667668,50806615,14349098,4515343,69255765,76825057,20140249,45407418,64602895,26734892,86798033,31161687,77413012,30811010,94076128,48675329,17539625,8913721,20885148,4434662,65275241,37620363,14220886,48088883,6808825,34946859,21289531,22200378,16387575,8634541,12571310,50007421,66885828,65880522,14723410,55247455,19272365,43152977,56515456,59177718,83948335,80316608,23110625,44627776,41245325,14947650,36189527,44889423,40781854,4204661,74452589,51426311,62552524,75820087,75135448,70036438,43376279,84293052,86022504,22766820,88130087,31491938,79191827,19898053,98371444,26493119,97379790,56473732,43357947,66271566,5640302,37560164,88653118,3773993,6221471,49882705,7104732,11365791,49271185,77272628,22942635,32274392,26507214,95251277,95010552,5073754,36685023,97783876,44060493,30569392,15064655,29065964,79738755,61271144,17385531,21673260,16567550,44915235,45990383,85117092,57020481,36933038,54232247,74724075,16054533,93359396,42199455,54606384,32699744,57359924,4069912,9860195,30463802,40534591,67124282,74614639,92353856,34698463,93515664,90457870,45919976,42307449,52613508,57163802,94911072,83368048,45075471,18699206,89996536,35512853,83155442,33935899,70782102,3233569,81805959,74137926,49598724,96852321,38510840,63506281,45049308,7182642,12024238,23134715,91240048,58749,42692881,26397786,75986488,176257,40027975,94090109,48395186,62762857,56955985,20568363,91141395,21397057,21070110,79322415,38341669,99971982,62115552,87160386,69697787,70420215,37280276,72495719,61728685,22405120,16445503,58224549,28796059,17894977,17037369,78766358,16405341,54263880,83747892,37659250,3487592,92071990,8099994,88047921,73392814,73124510,76434144,2607799,37166608,99917599,22450468,93790285,85571389,18504197,2208785,81774825,5267545,99226875,56484900,97057187,48658605,91727510,22879907,33553959,92867155,5832946,76960303,98948034,36396314,61712234,41442762,94595668,76703609,97281847,18001617,92530431,83133790,55615885,48260151,88904910,19457589,21919959,90654836,60393039,98653983,54517921,17016156,4091162,37224844,36808486,47893286,749283,15163258,2331773,59405277,23740167,61897582,53632373,30163921,27187213,50702367,18466635,79942022,13468268,62428472,51315460,71965942,82779622,63152504,94711842,59614746,36139942,6505939,44664587,11757872 +33304202,62452034,89214616,47361209,61960400,35192533,83210802,15535065,47090124,4668450,92398073,93933709,6793819,38494874,4099191,77413012,86460488,78884452,54987042,55143724,53842979,22450468,76825057,29834512,29188588,7423788,34497327,7646095,56515456,9398733,30543215,80316608,96193415,45407418,99297164,43376279,93515664,11365791,59614746,73235980,37620363,14326617,74137926,74614639,97783876,16567550,51472275,66045587,53084256,112651,54663246,95957797,4064751,63506281,68816413,69136837,8807940,78602717,8099994,17764950,15064655,26664538,37675718,36808486,57240218,34236719,59910624,78785507,62232211,66832478,8129978,99917599,38658347,85242963,81855445,75153252,70004753,61897582,55247455,38061439,63152504,63967300,59501487,35996293,67030811,99971982,247198,60430369,70372191,55770687,99515901,72274002,73392814,96318094,19376156,48892201,26275734,45237957,73031054,1375023,9175338,14731700,67793644,71469330,52060076,98371444,56955985,26863229,61623915,4119747,12024238,31904591,62430984,81898046,32699744,73168565,19939935,99658235,14349098,99604946,51464002,24915585,82427263,23432750,68939068,56424103,45990383,66428795,85116755,48360198,415901,20122224,70727211,87160386,38645117,51047803,23848565,36580610,14947650,17386996,63372756,80246713,86022504,84840549,26063929,84187166,10366309,19891772,44481640,62496012,57803235,18663507,23194618,99021067,68824981,66137019,61859581,7011964,78589145,47708850,20836893,3235882,9623492,79922758,30463802,16099750,57241521,19101477,39801351,36812683,15148031,45075471,50007421,55189057,7182642,43501211,54232247,5111370,98462867,40865610,26493119,16445503,65017137,99965001,57359924,48675329,26292919,36685023,22129328,26744917,9058407,50702367,44245960,95726235,45617087,72738685,42199455,23110625,83133790,74724075,62357986,78766358,57248122,3294781,33699435,17957593,34698463,58224549,43152977,40686254,526217,49328608,72725103,94090109,89046466,88047921,26102057,98130363,89637706,51507425,84684495,874791,52613508,40677414,48893685,91664334,30771409,13468268,36930650,36505482,13348726,67281495,569864,17068582,1431742,26507214,22108665,91727510,59371804,65851721,92541302,33249630,77284619,46851987,11581181,61741594,72614359,34295794,92071990,75135448,77072625,65081429,3773993,27325428,16054533,54199160,3088684,44889423,2917920,77694700,42967683,4515343,94076128,18001617,49236559,65038678,24591705,3880712,82327024,57163802,99226875,40984766,38365584,62740044,77300457,37659250,13470059,8128637,20568363,14220886,42073124,7685448,88698958,65715134,8696647,95581843,4199704,20642888,98653983,86301513,55960386,39373729,35092039,14626618,70596786,99524975,30998561,33237508,79657802,70800879,78218845,54868730,22942635,83948335,53632373,1204161,82726008,8115266,44473167,29819940,77787724,40781854,2208785,71333116,5267545,1022677,18131876,10358899,15163258,30218878,61263068,91240048,4508700,9259676,90457870,4787945,78549759,86001008,73124510,64157906,321665,70420215,32161669,29029316,87598888,34432810,79738755,3233084,27665211,82532312,73786944,68702632,61271144,82897371,59109400,42692881,18783702,83155442,38341669,16405341,73617245,98739783,92033260,95395112,18699206,1569515,45428665,54427233,6505939,37183543,27625735,42307449,90272749,64848072,99690194,54014062,65271999,71657078,2717150,45790169,55753905,13231279,48088883,37501808,97940276,48260151,82339363,15948937,72777973,44842615,63628376,16971929,6525948,75543508,75407347,7517032,6221471,82178706,23569917,89419466,88904910,50806615,32151165,39171895,33061250,21533347,70036438,72373496,69579137,8634541,176257,62490109,16019925,56461322,53648154,45667668,64087743,26392416,92998591,30366150,93790285,77620120,92692978,89996536,20765474,45995325,32159704,54762643,35456853,34698428,92554120,76170907,76703609,66885828,93359396,56473732,76434144,45919976,48658605,76540481,9860968,88653118,44847298,51466049,68644627,28796059,77312810,31161687,30139692,824872,81853704,37166608,29994197,18466635,98943869,18806856,44479073,65047700,27411561,49641577,44060493,31126490,31733363,92692283,5822202,24733232,32250045,74452589,20486294,96735716,79821897,61982238,44983451,17365188,30764367,11161731,3633375,99861373,77377183,90090964,63059024,6986898,44550764,66250369,2331773,85571389,12571310,50567636,93053405,57658654,24058273,89499542,6153406,53802686,66611759,33565483,38510840,95290172,28550822,84127901,22879907,34493392,10309525,65338021,72358170,30090481,91141395,43322743,56531125,44664587,17146629,52261574,52204879,16097038,45794415,41380093,6819644,58180653,84406788,71316369,51588015,44627776,12416768,5640302,59318837,38008118,21993752,94911072,34667286,11543098,16595436,65275241,72793444,68875490,71083565,46540998,9829782,68041839,8651647,37891451,7502255,20002147,3487592,26397786,26734892,33797252,30395570,22997281,7955293,60581278,57020481,42947632,67451935,33553959,37435892,81805959,22766820,73021291,99549373,80251430,47298834,5970607,62428472,32426752,37739481,92353856,75229462,54606384,6808825,36753250,92803766,97281847,1788101,79880247,47792865,33336148,31727379,72495719,9603598,68102437,75777973,44348328,79191827,86242799,32590267,28928175,1197320,3509435,7919588,99729357,42782652,54058788,6871053,41442762,61928316,40534591,73222868,28851716,44846932,95010552,64602895,4204661,94516935,14093520,26114953,69355476,80555751,34946859,72357096,91938191,21001913,49598724,37957788,89078848,60102965,40197395,19272365,16387575,33201905,43933006,66903004,69641513,28787861,6521313,33628349,61712234,50188404,30891921,7687278,40027975,26998766,52293995,45996863,20885148,98948034,40781449,26139110,56153345,4069912,53393358,36845587,10961421,68316156,28734791,10597197,86543538,61373987,83150534,2891150,61141874,22113133,7104732,8373289,29932657,94711842,15015906,84293052,60106217,96709982,61728685,57961282,68128438,4985896,508198,58749,29959549,68204242,87720882,37280276,51149804,59197747,36396314,63015256,66322959,81774825,7300047,82979980,46870723,92787493,55615885,24826575,96726697,79136082,48673079,77272628,84002370,77898274,64098930,90013093,2204165,48774913,9188443,6157724,82886381,91281584,36933038,11757872,17385531,36189527,4434662,97011160,38009615,83368048,81274566,65454636,30653863,16380211,83083131,36780454,27187213,45381876,72019362,71300104,1250437,40152546,94360702,61859143,47213183,669105,29635537,88444207,15075176,14045383,81677380,18504197,78561158,62552524,30811010,30163921,36135,30569392,13819358,94539824,66271566,66667729,44784505,48395186,99333375,32058615,51426311,55602660,73109997,12303248,41092102,86821229,49271185,62115552,53547802,5482538,23740167,29510992,24953498,43506672,86118021,68110330,96420429,13862149,22200378,97641116,62936963,44177011,25636669,65880522,39201414,85023028,4798568,78300864,1239555,66319530,83302115,58751351,91802888,30694952,49597667,19486173,9860195,20140249,17016156,82651278,22405120,82052050,17037369,49882705,98648327,91255408,72238278,17727650,20867149,18833224,97561745,57393458,72278539,71965942,9886593,11923835,24168411,39986008,91990218,36468541,36312813,83651211,76315420,88251446,33123618,32274392,93566986,31491938,69255765,168541,34044787,90654836,45848907,74110882,29065964,3233569,99125126,42237907,17081350,90310261,13173644,65074535,20645197,80014588,38256849,84904436,69848388,77301523,12348118,56484900,75052463,25842078,76671482,76960303,29466406,16684829,23134715,33935899,2607799,50668599,42644903,55669657,83269727,749283,17857111,93562257,90061527,37192445,68000591,90158785,55470718,4095116,81172706,71522968,39553046,12664567,21070110,72732205,19457589,4806458,75128745,91957544,14723410,59177718,47738185,10264691,62803858,54517921,45049308,59405277,75986488,40356877,6038457,96852321,69605283,96906410,85117092,17976208,44844121,8913721,33431960,67084351,41481685,43357947,62051033,97379790,77187825,68694897,10649306,51715482,74357852,45306070,93057697,76330843,8729146,60955663,41245325,79806380,15536795,18411915,94595668,91831487,89699445,6111563,16424199,21673260,62762857,58208470,92604458,51590803,35512853,47887470,92215320,7066775,47893286,64055960,67124282,66663942,37224844,9599614,89217461,5832946,30787683,8733068,33895336,75820087,43045786,39847321,4978543,3183975,83747892,53888755,87710366,71920426,83789391,9257405,70367851,26776922,92867155,60176618,61815223,59329511,9575954,70782102,4091162,10453030,21289531,55850790,95251277,70541760,78909561,99224392,17894977,86798033,67513640,92530431,19898053,67474219,8055981,74743862,7229550,93270084,90004325,1936762,14363867,29401781,85711894,89804152,89811711,54263880,6497830,79322415,79942022,20681921,66116458,21919959,53666583,62693428,37560164,22435353,38022834,69697787,44915235,27041967,24619760,17539625,82779622,69786756,97057187,5073754,83533741,51315460,24314885,27185644,36139942,67031644,8791066,41092172,59981773,15902805,84166196,74441448,17058722,67227442,21397057,60393039,29516592,84349107,22721500,88130087,85695762 +47738185,75543508,67124282,78909561,17068582,77413012,59177718,21673260,22942635,44784505,80014588,6153406,53666583,45667668,85116755,57803235,83533741,39847321,71657078,99297164,7919588,18699206,35092039,29029316,99125126,92692978,72373496,9886593,37739481,9603598,75777973,77284619,30090481,81898046,15536795,44889423,98371444,62496012,88047921,40781449,81853704,21070110,55247455,95395112,4064751,72274002,29834512,27665211,9257405,30787683,10453030,36505482,9623492,94090109,88130087,2717150,73124510,16099750,36933038,5073754,37501808,29635537,79136082,86001008,70036438,75986488,67084351,61373987,62936963,66116458,55753905,29065964,96735716,4119747,33797252,33123618,44177011,51590803,64848072,24058273,23569917,20885148,30163921,69641513,16971929,92554120,168541,58749,40686254,3233084,67227442,5970607,16019925,15163258,22721500,65074535,38645117,82779622,26063929,34236719,18411915,99226875,72777973,97561745,33628349,98462867,99524975,66322959,51047803,26392416,89214616,15535065,5832946,44060493,48260151,7687278,61271144,90013093,30139692,28796059,66667729,669105,7229550,28851716,5111370,33336148,30764367,79806380,41380093,99658235,48893685,55850790,96193415,86798033,73031054,36753250,71920426,77377183,45428665,33201905,94595668,29819940,74357852,44481640,8913721,65275241,34493392,73392814,45996863,77272628,51464002,56531125,14093520,53547802,87598888,41245325,14045383,45995325,99690194,55470718,80246713,8651647,38365584,36780454,68816413,36396314,16445503,34497327,31126490,86301513,36312813,50702367,56955985,82339363,83651211,96709982,96852321,74724075,11923835,16405341,91831487,3487592,6793819,14326617,29959549,59197747,18783702,60102965,6221471,18833224,8733068,30395570,71333116,61982238,76434144,66271566,74441448,21919959,93270084,11543098,34946859,64087743,86543538,45237957,70367851,89217461,20002147,76540481,73222868,17037369,47893286,4099191,45794415,62740044,33249630,79821897,17857111,83789391,68204242,44842615,38658347,28928175,38008118,62452034,34432810,40984766,1936762,17727650,64157906,63628376,45848907,6808825,17539625,36808486,83210802,76330843,62490109,60955663,9829782,68000591,6505939,51315460,72019362,63967300,6871053,62762857,93933709,40027975,22766820,43376279,14349098,3509435,92803766,48088883,91664334,90272749,61859581,74452589,30694952,44479073,48675329,40534591,65038678,73786944,78766358,33895336,45617087,2208785,98948034,53842979,9175338,26139110,57248122,65338021,63506281,36139942,11757872,61623915,17957593,24591705,49236559,29466406,92353856,35996293,66250369,63059024,77312810,73617245,70596786,3233569,62430984,99515901,19486173,17016156,62552524,46870723,2204165,8634541,40197395,33553959,30463802,16097038,65880522,66611759,43045786,4204661,9058407,68644627,66137019,53802686,16380211,57240218,88653118,75128745,17764950,67281495,8129978,84187166,10309525,22113133,4787945,30569392,13231279,35512853,98130363,40677414,55143724,70800879,50806615,52261574,86022504,62232211,42947632,88444207,67793644,112651,44983451,29994197,89078848,54517921,60430369,59109400,23848565,96906410,62428472,16595436,68102437,69255765,24619760,50188404,93562257,83368048,71469330,91727510,84002370,58208470,90457870,19891772,14220886,14626618,66832478,69579137,4508700,59371804,50567636,22108665,89699445,55615885,70004753,21993752,36685023,91240048,10597197,65715134,82979980,10264691,93566986,65271999,28550822,69786756,65851721,7104732,20681921,34295794,54014062,53632373,749283,98943869,29516592,3088684,22200378,99917599,97379790,9860195,99965001,2331773,53393358,55770687,60581278,57393458,12416768,18001617,59501487,43933006,1197320,39801351,61712234,49271185,14363867,41092172,92604458,8099994,57163802,90158785,98648327,508198,84904436,55189057,99971982,81677380,99333375,31491938,95726235,30998561,88251446,44915235,83083131,321665,51715482,6157724,81805959,31727379,10358899,52613508,52204879,80555751,30366150,57020481,26507214,64055960,78561158,7300047,3633375,47090124,30653863,23134715,68316156,35192533,97011160,26292919,50668599,7955293,34698428,81172706,77694700,36580610,45919976,4095116,90090964,6986898,20568363,10366309,15948937,24826575,15075176,65017137,44473167,52293995,91141395,91281584,75820087,1431742,15015906,26998766,88698958,26664538,39986008,54606384,9188443,66903004,76671482,11581181,79322415,79880247,99549373,55669657,79922758,77620120,20122224,37620363,26744917,49597667,7423788,40152546,36189527,3294781,16054533,71300104,24168411,37224844,89811711,39201414,89804152,30218878,55960386,40865610,7182642,40356877,18504197,19101477,14947650,37166608,59981773,38022834,19376156,26397786,87160386,42782652,74110882,13468268,45790169,68702632,46540998,34698463,16424199,89046466,78602717,92071990,66428795,61263068,59614746,20765474,32590267,79657802,71316369,61859143,62693428,95957797,90654836,13173644,73235980,86460488,49641577,91255408,85571389,92033260,1250437,20140249,526217,25842078,99021067,94516935,28734791,12664567,92215320,61141874,3773993,8807940,15148031,12024238,6525948,32699744,33565483,26114953,66663942,97940276,64602895,56484900,65047700,76170907,2917920,70420215,94360702,30891921,20642888,47213183,49328608,1788101,87710366,47792865,57961282,27325428,33935899,53648154,93790285,43501211,47361209,4434662,18466635,66885828,2607799,7646095,67513640,58751351,7011964,19898053,78300864,20486294,61897582,415901,83155442,46851987,24733232,98739783,84293052,54058788,72357096,38494874,93359396,68128438,75153252,70541760,17976208,32161669,62051033,52060076,72278539,73168565,61728685,49598724,65081429,69848388,54663246,78218845,13862149,74743862,58224549,3183975,76960303,95581843,93053405,41092102,90310261,38256849,569864,67030811,60176618,32058615,11365791,69697787,58180653,42967683,9860968,4806458,7685448,47298834,36135,37183543,17385531,51588015,48360198,30771409,72732205,92541302,59910624,82427263,15064655,85242963,61741594,50007421,21533347,38341669,17894977,81855445,18806856,60106217,29188588,72793444,74614639,96726697,76825057,19272365,54199160,88904910,247198,27041967,44844121,54987042,11161731,3235882,36468541,7517032,26776922,22405120,44847298,77187825,53888755,37675718,176257,43357947,22879907,56461322,80251430,1204161,42307449,54263880,72725103,45306070,47708850,99861373,86242799,19457589,39553046,84684495,8791066,83150534,38061439,85117092,68939068,4985896,4091162,62357986,8729146,6497830,3880712,81274566,32159704,97783876,18131876,32250045,78785507,32426752,2891150,99729357,42073124,54427233,89637706,13348726,44245960,72238278,5640302,39373729,87720882,85711894,29401781,824872,18663507,95290172,12348118,82651278,874791,61815223,17365188,83302115,84349107,95010552,59318837,75135448,56153345,64098930,74137926,99604946,12303248,27187213,95251277,53084256,42237907,26275734,66319530,48673079,82726008,57359924,13470059,43152977,71083565,79191827,37659250,35456853,86118021,37435892,42692881,23740167,68824981,62803858,8115266,7502255,92398073,19939935,54762643,1375023,61928316,82532312,26493119,71522968,31904591,13819358,78589145,57658654,47887470,90004325,45075471,9575954,42199455,68875490,17386996,51466049,57241521,63152504,8055981,4515343,1022677,75229462,16684829,44664587,80316608,39171895,77787724,48395186,37280276,5482538,96420429,68694897,89419466,67031644,8373289,16387575,68041839,51426311,37192445,44348328,34044787,85695762,94076128,27411561,8696647,6521313,69605283,79738755,5267545,33431960,97641116,22450468,67451935,1239555,84840549,55602660,69136837,83269727,92530431,41481685,72614359,85023028,15902805,17081350,36845587,91990218,70372191,51472275,24314885,30811010,72495719,97281847,84166196,17146629,10961421,76315420,48774913,22997281,4668450,94711842,65454636,54232247,82327024,54868730,34667286,37560164,48892201,29510992,83133790,66045587,93515664,31161687,42644903,61960400,9398733,71965942,75052463,82897371,72738685,4798568,8128637,78549759,32151165,70727211,69355476,20867149,45990383,96318094,92787493,12571310,45381876,99224392,48658605,28787861,27185644,56515456,6111563,63015256,51149804,45407418,73021291,37891451,43506672,41442762,77300457,16567550,36930650,22129328,33304202,79942022,75407347,82886381,4978543,21001913,44627776,24915585,62115552,49882705,33699435,23194618,45049308,23432750,97057187,4069912,24953498,84127901,26734892,56473732,92998591,68110330,37957788,17058722,43322743,56424103,29932657,25636669,92867155,82052050,36812683,22435353,92692283,98653983,93057697,60393039,91938191,44550764,51507425,10649306,7066775,94911072,44846932,33237508,82178706,59405277,90061527,76703609,91957544,72358170,77301523,33061250,20836893,77898274,81774825,9259676,38510840,26863229,9599614,63372756,91802888,30543215,21289531,5822202,40781854,77072625,78884452,1569515,14731700,67474219,6038457,59329511,89996536,84406788,73109997,4199704,26102057,27625735,83948335,32274392,38009615,94539824,89499542,86821229,21397057,31733363,14723410,6819644,20645197,70782102,23110625,83747892 +47090124,38494874,79922758,64087743,85711894,15064655,29834512,27665211,65338021,67281495,92554120,35192533,98648327,38008118,55602660,61271144,20122224,91831487,88251446,32699744,61141874,33565483,96193415,62452034,16099750,36930650,9623492,36685023,33304202,70004753,1204161,65851721,75543508,69641513,88047921,43322743,86001008,98371444,80316608,95957797,80014588,39801351,16595436,54199160,1569515,78549759,84187166,58208470,31904591,24591705,45995325,7104732,74743862,62430984,4064751,60955663,34698428,50806615,4099191,14731700,77272628,52293995,57803235,1239555,25842078,90457870,30463802,64848072,76960303,61982238,16971929,69355476,17016156,45617087,83651211,8696647,33553959,57248122,2208785,32161669,99861373,60102965,29959549,26063929,53842979,42644903,62357986,57240218,97783876,95395112,50702367,46851987,33628349,9398733,47708850,55143724,2917920,247198,59177718,26392416,99524975,43376279,15535065,46870723,1431742,6793819,18699206,71657078,34493392,78561158,36780454,42947632,68702632,66903004,29029316,89419466,82427263,60106217,23569917,70800879,36580610,33249630,67084351,41481685,30163921,8913721,30090481,26998766,40027975,66611759,526217,4515343,34698463,72777973,40984766,63372756,40677414,66832478,41442762,89637706,37891451,82052050,34497327,79738755,55470718,22200378,37620363,52613508,31126490,43501211,55247455,28550822,48892201,45996863,63967300,93790285,54987042,76671482,56473732,99965001,82651278,17068582,4985896,71333116,19891772,30139692,77300457,4069912,33123618,51149804,52204879,44479073,99297164,77787724,37192445,95010552,30891921,62496012,14363867,35092039,77620120,94911072,58180653,64602895,10597197,28851716,28787861,92033260,37280276,112651,57241521,68644627,65880522,92215320,10358899,18806856,24058273,97641116,89078848,4119747,90272749,52060076,79191827,30543215,66319530,51047803,24915585,73617245,65038678,18411915,30218878,321665,72725103,37183543,34295794,83210802,93562257,74724075,68204242,92398073,84406788,4787945,61623915,99549373,1788101,48360198,26114953,86821229,29188588,74357852,51464002,51426311,28796059,41245325,90654836,89214616,54606384,56484900,176257,79657802,44473167,44889423,8128637,40686254,76434144,38658347,11365791,86242799,70596786,24826575,55770687,12571310,65074535,168541,54663246,22435353,7517032,21533347,4668450,10309525,3773993,66271566,62428472,51588015,10453030,99021067,72019362,81898046,68939068,76330843,38061439,77413012,2717150,81805959,53666583,98462867,42199455,44784505,87160386,19939935,30694952,75777973,3880712,8651647,23134715,14093520,40534591,75153252,55753905,93053405,44550764,6157724,64098930,43933006,50567636,47213183,28928175,9860968,6111563,43506672,96709982,37224844,74614639,6153406,16380211,62740044,76825057,69786756,75986488,50007421,56531125,59318837,80251430,66667729,8129978,66137019,73031054,68128438,14045383,19486173,47361209,61859143,94090109,79880247,17727650,62051033,77377183,669105,61373987,79821897,1375023,77284619,51590803,75229462,44983451,12416768,30569392,70036438,73392814,29819940,42782652,29932657,32159704,10366309,14947650,53648154,26776922,68694897,62693428,60581278,81677380,45428665,49882705,67793644,96906410,87598888,37957788,53888755,57393458,32250045,67227442,24733232,26507214,79806380,23432750,91938191,30771409,99658235,84840549,78589145,55669657,70367851,73168565,94360702,98948034,70372191,98130363,91802888,83083131,17976208,94595668,73786944,61263068,57359924,39373729,99690194,22766820,38365584,45790169,3509435,13348726,78218845,62936963,69136837,3233084,66045587,50188404,15948937,63152504,82726008,7066775,55960386,82979980,75820087,7919588,99125126,20681921,96726697,75135448,74110882,33797252,47298834,35456853,91727510,83302115,61741594,55615885,27185644,92692978,20885148,5970607,6525948,66322959,19101477,29065964,51466049,89499542,86460488,1197320,16019925,41380093,21673260,9257405,26139110,20002147,9886593,27325428,26664538,22997281,88653118,36808486,71083565,11923835,6221471,48260151,34236719,18504197,6808825,81855445,83948335,21070110,72614359,17081350,9599614,45237957,72373496,81274566,42307449,3235882,54427233,62762857,68816413,99515901,26863229,22942635,82779622,68102437,20645197,65017137,7687278,7229550,38341669,48774913,16097038,26292919,12348118,36505482,97561745,65715134,40781449,15075176,40152546,824872,569864,15163258,93566986,98739783,33895336,16424199,12664567,89996536,37659250,49328608,73222868,71300104,51472275,77312810,13231279,38645117,40197395,62115552,7182642,63628376,79942022,9259676,91990218,28734791,99604946,17764950,89804152,81172706,3294781,78766358,25636669,88698958,63059024,22405120,54762643,85116755,66428795,68824981,91141395,48658605,44177011,49597667,61712234,91664334,65271999,14326617,66116458,62490109,61897582,7502255,6038457,48088883,93359396,23848565,30395570,48673079,83789391,64055960,37560164,93933709,47893286,92692283,83368048,29994197,91957544,93515664,44348328,40356877,49641577,17365188,34946859,83155442,8055981,5482538,94711842,6986898,88444207,13173644,19376156,20642888,80246713,44060493,64157906,57658654,4091162,97940276,89046466,4806458,7955293,60430369,39553046,59109400,44846932,29516592,79136082,24314885,3233569,53632373,14349098,16054533,44915235,84349107,36135,15015906,65275241,52261574,20140249,13470059,62803858,59910624,20765474,8115266,74452589,34667286,15536795,69848388,53802686,84293052,43357947,96318094,56515456,9860195,22450468,34432810,77898274,33699435,53084256,16445503,37675718,93270084,42073124,87710366,87720882,29635537,18663507,45075471,44844121,55189057,85571389,66885828,10649306,19457589,67513640,37166608,99226875,54868730,70541760,51315460,29510992,67030811,86543538,58751351,85242963,32058615,99917599,80555751,47738185,10961421,4095116,54517921,94516935,7685448,30366150,4204661,37435892,749283,18833224,68875490,92530431,23740167,53393358,21919959,82886381,5111370,61815223,29401781,83533741,8373289,33431960,95290172,38022834,78785507,17385531,97281847,99971982,9188443,22879907,4434662,45049308,31733363,8729146,59371804,36312813,39201414,2331773,92803766,3633375,15902805,27187213,84002370,44842615,9058407,24953498,90013093,30653863,45848907,18001617,99224392,24619760,78300864,65047700,68000591,62552524,45919976,54014062,21993752,7646095,26275734,58224549,45306070,72793444,45407418,83269727,1022677,44245960,61960400,73124510,67474219,91281584,93057697,45990383,89811711,17386996,92353856,84684495,71316369,33336148,26493119,90090964,415901,56461322,11757872,70727211,70782102,58749,59614746,37739481,5822202,8807940,86022504,91240048,18131876,26744917,12024238,97011160,43045786,48893685,7011964,32426752,30764367,95251277,45381876,39847321,47792865,6505939,96735716,90158785,30811010,83133790,85117092,14626618,88904910,92071990,54232247,77187825,35996293,20486294,7423788,38009615,44664587,5640302,4508700,90310261,76540481,22113133,32274392,48675329,3487592,1250437,38256849,59501487,8634541,9175338,16567550,31161687,63506281,49236559,72274002,20836893,4199704,26734892,82339363,37501808,45667668,36139942,2607799,75052463,36753250,18783702,17037369,48395186,36812683,44481640,98943869,95726235,15148031,61728685,874791,29466406,68110330,32590267,49598724,21289531,92541302,51715482,60176618,74441448,56153345,42237907,92787493,81774825,92867155,11543098,61859581,69579137,33237508,10264691,70420215,42967683,78909561,27625735,50668599,9603598,57163802,67451935,92604458,66663942,23194618,82532312,71522968,19272365,56955985,16684829,59197747,9829782,17957593,72495719,13819358,1936762,6819644,26397786,26102057,19898053,36933038,30998561,69605283,63015256,9575954,18466635,17857111,2204165,20568363,6521313,31727379,89217461,57020481,94539824,68316156,77301523,7300047,66250369,39171895,74137926,49271185,17058722,69255765,97379790,86301513,59981773,5267545,41092172,41092102,47887470,76703609,27041967,90004325,73235980,81853704,92998591,6871053,62232211,99333375,4798568,89699445,16405341,24168411,33061250,8099994,96852321,57961282,21001913,39986008,94076128,65454636,83747892,23110625,3088684,75407347,53547802,22108665,14723410,98653983,67031644,2891150,54058788,71965942,59329511,72357096,69697787,17146629,51507425,5073754,3183975,36845587,45794415,13468268,88130087,33935899,20867149,42692881,40865610,73021291,22721500,84127901,77694700,99729357,17539625,77072625,13862149,27411561,22129328,38510840,55850790,34044787,72238278,32151165,79322415,82897371,35512853,96420429,82178706,44627776,36468541,21397057,46540998,85023028,30787683,60393039,4978543,16387575,56424103,14220886,75128745,65081429,85695762,68041839,72358170,8733068,71469330,40781854,86118021,11161731,78884452,78602717,72278539,76170907,72732205,44847298,43152977,91255408,6497830,8791066,95581843,71920426,84166196,59405277,12303248,5832946,11581181,33201905,83150534,67124282,508198,76315420,82327024,84904436,36396314,72738685,54263880,73109997,36189527,97057187,86798033,90061527,61928316,31491938,17894977 +73168565,68816413,10358899,67793644,63967300,16097038,3183975,40865610,47792865,53084256,49598724,5111370,4434662,17058722,26102057,70596786,20765474,35192533,31904591,82327024,72495719,4508700,76671482,70541760,15015906,79806380,54199160,75543508,37620363,77312810,71657078,18131876,8129978,6986898,415901,24168411,96726697,79136082,33628349,14731700,89637706,30395570,26392416,50188404,96193415,30891921,37957788,92398073,28928175,19101477,1197320,56461322,39201414,9886593,19272365,6793819,60106217,59371804,38658347,61141874,8913721,65715134,64055960,59981773,69786756,68824981,24058273,89214616,8128637,112651,27625735,8115266,2717150,98739783,19376156,99524975,93057697,43357947,72777973,77072625,8807940,21533347,8696647,26063929,81677380,44842615,14349098,78602717,56531125,55960386,82339363,62936963,55247455,50567636,38061439,9398733,17081350,29834512,68644627,39553046,53802686,23848565,25842078,45996863,98943869,7517032,16405341,37659250,3509435,40152546,42644903,98462867,44846932,77300457,20486294,24619760,72357096,35996293,10366309,51047803,91802888,97641116,17764950,77787724,84127901,69605283,33304202,93515664,22435353,10597197,15064655,19486173,4119747,76170907,15535065,7687278,39171895,7646095,61263068,92692978,84349107,13468268,21001913,42782652,30463802,8651647,99917599,97561745,84904436,40197395,52060076,6505939,9623492,68694897,51472275,43506672,95957797,53632373,94076128,64087743,68204242,83789391,84684495,89078848,92071990,36468541,5970607,16099750,53547802,26507214,46870723,9575954,82052050,66045587,43501211,22450468,49641577,50668599,95581843,77272628,20002147,49271185,4069912,72373496,92604458,37675718,66903004,83533741,4985896,51315460,49882705,61373987,72732205,4515343,81898046,22997281,14626618,11581181,57241521,31733363,37891451,14326617,35512853,55470718,508198,55143724,78766358,42967683,69355476,65851721,67451935,93053405,74441448,17365188,11757872,58180653,81855445,70800879,80555751,34493392,1569515,68000591,46851987,92787493,47298834,31727379,78218845,68875490,62232211,74452589,71469330,81274566,88653118,57020481,48893685,77284619,48774913,38022834,45407418,57240218,89419466,55189057,6497830,66250369,31491938,78589145,66885828,4668450,24591705,59109400,44473167,36930650,65081429,65017137,63372756,19457589,1204161,62051033,60430369,10309525,526217,26292919,43376279,2204165,62693428,45237957,91664334,95395112,62496012,13862149,91990218,56424103,82726008,75153252,30998561,32590267,81853704,33201905,76960303,89046466,32159704,77187825,49236559,29510992,99658235,44983451,19939935,27041967,45381876,62430984,31126490,99021067,61271144,48892201,44847298,4798568,86118021,17068582,99515901,69697787,22942635,23194618,56955985,57248122,70004753,14093520,26998766,29819940,35092039,52261574,87160386,55753905,41481685,44889423,59177718,85695762,29466406,21673260,4204661,16019925,81805959,30653863,42692881,73617245,30090481,17146629,94595668,3294781,37183543,53393358,73124510,669105,45995325,66832478,60102965,3233084,67281495,34432810,30811010,72238278,24733232,50007421,7423788,23432750,45794415,67030811,30366150,54427233,56515456,78549759,7919588,79657802,97940276,53666583,55602660,36753250,62452034,51466049,93933709,23110625,57658654,40781854,34044787,749283,17037369,33249630,66611759,16595436,37166608,73786944,67227442,83302115,39801351,12416768,94090109,91938191,38494874,58749,77413012,70782102,66271566,82178706,5640302,68110330,89996536,4978543,38008118,6221471,36812683,71333116,99690194,17016156,2917920,33237508,15948937,99297164,16380211,24953498,88444207,83747892,22108665,10649306,68102437,52293995,13231279,79821897,26863229,13173644,87710366,36933038,61982238,32250045,66428795,61859581,43045786,79942022,85116755,62803858,27325428,51507425,82897371,1250437,2208785,89217461,39373729,84840549,3088684,44479073,54987042,62552524,44664587,14947650,9058407,40677414,30163921,12348118,99333375,59501487,59614746,168541,33797252,45990383,7685448,17539625,56473732,72278539,65454636,29959549,6157724,77694700,73392814,57803235,3487592,16445503,6153406,49597667,7011964,83210802,87598888,56153345,48088883,38645117,7182642,12664567,27411561,94516935,34698463,65047700,30764367,33061250,34698428,30787683,40781449,54058788,42307449,62490109,85711894,74357852,68316156,86460488,22129328,94711842,66322959,18466635,94911072,40027975,90004325,71920426,64848072,43322743,65038678,68939068,8634541,73031054,79738755,62357986,86543538,38256849,92692283,84406788,95726235,1431742,47361209,51715482,49328608,874791,75128745,45790169,54014062,41380093,4064751,53842979,92033260,21289531,57393458,9257405,7229550,60955663,98648327,59197747,45049308,74110882,54762643,16387575,58751351,26734892,70036438,87720882,82532312,89699445,63059024,78909561,37192445,99965001,35456853,71300104,99861373,18663507,64157906,33935899,5482538,98130363,27665211,90310261,91957544,18806856,82779622,4095116,64098930,61728685,76434144,98371444,84187166,45848907,8373289,97011160,86301513,83651211,9188443,70372191,1375023,80251430,18783702,88047921,92803766,74743862,75229462,54663246,20645197,58208470,77377183,62740044,12303248,63628376,8729146,77301523,84002370,26139110,43933006,97783876,50806615,29188588,95290172,8733068,66663942,24915585,15902805,3233569,30694952,569864,23569917,44177011,2607799,67031644,33895336,36135,76540481,9860195,9603598,54606384,28734791,85023028,48395186,42073124,71316369,51149804,86242799,25636669,21993752,16684829,14045383,59910624,69579137,16567550,57163802,20122224,72738685,17727650,78300864,33123618,20885148,80316608,80014588,34295794,45667668,81172706,18504197,13348726,69136837,7955293,62115552,47090124,73235980,44784505,79922758,47738185,44348328,92541302,37560164,94360702,22766820,74137926,86022504,70420215,37224844,28851716,92215320,70367851,85242963,20642888,61815223,5267545,68041839,61960400,61623915,36808486,33553959,78884452,30569392,44550764,92867155,88698958,63015256,321665,86798033,29029316,59405277,48675329,83083131,11161731,10961421,247198,40686254,16424199,19891772,68702632,26114953,47213183,88251446,8099994,36396314,4787945,22405120,76703609,5822202,89499542,30218878,51426311,6808825,90654836,6521313,32161669,26275734,65338021,71083565,98653983,79880247,72019362,88904910,4199704,99729357,21070110,44844121,90457870,67084351,22113133,1239555,40356877,99224392,86001008,17976208,79322415,16971929,16054533,12571310,91141395,70727211,9175338,33565483,32274392,89804152,18411915,15075176,6525948,59318837,92353856,55770687,30771409,91255408,42237907,53648154,45919976,30543215,96318094,91727510,14220886,26664538,23134715,76330843,37501808,11923835,83948335,48360198,78785507,83368048,26397786,66137019,39986008,32058615,31161687,6038457,41092102,83150534,82886381,37739481,91831487,72725103,176257,4099191,75986488,44627776,90272749,36312813,96420429,75052463,44245960,55615885,3773993,82979980,24314885,99549373,99125126,96906410,17857111,6819644,824872,82427263,36189527,17894977,44481640,66667729,74614639,44060493,60581278,29065964,77898274,93270084,21919959,41245325,4806458,91281584,68128438,63506281,90061527,59329511,6111563,5073754,40534591,2891150,41092172,15536795,45306070,90090964,84293052,6871053,69848388,32426752,3235882,64602895,47708850,36685023,29994197,18833224,77620120,86821229,53888755,48260151,10264691,34497327,69255765,61741594,63152504,61928316,81774825,74724075,22879907,39847321,52613508,95010552,15163258,34667286,37280276,36580610,9259676,92998591,75777973,7104732,1788101,27185644,30139692,92554120,72793444,48658605,21397057,91240048,51588015,44915235,26776922,45075471,13470059,28787861,48673079,55850790,66319530,65275241,37435892,33699435,95251277,92530431,76825057,85117092,20140249,93359396,36845587,61897582,41442762,96709982,42199455,2331773,93566986,50702367,67474219,62762857,54868730,62428472,28550822,26744917,73222868,99604946,56484900,61859143,9599614,54263880,72614359,1022677,20836893,24826575,67513640,38365584,93562257,79191827,54517921,66116458,38341669,38510840,33431960,45428665,7300047,65271999,20681921,3633375,36780454,17385531,96735716,23740167,11543098,89811711,83155442,1936762,78561158,67124282,98948034,57359924,14723410,17386996,9829782,60176618,20568363,51590803,75407347,32699744,88130087,18001617,65880522,10453030,51464002,18699206,72358170,97281847,97379790,80246713,94539824,75820087,8791066,75135448,22200378,7502255,29932657,83133790,36505482,34236719,69641513,4091162,3880712,97057187,93790285,55669657,20867149,29401781,34946859,17957593,43152977,73021291,40984766,47893286,85571389,57961282,58224549,8055981,27187213,38009615,32151165,22721500,19898053,72274002,12024238,5832946,71522968,83269727,42947632,61712234,46540998,15148031,11365791,96852321,99226875,7066775,47887470,26493119,28796059,9860968,90158785,73109997,33336148,82651278,52204879,29635537,76315420,71965942,99971982,45617087,65074535,29516592,90013093,54232247,14363867,60393039,84166196,36139942,13819358 +16097038,73392814,14947650,7011964,7300047,99549373,43152977,90457870,32699744,95957797,45617087,89214616,91802888,95290172,68644627,22450468,44550764,88698958,65271999,96726697,96193415,44889423,89996536,74614639,4806458,45407418,67793644,63372756,37183543,21289531,86242799,66667729,48893685,4069912,14363867,55247455,89078848,51472275,44664587,61815223,62430984,57248122,73617245,89419466,29834512,70004753,51464002,17957593,31126490,51047803,569864,99226875,14093520,57241521,16019925,13819358,1250437,59177718,44983451,51588015,62452034,2331773,36780454,8807940,92554120,37166608,85242963,74724075,69255765,82178706,45790169,98130363,15015906,99965001,14349098,24058273,50702367,33237508,22129328,14326617,98371444,91281584,72725103,749283,54517921,70036438,76671482,33797252,97783876,21001913,45381876,22766820,33249630,112651,73235980,83302115,34667286,29994197,10358899,73222868,97379790,77072625,415901,83747892,18504197,3487592,93566986,72793444,83789391,4064751,91990218,37891451,22942635,15064655,18833224,38658347,36812683,72777973,26139110,9257405,16684829,80555751,74137926,47361209,58749,5111370,52204879,20140249,51426311,29959549,3183975,31904591,4798568,9623492,59318837,44842615,20568363,68102437,90272749,62936963,64098930,86001008,61982238,17068582,41481685,61623915,43322743,33628349,60102965,92692283,51466049,85571389,32426752,43045786,7955293,6793819,59405277,48260151,8099994,34493392,99224392,1431742,65454636,36580610,60430369,27665211,43376279,45990383,47708850,23134715,59981773,16445503,66885828,12348118,22721500,83269727,16099750,63967300,40197395,54427233,62496012,60955663,82886381,8913721,83368048,90061527,50188404,59614746,29029316,247198,49641577,26776922,85116755,32161669,26998766,38510840,21070110,97641116,65338021,95395112,36933038,68939068,53842979,7182642,17016156,5640302,22113133,4099191,88047921,58208470,17365188,10597197,2717150,78218845,508198,13468268,17764950,15163258,91831487,7646095,20642888,48360198,54762643,6153406,14220886,46851987,60581278,30998561,23110625,80316608,70596786,321665,72738685,99515901,76960303,55850790,82532312,88904910,76540481,56473732,73031054,68694897,99729357,29188588,39553046,39986008,6986898,1204161,3233084,49328608,57240218,78909561,35192533,84684495,78766358,10309525,44627776,26292919,67451935,51315460,62740044,94595668,59501487,77272628,54663246,20122224,15075176,78589145,35996293,67281495,6497830,53632373,98948034,33699435,96735716,51590803,34698428,29819940,55960386,72278539,71657078,29466406,69697787,84187166,64087743,93515664,58751351,2891150,83651211,90158785,31161687,92692978,16405341,3233569,66045587,47893286,93270084,14626618,18466635,55602660,77694700,42073124,65047700,18806856,4668450,98739783,9175338,1197320,72357096,52060076,13231279,93053405,24314885,55189057,59197747,37620363,27041967,84293052,17386996,92353856,45995325,526217,25842078,44479073,33304202,45919976,82779622,9058407,61859581,35456853,90090964,88653118,26275734,44844121,71083565,91727510,40781449,63015256,43357947,57961282,40027975,59371804,54987042,89046466,92033260,82427263,72274002,98462867,9188443,87710366,27625735,16380211,6505939,5482538,19939935,89804152,39171895,41442762,9599614,55669657,82979980,94539824,5822202,4985896,5970607,79821897,40534591,77312810,57020481,42237907,12303248,44245960,67227442,39847321,92530431,89217461,18663507,66250369,23432750,98943869,56531125,82339363,54014062,38645117,26114953,18699206,45237957,99524975,92604458,35512853,86118021,38009615,83150534,82052050,10366309,7502255,84002370,61263068,14045383,29065964,57163802,4508700,92803766,63059024,14723410,47792865,92398073,22200378,36930650,39201414,62232211,81172706,62762857,4787945,40984766,66611759,49597667,72373496,90013093,33895336,38061439,30764367,99917599,78549759,10453030,41092102,77413012,14731700,41380093,36135,94090109,26392416,30569392,73786944,61712234,18131876,22108665,11161731,99690194,30653863,30463802,40781854,76330843,42307449,3509435,91664334,45306070,28796059,80014588,29635537,99658235,66319530,31733363,51507425,73168565,45428665,10649306,20486294,97561745,72019362,36753250,68204242,40677414,51715482,9259676,6111563,71333116,7423788,56153345,61960400,68816413,64848072,16595436,84406788,6808825,52261574,26063929,72614359,84349107,61271144,27187213,64055960,12416768,30891921,47090124,36505482,13862149,55470718,93562257,63506281,96709982,99021067,81677380,20002147,874791,87720882,669105,92787493,44348328,15535065,69136837,53084256,98648327,39801351,68824981,71469330,12024238,29510992,29516592,15148031,11757872,77187825,1375023,68041839,92998591,50806615,30771409,4091162,68000591,53666583,45794415,66832478,74452589,61728685,36189527,23194618,74357852,77787724,63628376,2208785,48675329,56515456,19891772,11581181,4515343,78561158,43501211,8651647,61741594,34044787,85711894,44847298,4434662,91938191,55753905,3880712,62803858,81805959,16971929,40686254,66137019,83210802,24168411,40152546,17894977,20645197,78785507,65275241,86543538,61859143,67030811,2917920,83948335,28787861,72732205,99297164,73124510,75543508,81855445,81898046,78884452,28550822,71300104,82897371,75777973,30218878,25636669,32590267,24619760,37435892,91141395,18001617,75820087,26493119,64602895,32274392,48088883,3773993,5267545,96420429,89811711,8373289,46540998,42692881,36808486,12571310,95726235,79136082,70420215,84840549,75229462,94076128,42644903,55143724,97940276,92215320,8129978,27411561,88251446,34295794,16054533,42967683,7919588,62115552,38008118,24591705,7517032,45667668,44177011,81774825,77620120,82726008,79942022,42199455,68110330,30366150,97281847,49271185,1239555,47887470,45848907,57658654,4095116,8115266,49598724,52613508,77300457,65081429,30395570,84127901,66663942,40356877,82651278,28851716,30543215,30090481,53648154,62051033,8791066,8733068,17385531,38022834,62490109,94911072,70727211,10264691,83155442,9603598,18783702,81274566,85117092,43506672,19457589,48658605,75153252,12664567,85695762,37560164,13470059,17539625,69605283,30787683,83533741,55770687,36685023,41245325,54606384,93933709,66903004,57803235,20765474,76825057,58180653,77898274,78602717,34236719,44473167,7685448,22879907,26744917,8696647,6525948,38365584,26507214,3294781,31491938,77377183,72495719,90654836,95010552,33553959,37280276,19486173,74743862,59109400,98653983,22997281,76170907,79322415,38341669,86798033,26863229,45075471,7229550,63152504,28928175,65880522,54058788,47213183,21993752,32159704,9575954,60176618,76703609,83133790,70367851,86022504,54868730,10961421,13173644,11923835,96852321,22405120,56484900,17727650,69641513,88444207,41092172,91957544,50567636,72238278,37224844,11543098,19376156,99333375,56461322,59910624,74110882,9398733,33336148,65715134,70541760,68875490,53802686,27325428,94360702,79880247,48892201,2607799,47298834,79191827,34497327,29932657,51149804,26734892,168541,35092039,93359396,79922758,99971982,42947632,8634541,62693428,68128438,69786756,18411915,23740167,50007421,1569515,32250045,33565483,6157724,48395186,176257,6038457,42782652,56424103,44784505,99861373,82327024,1022677,79657802,13348726,72358170,31727379,69355476,30694952,6819644,67474219,44481640,24953498,87160386,34698463,23569917,94516935,15902805,36312813,97057187,75135448,75986488,70800879,80246713,96906410,75052463,36468541,53547802,86460488,69579137,66428795,66322959,58224549,40865610,39373729,71522968,99125126,21397057,84904436,15536795,38494874,76434144,26664538,44846932,87598888,2204165,21673260,5073754,54232247,90310261,26397786,57393458,7104732,54263880,6221471,89499542,45996863,91240048,9860968,62552524,8729146,77284619,43933006,66271566,79806380,17976208,19272365,65074535,24826575,78300864,36845587,9886593,17146629,33935899,37659250,46870723,29401781,19101477,68316156,65851721,67084351,90004325,11365791,50668599,49236559,3088684,89637706,85023028,37675718,70372191,4119747,65017137,36139942,47738185,33123618,4204661,20681921,24733232,88130087,33201905,57359924,17058722,3633375,23848565,37957788,89699445,68702632,30811010,37501808,20885148,38256849,93057697,20867149,94711842,62428472,86821229,1788101,66116458,44915235,71920426,30163921,48774913,62357986,71316369,61141874,34946859,70782102,33431960,67124282,1936762,15948937,77301523,86301513,36396314,24915585,92867155,71965942,99604946,91255408,61897582,95251277,76315420,8128637,97011160,53393358,55615885,17037369,96318094,56955985,27185644,59329511,54199160,19898053,6871053,9860195,93790285,3235882,60393039,7066775,49882705,61928316,32058615,9829782,64157906,81853704,17081350,67513640,80251430,21919959,92071990,824872,22435353,6521313,79738755,44060493,83083131,16424199,48673079,8055981,65038678,74441448,73021291,5832946,67031644,52293995,16567550,60106217,92541302,20836893,32151165,33061250,37739481,34432810,45049308,4199704,21533347,84166196,26102057,17857111,69848388,61373987,16387575,4978543,95581843,75407347,28734791,7687278,37192445,75128745,53888755,30139692,73109997 +64087743,33237508,73031054,77312810,26734892,20122224,40197395,27665211,6871053,65074535,87160386,57020481,31904591,29834512,80555751,6505939,71657078,51588015,80316608,67793644,35092039,73168565,18699206,17957593,16445503,94360702,4787945,89046466,55669657,37659250,69136837,78549759,98130363,14326617,14045383,98371444,14349098,36812683,47090124,7011964,99658235,22108665,40152546,16405341,96193415,70596786,97641116,12348118,51047803,96709982,84349107,91802888,31161687,69848388,569864,87598888,97783876,42692881,41481685,44844121,74110882,11581181,49328608,53648154,51507425,45790169,99021067,47361209,17016156,72732205,85242963,39171895,43376279,55143724,65454636,14220886,16019925,75153252,81805959,77072625,66611759,247198,32699744,29994197,43506672,55753905,83150534,73124510,112651,60581278,30463802,17058722,70782102,8696647,68816413,29959549,12571310,92692978,47298834,66903004,67281495,86301513,12416768,28734791,77284619,89996536,21533347,37183543,83651211,21289531,49641577,83155442,66137019,24058273,69255765,74743862,874791,87720882,26275734,95251277,46851987,8128637,79657802,61741594,68102437,96726697,36780454,82327024,99333375,9175338,17068582,93933709,93790285,56461322,321665,59614746,54199160,14723410,13231279,82339363,59501487,22129328,62051033,94911072,81855445,53393358,52204879,68702632,73786944,92998591,10358899,40356877,63967300,40781449,38061439,61859581,80014588,35192533,76170907,43357947,43501211,81274566,73392814,56473732,88047921,89214616,9603598,54987042,43045786,38494874,60430369,6793819,65038678,76540481,40686254,79806380,16380211,70800879,4095116,22997281,77187825,3773993,44481640,79136082,19939935,56515456,26998766,20140249,508198,57241521,44847298,42967683,54606384,9623492,33699435,1431742,51464002,95290172,13468268,68204242,33553959,17386996,36685023,37166608,14093520,34698428,22450468,93359396,86821229,68644627,44983451,77620120,10597197,44177011,29188588,84002370,76703609,90272749,74614639,37280276,51715482,36468541,67227442,39373729,91957544,16099750,61960400,19101477,18131876,63059024,7919588,4099191,79191827,53547802,33304202,93562257,749283,30764367,39801351,28851716,36312813,49597667,72738685,61728685,13862149,70727211,6986898,75777973,7300047,91281584,15148031,73235980,50188404,99524975,95395112,70036438,62452034,86242799,65851721,83083131,47893286,65338021,78218845,7685448,10309525,9398733,5970607,22766820,30543215,8651647,54663246,37501808,35996293,1197320,95726235,6525948,44889423,27041967,2891150,32590267,62430984,71469330,66250369,74724075,89811711,75128745,53084256,65715134,81898046,98739783,32159704,87710366,44846932,5832946,18783702,45237957,9886593,44842615,4119747,90310261,31491938,3233084,18001617,19891772,19486173,84684495,32250045,15535065,24591705,72725103,92398073,95957797,50007421,79942022,29466406,71300104,77413012,55770687,84187166,23848565,36845587,27187213,44348328,57248122,60955663,61623915,56424103,37891451,20486294,47792865,45617087,59197747,84293052,83210802,23432750,68316156,71083565,8099994,6497830,74137926,57359924,61271144,17764950,54058788,76960303,89804152,46870723,20765474,45667668,26392416,42947632,25636669,18663507,88653118,3880712,82886381,52261574,91938191,55247455,6819644,2204165,50567636,99125126,16097038,30787683,94076128,37675718,30218878,78602717,44550764,17365188,75543508,92692283,24733232,66319530,99515901,73617245,40781854,64848072,73222868,5482538,79322415,15015906,48395186,23134715,44784505,22942635,48774913,83789391,75135448,93566986,60102965,81677380,43152977,26102057,57163802,98648327,26063929,48360198,8129978,13348726,23740167,68041839,77787724,68694897,1239555,20002147,85571389,92530431,78766358,94090109,59329511,77301523,30366150,40534591,52293995,8634541,99729357,9860195,80251430,77300457,9259676,9188443,70541760,58749,37620363,26292919,50702367,15902805,7423788,10366309,59177718,72357096,37957788,79738755,38008118,96735716,16567550,34432810,23569917,59109400,3487592,4668450,59371804,88251446,44245960,69355476,45428665,29510992,62115552,36580610,33201905,48892201,78785507,38645117,77898274,33628349,76330843,11161731,62936963,62496012,56153345,36753250,99549373,68000591,20681921,4798568,75986488,16424199,63152504,8791066,48675329,83133790,42073124,82726008,18504197,44473167,55960386,2917920,26744917,26397786,63628376,824872,2717150,14626618,168541,82178706,61141874,82779622,53888755,66832478,8807940,57803235,21993752,73109997,31126490,37192445,4434662,99604946,43322743,55850790,96852321,61859143,70004753,26664538,61712234,44664587,40027975,66271566,61815223,69605283,36139942,34497327,91141395,48260151,72777973,5111370,48658605,23110625,76315420,45990383,72019362,68875490,81172706,91990218,65017137,31733363,62490109,37560164,79821897,42199455,59910624,53632373,90457870,77272628,19376156,67030811,41092172,76434144,3233569,7502255,22721500,66116458,99297164,24619760,6808825,55189057,82532312,40677414,54232247,26139110,63015256,67451935,15064655,33797252,20568363,88904910,72614359,16595436,7104732,98948034,85023028,57240218,40865610,61897582,12303248,26114953,71333116,54014062,79922758,1250437,4064751,96318094,7517032,7066775,1204161,62232211,93270084,33123618,94516935,63506281,51590803,57658654,86022504,47708850,96906410,4091162,72373496,99226875,30395570,56531125,13819358,69786756,22879907,77377183,12024238,90654836,49236559,89419466,89217461,89637706,14731700,17539625,97057187,33431960,14363867,4806458,49271185,29401781,6153406,3509435,20867149,65271999,74452589,32161669,3088684,415901,6038457,28787861,33249630,4985896,84127901,65047700,88444207,67124282,88698958,20885148,91255408,66667729,33895336,99965001,68939068,76825057,26507214,11543098,78909561,48088883,86001008,32274392,86543538,65081429,26776922,42307449,50806615,47213183,32151165,1022677,50668599,84904436,70420215,91664334,51426311,45306070,20642888,62693428,3183975,70372191,71920426,60393039,3294781,21001913,34493392,8115266,24915585,59405277,24953498,22113133,19457589,72278539,39847321,24168411,13173644,62552524,7229550,13470059,9860968,14947650,9599614,5640302,34295794,64602895,68110330,43933006,10649306,64055960,42237907,71965942,53842979,95581843,54868730,47738185,22435353,30891921,66428795,4515343,2208785,72793444,6521313,69641513,1375023,18466635,92071990,27185644,61982238,3235882,55602660,74357852,53666583,45996863,91831487,90090964,75229462,49598724,61373987,91727510,55470718,22405120,56484900,52613508,34698463,36930650,30163921,42644903,89078848,93057697,4069912,11757872,92803766,84166196,36505482,51466049,28796059,89499542,96420429,51472275,63372756,72495719,9058407,97561745,11365791,67513640,72274002,27411561,7955293,30653863,59981773,57961282,48893685,5267545,16054533,29516592,38658347,90158785,98943869,85117092,36933038,92787493,16971929,94711842,60106217,29029316,20836893,36189527,86460488,33935899,26493119,99917599,6221471,64098930,1569515,38341669,27625735,99224392,30090481,30771409,84406788,25842078,38022834,97281847,92604458,51149804,8373289,54427233,79880247,75820087,51315460,78561158,42782652,34946859,21919959,32058615,669105,84840549,68128438,30694952,6111563,90061527,19272365,45407418,54263880,5822202,29065964,97379790,66663942,526217,92554120,65880522,31727379,21070110,85116755,71522968,39201414,69579137,4508700,36396314,45794415,73021291,90004325,39986008,76671482,18411915,81853704,54762643,52060076,35456853,18806856,34236719,45848907,58180653,45995325,16684829,10961421,92215320,93053405,34044787,28928175,8733068,38510840,36808486,93515664,9829782,98653983,81774825,59318837,9257405,19898053,75407347,45075471,58224549,35512853,65275241,37739481,83302115,99690194,28550822,21673260,17081350,66885828,17857111,72358170,78884452,62357986,98462867,29932657,95010552,36135,94539824,83368048,15536795,74441448,62803858,10453030,48673079,44060493,82979980,61263068,8055981,37224844,58751351,7687278,9575954,20645197,16387575,67084351,30998561,41442762,69697787,33061250,72238278,77694700,5073754,82651278,29819940,83747892,8913721,82427263,99861373,6157724,78589145,23194618,11923835,12664567,44915235,38365584,97940276,57393458,41092102,30569392,18833224,67031644,45381876,38009615,88130087,66045587,10264691,68824981,3633375,86118021,66322959,89699445,82052050,86798033,30811010,33565483,24826575,40984766,92541302,53802686,71316369,82897371,62740044,26863229,30139692,85695762,85711894,4204661,60176618,70367851,46540998,75052463,62428472,7646095,58208470,1788101,41245325,22200378,90013093,44627776,92353856,15075176,80246713,17727650,47887470,83533741,7182642,54517921,2607799,45919976,99971982,78300864,27325428,92033260,29635537,17146629,83948335,17894977,4199704,92867155,38256849,39553046,176257,41380093,94595668,91240048,56955985,15948937,97011160,1936762,44479073,34667286,32426752,62762857,49882705,15163258,61928316,45049308,67474219,83269727,8729146,17976208,64157906,37435892,21397057,24314885,4978543,2331773,33336148,17385531,55615885,17037369 +44889423,98948034,44550764,96726697,44844121,99549373,51047803,20486294,78602717,1788101,68644627,74110882,92398073,76960303,14723410,43322743,81677380,68041839,77072625,17894977,39986008,43357947,72777973,1197320,60430369,14731700,30787683,30543215,48673079,79821897,67451935,56473732,93053405,4099191,82886381,85116755,65081429,37183543,10961421,247198,31904591,77898274,29994197,21993752,52261574,19457589,44842615,93057697,94711842,42073124,63059024,49641577,66667729,11161731,2917920,14626618,25636669,37620363,80316608,56424103,40781854,88698958,93933709,35192533,67281495,8099994,4668450,53648154,72725103,87720882,17764950,63967300,74743862,5822202,94090109,29959549,70036438,72019362,41481685,35456853,36505482,72357096,84684495,92604458,17727650,88653118,7104732,82979980,62430984,62552524,45407418,32590267,14326617,63372756,97783876,77620120,38658347,20867149,57241521,28796059,57393458,72278539,57961282,79191827,45237957,65271999,1431742,59614746,17016156,66428795,24619760,86242799,67793644,33431960,59371804,66611759,8729146,91255408,43376279,59177718,81274566,168541,36580610,45306070,97641116,26998766,69848388,47090124,80555751,93562257,9575954,34698463,32274392,13231279,22108665,96193415,59501487,10309525,91802888,43933006,98130363,18504197,15148031,84293052,62740044,49236559,6819644,21070110,61623915,53632373,93515664,36812683,63152504,55189057,46851987,68702632,68694897,26392416,65275241,8791066,39801351,13470059,4119747,22766820,38645117,85023028,54014062,92692283,89078848,8115266,23194618,34236719,13468268,84166196,83155442,36930650,62115552,51590803,61373987,54517921,90457870,99965001,22721500,83150534,69786756,5970607,19486173,874791,8373289,69605283,93566986,30998561,48260151,75777973,29188588,5111370,7300047,7502255,5640302,34698428,86022504,55143724,57248122,99226875,51472275,44846932,3233084,40984766,12348118,54427233,9603598,40677414,669105,39171895,36753250,26139110,16387575,62803858,6157724,33304202,73786944,4985896,19898053,46540998,26114953,59329511,30569392,43045786,94539824,13819358,95290172,43501211,46870723,95957797,72358170,89217461,54762643,62496012,43152977,40686254,99224392,48774913,23848565,49271185,5482538,17539625,91957544,64848072,36780454,49328608,16097038,59318837,2204165,47887470,9829782,12664567,82897371,43506672,2717150,40781449,10366309,41092102,83789391,90061527,27041967,91727510,91831487,19891772,44348328,18783702,28928175,80251430,22129328,99658235,14093520,69355476,22200378,60581278,22879907,73235980,7646095,47213183,9188443,6808825,9259676,7229550,53393358,15536795,11543098,38365584,82427263,66885828,83210802,61859581,84904436,18466635,68204242,61859143,8696647,50188404,30764367,44983451,56515456,4069912,58224549,38061439,29819940,23110625,59405277,45848907,4515343,58751351,27665211,44245960,29834512,90272749,31161687,86001008,45794415,70782102,28734791,2208785,73031054,58180653,20642888,13862149,90004325,61271144,81853704,73168565,37957788,99333375,86301513,19939935,6986898,64087743,22435353,3773993,38009615,74137926,7955293,33237508,49598724,81898046,65017137,15902805,88047921,16380211,39553046,81855445,61741594,89996536,89811711,54199160,12416768,47361209,30218878,65454636,85695762,26507214,70004753,42782652,33895336,37435892,76315420,32161669,54663246,73124510,96318094,71920426,50702367,9599614,75153252,66271566,6497830,49597667,18411915,82726008,97011160,17857111,33565483,54987042,62936963,77187825,79657802,40534591,65038678,67030811,17058722,89699445,4978543,67474219,36685023,99021067,55615885,86798033,88251446,44664587,98371444,17068582,2607799,76434144,62051033,66045587,98462867,42644903,97940276,77272628,9623492,68000591,69136837,41245325,51464002,8651647,85711894,78589145,52293995,34493392,70727211,75986488,99604946,1569515,36808486,38494874,54058788,321665,22450468,79880247,29029316,749283,34497327,20140249,71333116,20568363,9886593,4787945,34667286,72738685,93790285,78884452,9175338,82327024,14363867,16054533,45996863,68875490,33699435,98739783,4434662,73617245,35092039,48892201,508198,72373496,92998591,55850790,33628349,76703609,82178706,16971929,75229462,76540481,17146629,11365791,31126490,61815223,58749,95726235,26863229,7517032,77284619,569864,28550822,62452034,40356877,98653983,2891150,66250369,72495719,40152546,35996293,73222868,45049308,6153406,24826575,26292919,42307449,4091162,9860195,61982238,55669657,1239555,6038457,60955663,71965942,37739481,27625735,4199704,91990218,24591705,26102057,7687278,72274002,48395186,1250437,30694952,62490109,6525948,33553959,176257,86821229,97057187,92033260,96852321,29065964,12571310,75543508,3633375,92541302,21397057,6793819,18663507,31733363,54232247,44177011,45428665,67124282,51315460,50806615,89046466,77312810,56153345,90013093,22997281,94076128,14045383,65338021,63628376,94360702,24168411,27411561,3509435,70596786,57020481,20645197,89214616,45075471,92071990,59910624,8634541,61263068,30891921,75135448,67031644,77300457,82651278,36312813,92530431,72614359,66903004,17037369,1936762,44627776,19376156,72238278,64157906,55247455,16019925,86460488,19101477,58208470,8055981,55470718,28851716,23569917,24953498,30139692,15064655,50007421,79922758,24733232,37224844,26776922,8807940,71522968,55753905,33797252,66663942,50668599,32699744,37659250,77413012,90090964,12303248,77787724,32250045,5832946,78785507,4064751,26493119,61897582,42947632,14947650,3233569,824872,70420215,47298834,89637706,84406788,15535065,51466049,77301523,99729357,27185644,68316156,47708850,9058407,44473167,92803766,55602660,53547802,41442762,87160386,24058273,8733068,63015256,22113133,99861373,34044787,53084256,2331773,99515901,17081350,34946859,65715134,24915585,91664334,83651211,83747892,84840549,71300104,90310261,7011964,18833224,74724075,96735716,66116458,51507425,10649306,78218845,78561158,11923835,68816413,62232211,85571389,36396314,22942635,95010552,83302115,96906410,30463802,3235882,29932657,16424199,45919976,42199455,3183975,48658605,91240048,13173644,29635537,38510840,12024238,67227442,8913721,37891451,94516935,66319530,76825057,14349098,20885148,40197395,23134715,64098930,67513640,89419466,30090481,51588015,22405120,54606384,91938191,61712234,45790169,30811010,92215320,91141395,61960400,16445503,70372191,68128438,75128745,6111563,37501808,92554120,52204879,36135,93270084,82779622,3294781,83133790,47792865,15015906,69641513,84349107,38341669,79806380,41380093,98648327,32058615,53888755,76330843,53666583,40865610,88904910,65851721,33201905,79136082,57803235,82339363,30771409,44479073,26734892,8128637,20002147,57359924,74357852,89499542,27187213,89804152,99917599,71657078,44784505,1022677,51149804,4806458,92692978,94911072,31491938,20122224,45990383,75820087,80246713,66137019,33249630,47893286,31727379,7066775,86118021,48360198,36933038,71083565,87710366,24314885,3487592,32159704,37675718,68110330,10264691,50567636,78300864,53802686,59197747,65047700,79942022,94595668,33061250,78766358,99125126,92867155,99690194,60176618,112651,63506281,9860968,83533741,62762857,53842979,34295794,21289531,6505939,30653863,21001913,51715482,35512853,18806856,29516592,20681921,4508700,16567550,32426752,415901,81172706,21673260,80014588,56461322,10358899,99971982,17365188,34432810,85117092,68102437,78549759,5267545,26275734,36139942,77694700,11757872,74441448,6871053,74614639,97561745,42692881,61141874,95395112,29401781,42967683,51426311,40027975,73021291,38022834,73392814,29510992,64055960,91281584,62693428,18131876,1204161,85242963,77377183,70800879,4095116,71316369,78909561,10453030,30163921,14220886,84127901,81805959,39201414,69579137,57240218,82532312,9257405,68824981,65880522,65074535,79322415,97281847,26744917,48893685,69255765,20765474,71469330,92787493,16405341,72793444,62428472,59981773,82052050,33336148,96420429,7919588,36468541,16684829,87598888,3880712,81774825,4798568,45617087,70541760,5073754,7685448,52613508,18699206,97379790,90654836,39373729,26063929,6221471,96709982,56484900,86543538,17957593,76170907,21533347,37166608,45381876,37560164,7182642,61728685,92353856,15948937,526217,36189527,99524975,7423788,76671482,88444207,19272365,60102965,60393039,93359396,52060076,44847298,64602895,98943869,57163802,44481640,42237907,79738755,47738185,75407347,55960386,66322959,72732205,45667668,55770687,25842078,37192445,75052463,83368048,16099750,4204661,95251277,67084351,83083131,66832478,70367851,21919959,61928316,17385531,13348726,88130087,57658654,83948335,9398733,45995325,83269727,56531125,20836893,95581843,29466406,15163258,23432750,73109997,3088684,36845587,16595436,69697787,99297164,48088883,74452589,48675329,18001617,38008118,62357986,15075176,23740167,68939068,39847321,49882705,26397786,8129978,6521313,30395570,17386996,27325428,10597197,56955985,41092172,33935899,17976208,38256849,33123618,54868730,30366150,11581181,1375023,90158785,59109400,60106217,84002370,37280276,28787861,32151165,26664538,44060493,54263880,84187166,44915235 +24733232,35192533,76671482,57658654,99658235,6505939,98371444,77620120,7685448,99021067,99333375,35092039,4064751,15536795,45848907,26998766,37659250,4985896,79821897,29466406,44846932,54762643,30764367,94911072,14045383,44915235,29029316,79880247,17365188,37739481,44844121,73031054,49641577,80316608,82886381,62740044,1431742,90272749,57241521,73124510,62430984,39847321,14220886,4099191,57803235,29188588,19939935,16445503,96726697,8651647,59329511,55753905,78589145,15015906,62452034,65074535,508198,4668450,89046466,112651,1204161,70596786,60102965,51047803,83150534,36780454,2717150,29834512,99690194,91831487,51464002,32250045,64098930,80014588,36930650,81172706,16019925,68702632,42199455,59501487,65880522,89419466,64087743,75229462,90090964,71657078,80246713,37675718,59197747,72358170,66250369,88653118,15075176,53802686,51590803,10358899,60581278,50567636,34698428,96852321,74357852,81774825,30395570,36312813,72732205,89214616,8791066,60430369,81853704,69641513,39373729,85242963,43501211,48360198,20002147,28796059,30463802,86301513,11581181,5267545,40356877,57163802,36812683,9575954,62936963,18504197,5970607,13470059,59177718,52060076,7955293,39171895,32151165,54058788,77413012,29994197,34698463,73392814,45996863,21289531,98648327,63967300,29819940,31727379,89699445,78602717,30163921,20885148,83155442,19272365,48675329,83651211,48774913,26392416,33237508,30218878,59318837,15535065,85023028,40781449,32161669,79657802,70727211,57240218,7687278,22450468,6153406,14626618,48893685,56424103,73617245,31126490,74614639,61982238,9257405,22129328,20568363,92803766,78909561,67793644,72777973,30139692,89078848,51507425,67474219,95581843,4199704,3233084,77284619,44784505,61859581,66663942,70036438,61741594,8807940,17016156,3183975,46870723,99965001,8129978,68939068,92554120,4434662,38256849,44550764,15148031,96193415,24619760,415901,65715134,6871053,71920426,31161687,84187166,36753250,76960303,26292919,48088883,40152546,9188443,51149804,1788101,86821229,98948034,99549373,96735716,56531125,669105,8099994,62490109,50806615,83948335,55850790,66137019,66667729,1022677,56461322,6521313,71469330,54868730,4508700,10366309,3509435,5822202,53084256,18663507,53547802,61263068,46540998,12303248,78549759,7423788,61815223,2204165,8733068,49328608,35456853,27041967,4798568,5111370,70004753,72357096,75407347,43045786,90158785,39986008,89637706,45075471,96420429,79942022,84904436,16380211,6808825,97783876,7011964,23134715,21070110,45306070,73222868,98130363,18833224,18783702,99604946,749283,72278539,88047921,32274392,82779622,93562257,14731700,65454636,92215320,63015256,45790169,13231279,93933709,44842615,30366150,17068582,50188404,92998591,23432750,62693428,36580610,62115552,61712234,51426311,33628349,77898274,89217461,60176618,44177011,12348118,20486294,88444207,44664587,33895336,51588015,98739783,55143724,19457589,91727510,76703609,55470718,39801351,60955663,48395186,61623915,9860195,5073754,77312810,86543538,54014062,77301523,52204879,1197320,97561745,76170907,95395112,47738185,59981773,55602660,23740167,48260151,23569917,99524975,67281495,17957593,7104732,68102437,31904591,247198,79136082,73168565,69136837,53648154,77187825,40686254,42692881,70372191,94360702,77300457,10649306,83789391,36505482,29510992,84293052,63059024,53632373,77377183,18131876,44847298,12416768,87710366,59109400,40984766,90654836,59614746,4095116,77272628,69355476,26397786,66045587,9623492,9886593,52613508,40197395,43506672,29516592,60106217,34236719,44481640,44348328,10597197,37183543,40027975,20765474,72274002,55189057,38022834,5832946,91664334,168541,78300864,42307449,176257,51472275,9175338,43357947,44479073,77787724,79322415,56153345,62496012,19376156,78561158,47213183,526217,43152977,54606384,54987042,73786944,37435892,45995325,72725103,72019362,17764950,75135448,29959549,321665,72495719,9599614,75543508,11365791,13862149,12571310,3880712,33797252,26744917,89804152,99917599,24915585,39553046,37957788,91141395,45407418,65271999,66319530,4119747,22405120,6793819,58180653,91802888,35512853,90061527,47298834,52261574,17385531,37501808,49236559,83368048,94711842,87720882,44983451,38494874,13468268,55247455,4091162,32058615,20122224,75777973,69579137,78766358,32699744,22942635,88130087,73235980,22108665,34295794,92787493,98462867,86242799,16405341,3088684,99226875,49598724,23194618,55615885,65275241,28550822,67030811,26114953,9603598,53666583,7182642,4515343,30653863,10309525,48673079,47887470,24168411,7229550,92692283,91938191,44889423,68000591,33061250,17146629,33201905,15902805,75820087,55960386,86001008,2607799,13348726,67451935,80555751,43933006,20867149,84406788,65038678,99224392,36845587,76434144,25842078,69848388,76315420,16097038,30771409,93057697,82726008,71333116,19891772,20645197,17857111,36808486,30787683,61960400,88251446,84840549,3235882,78884452,37891451,21673260,83210802,4069912,92692978,7919588,45428665,93515664,11161731,54517921,26863229,68644627,41380093,27665211,24826575,3294781,28734791,95290172,6525948,33699435,87160386,50668599,70420215,1250437,99971982,34044787,97940276,66885828,91990218,82178706,77072625,92604458,22721500,6497830,27411561,14093520,93790285,8128637,15163258,72373496,74110882,8696647,28787861,569864,68824981,4204661,69255765,3233569,93053405,874791,32590267,17058722,71965942,83269727,77694700,95957797,34432810,66832478,17539625,43376279,26664538,26507214,97011160,79191827,6038457,13173644,75153252,42073124,10453030,6819644,82427263,26776922,27185644,97641116,18001617,69786756,14947650,81898046,17976208,37280276,21533347,66611759,50702367,68041839,80251430,67227442,29635537,81677380,31733363,9829782,22879907,67084351,75986488,3487592,74452589,76540481,42967683,18411915,66271566,45381876,26734892,42947632,90457870,39201414,90013093,41245325,67031644,23110625,70800879,85695762,78218845,33123618,83133790,30543215,22997281,18699206,92530431,92541302,68128438,58749,49882705,17081350,16099750,22113133,16595436,38341669,59910624,75128745,38365584,8913721,97057187,92398073,28851716,66116458,10264691,26139110,61271144,34946859,79738755,17894977,8634541,33304202,56515456,6111563,93270084,92071990,89499542,52293995,71083565,89811711,12024238,14326617,68875490,4806458,91957544,95251277,58224549,14723410,34493392,21993752,40677414,29065964,30569392,24058273,29932657,9860968,19101477,30811010,48892201,72738685,51315460,1239555,45919976,66903004,64602895,81274566,64157906,62357986,63628376,86118021,74724075,44245960,16054533,99297164,47090124,7300047,34667286,99729357,62803858,824872,70541760,94516935,85117092,41092172,4787945,62762857,57020481,26063929,75052463,47893286,98943869,98653983,79806380,25636669,7646095,54199160,44627776,84684495,84349107,27625735,68316156,72614359,79922758,35996293,22435353,1569515,99125126,36189527,41092102,88904910,89996536,31491938,63152504,87598888,70367851,93566986,21001913,33431960,56473732,81805959,97379790,94539824,38008118,53393358,2891150,84127901,69697787,16387575,38061439,82339363,33565483,61859143,38645117,74441448,20681921,65851721,86022504,82532312,45617087,33249630,42237907,76825057,26493119,40781854,38009615,11923835,20642888,56484900,8373289,94076128,36135,36685023,22200378,82979980,88698958,99515901,47792865,71522968,36468541,62051033,6157724,38510840,66428795,54663246,65017137,83747892,37560164,71316369,45667668,99861373,95726235,58751351,30694952,12664567,13819358,16424199,61373987,83083131,20836893,17037369,92353856,5482538,67513640,74743862,33935899,65047700,5640302,23848565,36396314,34497327,85711894,6986898,55770687,84002370,82327024,59405277,2331773,60393039,82651278,48658605,76330843,16971929,91255408,54427233,2917920,27187213,21919959,55669657,20140249,96906410,19486173,72793444,86460488,47361209,19898053,42644903,45794415,46851987,8729146,14349098,1936762,49271185,91240048,61728685,85116755,32159704,53888755,47708850,38658347,27325428,32426752,51715482,70782102,9398733,92033260,2208785,16684829,24953498,11543098,14363867,43322743,26102057,63372756,57393458,51466049,96318094,82052050,95010552,37192445,58208470,30891921,53842979,7066775,66322959,64848072,22766820,17386996,63506281,42782652,56955985,94090109,49597667,96709982,3633375,40865610,8055981,45049308,57248122,73021291,54263880,59371804,4978543,6221471,91281584,8115266,54232247,68204242,41442762,7502255,86798033,1375023,28928175,45237957,61928316,57359924,41481685,61141874,83533741,62428472,71300104,74137926,9259676,37166608,36139942,30090481,18466635,18806856,15948937,68816413,78785507,69605283,82897371,37620363,29401781,26275734,30998561,44473167,81855445,92867155,90004325,9058407,15064655,83302115,33336148,67124282,68694897,50007421,45990383,3773993,40534591,65081429,97281847,65338021,17727650,61897582,24591705,73109997,85571389,68110330,72238278,64055960,7517032,84166196,44060493,94595668,62552524,36933038,37224844,90310261,62232211,93359396,57961282,33553959,16567550,11757872,24314885,21397057,10961421 +669105,67281495,75153252,49236559,30139692,57803235,31904591,26507214,44784505,29188588,9886593,6986898,30543215,4099191,76434144,29029316,65715134,76960303,70596786,38494874,63967300,50806615,66832478,68644627,15535065,10309525,26863229,80014588,72274002,29932657,11365791,45407418,89214616,45794415,96726697,77312810,62430984,6038457,33553959,34044787,29834512,92554120,24058273,66667729,68204242,62452034,52261574,48360198,37620363,34236719,23848565,48673079,93933709,91664334,52293995,4515343,81853704,35192533,61263068,97281847,65038678,7104732,55470718,55753905,45237957,5970607,90272749,7687278,34497327,87720882,168541,4119747,14220886,62552524,44177011,47213183,29959549,62936963,50188404,415901,50007421,40197395,4787945,64602895,74743862,4091162,53648154,86001008,94539824,27665211,3294781,51047803,12664567,31126490,85711894,38658347,66250369,8128637,35092039,58224549,38645117,83651211,43506672,44889423,15015906,6221471,61373987,44983451,61728685,2917920,24953498,64848072,55615885,14045383,88047921,66045587,92071990,96318094,66611759,68000591,19939935,54427233,33895336,37183543,59318837,96193415,526217,34698428,1250437,44473167,91990218,33431960,749283,1569515,6153406,62051033,16099750,60430369,34295794,59177718,1204161,53084256,84349107,40356877,30891921,34946859,92033260,59197747,72357096,61982238,36312813,86022504,78766358,98130363,90090964,55247455,37675718,8807940,16595436,15064655,32426752,28550822,32151165,20486294,18699206,66428795,77301523,81898046,57240218,99524975,30163921,57241521,63059024,92353856,6808825,61741594,57393458,39373729,89699445,74357852,29819940,90457870,16424199,55189057,75229462,79821897,14731700,4798568,85116755,99549373,83083131,72019362,74441448,69848388,79922758,65338021,98371444,94516935,91802888,57359924,95726235,94911072,61712234,94090109,76315420,55770687,8696647,80316608,8055981,40984766,44550764,1022677,81677380,47090124,65017137,9829782,93359396,11923835,26776922,77187825,40781449,3088684,32161669,4064751,65851721,69641513,44915235,69255765,6525948,70004753,48675329,99515901,73222868,62428472,72738685,30694952,96906410,75543508,32159704,54199160,22113133,65271999,34493392,84127901,37739481,58751351,53547802,7646095,24591705,61623915,92530431,49328608,321665,82726008,82427263,35456853,21993752,37192445,99658235,20140249,99297164,48892201,8634541,62496012,17857111,99965001,54232247,93053405,60102965,70367851,60581278,84684495,91938191,77272628,17068582,68702632,82339363,9575954,19457589,84904436,75777973,72777973,44348328,30395570,33336148,40865610,42692881,34432810,30463802,54014062,5111370,48260151,81855445,33249630,92398073,88444207,26102057,56515456,99021067,42782652,17037369,8729146,71333116,54517921,69355476,26392416,26063929,78602717,5832946,4668450,61141874,78549759,73786944,22942635,38365584,98948034,17365188,54987042,48774913,88653118,93562257,10649306,4806458,22766820,83789391,4204661,19486173,80246713,89637706,78909561,96709982,73031054,82886381,44479073,98462867,14947650,1431742,67793644,44481640,36930650,92998591,96735716,89811711,45848907,42073124,1197320,247198,83533741,79942022,99125126,77620120,27185644,89804152,48893685,26292919,26493119,9599614,15148031,49598724,14626618,66116458,27411561,569864,53666583,75407347,12416768,46870723,30998561,91727510,9175338,66137019,5482538,38061439,26998766,43322743,32590267,42199455,79191827,1239555,49271185,176257,89499542,32699744,18504197,30569392,65454636,50668599,83302115,29466406,99861373,46851987,12571310,56424103,45075471,78589145,76330843,8651647,43045786,83210802,79136082,13231279,24619760,92787493,36505482,6111563,70372191,33628349,68816413,22997281,36812683,9623492,24826575,38341669,36933038,95957797,26139110,90654836,17058722,77413012,1936762,71083565,97641116,30653863,63015256,68875490,15948937,77787724,45428665,68694897,42967683,9398733,36685023,12024238,70800879,22200378,97561745,81172706,40027975,16019925,16971929,17386996,62740044,74614639,65275241,79806380,70036438,37435892,74110882,85242963,99226875,824872,56461322,94711842,90004325,36753250,74724075,7685448,39801351,10453030,8373289,2891150,43376279,99604946,11543098,77072625,15902805,75986488,92692978,77284619,16380211,17539625,47887470,30764367,90310261,86301513,80251430,31733363,16097038,1375023,65047700,3235882,20002147,29510992,62693428,4434662,75135448,39171895,45919976,87710366,72725103,48088883,14093520,6157724,44842615,53888755,71920426,36808486,22405120,20836893,45306070,89217461,51466049,36139942,98739783,9603598,61859581,38008118,26734892,9860195,6871053,3233084,18466635,79738755,69786756,71469330,79880247,9259676,40686254,1788101,55960386,17727650,40534591,22435353,74137926,65074535,6793819,47738185,72614359,97379790,83269727,77300457,72238278,19101477,23194618,68102437,7919588,99917599,20885148,9188443,73617245,37166608,99690194,51472275,29065964,66271566,73124510,67030811,73235980,33304202,89046466,81774825,7300047,55143724,20867149,45667668,7955293,3880712,55602660,43501211,72373496,91957544,71657078,24733232,58749,66322959,94360702,63372756,13468268,41442762,18806856,59371804,52060076,14326617,27187213,70541760,4069912,61859143,76170907,6505939,21673260,92215320,59614746,508198,41092172,38009615,59501487,31161687,7502255,21070110,23569917,36580610,37224844,18783702,39553046,33797252,72278539,82651278,97011160,67031644,32250045,24168411,2717150,84293052,62490109,12348118,62232211,28734791,97783876,45381876,91141395,11581181,17385531,17016156,19891772,52204879,67474219,2208785,16445503,86543538,17976208,68316156,82052050,112651,8115266,29635537,21533347,63152504,67084351,26114953,45790169,69136837,51590803,15536795,30366150,2204165,85023028,40152546,64087743,84166196,21919959,25842078,80555751,51715482,19376156,8099994,45990383,92604458,67451935,37501808,20765474,40677414,76540481,93057697,39847321,47792865,23432750,82327024,95290172,86821229,41481685,43152977,59910624,27625735,78785507,29401781,28928175,37957788,88130087,44060493,50702367,44847298,73021291,15163258,66319530,51588015,30218878,43933006,98648327,73168565,78218845,99333375,39986008,90013093,87598888,50567636,22721500,22129328,61960400,70782102,41380093,37659250,23134715,51426311,71965942,46540998,7517032,44846932,52613508,96420429,81274566,75128745,13348726,3633375,14723410,87160386,7229550,29994197,68041839,19898053,95251277,82979980,40781854,61897582,17957593,30771409,13470059,34698463,51464002,75052463,23110625,3487592,91831487,3509435,53842979,54663246,51507425,38510840,76671482,33935899,20122224,71316369,94076128,92867155,45617087,9257405,95581843,84187166,7011964,88251446,36845587,84002370,42307449,56955985,2607799,4508700,57961282,92541302,16054533,84406788,78884452,57658654,69697787,86460488,95010552,6497830,88698958,41245325,56531125,9058407,17894977,33565483,78561158,36189527,67124282,82897371,98653983,47708850,42947632,26397786,44627776,10366309,68939068,68128438,43357947,58180653,97940276,60106217,93515664,8129978,66663942,6819644,48395186,33123618,20681921,78300864,25636669,66903004,45995325,97057187,28787861,54058788,10358899,59109400,79657802,53632373,83150534,77694700,47361209,28796059,89419466,21289531,26744917,66885828,49597667,93270084,26664538,86798033,73392814,14363867,95395112,874791,54606384,36468541,20645197,74452589,51149804,33061250,82532312,36780454,53802686,99971982,20568363,18411915,47298834,37560164,36135,4985896,57020481,13862149,49641577,98943869,8913721,33237508,83155442,9860968,68824981,22450468,63506281,93566986,60955663,26275734,72358170,62115552,17081350,69579137,32274392,5822202,13173644,45996863,68110330,85571389,42644903,63628376,94595668,72495719,64157906,70727211,85695762,39201414,31727379,4978543,55850790,56473732,37891451,5267545,56153345,77377183,83133790,55669657,91255408,91281584,92692283,61928316,8791066,64098930,93790285,88904910,49882705,70420215,48658605,30787683,76703609,4199704,61271144,21001913,41092102,57163802,91240048,35512853,53393358,86118021,38256849,3233569,83747892,33699435,54263880,84840549,51315460,83368048,33201905,29516592,90061527,37280276,12303248,89078848,10961421,5640302,23740167,62762857,82779622,57248122,16387575,36396314,2331773,30811010,72732205,15075176,11161731,79322415,18131876,3183975,5073754,60393039,22108665,19272365,8733068,75820087,67227442,32058615,16567550,17764950,81805959,59329511,24314885,71300104,11757872,16684829,73109997,30090481,35996293,64055960,24915585,44245960,65880522,96852321,54868730,7423788,69605283,14349098,76825057,60176618,59405277,62357986,18833224,10264691,28851716,82178706,72793444,18001617,16405341,85117092,27041967,17146629,42237907,3773993,71522968,77898274,92803766,7182642,59981773,67513640,38022834,4095116,99224392,62803858,31491938,83948335,86242799,21397057,20642888,56484900,89996536,58208470,7066775,27325428,6521313,13819358,65081429,22879907,61815223,34667286,47893286,10597197,44664587,54762643,18663507,45049308,44844121,90158785,99729357 +47090124,71300104,39171895,12348118,8807940,27665211,97783876,4099191,98739783,44846932,51047803,65715134,26063929,67281495,29959549,16445503,26392416,508198,81274566,1431742,98462867,6819644,85023028,96193415,4787945,96726697,6153406,80251430,61263068,569864,34698428,56473732,78549759,43933006,1204161,45995325,56461322,35092039,37957788,86821229,17016156,21289531,68644627,8696647,96420429,66137019,3509435,77312810,5832946,92692283,77620120,51426311,47708850,61859143,168541,55753905,70004753,33304202,7919588,43501211,6793819,18699206,57163802,69641513,32250045,78589145,57240218,19939935,98130363,89078848,31491938,80316608,95290172,46870723,88653118,54058788,69355476,36580610,85242963,112651,22997281,17764950,35192533,75543508,89046466,76671482,83302115,4806458,20486294,38494874,21993752,74743862,67793644,44348328,9623492,40865610,44844121,43376279,7502255,4798568,12024238,15535065,29834512,43322743,57803235,3233569,34432810,20122224,20765474,42947632,55770687,5970607,62430984,74724075,14093520,46851987,76703609,29819940,62936963,7517032,13348726,99297164,24058273,36930650,321665,29994197,16387575,3294781,45996863,65074535,29510992,33553959,60955663,24619760,31161687,26734892,50702367,83210802,9398733,36780454,26664538,6505939,95395112,91240048,88047921,96318094,60581278,92215320,9860968,26292919,33431960,90004325,68702632,90013093,32161669,83083131,28851716,12416768,59371804,63628376,99333375,49641577,62452034,69605283,99549373,37659250,33797252,68102437,56424103,55143724,48088883,53084256,4119747,19486173,83789391,31733363,4069912,74357852,7423788,37620363,44245960,97641116,40534591,94911072,71657078,40152546,62740044,93562257,9603598,61623915,14045383,70541760,8099994,49597667,30395570,69136837,10358899,29029316,76540481,29065964,99604946,99861373,58208470,22450468,30569392,92398073,36505482,51466049,20645197,40356877,79922758,38061439,75229462,72738685,54762643,36753250,85711894,24733232,81805959,11923835,87710366,34493392,60430369,77377183,70800879,16097038,84349107,19101477,65038678,48360198,14349098,61728685,99965001,71920426,11161731,54199160,15015906,4095116,11581181,51588015,65271999,25842078,6986898,73124510,92554120,45237957,9886593,79821897,74110882,22108665,29932657,70372191,94595668,78561158,74441448,66903004,84293052,18783702,23194618,73617245,86301513,33895336,84187166,92803766,4199704,8651647,10597197,59910624,26493119,83150534,4985896,16595436,55960386,82886381,14326617,64087743,69579137,68204242,45407418,39201414,62490109,22435353,40686254,91802888,34497327,2917920,82052050,33123618,30764367,41380093,37435892,66250369,59501487,65047700,47213183,16019925,57961282,42967683,93933709,67451935,36468541,72777973,31126490,65338021,85117092,15948937,98648327,81898046,20867149,874791,32590267,76330843,38365584,72238278,99021067,70036438,37675718,9599614,8634541,91281584,53648154,52261574,33249630,89214616,26102057,45667668,30771409,51464002,50188404,30463802,87160386,21673260,17539625,15075176,61982238,20002147,66611759,6808825,40197395,83651211,89811711,65017137,54987042,53632373,55602660,98371444,19376156,83533741,72373496,81853704,61815223,29188588,4434662,89637706,28796059,60106217,24953498,45617087,95726235,73392814,72495719,16424199,10366309,59109400,43045786,53842979,78766358,669105,63967300,50567636,31904591,99226875,79880247,41481685,66319530,18833224,34044787,81855445,93270084,18131876,19272365,95957797,26776922,8373289,44889423,6871053,1022677,1197320,52060076,79191827,30218878,68824981,68939068,21070110,54014062,29466406,45790169,40781854,80555751,73168565,48774913,5267545,18806856,6497830,16971929,77898274,59197747,61960400,415901,49328608,44842615,32159704,62693428,64157906,51715482,79136082,28550822,2717150,41092102,4978543,15902805,32274392,17146629,99224392,7011964,14731700,23432750,47738185,30090481,36312813,88130087,14626618,30787683,84002370,49236559,64098930,47298834,97561745,59177718,61141874,35512853,66885828,40984766,56515456,92692978,3880712,82897371,51472275,23569917,86460488,88444207,86001008,84684495,76170907,82327024,44481640,55470718,37739481,1239555,38008118,73031054,71316369,33628349,90457870,54427233,79942022,97940276,24591705,8115266,78909561,68875490,82726008,61897582,50668599,42644903,72019362,78785507,96735716,58180653,61271144,7300047,48673079,15064655,13862149,53547802,5111370,62803858,75820087,17976208,99658235,78218845,93053405,3773993,98948034,65275241,93566986,37224844,8791066,40781449,37183543,57248122,43506672,27411561,92353856,95251277,47361209,526217,30139692,26114953,12571310,67084351,28787861,65081429,39986008,18663507,63015256,33201905,58749,83368048,44983451,39847321,16054533,96906410,44479073,76960303,13231279,67227442,20885148,39373729,247198,8129978,91727510,7104732,67124282,81172706,38658347,8913721,55615885,17727650,66428795,77187825,90654836,79738755,75986488,57020481,37192445,7955293,62232211,39553046,59318837,69848388,26744917,83269727,4064751,11365791,28928175,10961421,60102965,30653863,4091162,66271566,77072625,21533347,36685023,67513640,52293995,40677414,16380211,47792865,32699744,45075471,89804152,9188443,66322959,72725103,79657802,13173644,14723410,3487592,36189527,13468268,17058722,41245325,97011160,65851721,55247455,5482538,82779622,53802686,62115552,9575954,2891150,26139110,19457589,44550764,13470059,33699435,92033260,2204165,66832478,85116755,33237508,71333116,59329511,45919976,81677380,56531125,63059024,77301523,20681921,72732205,75407347,8733068,19891772,90272749,58751351,79806380,9259676,57393458,36812683,35996293,21001913,15163258,4515343,94516935,17957593,84840549,75052463,92071990,70596786,78602717,93790285,37166608,57359924,54663246,48395186,17068582,3183975,9257405,73235980,26998766,14947650,7646095,19898053,5640302,77272628,68000591,92541302,28734791,55189057,22942635,3235882,42237907,22879907,33565483,96852321,30163921,17365188,67031644,38510840,82178706,21919959,65454636,77284619,20642888,40027975,27625735,31727379,54606384,77300457,80246713,56484900,42199455,59405277,14363867,82532312,27041967,72274002,35456853,70367851,23740167,45428665,7229550,84406788,3088684,18411915,68816413,63152504,93057697,48658605,61373987,72793444,23848565,73786944,20836893,63372756,30891921,41092172,57241521,52204879,54232247,30998561,43152977,68128438,27185644,70727211,77787724,94711842,18001617,20140249,94076128,79322415,36808486,65880522,49598724,34236719,10309525,80014588,42073124,89419466,824872,16099750,4668450,7687278,23134715,38022834,94539824,88251446,5822202,48260151,82339363,7182642,54517921,30366150,38256849,91957544,33336148,44177011,64602895,48892201,3633375,99125126,74137926,66045587,47887470,69697787,89217461,99515901,75153252,99690194,36139942,26275734,3233084,6157724,50806615,87598888,43357947,86798033,82651278,83948335,90310261,39801351,89499542,24314885,34698463,42307449,27325428,44473167,30694952,53888755,8128637,30543215,73222868,67474219,24168411,90090964,60176618,57658654,44847298,53666583,86242799,10649306,86543538,92998591,72357096,37891451,59614746,9175338,72614359,4204661,27187213,85695762,90158785,38645117,62428472,75777973,62496012,82979980,2208785,45794415,67030811,91938191,16684829,56153345,1250437,36135,36845587,71522968,15536795,69786756,64848072,45306070,15148031,29401781,66663942,93515664,37560164,70420215,98653983,95581843,53393358,81774825,22129328,8055981,24826575,51149804,94090109,77413012,68694897,72278539,49271185,29516592,75135448,91664334,84127901,7685448,61859581,51590803,42782652,97281847,54868730,38009615,94360702,86022504,23110625,82427263,92604458,9860195,95010552,59981773,18466635,20568363,99524975,6111563,12303248,22405120,56955985,73021291,12664567,90061527,71469330,91990218,51507425,33061250,76434144,25636669,8729146,84904436,91255408,92867155,18504197,1788101,44915235,749283,83155442,75128745,37280276,17894977,17385531,6221471,45848907,49882705,11543098,60393039,74614639,11757872,88904910,33935899,61712234,2607799,62051033,45381876,22766820,16567550,71083565,70782102,99917599,68316156,44664587,63506281,29635537,52613508,10264691,6521313,17386996,17081350,66667729,55669657,78884452,14220886,16405341,73109997,38341669,48893685,44627776,1569515,44784505,78300864,32426752,87720882,46540998,85571389,44060493,64055960,34667286,9829782,26863229,98943869,92530431,96709982,32151165,32058615,77694700,91831487,34295794,72358170,22200378,13819358,26507214,89996536,68041839,97057187,86118021,62357986,74452589,76315420,93359396,7066775,99971982,92787493,30811010,51315460,37501808,99729357,55850790,22721500,9058407,88698958,71965942,48675329,4508700,6525948,66116458,1936762,176257,50007421,5073754,36933038,24915585,61741594,61928316,54263880,10453030,41442762,62762857,17037369,45990383,1375023,83133790,17857111,22113133,62552524,58224549,6038457,91141395,68110330,84166196,97379790,45049308,34946859,69255765,42692881,89699445,83747892,21397057,36396314,76825057,47893286,2331773,26397786 +66667729,72373496,2917920,69136837,14731700,4668450,22879907,35192533,52204879,61373987,93933709,1197320,93053405,18411915,66250369,59329511,14723410,36505482,12664567,62496012,66611759,97783876,33304202,34946859,83210802,76330843,99549373,45794415,526217,30543215,41092102,69355476,64157906,247198,1788101,48893685,72777973,37620363,81853704,40152546,24591705,95395112,58180653,112651,73786944,61859581,91240048,55770687,30891921,18783702,85242963,54232247,55189057,22129328,79821897,36780454,26292919,17386996,29029316,68824981,51047803,19272365,57248122,58751351,99917599,69641513,29834512,63372756,68102437,95010552,49271185,54606384,40984766,84684495,90061527,63059024,44177011,8055981,29188588,61960400,77312810,35456853,36933038,60430369,98943869,40027975,52613508,82897371,6808825,20486294,44842615,40781449,26776922,42692881,81898046,17894977,49641577,5267545,30463802,19891772,26139110,78785507,72278539,54427233,70004753,30998561,73031054,50702367,26744917,22200378,4508700,1204161,7011964,76315420,25842078,44983451,66137019,59501487,17146629,92033260,51472275,67281495,48774913,34698428,3294781,15535065,48360198,48892201,39553046,50007421,18504197,54199160,39986008,53648154,8099994,59197747,13470059,55753905,88698958,26664538,53632373,34044787,77072625,63015256,8129978,92692283,87598888,38061439,96193415,99658235,95290172,66319530,19376156,99515901,78884452,43376279,75777973,46870723,22997281,44481640,49598724,7300047,73124510,33237508,61271144,7955293,10358899,14326617,4099191,65074535,59910624,66045587,62430984,55247455,176257,75128745,68694897,51464002,61263068,62803858,68316156,34295794,874791,15902805,95957797,65038678,6038457,68000591,62490109,65851721,92398073,2204165,98130363,34493392,4787945,56461322,36312813,65338021,43501211,61741594,84293052,9886593,44348328,35512853,90013093,3509435,40534591,415901,62452034,57241521,96726697,35092039,99965001,45790169,93566986,45381876,60106217,89078848,47708850,60102965,29994197,23569917,83651211,23194618,55470718,55850790,1375023,37501808,91664334,3233084,32426752,61712234,98948034,36685023,56515456,168541,21070110,68644627,1431742,33699435,7646095,30218878,3088684,5111370,21919959,10366309,64602895,55669657,669105,5832946,65271999,321665,59318837,17976208,59614746,72738685,19898053,61815223,4978543,43152977,24915585,65715134,54517921,79922758,72274002,97379790,17957593,91990218,29819940,26392416,38645117,7423788,14626618,92787493,41442762,78766358,47792865,4064751,62115552,86460488,44889423,95581843,50567636,30569392,43933006,67451935,30771409,13862149,77787724,83269727,86301513,7919588,77620120,50188404,82427263,83150534,18663507,68204242,11365791,54663246,99604946,94076128,58224549,67793644,8729146,36139942,53666583,9599614,47213183,64087743,30366150,98371444,54868730,74357852,86821229,8634541,33797252,31126490,47361209,29065964,48675329,60176618,41245325,38658347,72357096,44844121,9829782,79191827,24058273,12303248,22405120,80251430,3235882,26863229,53084256,32250045,88047921,96735716,98462867,70800879,82178706,91727510,15064655,40686254,89217461,22113133,10597197,98739783,31904591,16445503,99690194,86001008,82651278,42644903,53802686,22450468,5822202,17081350,96318094,84840549,77694700,14947650,30139692,19101477,44473167,25636669,73392814,58749,29959549,93790285,99524975,64098930,57240218,24826575,8651647,52261574,73021291,45667668,9259676,47893286,96420429,51149804,70372191,72725103,17764950,97641116,69786756,98648327,81677380,70036438,75229462,62051033,92692978,62740044,70596786,90272749,42073124,27665211,72614359,85117092,8733068,74743862,60955663,10961421,83302115,16405341,33628349,99297164,61141874,82339363,6153406,16424199,15148031,91141395,42947632,39801351,80246713,51588015,40677414,85116755,77898274,67227442,63628376,24168411,66832478,78602717,42307449,44784505,33249630,2331773,75986488,10649306,62552524,61982238,97561745,99226875,99333375,11757872,65275241,12024238,45617087,29466406,23848565,57020481,72793444,33061250,36930650,19939935,5482538,45237957,62232211,85711894,51590803,54058788,49328608,81774825,90090964,42967683,9175338,26397786,17016156,44664587,55143724,37183543,24733232,33431960,64848072,94539824,86022504,44245960,45075471,94090109,89419466,17385531,20122224,34667286,43506672,15536795,80316608,56955985,67513640,3773993,22766820,94360702,93057697,74724075,81855445,36808486,749283,59371804,78909561,18131876,62428472,43322743,9623492,69848388,66428795,26275734,76825057,68939068,17365188,26998766,91938191,17727650,88904910,92071990,80014588,67084351,59109400,8807940,91831487,90310261,13231279,69255765,93270084,76703609,9603598,3880712,16684829,39171895,86242799,22435353,37891451,77284619,86543538,28796059,47090124,76434144,99971982,55960386,44479073,92604458,18699206,88653118,73222868,48673079,65017137,30764367,68041839,68110330,37224844,82886381,5640302,70727211,9575954,47887470,73168565,71657078,22108665,43045786,37659250,4806458,57658654,31727379,85571389,34698463,84187166,44627776,56531125,91281584,16099750,7687278,78549759,77187825,63967300,56473732,85695762,2717150,55615885,70420215,89214616,44847298,33565483,92541302,84349107,20568363,86118021,8373289,68816413,99861373,14363867,45306070,80555751,48260151,48395186,88251446,2208785,49882705,40197395,71469330,77300457,39847321,71083565,99021067,54263880,95726235,7517032,84166196,77377183,16595436,36189527,45428665,33895336,84904436,32274392,36845587,15948937,29510992,75135448,78589145,27411561,26063929,17539625,81274566,90004325,59981773,4515343,34236719,6986898,20885148,62693428,23110625,71522968,71333116,76540481,32699744,75153252,93515664,1936762,54762643,90654836,12416768,44915235,72019362,68702632,61859143,93359396,82979980,38494874,29516592,24953498,9257405,97940276,17068582,41481685,4069912,60581278,1250437,83368048,48658605,6871053,3183975,28928175,83747892,30694952,54014062,78300864,36753250,92554120,12348118,569864,35996293,13173644,53842979,77301523,5073754,62936963,28550822,21993752,63506281,30090481,20140249,74137926,12571310,46851987,62762857,14220886,42782652,33336148,13468268,79657802,96906410,93562257,21673260,56484900,11161731,70367851,16971929,22721500,71316369,16054533,66885828,72732205,94516935,37675718,38008118,72238278,67474219,17037369,42199455,27041967,16567550,31161687,67031644,99729357,50806615,66322959,33201905,92803766,4798568,3233569,90457870,40781854,4091162,66903004,37435892,57163802,4095116,67030811,32058615,5970607,89811711,89637706,6157724,36396314,38365584,37739481,76960303,76170907,53393358,36812683,8791066,20002147,82726008,27185644,45049308,75820087,10309525,78218845,6497830,22942635,97057187,38022834,72495719,36135,73235980,1022677,46540998,10453030,4985896,20642888,57961282,56153345,53547802,11923835,88444207,49236559,45996863,14093520,97011160,20681921,26734892,16380211,20645197,24619760,6793819,53888755,16097038,30395570,18833224,36580610,7182642,6525948,91802888,39373729,38510840,52293995,73617245,28787861,23134715,61728685,6111563,26493119,34497327,87710366,82779622,51315460,91957544,39201414,9860195,84002370,47738185,55602660,29635537,82532312,8128637,99224392,58208470,15015906,8913721,37192445,87160386,8115266,79136082,42237907,99125126,75543508,94911072,32161669,59177718,1569515,6819644,45407418,34432810,71965942,83948335,28734791,72358170,11543098,30653863,14045383,45919976,68128438,18466635,83083131,66116458,27325428,6505939,38341669,4204661,32590267,79880247,38009615,40865610,13819358,84406788,57359924,23432750,7066775,74614639,18806856,20867149,26507214,98653983,96709982,38256849,33123618,4119747,74441448,45995325,57803235,9058407,28851716,78561158,74110882,81805959,51507425,23740167,61623915,81172706,88130087,50668599,52060076,6221471,7502255,19486173,49597667,65454636,824872,16019925,69579137,21001913,65880522,44550764,60393039,17058722,92530431,27187213,26102057,66663942,9188443,48088883,89046466,17857111,51715482,79738755,65047700,8696647,59405277,77413012,24314885,31733363,26114953,44060493,86798033,75052463,20836893,20765474,83155442,62357986,14349098,83533741,75407347,71920426,51426311,10264691,83133790,87720882,9860968,30163921,21397057,41380093,54987042,77272628,19457589,2607799,27625735,73109997,89499542,41092172,15163258,30811010,71300104,2891150,94595668,15075176,4199704,37280276,3633375,7104732,7685448,4434662,95251277,3487592,21533347,89996536,44846932,47298834,13348726,40356877,32151165,45848907,65081429,57393458,32159704,21289531,29401781,16387575,508198,69697787,79942022,33553959,92353856,11581181,79806380,51466049,56424103,84127901,91255408,9398733,94711842,31491938,6521313,74452589,29932657,45990383,96852321,82052050,61928316,70541760,92215320,68875490,37166608,79322415,37957788,18001617,43357947,83789391,7229550,1239555,85023028,69605283,92998591,66271566,70782102,67124282,61897582,63152504,37560164,90158785,64055960,76671482,89804152,82327024,33935899,36468541,30787683,92867155,97281847,89699445 +81855445,24591705,98739783,36753250,45428665,97281847,8733068,9058407,37739481,68204242,30569392,10366309,72274002,93562257,6793819,42073124,8696647,98948034,92398073,30771409,42782652,50702367,11161731,61728685,16971929,49271185,18663507,93933709,66322959,19939935,66045587,89214616,56473732,34295794,33895336,86001008,27625735,93053405,85116755,62496012,20486294,34698463,89699445,19486173,95290172,38061439,18806856,88653118,16445503,9623492,54427233,96193415,73168565,35996293,68816413,33553959,52293995,91664334,83210802,65715134,99604946,45995325,36312813,57248122,72777973,3880712,15535065,4119747,1788101,16380211,61859143,73031054,2208785,17539625,8807940,63967300,6497830,2917920,40027975,72357096,63506281,77620120,51472275,39553046,4099191,67281495,15064655,62552524,71657078,22942635,40781449,11365791,31904591,54014062,29188588,40686254,37620363,92033260,70727211,54663246,87720882,61897582,70004753,71920426,14349098,61263068,59501487,37183543,9398733,62452034,30543215,36685023,20765474,75135448,98371444,16097038,62740044,26392416,53666583,3088684,20140249,18833224,70372191,62430984,45617087,68041839,18783702,70036438,38009615,19891772,30366150,50188404,8115266,7955293,60102965,24619760,9599614,59910624,98648327,77272628,88047921,43501211,36812683,12348118,9829782,47298834,67030811,90013093,23134715,33201905,55247455,95395112,22721500,51047803,44842615,14947650,78766358,61271144,30998561,92867155,54987042,55960386,17764950,77284619,39171895,73786944,30891921,2331773,40677414,97379790,89804152,74452589,44889423,28796059,95010552,79738755,24826575,34493392,80316608,62803858,76434144,9603598,17037369,43933006,88904910,65851721,47090124,3233569,2717150,526217,29959549,53648154,41442762,26776922,76540481,81853704,73392814,87160386,55602660,5111370,47708850,36505482,26493119,3487592,29819940,11923835,82979980,86460488,99861373,35192533,89078848,67513640,44177011,26863229,48260151,96318094,72358170,49882705,53547802,59197747,75153252,69786756,2607799,14093520,29932657,41092102,27665211,22405120,99917599,99658235,47887470,67124282,33431960,15948937,18466635,97783876,62693428,68644627,75543508,32590267,3773993,49641577,24058273,57961282,47361209,59109400,112651,73222868,48360198,64098930,84840549,6505939,42692881,2204165,4515343,56424103,89046466,6111563,91802888,29994197,99515901,62936963,33304202,58180653,3235882,92353856,7502255,54762643,32426752,69848388,94595668,78561158,9886593,96726697,99549373,55770687,69255765,38645117,71083565,79136082,38365584,85023028,99333375,72793444,63152504,82897371,247198,99125126,37166608,83155442,16099750,36930650,57803235,27185644,76330843,508198,46870723,65338021,27411561,13470059,49236559,55189057,55753905,22450468,51466049,26664538,38658347,42307449,4668450,34044787,37501808,9259676,83789391,1239555,17058722,45306070,61960400,5822202,56955985,1204161,26397786,80555751,4199704,8651647,37675718,39986008,73617245,61859581,37957788,71522968,90004325,14326617,90457870,36468541,1375023,26507214,61815223,51507425,40356877,41245325,95726235,37435892,51590803,55143724,17016156,96709982,33628349,43322743,30653863,83368048,53842979,56461322,4787945,176257,27325428,43045786,17068582,8099994,22435353,99729357,90090964,29834512,92215320,57393458,9860968,67451935,98130363,10649306,5482538,78218845,94090109,71469330,13468268,6153406,65271999,66903004,32699744,71333116,70367851,84187166,38022834,17727650,72725103,49597667,24314885,66137019,30764367,74110882,66663942,45667668,47792865,18001617,77187825,40984766,34698428,23569917,21533347,82178706,77312810,16054533,24733232,36780454,26102057,92998591,8055981,77787724,67474219,26734892,62490109,94539824,68128438,82886381,15163258,6819644,6521313,8634541,16405341,96852321,77694700,10961421,62115552,75052463,40781854,7423788,68824981,64087743,72738685,29065964,45919976,58749,79657802,21001913,69136837,99965001,63372756,824872,66832478,51464002,92071990,3233084,25636669,12416768,30218878,19898053,23110625,21070110,6808825,99021067,32161669,49328608,67793644,91957544,54232247,68102437,84684495,59177718,68875490,81805959,59329511,9575954,98462867,13231279,36580610,5970607,67227442,8128637,16019925,60955663,83651211,79806380,52261574,17857111,96906410,91727510,58208470,72495719,78602717,45407418,45790169,9257405,76960303,3633375,415901,19376156,62232211,93515664,34432810,669105,84293052,80246713,68316156,168541,4978543,56515456,60430369,70800879,78300864,28928175,53802686,15148031,34497327,95581843,6525948,63059024,41481685,44481640,1569515,65017137,22766820,34236719,30163921,79821897,569864,33797252,26275734,89811711,57241521,30090481,53888755,15075176,33249630,87598888,1431742,55470718,85711894,20122224,33123618,35456853,81898046,59371804,97940276,84904436,66250369,45996863,44983451,44784505,59318837,71300104,91831487,90158785,18699206,51426311,38494874,30395570,16387575,93057697,80251430,23740167,92787493,51149804,28734791,61712234,42237907,85117092,86242799,32159704,64157906,19101477,66611759,91141395,73124510,89637706,97011160,82726008,32250045,70420215,30694952,74724075,2891150,4806458,40197395,83133790,22997281,8373289,7104732,99224392,76671482,45237957,4095116,68702632,19457589,3294781,35512853,22200378,17081350,75986488,89996536,91281584,31126490,13173644,50668599,89419466,7229550,10358899,88444207,874791,99971982,45075471,67084351,83533741,39847321,98653983,23194618,48395186,72278539,12571310,47213183,92803766,72238278,48892201,30139692,90310261,75777973,19272365,44479073,29029316,52060076,10453030,65880522,86301513,48658605,61141874,78785507,32274392,40865610,16567550,77413012,4798568,10309525,63015256,1197320,77301523,51315460,91990218,73235980,37891451,3509435,36189527,44550764,77072625,54058788,7517032,95957797,37560164,56484900,83150534,9860195,43357947,33061250,20002147,92692283,89217461,99297164,31491938,90272749,52204879,74137926,61982238,5832946,84406788,45848907,61741594,84166196,54517921,97561745,58224549,6038457,38008118,45990383,49598724,8129978,10597197,94711842,7011964,23848565,44473167,66667729,70596786,77898274,34946859,96735716,75407347,56531125,29635537,89499542,17957593,64602895,47738185,75820087,22879907,6986898,7182642,16595436,71316369,69605283,84002370,72614359,50567636,48088883,86022504,7685448,78589145,51588015,94076128,36933038,53393358,64848072,46851987,22113133,45381876,7687278,56153345,76703609,62762857,68939068,57163802,36808486,83302115,18504197,48675329,57240218,14220886,20642888,31733363,20836893,92692978,57020481,66319530,30463802,99524975,4069912,38510840,41092172,85242963,33699435,39801351,27187213,50806615,39201414,41380093,61623915,42967683,51715482,65074535,43376279,16424199,69641513,44627776,30787683,4204661,1250437,45794415,11543098,31161687,83083131,54263880,91240048,65454636,87710366,30811010,21397057,76170907,97641116,20885148,90654836,82532312,21993752,74441448,80014588,26114953,77300457,91255408,61928316,48673079,54199160,83948335,53084256,94911072,26998766,85571389,66271566,86821229,54606384,7646095,33935899,60393039,65275241,85695762,17386996,48893685,40534591,44846932,14045383,24168411,58751351,55615885,50007421,20645197,17146629,98943869,26744917,14626618,59614746,33237508,93790285,6221471,69697787,73021291,26063929,81172706,82779622,42644903,92541302,76825057,37192445,22108665,72019362,74743862,5267545,38341669,65038678,70541760,42199455,94360702,8913721,36139942,7066775,66885828,46540998,37280276,93566986,20867149,79191827,60176618,69579137,5640302,18131876,92604458,99226875,13819358,78884452,43152977,20681921,65047700,48774913,57359924,76315420,53632373,28851716,61373987,35092039,8729146,57658654,20568363,3183975,82427263,25842078,82339363,60106217,94516935,43506672,28550822,55669657,13348726,29401781,15536795,36135,33336148,66116458,63628376,17894977,14723410,44664587,81774825,88251446,99690194,62428472,44245960,65081429,75229462,6871053,88698958,18411915,33565483,21673260,9188443,4091162,40152546,92554120,66428795,88130087,44348328,68000591,92530431,36396314,83269727,24915585,47893286,64055960,44847298,4508700,54868730,22129328,75128745,91938191,45049308,1022677,86543538,59405277,74357852,73109997,28787861,72732205,78909561,69355476,10264691,12024238,9175338,17976208,11581181,86798033,52613508,72373496,83747892,95251277,34667286,62357986,42947632,93359396,27041967,79880247,60581278,26139110,17385531,29466406,81677380,32058615,1936762,14731700,84349107,14363867,62051033,24953498,321665,15015906,44060493,82651278,31727379,55850790,38256849,79322415,84127901,86118021,17365188,68694897,7919588,8791066,96420429,82052050,44844121,77377183,749283,29510992,82327024,79922758,78549759,4434662,97057187,5073754,4985896,32151165,12303248,71965942,79942022,68110330,37224844,26292919,90061527,37659250,93270084,67031644,70782102,74614639,16684829,81274566,4064751,39373729,21919959,12664567,21289531,23432750,15902805,29516592,44915235,59981773,11757872,7300047,6157724,13862149,36845587 +4668450,60430369,54199160,14326617,19376156,70420215,89214616,67793644,56955985,59501487,50567636,96193415,32590267,84684495,77072625,68644627,78602717,91957544,13470059,54606384,36812683,22108665,99549373,33699435,66611759,7685448,88444207,92692978,44889423,17386996,4119747,59177718,38365584,73168565,73617245,27625735,5970607,6819644,19939935,31904591,79821897,93933709,60581278,67227442,92398073,48892201,20002147,91727510,72373496,94076128,70372191,59371804,48260151,42692881,569864,93515664,15535065,54427233,75153252,33237508,20765474,20122224,37659250,94711842,95957797,81677380,68110330,874791,526217,16380211,48360198,72274002,13862149,26139110,53393358,60102965,6793819,99021067,86460488,39553046,59614746,63015256,89637706,71316369,19272365,43045786,34295794,99658235,88047921,20642888,14626618,75986488,47361209,78766358,62452034,18783702,29188588,6038457,35192533,83210802,61623915,8099994,74743862,16445503,63152504,93790285,20568363,30998561,68041839,99297164,24058273,30543215,44983451,57020481,24591705,56461322,18833224,49598724,73124510,18663507,30764367,67030811,17976208,27665211,70541760,36685023,26102057,69136837,2208785,36505482,63967300,18466635,51507425,44245960,62430984,75128745,51047803,94516935,11161731,54762643,53802686,33304202,17081350,1431742,99333375,66271566,16424199,19891772,71657078,31126490,77787724,28928175,68316156,30787683,49236559,31733363,62496012,2917920,48088883,37620363,44847298,55753905,321665,73222868,76960303,6153406,14093520,38061439,38494874,73235980,37675718,37183543,20885148,9575954,16567550,33061250,3233569,82178706,36930650,6871053,92787493,59910624,61960400,50188404,17764950,85116755,72777973,44348328,33895336,54058788,7229550,39986008,92604458,79806380,72614359,3633375,74110882,53084256,5111370,72732205,82327024,67513640,6808825,15075176,33628349,30891921,26744917,89804152,31161687,26063929,85242963,95010552,28796059,68875490,72278539,43322743,88653118,45049308,30463802,4798568,72738685,30218878,95395112,13231279,68702632,12416768,22450468,99861373,12348118,59109400,71920426,11581181,92071990,47213183,87598888,88904910,26493119,44481640,78549759,75777973,8733068,86242799,82897371,97783876,79880247,74441448,15536795,63628376,51464002,80316608,45790169,77620120,7300047,78785507,77272628,44550764,47887470,20140249,20867149,84127901,61712234,55850790,14220886,99515901,3235882,43501211,26392416,59197747,20486294,7687278,1022677,33797252,64087743,36753250,82532312,97641116,84293052,72357096,29834512,65074535,93053405,4806458,60176618,14731700,3183975,65017137,52204879,49328608,66667729,65454636,6986898,77694700,37891451,54263880,56484900,95290172,70004753,40781449,53632373,40197395,21919959,29994197,68816413,57248122,4978543,22405120,95726235,21673260,68824981,36780454,35092039,98130363,72019362,67124282,80014588,66250369,56153345,98371444,51472275,30139692,64098930,72725103,66903004,3880712,87720882,18806856,61373987,42307449,81853704,54014062,34946859,87160386,22435353,77312810,55247455,66663942,55189057,41481685,61263068,75135448,47738185,89699445,99226875,51590803,22766820,10309525,17068582,36139942,66045587,3088684,44844121,84406788,51466049,46870723,75543508,99917599,5073754,93566986,1239555,1250437,6505939,34667286,69848388,70367851,34236719,63506281,4515343,48673079,9603598,78884452,17146629,91255408,34698428,65880522,18504197,74357852,9175338,9259676,89046466,29466406,4204661,30366150,1788101,54987042,34698463,23569917,2891150,62936963,61859581,91990218,97011160,76434144,41245325,9623492,83155442,77300457,19898053,7423788,1197320,508198,90090964,38658347,11365791,41380093,93359396,65338021,78300864,39171895,61897582,17857111,30771409,8128637,26114953,17539625,43506672,70800879,20681921,8634541,15148031,49597667,56531125,46851987,7011964,62051033,44473167,44784505,71083565,80555751,67281495,68204242,91281584,71300104,10597197,74137926,40677414,14947650,66322959,7955293,168541,32699744,59318837,63059024,57393458,52261574,21993752,91664334,53888755,86798033,76671482,4099191,13468268,64157906,73031054,71469330,90457870,10366309,79136082,48893685,92554120,51149804,36312813,17058722,18131876,83150534,45996863,21001913,84166196,76170907,67031644,27411561,8373289,4069912,84349107,99965001,6221471,9398733,77301523,82779622,73786944,50007421,29959549,59981773,92353856,56515456,51715482,7919588,82886381,40865610,93562257,247198,65271999,10358899,40781854,90061527,62115552,61271144,98739783,96906410,3294781,82726008,81274566,73021291,56473732,10961421,48658605,32161669,1569515,86118021,65038678,55143724,55602660,99729357,65851721,61141874,66137019,749283,69605283,53648154,15015906,6525948,62803858,49271185,70596786,83789391,1204161,44842615,59329511,24619760,5822202,72358170,79657802,54517921,36580610,34432810,20645197,78589145,41092102,24826575,65047700,47792865,45407418,9058407,29635537,824872,85023028,73392814,53547802,48395186,87710366,45919976,17894977,38008118,96726697,6111563,10453030,92998591,28851716,72495719,14349098,44177011,23134715,669105,16099750,71965942,6497830,98462867,91938191,4091162,29516592,24168411,36933038,40356877,34493392,23194618,77284619,50702367,79738755,9886593,8651647,23740167,3487592,27041967,112651,22113133,37560164,86821229,43357947,82427263,62740044,45075471,8129978,8115266,29401781,75407347,92803766,68939068,40686254,94090109,86543538,96735716,69579137,98943869,37501808,17016156,4064751,83368048,28734791,26734892,49641577,99125126,68128438,77187825,415901,32274392,39801351,99524975,95581843,44664587,43376279,51588015,18699206,13348726,89217461,83747892,80246713,58224549,3233084,77413012,4199704,32058615,45381876,57163802,12664567,8696647,57241521,16684829,65081429,22200378,66428795,17037369,91141395,98948034,92215320,32426752,83651211,44846932,40027975,27185644,45848907,33565483,3509435,24915585,4434662,16405341,40152546,99690194,15163258,78218845,77377183,82052050,60106217,15064655,44627776,42967683,50806615,13819358,96318094,89419466,65715134,12024238,14045383,64602895,61982238,32159704,9860968,16971929,47893286,76330843,83948335,70727211,57240218,71522968,14363867,19457589,81805959,76540481,81898046,42782652,90013093,47090124,34497327,81774825,2717150,97281847,70036438,55770687,23110625,32250045,63372756,85117092,8807940,26863229,39201414,86001008,82339363,68694897,91240048,72238278,31727379,47708850,92692283,16097038,75229462,2331773,57359924,45995325,45237957,94360702,7502255,90654836,33249630,69255765,54868730,12303248,53842979,52060076,99604946,36189527,32151165,29029316,78909561,58751351,88698958,86301513,96420429,39847321,43152977,66832478,36468541,33201905,57803235,97561745,97940276,37280276,25842078,25636669,4095116,35456853,29932657,37435892,58208470,61928316,30163921,7104732,76825057,22721500,98653983,17727650,51315460,57658654,64848072,60955663,84840549,92033260,48675329,62232211,21533347,93057697,16054533,26292919,26998766,89996536,92867155,38645117,56424103,74452589,79191827,47298834,8729146,40984766,24314885,54663246,22129328,26507214,69355476,88130087,62490109,15948937,5267545,26275734,55470718,36396314,4508700,4985896,37166608,5832946,90272749,27187213,91802888,43933006,97379790,37739481,96852321,44479073,7517032,40534591,21289531,98648327,42947632,82651278,11923835,17365188,38022834,55669657,1375023,29510992,30090481,2204165,37192445,57961282,76703609,22997281,19486173,35512853,16387575,36135,73109997,37957788,95251277,3773993,51426311,89078848,85571389,46540998,22879907,33123618,80251430,74724075,69697787,5640302,59405277,99224392,18001617,62762857,6521313,90004325,70782102,90310261,42199455,94595668,61815223,55960386,45428665,68000591,45667668,94911072,45617087,50668599,36808486,83133790,2607799,97057187,30653863,78561158,30569392,33336148,85695762,81855445,61728685,26776922,86022504,67084351,9829782,18411915,15902805,45990383,31491938,89499542,83533741,28787861,83269727,29819940,21397057,85711894,66319530,4787945,83083131,5482538,8913721,41092172,88251446,33431960,76315420,84002370,13173644,58749,61741594,9860195,53666583,58180653,26397786,96709982,11543098,8791066,92530431,68102437,38009615,23432750,92541302,84187166,23848565,9599614,84904436,65275241,10649306,19101477,71333116,39373729,27325428,37224844,9188443,45306070,89811711,8055981,35996293,62693428,52613508,38510840,26664538,45794415,33935899,52293995,62428472,22942635,49882705,90158785,17957593,94539824,75820087,10264691,79922758,16019925,62552524,34044787,24953498,81172706,69786756,30694952,74614639,38256849,54232247,75052463,99971982,91831487,66885828,20836893,67451935,30811010,79322415,44915235,79942022,42237907,77898274,38341669,30395570,9257405,62357986,7182642,93270084,6157724,1936762,11757872,42644903,48774913,69641513,29065964,67474219,72793444,21070110,55615885,66116458,83302115,28550822,61859143,12571310,42073124,7066775,7646095,60393039,33553959,16595436,176257,41442762,44060493,82979980,64055960,17385531,24733232,14723410,36845587 +31161687,27665211,2717150,40152546,60430369,82979980,55753905,91664334,12571310,86821229,37183543,83083131,3183975,47361209,92554120,51715482,38494874,70800879,22721500,13862149,35092039,43322743,5970607,49641577,86022504,40356877,77620120,4099191,12348118,29029316,75820087,26493119,75229462,98948034,85571389,75777973,48675329,43357947,96420429,45996863,47090124,66116458,79657802,61373987,90061527,14626618,61982238,33565483,31126490,81677380,11543098,67793644,36580610,17727650,569864,526217,85116755,4434662,91727510,68644627,92215320,88251446,45617087,50702367,99965001,89499542,34698428,22450468,9188443,1022677,76960303,14045383,13231279,77300457,4806458,75153252,66611759,90013093,62496012,4787945,26998766,80014588,7687278,40197395,20867149,87710366,8373289,9623492,15064655,44889423,78549759,49597667,73786944,77284619,19101477,30163921,168541,15536795,54663246,10453030,99224392,39847321,17386996,51047803,70727211,28851716,59177718,66250369,49236559,16380211,37435892,824872,38008118,74357852,22766820,59501487,60102965,78589145,61859143,66832478,98130363,29401781,55615885,32161669,76315420,1250437,45237957,1569515,63059024,65275241,34698463,73617245,82886381,79922758,89811711,96193415,6038457,6986898,75407347,20836893,2208785,44842615,36780454,73235980,73124510,54606384,60955663,8055981,89214616,41481685,68316156,47708850,27325428,15015906,67474219,25842078,39986008,35456853,79136082,72725103,19486173,65454636,77072625,50567636,38341669,20122224,30090481,19939935,26507214,89699445,37620363,6808825,62452034,44844121,54232247,76540481,55247455,91957544,74743862,62740044,57240218,29510992,99549373,99226875,36808486,95290172,74110882,81855445,45306070,38256849,58751351,85023028,43045786,92398073,53888755,94539824,96735716,64098930,83155442,84166196,33628349,11923835,79821897,62357986,71920426,96726697,23848565,36753250,65047700,55143724,92998591,39201414,29819940,33336148,28928175,5832946,88653118,8913721,97561745,81172706,68000591,71333116,88047921,50806615,1239555,30543215,83651211,67124282,67281495,72274002,64848072,69255765,74137926,3487592,22435353,26776922,89078848,61741594,54517921,20140249,78602717,45407418,57803235,21993752,22997281,10309525,67084351,92692283,74614639,70596786,9886593,44983451,53802686,63372756,46870723,16971929,79880247,61712234,24058273,1431742,38645117,54199160,52261574,4668450,9860968,29932657,53547802,92071990,61263068,28550822,84187166,47213183,39801351,99604946,33304202,73031054,92033260,14220886,62051033,61271144,65081429,34236719,15535065,52293995,82339363,54427233,26063929,59614746,66322959,83210802,99861373,14326617,14947650,76170907,42644903,18806856,30653863,54014062,73222868,54058788,56473732,98648327,8807940,96318094,24915585,8099994,96906410,2891150,44348328,17539625,40781449,70541760,20002147,78218845,8791066,32699744,14093520,46540998,82726008,17016156,92353856,26114953,33201905,38365584,29834512,49882705,84840549,32590267,69641513,7300047,77272628,20642888,247198,14363867,72357096,39373729,29994197,9398733,82779622,66885828,51464002,30764367,68128438,6497830,36505482,80251430,50188404,90272749,7066775,48360198,18783702,53648154,63967300,78785507,61623915,33553959,9575954,34295794,12024238,45794415,77187825,89804152,6157724,6793819,74441448,37192445,81774825,66428795,95957797,91831487,14731700,4064751,36468541,5111370,48892201,33237508,99971982,32159704,40677414,93057697,93562257,36812683,36139942,19898053,93566986,17976208,75543508,26102057,40984766,37739481,28734791,71657078,68875490,53666583,20885148,91802888,33431960,17764950,15948937,93515664,23110625,67031644,56461322,86301513,65880522,99658235,90457870,49271185,4119747,70420215,9603598,31733363,65271999,14349098,44550764,89046466,51588015,82897371,37560164,45428665,62115552,77413012,16445503,9860195,70367851,20765474,26392416,33797252,6871053,21919959,89419466,10264691,60581278,36685023,55470718,29516592,87160386,874791,99125126,19376156,34667286,22879907,4798568,59318837,66045587,76671482,76703609,3233569,8115266,23569917,73109997,82052050,97281847,31904591,6521313,96852321,75128745,48658605,59197747,44245960,30998561,80555751,90004325,62430984,22108665,10649306,7011964,81898046,508198,44473167,7229550,48260151,65715134,15163258,56955985,24591705,34044787,59371804,9175338,23194618,69136837,36135,26292919,36845587,64602895,75986488,65338021,20568363,38061439,85242963,13819358,2917920,99297164,7423788,17068582,321665,44784505,34497327,61728685,84406788,55602660,5640302,99021067,4095116,76330843,71300104,29635537,83302115,17037369,84684495,68702632,32151165,54868730,72614359,24826575,6153406,3233084,94090109,67030811,91938191,72278539,83133790,24619760,73168565,36312813,62936963,1197320,56515456,70372191,57020481,1375023,29959549,55669657,2607799,92604458,52613508,18699206,47792865,72373496,90310261,9599614,4069912,43933006,12416768,57248122,5482538,80316608,1204161,66667729,3633375,30771409,42199455,79322415,57658654,32250045,45848907,21673260,45075471,63015256,30694952,7685448,88444207,76825057,20486294,43376279,32426752,30787683,92803766,95010552,27411561,26744917,18833224,44481640,62803858,11365791,58224549,35512853,21289531,44479073,16019925,97011160,3088684,20681921,42073124,84904436,82532312,86543538,66271566,53842979,3880712,29466406,65017137,415901,44177011,85117092,92541302,52204879,53632373,67451935,68694897,10961421,57163802,57961282,71083565,62490109,56424103,64157906,93270084,85711894,4091162,9259676,47738185,54762643,69579137,97940276,86460488,27625735,29188588,3294781,94711842,44846932,81274566,83368048,81853704,72019362,36396314,46851987,68824981,7955293,61859581,6221471,7646095,3509435,98739783,97057187,94516935,1788101,65851721,17146629,45990383,10358899,26734892,3235882,17081350,22200378,71316369,36930650,11757872,58180653,33895336,54987042,36189527,6525948,95726235,60106217,30569392,15075176,9058407,62552524,72732205,30463802,35192533,30891921,4515343,39553046,58749,41092102,51472275,29065964,69355476,83150534,60176618,73021291,19457589,69697787,35996293,22113133,77301523,51466049,97783876,37675718,26863229,669105,64087743,57359924,32274392,86001008,52060076,78766358,65074535,90158785,58208470,92692978,56531125,99515901,84293052,68939068,83747892,91255408,84127901,59329511,66903004,48774913,48088883,63506281,93790285,78561158,82651278,6505939,51426311,68102437,37224844,94911072,18663507,17894977,57241521,4204661,43501211,18411915,7502255,33935899,45790169,55189057,63628376,37166608,16595436,26139110,44060493,84349107,17857111,19891772,16684829,11161731,24953498,112651,79806380,33061250,67227442,89996536,22405120,88904910,83533741,8733068,45667668,16424199,76434144,17385531,25636669,57393458,99917599,68204242,8651647,45995325,49328608,4508700,34432810,48673079,44627776,47298834,34493392,47887470,90090964,89637706,48893685,83269727,42237907,93933709,44915235,66137019,6819644,38658347,59910624,8129978,7182642,41442762,78300864,80246713,30811010,71469330,18001617,33249630,3773993,5822202,30366150,61141874,91281584,7919588,23740167,83948335,38510840,81805959,62428472,72793444,71522968,75135448,16567550,15902805,98462867,8634541,40865610,87598888,27187213,56484900,68816413,69786756,79191827,83789391,50668599,17058722,77312810,94595668,99524975,61815223,24168411,40781854,72777973,55770687,88130087,38009615,38022834,40686254,92530431,82178706,92787493,9257405,98371444,31491938,93359396,70004753,43506672,68110330,86798033,55960386,62762857,95251277,97379790,91990218,75052463,82427263,77787724,86118021,93053405,21070110,94076128,70782102,61928316,51590803,45919976,77898274,73392814,5267545,42967683,18131876,2204165,30218878,95395112,63152504,19272365,7104732,21533347,33123618,59109400,15148031,92867155,69848388,45049308,51315460,22942635,4199704,749283,55850790,53393358,13348726,32058615,6111563,84002370,16405341,72358170,34946859,77694700,28796059,98943869,42307449,42782652,91141395,26664538,4985896,16099750,9829782,18504197,70036438,49598724,61960400,16097038,72238278,50007421,27185644,62693428,8696647,37891451,41245325,37659250,13468268,37501808,21001913,13173644,59405277,24314885,72738685,65038678,68041839,39171895,74452589,59981773,24733232,11581181,51507425,40027975,27041967,20645197,42692881,56153345,16054533,71965942,77377183,33699435,8128637,79942022,97641116,51149804,16387575,61897582,30139692,23134715,89217461,12664567,28787861,87720882,42947632,78884452,99690194,86242799,40534591,18466635,48395186,31727379,44847298,10597197,41092172,26397786,10366309,47893286,13470059,14723410,17365188,41380093,62232211,37280276,96709982,94360702,88698958,8729146,54263880,30395570,66663942,176257,17957593,5073754,72495719,2331773,22129328,43152977,78909561,64055960,99333375,7517032,36933038,66319530,99729357,26275734,95581843,12303248,37957788,79738755,60393039,85695762,1936762,98653983,90654836,4978543,91240048,67513640,74724075,21397057,45381876,23432750,44664587,69605283,82327024,53084256 +62051033,53802686,65851721,37620363,89046466,52204879,92398073,31904591,43376279,33628349,29834512,61373987,20486294,62936963,99658235,3509435,26392416,59197747,65338021,57248122,96193415,82339363,86118021,89214616,73617245,24915585,74724075,52261574,91802888,99515901,99917599,69355476,37560164,34493392,78766358,66667729,66611759,38658347,69786756,90457870,84127901,51464002,71316369,67793644,69255765,85116755,39373729,64602895,874791,84349107,73222868,4099191,10366309,49328608,98948034,16054533,35092039,2204165,57803235,22766820,2917920,73235980,57020481,39847321,16099750,11365791,66271566,33935899,55247455,4119747,7300047,3233084,45990383,66137019,59614746,66903004,57240218,7687278,4668450,71333116,64087743,10309525,84684495,23194618,92033260,29188588,17081350,20867149,87598888,29994197,27187213,65271999,66250369,1022677,44889423,75229462,44550764,35192533,36930650,6505939,73168565,81274566,69848388,12348118,61263068,26744917,24168411,1431742,53393358,97641116,68000591,51047803,78549759,66045587,40865610,34295794,49641577,60430369,37739481,71657078,89217461,14326617,3880712,71920426,12303248,39553046,77187825,79806380,37659250,1204161,45848907,63967300,21533347,22879907,45237957,96726697,6793819,247198,24058273,87160386,33304202,37675718,44844121,8129978,93053405,44842615,96906410,68875490,67030811,56153345,61141874,72373496,78884452,28796059,8729146,65074535,36139942,30463802,55143724,74137926,38494874,40534591,22113133,99297164,68816413,77272628,55470718,36812683,1569515,55850790,47298834,26734892,76434144,60106217,78909561,112651,73021291,40677414,83651211,52293995,49597667,66319530,39171895,24619760,68316156,90272749,1239555,62693428,29819940,72777973,20568363,37183543,94711842,29029316,69136837,26998766,79821897,19486173,75777973,53842979,38061439,43357947,18699206,16387575,61623915,95957797,6808825,60581278,26863229,92867155,44627776,38341669,57241521,2891150,88653118,5073754,91938191,48673079,21001913,71083565,53666583,97379790,15948937,18783702,53632373,59371804,34497327,55669657,70782102,3487592,47361209,63372756,93790285,73392814,46851987,65275241,93515664,37891451,91240048,60176618,33699435,73124510,98943869,45996863,21289531,40686254,6525948,17058722,30998561,16019925,14626618,61960400,92530431,86301513,29510992,669105,57359924,37224844,22108665,34044787,82726008,43501211,22942635,508198,50188404,74110882,19376156,15015906,76960303,18663507,51715482,64055960,75543508,44784505,81805959,67451935,6153406,86460488,91990218,47090124,84002370,6986898,19457589,27665211,59910624,168541,16445503,36468541,98130363,89699445,30218878,4515343,26063929,34667286,79922758,7104732,35512853,83155442,68204242,14093520,65454636,17764950,30891921,33249630,81677380,92604458,54199160,61271144,44847298,18833224,83789391,20885148,75128745,34236719,10358899,36312813,31161687,81898046,9860195,41481685,15535065,66832478,56531125,58224549,44177011,92215320,83368048,51149804,91281584,65017137,81855445,17365188,93566986,92787493,6038457,86001008,83210802,3183975,93270084,45075471,92541302,72019362,30366150,20645197,88047921,10453030,99021067,46540998,33895336,64098930,66116458,13862149,61728685,12024238,82327024,66885828,96735716,82886381,6497830,44473167,73031054,20002147,95726235,31727379,62452034,76540481,48774913,77694700,43506672,1788101,98653983,62430984,78218845,90090964,17727650,32274392,68702632,88444207,52613508,7502255,53648154,22450468,49882705,67124282,8651647,57393458,24591705,77284619,77413012,49598724,79738755,47887470,16971929,99224392,21993752,65047700,83083131,94090109,30694952,42199455,7919588,85023028,9259676,42782652,44983451,89419466,77301523,7646095,97281847,72614359,33237508,99125126,46870723,22997281,18466635,61712234,30787683,37166608,13468268,80014588,72725103,32159704,95251277,33565483,30771409,34698428,72278539,95010552,36780454,26493119,39801351,41245325,20122224,14045383,45381876,97561745,69697787,65038678,4806458,87720882,50668599,13470059,47708850,19891772,8696647,77312810,99226875,76315420,23432750,10597197,5111370,72738685,43045786,3233569,92998591,48088883,55189057,54663246,91664334,82779622,62762857,68128438,75052463,45995325,51466049,51588015,55753905,9188443,44060493,88130087,78785507,23110625,47213183,8373289,2717150,526217,20765474,97940276,99604946,30653863,16405341,92692978,98371444,54987042,36580610,45306070,40781854,16097038,13231279,79880247,56484900,3633375,76330843,98648327,68041839,5822202,23134715,76170907,34432810,60955663,38365584,95395112,86022504,29959549,44846932,92554120,1375023,54606384,86821229,55602660,68694897,77072625,75153252,89499542,26776922,40356877,77620120,91727510,6819644,21070110,84406788,28787861,83533741,59501487,63628376,8128637,36135,16595436,70727211,33061250,94076128,23569917,63059024,54517921,54014062,65880522,62496012,79191827,45794415,41380093,85711894,98739783,67227442,1250437,99524975,54762643,58751351,47792865,97783876,74743862,86242799,824872,27325428,67281495,5482538,44481640,43322743,84840549,72357096,7423788,16567550,9829782,13348726,30139692,78589145,9575954,81853704,93933709,14220886,66322959,47738185,5970607,30163921,50702367,22129328,14723410,11161731,40152546,24826575,83302115,36685023,1197320,30543215,6157724,22721500,82651278,26139110,16684829,36505482,44245960,62232211,4434662,32250045,54058788,51472275,26507214,96852321,54263880,95290172,19939935,3088684,92692283,48360198,77377183,40197395,68644627,56461322,88904910,61859581,60102965,78300864,62803858,2208785,4069912,10961421,47893286,9886593,79136082,14349098,4204661,26275734,13173644,3235882,28928175,21673260,40781449,17037369,31126490,7685448,15902805,72495719,70420215,9257405,41092102,28550822,8791066,70372191,76825057,82427263,81774825,61859143,69579137,35996293,9603598,99971982,62490109,23848565,26664538,27625735,70596786,54427233,99333375,62357986,61741594,48675329,72274002,4064751,44664587,23740167,42307449,32161669,58749,37501808,17068582,18504197,70800879,4798568,3773993,94911072,78561158,17976208,63015256,94595668,65715134,67474219,69605283,18806856,22435353,25636669,6871053,55960386,61928316,17386996,38645117,74614639,24953498,26397786,56424103,17146629,13819358,63506281,66428795,14731700,8099994,8913721,9398733,749283,29065964,37957788,59329511,4199704,26292919,6221471,53084256,38008118,9599614,33553959,71300104,99861373,42947632,45428665,93359396,9058407,62552524,3294781,29635537,82532312,7955293,21919959,57163802,176257,57658654,59318837,91141395,82897371,55770687,17957593,38009615,30764367,84166196,50806615,19101477,74357852,85242963,44348328,94539824,34698463,89804152,16424199,29932657,56473732,41092172,84293052,321665,2607799,43933006,7066775,18411915,8807940,33797252,37435892,73786944,29401781,80555751,4508700,92353856,61982238,9623492,4091162,36753250,93057697,89637706,53888755,65081429,96709982,24733232,59981773,80246713,75820087,64848072,42692881,42073124,80316608,70004753,19898053,7229550,14947650,8055981,68102437,12664567,9860968,89811711,43152977,91831487,59177718,30811010,83133790,17857111,72358170,94516935,36189527,96318094,45407418,62740044,37192445,51426311,40027975,37280276,71965942,15075176,30090481,86543538,70036438,82052050,569864,32426752,75135448,36396314,30395570,20140249,4787945,89996536,83150534,12416768,82178706,67084351,82979980,40984766,51590803,7182642,99690194,18131876,22405120,77300457,5640302,71469330,45667668,68824981,38022834,9175338,98462867,99965001,42967683,89078848,83948335,88251446,50567636,61897582,56515456,48260151,63152504,48395186,74452589,90654836,15148031,17539625,7517032,88698958,93562257,11581181,48892201,85571389,33201905,67513640,77898274,90158785,26114953,31491938,54232247,78602717,5832946,27411561,38256849,77787724,90013093,22200378,15064655,28734791,91255408,64157906,44479073,99549373,97011160,58208470,27041967,83747892,74441448,12571310,61815223,39986008,70367851,48893685,29466406,75407347,53547802,45049308,20642888,62115552,11757872,48658605,8634541,33123618,39201414,50007421,41442762,87710366,62428472,49271185,8115266,33336148,7011964,31733363,69641513,91957544,8733068,76703609,17894977,19272365,6521313,36933038,10649306,6111563,72238278,35456853,99729357,44915235,17385531,56955985,58180653,42237907,86798033,60393039,10264691,15163258,26102057,68939068,59405277,95581843,4095116,4985896,5267545,72793444,25842078,67031644,97057187,36808486,66663942,96420429,32151165,57961282,27185644,85117092,20681921,17016156,51507425,49236559,32590267,55615885,54868730,2331773,32699744,84187166,45919976,68110330,45790169,11543098,76671482,16380211,51315460,70541760,28851716,42644903,90310261,73109997,75986488,79657802,81172706,18001617,415901,72732205,32058615,38510840,52060076,85695762,1936762,80251430,92071990,21397057,94360702,15536795,79322415,84904436,92803766,29516592,4978543,30569392,36845587,24314885,45617087,90061527,59109400,20836893,79942022,14363867,34946859,33431960,11923835,90004325,83269727,71522968 +9398733,63967300,62740044,85023028,42237907,62936963,56515456,77272628,37183543,66045587,19486173,10366309,62430984,78602717,97011160,29834512,73617245,4099191,45428665,28796059,40356877,33797252,38365584,99965001,95957797,38022834,87598888,54606384,48395186,17764950,90457870,99515901,64098930,36930650,55602660,79191827,55247455,7182642,8696647,26744917,52613508,36580610,7229550,77413012,9257405,99917599,89046466,30764367,37501808,62357986,29959549,14093520,45075471,27041967,526217,11543098,61741594,70800879,29932657,34698428,112651,99861373,83155442,47298834,8099994,95251277,4119747,62452034,68875490,61271144,1204161,98948034,69786756,89078848,20765474,43357947,80251430,92215320,60430369,34497327,51590803,98371444,80316608,37739481,54762643,16387575,61982238,33336148,8651647,28851716,13819358,99524975,14349098,1239555,44479073,18504197,43152977,33304202,70372191,89996536,32058615,96193415,66903004,61623915,73222868,92554120,44550764,36685023,50188404,16595436,7955293,5640302,4064751,43933006,74614639,66832478,26397786,55470718,20486294,20645197,7104732,89214616,18663507,77284619,32699744,15148031,1431742,87720882,43506672,97783876,68128438,69605283,65880522,6505939,68644627,74110882,57803235,68702632,83789391,9058407,9829782,45306070,93270084,99224392,65074535,68102437,99021067,82979980,33699435,77898274,168541,73392814,22942635,35512853,86001008,93933709,69579137,76671482,18833224,84187166,33123618,38009615,90272749,65454636,81172706,8733068,35092039,90090964,80014588,98653983,20885148,16567550,4199704,72357096,24733232,97281847,43501211,73168565,48360198,70367851,51464002,90013093,24619760,65081429,824872,36753250,47090124,10597197,16019925,10309525,37620363,31904591,73124510,12024238,60176618,17365188,16445503,10453030,20642888,8807940,508198,73235980,5267545,30771409,68939068,19376156,50806615,60106217,42947632,53802686,2891150,70727211,29065964,77787724,8913721,53084256,78909561,3233084,53632373,54199160,56153345,98739783,26392416,49597667,19939935,55143724,99549373,30163921,92353856,85571389,38061439,45995325,93562257,53393358,874791,85242963,30694952,36808486,1022677,13231279,1788101,40677414,14947650,71083565,45848907,50702367,4985896,2208785,7685448,40197395,30463802,92604458,26998766,77377183,36505482,39553046,44842615,29401781,92541302,54014062,65338021,77300457,27325428,72373496,27665211,6157724,44784505,17037369,36312813,21397057,91727510,76540481,11581181,60581278,27411561,13173644,18466635,24058273,44844121,38256849,69848388,67084351,45407418,78589145,75777973,67793644,46851987,36780454,56461322,78549759,44245960,35996293,20122224,37280276,10649306,62803858,15948937,30787683,12348118,91281584,79806380,88653118,39801351,99729357,57393458,20140249,89637706,48892201,17068582,73786944,19457589,2717150,44889423,62496012,51426311,91831487,73021291,2204165,33249630,38341669,20002147,96318094,59197747,89699445,6793819,93566986,57248122,61897582,86460488,75052463,75543508,67474219,68041839,54058788,21289531,51047803,3509435,14045383,38008118,247198,3773993,61141874,79880247,84127901,22129328,7517032,49271185,71333116,2607799,62051033,9860195,23134715,30543215,18783702,91240048,67281495,69136837,78766358,72777973,29819940,6808825,66250369,84293052,34432810,54987042,32151165,84904436,3233569,6221471,34698463,13862149,44177011,68816413,22721500,56473732,58208470,35456853,98462867,68824981,83150534,44481640,38658347,94090109,82651278,40781854,66667729,75128745,63059024,33553959,84406788,52204879,32161669,41092102,11923835,23740167,11365791,48675329,16684829,28787861,99658235,33935899,99604946,66611759,44627776,62762857,4798568,29635537,71657078,24826575,30998561,70420215,21070110,96735716,81898046,79922758,77312810,71300104,77301523,74724075,64087743,43376279,33565483,7919588,74452589,92692978,71316369,12303248,57240218,54427233,26292919,29994197,49882705,34946859,65038678,59981773,91957544,26275734,35192533,31733363,8115266,4668450,20681921,6111563,72274002,30653863,98648327,42199455,41245325,59177718,97057187,19898053,34236719,37675718,81677380,13348726,669105,97561745,97940276,88047921,28928175,17857111,64602895,49236559,48088883,9603598,51466049,57241521,45667668,9623492,89419466,71965942,44060493,3088684,68316156,91990218,48673079,4515343,16099750,79738755,1250437,5970607,34493392,6525948,30218878,89811711,7423788,98943869,65017137,24168411,46540998,78561158,84840549,83133790,25636669,6497830,62490109,90061527,79136082,39171895,3880712,24915585,42307449,94911072,66137019,79942022,68000591,71522968,6986898,26493119,2917920,69641513,10358899,4095116,18806856,32159704,19272365,18699206,64848072,40027975,6153406,52293995,82726008,3235882,95726235,15075176,51588015,8729146,83651211,76315420,55615885,83948335,21001913,14220886,51472275,6819644,42782652,41481685,41380093,62693428,70036438,37659250,44348328,94516935,31491938,85116755,88904910,93790285,8373289,15536795,84349107,33628349,61263068,81853704,22108665,92530431,88251446,17386996,415901,98130363,23569917,58224549,2331773,9175338,7066775,1569515,9188443,97641116,96906410,76170907,30139692,33237508,63152504,11161731,37891451,96852321,72732205,55189057,61373987,43322743,31161687,56424103,19891772,55669657,30366150,16405341,83747892,18001617,54663246,49328608,75820087,50567636,36468541,51715482,7687278,3294781,47887470,22879907,29188588,53648154,48260151,1936762,91802888,9259676,40686254,45790169,36189527,76330843,3487592,67227442,91938191,88698958,44846932,61728685,43045786,31727379,30569392,54517921,28550822,73031054,27625735,29516592,48774913,22405120,65047700,15535065,62552524,18411915,65851721,15015906,38494874,72725103,88130087,17016156,47792865,59501487,57020481,33201905,16380211,45381876,32250045,57359924,44983451,17146629,34295794,47361209,29510992,85117092,36812683,45996863,67031644,82427263,60955663,45617087,76434144,49598724,26102057,99125126,83210802,63506281,99226875,72614359,96726697,12416768,72238278,92998591,82886381,99690194,58749,58180653,16054533,34044787,6521313,39373729,40152546,24953498,52261574,71920426,79657802,61960400,32426752,95395112,97379790,66885828,90654836,82178706,30090481,92033260,93515664,49641577,69255765,45919976,81274566,37957788,67513640,20867149,99297164,47893286,22435353,16971929,8055981,68694897,569864,8791066,59910624,78218845,83083131,47738185,13470059,23194618,40534591,42073124,48658605,82532312,67124282,94711842,15902805,60102965,4508700,14731700,20568363,5832946,14723410,17058722,66428795,33431960,45990383,78300864,26734892,59109400,14626618,77187825,67030811,61815223,53842979,89217461,23432750,39847321,68110330,65275241,31126490,5111370,45237957,59318837,6038457,17976208,22200378,77620120,38510840,94539824,61712234,78884452,39201414,62115552,7300047,92803766,56484900,22450468,63628376,81855445,21993752,91255408,40781449,21919959,32590267,99333375,4806458,8634541,59614746,92398073,52060076,36845587,66116458,56531125,53888755,84002370,72793444,83533741,66322959,14326617,90158785,60393039,74137926,16424199,72495719,82779622,47213183,10961421,7011964,69355476,9575954,9599614,25842078,86798033,55960386,75407347,30395570,4978543,57961282,26507214,75986488,47708850,4091162,5822202,7646095,66663942,66319530,82052050,95010552,4434662,53666583,92692283,24591705,99971982,70004753,69697787,81805959,90310261,17539625,70596786,79821897,59371804,39986008,84684495,14363867,72358170,321665,9886593,22997281,26139110,33061250,30891921,4204661,45794415,6871053,92867155,86242799,74743862,26114953,23110625,26063929,17957593,55770687,12571310,40984766,37224844,74357852,76703609,30811010,15163258,4787945,86543538,1197320,24314885,75135448,91141395,76960303,94360702,3633375,53547802,18131876,66271566,54232247,51507425,68204242,80246713,15064655,95581843,96709982,89499542,87160386,17727650,48893685,44473167,7502255,5073754,38645117,46870723,89804152,44664587,83302115,40865610,57658654,5482538,21533347,42692881,91664334,16097038,11757872,23848565,67451935,65271999,74441448,8129978,72019362,50668599,12664567,93359396,13468268,58751351,90004325,61859581,17081350,92071990,29029316,36135,83269727,33895336,92787493,64157906,27185644,86301513,3183975,75153252,176257,37166608,70541760,749283,51315460,61859143,57163802,22766820,85695762,87710366,50007421,63372756,64055960,32274392,37435892,63015256,22113133,54868730,59405277,51149804,54263880,76825057,77694700,9860968,26863229,65715134,93053405,29466406,37560164,70782102,71469330,34667286,4069912,95290172,80555751,72738685,41092172,82339363,83368048,85711894,59329511,82897371,36139942,8128637,44847298,77072625,94076128,62232211,36933038,45049308,84166196,56955985,72278539,26664538,1375023,19101477,62428472,55753905,36396314,82327024,79322415,61928316,42644903,26776922,86118021,88444207,20836893,42967683,86821229,27187213,86022504,96420429,17385531,21673260,55850790,37192445,44915235,78785507,73109997,75229462,93057697,17894977,94595668,41442762,81774825,28734791,10264691 +84349107,68644627,26139110,66250369,77312810,79821897,70367851,14626618,73222868,4099191,91938191,24058273,1204161,31904591,98739783,321665,74137926,60430369,55753905,10597197,4787945,6986898,1569515,26734892,32426752,74110882,49328608,85242963,92398073,23194618,99515901,72777973,42782652,824872,33699435,66832478,66611759,83651211,41092172,17146629,85116755,55602660,96193415,72373496,99861373,60176618,55470718,10309525,63059024,44348328,89214616,76170907,52613508,54014062,48893685,37501808,27665211,86821229,29188588,77272628,79191827,50702367,99917599,99549373,37675718,37891451,65074535,65038678,49236559,68000591,78561158,39171895,9623492,29510992,7517032,20122224,65017137,5111370,10366309,62430984,32058615,95581843,26063929,65275241,30395570,70800879,78766358,34497327,93270084,68816413,20885148,99524975,20486294,75777973,32250045,18783702,85023028,16019925,22879907,74724075,68824981,97561745,23110625,99297164,99021067,7011964,96726697,82886381,51047803,32159704,29994197,61859143,80014588,6153406,44889423,77620120,40865610,71333116,82327024,50567636,57359924,66116458,60102965,7646095,43501211,8807940,59371804,36580610,52204879,6221471,95957797,15535065,526217,12664567,57248122,34946859,69136837,18699206,88047921,50188404,37739481,4798568,92692283,5822202,55247455,54199160,29029316,5832946,15148031,669105,33237508,97641116,29466406,17058722,68204242,57803235,75407347,28787861,99604946,35092039,81855445,90457870,19939935,16054533,48260151,94090109,20867149,62051033,74614639,37166608,56515456,68875490,89811711,6525948,67281495,44177011,1431742,48673079,30366150,569864,37183543,26507214,62496012,21919959,66667729,91990218,88653118,42199455,54263880,61373987,43506672,29819940,39553046,8696647,36933038,15948937,19898053,92554120,44983451,9599614,74743862,70372191,16099750,38061439,34432810,52261574,79922758,34236719,83789391,30998561,16684829,37620363,8129978,12303248,39373729,88444207,3509435,28796059,86001008,16971929,86118021,28550822,4985896,46870723,56153345,45617087,26776922,36312813,78300864,75128745,48360198,67793644,50806615,35456853,90090964,54868730,70004753,68316156,50668599,70036438,6505939,86543538,99965001,93933709,17068582,32590267,33565483,43045786,99224392,98130363,90061527,44473167,54427233,24619760,83155442,55143724,77072625,9886593,62936963,7687278,3233084,34698428,51472275,15075176,34295794,80246713,91957544,96318094,59197747,36930650,7229550,63967300,61141874,20002147,38008118,8651647,53666583,24733232,81853704,60106217,12348118,7685448,74441448,17037369,92604458,29834512,84684495,47792865,66903004,55960386,61271144,69786756,66428795,8128637,44784505,23848565,48658605,4668450,40686254,65851721,24953498,34493392,32151165,45990383,97783876,16424199,15015906,76434144,16595436,45790169,64087743,8913721,17727650,59910624,93515664,90310261,28928175,30543215,4119747,2208785,49271185,11365791,56424103,38341669,63372756,99658235,40197395,78549759,21993752,7502255,6111563,43357947,48892201,29065964,48774913,98462867,508198,95395112,45919976,73168565,23134715,25842078,84127901,29516592,31126490,77694700,9058407,81677380,40781854,54987042,53084256,56461322,26292919,94516935,14326617,19101477,3294781,47893286,43376279,38658347,17764950,22942635,95726235,4515343,63506281,77787724,43322743,82427263,42237907,45848907,81172706,83302115,78602717,82726008,81274566,42644903,33553959,64602895,45306070,3235882,9257405,35192533,60581278,45995325,94711842,61263068,11543098,20140249,9575954,76315420,92998591,16567550,14947650,19891772,71316369,78884452,45075471,24168411,75229462,67124282,61859581,51464002,44842615,57658654,7423788,59109400,1936762,33249630,38494874,92033260,73124510,18504197,64848072,84904436,8373289,3183975,87598888,36685023,24591705,40534591,62357986,21289531,45667668,30163921,83150534,33797252,27187213,37957788,84293052,66137019,79738755,82339363,55770687,87160386,42073124,33123618,44847298,56531125,168541,45237957,91727510,75153252,6038457,66663942,2917920,53547802,20836893,80316608,95290172,54663246,26744917,30139692,112651,42947632,11923835,4064751,19272365,39986008,21397057,66045587,71083565,69697787,49598724,30764367,51588015,57020481,76960303,82979980,4204661,68041839,9603598,8733068,874791,45381876,92530431,68128438,93053405,73392814,23569917,9188443,71657078,75543508,32699744,89046466,13173644,8634541,20642888,42692881,96735716,94911072,8099994,44245960,16405341,22766820,86460488,83133790,62452034,69579137,65715134,26863229,68110330,17857111,70420215,77413012,11581181,36505482,77284619,11757872,44550764,40027975,1197320,97940276,6871053,85571389,44481640,30891921,76540481,30463802,45996863,13231279,88130087,54606384,70596786,89419466,38256849,13470059,65271999,68102437,72274002,92787493,33628349,6497830,97011160,5970607,80251430,20645197,18411915,8055981,98371444,4091162,22129328,6808825,35512853,22108665,62803858,14220886,7919588,58749,96906410,26392416,92692978,67227442,10358899,75986488,33895336,40152546,82651278,31161687,50007421,89804152,89996536,14045383,91802888,74452589,47213183,30811010,83210802,10961421,68939068,30090481,57240218,22721500,95010552,7955293,26664538,16445503,15163258,82897371,65338021,39847321,15064655,41245325,10649306,28851716,42967683,96420429,97281847,68702632,32161669,33304202,82779622,72738685,63152504,54058788,39801351,74357852,20568363,15536795,73235980,7300047,31733363,67031644,9175338,21533347,97379790,93566986,11161731,58208470,27041967,7104732,5073754,67084351,78909561,29932657,16387575,25636669,67474219,49597667,9860195,33431960,47708850,57241521,17539625,15902805,72495719,17894977,82178706,87710366,12416768,40677414,93790285,51466049,64157906,17385531,99971982,59981773,29959549,41380093,86242799,1022677,62232211,72357096,89499542,58224549,83533741,23432750,47361209,8729146,14731700,61982238,86022504,55615885,6819644,61712234,90013093,62552524,53888755,1375023,33336148,69848388,46851987,87720882,91281584,31727379,69355476,96709982,22450468,91664334,44479073,98648327,81805959,66319530,62762857,99226875,79136082,16380211,98943869,18833224,37659250,71920426,94539824,70541760,68694897,13348726,72725103,17081350,6793819,749283,53842979,61815223,43152977,33935899,61960400,36812683,45407418,72732205,4508700,41442762,38365584,14093520,84166196,44915235,18663507,72614359,95251277,29401781,36845587,73031054,17976208,26102057,12024238,59177718,35996293,26114953,98948034,62693428,1788101,57393458,3880712,53632373,52293995,41092102,20681921,26397786,88251446,3088684,71522968,20765474,78589145,36396314,22435353,93562257,3773993,77377183,24915585,72278539,28734791,85711894,48675329,33201905,37560164,83368048,53648154,79657802,73617245,71300104,13468268,90272749,58751351,24314885,39201414,83948335,19486173,77301523,66322959,53393358,46540998,72358170,22405120,24826575,41481685,17016156,26998766,22200378,93057697,53802686,22113133,2717150,66885828,84840549,59614746,45428665,36139942,59329511,44060493,247198,59405277,1250437,13862149,14723410,61728685,67030811,59318837,415901,44627776,36780454,18466635,61623915,48395186,6157724,19376156,61897582,14349098,63628376,9398733,83269727,27325428,88698958,86798033,54762643,51507425,58180653,78218845,44844121,84406788,22997281,47738185,79880247,3633375,37280276,2891150,33061250,4095116,91240048,73786944,43933006,79806380,89699445,81898046,47298834,36808486,40356877,13819358,92541302,89217461,30218878,14363867,38009615,76330843,79942022,18806856,81774825,67451935,42307449,77300457,49882705,17957593,72019362,8791066,77898274,21673260,26493119,94595668,3233569,9259676,80555751,89078848,40984766,82532312,34698463,65047700,55669657,2204165,84002370,34044787,85695762,73109997,57163802,84187166,5482538,78785507,92803766,30787683,72238278,47090124,40781449,16097038,36753250,71965942,38645117,98653983,4806458,21001913,45794415,94076128,62740044,37224844,97057187,26275734,77187825,19457589,52060076,56473732,56484900,59501487,91831487,30694952,27185644,21070110,64055960,17365188,66271566,4199704,9829782,37435892,30653863,30569392,76825057,91255408,75820087,63015256,65454636,4434662,83747892,51315460,4069912,90654836,92353856,99690194,18001617,73021291,99125126,5640302,36189527,69255765,51426311,176257,18131876,96852321,69641513,89637706,75052463,47887470,37192445,3487592,34667286,55850790,51149804,90004325,17386996,94360702,54517921,69605283,32274392,92215320,1239555,76671482,2607799,82052050,12571310,85117092,76703609,88904910,8115266,51715482,7066775,86301513,44846932,70727211,27411561,92071990,48088883,62115552,71469330,51590803,93359396,49641577,38510840,10453030,5267545,30771409,60393039,99333375,56955985,70782102,29635537,36135,55189057,62428472,36468541,64098930,67513640,9860968,57961282,61741594,6521313,99729357,61928316,92867155,38022834,4978543,65081429,62490109,23740167,91141395,2331773,27625735,45049308,83083131,60955663,7182642,44664587,54232247,75135448,79322415,72793444,65880522,31491938,90158785,10264691 +77620120,89499542,57803235,67281495,29029316,10366309,669105,4099191,66250369,99604946,36312813,321665,35192533,43376279,16097038,19939935,55753905,65074535,14626618,55470718,6153406,39801351,68644627,34497327,247198,39171895,5970607,24058273,68702632,37620363,75229462,51464002,26292919,67793644,73235980,76315420,38658347,90013093,48892201,59109400,40984766,99549373,824872,35092039,95726235,26392416,61373987,12664567,5267545,29834512,30163921,62430984,69355476,29932657,63059024,99515901,20836893,19101477,66667729,5822202,74110882,17365188,98739783,55247455,34698428,28734791,52261574,49882705,15535065,71657078,27665211,32159704,70596786,56515456,83302115,75543508,54427233,77272628,54199160,65038678,79880247,83651211,86821229,96735716,8651647,16445503,85242963,36505482,65851721,26063929,33061250,12348118,70004753,44481640,569864,77312810,98371444,22405120,4985896,40865610,30463802,61728685,30395570,75777973,66045587,74137926,80014588,98130363,48360198,95251277,23848565,44889423,31904591,49236559,91727510,47090124,168541,45075471,22113133,69579137,89046466,54663246,84406788,80316608,4119747,1204161,91990218,87160386,95581843,65715134,66832478,99965001,39373729,56424103,52060076,62496012,65017137,14220886,37675718,61712234,85116755,34698463,92554120,93933709,78884452,99226875,60106217,5482538,4064751,53632373,55143724,40781854,31126490,32151165,68939068,22766820,26507214,18504197,7919588,62232211,20122224,69136837,96193415,30998561,37739481,68875490,15948937,94516935,1569515,55960386,33249630,43506672,6111563,72274002,20486294,43933006,6871053,55602660,86460488,16387575,73786944,96726697,51047803,77187825,31727379,65338021,36930650,6819644,10358899,10309525,93790285,16054533,78549759,4515343,7685448,90090964,67451935,82897371,40197395,83150534,52293995,45790169,29188588,62740044,26275734,7687278,69605283,25842078,22108665,38494874,59910624,51149804,49641577,16971929,37659250,79942022,71333116,61741594,59329511,26139110,47361209,54014062,88444207,4668450,1239555,30139692,8099994,16019925,72777973,46851987,20002147,94360702,15075176,12571310,57240218,75128745,29466406,84840549,32161669,42199455,63967300,37192445,76960303,2717150,66428795,93053405,73392814,74743862,60430369,87598888,82427263,94911072,99524975,96318094,44842615,92033260,31733363,33431960,176257,62552524,77694700,29994197,26863229,36580610,98462867,64602895,61960400,22997281,9058407,83210802,78589145,48260151,37224844,75407347,55770687,50806615,75153252,46870723,75820087,3509435,72738685,21993752,30366150,33237508,9175338,9188443,84349107,61263068,48673079,53393358,3487592,72614359,526217,32250045,99658235,62936963,88047921,86001008,57359924,53842979,62051033,33797252,74724075,1250437,75986488,24953498,17385531,76434144,58751351,14731700,32426752,33565483,21533347,38341669,73124510,85023028,18699206,48774913,44846932,20765474,51472275,50702367,99021067,9886593,61982238,15148031,8128637,56531125,3233084,89214616,82726008,28796059,98648327,71316369,83789391,17894977,60176618,38061439,26493119,91957544,19376156,29959549,44473167,78602717,1431742,26114953,94711842,40152546,73031054,12416768,18833224,55850790,44915235,93057697,92541302,82979980,81805959,47792865,48893685,53666583,29065964,44784505,69641513,96709982,66611759,35456853,23134715,33201905,27041967,64848072,68694897,88251446,82886381,4787945,77284619,81677380,58224549,88653118,39201414,98948034,62693428,63628376,84187166,7502255,45617087,7646095,72019362,23432750,62452034,15536795,84684495,77072625,4798568,91664334,80246713,6986898,62803858,38365584,94090109,14093520,44844121,44177011,33628349,36812683,112651,7517032,66137019,54517921,5111370,3633375,44348328,19457589,33304202,81855445,89078848,91802888,18783702,30694952,52613508,5832946,9599614,2331773,24733232,26998766,40686254,4508700,90272749,8729146,7104732,33699435,18131876,68102437,73109997,71300104,80251430,47213183,17068582,45237957,16405341,70800879,61859143,17857111,79821897,89804152,34295794,11581181,92692283,60581278,26734892,73617245,45848907,9860195,8807940,34236719,40534591,61271144,20645197,89419466,44550764,45049308,57658654,77787724,88904910,14947650,89637706,26664538,90457870,6808825,67474219,42782652,415901,60102965,17081350,97561745,52204879,99861373,99333375,66319530,51590803,51426311,97940276,42947632,59501487,84904436,7300047,72373496,42692881,4069912,68816413,6793819,9860968,53084256,17764950,14326617,7423788,3088684,70541760,32699744,92398073,36808486,45407418,99297164,76825057,6525948,78766358,37957788,68824981,11365791,1788101,64087743,70372191,61815223,47708850,59371804,77301523,3294781,749283,71083565,15163258,17957593,49328608,95957797,63015256,42967683,22129328,44479073,17037369,50668599,93515664,69255765,31161687,26776922,93270084,15902805,50567636,72495719,24591705,20140249,30543215,21001913,77413012,55189057,90310261,82339363,18663507,81172706,2891150,1197320,45428665,17016156,76703609,28787861,17146629,91831487,16380211,46540998,84166196,72725103,36780454,76330843,7011964,91938191,7955293,8373289,39847321,14045383,30764367,86543538,76540481,66903004,18411915,55669657,65880522,54868730,6505939,10649306,40027975,84293052,22721500,16424199,85711894,35512853,21919959,78561158,70036438,94595668,5073754,84002370,59177718,34432810,6038457,2208785,95290172,23569917,37183543,41092102,50188404,9829782,8115266,24915585,63506281,36189527,83133790,68000591,59405277,71469330,61623915,67084351,57393458,20568363,59614746,83948335,17976208,77377183,33895336,59981773,14363867,44245960,508198,81274566,92787493,16595436,41245325,13470059,13862149,6221471,93566986,54263880,37280276,44983451,74441448,8129978,97011160,36753250,43152977,1375023,20885148,13173644,78785507,40781449,54606384,29516592,3773993,95395112,38022834,87710366,7066775,34946859,96906410,26397786,14349098,30569392,53888755,57961282,24619760,85571389,44847298,3183975,53648154,38008118,70420215,33935899,82327024,83155442,86118021,69848388,57248122,15015906,24826575,70367851,9623492,53802686,92604458,78300864,16099750,28550822,81853704,27411561,30891921,32274392,48088883,79922758,22450468,30787683,6497830,22942635,27185644,45794415,57241521,73222868,79657802,76170907,73168565,22879907,4095116,62357986,39553046,37891451,41442762,99224392,44060493,3235882,77300457,85695762,92530431,12024238,14723410,34493392,50007421,51466049,4978543,45381876,4199704,36933038,75052463,40356877,65275241,34044787,44664587,36845587,66885828,36135,45667668,21070110,38009615,97281847,45919976,91281584,47887470,9257405,99971982,13348726,54762643,1022677,4091162,16684829,86301513,43501211,67227442,43322743,18466635,69786756,2917920,67513640,83083131,9259676,79191827,2607799,56153345,54058788,85117092,28851716,47298834,78218845,61141874,66271566,45995325,64055960,11757872,74357852,97783876,87720882,17727650,26102057,23194618,75135448,6157724,80555751,22435353,93359396,8696647,68204242,33336148,19272365,37166608,19486173,63372756,8733068,8634541,96852321,97057187,70727211,74452589,18806856,82532312,54987042,88130087,23110625,92215320,95010552,72732205,10597197,53547802,97641116,89217461,29635537,79738755,78909561,41092172,30653863,17386996,73021291,38645117,86798033,65047700,11543098,58208470,40677414,81898046,90654836,27325428,10453030,41380093,45306070,59318837,76671482,61928316,45990383,13231279,68316156,21673260,41481685,86022504,27187213,19898053,37435892,57163802,67031644,71920426,42307449,38256849,42073124,30811010,36139942,79136082,10961421,29819940,21397057,56473732,92692978,62762857,77898274,37560164,91240048,99125126,28928175,82052050,3880712,90158785,66322959,15064655,71965942,11923835,19891772,68041839,99917599,12303248,30771409,35996293,92998591,72357096,51507425,874791,39986008,30090481,89811711,33553959,64157906,74614639,65271999,4806458,8913721,93562257,5640302,8055981,61859581,32590267,42237907,70782102,97379790,17058722,55615885,29510992,66116458,20681921,4204661,99690194,7182642,69697787,26744917,83533741,20867149,56484900,94076128,61897582,9398733,13819358,56461322,62490109,2204165,72238278,13468268,66663942,8791066,29401781,38510840,59197747,9603598,92353856,43357947,64098930,25636669,72278539,72358170,23740167,86242799,62115552,48395186,56955985,96420429,91141395,24168411,49598724,84127901,18001617,1936762,83747892,49271185,16567550,92071990,89699445,32058615,67030811,92867155,57020481,54232247,44627776,79806380,65081429,30218878,94539824,11161731,37501808,58749,62428472,82651278,51315460,67124282,9575954,98653983,45996863,6521313,51715482,88698958,48675329,42644903,34667286,48658605,91255408,22200378,58180653,47738185,90004325,98943869,49597667,79322415,92803766,7229550,63152504,83269727,60393039,20642888,68128438,89996536,51588015,21289531,3233569,43045786,60955663,31491938,36468541,47893286,4434662,36685023,72793444,71522968,83368048,17539625,82178706,81774825,65454636,82779622,27625735,24314885,33123618,68110330,36396314,90061527,10264691,99729357 +53547802,73617245,41245325,17385531,40781449,86022504,6808825,44889423,64157906,61263068,41380093,29065964,7955293,55753905,85116755,27411561,99971982,35456853,92554120,8807940,66428795,53802686,15535065,92787493,79821897,4064751,60102965,96193415,4978543,17727650,45237957,40984766,32590267,36505482,37620363,72777973,34044787,67793644,14947650,52060076,4099191,16097038,45617087,38008118,66250369,51464002,82897371,97561745,72738685,41092102,92033260,54517921,19939935,51472275,93933709,33797252,65851721,54762643,89811711,8634541,17068582,55247455,31126490,9599614,91255408,29819940,59329511,96852321,88653118,97379790,93515664,56955985,10309525,6986898,89419466,93270084,73235980,5111370,58751351,44842615,91664334,21919959,15075176,94595668,22113133,62740044,63372756,38645117,50668599,40865610,59318837,82979980,2891150,62496012,26744917,29029316,89214616,36753250,6793819,31904591,96726697,9886593,26493119,57240218,68204242,53842979,1431742,83210802,21993752,77413012,7182642,90457870,21070110,78602717,71657078,6505939,15536795,16445503,80014588,36808486,44348328,44915235,62430984,72373496,97783876,84187166,79322415,24058273,49641577,4787945,73786944,75229462,78589145,44784505,65275241,30764367,55669657,30771409,3509435,5970607,98948034,84684495,65271999,38494874,18806856,16099750,77284619,54014062,95290172,60955663,62452034,50806615,36580610,99515901,8129978,99658235,71920426,29994197,72278539,247198,91990218,98943869,51047803,65017137,17764950,51149804,19101477,7646095,61859143,67030811,67451935,33061250,9188443,415901,98371444,25842078,58224549,77072625,35996293,94090109,1197320,65715134,78561158,66903004,99690194,69697787,39373729,84127901,24953498,37435892,18699206,39986008,68644627,82726008,3633375,44481640,28550822,22942635,48260151,56531125,10366309,71469330,66832478,54987042,83269727,27665211,63059024,34493392,23569917,9257405,45996863,36780454,21289531,74137926,98130363,82178706,18833224,4091162,66667729,81853704,95581843,18131876,9575954,83651211,26664538,32161669,81274566,92692283,42782652,72274002,30163921,55189057,30366150,22997281,13231279,36312813,669105,56424103,26998766,38658347,7502255,16019925,1250437,66885828,72019362,10264691,73222868,78766358,37675718,37891451,28928175,44983451,35192533,45407418,62762857,8913721,39801351,14731700,1788101,74110882,47090124,15015906,89699445,45075471,77694700,17365188,72238278,62552524,73124510,37957788,28851716,76960303,29510992,6221471,29834512,7066775,96735716,22405120,3487592,82886381,62490109,57961282,55615885,83533741,7104732,4798568,99297164,26392416,4508700,44550764,40197395,89637706,71300104,79657802,90090964,63015256,14093520,86001008,4515343,30090481,41442762,99549373,99965001,86118021,70036438,42073124,50702367,45428665,68875490,14220886,34946859,45990383,48675329,98653983,68000591,50188404,83368048,37560164,66663942,63152504,30694952,16054533,27625735,43376279,65338021,74441448,4204661,76434144,4434662,24591705,61982238,66116458,76315420,89804152,90013093,56515456,94911072,30787683,54058788,20867149,569864,50567636,39847321,11543098,4806458,57658654,94076128,23194618,8651647,68824981,71083565,42967683,4095116,98462867,7300047,24168411,26776922,17037369,10597197,9259676,26863229,52261574,92215320,176257,58749,99021067,67281495,91957544,83302115,8128637,75986488,76330843,64848072,62115552,12664567,70727211,36933038,749283,62693428,45794415,77272628,70372191,57803235,67084351,59405277,9829782,96906410,61960400,55143724,73392814,29635537,63967300,87160386,16380211,24314885,22721500,7423788,72358170,60106217,20568363,69641513,45381876,32699744,13173644,11757872,67227442,32058615,33628349,26114953,34295794,68102437,55602660,11581181,34236719,54427233,14326617,18783702,57241521,98739783,33431960,39171895,74743862,57359924,83150534,12348118,20885148,21673260,69255765,71333116,22108665,81898046,96420429,56484900,63506281,4069912,68041839,8373289,55960386,69355476,33895336,17976208,39553046,38365584,53888755,53648154,26063929,92604458,59197747,7687278,92692978,84840549,90272749,59371804,10961421,39201414,65047700,11923835,4668450,34497327,55770687,78909561,6525948,40781854,30569392,9623492,3183975,44479073,82339363,12416768,32159704,2331773,47738185,72357096,25636669,34667286,45848907,48360198,78218845,93053405,14045383,77620120,99861373,45919976,59177718,33237508,19272365,30395570,37501808,95957797,75407347,18663507,15064655,30218878,95395112,82327024,74357852,77898274,86301513,65081429,29188588,95726235,37280276,3294781,22129328,65880522,73168565,69605283,81172706,29466406,58180653,30998561,97281847,3088684,13348726,20002147,64055960,57163802,66045587,84349107,5482538,60430369,45790169,7919588,17146629,68816413,74452589,79880247,88444207,53632373,80246713,45667668,18411915,92867155,66322959,14363867,66611759,58208470,321665,40534591,38510840,92398073,10358899,99524975,51715482,32426752,43501211,53393358,93790285,34698463,47708850,61928316,65038678,74724075,22450468,40686254,75135448,59614746,26139110,66319530,22200378,99729357,508198,70420215,69136837,33336148,87598888,3773993,37739481,59501487,61271144,16424199,64602895,24619760,53666583,2208785,33201905,2204165,48893685,47792865,90004325,31491938,1204161,37183543,77787724,34432810,84002370,18001617,49236559,28787861,45995325,2717150,41092172,13468268,93359396,76825057,44177011,20140249,49271185,21397057,61373987,44847298,62232211,43357947,17539625,51315460,40152546,90061527,69786756,84904436,50007421,82651278,9860195,16971929,26507214,8791066,168541,91802888,37166608,11161731,99604946,56473732,72725103,1936762,35092039,10453030,54868730,47361209,34698428,5073754,31727379,84406788,52204879,54199160,99226875,7517032,43933006,29516592,1569515,15902805,40677414,67474219,7685448,79942022,17016156,76703609,112651,32274392,79191827,824872,61859581,6871053,2607799,92541302,77312810,44844121,85242963,8729146,72614359,89217461,26292919,91727510,99224392,71316369,29959549,77187825,51426311,79922758,6111563,15163258,95010552,94539824,5822202,70367851,99125126,19486173,55470718,89996536,14349098,91831487,17386996,32250045,37192445,51466049,60581278,69579137,49328608,23432750,36930650,89499542,2917920,29932657,12303248,42947632,9603598,9058407,7011964,23848565,71522968,89046466,38009615,66137019,19898053,30543215,12571310,40356877,36396314,42307449,38256849,43322743,83747892,9860968,75543508,92353856,88698958,33699435,80555751,26734892,6157724,48395186,30891921,38061439,92530431,526217,42199455,37224844,61141874,7229550,6521313,4119747,37659250,70004753,62051033,43506672,44664587,98648327,36812683,6153406,70541760,36685023,76540481,61623915,53084256,64098930,8115266,93566986,15948937,6497830,17894977,44627776,92803766,17957593,3233569,30653863,68939068,63628376,20642888,20122224,19457589,13470059,47893286,49597667,20486294,48088883,62803858,16387575,22435353,93057697,51588015,48673079,92071990,60393039,96318094,20765474,8696647,8733068,44245960,97940276,75128745,87710366,44473167,85117092,88904910,3880712,67124282,56153345,9398733,75777973,91281584,24733232,88047921,36139942,33249630,33565483,14626618,55850790,52293995,42237907,54663246,38341669,44846932,83789391,99917599,77300457,43045786,90310261,26102057,72495719,31161687,68110330,94516935,61728685,40027975,22766820,24826575,83083131,48892201,78785507,97011160,20681921,36468541,66271566,4985896,20836893,86460488,28734791,51590803,19891772,42644903,75153252,14723410,49882705,56461322,79136082,57393458,6038457,72732205,3233084,70596786,43152977,69848388,36135,86242799,96709982,42692881,16595436,27187213,23110625,83948335,68702632,89078848,88130087,33304202,16684829,22879907,874791,68694897,47298834,17857111,71965942,57248122,91141395,5640302,62936963,87720882,82427263,64087743,12024238,27325428,99333375,76170907,38022834,26397786,97641116,44060493,94711842,83155442,4199704,78300864,46851987,80316608,62428472,33553959,82052050,59910624,80251430,18466635,51507425,59109400,60176618,78884452,74614639,61897582,83133790,5832946,81774825,52613508,86543538,94360702,61815223,54232247,13862149,13819358,46870723,97057187,92998591,30139692,81855445,45049308,10649306,21533347,76671482,15148031,20645197,5267545,81805959,84293052,75052463,88251446,85571389,48774913,82779622,79806380,1022677,84166196,47213183,27185644,33123618,18504197,81677380,11365791,21001913,73109997,17058722,28796059,73031054,82532312,3235882,79738755,75820087,17081350,54263880,23740167,31733363,24915585,93562257,54606384,30811010,35512853,33935899,36189527,8055981,85711894,1239555,86821229,61741594,77377183,19376156,59981773,85695762,72793444,68316156,86798033,36845587,29401781,57020481,91240048,26275734,67031644,65074535,6819644,85023028,16405341,91938191,46540998,9175338,61712234,70800879,68128438,27041967,45306070,47887470,30463802,67513640,78549759,23134715,95251277,41481685,48658605,73021291,1375023,49598724,90654836,65454636,70782102,32151165,8099994,90158785,16567550,77301523,62357986 +63967300,59177718,93933709,87598888,54427233,78602717,33304202,42307449,89078848,79806380,30090481,99965001,14626618,63628376,54762643,19376156,27625735,27665211,16097038,4985896,7011964,99226875,18504197,22108665,49882705,95395112,75543508,44844121,54199160,10366309,26392416,25842078,2208785,65338021,1204161,247198,93053405,37620363,61263068,19486173,18833224,51464002,41481685,73235980,43376279,68694897,51149804,75135448,38022834,46540998,67793644,68702632,19939935,14349098,66271566,34698463,37183543,9398733,49598724,99549373,26275734,14731700,22997281,3509435,20885148,22129328,7104732,1250437,89637706,40152546,39801351,7229550,64602895,98462867,24314885,84187166,95726235,33061250,15536795,26863229,32590267,4668450,85116755,38061439,30998561,44842615,22450468,51590803,69355476,16019925,55669657,42782652,66045587,38658347,82427263,60955663,59318837,64848072,81898046,73031054,73786944,92541302,94516935,36780454,64087743,93270084,26998766,3773993,4119747,38365584,8651647,86001008,32159704,44784505,17068582,2204165,10309525,60102965,93562257,3233084,99297164,70420215,5640302,18663507,96193415,32161669,42237907,60430369,4434662,6793819,80246713,72614359,16445503,62740044,78785507,34493392,21993752,54606384,56531125,99917599,63372756,63059024,57803235,27041967,18806856,88047921,1936762,58180653,91831487,20568363,62762857,35192533,12664567,9259676,60581278,7955293,55602660,82339363,33699435,41380093,71657078,15015906,99333375,33797252,82897371,83150534,17386996,49641577,31733363,82726008,63152504,47887470,16054533,44479073,65851721,83210802,51588015,40984766,76825057,44847298,34667286,50567636,47090124,9175338,37891451,39553046,93057697,81855445,10358899,79922758,68644627,12416768,89419466,99224392,53802686,321665,34432810,70036438,63015256,66250369,5482538,62357986,8729146,86242799,88698958,92692978,79880247,72725103,73124510,94539824,824872,29510992,70800879,95290172,65454636,61815223,17764950,89996536,81805959,45381876,57241521,17365188,56153345,59371804,64098930,80014588,77284619,87720882,58208470,23848565,48892201,95957797,34295794,92033260,77272628,62936963,14093520,27185644,61982238,85571389,68128438,8099994,36685023,91990218,70596786,93566986,40781449,35512853,38008118,9058407,36580610,88904910,67474219,62452034,30764367,24826575,73392814,40027975,3487592,70367851,9623492,77072625,1197320,65074535,112651,24591705,6153406,31727379,2717150,81274566,15902805,61271144,9886593,51466049,91727510,89046466,20765474,45049308,2607799,30891921,25636669,47361209,84406788,55189057,37501808,53084256,7919588,62115552,1569515,61741594,56473732,44983451,62430984,45075471,36812683,44550764,42947632,4099191,569864,79821897,30787683,2891150,508198,20122224,7423788,12571310,73617245,37435892,55143724,89214616,50188404,13173644,43506672,2917920,66322959,17081350,86798033,94076128,21533347,91240048,81677380,30771409,57248122,56955985,17976208,54987042,83368048,36505482,68939068,10649306,17957593,75777973,74110882,526217,62051033,19891772,78549759,6808825,34698428,82052050,68824981,43152977,16099750,65880522,54663246,669105,88653118,39171895,45617087,75820087,78589145,78766358,20642888,4064751,72732205,66667729,85242963,55470718,48088883,69786756,749283,79657802,37659250,16684829,68110330,99515901,20486294,76671482,91664334,93515664,77787724,30694952,14363867,83155442,92998591,33201905,71316369,91141395,36930650,97561745,62552524,94090109,3183975,4798568,29932657,60176618,4978543,78884452,14947650,48360198,28928175,10961421,78561158,23432750,26102057,87710366,91281584,8807940,45237957,36135,18411915,72793444,74357852,11543098,76315420,87160386,26292919,69605283,76703609,75153252,5267545,96735716,85117092,40677414,53393358,17385531,72278539,31126490,74137926,51507425,29959549,98130363,67227442,28851716,49271185,10453030,45848907,42692881,73168565,48658605,35092039,7517032,62803858,79738755,53842979,66903004,74724075,28796059,9603598,82178706,59197747,4508700,39986008,88251446,68816413,82979980,46851987,16567550,90013093,77413012,51715482,64055960,74743862,42644903,15535065,37560164,14326617,66116458,31904591,22721500,92867155,13862149,76540481,75986488,22766820,53632373,59981773,94911072,70372191,91938191,6986898,44889423,62496012,92554120,32426752,29834512,11161731,26397786,71333116,68041839,56461322,53648154,43501211,30163921,51472275,65081429,38256849,7687278,97940276,24915585,43322743,59109400,18131876,23740167,8733068,84002370,49328608,26114953,33431960,12348118,12024238,66663942,65017137,66137019,83789391,12303248,20867149,30543215,61373987,98371444,19457589,73222868,4095116,874791,21673260,66319530,16971929,77620120,1431742,97783876,8696647,29029316,54058788,92692283,44245960,9860195,38645117,5111370,86543538,64157906,55247455,82532312,77377183,86460488,79322415,68000591,77300457,79191827,59329511,67281495,77187825,168541,68875490,9257405,90310261,3880712,70727211,82779622,40534591,10264691,21001913,6497830,45407418,33237508,31491938,76330843,99524975,23569917,86118021,20645197,16380211,5073754,6525948,17016156,11581181,35996293,30395570,92353856,8129978,18466635,59501487,22942635,52261574,43933006,51047803,9575954,92604458,5822202,72373496,15064655,69255765,84840549,15948937,51426311,33628349,72357096,45996863,47893286,76960303,29994197,67031644,84684495,33336148,75052463,40781854,33123618,4515343,99658235,72274002,53888755,26139110,3233569,54014062,61859581,44664587,29466406,42199455,48395186,44348328,46870723,4806458,92398073,54868730,62428472,88444207,29516592,45990383,32151165,22200378,3294781,95251277,75128745,43045786,7685448,43357947,1788101,98948034,59614746,7300047,6819644,31161687,18699206,10597197,16424199,71083565,66611759,8913721,81853704,57359924,74614639,83083131,40356877,80555751,60106217,13470059,70541760,94360702,96709982,81172706,98648327,22435353,20140249,8055981,16387575,45794415,61623915,78909561,32250045,17146629,19101477,92787493,13348726,22405120,86821229,74441448,33565483,83533741,40197395,69579137,34497327,4199704,39373729,98943869,38341669,65271999,92215320,37739481,99125126,57240218,40686254,44627776,36933038,8373289,61712234,18783702,67451935,37192445,29065964,1239555,65275241,99729357,97011160,37166608,71965942,97641116,91957544,96726697,71522968,86301513,9829782,77312810,53547802,14045383,35456853,99971982,6111563,47213183,66428795,9188443,23194618,91802888,36189527,99690194,26507214,78218845,57961282,99861373,26063929,30811010,26744917,80316608,29188588,52204879,92803766,5970607,44481640,50702367,79136082,44060493,77301523,70004753,14723410,55753905,82886381,66885828,98739783,62232211,79942022,8128637,36753250,56484900,16405341,54263880,71469330,7066775,1022677,30463802,28734791,92530431,48893685,7182642,2331773,34946859,28787861,90158785,4204661,56515456,32699744,44473167,84127901,90457870,59405277,72777973,49597667,38494874,37957788,89217461,17539625,59910624,17037369,69848388,30366150,89804152,39201414,83747892,69697787,95010552,90272749,67030811,86022504,30653863,36139942,91255408,68316156,14220886,72019362,96906410,65038678,45790169,90004325,3088684,42967683,24058273,89499542,47708850,27411561,61960400,42073124,21070110,51315460,69136837,15148031,30218878,22113133,36845587,20002147,37675718,99021067,26493119,17894977,47738185,61859143,6038457,90090964,62693428,61928316,83133790,30569392,13231279,52293995,4069912,48774913,67513640,45995325,49236559,57658654,6157724,57393458,82651278,65715134,29635537,37224844,66832478,55770687,6871053,76170907,80251430,22879907,73109997,83651211,16595436,41442762,84293052,54517921,52060076,37280276,33935899,39847321,20836893,17857111,40865610,7646095,53666583,34236719,85023028,88130087,71300104,36468541,36312813,27325428,3235882,29819940,83948335,45667668,48260151,13468268,72495719,48675329,21919959,58224549,8115266,68102437,85711894,6505939,71920426,44846932,69641513,17058722,3633375,56424103,30139692,72238278,48673079,8791066,75229462,47298834,72738685,74452589,52613508,36808486,81774825,26734892,50007421,90654836,15075176,18001617,84904436,23110625,24168411,26776922,26664538,78300864,62490109,44915235,5832946,9599614,11757872,98653983,77694700,57163802,83269727,73021291,6521313,92071990,55960386,60393039,33249630,41245325,1375023,77898274,97379790,20681921,93359396,19272365,21397057,415901,54232247,90061527,176257,97057187,93790285,96318094,58751351,67124282,99604946,58749,83302115,85695762,44177011,45428665,61728685,61897582,17727650,24733232,9860968,65047700,32274392,27187213,28550822,75407347,84349107,21289531,55850790,63506281,11365791,41092172,89699445,47792865,4787945,82327024,34044787,29401781,70782102,6221471,33895336,24619760,72358170,61141874,68204242,50668599,41092102,55615885,23134715,84166196,76434144,36396314,11923835,24953498,7502255,94595668,45306070,57020481,19898053,89811711,94711842,67084351,45919976,15163258,32058615,33553959,95581843,4091162,97281847,96420429,50806615,38510840,38009615,13819358,96852321,8634541 +12348118,96193415,75153252,44245960,70367851,33201905,74110882,20885148,23194618,35092039,22108665,61859143,75407347,53842979,57803235,81677380,97561745,86821229,27665211,6986898,21993752,99524975,70541760,9623492,52293995,75543508,9860195,46851987,62496012,17068582,83533741,32159704,17058722,77312810,10309525,55753905,15535065,39801351,93270084,15536795,85571389,37192445,41092172,99297164,77284619,14349098,62693428,6808825,80014588,61271144,55247455,50702367,26114953,84293052,33628349,74614639,38008118,9398733,20642888,68644627,24591705,28851716,17976208,61373987,62936963,91281584,67793644,8913721,22129328,16971929,5111370,64087743,84349107,94539824,69355476,63967300,21533347,30694952,6793819,33797252,18699206,79821897,97011160,19891772,16019925,64848072,77272628,95957797,66322959,47792865,96726697,59177718,3233084,26139110,89214616,99515901,68204242,79136082,34432810,44784505,31904591,39847321,89804152,47893286,3880712,23848565,75777973,29819940,50806615,6153406,96852321,88653118,70727211,87720882,80251430,20140249,60581278,26998766,33237508,30787683,91727510,31126490,93790285,19486173,50007421,92541302,91957544,79806380,99224392,60106217,7104732,38658347,44842615,40984766,82779622,18833224,56473732,45919976,24314885,8696647,89419466,44060493,17957593,13173644,44348328,50188404,76540481,2208785,79657802,98739783,36580610,40356877,68824981,16380211,17539625,68875490,3509435,3183975,25842078,61982238,88047921,97641116,71083565,36753250,74441448,65851721,30569392,71657078,72725103,51715482,67474219,98371444,7919588,81898046,13862149,40152546,9603598,57241521,45790169,32058615,61741594,58751351,45990383,99965001,56531125,48892201,60430369,7687278,83210802,34295794,20836893,11161731,53888755,6505939,98462867,78549759,61897582,16424199,81274566,24733232,81172706,4095116,34236719,11543098,83789391,32161669,10366309,73168565,16054533,75128745,37560164,73124510,94516935,7423788,22435353,36139942,38365584,92554120,38341669,53084256,22879907,37280276,1431742,22766820,62357986,27325428,7685448,54263880,62740044,71333116,44889423,88444207,40677414,73222868,64055960,27041967,19101477,92998591,43376279,65271999,18783702,78602717,14220886,62051033,38061439,37891451,34946859,67030811,91831487,32250045,44177011,68316156,72373496,321665,91990218,90310261,17727650,92803766,65047700,94360702,13468268,19939935,17037369,26776922,97940276,37620363,39986008,45237957,168541,28928175,37739481,69848388,47708850,91664334,29516592,43357947,89499542,31727379,82979980,70596786,526217,65454636,77620120,2917920,36930650,4798568,11923835,81805959,415901,22997281,66663942,77377183,54014062,54762643,23569917,60102965,70800879,82339363,61623915,14093520,9886593,29401781,1204161,91802888,61815223,10358899,97057187,73031054,30395570,44983451,42644903,40197395,2717150,51047803,85116755,7502255,33249630,8128637,9188443,89811711,26734892,64602895,59614746,26292919,28796059,6525948,29510992,10597197,42947632,49236559,21070110,36396314,44550764,83133790,36685023,55669657,63152504,53632373,49598724,6157724,87160386,15064655,72019362,96709982,84840549,71316369,54199160,37224844,17365188,5832946,68816413,37183543,42692881,40865610,54868730,6871053,18663507,72278539,4434662,62552524,95395112,59910624,49271185,45428665,10961421,44844121,16387575,61141874,39373729,28734791,82532312,20486294,40781449,96906410,93562257,23110625,21673260,73235980,508198,72738685,29466406,65275241,48260151,8651647,3088684,68102437,62452034,57658654,84684495,43322743,72732205,27411561,42199455,99549373,52613508,17081350,77413012,7066775,99658235,36780454,24058273,88130087,30163921,54606384,56153345,63015256,43506672,99604946,78909561,42307449,92867155,55189057,4119747,71965942,47090124,45995325,43933006,83651211,8129978,54058788,48893685,14947650,54987042,93057697,4668450,6521313,20645197,42782652,78785507,79942022,87710366,29994197,72777973,73109997,36505482,24915585,7229550,67281495,11365791,66271566,65074535,36845587,14626618,99021067,59197747,98948034,91255408,96420429,24168411,76315420,86543538,8373289,6497830,69697787,61859581,69786756,34493392,30218878,55143724,4099191,33935899,21397057,69255765,3233569,66832478,77301523,51464002,66250369,90090964,29932657,75135448,669105,14045383,61263068,66116458,59109400,53547802,20002147,56484900,60955663,94711842,45794415,26397786,89046466,68702632,34497327,58749,89078848,26102057,56461322,81853704,84002370,8055981,48658605,35192533,62803858,94090109,80555751,87598888,44473167,65017137,26392416,46540998,74743862,9860968,18806856,45049308,33431960,95010552,75986488,67031644,38645117,66611759,33123618,2891150,53802686,90061527,77787724,67227442,70420215,39201414,22942635,92398073,63506281,99861373,33304202,41481685,1788101,39171895,98943869,79922758,38494874,12571310,59501487,75229462,30543215,26744917,45075471,22721500,44847298,7517032,6221471,37501808,13231279,64098930,75052463,92033260,8733068,93566986,98648327,28787861,76170907,45848907,37166608,16445503,90272749,92604458,65715134,3773993,29959549,50567636,30366150,51590803,78589145,40534591,30891921,73021291,1022677,83155442,54427233,78561158,82726008,44481640,62428472,19376156,31161687,51472275,42967683,49882705,3294781,8791066,569864,50668599,874791,3633375,59371804,83083131,4069912,85242963,20122224,26063929,45306070,12416768,86118021,52060076,40686254,55770687,98130363,49597667,36312813,1569515,69641513,47738185,30998561,67124282,92692978,36812683,11581181,15163258,36808486,64157906,16684829,53393358,66137019,82052050,16595436,48774913,55615885,68041839,6038457,84187166,21919959,76330843,10453030,15948937,32590267,17764950,72274002,90013093,86798033,68939068,53666583,51426311,33699435,16405341,57961282,749283,99729357,24826575,34667286,4064751,9575954,66428795,26507214,9259676,15902805,92787493,28550822,30764367,68128438,65081429,78218845,55470718,57359924,41245325,7955293,10649306,20568363,9829782,16567550,4787945,57240218,65038678,74357852,5073754,70782102,20867149,67084351,85711894,92692283,95290172,79880247,1197320,62430984,93359396,21289531,5970607,41092102,69579137,37675718,85695762,40027975,76434144,20765474,66885828,23740167,17857111,81855445,77694700,57248122,73617245,92353856,34698428,7011964,52261574,29834512,75820087,12024238,43045786,63628376,1250437,47213183,30811010,79191827,3487592,63372756,37659250,26493119,42073124,26863229,72357096,14731700,65338021,84166196,54663246,73392814,33565483,824872,15015906,45667668,39553046,93053405,76960303,86022504,80316608,8099994,72495719,33336148,91141395,7300047,247198,48675329,11757872,33061250,32426752,86460488,33895336,26275734,90004325,29065964,1375023,17894977,47361209,19457589,24953498,15148031,54232247,99971982,84127901,94911072,35456853,69605283,12303248,77072625,51588015,49641577,51315460,48088883,56424103,88251446,52204879,91240048,69136837,76703609,4985896,63059024,73786944,35512853,92530431,36189527,17386996,53648154,56515456,97281847,16097038,66045587,94076128,19272365,74137926,112651,38022834,9257405,95581843,32151165,48673079,14326617,24619760,61960400,12664567,22200378,42237907,1239555,34698463,8634541,58208470,4204661,176257,54517921,51466049,14723410,71522968,51507425,60176618,88904910,93933709,90457870,41380093,31491938,59318837,68000591,84904436,55602660,89996536,83368048,70036438,82327024,26664538,36468541,66319530,99917599,86001008,13470059,18466635,32274392,62490109,8115266,17016156,6111563,92215320,31733363,99333375,30463802,30139692,89699445,78300864,45617087,33553959,18001617,83150534,17146629,27185644,68694897,88698958,68110330,74452589,7646095,77898274,96735716,59329511,13819358,44479073,74724075,37957788,22450468,82886381,96318094,45996863,76671482,29188588,37435892,71469330,97379790,62762857,46870723,85117092,5267545,9599614,15075176,9058407,44846932,48360198,77300457,61728685,43501211,30771409,62232211,79322415,29029316,66903004,72358170,95726235,21001913,20681921,77187825,83302115,18504197,83269727,71300104,8807940,94595668,8729146,44915235,97783876,16099750,98653983,85023028,76825057,9175338,7182642,57020481,72238278,35996293,86301513,61712234,4508700,47298834,30653863,4806458,66667729,14363867,4091162,49328608,59981773,36135,30090481,19898053,23432750,43152977,80246713,90158785,38256849,18411915,6819644,99226875,72793444,47887470,25636669,93515664,57393458,17385531,67513640,40781854,82427263,3235882,1936762,22113133,5640302,32699744,71920426,84406788,27187213,82651278,82178706,34044787,91938191,27625735,2331773,90654836,70372191,89637706,81774825,48395186,57163802,45407418,78766358,99125126,95251277,83948335,70004753,23134715,2607799,44627776,13348726,82897371,4515343,18131876,86242799,79738755,60393039,10264691,55850790,62115552,41442762,5482538,38510840,89217461,92071990,72614359,61928316,51149804,4199704,38009615,22405120,67451935,2204165,45381876,78884452,65880522,56955985,5822202,44664587,99690194,36933038,4978543,58180653,55960386,59405277,58224549,83747892,29635537 +65851721,94516935,65454636,58208470,4119747,67281495,33628349,20885148,55247455,89637706,41380093,56531125,91255408,80251430,70800879,28851716,82427263,54427233,97281847,54762643,24733232,99861373,17727650,26392416,44784505,29994197,61741594,168541,30653863,45790169,66045587,97940276,96193415,3233084,70036438,86001008,38061439,77300457,92398073,89078848,29959549,43045786,87160386,69786756,89214616,30218878,37739481,9860195,4508700,81677380,19101477,53842979,15064655,321665,27325428,88047921,33201905,45996863,77312810,99658235,112651,35456853,22721500,59501487,18783702,7919588,62693428,46540998,5970607,99524975,96726697,17081350,44473167,30543215,21673260,70004753,66832478,1022677,57240218,30139692,20122224,89699445,61373987,63372756,70596786,57248122,43933006,18663507,97561745,75543508,60955663,1239555,50806615,52060076,5111370,71657078,43357947,80555751,43322743,64848072,54517921,98948034,33565483,44245960,33797252,81853704,40677414,36930650,93790285,1788101,17037369,65715134,93515664,85571389,68644627,57803235,48260151,12348118,94076128,12664567,56515456,29635537,53888755,45306070,98943869,40865610,65880522,37957788,669105,62452034,91957544,78549759,77413012,34698463,14093520,48360198,92033260,51047803,49236559,14326617,50188404,29401781,55753905,97011160,30811010,526217,20140249,38494874,26493119,62496012,88130087,7502255,93566986,38022834,53666583,4668450,30569392,84904436,9886593,88444207,9398733,48673079,24591705,15015906,99965001,13231279,47708850,749283,10366309,35092039,95581843,80014588,15536795,34946859,75986488,57359924,75153252,62490109,93933709,37183543,98739783,47298834,8696647,27665211,42782652,34698428,99125126,34295794,66250369,9860968,74110882,36780454,874791,96735716,6111563,2204165,79657802,44889423,4099191,39801351,21533347,65038678,3235882,90457870,16405341,33553959,7104732,82886381,6153406,16097038,95957797,61271144,83210802,27041967,94711842,64087743,53648154,40686254,6221471,74743862,75777973,80316608,18466635,69641513,68816413,24619760,90004325,69848388,91802888,36505482,9188443,89811711,1569515,99604946,11923835,90090964,53632373,77272628,58749,86543538,23110625,56473732,32161669,13470059,65047700,67031644,28734791,45428665,6986898,21001913,63967300,29819940,79191827,30787683,62936963,29834512,68102437,20642888,8791066,89046466,16684829,5267545,88653118,79136082,93359396,59910624,53802686,59109400,54606384,56955985,9058407,15535065,22108665,84349107,24826575,44479073,64098930,20867149,88251446,51315460,57393458,17058722,26292919,86798033,51464002,3088684,48893685,45919976,1431742,60581278,37192445,60430369,37435892,40534591,93562257,39847321,19486173,59318837,73617245,67124282,75820087,13862149,61141874,63506281,59371804,39201414,43506672,7685448,36685023,76671482,11365791,44842615,82651278,14349098,61897582,40152546,20486294,62051033,90272749,41481685,96709982,37280276,8115266,88904910,55850790,16424199,24915585,82779622,37675718,20681921,28928175,38008118,9175338,83651211,66611759,77377183,55470718,78300864,47887470,73021291,58751351,78602717,38365584,44983451,68041839,12416768,69605283,26114953,38009615,10453030,14731700,48675329,30771409,70782102,72777973,85711894,72274002,91831487,15948937,17068582,8733068,26998766,26063929,49597667,52293995,49641577,50007421,82979980,1204161,96906410,3880712,21993752,3773993,54868730,17894977,12571310,8913721,8128637,31733363,18699206,3509435,84187166,2717150,26102057,73222868,45049308,1250437,84127901,16971929,45667668,66903004,91281584,89499542,66885828,46870723,4515343,18833224,4985896,72725103,98130363,72357096,81898046,45848907,94595668,57961282,37620363,71920426,23569917,62740044,42199455,61859143,84840549,71300104,16595436,63059024,73031054,7182642,77284619,32699744,4199704,78589145,9599614,85116755,92530431,31904591,55602660,3183975,76825057,9623492,36468541,19457589,48774913,40027975,98648327,30090481,98653983,15075176,54199160,2331773,81172706,8651647,19939935,86821229,22942635,36933038,94090109,97641116,52261574,33123618,84406788,68939068,53547802,29188588,35512853,78884452,33431960,46851987,10309525,51507425,22450468,95726235,83155442,29029316,97783876,95010552,92554120,45237957,76170907,13173644,72373496,52613508,20836893,47213183,64157906,73168565,68702632,39171895,65271999,74357852,56484900,99297164,72738685,44481640,65081429,62762857,44627776,83789391,44915235,95251277,91240048,76540481,48892201,51426311,47738185,6157724,43501211,23848565,30463802,21070110,60106217,66319530,76315420,62428472,5073754,4798568,73235980,78766358,8055981,60102965,44060493,39986008,82052050,77620120,79880247,29466406,38341669,17539625,44846932,6808825,73124510,66137019,85695762,16567550,54663246,95395112,21397057,6505939,35996293,67227442,18806856,33061250,86301513,67793644,85023028,51472275,77301523,2208785,84293052,19891772,93057697,63015256,65074535,17365188,31727379,44550764,69355476,43376279,92604458,37659250,63152504,99224392,61623915,30764367,9829782,7229550,72614359,40356877,68128438,76330843,92215320,27185644,7687278,65017137,55189057,11757872,39553046,62357986,26776922,66271566,35192533,57658654,36808486,79806380,39373729,68204242,83533741,53084256,28550822,4064751,63628376,4091162,10358899,44348328,90310261,86460488,74441448,75052463,99690194,44177011,56461322,22405120,26139110,98462867,45794415,38658347,18001617,55669657,247198,65275241,19898053,569864,19272365,20765474,1197320,93053405,40197395,23194618,59329511,66667729,56424103,84684495,48088883,33336148,47361209,36580610,66322959,33304202,24058273,44844121,824872,67513640,83269727,89217461,51715482,49271185,4806458,10597197,84166196,33249630,2917920,36312813,44847298,20002147,27625735,68316156,99021067,42307449,73786944,25842078,26744917,37891451,62803858,34667286,10961421,99226875,9603598,87598888,34044787,68875490,68824981,55770687,92803766,78218845,22200378,27411561,69136837,67084351,66428795,34432810,71083565,86118021,3487592,11161731,8373289,32159704,99549373,91990218,89419466,17857111,18131876,38645117,42237907,82726008,96318094,65338021,59177718,508198,26664538,68694897,30366150,96852321,92692978,17957593,92787493,61263068,18411915,61859581,45995325,14363867,69255765,33237508,4095116,17386996,14220886,17016156,50668599,30694952,21919959,58180653,80246713,92867155,22766820,36189527,15148031,6525948,72278539,79821897,1375023,42692881,40781449,8129978,28796059,17976208,45617087,61928316,15902805,53393358,36812683,90654836,5822202,23432750,47090124,8634541,83302115,7517032,55615885,37501808,86242799,89804152,4069912,98371444,16099750,16387575,6038457,415901,66116458,51466049,24168411,71965942,45990383,20568363,81274566,22113133,76960303,34236719,74452589,7646095,41092102,61982238,75135448,54058788,81855445,94360702,9257405,36135,42073124,14947650,6521313,9259676,92692283,30395570,75229462,11543098,62232211,92998591,36396314,99729357,51588015,7423788,70541760,97379790,67451935,24314885,77187825,33895336,71333116,176257,54014062,58224549,91141395,74724075,59197747,41245325,47792865,50702367,76434144,31126490,51149804,70727211,64602895,3294781,13348726,61960400,82339363,81774825,77072625,91727510,86022504,34493392,83747892,48395186,55960386,99917599,36753250,5832946,66663942,84002370,70367851,6497830,36845587,31161687,55143724,68110330,33699435,72732205,49882705,74614639,72793444,70420215,92541302,26507214,12024238,14045383,77787724,57241521,83368048,40984766,2891150,16380211,15163258,26734892,72019362,59614746,8807940,72238278,90158785,20645197,72495719,64055960,17764950,4204661,22997281,10264691,62430984,91664334,32590267,49598724,78785507,81805959,2607799,45075471,92071990,48658605,83083131,28787861,82327024,78909561,22879907,71316369,54987042,25636669,69697787,29932657,29516592,71522968,3633375,36139942,5482538,61712234,60176618,13819358,73392814,7955293,69579137,30163921,30998561,74137926,78561158,67474219,6871053,61815223,38256849,85242963,32058615,7066775,16054533,52204879,18504197,57020481,34497327,8729146,83150534,87720882,59405277,99333375,29510992,14626618,19376156,32274392,22435353,70372191,16019925,4978543,26863229,61728685,42967683,68000591,79942022,23740167,6793819,45381876,4787945,90013093,67030811,33935899,51590803,41442762,26275734,79922758,1936762,83133790,54232247,91938191,54263880,4434662,23134715,3233569,75407347,37224844,79322415,94539824,94911072,79738755,72358170,95290172,71469330,17146629,13468268,76703609,5640302,30891921,62115552,26397786,82532312,97057187,32426752,40781854,99971982,83948335,92353856,7011964,50567636,62552524,77898274,11581181,88698958,99515901,9575954,82897371,17385531,32151165,24953498,57163802,16445503,82178706,45407418,42947632,59981773,87710366,37166608,49328608,6819644,7300047,56153345,90061527,44664587,93270084,32250045,27187213,60393039,10649306,77694700,14723410,8099994,75128745,37560164,21289531,31491938,89996536,22129328,42644903,43152977,85117092,96420429,47893286,38510840,41092172,12303248,29065964,73109997 +19939935,19891772,29819940,78602717,8807940,45075471,68816413,33061250,39171895,92541302,26392416,97783876,63967300,73235980,3773993,99658235,40197395,12348118,55602660,28851716,669105,66832478,96726697,25842078,39801351,2717150,81172706,97561745,18783702,49641577,66250369,38022834,17068582,60102965,1431742,8696647,99224392,56461322,91957544,23848565,62496012,51715482,38061439,35192533,7517032,42782652,77312810,19457589,44889423,4119747,60955663,62051033,6505939,95395112,29959549,22108665,69697787,8099994,16971929,30998561,2204165,30764367,83155442,43322743,4099191,66045587,79880247,65074535,91727510,44842615,72357096,12416768,6153406,20486294,67474219,43506672,65275241,23569917,9860195,69786756,35512853,93562257,73392814,79821897,27665211,85023028,99515901,37620363,99861373,57240218,34493392,62430984,14349098,4985896,26507214,61815223,17016156,59910624,75777973,76540481,67281495,22942635,7955293,15948937,97379790,43933006,73124510,40686254,44479073,93270084,98948034,65017137,1204161,48260151,73031054,84293052,14947650,85695762,77284619,36189527,4806458,5832946,93515664,77898274,8733068,82886381,58180653,37501808,68824981,85242963,72495719,66903004,29834512,29029316,40356877,45667668,90090964,36753250,69605283,73168565,45237957,55247455,11923835,55189057,62428472,92398073,87598888,4978543,51464002,56473732,59197747,32590267,29994197,19376156,13468268,4798568,48673079,21919959,25636669,11543098,2607799,7502255,69255765,36930650,68102437,81855445,73786944,71920426,45990383,81898046,99524975,78909561,23194618,97011160,90272749,86242799,98739783,61271144,63372756,569864,3233084,32161669,81274566,45996863,18411915,70004753,79806380,13470059,74614639,53547802,18699206,17037369,4204661,33553959,26734892,65454636,70036438,7229550,42073124,76960303,54987042,48088883,75820087,168541,36808486,33304202,78549759,7919588,26292919,4668450,28928175,64055960,56955985,60430369,16445503,67227442,82979980,62803858,51047803,112651,29188588,47708850,30811010,84406788,55960386,83210802,508198,45794415,29065964,70800879,53802686,89078848,64602895,79136082,42237907,99549373,59318837,55669657,68644627,70372191,64157906,20568363,77377183,58749,81677380,74743862,82897371,65880522,9886593,43376279,33201905,89419466,72777973,89637706,10366309,68694897,84684495,95290172,89811711,67513640,16019925,16595436,38365584,19486173,67451935,98943869,66116458,20140249,91990218,40677414,70727211,80014588,8115266,4434662,45995325,55615885,4069912,80555751,99965001,53648154,30139692,8729146,77272628,64087743,70596786,16387575,21289531,52613508,27411561,96852321,26863229,5111370,53632373,33336148,22129328,42644903,91240048,51466049,87720882,71316369,36505482,16424199,62740044,54762643,50007421,31904591,66137019,9599614,73617245,88653118,9398733,7423788,17386996,36312813,92692283,6986898,36580610,30543215,83789391,54014062,17081350,10358899,99226875,247198,37192445,45790169,99604946,49597667,49598724,30395570,54058788,19898053,66322959,99021067,13862149,8791066,40781449,27041967,57803235,91141395,24733232,22997281,37435892,55143724,83083131,8651647,52204879,62452034,76330843,86821229,24953498,75153252,44844121,23134715,54199160,22435353,97641116,65038678,33431960,50188404,31491938,92803766,3233569,82532312,61859143,77620120,3294781,89214616,92554120,19272365,73021291,88047921,51149804,17146629,89996536,37739481,57241521,96193415,83533741,62693428,69136837,34236719,38645117,70367851,71083565,20765474,80251430,28550822,91664334,71657078,13231279,526217,51315460,20867149,17539625,16097038,41481685,84349107,90004325,5640302,35092039,45306070,76703609,61728685,74137926,49271185,46540998,39553046,16099750,79191827,44983451,17058722,16054533,37280276,74357852,54606384,29466406,8913721,67793644,4787945,50702367,1788101,40152546,46851987,54427233,18833224,26275734,45848907,12571310,71333116,64098930,29932657,20885148,68316156,10649306,34698428,38341669,44846932,62936963,89804152,94911072,56515456,75052463,2208785,93053405,11365791,92215320,72278539,94360702,99971982,415901,94539824,85117092,87160386,71522968,34295794,76315420,36845587,39847321,94076128,24619760,78766358,85571389,84840549,33628349,34497327,17727650,53666583,48675329,36685023,3509435,98648327,1936762,46870723,66611759,22879907,65047700,68875490,68204242,91281584,52060076,7687278,35456853,99729357,4064751,64848072,874791,86001008,95726235,66319530,53393358,62490109,44550764,99297164,66885828,74452589,8634541,32058615,33237508,54517921,84904436,32274392,30163921,41092102,57393458,44481640,85711894,41245325,23110625,31126490,65338021,61960400,96318094,30090481,9058407,47361209,56531125,7104732,35996293,38008118,88904910,30694952,27625735,92787493,32159704,55753905,9257405,79657802,50567636,85116755,32250045,21673260,94090109,90061527,18504197,49236559,78561158,56153345,74110882,30569392,44627776,77072625,51507425,88251446,99333375,44784505,90310261,14093520,26744917,72019362,20642888,76170907,20122224,6111563,45919976,70541760,67030811,26397786,24168411,59177718,40781854,749283,63015256,97281847,43501211,23432750,72274002,98462867,13348726,6793819,56424103,22721500,36933038,59614746,55770687,91802888,7011964,33797252,86460488,14045383,63628376,61897582,34946859,1197320,26493119,17976208,53842979,3880712,3183975,61373987,88130087,8055981,11581181,58224549,22405120,42199455,84166196,32426752,44915235,92071990,22450468,77413012,7685448,60581278,54263880,47213183,97940276,76825057,92604458,39201414,21070110,37659250,92353856,37183543,93933709,38494874,47792865,72732205,63152504,36468541,41442762,6525948,37957788,5822202,83948335,67031644,47090124,29401781,17894977,10309525,72614359,41380093,79942022,17764950,78589145,78785507,31727379,9575954,78884452,72738685,51472275,10961421,14731700,43045786,45407418,59501487,75229462,53084256,26998766,21993752,15536795,65851721,39986008,321665,34698463,14220886,48360198,10597197,71300104,68041839,48658605,43152977,3235882,36396314,21533347,31161687,59329511,81853704,15163258,92692978,80316608,52261574,59405277,57020481,84127901,15075176,78218845,54868730,76434144,5482538,51590803,75543508,92998591,98130363,93057697,68000591,40984766,60106217,68939068,92033260,93359396,30787683,11161731,36780454,2917920,48892201,62232211,48395186,8373289,6497830,62357986,47298834,37166608,69848388,51588015,15148031,6808825,7066775,94516935,4199704,95957797,24591705,75128745,28787861,65715134,14626618,15535065,91831487,17857111,88444207,72238278,68702632,34432810,61263068,9623492,30653863,43357947,18466635,19101477,45428665,26776922,34044787,26139110,33123618,39373729,57248122,82427263,95581843,61928316,82726008,93566986,24915585,49328608,69641513,38510840,84002370,26664538,4091162,6521313,14326617,20002147,45381876,26102057,29510992,84187166,36812683,81805959,61741594,86301513,89046466,82178706,65271999,38658347,71469330,23740167,44847298,99917599,40534591,42692881,87710366,4095116,40865610,16684829,1375023,72373496,63059024,83651211,61623915,3487592,52293995,90158785,4508700,31733363,60176618,30366150,30771409,44348328,69579137,74441448,30463802,18001617,65081429,90457870,30891921,47887470,98371444,75986488,33935899,61141874,59109400,24826575,58751351,66663942,93790285,9829782,8128637,57359924,62115552,99125126,59371804,44664587,37675718,90013093,44473167,6819644,15064655,62552524,41092172,83150534,18806856,15902805,91938191,18663507,66667729,11757872,6871053,27185644,42307449,74724075,9603598,37560164,83302115,24058273,17385531,28796059,9175338,99690194,76671482,12024238,5267545,63506281,61859581,37224844,61712234,38256849,82651278,2331773,29516592,82779622,83747892,71965942,4515343,96735716,176257,16380211,38009615,77694700,7182642,15015906,66428795,86022504,22200378,9860968,6157724,69355476,47738185,98653983,44245960,27187213,824872,73222868,16567550,90654836,88698958,1250437,50806615,28734791,36139942,57961282,7646095,48774913,1569515,20681921,62762857,89699445,89217461,75407347,29635537,42947632,72358170,92530431,79322415,54663246,49882705,22766820,54232247,21397057,16405341,24314885,55470718,40027975,57163802,82339363,8129978,45049308,9188443,66271566,2891150,33895336,33699435,26063929,80246713,32699744,70782102,5970607,50668599,14723410,30218878,53888755,18131876,92867155,70420215,61982238,97057187,14363867,6038457,79738755,42967683,96420429,44177011,86798033,91255408,82052050,83368048,45617087,17957593,1239555,75135448,77300457,77301523,95010552,20645197,47893286,6221471,27325428,67084351,58208470,67124282,36135,26114953,86543538,68128438,13173644,48893685,3633375,94595668,12303248,81774825,73109997,79922758,12664567,96709982,10264691,1022677,72725103,33249630,68110330,10453030,72793444,7300047,95251277,57658654,56484900,17365188,33565483,21001913,32151165,82327024,37891451,55850790,83133790,77187825,96906410,94711842,3088684,22113133,78300864,44060493,9259676,51426311,86118021,77787724,60393039,34667286,83269727,59981773,5073754,89499542,13819358,20836893 +42782652,44473167,62496012,66250369,7502255,29188588,10358899,17081350,69605283,84684495,10366309,38494874,569864,70004753,77694700,98371444,29994197,65851721,16097038,824872,24733232,49641577,63152504,34295794,13231279,4099191,23569917,8791066,6808825,77284619,73021291,93057697,29510992,56424103,44245960,56531125,71920426,82178706,81677380,51047803,72777973,72274002,9886593,55850790,18783702,77312810,33201905,34698463,73222868,36505482,59371804,20486294,88444207,14220886,98648327,45237957,90090964,95290172,3633375,14326617,88047921,23848565,26292919,61982238,68041839,73235980,96193415,4119747,93515664,13470059,75543508,38061439,41245325,33061250,9188443,45790169,24915585,63506281,44842615,40197395,17037369,93933709,27411561,79821897,86460488,74110882,32699744,26863229,50806615,29959549,55470718,66832478,34946859,81172706,18663507,35996293,65880522,18806856,20140249,7229550,37620363,79657802,48892201,92398073,44550764,56153345,9860195,73392814,12348118,41481685,83150534,94516935,86022504,9575954,24168411,30771409,44177011,24619760,72725103,98130363,44889423,68816413,87160386,6111563,81855445,98739783,82886381,54517921,14093520,98948034,55753905,64087743,61741594,57803235,77787724,55247455,99515901,83789391,63967300,44627776,6525948,66116458,30395570,46540998,84904436,17068582,54014062,53842979,70800879,78602717,49236559,81853704,33304202,70036438,68644627,89078848,52060076,83651211,31904591,82726008,63015256,58208470,93053405,96726697,36753250,28928175,34493392,68875490,33237508,15535065,43045786,66322959,59981773,40686254,72495719,63372756,30218878,33797252,89637706,91957544,24826575,43506672,38658347,20836893,77072625,47213183,68204242,55143724,8055981,57359924,50668599,32161669,31126490,74743862,35456853,21533347,60393039,321665,82979980,70596786,6038457,85116755,87720882,16019925,89214616,35192533,19486173,60430369,95395112,67281495,56515456,56955985,29029316,17727650,94539824,91831487,42199455,66428795,37739481,50702367,22942635,92787493,33431960,99917599,29466406,79322415,69848388,99125126,8696647,5970607,37675718,1239555,99224392,89419466,3233084,38009615,22721500,51472275,50188404,29819940,92692978,74452589,71083565,72019362,30543215,26998766,41380093,80251430,247198,27325428,65715134,76540481,48893685,82339363,1569515,45306070,1788101,33565483,68102437,11543098,16099750,7011964,1204161,36312813,92604458,75153252,65038678,77300457,83133790,44784505,4508700,53393358,13468268,78766358,26734892,86118021,42237907,45381876,88251446,70367851,25636669,36780454,57393458,27665211,41092172,62051033,61271144,61141874,14947650,89499542,7066775,20867149,54762643,29635537,83302115,48673079,30787683,88653118,749283,55189057,83533741,68694897,82327024,669105,14349098,10453030,62693428,19101477,68000591,73031054,29834512,99021067,62740044,9623492,90310261,60102965,32590267,1250437,49328608,18466635,40152546,4434662,12664567,39801351,15015906,10309525,39201414,26102057,526217,51588015,42967683,77620120,58749,15064655,32274392,44847298,7646095,60581278,6221471,69355476,97561745,96852321,26392416,67474219,80555751,61815223,62430984,77413012,37891451,99971982,9603598,4515343,16054533,43501211,30366150,38341669,34698428,64055960,41442762,22200378,92071990,36580610,62762857,63059024,42644903,33628349,69579137,86001008,59109400,65271999,30653863,20122224,35092039,84166196,44479073,9860968,99524975,86798033,8128637,48260151,82651278,45407418,95581843,37501808,15148031,45428665,30463802,99861373,3294781,72793444,2204165,65017137,77272628,21993752,7517032,43376279,508198,96709982,55770687,24314885,74357852,44983451,47298834,45848907,91990218,8115266,49271185,59177718,30998561,33699435,82427263,16971929,8729146,40781854,67124282,2331773,62452034,90272749,36930650,4668450,51464002,17365188,49597667,39553046,9398733,4985896,99549373,48360198,81274566,30811010,59318837,72357096,70541760,97940276,72358170,62936963,61623915,91938191,16405341,5073754,36135,78300864,59501487,92530431,71469330,62232211,36685023,64602895,5482538,50007421,76434144,53648154,54987042,65081429,66663942,99297164,7104732,26139110,9599614,71316369,1375023,59910624,83269727,82897371,3487592,16595436,39373729,38022834,45617087,40677414,73168565,20885148,90013093,53666583,80246713,8913721,2917920,92033260,86543538,61859143,3183975,30569392,63628376,47361209,31727379,84127901,40984766,20642888,22405120,26114953,78884452,47792865,22113133,91255408,7687278,44060493,64848072,84349107,57658654,45996863,97011160,8373289,48774913,40356877,39847321,99965001,19272365,3088684,61373987,4064751,65074535,26507214,28851716,3880712,23194618,66885828,78589145,54427233,95010552,85023028,89699445,85711894,6871053,94711842,45794415,92554120,2891150,95957797,22129328,79136082,62490109,91802888,11923835,94595668,46851987,22108665,11161731,37183543,94090109,91141395,6986898,87598888,41092102,93790285,58224549,14626618,89811711,874791,60106217,20681921,19898053,99658235,24058273,54199160,8651647,10597197,19891772,51590803,16380211,76170907,81898046,26776922,67031644,76315420,24591705,75407347,69255765,12416768,69641513,3235882,76960303,38365584,67030811,99729357,53802686,62552524,79922758,7423788,47708850,38256849,66137019,30139692,66611759,86301513,80014588,98943869,57240218,38645117,96318094,7955293,95726235,83210802,61712234,69697787,16424199,64157906,8733068,54868730,49598724,61960400,15163258,2208785,75777973,4978543,32426752,14045383,28550822,39171895,34236719,78785507,5267545,16567550,97281847,73617245,55602660,16445503,66319530,33553959,17957593,53888755,91281584,13173644,68702632,20568363,62428472,56484900,53547802,79880247,42073124,91240048,96906410,71657078,88698958,96735716,17764950,5822202,40781449,28787861,32151165,67227442,5111370,52261574,89046466,98462867,68939068,1431742,68316156,91664334,18699206,168541,22879907,37560164,67793644,34044787,65047700,92803766,48658605,33249630,57163802,30694952,34497327,21289531,76703609,83747892,45919976,10961421,62803858,415901,96420429,72373496,56473732,47887470,54232247,26493119,51426311,93359396,93562257,30764367,43933006,58751351,71522968,84840549,67084351,71333116,79191827,42307449,69136837,69786756,42692881,65454636,92998591,11581181,28734791,17385531,26275734,55669657,75820087,75128745,17894977,20645197,74614639,14731700,70727211,37192445,37659250,6153406,61928316,19376156,65338021,70372191,74724075,11757872,22435353,19939935,93270084,53084256,57020481,21070110,49882705,59197747,50567636,89804152,91727510,78549759,27185644,81805959,78909561,79738755,35512853,55960386,29065964,23432750,1936762,90004325,13348726,84002370,14723410,43322743,31733363,79806380,85242963,27187213,6793819,16684829,76825057,20002147,97641116,32159704,37224844,21673260,44481640,15075176,43357947,75135448,23134715,7919588,45667668,10649306,66271566,37435892,7685448,54263880,4204661,40027975,32058615,68110330,17146629,9175338,82052050,75052463,15536795,26063929,98653983,3509435,45990383,89217461,45075471,51507425,79942022,51315460,44664587,93566986,54663246,78218845,18411915,92867155,70782102,1197320,2717150,36812683,22997281,44844121,13819358,68128438,29932657,72732205,61728685,6157724,33895336,45049308,21397057,59329511,53632373,66667729,61859581,48675329,99604946,88904910,85571389,52204879,76330843,52293995,68824981,45995325,84293052,8634541,36808486,9829782,7300047,4095116,99226875,70420215,29401781,83948335,85695762,87710366,77377183,77301523,33123618,72278539,73109997,60955663,97783876,12571310,40865610,90654836,84406788,32250045,29516592,4798568,89996536,6497830,30090481,92353856,59405277,46870723,19457589,38008118,58180653,9058407,27625735,6505939,112651,10264691,36933038,74137926,17386996,80316608,54606384,6521313,2607799,94360702,5832946,24953498,92541302,22450468,97057187,36396314,57961282,51149804,83083131,90457870,44348328,82532312,8807940,40534591,1022677,72738685,21001913,25842078,36189527,77898274,72238278,86821229,20765474,78561158,92692283,23110625,65275241,31161687,4091162,74441448,51715482,17539625,4199704,22766820,39986008,18131876,3773993,26397786,6819644,66045587,95251277,55615885,61263068,17976208,44846932,51466049,4069912,37957788,90158785,47090124,27041967,42947632,26664538,52613508,57248122,17857111,67451935,17016156,86242799,88130087,7182642,66903004,72614359,71965942,34432810,26744917,71300104,47893286,75986488,30163921,61897582,18504197,17058722,18001617,92215320,56461322,83368048,83155442,33935899,99690194,37166608,9257405,16387575,15948937,30891921,18833224,36468541,94076128,11365791,13862149,34667286,44915235,12024238,99333375,33336148,36845587,9259676,73786944,176257,5640302,64098930,14363867,8129978,28796059,77187825,97379790,84187166,15902805,62115552,47738185,59614746,73124510,90061527,4806458,3233569,43152977,31491938,57241521,54058788,76671482,82779622,4787945,94911072,60176618,48395186,12303248,48088883,8099994,62357986,23740167,37280276,36139942,75229462,38510840,81774825,85117092,21919959,67513640 +45919976,22942635,56515456,86001008,44481640,88653118,35456853,95957797,8651647,8634541,526217,61373987,50188404,57961282,90310261,97783876,29932657,97281847,112651,11543098,8696647,56424103,61859143,68000591,84293052,83210802,26392416,59109400,73124510,98371444,62452034,73222868,36780454,36933038,19272365,36808486,70367851,36189527,2917920,5970607,31733363,73168565,39801351,4668450,62803858,31904591,8807940,36312813,7011964,33249630,35996293,6808825,85117092,68204242,92554120,16380211,64157906,53547802,70420215,62496012,93933709,79657802,60106217,61263068,36812683,18783702,26114953,54199160,57163802,24733232,48892201,56531125,69641513,76703609,53888755,5640302,99125126,26063929,20002147,37183543,53393358,14093520,77898274,63059024,39847321,99861373,65081429,55189057,9886593,14947650,44177011,82178706,50702367,39986008,66611759,36685023,34698463,68644627,48893685,61141874,49328608,82979980,95726235,66667729,19939935,73786944,88698958,42782652,91255408,24591705,29994197,38008118,415901,96318094,71522968,81853704,21397057,36580610,68110330,5482538,93270084,70372191,43376279,36505482,17857111,62490109,16019925,64098930,81855445,17016156,7104732,52293995,45790169,75543508,6221471,53802686,29065964,76170907,32159704,16445503,83533741,37620363,34295794,44842615,6153406,40865610,94076128,14349098,247198,7182642,59501487,49597667,17764950,19486173,69136837,84904436,41380093,91957544,5111370,85242963,32426752,15536795,9829782,77284619,60102965,4798568,44550764,45407418,18806856,72614359,40027975,84349107,569864,61897582,51047803,94711842,43501211,9603598,72738685,4099191,27665211,99604946,71657078,14220886,17976208,98462867,83302115,3773993,43322743,74357852,80555751,33895336,16387575,34698428,71965942,36753250,72373496,44060493,96193415,80014588,24058273,26664538,77312810,85023028,18699206,75407347,53084256,23134715,51472275,5832946,38061439,61982238,67281495,55770687,71333116,17365188,3880712,76434144,37739481,77187825,76330843,78766358,10358899,44889423,27187213,80246713,65038678,14045383,1788101,84187166,21070110,3088684,94539824,65017137,39171895,7517032,30771409,47090124,37957788,62552524,71300104,12024238,13348726,28787861,31491938,99690194,72777973,98739783,58749,90013093,55470718,95395112,28928175,28550822,13173644,59329511,92803766,7955293,25842078,76540481,77694700,38365584,11161731,49641577,34493392,29819940,61960400,66832478,77413012,70036438,18131876,13470059,39553046,92033260,72793444,15064655,33123618,45848907,99515901,93359396,58208470,34432810,82052050,41092172,99021067,89078848,11923835,16567550,13819358,30218878,23740167,3294781,45428665,18663507,41092102,9575954,83368048,45667668,49882705,99549373,21993752,15535065,1375023,93566986,48774913,67793644,90457870,32590267,34044787,23569917,88444207,22129328,60430369,63967300,14731700,26744917,64055960,3235882,96906410,2331773,61271144,13231279,74441448,77787724,874791,12416768,37166608,89499542,92604458,16405341,55669657,68824981,6793819,45996863,6986898,38494874,84840549,78589145,30694952,74137926,23848565,37192445,64602895,47213183,93057697,16097038,13862149,9188443,57359924,33336148,99917599,43933006,53648154,70541760,36930650,12664567,69848388,59318837,37891451,15148031,70004753,66137019,6505939,19376156,94516935,79738755,77300457,64087743,10649306,17727650,86821229,26139110,66250369,29401781,96726697,16971929,58751351,96735716,30764367,48360198,90004325,90061527,40686254,54663246,6819644,30366150,66428795,89214616,9257405,11581181,45990383,54014062,65338021,40152546,22405120,33797252,6038457,98653983,77301523,52060076,88047921,53666583,68816413,8055981,65880522,87598888,64848072,3633375,18833224,33237508,72238278,57248122,73031054,2607799,91990218,81898046,51315460,97561745,95290172,62430984,18504197,93790285,95581843,67451935,10366309,10961421,24619760,81805959,59177718,38645117,41442762,32161669,79806380,97379790,54762643,3233084,97011160,33304202,35092039,33565483,21919959,17385531,53842979,44245960,73235980,20885148,30395570,65074535,78300864,40677414,85116755,42967683,8129978,86022504,15948937,29834512,32250045,79191827,82779622,31126490,27325428,95010552,28734791,18411915,49236559,18466635,69355476,72357096,50668599,7687278,1569515,76671482,48675329,66885828,4434662,50007421,80316608,67124282,33201905,72019362,37501808,44983451,83133790,71083565,10597197,43045786,60393039,47738185,96420429,51426311,19101477,78218845,55602660,89637706,7919588,15163258,1022677,61815223,27411561,9623492,98648327,92530431,20867149,69255765,57803235,86460488,42307449,82651278,72495719,92787493,19898053,97940276,33628349,79942022,66045587,98943869,4806458,6157724,90272749,4204661,9398733,7646095,74452589,1204161,22879907,98130363,46870723,87710366,34236719,73392814,62051033,4787945,12348118,82339363,89046466,70596786,34667286,48658605,17058722,7502255,29959549,94090109,83789391,51590803,54232247,1250437,89419466,72725103,37659250,75777973,45995325,28796059,50806615,2891150,1936762,29466406,63152504,43152977,24953498,17037369,66663942,22113133,22450468,4508700,99524975,83083131,23194618,61859581,26493119,824872,37675718,97057187,98948034,55247455,19891772,36139942,99658235,39373729,30139692,87160386,77272628,26507214,20681921,15015906,83150534,37280276,75986488,70727211,44348328,30569392,55960386,61623915,92692283,97641116,508198,26998766,54606384,62936963,321665,9259676,87720882,44473167,52261574,70800879,45075471,60581278,21533347,8128637,29510992,30998561,17146629,91141395,7300047,7423788,91831487,5267545,63506281,79922758,65275241,48395186,40534591,11757872,94911072,16424199,29635537,7685448,1431742,9599614,57020481,45617087,80251430,91727510,91802888,37224844,83948335,81677380,51464002,81274566,28851716,2717150,62693428,71920426,33431960,96709982,11365791,20486294,43506672,26292919,78602717,24915585,30163921,94595668,90090964,62762857,4064751,1197320,69786756,56484900,42073124,68939068,84127901,63372756,47298834,35512853,59197747,39201414,8733068,31161687,51507425,68128438,8729146,5822202,26397786,17068582,20765474,72274002,4119747,22997281,51588015,84684495,21001913,57658654,1239555,61928316,73617245,669105,35192533,85571389,6497830,66903004,14626618,5073754,55753905,68102437,72732205,55143724,2208785,54427233,72278539,8913721,42692881,82327024,77072625,4199704,93562257,92692978,83269727,25636669,59910624,59371804,57393458,4515343,93053405,95251277,63015256,59981773,54263880,88904910,53632373,57241521,9058407,24826575,68316156,17957593,168541,75135448,68702632,52204879,45237957,62232211,74743862,93515664,22766820,79136082,43357947,45794415,68875490,88251446,22435353,54868730,8791066,40781449,42644903,82726008,29516592,67227442,56473732,76960303,30787683,75229462,79821897,91664334,40356877,78909561,84166196,99729357,20836893,44627776,17539625,81172706,26776922,40781854,34497327,16099750,38009615,14326617,8115266,20568363,32699744,44846932,3509435,33553959,92215320,15075176,71316369,67031644,51715482,99297164,44784505,74110882,26734892,41245325,8099994,76825057,77620120,62740044,74724075,89811711,58224549,52613508,60955663,91938191,68041839,6521313,71469330,38341669,4095116,61728685,42947632,33699435,27625735,18001617,22108665,69605283,65715134,82897371,99965001,38022834,3487592,749283,51149804,9860195,45306070,82427263,55615885,72358170,89699445,4978543,36396314,86301513,47361209,20645197,29029316,8373289,16595436,30090481,19457589,92998591,96852321,45049308,40197395,20642888,67030811,99333375,6871053,14363867,83155442,38658347,90654836,37560164,60176618,33061250,3183975,92071990,44844121,30463802,56461322,48673079,89804152,30811010,56153345,59405277,10309525,176257,92398073,86798033,36135,69579137,21673260,50567636,48260151,40984766,89996536,36468541,4091162,86543538,22200378,92541302,65047700,77377183,67084351,44915235,59614746,65851721,13468268,23432750,89217461,83651211,82886381,7229550,32058615,10453030,47708850,99226875,79880247,34946859,83747892,74614639,86118021,44479073,33935899,65271999,22721500,67513640,62357986,38256849,56955985,47887470,84002370,3233569,91240048,16054533,42237907,27041967,62428472,15902805,30891921,63628376,4069912,48088883,82532312,78561158,38510840,27185644,9860968,66322959,26863229,46540998,54058788,92867155,10264691,54987042,47893286,92353856,66319530,17386996,47792865,65454636,30543215,67474219,12571310,66116458,46851987,85695762,75153252,84406788,91281584,54517921,78785507,66271566,26102057,75128745,94360702,61741594,29188588,24314885,68694897,49598724,78884452,99224392,31727379,20140249,44664587,37435892,20122224,88130087,85711894,69697787,78549759,2204165,99971982,76315420,49271185,21289531,81774825,6525948,70782102,23110625,86242799,9175338,90158785,6111563,55850790,75052463,58180653,26275734,17894977,32274392,62115552,41481685,73109997,79322415,14723410,4985896,30653863,44847298,57240218,36845587,61712234,16684829,7066775,51466049,12303248,42199455,17081350,73021291,24168411,75820087,45381876,32151165 +72725103,91727510,98371444,27665211,84406788,96193415,44889423,95957797,50702367,22450468,29065964,95395112,44348328,88653118,37891451,77787724,29834512,89419466,82052050,51464002,15015906,44983451,62452034,4668450,90457870,10649306,9259676,28796059,33431960,49882705,6793819,47213183,46851987,70420215,7955293,23848565,22129328,16445503,83155442,23194618,60102965,83210802,6505939,77284619,56484900,68824981,84684495,8129978,53802686,99515901,66663942,40984766,62740044,16054533,2717150,35192533,82726008,45407418,1431742,42692881,95290172,73124510,19939935,99549373,669105,7423788,6525948,321665,75135448,42199455,9575954,61263068,99971982,18833224,14220886,81853704,30543215,20568363,32250045,25636669,96318094,69136837,99917599,415901,47090124,4515343,34698463,61960400,39801351,65851721,63152504,45790169,33699435,74724075,31904591,62552524,17068582,35456853,93053405,37435892,9623492,79191827,89214616,37620363,88047921,81898046,69355476,49328608,18806856,13470059,44479073,32590267,45990383,57163802,61982238,91802888,18663507,74137926,62762857,48892201,11543098,6808825,83150534,10366309,20140249,16971929,20002147,44481640,59614746,4798568,61271144,33304202,76434144,77272628,15535065,78602717,93270084,94516935,42782652,72274002,54987042,45919976,21993752,65275241,18411915,68939068,22108665,39986008,59318837,61623915,39201414,508198,45049308,98648327,11161731,75986488,9886593,72738685,55247455,87710366,64087743,62803858,3633375,18504197,59371804,99524975,68204242,33797252,99658235,5970607,77620120,54663246,40197395,10309525,44245960,75407347,14947650,68702632,91990218,52613508,40677414,76540481,3773993,96726697,55753905,39553046,15148031,87720882,38645117,82178706,30891921,55470718,74110882,33237508,83368048,89996536,72732205,38008118,65017137,749283,51588015,34698428,89637706,67793644,17764950,54762643,8729146,37659250,43376279,55770687,44784505,40686254,44842615,55669657,33628349,72373496,65038678,247198,56515456,74441448,66137019,31161687,44473167,12571310,70367851,59197747,84187166,26139110,35092039,5111370,26776922,66250369,42947632,30771409,26863229,65715134,58180653,12416768,45848907,80246713,82979980,4069912,33249630,53547802,99333375,29959549,14626618,91664334,79821897,93057697,13173644,29932657,54199160,84840549,83533741,7685448,78589145,4099191,53393358,76960303,8807940,43322743,51426311,20885148,6038457,9188443,43933006,20867149,44177011,59910624,63967300,34493392,7646095,62496012,18699206,97783876,50188404,38494874,21397057,93933709,53084256,71083565,26392416,36580610,98462867,16019925,29510992,1197320,33061250,46540998,1788101,65338021,91957544,51149804,32274392,81677380,14349098,17957593,92787493,85116755,42967683,82339363,57803235,19376156,68816413,5073754,3294781,44550764,51507425,30395570,75153252,67030811,48088883,28734791,27411561,29188588,7011964,60430369,1569515,45075471,78218845,7919588,51047803,3880712,52293995,77187825,91831487,94090109,19272365,96906410,30569392,54517921,79942022,87598888,62430984,89046466,85117092,29466406,78561158,3233569,60176618,36812683,56153345,36468541,94911072,3487592,92803766,77301523,50668599,22942635,94076128,40027975,67451935,12348118,97379790,3183975,8128637,61897582,71657078,31733363,92692283,78785507,52261574,61859143,37675718,9058407,69255765,15064655,63506281,98653983,55960386,53648154,59177718,97641116,36685023,16380211,83789391,72019362,51590803,77694700,32159704,92554120,6153406,47361209,61373987,57241521,82897371,27185644,72614359,68000591,75229462,99021067,79922758,3233084,21533347,44844121,24314885,99861373,70372191,37280276,40781854,66045587,4064751,8099994,24591705,26063929,88251446,25842078,64055960,57359924,39373729,83269727,73222868,61141874,4204661,64157906,31727379,39847321,86118021,8791066,55189057,66885828,94595668,66832478,14363867,99729357,8696647,97561745,29819940,17365188,54868730,75543508,48360198,28928175,63015256,37183543,20765474,26998766,50567636,82886381,98948034,92998591,73617245,33201905,65074535,92530431,26744917,73235980,63628376,1204161,41245325,84349107,72495719,51315460,99226875,18466635,37560164,12664567,22766820,4434662,85242963,65081429,29994197,78300864,9599614,59329511,98739783,9860195,65880522,76671482,78766358,90013093,65271999,70727211,6497830,3509435,7182642,22435353,92692978,71920426,49598724,26734892,71469330,49597667,89078848,38061439,82327024,30218878,65047700,66428795,36505482,48260151,7229550,56461322,59501487,15075176,34667286,9603598,44847298,61815223,17894977,64602895,49236559,30463802,34044787,85571389,24058273,77312810,41092172,1250437,14045383,92604458,59405277,18783702,41380093,92071990,30163921,16387575,70541760,30787683,93359396,81805959,48658605,79880247,54014062,9257405,73168565,38341669,17976208,92398073,34432810,4978543,60581278,97281847,88444207,29029316,22113133,71333116,23134715,77898274,70800879,33553959,54232247,74357852,51472275,84127901,2607799,61859581,34295794,43152977,89804152,2891150,62490109,49271185,16595436,13468268,49641577,67474219,10961421,56424103,68041839,42644903,72777973,37166608,67227442,17037369,57658654,67281495,80316608,64848072,47708850,59109400,41442762,14093520,50007421,98943869,66611759,44060493,93562257,38658347,82427263,69697787,20642888,24915585,84293052,80014588,46870723,10358899,30998561,29516592,71316369,40356877,19891772,53842979,51715482,6986898,5832946,21289531,51466049,45995325,53888755,66319530,39171895,56955985,27625735,93515664,7502255,79657802,57248122,99604946,94711842,7300047,17146629,16099750,42307449,70036438,83948335,90272749,62936963,55143724,26507214,24168411,5640302,26275734,99297164,6221471,2208785,95726235,66322959,88698958,24733232,86821229,89499542,44915235,76703609,26114953,14326617,80251430,16097038,17058722,80555751,33935899,69579137,91141395,15536795,55602660,89217461,40781449,2917920,95581843,43501211,31126490,74743862,24826575,63372756,58749,28851716,20645197,68110330,9175338,76315420,11365791,8651647,72357096,52204879,86001008,58224549,37192445,45428665,56531125,36753250,41481685,15948937,48673079,12024238,53632373,7104732,60106217,4806458,2204165,30139692,83083131,79806380,34946859,72278539,36312813,35996293,97057187,14731700,57961282,68644627,50806615,8373289,91255408,36930650,17016156,60955663,27187213,3088684,94360702,33895336,45617087,36808486,4095116,34497327,76170907,23740167,99965001,72238278,83133790,95010552,43357947,1022677,18131876,44846932,68128438,30764367,5822202,569864,85023028,99224392,68102437,20486294,90090964,27325428,112651,38365584,75128745,94539824,26664538,41092102,82779622,3235882,63059024,81855445,66903004,56473732,70004753,91281584,26493119,14723410,86301513,19486173,92033260,45237957,36135,32161669,17385531,24619760,15163258,70596786,86022504,76330843,17539625,96852321,98130363,8634541,61728685,16567550,8115266,20122224,526217,57020481,81774825,23432750,37739481,37501808,4119747,71300104,78909561,6111563,17857111,4199704,81274566,54606384,7687278,73031054,8055981,61712234,81172706,16684829,30090481,8733068,69641513,13862149,57240218,32426752,5267545,24953498,64098930,77072625,73109997,74614639,84904436,21070110,9860968,37224844,87160386,92353856,45381876,82651278,52060076,82532312,75820087,34236719,33565483,47893286,6871053,75777973,96420429,90004325,10264691,66271566,44664587,36933038,45794415,71522968,13819358,30366150,36189527,2331773,43045786,6157724,78549759,61741594,42237907,4508700,58751351,17386996,90310261,85695762,42073124,75052463,72793444,66667729,20836893,79738755,68694897,11581181,33123618,92541302,76825057,1375023,176257,86460488,4985896,15902805,54058788,62357986,84166196,37957788,62693428,88904910,54427233,38256849,32699744,6819644,23569917,32058615,19457589,21919959,13348726,26102057,92867155,12303248,67084351,29401781,55615885,66116458,26292919,84002370,23110625,22721500,9398733,72358170,48893685,73392814,19101477,77413012,28787861,78884452,40865610,7517032,99690194,30694952,73786944,6521313,85711894,1239555,22200378,16405341,93566986,40152546,36780454,18001617,53666583,77377183,11757872,45667668,47792865,77300457,28550822,48395186,69605283,32151165,89699445,31491938,91240048,71965942,91938191,10597197,60393039,47738185,69848388,83747892,67124282,36845587,36396314,35512853,86543538,27041967,62232211,30653863,83302115,97011160,21001913,62051033,4787945,90061527,874791,45306070,9829782,90654836,68875490,16424199,22405120,43506672,96735716,11923835,93790285,90158785,10453030,824872,99125126,74452589,8913721,48675329,48774913,45996863,65454636,30811010,38022834,38009615,47887470,73021291,1936762,62428472,5482538,86242799,83651211,57393458,67031644,58208470,33336148,29635537,38510840,44627776,26397786,40534591,70782102,21673260,13231279,22997281,4091162,17727650,62115552,22879907,168541,20681921,7066775,89811711,61928316,17081350,68316156,36139942,59981773,67513640,79136082,47298834,69786756,86798033,95251277,79322415,92215320,55850790,97940276,96709982,88130087,54263880,19898053 +4668450,68702632,96193415,66250369,8807940,77312810,85571389,9886593,17764950,55470718,29834512,57241521,89046466,43376279,64087743,94539824,70800879,32159704,61263068,81805959,26734892,15015906,44784505,29959549,13231279,79821897,44348328,87710366,51047803,26292919,81677380,99658235,78589145,14349098,47361209,89637706,63967300,75543508,9860968,38061439,25842078,40152546,96726697,89214616,62051033,29510992,39373729,16019925,6793819,39801351,4099191,69641513,2717150,76960303,65851721,4119747,54427233,45794415,45237957,4434662,35092039,77272628,30764367,61859581,65275241,81853704,43501211,12348118,16595436,72274002,14947650,65074535,20140249,48774913,77300457,88653118,34698428,26392416,99515901,76315420,77301523,77620120,54058788,92692283,53802686,8696647,78561158,72019362,79136082,58208470,21533347,48675329,40356877,6525948,12416768,20486294,44473167,33431960,12024238,16445503,1204161,68128438,18504197,82886381,84187166,19486173,19939935,31126490,80014588,6808825,53547802,64848072,112651,22766820,73031054,53632373,56531125,20765474,34497327,57240218,17957593,93933709,80316608,89499542,75777973,62936963,70596786,4095116,92554120,54517921,95957797,85116755,74743862,2891150,14723410,44889423,47090124,51588015,83948335,49236559,82427263,8129978,67793644,61373987,95726235,30891921,80251430,68939068,92215320,1569515,49641577,68041839,4798568,74724075,9623492,17365188,89217461,5832946,51426311,57803235,91664334,17058722,61271144,72357096,82339363,91957544,75229462,31161687,94911072,16567550,93562257,73235980,98943869,98462867,36780454,78909561,16097038,18699206,13862149,57658654,51464002,34236719,569864,85023028,86821229,79942022,9575954,33304202,40686254,4064751,75153252,79880247,40027975,88251446,55753905,47792865,10309525,43045786,46851987,97940276,247198,63059024,59197747,8115266,30787683,62693428,47708850,97561745,63372756,7685448,9259676,27665211,28734791,168541,26102057,24915585,10358899,71333116,27325428,28851716,92604458,32161669,91802888,14045383,54987042,30694952,39201414,53393358,90310261,44481640,8729146,98130363,83533741,92692978,8055981,6986898,44844121,36930650,68644627,70541760,9860195,98648327,83789391,98371444,92398073,96318094,90013093,24058273,26998766,21993752,50668599,33201905,55669657,66116458,8791066,44842615,58180653,40781449,33237508,52293995,22405120,80246713,60430369,39847321,72725103,66271566,4985896,68204242,22942635,41481685,749283,55247455,16054533,45996863,74452589,45848907,16099750,60955663,60106217,74357852,62740044,72614359,29635537,20836893,78549759,82651278,35192533,65454636,92998591,14731700,56424103,37957788,49597667,66319530,54232247,94090109,46540998,66137019,14093520,17068582,5640302,99524975,55189057,33699435,9175338,9829782,20867149,37620363,73786944,65880522,46870723,42644903,17037369,32590267,91938191,669105,57359924,69697787,62552524,16684829,30543215,99861373,73392814,66322959,59501487,42782652,39986008,74614639,61741594,7011964,51315460,99549373,13470059,1239555,6153406,45407418,10453030,36580610,59177718,10366309,99226875,87720882,90457870,31733363,26507214,1022677,30771409,1250437,55770687,58751351,49598724,59318837,78785507,29819940,59109400,81898046,37659250,16387575,78300864,76170907,95290172,7300047,79657802,22997281,29466406,51715482,26863229,78766358,79922758,19376156,65081429,40865610,30090481,67474219,32250045,69579137,9188443,37739481,64055960,26139110,44177011,15536795,29401781,6521313,508198,18783702,54663246,87598888,51466049,7502255,7955293,84684495,40197395,60581278,88444207,68694897,88130087,45428665,24733232,8128637,94516935,2917920,23569917,61859143,36808486,77787724,33797252,62452034,36685023,4069912,66428795,12571310,49271185,90272749,35456853,59614746,92033260,53888755,72738685,50567636,2204165,26776922,98948034,70004753,30139692,36505482,61623915,62428472,43322743,5111370,81172706,18833224,71316369,58749,93057697,30218878,87160386,20122224,8651647,31904591,874791,78602717,84293052,60102965,52261574,37192445,4204661,40677414,37891451,26493119,71083565,62490109,22113133,6505939,7066775,30811010,56515456,38256849,66832478,38341669,83150534,34295794,84904436,3294781,38022834,15535065,17976208,37501808,42692881,67281495,54762643,13348726,55602660,96735716,47213183,67031644,79806380,6497830,30395570,1197320,97379790,77284619,23194618,18001617,7646095,80555751,7229550,30366150,321665,5970607,75052463,78218845,8373289,84127901,33628349,36312813,31491938,38494874,37280276,30653863,3183975,65047700,76671482,28550822,61960400,65338021,22450468,65715134,1375023,99224392,29029316,19101477,52204879,69355476,66611759,8733068,82052050,3233569,49328608,20002147,68824981,74137926,73124510,24619760,50188404,56461322,90004325,19891772,92803766,47887470,3235882,70036438,53648154,54014062,11161731,73222868,33565483,29516592,28796059,38008118,81855445,33336148,62430984,14326617,85695762,29932657,91831487,77187825,69136837,99297164,91727510,97783876,2208785,69255765,45075471,85242963,43506672,82897371,4508700,20885148,14363867,96709982,71469330,89078848,34493392,83651211,16971929,69848388,56484900,37224844,48892201,54606384,44846932,40781854,48088883,61141874,68875490,51472275,45381876,43933006,76540481,34698463,68102437,86001008,23848565,4806458,7919588,61982238,54199160,71522968,36845587,98739783,50806615,29188588,82779622,30163921,83083131,60393039,78884452,95251277,37435892,29065964,3633375,38645117,15148031,83133790,63628376,21673260,11365791,99021067,70367851,66667729,97057187,88047921,72793444,29994197,82532312,19272365,20642888,52613508,83210802,42199455,9603598,84166196,97011160,86301513,37560164,33935899,11923835,28928175,55143724,45990383,6038457,92530431,4515343,72278539,66885828,93053405,77072625,62803858,17386996,40984766,41092102,34044787,27625735,26114953,41380093,68316156,14220886,73617245,63015256,10597197,19457589,76330843,50702367,88904910,3487592,42307449,17727650,24953498,4199704,68816413,93515664,38365584,75986488,3088684,43357947,68000591,44983451,3233084,38009615,86460488,90158785,91141395,93270084,59329511,89996536,30463802,94711842,9398733,11581181,99965001,71300104,36468541,67513640,48360198,53666583,70372191,66045587,3773993,45790169,76434144,85117092,62232211,37675718,99604946,66903004,74110882,97281847,71920426,45667668,64157906,7104732,20645197,35996293,4787945,86543538,415901,65271999,30998561,57961282,9257405,12664567,50007421,82178706,48893685,57393458,81274566,54868730,49882705,66663942,5267545,44550764,13173644,36753250,61712234,18131876,45306070,98653983,91990218,824872,55960386,526217,21070110,97641116,89699445,77413012,39171895,44847298,92541302,33061250,60176618,90090964,14626618,32151165,84002370,4978543,59371804,17539625,33553959,32274392,15902805,63152504,21289531,94360702,82327024,71965942,37183543,95010552,84406788,38658347,26744917,71657078,21001913,9599614,82726008,62357986,44627776,62496012,83302115,48658605,48673079,47893286,7182642,45049308,5482538,1431742,95395112,15064655,76703609,39553046,70782102,26063929,92071990,85711894,45919976,23432750,51149804,83269727,72358170,72373496,99333375,94076128,72732205,52060076,2607799,77377183,36933038,27411561,6871053,53084256,11543098,91240048,42073124,35512853,89419466,13819358,61728685,44245960,24314885,6221471,93359396,47298834,45617087,70727211,17894977,51507425,99729357,7687278,32699744,31727379,47738185,10649306,18411915,28787861,57248122,22129328,93566986,19898053,57163802,44479073,67124282,72495719,20568363,42947632,13468268,33249630,61815223,65038678,6111563,48260151,22721500,86242799,10961421,30569392,73168565,75820087,58224549,3880712,94595668,44060493,23134715,7517032,83155442,96906410,18663507,92787493,77898274,6819644,24826575,79191827,67227442,72238278,27041967,18466635,3509435,53842979,65017137,54263880,91281584,48395186,76825057,59910624,41245325,82979980,7423788,17857111,18806856,36135,68110330,9058407,23110625,89804152,34667286,17385531,5822202,16424199,32426752,86022504,51590803,8913721,15075176,90061527,17016156,33123618,89811711,75407347,43152977,67451935,86118021,69605283,36812683,16405341,77694700,1788101,72777973,8099994,16380211,34432810,25636669,44915235,96420429,33895336,75128745,15163258,79738755,42237907,27185644,22435353,91255408,23740167,70420215,84349107,90654836,1936762,20681921,44664587,64602895,24591705,73109997,17146629,99125126,92353856,92867155,21919959,83368048,42967683,21397057,55615885,27187213,75135448,99971982,11757872,62115552,26275734,93790285,84840549,99917599,86798033,22108665,36139942,81774825,61897582,12303248,56473732,6157724,41442762,69786756,56153345,176257,15948937,95581843,59981773,88698958,32058615,74441448,26664538,96852321,45995325,57020481,79322415,99690194,61928316,64098930,4091162,59405277,38510840,36189527,22879907,36396314,37166608,34946859,62762857,24168411,10264691,73021291,17081350,67030811,83747892,26397786,5073754,63506281,8634541,67084351,55850790,56955985,22200378,41092172,40534591,2331773 +9623492,34493392,22450468,28928175,99917599,5111370,2917920,39171895,74357852,16097038,20885148,94911072,70596786,59197747,19939935,6986898,12348118,95010552,57803235,97561745,17764950,99965001,92554120,83150534,52261574,4787945,29188588,51047803,68875490,63967300,93933709,85242963,7919588,37501808,1204161,78602717,37166608,56531125,37739481,48673079,25636669,74614639,86022504,54987042,71333116,61859581,73392814,13231279,33895336,66885828,27665211,81898046,8807940,89046466,66903004,65271999,68702632,38658347,26114953,95581843,76703609,66045587,3509435,8129978,7517032,49236559,96709982,87710366,84904436,94090109,97783876,73222868,10366309,24619760,42307449,1022677,36580610,34236719,86821229,99604946,98130363,29029316,81274566,20642888,32590267,98739783,35192533,1569515,37620363,48892201,89214616,80246713,73168565,37192445,88047921,61897582,16445503,28796059,29466406,43376279,78561158,77272628,29819940,40356877,17058722,89078848,24733232,36685023,90090964,64087743,15535065,26275734,4199704,64848072,51472275,247198,4099191,43045786,16099750,34432810,92998591,6221471,6793819,8696647,61982238,66271566,16595436,36312813,16019925,20645197,49598724,20002147,7646095,79922758,65338021,7955293,44479073,30891921,16424199,53084256,78766358,46870723,93270084,5832946,62115552,9603598,1431742,70800879,36780454,14326617,6808825,77377183,54014062,41092102,88653118,17068582,81855445,69355476,71657078,18833224,71316369,83210802,6505939,34946859,52613508,70372191,79191827,47738185,36189527,72777973,67030811,33061250,56473732,53802686,77620120,61859143,8099994,65715134,74743862,62430984,54199160,3773993,59329511,68041839,77413012,6819644,77072625,23194618,36812683,38494874,71920426,76540481,10597197,88251446,72238278,79942022,19898053,67474219,22108665,14626618,32426752,76434144,92692978,44889423,38022834,9257405,8729146,32159704,50668599,26397786,68204242,30163921,40865610,55602660,54762643,98371444,40027975,67793644,29994197,824872,26734892,82427263,58749,38008118,38341669,19457589,21993752,73031054,99549373,2717150,5267545,18699206,82886381,63059024,61271144,60430369,64602895,85116755,66322959,93515664,66611759,52060076,40197395,39553046,38645117,97940276,93562257,89419466,43357947,38510840,66667729,9599614,8651647,76960303,18131876,31904591,77312810,61928316,80316608,35512853,17727650,7229550,90272749,99971982,64055960,72495719,33336148,56461322,4119747,24058273,37675718,38061439,26507214,8733068,44784505,9575954,14220886,112651,72358170,94711842,23740167,91802888,31727379,5482538,83302115,19891772,81172706,55247455,33304202,55960386,43506672,26998766,26493119,33237508,92398073,79806380,20486294,86001008,99125126,73235980,22879907,59910624,45075471,34698463,10358899,18466635,68816413,88698958,32250045,62428472,73124510,29510992,48260151,26863229,88130087,36753250,40152546,53632373,30463802,95957797,72373496,32151165,81853704,7502255,41245325,96193415,66137019,44842615,50188404,65017137,62693428,50806615,55143724,9886593,87720882,99861373,28550822,31733363,21533347,72732205,15148031,9398733,90457870,62452034,70367851,49641577,88904910,44983451,47090124,78884452,9058407,95395112,89699445,49882705,93053405,68316156,82779622,42199455,53842979,73617245,78300864,70541760,31126490,99224392,77187825,55753905,19376156,87160386,68128438,11161731,28734791,15163258,57241521,72738685,62357986,29932657,30395570,18783702,83155442,65074535,74110882,9188443,36505482,63506281,66250369,75153252,45237957,94539824,16971929,92692283,33565483,37957788,36930650,45996863,38365584,30787683,26392416,34698428,22129328,61815223,60955663,62936963,22997281,76671482,83083131,6153406,77301523,20867149,95290172,32161669,1788101,9860195,35996293,66832478,1375023,17976208,17037369,93057697,99226875,23569917,33553959,51507425,79136082,4668450,54427233,6497830,37435892,83789391,61263068,669105,99515901,11581181,69786756,61373987,4095116,74452589,30139692,91281584,27411561,53666583,68644627,13468268,41092172,65275241,15075176,72019362,56153345,874791,83651211,16567550,27625735,76170907,49328608,70036438,40534591,30764367,16054533,18001617,45428665,54263880,98943869,12024238,34044787,92215320,54663246,37183543,54606384,17539625,94076128,61728685,29959549,42782652,15948937,72357096,62803858,68000591,77284619,84127901,80014588,30366150,51466049,84293052,3487592,44915235,18806856,72274002,75135448,44245960,30090481,53547802,85695762,83948335,1936762,3633375,44348328,50702367,40984766,42073124,90310261,20765474,45990383,65038678,45995325,88444207,36808486,81677380,69579137,72725103,62496012,7687278,26102057,44844121,42692881,64157906,60106217,48675329,34497327,15902805,14947650,87598888,42237907,82897371,67451935,9175338,96735716,48360198,30543215,98948034,63152504,72278539,92604458,46851987,37659250,95726235,73786944,75820087,11365791,92541302,44550764,99297164,55770687,70727211,22942635,46540998,1197320,21070110,71300104,84187166,67031644,19272365,29834512,26292919,45848907,39373729,79738755,55189057,35456853,43501211,53888755,47893286,10649306,98462867,79880247,43322743,96726697,51464002,90013093,3088684,86242799,23110625,8128637,4064751,69641513,66116458,57248122,49271185,62051033,58180653,74441448,16387575,37891451,78909561,5073754,98648327,84406788,30653863,2607799,17016156,92071990,36933038,91727510,56515456,44177011,176257,6038457,91664334,33123618,45790169,56424103,16684829,82726008,7685448,569864,20568363,89811711,34295794,82052050,19101477,89637706,749283,84840549,19486173,65851721,92033260,61623915,17365188,85023028,91831487,17385531,79657802,78549759,29065964,4515343,99524975,42947632,60102965,14723410,33699435,14731700,97641116,67084351,4806458,79821897,3183975,71083565,36139942,1250437,82979980,59371804,66428795,23848565,75543508,62232211,42644903,40686254,8373289,84684495,85571389,508198,13173644,13348726,54232247,74724075,75128745,92353856,25842078,76315420,7104732,3235882,68824981,22200378,10961421,18663507,61141874,41481685,12303248,2204165,35092039,90061527,23432750,415901,91240048,91938191,26139110,20140249,84349107,59318837,17386996,60176618,68694897,54058788,73021291,82532312,69697787,59109400,52293995,11543098,39801351,33201905,41442762,3294781,63628376,17894977,3233084,22405120,20122224,78785507,45667668,48658605,93359396,59177718,45306070,23134715,51315460,18411915,80555751,24591705,4069912,33797252,15064655,70004753,86301513,7423788,63372756,27185644,89804152,97057187,93790285,14045383,526217,71469330,2208785,77898274,45919976,28851716,48088883,60581278,75986488,62740044,84166196,85117092,99658235,57020481,51588015,97011160,85711894,26063929,21919959,4798568,6521313,96318094,56484900,33628349,91957544,54868730,52204879,86460488,55470718,96906410,7011964,17146629,75407347,48395186,64098930,91255408,95251277,37560164,99021067,5822202,27325428,92867155,67227442,53648154,78218845,6525948,30771409,42967683,89996536,78589145,17957593,26744917,51149804,30569392,47792865,15536795,31491938,44664587,57393458,11923835,8115266,30218878,40677414,45407418,30694952,77787724,6871053,86798033,83368048,92803766,58751351,71965942,48774913,57163802,91990218,17081350,57658654,63015256,86118021,96852321,55669657,3233569,9829782,97379790,5640302,43152977,82339363,74137926,81805959,27041967,54517921,13470059,44627776,15015906,24915585,7182642,57240218,45381876,76825057,10309525,30811010,21397057,27187213,91141395,89499542,33249630,59501487,168541,51590803,99690194,75229462,98653983,99729357,68939068,36468541,69255765,62762857,90004325,75052463,44060493,33431960,26664538,1239555,39986008,47298834,24826575,44847298,66663942,2331773,4091162,31161687,59981773,4978543,51426311,69136837,93566986,5970607,8913721,89217461,47708850,17857111,57961282,83133790,22435353,65047700,9860968,4204661,21673260,40781449,9259676,2891150,58224549,72793444,18504197,48893685,45617087,21289531,28787861,77300457,67124282,39201414,73109997,61960400,47887470,44481640,92787493,53393358,22113133,79322415,61741594,50007421,62552524,94360702,3880712,4434662,82327024,84002370,80251430,49597667,11757872,82651278,59614746,40781854,16380211,29401781,8634541,41380093,66319530,12416768,32058615,75777973,51715482,90654836,81774825,65880522,44846932,67281495,24953498,36845587,7300047,47213183,14093520,39847321,36396314,97281847,45794415,62490109,96420429,56955985,72614359,55615885,22721500,94516935,4985896,69848388,36135,38256849,32699744,38009615,8055981,24314885,43933006,92530431,68102437,69605283,34667286,29635537,47361209,8791066,7066775,57359924,26776922,22766820,99333375,14363867,12664567,24168411,14349098,55850790,70420215,59405277,37224844,12571310,10453030,44473167,4508700,86543538,65454636,21001913,60393039,50567636,13862149,16405341,321665,10264691,58208470,77694700,20681921,20836893,33935899,70782102,67513640,65081429,83533741,82178706,32274392,68110330,83269727,13819358,76330843,94595668,30998561,61712234,71522968,37280276,6111563,45049308,6157724,83747892,29516592,90158785 +37183543,86022504,33237508,65271999,1431742,31904591,56515456,81853704,53888755,67030811,66611759,23194618,63967300,29994197,13468268,8696647,53547802,73222868,4099191,83210802,13173644,15535065,67451935,68816413,37501808,23110625,6808825,247198,36780454,40686254,3233084,17365188,37166608,34493392,61263068,70036438,68204242,9599614,98462867,65851721,72777973,92554120,33553959,57241521,94911072,92692283,93053405,112651,69355476,77072625,30218878,69697787,6505939,66663942,54014062,41380093,98371444,96193415,65275241,7300047,26998766,32590267,49328608,12348118,81274566,42782652,72278539,55189057,30764367,12571310,65338021,77284619,95395112,11161731,82979980,78909561,62452034,45407418,99658235,93562257,19101477,83533741,93933709,81898046,96726697,99125126,29029316,89214616,90457870,91802888,89811711,57803235,66885828,2891150,51588015,70800879,31491938,38061439,39847321,22129328,7955293,57961282,3880712,83789391,33895336,61859581,39171895,87160386,55143724,73617245,63372756,44889423,66137019,56461322,9829782,33123618,47090124,38645117,95290172,66116458,66045587,24591705,43933006,25636669,61859143,93515664,37659250,68644627,84349107,70727211,88653118,29819940,30569392,23432750,45919976,39986008,83133790,23569917,43322743,39373729,15902805,9058407,53802686,1022677,59197747,61815223,73031054,16445503,64087743,36396314,72725103,43376279,19939935,33431960,99690194,61141874,29834512,36685023,68875490,43501211,89699445,54762643,10597197,10366309,19891772,54987042,6793819,69641513,44842615,62430984,41481685,65715134,14093520,44550764,9603598,59614746,49641577,14731700,84127901,45617087,44983451,9259676,4064751,45237957,37620363,5073754,50668599,84904436,61623915,83651211,20002147,17068582,55753905,97281847,14326617,8651647,92803766,71083565,66322959,99729357,65038678,34432810,77898274,62693428,45995325,92530431,61897582,58224549,45794415,53842979,93359396,53084256,61728685,91831487,15148031,17058722,29466406,45990383,76170907,9623492,72373496,42073124,8807940,93057697,97940276,56473732,16380211,9188443,69786756,66832478,78218845,75135448,73786944,63015256,95726235,84840549,7104732,81677380,51472275,2917920,36930650,90061527,40677414,74614639,2204165,76434144,64602895,85116755,60393039,51507425,31727379,42307449,53648154,79657802,17764950,74743862,99226875,13231279,44664587,32161669,16019925,60581278,90654836,12416768,37739481,7502255,89046466,68041839,36580610,77413012,78561158,669105,96709982,72793444,1204161,8129978,63628376,7229550,38658347,31161687,6521313,34295794,8791066,64157906,71333116,36753250,22108665,34497327,7011964,29635537,86001008,29188588,59318837,63152504,21070110,18001617,16567550,21533347,35456853,72738685,17957593,18783702,69255765,65017137,98948034,15064655,41092102,45306070,55960386,88047921,96735716,82339363,16099750,415901,95581843,45075471,36933038,92787493,4668450,55602660,874791,79821897,96318094,52261574,99549373,52613508,37675718,49271185,40781449,50188404,97561745,99604946,87720882,36468541,59910624,37957788,10264691,91255408,36808486,17539625,33304202,62803858,54058788,74137926,67793644,4069912,65047700,61928316,38494874,22942635,3509435,1788101,77312810,97641116,7066775,66903004,99965001,26114953,4095116,42692881,59177718,65454636,40197395,749283,52293995,72274002,94076128,89637706,35092039,7182642,83368048,92033260,80555751,24733232,81805959,6221471,45428665,55770687,20486294,90090964,79942022,30653863,63059024,60430369,33628349,47738185,73235980,56424103,71469330,44784505,48673079,40152546,57240218,11365791,16387575,92998591,71920426,3633375,569864,93270084,88904910,30366150,90004325,45790169,26664538,3487592,83302115,30543215,66271566,74110882,93566986,13348726,18806856,63506281,21919959,70782102,94090109,75229462,48088883,77300457,38008118,62762857,33797252,7919588,14045383,49236559,61271144,74724075,20681921,168541,92604458,73124510,34667286,29932657,52060076,99917599,84187166,16097038,92215320,71657078,62051033,33336148,78884452,19457589,27665211,88698958,38341669,78602717,6497830,24619760,8128637,5267545,26493119,28796059,9886593,88130087,95010552,75153252,82178706,26292919,98130363,67031644,89419466,19272365,66250369,99971982,51464002,50702367,81855445,55247455,91990218,32250045,21993752,80014588,62936963,43152977,94539824,36505482,76540481,16684829,80251430,50007421,6153406,7423788,36312813,14723410,99515901,62115552,28928175,79922758,61982238,8099994,20867149,52204879,44481640,5482538,51047803,321665,61741594,18699206,7646095,26392416,59501487,16971929,72357096,29065964,20885148,44348328,17016156,28550822,18663507,64848072,4787945,30771409,62232211,6525948,38365584,35996293,26734892,33249630,3235882,26863229,82886381,55669657,62496012,77620120,20642888,22450468,32159704,60955663,57658654,34044787,3773993,37560164,22879907,22766820,97783876,10309525,6157724,67227442,40356877,73168565,71522968,78589145,54232247,72019362,62357986,10358899,48260151,86118021,26776922,79136082,4199704,25842078,44245960,67281495,1250437,40027975,44060493,15163258,78766358,96852321,72732205,22405120,3088684,92353856,79806380,4985896,8729146,97379790,81774825,88444207,57393458,82726008,87710366,40534591,69848388,48360198,5822202,57163802,67474219,99524975,81172706,54199160,72495719,50567636,14947650,9257405,68316156,66428795,26063929,37435892,86798033,50806615,43357947,77187825,93790285,24168411,98648327,68000591,96906410,7685448,14349098,27625735,54606384,70004753,72238278,42199455,76315420,18466635,20568363,54663246,6986898,55850790,83083131,72614359,55615885,34236719,51149804,78785507,90310261,46870723,83150534,95957797,28851716,28734791,508198,74441448,26275734,59329511,824872,8373289,40984766,18411915,97011160,70541760,1375023,91664334,36812683,43506672,45667668,12024238,9860195,44844121,85117092,51466049,18131876,34698463,61960400,74357852,19486173,69136837,4508700,48395186,45996863,32699744,49597667,64055960,85711894,27411561,56531125,85023028,62740044,15536795,99333375,15075176,1197320,24058273,56955985,92541302,86242799,15015906,83155442,59109400,27187213,26397786,45848907,57248122,54517921,70420215,30163921,40781854,12303248,16424199,79191827,97057187,37280276,98943869,69605283,48675329,42947632,20122224,17727650,22721500,27041967,59405277,89217461,71300104,13470059,98739783,45381876,43045786,58208470,92398073,22435353,68702632,34946859,92692978,5970607,76671482,17894977,76825057,41442762,88251446,44473167,17857111,18833224,57359924,71316369,34698428,10453030,32058615,5111370,33201905,85242963,26744917,44627776,36135,99297164,99861373,91281584,29401781,4119747,86301513,77787724,77694700,39801351,22200378,42237907,21001913,46851987,41245325,20140249,42644903,4806458,29510992,91938191,30787683,54427233,75407347,8634541,77272628,9575954,68102437,8115266,16595436,66667729,30395570,36845587,22997281,86821229,73392814,59371804,17385531,85571389,16054533,79322415,526217,70596786,79738755,38009615,62552524,24826575,62490109,10961421,3233569,76960303,65880522,38022834,48658605,70372191,32426752,53632373,23848565,12664567,19376156,35192533,14220886,1936762,83269727,30139692,77301523,90272749,44846932,92071990,4798568,82427263,47361209,65074535,23740167,80246713,18504197,8913721,11923835,46540998,176257,95251277,30463802,91727510,51715482,41092172,72358170,66319530,1569515,75820087,21289531,64098930,94360702,70367851,11543098,76703609,60102965,78549759,99021067,42967683,24314885,58180653,38510840,54868730,82651278,26139110,58749,28787861,24915585,38256849,84166196,47298834,69579137,5832946,75777973,7517032,44915235,89078848,75986488,33699435,77377183,17037369,99224392,20765474,48893685,80316608,9175338,53393358,55470718,40865610,44177011,74452589,29959549,47708850,54263880,75543508,37192445,14626618,76330843,31126490,9398733,4091162,7687278,84406788,24953498,49882705,89499542,27325428,44479073,26507214,62428472,90158785,15948937,89804152,31733363,4204661,68939068,56484900,94711842,39553046,37891451,22113133,48774913,82327024,53666583,32274392,84684495,51590803,98653983,47792865,36139942,51426311,57020481,68110330,30998561,1239555,79880247,2607799,68824981,86543538,6038457,10649306,26102057,33061250,13862149,78300864,47213183,39201414,6819644,2208785,32151165,14363867,33565483,58751351,92867155,67124282,13819358,73109997,67084351,84293052,96420429,8055981,65081429,47893286,68128438,20645197,16405341,35512853,2717150,89996536,94595668,3294781,61373987,59981773,17081350,5640302,9860968,87598888,48892201,82779622,82052050,90013093,83747892,30090481,4515343,60176618,82897371,68694897,6111563,36189527,85695762,19898053,11581181,30811010,23134715,33935899,51315460,27185644,21673260,94516935,56153345,37224844,17146629,49598724,20836893,8733068,2331773,47887470,6871053,21397057,91141395,44847298,71965942,60106217,4434662,73021291,30694952,67513640,82532312,91240048,3183975,29516592,61712234,83948335,84002370,75052463,17976208,11757872,17386996,4978543,75128745,30891921,91957544,86460488,45049308 +26392416,45381876,36780454,71920426,16971929,36505482,60393039,508198,98130363,55753905,39171895,60102965,73031054,22721500,55143724,24733232,19939935,98739783,5822202,669105,77620120,6871053,41481685,81805959,18833224,49328608,16097038,75543508,49641577,33553959,90013093,10358899,30395570,58180653,24591705,99604946,37620363,37501808,33201905,69605283,82979980,13231279,32161669,57359924,56515456,51464002,27665211,31126490,77284619,2717150,4099191,26493119,15075176,65038678,67281495,65715134,74110882,18663507,29029316,9175338,51047803,74724075,96735716,73235980,93562257,44889423,34295794,96726697,95290172,569864,55470718,37659250,53666583,27325428,71333116,39847321,83210802,68875490,62693428,44842615,73392814,5832946,67227442,81855445,30764367,82726008,54014062,96193415,43376279,3773993,35092039,75820087,75135448,88444207,98462867,59329511,33304202,64848072,83789391,42782652,68939068,4095116,95726235,26734892,3233084,35996293,94090109,73786944,79657802,61859143,30653863,54427233,89217461,31904591,93270084,12416768,68694897,8696647,29834512,95581843,77312810,28851716,11161731,23848565,55669657,20486294,48774913,49271185,39201414,83302115,51466049,3633375,92398073,5111370,41442762,17068582,16019925,16445503,56955985,84166196,77694700,29466406,26863229,28928175,54517921,57248122,81677380,30787683,93053405,13470059,70782102,98948034,14349098,68702632,29959549,74137926,65851721,63967300,10366309,87598888,44479073,321665,2208785,31161687,9623492,247198,72238278,22997281,36312813,62496012,99965001,36580610,4064751,54762643,50668599,52060076,61373987,99524975,14626618,61271144,55602660,59981773,60176618,38061439,32159704,7502255,91664334,65074535,62936963,4806458,37192445,16099750,33565483,66045587,91831487,59910624,72777973,9886593,44473167,22942635,61712234,2607799,19101477,23432750,33628349,37739481,75986488,63059024,74743862,53802686,21673260,59197747,53632373,30463802,59109400,824872,36812683,95395112,92033260,9860968,17146629,84684495,30163921,6153406,82886381,87160386,8651647,12348118,40781449,40984766,68824981,64087743,46870723,22108665,70004753,72358170,68041839,54058788,70036438,69136837,30569392,38022834,66832478,93566986,98943869,37675718,6986898,22435353,36930650,99125126,89499542,22405120,6808825,42692881,90457870,35192533,62740044,47361209,45237957,15948937,112651,97011160,43933006,92692978,67474219,45617087,85116755,75153252,54232247,7300047,62430984,9398733,3487592,92692283,13819358,38494874,45996863,52293995,56531125,26998766,67084351,15535065,30694952,66250369,57241521,1022677,50188404,8115266,67513640,43501211,57240218,8807940,70727211,24314885,91281584,17016156,55960386,29188588,9058407,67793644,84002370,62115552,17957593,91802888,73124510,77187825,53888755,53393358,22879907,68644627,29994197,8055981,16380211,56153345,82178706,39553046,69848388,89811711,45407418,78561158,68204242,2331773,20122224,40197395,78602717,79322415,28550822,5482538,526217,55247455,61982238,6521313,40686254,77898274,66428795,8733068,49236559,28734791,76540481,66663942,43322743,36753250,65017137,86460488,72793444,17081350,86242799,24058273,8128637,45995325,29932657,34698428,47887470,63152504,17058722,30771409,22113133,2204165,83150534,39986008,85711894,37435892,14363867,31733363,42644903,88047921,44348328,71657078,89214616,76825057,65880522,61623915,48675329,749283,85242963,72373496,42199455,48360198,24826575,51507425,10309525,90158785,76703609,61728685,25842078,8129978,30139692,27041967,77300457,82897371,6038457,30998561,33431960,27625735,92353856,74357852,26063929,7011964,34698463,4119747,6525948,32274392,58751351,30218878,93933709,13468268,61141874,48260151,90272749,59371804,9599614,47090124,21993752,23134715,16054533,1569515,84904436,30090481,80246713,75229462,70367851,3294781,33797252,44177011,76960303,57803235,21001913,99297164,78766358,29510992,51426311,9829782,78884452,65275241,17764950,5970607,7229550,56424103,67451935,62803858,92215320,44245960,69697787,81172706,41380093,22450468,69255765,40677414,25636669,64055960,87720882,99226875,92554120,92867155,26776922,44784505,48673079,9860195,53648154,42967683,49597667,83651211,51149804,168541,16405341,82427263,45306070,51315460,61263068,33061250,99333375,89699445,32699744,4787945,97561745,45848907,37891451,73617245,2917920,17976208,31491938,98371444,33249630,7646095,54199160,87710366,91957544,66319530,34236719,18504197,20568363,1788101,20836893,62232211,1197320,72738685,17727650,77787724,8373289,14093520,47738185,71083565,93790285,4199704,64098930,44846932,75052463,89419466,65081429,89637706,50702367,3235882,14947650,7066775,86022504,88904910,3880712,23569917,33895336,7685448,47213183,47792865,10597197,77272628,58208470,78549759,76330843,69579137,59318837,47708850,24619760,20140249,66116458,66903004,86821229,72357096,12571310,14220886,51588015,45428665,8791066,8729146,72278539,26292919,44060493,92541302,19376156,74452589,6819644,83368048,62051033,85023028,18699206,60955663,36845587,34497327,94516935,7423788,6505939,94595668,57393458,61928316,75128745,19898053,72019362,86798033,86001008,71316369,51472275,84406788,99861373,89078848,80316608,66667729,30811010,67030811,99224392,45990383,83083131,63015256,9575954,29819940,40865610,62552524,65454636,50806615,63372756,30543215,44481640,39373729,77072625,99549373,99658235,40027975,63628376,72614359,33237508,38341669,53842979,54606384,17386996,9188443,89804152,61815223,41092102,18783702,98653983,39801351,18466635,46540998,3509435,61741594,64602895,54663246,62452034,13173644,26102057,50567636,79136082,20645197,40152546,57020481,91727510,63506281,38008118,16424199,32426752,86118021,66322959,4668450,85695762,70596786,88653118,91240048,78909561,4069912,21070110,18411915,36189527,86543538,67031644,29065964,19457589,47298834,44627776,65271999,57163802,80014588,18806856,29635537,76315420,66885828,69355476,42237907,38658347,38365584,10264691,14326617,88251446,15536795,18131876,70420215,61859581,23194618,90090964,52613508,32058615,57961282,83155442,98648327,66137019,2891150,9259676,48893685,97940276,71300104,17894977,1936762,26507214,84187166,81853704,94911072,38009615,51590803,7919588,93515664,68128438,36135,40356877,415901,53547802,17857111,68816413,78785507,93057697,60430369,66611759,70372191,43357947,1204161,94360702,88130087,26139110,60581278,71522968,6793819,57658654,34493392,48088883,24168411,44847298,53084256,83269727,32250045,59177718,5640302,44844121,15015906,5073754,44983451,20867149,30891921,82779622,55189057,72274002,70800879,36808486,20885148,96420429,27185644,56484900,72495719,73168565,93359396,34432810,97281847,79191827,26275734,16595436,54987042,32151165,73222868,40534591,45919976,90310261,8913721,7687278,37957788,21533347,3233569,34044787,14723410,33935899,83948335,49882705,49598724,89996536,76434144,81898046,45667668,82532312,68000591,79922758,78589145,79738755,1250437,91938191,94539824,38645117,16684829,33123618,4985896,12664567,37560164,45794415,94076128,55850790,66271566,37183543,32590267,92604458,71469330,68102437,7955293,26114953,96852321,79821897,61960400,41092172,61897582,6497830,45075471,9257405,41245325,52204879,15163258,75407347,44550764,65338021,92071990,58749,24915585,77413012,7182642,77377183,81274566,59501487,10961421,1239555,92803766,79942022,80251430,85571389,44664587,6111563,40781854,45790169,15148031,35456853,27187213,82339363,43152977,17365188,92787493,19891772,4204661,7104732,43506672,20681921,17037369,11923835,71965942,75777973,34667286,95251277,79880247,99917599,91990218,26664538,176257,69641513,76170907,43045786,18001617,46851987,24953498,7517032,64157906,58224549,91255408,92998591,1375023,38256849,35512853,3088684,76671482,30366150,70541760,62762857,22129328,22200378,1431742,11365791,80555751,14731700,48658605,28796059,51715482,42073124,79806380,78300864,4508700,82052050,44915235,33699435,97783876,88698958,83133790,99971982,19486173,54263880,72725103,8099994,82327024,90004325,99729357,38510840,4091162,37280276,62428472,4515343,56473732,97379790,13862149,74614639,78218845,13348726,11757872,83533741,99515901,12303248,54868730,874791,42307449,73109997,84127901,29401781,48892201,10453030,99021067,59614746,28787861,60106217,6157724,22766820,84293052,99690194,8634541,50007421,86301513,92530431,59405277,52261574,29516592,3183975,4978543,11543098,96906410,23740167,89046466,23110625,4434662,20002147,96318094,55770687,37166608,82651278,17385531,33336148,15064655,62357986,14045383,74441448,96709982,16387575,84349107,26744917,11581181,84840549,72732205,97057187,73021291,10649306,97641116,94711842,90654836,31727379,9603598,68316156,69786756,17539625,5267545,19272365,67124282,36685023,36468541,36139942,65047700,21397057,36933038,20765474,42947632,95957797,62490109,56461322,21919959,47893286,20642888,6221471,12024238,26397786,4798568,95010552,91141395,48395186,27411561,90061527,77301523,21289531,85117092,16567550,55615885,15902805,36396314,81774825,37224844,34946859,68110330,45049308,83747892 +73168565,99549373,64157906,76170907,55753905,53084256,49641577,247198,34295794,9623492,38494874,51472275,25842078,29029316,99125126,54058788,59197747,2204165,31161687,74357852,92692283,83210802,81677380,87710366,37183543,41092102,92033260,14093520,63628376,415901,86543538,12416768,29959549,3183975,62232211,168541,57961282,95395112,10309525,41481685,31733363,83789391,669105,52261574,85116755,18833224,51715482,47708850,99333375,86022504,66319530,20486294,66250369,33237508,96193415,4119747,35996293,99224392,50567636,38061439,76315420,66322959,4806458,22108665,69355476,12571310,83533741,65454636,9259676,71920426,74743862,55602660,86301513,67451935,99604946,61263068,61741594,29188588,66885828,35192533,68939068,20642888,78909561,34236719,85695762,69579137,22450468,82726008,68694897,10358899,40781854,51464002,50188404,77284619,1197320,44060493,18411915,45407418,36753250,5970607,92353856,30218878,16595436,23848565,60430369,59501487,71333116,45996863,19898053,9603598,7300047,81898046,9058407,15075176,83150534,1204161,81853704,48360198,68110330,87598888,82651278,59371804,62430984,21993752,3233084,66428795,57658654,67793644,60176618,7104732,96735716,77187825,16097038,4204661,55143724,3235882,61859581,39986008,61373987,55247455,6221471,37435892,67031644,65074535,31491938,66116458,7229550,33304202,6808825,89811711,62936963,24591705,19939935,39201414,9886593,36312813,29994197,73235980,91664334,87160386,36933038,61271144,95290172,76703609,45667668,22997281,70596786,30998561,54199160,20140249,28851716,70800879,6038457,62496012,5832946,44983451,9188443,65715134,53547802,36808486,7011964,77620120,99965001,82339363,18783702,32590267,78766358,68875490,112651,53632373,14723410,53648154,73031054,85023028,70036438,38645117,35092039,4668450,59109400,34698428,73786944,36135,78218845,93515664,61859143,15015906,55669657,26063929,68824981,20867149,54427233,55189057,824872,40686254,29834512,75153252,65271999,66611759,5822202,3633375,77300457,26102057,30764367,53802686,10597197,98462867,16380211,44889423,91802888,62452034,44550764,42967683,20765474,26275734,93270084,51047803,37620363,20122224,71316369,65880522,81274566,76671482,60955663,66667729,90654836,73392814,54762643,98739783,19272365,95726235,12348118,10453030,78589145,33201905,526217,68316156,54868730,80555751,10961421,71657078,92998591,56153345,80246713,56531125,74137926,84002370,58180653,64848072,68816413,40197395,84349107,92692978,93053405,51588015,6793819,62357986,23110625,56424103,30139692,55850790,61728685,88251446,54663246,83651211,96726697,57241521,71083565,36780454,84684495,10366309,45794415,6111563,33935899,68644627,13470059,92554120,48774913,90004325,65047700,20681921,32151165,49597667,89214616,44177011,72373496,54606384,34667286,60102965,79821897,78785507,82897371,19891772,10264691,83083131,6497830,71522968,72777973,62428472,38008118,7687278,2208785,30395570,59981773,36812683,7955293,8129978,43357947,64055960,51590803,8733068,93057697,62051033,17068582,4508700,22129328,45237957,99658235,24619760,48673079,68102437,84406788,99226875,74452589,91990218,14363867,15902805,47090124,44479073,76960303,8696647,43322743,63015256,90457870,45617087,72614359,40984766,45790169,92398073,569864,6819644,92604458,94090109,98648327,2917920,73617245,96709982,11543098,45075471,73109997,89499542,51507425,62803858,31904591,2891150,17727650,75128745,17857111,17081350,82979980,72278539,92215320,9829782,30569392,3294781,84187166,57803235,65038678,6153406,1250437,26664538,77072625,63967300,1936762,54987042,72732205,41092172,27325428,73222868,46870723,97561745,4787945,19486173,32426752,14349098,91831487,24826575,44481640,58208470,4064751,30787683,17365188,89996536,57393458,43045786,9175338,40781449,29819940,20836893,17385531,90013093,11161731,6525948,47213183,31126490,68000591,27041967,72738685,74724075,89804152,13231279,15148031,8651647,92541302,36468541,85242963,7502255,50702367,66903004,3487592,14626618,56461322,39847321,57020481,79880247,57248122,54263880,70541760,33249630,74110882,47361209,30366150,46540998,26776922,1022677,75543508,70727211,45848907,13862149,56955985,5482538,66137019,9860195,20002147,33431960,57359924,94076128,93933709,19101477,90090964,33628349,53842979,508198,47893286,82779622,58749,50806615,67474219,15064655,23740167,14326617,61712234,24915585,99690194,874791,65338021,32159704,26734892,79806380,5640302,26392416,42073124,33061250,17386996,77377183,23194618,33797252,95010552,63152504,37560164,10649306,70004753,67227442,66832478,5073754,37739481,29065964,3233569,50007421,61982238,72019362,4798568,74441448,6521313,64602895,7919588,69697787,65851721,44844121,55960386,17764950,69786756,99297164,3509435,44784505,17058722,99524975,42644903,98130363,17539625,36505482,17957593,59177718,49882705,61928316,22435353,77312810,90272749,34044787,48893685,48088883,33336148,4069912,50668599,1431742,85117092,84166196,75229462,9398733,78884452,39553046,70372191,2717150,79738755,97641116,33699435,79136082,37957788,24058273,29510992,88904910,53888755,96318094,45381876,9575954,72725103,4099191,21919959,37501808,49271185,8373289,26493119,90061527,27411561,40152546,30771409,57240218,88130087,48395186,59329511,71469330,13468268,62693428,61141874,4095116,12024238,38022834,76330843,40356877,5267545,86118021,22721500,83155442,99861373,88653118,26292919,16387575,30653863,91141395,8055981,4978543,94516935,27185644,33565483,14731700,29635537,67030811,44627776,88444207,29401781,45306070,26744917,6986898,8099994,97783876,60581278,75135448,73124510,27665211,67281495,68204242,26998766,31727379,8807940,26507214,30543215,46851987,68041839,8128637,19376156,83302115,16971929,39801351,44915235,78300864,3773993,69641513,79191827,86821229,72793444,43376279,11923835,72495719,63372756,20885148,43152977,24168411,53666583,82327024,78602717,18663507,66271566,20568363,72238278,98948034,59614746,78549759,7066775,77413012,96852321,28734791,38365584,42237907,16424199,36396314,56515456,44842615,48892201,95957797,55470718,20645197,28550822,38658347,47887470,83269727,21289531,89637706,67513640,36845587,77694700,72274002,66045587,44473167,77272628,72357096,86242799,76540481,44348328,69255765,4515343,2607799,44245960,86460488,63059024,77301523,34493392,35456853,70420215,41380093,55770687,39373729,81855445,76434144,66663942,44847298,51315460,53393358,22942635,84127901,4434662,22405120,16445503,92787493,12303248,1239555,97379790,80014588,61815223,94911072,91938191,36580610,15163258,47738185,14947650,14220886,22879907,45428665,42782652,49328608,43933006,37192445,97940276,21001913,17976208,40677414,72358170,64087743,81805959,75407347,44846932,89217461,76825057,75820087,16099750,62740044,7423788,18699206,24314885,321665,17894977,82178706,59318837,32250045,45919976,95581843,68128438,34497327,98371444,94360702,18504197,28928175,61623915,4199704,91240048,7646095,45990383,15535065,98653983,23134715,54232247,52060076,34946859,38009615,65081429,69605283,89419466,65017137,47792865,57163802,73021291,9257405,7182642,85571389,33895336,92071990,51466049,79942022,27625735,16019925,91255408,4091162,23569917,69848388,79657802,26863229,8634541,43501211,33123618,15536795,88047921,94539824,18806856,29466406,99971982,82886381,29932657,67124282,8729146,70367851,19457589,8913721,40027975,22113133,17146629,71300104,75777973,49236559,93790285,62115552,12664567,81774825,37891451,42692881,30463802,82532312,48675329,21673260,16684829,79922758,59910624,41442762,17037369,16567550,93562257,38256849,6505939,99917599,92803766,9860968,29516592,3088684,49598724,90310261,32274392,91957544,32161669,6871053,81172706,22766820,22200378,47298834,85711894,84904436,8115266,37659250,40865610,60106217,89078848,36189527,40534591,18131876,84293052,64098930,15948937,88698958,13348726,30891921,26139110,1375023,87720882,8791066,97057187,14045383,43506672,26114953,37675718,83948335,54517921,67084351,21533347,75986488,89046466,749283,3880712,78561158,33553959,74614639,52204879,61960400,42307449,65275241,75052463,51149804,69136837,25636669,82427263,93566986,91727510,39171895,92867155,32699744,99515901,1569515,4985896,68702632,90158785,91281584,60393039,32058615,6157724,36685023,23432750,37224844,13173644,17016156,52293995,97281847,18001617,54014062,48658605,82052050,42199455,13819358,5111370,24733232,44664587,96906410,36930650,7685448,79322415,97011160,80251430,28787861,51426311,94711842,35512853,63506281,42947632,98943869,26397786,30811010,30090481,7517032,56473732,95251277,30694952,83368048,89699445,37166608,56484900,41245325,52613508,16054533,11757872,86001008,30163921,21070110,92530431,86798033,45049308,18466635,28796059,58224549,77898274,59405277,83747892,62490109,48260151,62552524,45995325,62762857,58751351,1788101,84840549,80316608,21397057,99021067,77787724,34698463,11365791,55615885,83133790,11581181,71965942,38341669,96420429,94595668,24953498,34432810,176257,93359396,2331773,27187213,37280276,16405341,70782102,9599614,61897582,99729357,36139942,38510840 +168541,6221471,69641513,20002147,10309525,44481640,96193415,30366150,66319530,33797252,18806856,92554120,62936963,14093520,6793819,66667729,10358899,19486173,48892201,48675329,45407418,57393458,84002370,70372191,53648154,37620363,95957797,40534591,1431742,60430369,48360198,24058273,17365188,51047803,15015906,85116755,1022677,45996863,44784505,15535065,68644627,22997281,72373496,78589145,97783876,79191827,96906410,90654836,44844121,86301513,31161687,2204165,77377183,57240218,66250369,99125126,4668450,874791,38061439,66045587,5267545,3880712,53842979,73124510,82726008,60102965,21070110,31733363,3294781,75543508,3088684,91990218,30218878,34236719,78218845,6808825,99549373,53084256,44060493,84127901,16380211,46540998,34497327,9398733,20642888,415901,7300047,17539625,77272628,5970607,96735716,35192533,38494874,20765474,60581278,39553046,94090109,73617245,75135448,61741594,57803235,47887470,35092039,62452034,56531125,16099750,72019362,78766358,37183543,19376156,526217,34946859,11923835,4064751,55770687,824872,57961282,29029316,62740044,16097038,508198,55470718,61712234,99604946,55189057,38365584,26664538,53632373,43152977,51590803,76671482,3633375,62051033,79136082,11581181,26102057,86022504,73222868,38645117,73392814,92353856,27665211,71083565,4434662,7229550,72793444,38008118,36312813,31126490,2208785,69355476,99658235,68204242,59501487,56955985,51466049,80246713,33336148,88047921,92604458,65338021,62490109,4515343,40984766,93933709,20486294,93270084,87720882,94516935,47361209,11365791,52204879,84684495,72278539,71300104,81853704,48893685,569864,21001913,71333116,83083131,26392416,70004753,90013093,34698428,98653983,62428472,83150534,50806615,95010552,67281495,12571310,33699435,41481685,61263068,19101477,24826575,7955293,26998766,92215320,40027975,9623492,68000591,27411561,39201414,84406788,19457589,112651,77413012,73168565,58749,29188588,2917920,33123618,89214616,17727650,1250437,42237907,48088883,72274002,82651278,36580610,96318094,66322959,4099191,44550764,6111563,72358170,3509435,47738185,14220886,91831487,39847321,90272749,79922758,76330843,3233569,51426311,47090124,62430984,32151165,63967300,67793644,17764950,19939935,10366309,42967683,98371444,28796059,36468541,59371804,5111370,60106217,99297164,32590267,8129978,73786944,30764367,78602717,36189527,45617087,33628349,9188443,9603598,59109400,16684829,90457870,65081429,99524975,26114953,86543538,79806380,38658347,50188404,16567550,78909561,66116458,71522968,80316608,83210802,84349107,56424103,44245960,22113133,30771409,55753905,63152504,26275734,34432810,33249630,83368048,83533741,85117092,34044787,96709982,13348726,86460488,27325428,12303248,13231279,61982238,32159704,22721500,20645197,82339363,17957593,44177011,92787493,8128637,22942635,10597197,54606384,15064655,37435892,91664334,37891451,7687278,74724075,9886593,88653118,57241521,7919588,91802888,96726697,5073754,1936762,14731700,83269727,22450468,11161731,96420429,68041839,63059024,70727211,55247455,34698463,84840549,51472275,83651211,5832946,34295794,49598724,10453030,54263880,98462867,33237508,97561745,21919959,44479073,3233084,65271999,32426752,37192445,71316369,76540481,75229462,30653863,36135,80555751,37166608,57658654,52261574,15148031,29065964,13173644,2607799,77787724,39373729,4199704,63015256,39801351,59329511,6153406,72732205,30139692,3235882,10264691,45990383,39986008,24915585,70367851,33935899,669105,31491938,72357096,36753250,8696647,7104732,22435353,34667286,33304202,8733068,30998561,45075471,30787683,35996293,98648327,99917599,15075176,54199160,26507214,97011160,29834512,80014588,29959549,14723410,68316156,89811711,94711842,46851987,98130363,36505482,87598888,99690194,61373987,15536795,4798568,41092102,61141874,29635537,21673260,77694700,91727510,78549759,75153252,79821897,7646095,20681921,57359924,68875490,18833224,62496012,29932657,76434144,50567636,47708850,95395112,99965001,65880522,82427263,70800879,22108665,78785507,36812683,36780454,84187166,52060076,92033260,71657078,93562257,99861373,26744917,40356877,32161669,7011964,53888755,66903004,68128438,65038678,85023028,23569917,74110882,91141395,38009615,24168411,1788101,35456853,64157906,50668599,49328608,45794415,38022834,43501211,90158785,66611759,59177718,9829782,14626618,16054533,87710366,17386996,24619760,28928175,82178706,45919976,93515664,9259676,13862149,92803766,31904591,51715482,40197395,56461322,65454636,31727379,6525948,88444207,2331773,70036438,42199455,50007421,5482538,76703609,29819940,17068582,61897582,40686254,24733232,18663507,21993752,61928316,56484900,81898046,58224549,37957788,99515901,18504197,70596786,77312810,66885828,96852321,10649306,37659250,18783702,57248122,99226875,11543098,7502255,65715134,8099994,38256849,2891150,17857111,8807940,99971982,83155442,54058788,43933006,9257405,16595436,90090964,2717150,41245325,37280276,85242963,26063929,30163921,70420215,42692881,48673079,16445503,89078848,6819644,61623915,82052050,79657802,20122224,63372756,73109997,20568363,92867155,93790285,29994197,93053405,247198,7423788,37739481,36139942,7182642,49271185,98943869,78300864,84293052,30891921,67474219,95290172,94076128,321665,45790169,37675718,23134715,63628376,77187825,75986488,20140249,98739783,59981773,72725103,88251446,20885148,23740167,44889423,64087743,9058407,30569392,88904910,30395570,3773993,92541302,81677380,81774825,3183975,18411915,72614359,61859143,64055960,49597667,43045786,50702367,33565483,89499542,61960400,75407347,83133790,91240048,60955663,55615885,69579137,90310261,749283,49641577,16387575,35512853,70541760,9860195,16424199,68694897,53802686,17037369,94360702,66428795,77284619,21289531,18466635,42644903,76960303,62115552,14947650,90061527,33201905,36685023,44983451,36930650,77072625,69786756,30811010,66663942,28787861,30543215,58208470,4204661,1204161,28550822,4806458,91938191,8791066,46870723,69848388,32250045,83789391,19898053,55960386,13819358,59614746,8913721,12416768,44473167,30463802,74357852,64098930,14045383,80251430,8651647,26776922,44842615,38341669,65074535,89637706,22879907,4119747,32699744,65047700,60393039,4978543,99729357,55602660,66271566,21533347,22405120,88698958,68102437,74452589,76170907,19891772,18699206,18001617,43506672,54232247,79942022,9175338,99333375,22200378,77620120,48395186,20867149,1239555,67084351,5640302,91255408,19272365,36808486,69255765,65851721,40781449,14363867,47298834,41380093,27625735,23194618,51464002,87160386,7685448,42782652,97641116,38510840,24953498,55850790,44847298,8634541,86242799,1197320,8055981,33895336,66832478,72495719,29466406,36396314,34493392,16971929,55143724,59197747,51588015,95251277,72777973,92692978,43357947,33431960,86798033,99224392,61271144,22766820,74743862,42947632,9575954,12024238,17385531,57163802,69697787,22129328,47893286,1375023,64602895,7517032,6038457,59318837,26139110,8115266,75777973,15902805,79738755,75052463,9599614,82327024,8373289,92692283,95581843,29510992,67451935,27185644,12664567,54663246,40865610,37560164,86001008,65017137,81855445,47213183,62762857,6505939,28851716,84904436,91281584,58751351,64848072,44348328,58180653,95726235,32058615,5822202,27187213,63506281,67030811,176257,45428665,27041967,69136837,40152546,45848907,25636669,30090481,78561158,39171895,16019925,81172706,43376279,51507425,76315420,15163258,61728685,67031644,89804152,82897371,73031054,94595668,56515456,45667668,52293995,45381876,82532312,56153345,36933038,6157724,68110330,4787945,79880247,42307449,33061250,86118021,78884452,62357986,91957544,73235980,56473732,52613508,89046466,44915235,67227442,37224844,79322415,92998591,6871053,81805959,14326617,94911072,1569515,45237957,72738685,20836893,82886381,3487592,26397786,67124282,13468268,53547802,81274566,12348118,24314885,55669657,18131876,82979980,26493119,71965942,97379790,24591705,40781854,62232211,83302115,92071990,4985896,68702632,8729146,23110625,40677414,26292919,16405341,17081350,11757872,66137019,54868730,74137926,61859581,49236559,6986898,29516592,89419466,77301523,10961421,69605283,54762643,67513640,9860968,98948034,17976208,4508700,51315460,26863229,45995325,93057697,42073124,17894977,76825057,59910624,60176618,53393358,37501808,97940276,48658605,85571389,75128745,54987042,54427233,68939068,54517921,77300457,26734892,68816413,83747892,25842078,4091162,13470059,45306070,88130087,84166196,7066775,53666583,23432750,97281847,68824981,73021291,82779622,89699445,92530431,71469330,62693428,30694952,61815223,14349098,44627776,43322743,74614639,62552524,75820087,17146629,93566986,41092172,4069912,32274392,28734791,44664587,17016156,6497830,57020481,21397057,23848565,54014062,51149804,86821229,89996536,71920426,6521313,90004325,48260151,99021067,72238278,47792865,33553959,97057187,44846932,92398073,49882705,85711894,93359396,36845587,15948937,62803858,65275241,77898274,48774913,94539824,83948335,45049308,89217461,74441448,59405277,29401781,4095116,41442762,17058722,85695762,70782102 +4099191,37560164,17764950,57803235,38022834,29029316,37620363,48892201,31126490,47090124,65047700,12348118,78785507,95010552,44479073,30694952,60430369,37739481,38658347,92554120,3183975,73617245,62430984,49598724,86022504,70800879,93933709,61982238,98130363,29834512,87710366,83302115,8129978,77272628,77312810,67474219,68204242,75052463,45996863,81274566,51047803,23569917,69355476,56531125,92071990,24733232,55189057,92398073,39171895,22879907,22435353,16099750,35512853,10597197,3509435,29510992,17976208,35996293,40865610,3088684,26998766,4119747,80316608,4515343,8791066,63628376,62428472,73168565,64602895,45407418,80251430,10366309,36753250,17081350,36580610,49882705,73392814,55247455,98371444,2917920,15535065,10309525,95957797,52060076,44915235,94090109,79821897,69136837,5111370,37183543,168541,59109400,83210802,92033260,36808486,67793644,16445503,55753905,76434144,55602660,4064751,42199455,72274002,5267545,4787945,89078848,45075471,22942635,21533347,35192533,16019925,30163921,66137019,62496012,79922758,45237957,65715134,50188404,63152504,43506672,37192445,32250045,7955293,88444207,22997281,38008118,11581181,61141874,52613508,64087743,96709982,91664334,26392416,30395570,16097038,13231279,53632373,2717150,9257405,54987042,5640302,1204161,17727650,35092039,60581278,74357852,8807940,16595436,76960303,92803766,4668450,18131876,1431742,71333116,38061439,96193415,73222868,49641577,22450468,2204165,59318837,36312813,65338021,36780454,60106217,67030811,33201905,44784505,85711894,44177011,70596786,20867149,65271999,82339363,91240048,38494874,17146629,61271144,51472275,27411561,824872,66832478,86821229,41245325,30891921,98739783,53842979,98462867,93057697,99515901,47708850,26507214,17037369,9398733,76671482,20645197,86460488,84002370,44627776,79942022,9575954,12024238,75543508,20765474,70367851,88251446,13173644,78602717,30764367,66250369,62693428,27665211,9860195,29819940,99297164,15015906,14326617,33336148,51590803,40356877,83083131,40152546,93562257,7687278,33304202,14626618,63059024,69605283,29188588,84684495,99965001,34497327,59501487,87720882,83651211,61373987,29466406,247198,21993752,65081429,44844121,48675329,34493392,4806458,34236719,53802686,78909561,73235980,26102057,10649306,30543215,6793819,61741594,92353856,64848072,9175338,62051033,71657078,81172706,99524975,67451935,77187825,9188443,62490109,87598888,80014588,18699206,24915585,67031644,40984766,94711842,66116458,23432750,96735716,38256849,37224844,26863229,81898046,30653863,23194618,53648154,3233084,57163802,44473167,78549759,19486173,31161687,41092102,91281584,44245960,72373496,26664538,91141395,19101477,77284619,57240218,54663246,4434662,47361209,62936963,70036438,66667729,76315420,55770687,66319530,58180653,76170907,56461322,63967300,89811711,45617087,59329511,66428795,5970607,57359924,60955663,14947650,38645117,34295794,92787493,24058273,68041839,6038457,7919588,77620120,21070110,48774913,69697787,88653118,7646095,95581843,17385531,30771409,53547802,21289531,73021291,61263068,24826575,70004753,38365584,61623915,44983451,20486294,55470718,44842615,54762643,56473732,8099994,79880247,40027975,23110625,54199160,74743862,25842078,28928175,30463802,30787683,90272749,33061250,6871053,55669657,96318094,60102965,13348726,90090964,32161669,19376156,53084256,68939068,4069912,27325428,97011160,93515664,42782652,34698463,56515456,79136082,75820087,81677380,84904436,24168411,10358899,75153252,42947632,17386996,30366150,59197747,47298834,97940276,42073124,86001008,88047921,17957593,76703609,16567550,59981773,56153345,89214616,35456853,74441448,26114953,90013093,28851716,569864,22200378,50806615,31904591,62115552,93053405,7517032,50567636,25636669,36505482,52293995,78561158,77377183,52204879,39373729,87160386,63015256,82427263,13862149,69641513,84127901,22721500,27041967,18806856,3633375,1569515,24619760,61928316,508198,20642888,75777973,13468268,79322415,45848907,63506281,85116755,36685023,44846932,68000591,22108665,9623492,66903004,59371804,66271566,92541302,26063929,669105,62740044,99917599,3233569,48893685,42307449,3880712,50668599,33895336,69786756,91990218,874791,28734791,82726008,97561745,68875490,91802888,76330843,1022677,55143724,39847321,5482538,6808825,81855445,49236559,6505939,7011964,72238278,48360198,26493119,96906410,46851987,43322743,92692283,33699435,526217,62452034,33249630,5832946,14220886,51466049,43376279,78300864,29994197,4095116,71920426,71316369,34698428,68816413,84349107,91957544,65880522,75986488,70372191,55960386,20885148,64055960,67124282,3487592,72495719,8651647,72358170,86798033,32590267,91831487,48673079,11543098,1197320,70420215,94516935,82897371,83150534,77787724,47792865,31733363,89046466,38341669,24953498,18504197,33237508,32274392,4199704,6153406,30811010,96726697,20836893,74452589,33628349,26275734,60176618,68644627,83789391,37891451,17068582,89419466,99604946,62232211,68128438,19457589,41092172,8913721,54263880,99658235,30090481,36135,71300104,30569392,14349098,20140249,98653983,56955985,14093520,40781449,66045587,321665,31727379,9058407,54232247,30218878,73124510,65851721,10961421,82651278,36189527,15948937,77072625,48260151,37166608,36812683,1250437,39553046,40686254,18833224,52261574,62357986,51426311,39201414,70727211,37435892,88698958,69848388,95290172,42237907,46540998,41481685,94911072,44664587,67227442,32159704,22405120,44060493,71083565,34432810,83948335,65017137,20002147,23848565,94539824,40197395,72357096,39801351,36139942,71469330,81805959,95726235,79738755,59910624,29635537,99224392,68702632,14731700,80246713,77413012,65275241,18783702,96852321,1936762,78589145,63372756,34946859,90004325,54058788,68102437,42692881,12303248,71965942,112651,98648327,72777973,42644903,94595668,20568363,91727510,67084351,82979980,93270084,79191827,68316156,20122224,58751351,32426752,93359396,4978543,72019362,42967683,89499542,23740167,29932657,65074535,99549373,9886593,21673260,27625735,79657802,78766358,8696647,70541760,73031054,57241521,66322959,36933038,15148031,51464002,61815223,37675718,90457870,40534591,99690194,51588015,37659250,15163258,45995325,64157906,22113133,9860968,84293052,32699744,74110882,58749,26397786,4798568,4985896,92998591,415901,1788101,96420429,11923835,84840549,44481640,6986898,44550764,69579137,57658654,8373289,26139110,16684829,75407347,27185644,14045383,19891772,76540481,97057187,45428665,18411915,89637706,89699445,73786944,43357947,7502255,37501808,43501211,99333375,92604458,97783876,9603598,28550822,6521313,56424103,48088883,92215320,19898053,22129328,61859581,45794415,30139692,4204661,67513640,46870723,15902805,57393458,29516592,66611759,72738685,66885828,28796059,34044787,7104732,17539625,38510840,81853704,83269727,44889423,16971929,6525948,44847298,24591705,59177718,82532312,12664567,82779622,61859143,47213183,72725103,48658605,88904910,18466635,74614639,54517921,749283,77300457,55615885,17365188,30998561,53393358,8729146,98948034,64098930,92692978,29401781,54014062,50702367,33797252,84187166,61728685,60393039,75128745,29065964,23134715,18663507,97281847,15064655,77694700,58208470,62552524,82886381,72793444,36396314,99021067,43045786,16380211,41380093,81774825,3294781,84406788,99125126,58224549,54606384,33431960,99861373,85242963,45990383,86301513,92867155,90158785,1375023,74724075,85571389,7685448,57248122,50007421,43933006,19272365,44348328,15536795,95251277,72278539,32151165,97641116,26734892,98943869,33123618,176257,91938191,61712234,38009615,54427233,1239555,34667286,67281495,75135448,15075176,7300047,49271185,82052050,83133790,74137926,7229550,89804152,93790285,90061527,8115266,6221471,7182642,12571310,85117092,55850790,56484900,72732205,36468541,53666583,2331773,78884452,51507425,89996536,6111563,86242799,11365791,45919976,53888755,80555751,99971982,72614359,88130087,29959549,10453030,75229462,4508700,33565483,73109997,78218845,33935899,84166196,28787861,95395112,18001617,45306070,16424199,8634541,26776922,82327024,4091162,83533741,90654836,86118021,83368048,13470059,41442762,33553959,86543538,9599614,26292919,37957788,51149804,66663942,90310261,49597667,49328608,10264691,45049308,54868730,7066775,51315460,19939935,31491938,51715482,62762857,61960400,82178706,65038678,26744917,47738185,99226875,69255765,47893286,68824981,16054533,91255408,2891150,45381876,12416768,2208785,40677414,79806380,3235882,16387575,77301523,14363867,21919959,3773993,45790169,59614746,8055981,47887470,48395186,22766820,62803858,21001913,36845587,14723410,17058722,61897582,8733068,7423788,17857111,8128637,16405341,83155442,9259676,37280276,94076128,85695762,70782102,11757872,76825057,68694897,6819644,21397057,17016156,9829782,94360702,77898274,85023028,93566986,5822202,20681921,59405277,39986008,99729357,36930650,2607799,6157724,17894977,89217461,40781854,24314885,27187213,65454636,83747892,57961282,45667668,92530431,5073754,57020481,6497830,71522968,43152977,68110330,11161731,32058615,97379790,13819358 +31733363,38494874,31126490,4204661,36505482,57248122,82979980,15075176,49236559,89214616,36812683,74357852,26493119,13470059,92692978,92554120,16595436,38022834,7502255,66903004,27185644,30139692,71333116,70596786,33237508,27325428,86543538,91664334,83210802,65081429,18131876,40865610,6221471,28851716,99965001,63015256,12348118,57359924,40781854,91255408,45237957,18783702,6521313,97783876,90457870,68939068,68816413,6505939,67793644,92692283,93515664,70004753,66832478,65017137,56955985,55753905,508198,874791,40197395,53084256,27665211,23432750,18699206,1022677,59371804,72238278,26063929,29994197,60102965,26863229,57241521,23110625,99524975,94090109,89046466,80246713,85242963,13231279,4064751,17957593,48088883,96193415,19101477,55189057,47361209,76671482,16971929,37560164,54663246,26664538,14093520,415901,79821897,1431742,3633375,99861373,81172706,47893286,26114953,34295794,24591705,82897371,72725103,3773993,88444207,44846932,1375023,61373987,98739783,37957788,99125126,60430369,20002147,50668599,38008118,18833224,95010552,54987042,44060493,22766820,16097038,112651,20765474,26397786,84127901,36780454,46870723,78218845,18001617,37183543,67451935,83155442,84406788,22450468,4069912,37192445,43376279,76703609,247198,68000591,99917599,29029316,43045786,33304202,27625735,37675718,35192533,44983451,3509435,17058722,4806458,62496012,21533347,98943869,79657802,62232211,71300104,5111370,32161669,59109400,44915235,17727650,76434144,45990383,18663507,73235980,6986898,83789391,2717150,41380093,37620363,77284619,20836893,8128637,93359396,98653983,27041967,30163921,26102057,3880712,28734791,95957797,7955293,61859143,89637706,71920426,76825057,14220886,14626618,6793819,84187166,35092039,33201905,42782652,93933709,67124282,39201414,26275734,93566986,56424103,44847298,20122224,22113133,23134715,54199160,569864,66428795,70036438,57240218,59501487,3235882,73031054,68824981,51464002,74724075,7229550,30771409,44842615,74137926,42967683,64055960,28787861,9058407,53842979,99297164,66250369,73222868,15163258,8696647,34236719,17764950,88047921,7423788,7919588,73168565,89419466,92398073,87598888,54868730,29834512,77694700,84904436,95290172,98130363,37739481,92541302,21993752,19457589,55143724,61141874,26998766,3294781,22108665,97281847,9575954,61271144,43357947,33565483,68644627,44245960,1197320,49641577,77620120,39171895,25842078,26392416,749283,59614746,89996536,91957544,88653118,82886381,62452034,72274002,81677380,47090124,10309525,30366150,1204161,33553959,92998591,44473167,50567636,77377183,6525948,20885148,17081350,4515343,8807940,70727211,19272365,69355476,83083131,32250045,64848072,28796059,96735716,28550822,30787683,36580610,14363867,35996293,82339363,34497327,75153252,62740044,60106217,5482538,75229462,22997281,45919976,15015906,28928175,24168411,17976208,80014588,36933038,23848565,47738185,70800879,44177011,80555751,75407347,99729357,90272749,53632373,24058273,93790285,66885828,77300457,48673079,47298834,61859581,66667729,44481640,63628376,74452589,44889423,71657078,38645117,45381876,3233084,52060076,14947650,27411561,71316369,9623492,39801351,77072625,78589145,9603598,40686254,11581181,66045587,3183975,23569917,2208785,65271999,72793444,38341669,78300864,669105,65454636,65851721,61960400,26744917,14349098,6871053,23740167,95726235,33935899,50702367,49597667,7300047,54263880,67084351,16405341,30653863,13348726,45428665,81805959,63059024,66663942,97011160,67030811,32274392,99549373,91240048,29188588,77787724,64602895,51047803,37435892,78602717,34493392,74441448,4091162,4119747,85116755,57803235,2204165,88904910,526217,84349107,56153345,50188404,99604946,55770687,70541760,9398733,9175338,1239555,54058788,45049308,90013093,40984766,92803766,14045383,68875490,29959549,38658347,50806615,78766358,82532312,75543508,33249630,65047700,7646095,8099994,85023028,51507425,75128745,61982238,59910624,99515901,68041839,69697787,14326617,73021291,20642888,86301513,1936762,11161731,79136082,69786756,48360198,51472275,17857111,95251277,70372191,53666583,18806856,45996863,15535065,53888755,9860195,82726008,76960303,34698463,54762643,81853704,48893685,72278539,42237907,29466406,72495719,15148031,79880247,77413012,60581278,45667668,321665,17068582,73786944,94516935,79922758,33797252,90310261,8129978,24733232,90061527,30764367,53802686,30090481,16019925,69641513,68694897,42073124,87160386,16054533,55247455,85571389,14731700,4668450,31904591,68204242,76315420,44784505,48892201,39553046,4434662,92604458,4508700,20140249,41481685,91802888,82651278,37891451,6038457,89699445,69579137,71965942,66319530,19891772,42947632,62936963,72614359,4199704,47708850,32590267,19939935,6808825,93053405,37659250,20867149,33699435,36753250,36845587,94911072,36930650,99224392,83269727,86821229,81274566,78561158,29819940,17539625,86242799,56515456,65880522,63967300,77187825,69605283,66322959,50007421,68128438,42644903,40356877,88130087,42307449,4798568,56531125,29635537,34432810,21673260,30395570,62430984,40152546,96420429,7182642,12024238,53393358,33061250,19486173,83651211,46851987,40027975,53547802,24619760,10961421,55850790,5970607,77312810,45617087,77272628,17385531,2331773,17386996,84002370,82327024,81898046,176257,3088684,83948335,65715134,61263068,8055981,86118021,95581843,44550764,65275241,30543215,96709982,73124510,47792865,49882705,89078848,55669657,32159704,32058615,1569515,36312813,32151165,51715482,2917920,86022504,66611759,75986488,98462867,67281495,49271185,89804152,30218878,73617245,8373289,10366309,98948034,96906410,86460488,10358899,36808486,45848907,78884452,89499542,82052050,70420215,61897582,76170907,62693428,85117092,92071990,69848388,13862149,39373729,11923835,15064655,63152504,44627776,17146629,83368048,41092102,97057187,67474219,61741594,57020481,52261574,71083565,4099191,41092172,88251446,68316156,62762857,16445503,44844121,97561745,68102437,18466635,49598724,5073754,84684495,12416768,80316608,21001913,26507214,85711894,39847321,67513640,63372756,13468268,38061439,65338021,7104732,54427233,168541,26734892,44348328,74110882,72732205,90158785,40534591,30891921,70367851,10597197,99971982,36189527,22435353,17037369,54606384,43933006,6153406,54517921,12664567,75777973,64087743,13819358,89811711,92215320,62357986,54232247,75052463,77898274,74614639,71469330,58208470,88698958,65038678,9860968,1250437,79738755,9188443,4787945,33628349,19376156,83302115,38365584,72738685,61815223,53648154,94711842,43322743,52204879,40781449,46540998,42199455,2607799,37501808,45995325,82779622,45790169,97379790,14723410,56473732,64157906,48260151,8651647,18411915,16387575,6497830,93270084,33431960,36468541,11365791,61928316,8634541,31491938,54014062,94360702,76540481,98371444,91990218,32699744,62803858,23194618,38510840,78549759,99690194,56484900,87710366,51315460,90004325,86798033,39986008,76330843,52613508,29401781,83150534,52293995,21289531,96726697,97940276,51466049,3487592,6111563,7517032,62490109,8913721,43506672,69136837,57393458,9886593,98648327,99658235,59329511,33895336,3233569,27187213,16380211,99333375,82178706,16099750,5267545,37280276,45407418,24915585,74743862,30463802,79942022,84293052,8115266,79191827,92787493,84166196,9259676,93562257,94076128,16567550,58180653,57961282,43501211,44479073,92033260,25636669,63506281,43152977,31727379,48774913,31161687,38256849,90654836,42692881,5822202,72019362,21397057,61728685,26139110,30811010,22129328,8733068,86001008,45794415,91727510,67227442,60955663,55960386,30998561,78785507,11757872,4978543,7011964,96852321,59177718,55602660,72373496,59318837,40677414,66271566,15902805,55470718,48658605,9829782,44664587,91938191,91141395,82427263,51149804,15948937,51588015,2891150,67031644,10264691,22200378,22405120,93057697,73392814,36396314,68110330,7685448,17016156,84840549,70782102,51426311,94595668,35512853,16684829,73109997,92530431,95395112,6819644,57658654,58751351,6157724,11543098,59197747,20681921,69255765,47213183,99226875,96318094,66137019,60176618,22879907,99021067,1788101,75135448,55615885,71522968,16424199,57163802,66116458,20568363,12571310,72358170,33336148,41442762,33123618,20486294,824872,61623915,7066775,24826575,34946859,9257405,9599614,17894977,21070110,8791066,20645197,36685023,34044787,5640302,7687278,72777973,37166608,92867155,62428472,47887470,58224549,34698428,36135,38009615,72357096,58749,49328608,17365188,91831487,75820087,77301523,59405277,21919959,29932657,13173644,15536795,87720882,22942635,83133790,61712234,62051033,29510992,81855445,59981773,94539824,79806380,24314885,62552524,92353856,29516592,65074535,26292919,81774825,85695762,30694952,5832946,80251430,35456853,24953498,79322415,62115552,45306070,34667286,10453030,29065964,32426752,64098930,48675329,22721500,26776922,10649306,48395186,91281584,37224844,68702632,36139942,19898053,45075471,56461322,4095116,12303248,83533741,18504197,8729146,60393039,90090964,83747892,97641116,4985896,78909561,41245325,30569392,89217461,51590803 +55470718,55753905,9886593,83210802,89214616,62496012,84840549,64087743,91664334,76960303,36930650,30139692,23848565,65715134,30366150,27665211,83150534,6153406,72495719,16971929,80246713,29029316,91727510,48893685,3233569,50567636,569864,15535065,70004753,34698428,112651,20002147,415901,61982238,66322959,85116755,71333116,33628349,19376156,11365791,99861373,15075176,78218845,34493392,83155442,78785507,71657078,65275241,44983451,78766358,5111370,31904591,70372191,86001008,33249630,96193415,35192533,54058788,66832478,63372756,26292919,79806380,29819940,88653118,72777973,65038678,14947650,50806615,39847321,72274002,78589145,9623492,26139110,40152546,91938191,22997281,31126490,97783876,18663507,16595436,99549373,49328608,43501211,30998561,19939935,92692978,36780454,4806458,90457870,54606384,45919976,88444207,35092039,32426752,71522968,33123618,87710366,51047803,53842979,29188588,79136082,82726008,28928175,32590267,15163258,5822202,56531125,17539625,83269727,68824981,67793644,78549759,29466406,526217,68875490,73392814,28787861,30891921,66428795,36812683,16054533,4668450,62936963,39171895,92398073,78561158,64098930,13348726,68316156,80555751,8651647,45237957,55770687,61373987,73222868,30653863,28796059,26114953,85023028,76825057,26392416,90013093,34295794,86460488,60106217,88047921,61263068,96726697,14731700,74357852,38645117,45848907,6808825,50668599,10961421,7502255,16099750,89637706,176257,61623915,11161731,45996863,15148031,321665,16445503,72725103,90272749,53393358,37435892,13173644,4508700,7300047,67281495,20486294,8696647,18833224,32250045,15015906,57240218,82052050,27325428,49598724,95581843,46851987,62552524,12348118,40677414,99604946,28550822,42199455,63967300,38365584,69136837,71469330,45306070,32159704,81677380,61141874,30787683,62232211,36505482,40865610,62693428,55247455,1204161,50702367,24058273,73031054,40356877,24619760,77272628,34236719,94595668,71920426,18504197,54427233,48892201,7423788,39201414,47298834,8807940,83302115,45990383,61928316,17764950,4787945,76671482,68702632,42644903,34698463,26734892,65017137,75229462,4064751,62357986,85695762,38494874,74137926,45428665,1375023,24915585,51472275,93057697,77377183,68128438,29834512,86543538,30218878,59371804,874791,83533741,3235882,6793819,16380211,26063929,29065964,77620120,7955293,99515901,1569515,98648327,73124510,44889423,59197747,99917599,26493119,83789391,9599614,38061439,94539824,69641513,29994197,56461322,83368048,60102965,18131876,62452034,3088684,37166608,80316608,66885828,81774825,86821229,45667668,8129978,77694700,20765474,81853704,51149804,69579137,97561745,84904436,21001913,33304202,66271566,57803235,33565483,52613508,94711842,62762857,65454636,44550764,95726235,77300457,17037369,7182642,58224549,8373289,91141395,30395570,1788101,53547802,61859143,19898053,55602660,75543508,99690194,4515343,1431742,59501487,669105,78909561,53666583,58749,68816413,45995325,44481640,9398733,15948937,24733232,8733068,18806856,36753250,66137019,4099191,95395112,92803766,66667729,43045786,51464002,61960400,52261574,6505939,79922758,67031644,27187213,77413012,95010552,39801351,59981773,5832946,57658654,56424103,47090124,73168565,18783702,99224392,3233084,46870723,60430369,62430984,57393458,10358899,16684829,82651278,72373496,37659250,77072625,82532312,54014062,26998766,30764367,92554120,45407418,38008118,52293995,76170907,8634541,37739481,99297164,47887470,47738185,36189527,2917920,92033260,19101477,62051033,69255765,56515456,20642888,84127901,7104732,2208785,79880247,63059024,48658605,13470059,42967683,82427263,54517921,70036438,96906410,36685023,95290172,20122224,26664538,20885148,49641577,5073754,4069912,59177718,6986898,38256849,65880522,19457589,70596786,58751351,78884452,63628376,83948335,72614359,54199160,7011964,54663246,2331773,71316369,86022504,47708850,7229550,20568363,70541760,42947632,1239555,17058722,55615885,3294781,22766820,79738755,97011160,61815223,56484900,39553046,33797252,38341669,66663942,6871053,30463802,4119747,22405120,22108665,8099994,92867155,97281847,17727650,81805959,13819358,77787724,23569917,34497327,26863229,22942635,37891451,18411915,1022677,2607799,72738685,9175338,41380093,61859581,5970607,82979980,69697787,65338021,48360198,33895336,64602895,57241521,57359924,32161669,77312810,99125126,6038457,70800879,5267545,27625735,13468268,17146629,85571389,14326617,7517032,45790169,49882705,62740044,78300864,68204242,99658235,30163921,82886381,76330843,41092172,94516935,49597667,93053405,76434144,1250437,4434662,29959549,12416768,86798033,44177011,90310261,92998591,19891772,43376279,59109400,41245325,17068582,14626618,85242963,81855445,66319530,15902805,59910624,55143724,50188404,65851721,61897582,98130363,7687278,77301523,48088883,68644627,75986488,3487592,10264691,6221471,99971982,33201905,29932657,12024238,33699435,75135448,92787493,16097038,40686254,89419466,35512853,37183543,76703609,39986008,7066775,7646095,44842615,90090964,8115266,22129328,45075471,82339363,17976208,82327024,67124282,36135,16387575,31161687,4095116,71300104,77284619,73617245,72357096,9860195,26397786,81274566,25842078,39373729,46540998,66116458,58180653,77898274,67451935,66611759,88698958,66250369,89217461,10366309,95957797,92215320,94911072,52060076,51466049,36139942,86242799,35456853,68694897,13231279,32058615,7685448,57248122,47361209,28734791,24591705,99965001,79821897,70727211,1197320,75407347,38658347,60955663,63506281,45381876,85117092,51588015,9259676,29635537,76315420,91240048,92604458,14363867,44348328,85711894,17386996,69786756,33336148,70367851,30543215,30771409,3633375,64055960,14093520,64157906,86301513,73786944,67030811,54263880,3509435,247198,65074535,17957593,37192445,9603598,92071990,29516592,37280276,36580610,22450468,30569392,22200378,5482538,91831487,59318837,89499542,30811010,48673079,9860968,61728685,34432810,17894977,73235980,43933006,37620363,96709982,2204165,9188443,74743862,36312813,98653983,65047700,55850790,10597197,72732205,54232247,26507214,66903004,69848388,99524975,54868730,15536795,65271999,21993752,92692283,56955985,22435353,93515664,55960386,18699206,94076128,20836893,20140249,99729357,53084256,44846932,22879907,59329511,40781449,76540481,51715482,90158785,48675329,66045587,14220886,31491938,10309525,68939068,98462867,168541,16019925,17016156,98371444,94090109,26776922,47213183,91802888,34044787,62803858,93790285,12303248,40027975,44627776,2717150,19486173,43152977,62115552,40197395,14045383,44844121,81898046,34946859,80014588,59614746,21070110,64848072,53802686,7919588,60393039,31727379,21919959,33237508,65081429,89046466,49271185,40534591,67513640,51315460,53648154,97379790,70420215,91281584,42073124,97641116,51507425,98948034,20867149,62428472,24826575,9257405,98943869,56153345,44060493,93270084,24168411,42307449,57961282,6157724,77187825,82779622,4978543,48260151,83651211,62490109,3183975,16567550,72019362,22113133,49236559,16424199,68110330,8055981,52204879,32151165,63015256,43322743,95251277,15064655,51590803,2891150,84166196,93566986,17385531,74614639,44479073,60581278,30090481,82178706,55669657,55189057,38009615,9575954,87160386,10649306,45794415,8128637,99333375,72793444,29510992,73109997,98739783,47792865,35996293,69355476,87598888,82897371,75153252,3880712,96735716,74724075,508198,68000591,22721500,91990218,23110625,68102437,44473167,36468541,41481685,33553959,53888755,37957788,44784505,84187166,16405341,8913721,84002370,97940276,42237907,57020481,86118021,92530431,40984766,4204661,72278539,75052463,61712234,88904910,4798568,75777973,81172706,71965942,91957544,61271144,33935899,23432750,19272365,9058407,42692881,33431960,54762643,12571310,6525948,6497830,58208470,37675718,6111563,25636669,83083131,44664587,24314885,42782652,17365188,32699744,20645197,74110882,90654836,93359396,96318094,70782102,4199704,84406788,34667286,32274392,89078848,21673260,71083565,79657802,80251430,48774913,67474219,8729146,31733363,59405277,21289531,88251446,89811711,99021067,96420429,37501808,44245960,10453030,44915235,83133790,44847298,6521313,26102057,93933709,18466635,36396314,11923835,84684495,41092102,99226875,92541302,5640302,88130087,50007421,41442762,36808486,75128745,79942022,89996536,54987042,40781854,17857111,36933038,94360702,11757872,13862149,749283,3773993,9829782,72358170,1936762,53632373,28851716,43506672,56473732,36845587,63152504,51426311,824872,17081350,97057187,45049308,21533347,78602717,11543098,11581181,74452589,93562257,57163802,23134715,84293052,27041967,38022834,72238278,67227442,26744917,90004325,30694952,20681921,23740167,89699445,33061250,83747892,89804152,24953498,68041839,96852321,29401781,84349107,48395186,8791066,79191827,27185644,23194618,45617087,26275734,12664567,47893286,92353856,4985896,18001617,14723410,14349098,74441448,4091162,87720882,61741594,37224844,69605283,75820087,73021291,67084351,91255408,38510840,79322415,90061527,27411561,60176618,43357947,21397057,6819644,37560164 +874791,67793644,14093520,62936963,55669657,51464002,73617245,54058788,49641577,20765474,30764367,43933006,33628349,99965001,13470059,77787724,63059024,65851721,67281495,72274002,51047803,9860968,40686254,40677414,36812683,18663507,36312813,38365584,96193415,39171895,93566986,12348118,59109400,40152546,7423788,86301513,91255408,82979980,31733363,3233084,24733232,2717150,68644627,47361209,64098930,65454636,84840549,93562257,99549373,45428665,27325428,75135448,36505482,33565483,83210802,61741594,56531125,14220886,68128438,66903004,38022834,83155442,96726697,81898046,95290172,1788101,54663246,75052463,97011160,55143724,89078848,30218878,76540481,56473732,43357947,61859143,66045587,77620120,62740044,20486294,59501487,39201414,78589145,33797252,44245960,96906410,4069912,58208470,68041839,60430369,72738685,60102965,53084256,44481640,8373289,45306070,40356877,83083131,52060076,77413012,44844121,10358899,11161731,3880712,84406788,71920426,62430984,22721500,36580610,33201905,84904436,9188443,70727211,9058407,39553046,57359924,26114953,69641513,30395570,54427233,80014588,87720882,44842615,73222868,1431742,78549759,92398073,80251430,20642888,60581278,45790169,83150534,92541302,19939935,71300104,24619760,3294781,97561745,30787683,85116755,70372191,61373987,62496012,75543508,6793819,96318094,27665211,37560164,17146629,36685023,44348328,77284619,95251277,15148031,22997281,43501211,27625735,20867149,62803858,66250369,89637706,4806458,526217,99333375,78561158,7229550,57240218,35092039,6871053,29466406,88653118,32590267,72614359,33431960,44550764,72732205,14731700,37183543,12024238,85023028,37891451,14349098,86242799,3509435,40984766,65017137,65074535,61960400,44784505,47090124,26392416,21001913,59329511,88251446,11365791,81172706,5640302,51590803,79806380,80316608,76330843,16054533,64157906,84293052,6497830,49597667,19891772,26493119,10366309,45237957,76825057,81274566,77187825,45617087,65338021,99297164,32161669,69786756,4119747,34698463,3487592,38645117,33699435,80246713,27411561,69605283,69136837,29994197,23110625,48893685,58749,64848072,94360702,29959549,25842078,98739783,5970607,26776922,6038457,20568363,30771409,10264691,6525948,86798033,4668450,13231279,88047921,80555751,14045383,247198,27185644,53632373,19486173,2208785,43152977,30139692,97783876,85242963,37620363,84166196,17857111,75229462,33304202,14626618,52293995,22450468,34667286,18833224,58751351,66667729,90158785,16405341,44846932,23432750,70782102,78602717,15948937,83789391,89214616,78218845,61982238,46851987,62115552,69355476,35996293,98462867,81855445,59405277,91727510,68316156,65081429,99021067,81853704,72777973,5111370,10453030,93053405,22108665,82886381,74110882,14326617,68939068,55470718,17058722,36753250,4508700,24826575,11923835,27041967,82726008,68102437,15535065,25636669,28928175,20645197,37957788,90013093,7502255,5073754,93515664,91831487,112651,89046466,30694952,31161687,4199704,72278539,55753905,33336148,24168411,91802888,57803235,94516935,60106217,20140249,21993752,51472275,68000591,20002147,42199455,92215320,99524975,9398733,5822202,68816413,60176618,17727650,12416768,45996863,48088883,50188404,17764950,99861373,38494874,86821229,47298834,34236719,69848388,44889423,95957797,13819358,31126490,37739481,43322743,5482538,72495719,3183975,63628376,39847321,29188588,36930650,62762857,50567636,57393458,77377183,74743862,40781449,83948335,48774913,40027975,44627776,32274392,71657078,93057697,70004753,60955663,84187166,91664334,29834512,30163921,53393358,81805959,3773993,18783702,51507425,61712234,44983451,55850790,87710366,63152504,42967683,20122224,98130363,93933709,3633375,6153406,96735716,15015906,2204165,14947650,72725103,93270084,17068582,63015256,33061250,86543538,46540998,76671482,15075176,90061527,98648327,29029316,38658347,3233569,26397786,7955293,77312810,73786944,3235882,18466635,69579137,8733068,49271185,41092102,168541,59318837,15064655,55189057,7646095,41380093,98948034,12571310,48658605,73235980,96852321,14363867,70596786,95010552,28851716,1197320,7687278,48360198,59177718,66137019,53648154,68204242,61271144,19272365,88444207,44847298,7104732,28734791,89811711,26664538,67124282,74357852,77272628,35456853,57241521,38008118,70800879,24591705,72019362,82779622,89996536,6521313,32250045,61897582,92692978,87160386,6819644,68824981,95581843,89804152,42947632,22435353,74724075,99224392,41481685,91938191,26292919,43506672,81677380,93790285,7685448,44060493,28787861,22200378,55602660,17539625,92530431,37435892,43376279,46870723,30463802,92803766,99604946,30569392,61859581,19457589,4099191,77898274,55247455,26063929,2917920,79191827,63967300,34497327,11581181,90090964,36780454,87598888,9603598,28796059,85117092,86460488,9175338,92353856,54762643,72358170,79136082,64602895,19101477,75820087,8128637,23194618,17081350,824872,51588015,53842979,6111563,22942635,27187213,7011964,54606384,77300457,67031644,54987042,65880522,59197747,4978543,4095116,13862149,51149804,9829782,37659250,7919588,20885148,75128745,91957544,12303248,21289531,31727379,52613508,86022504,48673079,15536795,29401781,86001008,84684495,2891150,19376156,32159704,9259676,40534591,16019925,26744917,18504197,33237508,64087743,85695762,669105,92787493,415901,73168565,73021291,20836893,70541760,93359396,97940276,13468268,61623915,98943869,7300047,56955985,92692283,38256849,21673260,51715482,18131876,61815223,90457870,98371444,36468541,34295794,16097038,42073124,62452034,1204161,71083565,92033260,67030811,26102057,83651211,42237907,79738755,61928316,92554120,79880247,82327024,76315420,26139110,6808825,29516592,73392814,84349107,38009615,569864,77072625,30543215,34698428,83302115,54199160,18806856,75153252,45407418,73109997,72357096,97641116,89699445,3088684,51466049,45919976,22766820,12664567,53547802,36189527,321665,29932657,85711894,92604458,65038678,35512853,39801351,59614746,1375023,74137926,21070110,71316369,16380211,66271566,23134715,89217461,89499542,24058273,2331773,53802686,6505939,30653863,29635537,17976208,4064751,33935899,26998766,6221471,73124510,49236559,10961421,10649306,24915585,44479073,34493392,18699206,70367851,99226875,67513640,39986008,69697787,94595668,66663942,31491938,21397057,9257405,49598724,47887470,83747892,37501808,57248122,76170907,99658235,11543098,99917599,37192445,94911072,36808486,62357986,17016156,91990218,33249630,65275241,58224549,4515343,4787945,54014062,8791066,40781854,57961282,49882705,15902805,9886593,86118021,22129328,62490109,38341669,90272749,88904910,82651278,16567550,82427263,17957593,47708850,55770687,1022677,23848565,88698958,47792865,50702367,75986488,16595436,23569917,47893286,60393039,71333116,45667668,7066775,52261574,29819940,1569515,67084351,76960303,33553959,94076128,18411915,99515901,8055981,13348726,97057187,75777973,79657802,53666583,66322959,508198,17365188,65047700,99729357,91281584,96709982,4434662,47213183,92867155,59371804,4798568,14723410,54232247,66319530,56424103,72793444,79922758,19898053,42692881,21533347,66611759,72373496,73031054,56461322,68694897,64055960,95395112,66832478,44177011,66116458,45990383,15163258,85571389,22879907,54517921,59910624,50668599,9599614,16971929,69255765,26863229,48395186,57658654,76703609,5267545,42307449,31904591,8099994,8115266,16445503,56515456,78300864,65715134,32699744,70420215,34946859,71469330,94090109,71965942,8807940,95726235,22405120,61141874,17894977,8651647,32058615,58180653,65271999,78766358,91141395,59981773,66885828,53888755,35192533,43045786,92998591,749283,61263068,48675329,7182642,24314885,67451935,34432810,92071990,63372756,4985896,76434144,40865610,45848907,30366150,99690194,88130087,42644903,18001617,97281847,55960386,89419466,16684829,55615885,30090481,62051033,33123618,84127901,48892201,90310261,45794415,10309525,78884452,57020481,26275734,9575954,24953498,97379790,41245325,1936762,70036438,16387575,32151165,1239555,99125126,72238278,50806615,30811010,23740167,8634541,9623492,4091162,39373729,48260151,57163802,68702632,10597197,82178706,56153345,2607799,38510840,8129978,45049308,21919959,5832946,38061439,77301523,32426752,33895336,82339363,26734892,49328608,51426311,63506281,16424199,56484900,62693428,41092172,54868730,83133790,13173644,83533741,83368048,74441448,36135,96420429,37224844,6986898,82897371,42782652,8696647,51315460,40197395,176257,1250437,41442762,37280276,29510992,44915235,6157724,29065964,68110330,36845587,66428795,62552524,44473167,22113133,17037369,84002370,36139942,8729146,74614639,20681921,4204661,61728685,50007421,75407347,94539824,47738185,67474219,94711842,45381876,30891921,9860195,90654836,79821897,68875490,77694700,91240048,78909561,17386996,62428472,62232211,78785507,11757872,99971982,67227442,82052050,81774825,37675718,44664587,37166608,28550822,54263880,79322415,34044787,79942022,71522968,82532312,8913721,83269727,90004325,45075471,30998561,45995325,98653983,36933038,74452589,52204879,16099750,7517032,26507214,36396314,17385531 +70800879,60106217,83155442,7423788,44983451,90013093,55770687,81853704,6505939,61373987,34698428,34497327,97783876,6497830,8651647,12348118,62762857,77620120,8099994,26998766,15535065,1250437,27411561,55247455,44177011,76170907,26744917,9599614,49236559,10366309,77301523,85023028,1431742,76540481,72274002,69848388,44842615,76434144,26493119,75128745,22129328,53666583,40152546,98948034,89214616,72738685,12303248,4668450,78766358,70596786,50806615,62552524,52261574,48892201,29466406,14723410,44889423,73786944,62496012,415901,37280276,33431960,99729357,55470718,26114953,93057697,18663507,44846932,99965001,26776922,94539824,112651,62452034,99549373,98648327,30891921,31126490,81677380,50702367,89419466,29959549,68816413,65454636,6038457,39171895,38658347,35092039,73021291,9603598,44481640,98739783,77284619,33201905,40356877,29834512,71657078,30366150,59329511,96193415,62232211,48658605,67124282,42947632,62803858,39801351,61741594,99515901,5267545,65851721,37675718,66832478,50188404,1197320,91802888,63967300,29188588,70372191,11543098,34432810,33237508,68875490,69786756,1239555,86301513,81172706,43933006,52293995,22766820,57240218,22108665,1788101,24915585,91664334,84349107,39201414,63059024,669105,30787683,18783702,96726697,65074535,67281495,47792865,73124510,42692881,19272365,3233569,19486173,68102437,82178706,88653118,38341669,70541760,99021067,86821229,33895336,77072625,30543215,79136082,76330843,29932657,53888755,13862149,49882705,78549759,17365188,73617245,97057187,20836893,75052463,48088883,38494874,66319530,95581843,20885148,60581278,59501487,99224392,31904591,53648154,78884452,37891451,45306070,1569515,4515343,22721500,57658654,1204161,42237907,55602660,51426311,71300104,82532312,20122224,18504197,60955663,14220886,39986008,10649306,22435353,54427233,36930650,86001008,12416768,53547802,17957593,91727510,32250045,4978543,24619760,18699206,78589145,51590803,61960400,45848907,95395112,8791066,44847298,9188443,4798568,38008118,72278539,66611759,2891150,56461322,77413012,17894977,34946859,33797252,68702632,43045786,19101477,46851987,67793644,32161669,33628349,34493392,70036438,89699445,3183975,5111370,2607799,45237957,44348328,9886593,78785507,27325428,95957797,95251277,56515456,15902805,42967683,37739481,30771409,99524975,9259676,74110882,51047803,89046466,13470059,10358899,51464002,4099191,28734791,63628376,42307449,5073754,64087743,87598888,71920426,54014062,3509435,35192533,824872,62740044,28796059,27625735,73222868,38256849,62490109,19939935,85117092,99658235,60430369,6871053,41442762,34044787,85571389,8115266,23848565,33304202,97561745,9860195,8634541,2204165,72725103,54987042,79922758,55143724,69355476,45667668,16445503,80316608,36505482,16054533,9575954,8733068,20002147,62936963,22405120,36753250,17037369,19898053,59614746,29510992,65715134,6111563,7955293,87160386,21993752,10453030,77787724,168541,40865610,59910624,99604946,44784505,94516935,47213183,27665211,17764950,8055981,37620363,17539625,54517921,45428665,49641577,49597667,68316156,72777973,77694700,95726235,749283,40677414,10961421,6521313,77377183,75135448,11365791,54263880,83747892,66428795,56473732,20765474,48360198,4199704,61728685,4434662,21289531,64602895,64848072,62430984,18131876,77300457,874791,76671482,51472275,44245960,72357096,93566986,33123618,508198,73235980,88904910,14326617,6793819,92692283,78218845,93053405,80251430,87720882,72614359,78300864,91957544,7687278,37435892,71522968,66250369,7104732,24953498,18833224,69579137,91281584,33935899,29401781,99226875,57020481,83789391,526217,42199455,92692978,80555751,83651211,55189057,30139692,16019925,33699435,39553046,16595436,52204879,70727211,30764367,55669657,92867155,9860968,37183543,88047921,14093520,7011964,66322959,20486294,68939068,76315420,71965942,99917599,54606384,67084351,75986488,16405341,6153406,40781854,97940276,43501211,40984766,84406788,67227442,64098930,17146629,17016156,30998561,14349098,43357947,4806458,569864,36312813,93562257,93933709,51715482,84840549,99861373,4985896,24733232,35456853,92998591,65271999,55753905,24058273,84127901,74743862,92604458,26397786,20140249,49598724,40197395,57803235,89996536,23569917,45790169,90310261,44627776,21070110,15536795,99333375,6986898,84187166,77312810,36812683,96735716,38510840,68824981,97281847,34295794,98130363,91990218,81274566,5482538,9257405,59197747,34236719,36189527,27041967,61263068,24591705,83150534,75407347,74441448,61815223,75153252,6525948,3773993,15948937,61271144,17857111,5970607,66667729,54868730,95010552,47893286,71083565,78909561,85242963,19457589,54058788,16380211,70004753,50668599,36396314,68644627,38022834,61859581,93270084,86798033,9829782,9058407,93790285,16097038,51149804,33553959,92541302,97011160,63015256,19376156,8696647,7919588,92530431,15064655,4119747,30163921,32426752,43322743,61859143,51466049,95290172,18806856,89804152,17058722,56424103,19891772,92398073,68000591,77272628,82726008,48675329,89499542,53802686,4508700,71469330,15015906,12571310,3294781,38645117,15148031,32159704,65017137,37957788,23194618,6221471,85695762,7182642,2331773,42782652,69641513,4095116,73168565,28851716,37166608,9175338,17068582,30463802,3233084,36139942,9398733,29994197,17386996,29819940,43152977,33249630,77187825,99125126,50567636,53084256,61623915,6808825,33061250,83533741,46540998,66885828,69255765,96906410,69136837,34698463,72793444,96852321,92215320,48260151,69605283,83083131,79657802,14947650,59371804,90272749,13819358,36845587,41092102,22997281,44550764,79806380,30653863,76703609,32590267,32058615,7229550,83269727,60176618,51507425,80014588,26102057,88130087,16387575,78602717,89811711,28787861,92803766,52613508,67451935,49328608,7502255,20867149,27187213,51315460,10597197,18001617,32274392,44844121,27185644,93359396,3088684,94076128,98943869,31161687,55615885,8128637,81855445,26392416,16567550,7300047,61982238,73109997,92554120,94360702,88251446,12664567,43506672,72373496,74137926,82979980,38009615,36685023,13231279,13173644,9623492,38365584,91255408,26292919,60102965,82327024,1022677,8913721,41245325,98653983,26063929,79191827,7685448,49271185,68041839,75229462,83133790,24314885,45990383,48673079,17727650,54762643,21919959,90158785,35512853,68204242,15075176,54199160,22113133,58751351,54663246,3880712,73031054,82779622,91831487,58224549,67474219,7646095,26734892,11161731,3235882,2208785,11923835,96420429,79821897,78561158,62693428,75543508,89078848,31733363,98371444,4091162,82897371,74357852,10309525,14626618,44915235,22942635,72732205,8807940,40686254,247198,71316369,5832946,64157906,91141395,89637706,84684495,17385531,59177718,30569392,16099750,63506281,57248122,45075471,82886381,80246713,89217461,47887470,82651278,45995325,2717150,81898046,83302115,66137019,62051033,16424199,81774825,91938191,31491938,31727379,21397057,56484900,94595668,96709982,5640302,37501808,84904436,93515664,44473167,47361209,72495719,13348726,99297164,74724075,38061439,57241521,11581181,85116755,18411915,4069912,45919976,68128438,34667286,321665,25636669,70782102,12024238,26139110,30395570,17976208,58749,17081350,40534591,39847321,75820087,56531125,1375023,79942022,59109400,44664587,25842078,79880247,36580610,65275241,57163802,67030811,59318837,33336148,16684829,94711842,65038678,23134715,53393358,69697787,71333116,23432750,54232247,90004325,53842979,26275734,73392814,40781449,3487592,55960386,63152504,6819644,47090124,20645197,43376279,21533347,14045383,16971929,99690194,92787493,8129978,90090964,5822202,86118021,21001913,57359924,20568363,37224844,36468541,81805959,22879907,97379790,58208470,84293052,92071990,28928175,75777973,79322415,30090481,67031644,3633375,97641116,51588015,48893685,33565483,32151165,24168411,45049308,47298834,61141874,99971982,14731700,66663942,82427263,44060493,86543538,41092172,52060076,40027975,59981773,59405277,7517032,41481685,23740167,36135,61712234,4064751,66045587,91240048,20642888,4787945,176257,26863229,72238278,62428472,29065964,8373289,22450468,83210802,55850790,56153345,83948335,62115552,53632373,41380093,70367851,61928316,88444207,86242799,68110330,74614639,45381876,90654836,94911072,29516592,11757872,29635537,45794415,6157724,72019362,82052050,87710366,42073124,26507214,70420215,47708850,48774913,76960303,7066775,13468268,23110625,74452589,2917920,28550822,56955985,36933038,92033260,30694952,72358170,82339363,65880522,77898274,66271566,47738185,66903004,90457870,65047700,36780454,48395186,96318094,88698958,24826575,94090109,84002370,37560164,45407418,22200378,45996863,18466635,4204661,57393458,63372756,21673260,35996293,58180653,29029316,42644903,39373729,37659250,86460488,57961282,98462867,79738755,30218878,65338021,66116458,92353856,8729146,68694897,32699744,62357986,83368048,37192445,44479073,36808486,86022504,61897582,85711894,26664538,65081429,45617087,14363867,67513640,1936762,76825057,84166196,46870723,50007421,20681921,15163258,64055960,90061527,30811010,10264691,60393039 +80555751,53842979,53802686,90310261,54199160,94076128,52261574,81853704,66250369,78766358,32159704,65715134,3294781,89214616,63967300,82886381,37891451,34493392,96906410,46870723,54427233,7687278,72732205,28928175,68875490,79191827,36812683,37192445,83155442,45790169,68000591,61373987,669105,1197320,34432810,79657802,48360198,93515664,9623492,91255408,66667729,95581843,7423788,44177011,10366309,96193415,77413012,59371804,71657078,86821229,87160386,88904910,99604946,24591705,63059024,29819940,98130363,36753250,51472275,92033260,67793644,18783702,43357947,60106217,6986898,99917599,96318094,94516935,54014062,53547802,66045587,526217,57658654,99861373,56515456,67281495,6793819,70372191,44784505,9398733,99524975,4787945,20885148,66832478,49236559,92787493,24619760,5970607,55753905,1250437,63506281,7104732,14731700,68824981,36312813,40865610,15064655,80014588,11161731,19272365,74110882,11581181,59329511,45428665,6221471,44889423,49641577,3509435,19939935,57803235,15948937,8115266,68204242,75128745,85242963,85117092,34295794,4515343,8733068,62740044,15535065,81855445,69355476,56424103,17068582,54987042,78589145,64848072,39986008,3183975,4798568,9599614,6808825,73168565,34044787,48675329,26063929,58751351,99965001,37739481,84840549,48673079,61859581,38008118,38061439,38658347,6521313,42692881,59501487,57248122,8807940,13819358,20642888,84349107,28734791,71920426,3235882,68316156,44983451,17857111,62452034,12664567,3773993,63015256,36930650,90457870,34698428,77300457,36468541,30891921,72278539,88047921,12348118,22450468,14947650,43045786,33565483,56461322,52293995,40781449,21993752,74137926,79942022,16019925,77694700,20486294,4668450,45667668,89804152,72793444,94090109,92398073,34698463,55247455,99226875,8373289,29959549,68816413,73031054,9188443,76170907,28851716,58224549,62496012,76703609,16405341,40677414,66611759,55770687,54606384,19376156,17764950,1239555,53632373,82339363,69579137,29510992,61263068,33237508,8129978,33797252,14220886,81677380,74743862,72738685,48774913,60102965,99021067,11757872,4978543,43501211,50668599,92554120,5482538,64157906,415901,50702367,82651278,8696647,5111370,14349098,95726235,91990218,72725103,50188404,19101477,168541,86118021,89419466,50007421,72614359,26998766,42782652,83651211,75543508,17058722,79922758,15148031,79821897,18131876,21919959,33123618,31904591,42307449,89046466,32161669,20140249,27625735,72495719,93270084,7011964,66885828,88251446,84187166,29029316,17727650,86242799,93359396,20645197,78561158,98739783,19486173,39201414,30787683,54663246,53084256,26734892,47708850,8128637,52204879,11365791,8634541,38365584,44844121,35456853,26744917,87710366,37183543,42073124,20765474,80251430,77301523,65338021,17037369,7646095,38645117,13862149,40197395,824872,24058273,32058615,45848907,6871053,38494874,36845587,8913721,32151165,67124282,2717150,39373729,16971929,95957797,10961421,86022504,16387575,71333116,54762643,6038457,94539824,35192533,10453030,82726008,85023028,65271999,92692283,17365188,95395112,96726697,44550764,4985896,43376279,23110625,42947632,61859143,27665211,78218845,1375023,84684495,94911072,48893685,18806856,18466635,96709982,89078848,26392416,73392814,66428795,50806615,31733363,83789391,37675718,18833224,65851721,79806380,70727211,23569917,7955293,97783876,59614746,60430369,81274566,26275734,44842615,29994197,30366150,72373496,17539625,97940276,91802888,22108665,96735716,99515901,3088684,67451935,1569515,75407347,6497830,5073754,22766820,83210802,66663942,23134715,81898046,47213183,26664538,65038678,36933038,10309525,99658235,33553959,40356877,68128438,112651,1022677,83302115,73124510,21533347,98943869,14723410,12416768,75153252,28550822,65454636,4508700,37620363,33628349,77187825,62430984,45990383,68939068,62803858,62936963,83150534,89499542,18663507,4119747,48892201,91664334,247198,29932657,35092039,37659250,62357986,97057187,749283,23848565,49328608,36580610,32699744,69136837,97281847,9860968,2331773,77284619,5822202,51464002,36396314,4099191,26292919,83747892,41380093,70596786,30218878,74357852,2208785,62693428,30395570,92530431,50567636,30694952,89699445,40534591,874791,75777973,24915585,35512853,71316369,34236719,9829782,22113133,47298834,72357096,321665,53666583,64602895,41245325,69848388,98948034,47792865,58749,14045383,14093520,29188588,61928316,26507214,91240048,33304202,80246713,4069912,82052050,57163802,59318837,56473732,5267545,40027975,77377183,45075471,64055960,61960400,73021291,62552524,49271185,65275241,26114953,5832946,33249630,7685448,55850790,34497327,65017137,97561745,84293052,56531125,14326617,61741594,6525948,9175338,89637706,33201905,1204161,16424199,17976208,4434662,93933709,29466406,1788101,77072625,90090964,22997281,17016156,3487592,78300864,73617245,70782102,77620120,93053405,9603598,26776922,69697787,61728685,56484900,40781854,62115552,30771409,96420429,73222868,79136082,35996293,89996536,70541760,33061250,9058407,76825057,29834512,45996863,16097038,21673260,39801351,74441448,62232211,33895336,21001913,9575954,95010552,18001617,44481640,62051033,31727379,41481685,45306070,26102057,75229462,40984766,10358899,83533741,44915235,17146629,84166196,21289531,508198,68644627,67084351,78884452,72274002,67031644,99549373,66322959,30543215,74724075,18411915,54517921,39171895,86460488,26863229,11923835,82779622,79880247,17081350,8099994,15015906,33336148,30653863,78602717,6505939,57241521,82979980,81774825,20836893,13470059,98648327,16380211,27041967,60176618,67030811,92692978,82178706,31126490,32250045,75820087,30764367,53888755,93790285,82427263,90013093,99971982,71965942,10649306,52060076,89811711,97641116,48260151,22942635,27411561,37280276,93566986,30090481,76540481,6153406,25636669,71083565,91938191,44348328,37501808,29065964,66903004,12303248,94360702,59109400,20002147,55143724,3880712,4806458,44847298,61897582,26139110,73235980,61982238,90004325,55189057,53648154,88444207,17894977,7502255,65074535,66137019,12024238,45919976,71522968,80316608,51047803,84904436,92998591,76315420,9886593,82532312,14626618,73786944,2891150,30139692,38341669,10597197,30811010,8651647,27325428,72019362,36135,84002370,70800879,71300104,45617087,77272628,84406788,29401781,91957544,88653118,87720882,51507425,7517032,44245960,90272749,7919588,45794415,57961282,8055981,6819644,63372756,49597667,54232247,57020481,13468268,47090124,27185644,47893286,13231279,61271144,97379790,28796059,86001008,36808486,24953498,15536795,54058788,82897371,16054533,91831487,91727510,45049308,46851987,19457589,16099750,47361209,68102437,87598888,77898274,86543538,23432750,9259676,34946859,40152546,23194618,66271566,88130087,19898053,62490109,3633375,23740167,44473167,24314885,32426752,83368048,2204165,76960303,20122224,27187213,46540998,74452589,22129328,51315460,99125126,43933006,47887470,55602660,17385531,99224392,2607799,85116755,22721500,33431960,64087743,15075176,569864,176257,96852321,37435892,68694897,24733232,67227442,62762857,22200378,56153345,2917920,81172706,56955985,94595668,58180653,51466049,65081429,39553046,84127901,67474219,1431742,93057697,90654836,41442762,33699435,60955663,94711842,95290172,70036438,54868730,81805959,19891772,76434144,4199704,20867149,4091162,43152977,68041839,21070110,92215320,85695762,90158785,65047700,29635537,72777973,86301513,36685023,68110330,36139942,85571389,54263880,22435353,34667286,69641513,83083131,30569392,44846932,16445503,42199455,59405277,37166608,15163258,78549759,77312810,65880522,51715482,57359924,37560164,37957788,92604458,83269727,78785507,74614639,53393358,72358170,58208470,69255765,98653983,21397057,30163921,13348726,32274392,76671482,15902805,28787861,68702632,61712234,30998561,98462867,66319530,45237957,7300047,70004753,70420215,16567550,3233084,82327024,41092172,69786756,38022834,24168411,92071990,18504197,7229550,43322743,16595436,22405120,70367851,98371444,92541302,44479073,22879907,51149804,99297164,8729146,45381876,4064751,36780454,4204661,32590267,26493119,48088883,92353856,11543098,99729357,8791066,18699206,60581278,69605283,38510840,42644903,4095116,45407418,59197747,12571310,5640302,26397786,17957593,99333375,33935899,63628376,93562257,20568363,85711894,42237907,88698958,36505482,45995325,42967683,44060493,75986488,38009615,1936762,48658605,57240218,48395186,75135448,52613508,30463802,51590803,61141874,89217461,66116458,73109997,16684829,40686254,90061527,92803766,59910624,77787724,61815223,55470718,41092102,51426311,59177718,9860195,86798033,72238278,39847321,49598724,55960386,92867155,6111563,31491938,83948335,44627776,95251277,13173644,55615885,83133790,61623915,57393458,91281584,71469330,7182642,78909561,49882705,31161687,36189527,9257405,38256849,47738185,17386996,79322415,43506672,51588015,76330843,91141395,64098930,97011160,10264691,3233569,24826575,99690194,25842078,7066775,29516592,44664587,20681921,6157724,79738755,59981773,14363867,63152504,55669657,75052463,62428472,67513640,37224844,60393039 +5111370,52613508,45407418,43376279,89078848,70036438,24058273,51047803,47090124,44481640,47792865,24168411,95957797,54663246,63059024,30090481,84904436,44983451,60102965,10453030,20765474,1431742,4806458,55247455,35996293,29065964,90457870,98371444,67474219,33201905,93566986,96735716,20002147,29994197,19101477,168541,68644627,71333116,2717150,669105,5482538,88653118,68000591,75543508,29834512,2607799,94595668,76330843,92604458,99524975,99549373,9058407,51464002,9257405,89214616,73786944,83302115,3509435,18411915,66667729,45617087,49598724,16445503,62430984,85116755,60430369,49641577,55753905,72373496,66045587,75777973,71657078,18783702,68816413,35192533,30463802,36808486,53666583,38061439,96193415,17727650,70800879,18699206,43322743,61623915,22721500,56531125,8807940,65338021,39801351,48088883,83651211,6793819,34236719,415901,34044787,45794415,52261574,65275241,55960386,66250369,43357947,62693428,76540481,14093520,4204661,4099191,29029316,7646095,33553959,78766358,41092102,13348726,73124510,27411561,50702367,97561745,66832478,26392416,7919588,82979980,92998591,66903004,16097038,21070110,92803766,36780454,86001008,92787493,61859143,45306070,14326617,38658347,34698428,13862149,75986488,84349107,78909561,72614359,79191827,76434144,36312813,15148031,67084351,61263068,4668450,84840549,10309525,93515664,508198,62452034,32426752,99604946,44842615,6525948,22942635,27665211,74137926,47361209,91281584,4095116,83368048,17764950,95395112,92692283,24619760,30366150,69136837,42644903,66137019,9599614,99965001,99226875,14626618,49271185,81853704,13173644,33797252,83747892,61373987,30891921,94711842,39373729,84166196,19272365,25842078,73617245,67793644,63506281,18131876,90310261,98648327,91802888,29819940,60955663,58751351,45075471,86301513,79657802,37620363,46851987,35092039,33237508,53547802,247198,84187166,69355476,17016156,26507214,14220886,2331773,5640302,71522968,44479073,52204879,92215320,57240218,2204165,67513640,16054533,98739783,67281495,35456853,89499542,39553046,57248122,54199160,9175338,90061527,75229462,99125126,93562257,54058788,95010552,48260151,95290172,321665,32590267,49328608,77272628,37183543,77787724,76671482,77284619,44889423,22113133,92353856,36580610,72777973,55470718,18504197,4119747,7011964,4508700,99861373,28928175,59197747,34493392,10358899,38008118,4064751,30395570,94360702,62357986,61982238,40984766,47738185,33249630,85571389,2917920,94090109,71920426,53802686,93933709,31161687,92398073,26397786,77187825,76170907,46870723,8129978,37739481,1204161,7423788,72738685,14947650,62936963,19486173,8651647,15064655,26493119,36505482,74357852,9188443,16380211,4787945,67031644,84684495,73031054,71083565,14363867,57020481,61897582,61271144,5832946,1375023,82897371,15163258,22450468,57393458,10597197,30764367,16567550,40197395,62740044,20140249,38022834,48893685,15535065,7517032,47708850,41442762,82886381,8099994,77694700,50188404,81898046,40781449,66271566,61141874,42967683,54762643,68939068,88251446,65047700,59910624,30139692,16405341,11543098,74743862,26776922,12664567,7955293,96726697,87710366,6505939,85117092,9829782,22879907,69255765,19376156,43152977,61815223,32250045,4515343,67124282,97940276,74441448,80014588,74614639,39171895,99333375,56473732,31126490,90272749,176257,10649306,86118021,51590803,40865610,62803858,65271999,19939935,71300104,83210802,72725103,74110882,3773993,18663507,59177718,48892201,32151165,86022504,89811711,86242799,61960400,98943869,69786756,5970607,53632373,97641116,10366309,96906410,54987042,11923835,81855445,76825057,53648154,92554120,6221471,29188588,79136082,18833224,70367851,80316608,59329511,15948937,99021067,8733068,85023028,69641513,37659250,19891772,48395186,526217,70727211,824872,20568363,54232247,16099750,51588015,4798568,6157724,40686254,68694897,10961421,12571310,66428795,99515901,67227442,56424103,91831487,44473167,99658235,68824981,33061250,15015906,28851716,3183975,874791,34497327,40534591,62232211,65715134,73168565,37675718,51507425,56515456,73235980,54517921,30811010,29635537,8791066,64602895,82052050,30218878,60176618,2208785,51426311,45237957,42073124,17386996,57163802,14045383,89419466,93270084,8055981,84293052,48673079,33895336,36812683,32161669,38494874,51472275,70372191,20885148,42947632,13819358,61712234,62051033,41380093,44177011,1197320,38365584,24733232,55770687,9603598,73392814,16595436,28734791,83150534,12416768,45995325,37192445,77072625,83533741,85242963,25636669,77620120,82339363,38341669,40027975,50806615,36933038,76960303,75820087,58749,29466406,6497830,78218845,83083131,44915235,9623492,66322959,27325428,33336148,20486294,3233084,77312810,99971982,112651,66885828,29959549,88047921,62762857,43501211,91957544,5822202,59109400,79821897,3880712,30998561,35512853,61728685,86821229,45996863,56153345,49882705,72238278,6153406,34946859,33628349,89996536,92071990,30569392,38645117,63372756,52293995,43045786,80251430,33699435,91141395,91727510,91990218,65074535,55143724,59614746,6521313,62115552,81677380,18466635,20645197,80246713,98462867,42237907,55189057,45667668,97011160,23569917,27187213,75135448,97783876,62552524,99297164,87598888,95726235,16684829,44844121,65038678,17539625,67451935,66611759,57803235,49236559,21993752,97379790,72274002,15075176,65017137,7182642,67030811,64098930,91664334,17081350,57241521,44348328,26063929,64848072,23848565,42199455,92033260,26275734,34295794,37166608,36135,6986898,8729146,68204242,79922758,89046466,54606384,82427263,70004753,63628376,45919976,8634541,78602717,36396314,31904591,50668599,27041967,9575954,30163921,90013093,88130087,1239555,77377183,77413012,50007421,39847321,58224549,36468541,42692881,75052463,56461322,89637706,8373289,52060076,17385531,41245325,74724075,48360198,17857111,33935899,90090964,4069912,47298834,98130363,39201414,22435353,38009615,99690194,54014062,64157906,96318094,7687278,82726008,83133790,7300047,40152546,21673260,3294781,61741594,22108665,33565483,36930650,43933006,81805959,37891451,26998766,94076128,1569515,65880522,36753250,95581843,88444207,64087743,20867149,22200378,70596786,5267545,21533347,68316156,81774825,40781854,88904910,31733363,37957788,77300457,76703609,17068582,20681921,90158785,83948335,65851721,26114953,21001913,31491938,93053405,72495719,37501808,48675329,36845587,44846932,45790169,96709982,34432810,30787683,57961282,56955985,37224844,51715482,63967300,17957593,12348118,17037369,44784505,83155442,78884452,53888755,8913721,26139110,44847298,28796059,39986008,70420215,23194618,45428665,49597667,89217461,72278539,41092172,26863229,44245960,62496012,22766820,30653863,1250437,16971929,71965942,36189527,21919959,82532312,83269727,92541302,84002370,9886593,73222868,55850790,16019925,26664538,83789391,47887470,79806380,92530431,97281847,59981773,60581278,11757872,55669657,97057187,78589145,41481685,88698958,68702632,54868730,69605283,16387575,63152504,46540998,21397057,82779622,60106217,34698463,4091162,6111563,53842979,64055960,24591705,66319530,17058722,7685448,75407347,14731700,68102437,29401781,33431960,65081429,72793444,99917599,3235882,22997281,16424199,2891150,78561158,6871053,66116458,62490109,5073754,18806856,61859581,14349098,77898274,33304202,87160386,40356877,59501487,33123618,78785507,75128745,89699445,69697787,79738755,18001617,85695762,78300864,91938191,44664587,81274566,749283,99729357,80555751,31727379,68128438,98948034,96420429,27185644,82178706,6819644,50567636,59405277,7502255,1936762,32058615,51466049,54427233,76315420,6808825,3088684,19457589,11365791,26744917,3233569,40677414,82651278,13231279,15902805,96852321,22405120,89804152,44060493,20122224,59318837,15536795,12024238,70541760,84406788,84127901,94516935,37435892,38256849,24915585,78549759,57658654,58180653,36685023,81172706,17894977,569864,59371804,3487592,11581181,72019362,28550822,26102057,7066775,86543538,55602660,51315460,23432750,43506672,53084256,24953498,42307449,66663942,91240048,58208470,17976208,45381876,20642888,90004325,8115266,48774913,65454636,68875490,71469330,79942022,94911072,4985896,32699744,27625735,72357096,45848907,38510840,73109997,56484900,48658605,17146629,51149804,86460488,23110625,54263880,7104732,23740167,44550764,24826575,19898053,8128637,74452589,36139942,55615885,75153252,45990383,53393358,90654836,10264691,26292919,93359396,34667286,87720882,85711894,98653983,86798033,93057697,45049308,23134715,29932657,6038457,29516592,9860195,1788101,17365188,32159704,92692978,69579137,28787861,93790285,30543215,30694952,21289531,62428472,42782652,20836893,9398733,1022677,69848388,72358170,79322415,94539824,44627776,7229550,13470059,3633375,91255408,72732205,95251277,79880247,22129328,82327024,9259676,47893286,24314885,57359924,73021291,37280276,99224392,13468268,92867155,12303248,30771409,68041839,60393039,71316369,4434662,11161731,8696647,32274392,61928316,14723410,4199704,47213183,26734892,29510992,77301523,4978543,70782102,63015256,37560164,68110330,9860968 +67451935,63967300,4095116,13173644,55753905,36505482,56424103,17764950,9886593,29466406,62496012,12348118,44983451,88698958,76960303,64087743,40152546,48673079,85116755,33237508,24733232,60102965,31126490,54987042,9575954,75986488,10597197,68875490,17037369,569864,40356877,57163802,13231279,83210802,89637706,82327024,29188588,22997281,72373496,45996863,78602717,71469330,73235980,87598888,78589145,51047803,87710366,59177718,13468268,29819940,57803235,92398073,71333116,14947650,30764367,90090964,7423788,8791066,20002147,86022504,9623492,14220886,2204165,94516935,99965001,6153406,36780454,89419466,92787493,27665211,5832946,30395570,65715134,80316608,9860195,8099994,61982238,7182642,35092039,14731700,79322415,78909561,68041839,3487592,42644903,16567550,66319530,1788101,29834512,40027975,8807940,66832478,65851721,20486294,63372756,4099191,68702632,20140249,83155442,79657802,18663507,38645117,18806856,15015906,5111370,48260151,73031054,44915235,54762643,68128438,9188443,35456853,82427263,97057187,55247455,23569917,10366309,74357852,67227442,99549373,247198,71657078,96726697,98371444,63152504,79821897,45848907,61960400,18783702,92033260,70372191,88047921,49882705,25636669,45237957,35192533,37739481,81172706,321665,44889423,99971982,78766358,66428795,7646095,20122224,72357096,55143724,48892201,96852321,26114953,45995325,88653118,43501211,21070110,66885828,98653983,22942635,45407418,30139692,41092102,7955293,93933709,85117092,33699435,66667729,57240218,59329511,7011964,56515456,7685448,51464002,65275241,32426752,26292919,94090109,8115266,77694700,79922758,9599614,82178706,41245325,30771409,21993752,3509435,67474219,95581843,32699744,27325428,59981773,168541,83302115,91802888,73124510,92803766,60955663,92071990,29994197,71920426,81898046,34698428,38008118,669105,29959549,56531125,1197320,96193415,61928316,749283,45617087,95290172,16019925,72274002,88251446,98648327,76170907,80251430,10309525,19898053,33895336,82886381,29516592,43045786,58180653,40677414,34295794,20867149,90272749,59197747,68816413,55602660,42237907,77620120,34236719,28550822,44784505,84684495,53547802,30998561,45428665,61263068,32058615,6221471,4091162,81774825,78785507,48658605,82897371,7919588,40781854,99917599,96709982,71300104,15535065,28928175,53632373,18504197,55850790,83150534,1431742,15064655,73786944,66663942,415901,62803858,70541760,30787683,4119747,37659250,89078848,40865610,17386996,44479073,93515664,44844121,37620363,34698463,70036438,55669657,70004753,56473732,9860968,70727211,96735716,5267545,50188404,54199160,508198,16445503,7517032,42967683,91141395,22129328,42073124,89811711,83133790,78300864,30543215,17957593,73222868,874791,68204242,28734791,36753250,48675329,4668450,98462867,54517921,77312810,6793819,10649306,47361209,27411561,63015256,26507214,5822202,92692283,9175338,15075176,73617245,61815223,84904436,55189057,86242799,31904591,2891150,84349107,38022834,35996293,81805959,7502255,26139110,26664538,34493392,49641577,63506281,2208785,81274566,99604946,51472275,67793644,91831487,69605283,5640302,83368048,59318837,37183543,38009615,8733068,59910624,90013093,5970607,16684829,71316369,77284619,32590267,14093520,64602895,92554120,8651647,30163921,79136082,69579137,6808825,44348328,66903004,30090481,99729357,89214616,83269727,65880522,99515901,24619760,64157906,97783876,22405120,45667668,99690194,32159704,65271999,16099750,62936963,37435892,21533347,65047700,11923835,15902805,76315420,55470718,40781449,75820087,69697787,67281495,12416768,43933006,8634541,18131876,83747892,32250045,33797252,47708850,19272365,66116458,50806615,3233569,39847321,77072625,70800879,99861373,76434144,6986898,19891772,47298834,30366150,3633375,54868730,86001008,93562257,49271185,61859581,58208470,66271566,73021291,8129978,3088684,82979980,47887470,43506672,81853704,52060076,26493119,33553959,91727510,33431960,98130363,73168565,18833224,16097038,23194618,23848565,99125126,32274392,16054533,85571389,55615885,45075471,77272628,42692881,38256849,59501487,84127901,72725103,43322743,66322959,51149804,45919976,14326617,32161669,16405341,2717150,24826575,11161731,3233084,51507425,3183975,31161687,53842979,61623915,51588015,23110625,93053405,1204161,2607799,65017137,42307449,98739783,34044787,82726008,89699445,63059024,17539625,75135448,62430984,26776922,10358899,62693428,86460488,99658235,71522968,57658654,63628376,99524975,1569515,5482538,94911072,47090124,84840549,7300047,52613508,39171895,44550764,40984766,64098930,99021067,1250437,30463802,3294781,95395112,22450468,10453030,21289531,18699206,17058722,93790285,6038457,28796059,14349098,83651211,95726235,89046466,99297164,22435353,36930650,69355476,33123618,22879907,91664334,76540481,824872,24058273,56153345,92867155,4434662,9829782,39986008,38061439,56461322,37166608,72238278,53888755,6521313,52204879,4985896,60106217,61728685,73392814,36580610,9058407,44481640,76825057,5073754,92692978,16971929,76330843,16424199,40686254,68102437,92530431,61859143,83948335,66250369,50668599,78218845,50702367,52261574,13348726,18411915,30811010,90004325,22108665,66611759,69641513,44177011,95010552,44060493,87720882,79738755,49328608,19376156,93270084,85695762,30694952,48088883,4787945,98943869,79191827,61741594,44473167,27625735,62552524,77787724,45790169,18001617,80555751,77300457,46540998,72777973,36685023,65038678,18466635,43376279,39201414,19101477,88904910,45306070,7229550,10961421,94539824,29510992,15148031,44847298,13819358,11543098,72614359,56484900,56955985,7066775,92215320,37501808,8128637,34497327,90457870,44846932,4069912,28787861,54663246,91990218,84293052,72358170,89804152,20765474,34432810,20836893,84187166,65338021,94595668,80246713,38658347,78561158,94076128,50567636,69136837,15536795,46870723,55770687,68000591,54606384,72278539,57241521,68694897,72793444,59371804,83533741,62740044,9398733,54232247,97940276,55960386,43357947,74614639,82532312,75229462,19486173,87160386,82052050,6525948,68939068,17727650,11581181,8696647,49236559,86301513,82339363,4806458,85242963,16595436,71965942,9257405,12571310,54014062,92353856,61271144,70420215,62232211,74137926,6505939,12024238,59109400,80014588,79806380,44627776,75777973,62115552,93057697,75543508,45381876,24953498,30653863,112651,26392416,57393458,97561745,9603598,44842615,48774913,4204661,75153252,33304202,62762857,71083565,15163258,33201905,6871053,17068582,7104732,68644627,14045383,36808486,77377183,30891921,75128745,3773993,58749,39553046,92541302,91957544,33565483,4515343,8913721,62490109,27187213,17146629,37891451,77898274,14626618,53666583,76671482,59614746,30218878,86821229,81855445,97011160,38494874,25842078,46851987,72738685,54058788,21673260,24915585,26998766,39801351,62428472,42782652,91240048,31727379,49598724,67124282,47792865,60581278,36468541,36312813,89217461,53802686,58224549,8729146,65454636,47213183,79880247,48893685,66137019,4199704,42947632,77187825,60430369,37192445,17081350,24168411,53648154,69848388,20568363,37280276,88444207,72019362,94360702,17894977,72732205,27041967,6497830,17365188,29065964,8373289,82651278,26397786,40534591,67084351,23432750,74110882,77413012,32151165,16380211,53393358,31491938,98948034,47738185,95957797,34667286,20885148,41481685,38341669,21919959,34946859,47893286,65074535,36189527,79942022,19939935,78884452,84002370,57961282,35512853,92604458,30569392,176257,70596786,22721500,37957788,84406788,41442762,37675718,97641116,36396314,90158785,28851716,19457589,61373987,3880712,57248122,66045587,33249630,36845587,72495719,88130087,4978543,65081429,67031644,57359924,90310261,74743862,96318094,68316156,99333375,33061250,51315460,83083131,20642888,83789391,78549759,26275734,33628349,17976208,93359396,81677380,44245960,48360198,2917920,90061527,10264691,76703609,1936762,64055960,9259676,4508700,26744917,14723410,51466049,6819644,29635537,13470059,54263880,45794415,4064751,61141874,61897582,26734892,51426311,62452034,85711894,6157724,36933038,29029316,69786756,45990383,91938191,99226875,36812683,60393039,74441448,22766820,84166196,26063929,61712234,41380093,2331773,11757872,22200378,64848072,12664567,26102057,50007421,89996536,1022677,58751351,75052463,36135,97379790,67030811,60176618,85023028,96420429,53084256,42199455,91281584,67513640,68824981,68110330,54427233,17016156,51715482,90654836,86798033,7687278,62051033,26863229,20645197,74724075,1239555,17385531,39373729,24591705,77301523,24314885,82779622,49597667,40197395,73109997,69255765,74452589,97281847,14363867,51590803,93566986,21001913,44664587,70367851,27185644,99224392,37560164,52293995,43152977,13862149,4798568,37224844,33336148,23134715,62357986,36139942,29932657,94711842,86118021,31733363,38365584,8055981,526217,89499542,92998591,22113133,57020481,86543538,23740167,70782102,20681921,91255408,33935899,45049308,38510840,15948937,1375023,17857111,75407347,95251277,12303248,48395186,41092172,96906410,16387575,3235882,59405277,6111563,11365791,29401781,21397057 +10358899,2891150,8634541,47792865,72373496,53666583,19272365,415901,77620120,29834512,7011964,98943869,76330843,11923835,31491938,37620363,14220886,99524975,55960386,22129328,24058273,6808825,68824981,29959549,62936963,68102437,56515456,26776922,6497830,69255765,37659250,99515901,76170907,59981773,28928175,73124510,27665211,26744917,96420429,68816413,96735716,45790169,95957797,96193415,66885828,93270084,21001913,93057697,52261574,79136082,49271185,12348118,49641577,17365188,22766820,18663507,9829782,91802888,35996293,26734892,39553046,14093520,75543508,95395112,2717150,40865610,51047803,19376156,85242963,76540481,40197395,34698428,97641116,21993752,569864,69355476,74614639,33249630,33201905,47361209,95726235,38658347,67793644,49597667,43501211,77694700,29994197,52204879,50188404,54199160,55247455,4668450,32161669,79806380,16380211,9623492,44847298,8729146,40781854,83083131,6986898,5970607,78884452,65074535,62452034,71300104,22997281,16445503,33237508,508198,85116755,15064655,48893685,90310261,45237957,50806615,89214616,14947650,34493392,13862149,40152546,60430369,53632373,72738685,80014588,65715134,8807940,57163802,60955663,17957593,53842979,20765474,18806856,69579137,35092039,96726697,44245960,98948034,84002370,92604458,98462867,44473167,41092102,98648327,60581278,42967683,30463802,46851987,40984766,67030811,54517921,68644627,44844121,48892201,38061439,36753250,45794415,55470718,26392416,82178706,65017137,38494874,14326617,3294781,53802686,81805959,9188443,63967300,76703609,92692978,83533741,1250437,66045587,44889423,7423788,81855445,6871053,59501487,69136837,8055981,98739783,65851721,5111370,73235980,44481640,91727510,9886593,4508700,67084351,40534591,58749,23848565,33431960,33797252,62496012,39201414,96709982,30395570,88653118,51426311,36468541,40686254,73786944,78602717,92353856,78218845,99021067,93053405,61741594,14349098,3487592,61960400,44348328,44842615,62740044,51590803,67281495,61623915,112651,78766358,61373987,70541760,93359396,68204242,84904436,8129978,68000591,20002147,15535065,99917599,33304202,39801351,75128745,99658235,51507425,20140249,83150534,27325428,66832478,64098930,9575954,19101477,31904591,80251430,64055960,71920426,37501808,50702367,48675329,39847321,63059024,55850790,72238278,17058722,1375023,86301513,44177011,25636669,31161687,54232247,68694897,90090964,40027975,44846932,7687278,62232211,23569917,89046466,61982238,65271999,57240218,7300047,55189057,5832946,38256849,80555751,20645197,6525948,89499542,72777973,48360198,18783702,11543098,20681921,93933709,81172706,74357852,72019362,87598888,30998561,4204661,36933038,15902805,96318094,98371444,34432810,67227442,7919588,77312810,73392814,47090124,36812683,11757872,91664334,45996863,68316156,9398733,36930650,83133790,58751351,23194618,99226875,41481685,89699445,7517032,30218878,34044787,8651647,81853704,86001008,39986008,9259676,64848072,18131876,69641513,94090109,50567636,19939935,51715482,26114953,95581843,62430984,59109400,4434662,54014062,68110330,65338021,43376279,82726008,54606384,14045383,83747892,90013093,2607799,30694952,57020481,8733068,24619760,3509435,3088684,45306070,6038457,29401781,4095116,99125126,34497327,57961282,53084256,66250369,42692881,59197747,3880712,4119747,59371804,74441448,84127901,70372191,49598724,73031054,40781449,36312813,85023028,5482538,1431742,18466635,48774913,89419466,4787945,61728685,24591705,57803235,72725103,98130363,61815223,84187166,53648154,26139110,53393358,71333116,57393458,6793819,36580610,3233084,99861373,73617245,3183975,70800879,97561745,77787724,26998766,58208470,9058407,59614746,52613508,59177718,8128637,54058788,9257405,10366309,32274392,92398073,36845587,43933006,20885148,62803858,54427233,72278539,36808486,247198,61859143,82532312,13231279,1204161,21070110,93562257,48395186,12416768,44983451,27411561,60106217,26275734,56424103,94595668,66322959,79657802,5073754,99604946,74137926,62693428,8791066,10453030,34698463,10649306,16595436,63015256,1197320,18833224,57658654,70596786,17764950,13819358,15163258,5640302,14363867,71469330,42073124,49328608,84349107,77301523,45995325,66903004,89217461,16684829,32159704,72732205,85711894,97783876,26102057,56531125,31733363,30787683,78909561,33895336,29466406,81677380,83948335,47738185,49236559,33061250,9599614,71965942,33628349,18699206,526217,77284619,66137019,42237907,64157906,84684495,16405341,42947632,40677414,37166608,44550764,11161731,91990218,51464002,33553959,29819940,79922758,89078848,32590267,61271144,61263068,81898046,30653863,38008118,6153406,8696647,36505482,76671482,22405120,90061527,75229462,2204165,45428665,4806458,66611759,14626618,7646095,4515343,39373729,8115266,45919976,21289531,86798033,77300457,99965001,56473732,66667729,8099994,88251446,38510840,4064751,17386996,874791,55770687,92803766,35512853,20486294,35456853,17068582,90457870,38022834,6505939,56153345,70004753,91831487,38365584,22721500,71522968,99297164,29932657,53888755,1022677,6819644,52293995,29029316,77898274,1569515,33565483,4069912,22450468,66663942,12664567,45075471,54263880,70367851,44664587,99729357,8373289,19898053,62552524,96852321,56461322,29516592,19486173,37435892,88444207,68128438,89811711,71657078,84406788,16424199,16054533,97011160,83210802,24168411,64087743,29510992,35192533,72793444,86118021,13468268,82327024,43152977,32699744,28851716,82651278,37891451,70420215,44784505,7955293,9603598,70727211,749283,95010552,17146629,5822202,72357096,55669657,60102965,61141874,21673260,4091162,4099191,15015906,29188588,37675718,79322415,84840549,99224392,82339363,71083565,22942635,3773993,19891772,94516935,30569392,29065964,27187213,669105,45667668,82052050,25842078,17016156,67124282,37192445,99549373,77072625,50668599,97057187,45990383,86242799,69697787,47213183,46870723,67474219,4798568,48658605,57359924,54762643,59405277,29635537,7182642,61859581,69605283,36780454,5267545,30891921,34946859,32426752,16097038,65081429,13470059,61712234,42782652,31727379,86022504,83789391,45407418,78589145,18504197,38009615,16019925,48088883,73168565,32250045,22113133,3235882,93566986,73222868,65454636,95290172,32151165,70036438,43045786,24826575,83368048,26397786,6221471,84166196,52060076,33699435,57241521,97281847,168541,68875490,55753905,92541302,18001617,45381876,63628376,77187825,94539824,24915585,36189527,62490109,7229550,65047700,72495719,45848907,33935899,87720882,75135448,17081350,65275241,54663246,48260151,8913721,34667286,79821897,41442762,26292919,51588015,79191827,7685448,91281584,2331773,49882705,88047921,99971982,82886381,62762857,26493119,38645117,78300864,30090481,89637706,18411915,24953498,21533347,22108665,20867149,87710366,75777973,91957544,33123618,34295794,15536795,84293052,77413012,90004325,99690194,14731700,92554120,62115552,53547802,16971929,37739481,68939068,71316369,15148031,74110882,74724075,41245325,78561158,20122224,62051033,19457589,17727650,30764367,85571389,12571310,39171895,36135,92215320,17894977,321665,94911072,86460488,68702632,80316608,90272749,3233569,91255408,75407347,9860195,3633375,41092172,59329511,58180653,72614359,23134715,22879907,77272628,92787493,94360702,54868730,9175338,30139692,28734791,1936762,55143724,2208785,31126490,57248122,47298834,17385531,91141395,37224844,14723410,26063929,17976208,92998591,10597197,58224549,43357947,92867155,82979980,6111563,40356877,92530431,78549759,75052463,7066775,1788101,75820087,33336148,21919959,4985896,16099750,83269727,26507214,83651211,34236719,73109997,24733232,66116458,66271566,30366150,75986488,89996536,37957788,9860968,63506281,87160386,74743862,80246713,30771409,27625735,43506672,56955985,59910624,74452589,56484900,92071990,72274002,88904910,16567550,23432750,67513640,22435353,78785507,64602895,79942022,61928316,51472275,7502255,83302115,67451935,20568363,51315460,69786756,88698958,1239555,76434144,20642888,73021291,70782102,176257,26664538,94076128,89804152,28550822,6157724,86543538,16387575,90158785,22200378,51466049,91240048,43322743,76315420,10309525,44479073,63372756,51149804,65038678,55602660,91938191,92033260,12303248,2917920,11581181,66319530,36396314,42307449,4199704,6521313,17857111,44060493,37183543,54987042,75153252,21397057,48673079,15075176,97940276,86821229,68041839,41380093,93515664,85695762,824872,17539625,63152504,17037369,60176618,62428472,32058615,69848388,99333375,47887470,47708850,85117092,95251277,36139942,27041967,83155442,13173644,98653983,24314885,67031644,82897371,92692283,65880522,7104732,30163921,81274566,4978543,82779622,90654836,79880247,45617087,12024238,44627776,47893286,81774825,13348726,36685023,97379790,66428795,20836893,42644903,42199455,30543215,37280276,94711842,96906410,59318837,50007421,30811010,76825057,44915235,61897582,72358170,26863229,79738755,45049308,27185644,93790285,23740167,76960303,10264691,38341669,60393039,88130087,28787861,82427263,46540998,28796059,10961421,77377183,55615885,37560164,11365791,23110625,15948937,62357986 +13231279,99965001,98739783,99549373,61859143,59197747,54014062,73786944,27665211,28796059,247198,95290172,112651,44889423,51047803,6819644,12348118,86821229,36812683,4806458,28851716,66322959,5111370,47090124,82979980,19939935,37183543,51464002,36505482,9623492,30395570,18783702,569864,49641577,36930650,30787683,35456853,37739481,508198,7502255,90272749,36580610,83368048,36780454,62936963,43376279,98462867,45428665,99861373,96726697,61373987,669105,40197395,59910624,92554120,15148031,68939068,29994197,92692283,83210802,8696647,71657078,90061527,65017137,79657802,99125126,77284619,33565483,85116755,6793819,11161731,88653118,7300047,1431742,8807940,39986008,18833224,90013093,26998766,93566986,57961282,44550764,10309525,26493119,24591705,74743862,16445503,99224392,43045786,68316156,39171895,73031054,98371444,1022677,19101477,97783876,55247455,9886593,26063929,68041839,60102965,13173644,98130363,63506281,62496012,29959549,1569515,83302115,61897582,9603598,40781449,89214616,82726008,50702367,17068582,15536795,74137926,70727211,20002147,61859581,66319530,22450468,42199455,64087743,60430369,3294781,64157906,31733363,37675718,14093520,67227442,45617087,65038678,63059024,40677414,39847321,33553959,92803766,45996863,55753905,8733068,81853704,71333116,3633375,1204161,85242963,91802888,59329511,69255765,54762643,99604946,82779622,10597197,36189527,45990383,80014588,48260151,72738685,48675329,4099191,14349098,68702632,29932657,51588015,27411561,35092039,73392814,30218878,6521313,81274566,76434144,83155442,18663507,37501808,65851721,22721500,65275241,13819358,84166196,61815223,29819940,34236719,56515456,22879907,66611759,62452034,67124282,72358170,13470059,30366150,51466049,16380211,97057187,7423788,28928175,75407347,68875490,84187166,56424103,81677380,20486294,57248122,3233569,24733232,4787945,69355476,76960303,22942635,44348328,68644627,32161669,10366309,65074535,18806856,93562257,53547802,72793444,87710366,7955293,85571389,874791,12571310,12416768,83651211,15948937,44245960,77312810,12024238,14045383,73222868,87720882,50188404,3235882,32159704,91664334,18411915,28550822,36312813,32058615,16971929,22997281,70036438,89811711,77413012,38494874,84293052,8791066,2717150,67084351,53666583,77377183,95395112,23848565,97561745,75128745,63967300,6808825,51715482,6986898,42073124,33201905,91957544,29834512,69605283,56473732,58751351,22129328,83150534,15163258,55143724,89419466,43357947,62428472,38008118,96193415,17016156,89499542,8651647,14326617,74357852,5822202,77620120,73168565,54663246,94539824,99333375,30463802,88904910,5640302,66250369,44844121,4095116,33304202,15015906,24915585,61263068,24314885,22108665,77694700,26397786,8634541,44983451,6153406,61982238,42947632,76540481,34295794,41092102,26776922,69786756,38061439,42644903,47708850,59109400,92998591,97940276,16595436,43152977,67281495,70596786,62693428,16097038,17727650,82178706,62803858,42237907,95726235,8055981,79880247,39801351,77272628,48892201,23194618,65338021,68816413,76170907,33797252,50668599,57393458,62762857,9175338,94076128,90310261,94911072,49597667,40356877,90090964,82886381,67793644,7919588,99515901,56531125,83083131,19891772,93790285,70800879,11365791,40686254,9398733,36753250,68102437,26507214,84684495,76703609,44481640,99658235,53084256,81855445,75153252,34497327,40152546,45306070,17957593,16567550,3183975,92604458,71316369,20140249,54427233,66116458,33699435,15075176,88444207,57241521,33237508,66903004,23569917,96709982,62115552,18699206,89078848,14731700,75986488,63372756,17146629,41092172,72357096,73235980,30771409,70541760,24826575,19272365,8115266,26734892,7011964,53888755,74614639,80555751,55470718,71920426,79821897,3233084,72274002,52293995,80246713,33895336,27041967,87160386,88698958,95957797,66832478,4119747,25842078,3880712,32250045,37560164,29029316,23110625,43933006,57240218,8373289,66667729,78909561,76330843,72732205,77072625,74110882,77898274,9860968,78549759,56484900,73617245,34698428,44842615,5832946,70420215,14723410,68128438,55669657,99226875,85023028,97281847,33336148,38658347,79922758,2917920,27185644,17764950,78218845,1250437,94516935,88130087,37435892,47887470,4668450,72278539,45995325,62357986,97011160,53648154,19898053,6871053,10358899,64098930,21070110,38009615,10961421,91831487,52060076,70004753,40865610,38365584,24058273,50007421,56153345,3773993,66137019,65454636,93270084,89046466,9058407,63628376,82052050,93933709,4064751,62740044,17058722,43322743,66428795,68694897,64848072,66663942,82897371,13862149,19486173,44060493,52261574,3509435,38645117,88047921,55189057,37620363,67030811,41245325,30543215,30090481,48893685,17894977,20122224,92541302,83948335,6505939,92033260,74724075,54987042,17037369,27325428,13348726,6221471,35996293,99971982,48395186,2607799,70372191,84840549,46870723,63015256,14947650,31126490,59177718,67474219,47738185,51472275,45237957,16099750,62490109,34493392,75229462,62232211,47361209,37957788,53393358,9575954,35192533,33431960,21001913,96852321,78884452,15535065,84904436,97641116,89699445,38022834,53802686,57803235,34698463,58749,61728685,7104732,65880522,415901,21993752,10453030,78602717,49328608,99917599,11543098,90457870,81898046,68204242,24619760,81172706,91255408,81805959,71300104,48673079,73021291,29466406,33628349,2891150,32590267,51590803,20642888,43501211,18131876,54199160,26139110,95010552,41380093,92215320,68000591,51149804,75777973,5482538,62552524,78561158,20765474,46540998,52204879,85117092,20867149,44846932,32151165,91281584,41481685,72777973,45848907,72019362,79191827,27187213,86022504,60176618,30653863,94360702,39373729,61623915,98648327,30694952,42782652,53842979,92692978,21919959,72725103,26114953,60955663,23740167,50806615,1788101,76671482,67513640,168541,69136837,6497830,4199704,73124510,54058788,38341669,9257405,89804152,94090109,7646095,17386996,61271144,47792865,67451935,96735716,31161687,33249630,39201414,18001617,65271999,98653983,2208785,36845587,59501487,65715134,79136082,86301513,30163921,17976208,60106217,23432750,526217,82651278,74452589,93053405,29188588,91240048,32426752,83789391,75543508,71083565,19376156,29401781,72495719,37166608,47893286,4798568,14626618,8099994,4515343,77300457,25636669,30569392,77787724,72373496,42692881,60581278,6038457,54606384,70367851,91727510,58208470,5970607,16387575,99729357,42967683,51507425,8913721,26744917,40534591,98948034,61960400,7182642,26392416,55602660,49271185,69579137,89637706,80251430,44847298,45667668,42307449,52613508,21289531,26292919,99021067,11923835,26275734,99297164,97379790,20645197,59614746,14220886,10264691,16019925,96906410,75052463,9860195,33935899,75135448,48360198,85711894,96420429,72238278,39553046,16405341,57163802,90158785,92398073,45790169,78589145,72614359,321665,77187825,91990218,98943869,31904591,44915235,23134715,30891921,30764367,75820087,37192445,96318094,31491938,40984766,61928316,35512853,86798033,55770687,33061250,7229550,48774913,44177011,36139942,82339363,22405120,86543538,94711842,91141395,15902805,45919976,20885148,86242799,68824981,34432810,1936762,37891451,29065964,67031644,21673260,2204165,95251277,1197320,18466635,50567636,31727379,17857111,66045587,64055960,84002370,33123618,20568363,11581181,4985896,176257,49598724,93515664,79806380,71522968,49236559,61141874,99690194,16054533,57020481,44479073,30139692,6525948,32274392,59371804,69641513,69848388,54517921,22113133,37659250,43506672,7517032,61712234,76315420,65047700,79738755,95581843,38510840,44784505,36396314,54232247,37280276,93359396,16684829,84406788,21533347,65081429,36685023,86001008,26664538,45075471,40027975,92787493,34044787,92530431,66885828,44627776,9829782,78300864,45381876,62430984,30998561,824872,1375023,62051033,90654836,28787861,5073754,2331773,17081350,82532312,13468268,15064655,9188443,10649306,49882705,63152504,85695762,9599614,16424199,24168411,83533741,53632373,89996536,55850790,70782102,92353856,24953498,84349107,84127901,4508700,99524975,56461322,34946859,36468541,26102057,47213183,51315460,4978543,28734791,36808486,86118021,92071990,78785507,22766820,93057697,57658654,87598888,47298834,1239555,6157724,82327024,88251446,17385531,79942022,22200378,21397057,22435353,45794415,86460488,7687278,48658605,44473167,3088684,14363867,12303248,73109997,45407418,8129978,46851987,66271566,83133790,17365188,44664587,60393039,20836893,64602895,57359924,9259676,19457589,8729146,4434662,89217461,55960386,78766358,7066775,40781854,91938191,4069912,51426311,12664567,36135,59981773,3487592,26863229,36933038,11757872,41442762,55615885,61741594,71469330,5267545,7685448,79322415,54868730,80316608,6111563,76825057,69697787,32699744,82427263,45049308,8128637,29516592,56955985,90004325,83269727,17539625,59318837,29510992,74441448,94595668,83747892,27625735,81774825,77301523,20681921,48088883,71965942,18504197,4091162,37224844,68110330,749283,58224549,54263880,4204661,38256849,29635537,30811010,58180653,92867155,34667286,59405277 +39801351,17957593,35092039,43376279,64848072,30764367,79657802,92692283,8807940,96726697,38061439,70596786,97940276,99658235,85571389,14349098,75153252,74724075,91802888,73124510,13231279,40197395,5111370,64602895,13470059,2717150,112651,77312810,65074535,98371444,58749,93933709,16019925,36580610,96193415,77620120,49641577,51315460,26292919,26734892,66428795,95726235,35192533,15535065,10358899,89996536,69255765,55669657,25842078,89419466,47090124,83083131,13862149,65454636,72738685,69641513,95957797,23569917,89214616,6793819,47708850,19939935,75229462,44348328,17068582,62452034,44481640,91831487,64055960,53842979,90272749,29994197,83133790,46540998,9259676,19891772,9886593,4064751,78218845,17365188,74614639,84187166,38494874,60102965,59197747,66250369,36845587,32161669,99021067,54232247,48774913,62051033,96709982,62430984,73031054,168541,73392814,415901,64087743,68102437,11581181,55753905,29029316,61741594,61859143,51464002,77413012,75820087,14045383,14947650,29065964,20645197,89046466,79922758,22942635,569864,18699206,29401781,53802686,51715482,4668450,48360198,3233084,42644903,23194618,77272628,50668599,45075471,10309525,62740044,72278539,10961421,9175338,76825057,15148031,61859581,12416768,30090481,89811711,7517032,22766820,34432810,43506672,37891451,874791,73235980,97379790,9603598,47792865,4119747,29834512,99861373,6986898,749283,7104732,24591705,86821229,67793644,10453030,77301523,33565483,93270084,33628349,87598888,29466406,40686254,36505482,84002370,28851716,54762643,21993752,57163802,47361209,19101477,26998766,78549759,61897582,16445503,29959549,61271144,39171895,48675329,1204161,18001617,54663246,48892201,87710366,3773993,67084351,29510992,14326617,61373987,4798568,7011964,76170907,72358170,75052463,44844121,74357852,69579137,26275734,38658347,91957544,75543508,68644627,77377183,26392416,15536795,83651211,89637706,57359924,9257405,4434662,34698428,72274002,92692978,84684495,76540481,3487592,53666583,45428665,40984766,19898053,5832946,39201414,45667668,92033260,76703609,9623492,38008118,45790169,70541760,50188404,54987042,62693428,26776922,8696647,82532312,93790285,16387575,33304202,83150534,23848565,6871053,9398733,4099191,30787683,49597667,51588015,89078848,30694952,28550822,6819644,92215320,80251430,79880247,57803235,66885828,70367851,63967300,12348118,4985896,61263068,68000591,7955293,51426311,44842615,81853704,45848907,92541302,44784505,43152977,66137019,6525948,247198,62357986,13348726,70800879,37192445,33201905,29188588,15015906,94360702,78602717,35996293,63152504,53632373,69355476,36753250,17976208,98462867,54427233,44245960,27325428,27185644,8733068,8913721,19486173,43322743,65338021,5640302,8791066,20765474,30395570,9860968,57658654,14093520,34295794,63372756,23134715,83533741,72793444,65271999,78589145,7687278,40027975,12303248,29819940,68702632,1250437,95395112,74452589,60430369,99729357,70004753,14626618,92554120,91281584,15163258,72495719,33336148,54058788,6808825,36808486,67451935,29932657,40677414,44847298,78909561,59177718,38645117,24058273,37620363,52204879,3509435,42967683,58180653,68816413,37280276,80014588,89217461,50007421,16054533,36685023,91938191,22129328,26744917,8055981,26493119,82897371,18131876,83948335,79942022,88251446,99297164,27665211,56153345,80316608,8115266,73617245,2204165,96735716,36189527,85023028,94911072,1022677,82052050,10366309,77284619,42307449,71657078,80555751,99524975,3880712,7300047,51590803,14220886,81172706,86001008,79191827,72725103,21001913,45996863,9188443,82779622,55470718,99515901,62803858,508198,23432750,57241521,86022504,99965001,56424103,87160386,88653118,50702367,85242963,97561745,44889423,72614359,8129978,1431742,321665,7685448,17539625,16971929,17386996,71965942,34497327,83789391,36780454,58208470,48260151,32699744,30998561,2208785,13819358,22108665,3294781,5482538,79136082,21533347,17727650,30463802,42782652,94539824,97783876,92398073,92998591,46851987,88698958,82979980,62496012,41092102,33237508,66319530,76330843,52293995,20885148,8099994,81898046,34698463,55189057,45990383,37560164,52613508,90310261,26114953,16424199,26063929,36930650,22997281,32159704,8373289,11757872,73168565,40865610,99549373,70727211,44177011,45049308,94516935,37435892,41245325,60106217,91255408,54517921,66271566,42073124,68128438,71083565,6505939,95290172,47213183,90090964,21673260,24953498,11365791,37183543,45237957,71333116,81677380,16097038,52261574,9860195,87720882,66832478,90061527,51149804,39373729,65275241,74110882,81805959,41481685,84127901,51047803,54263880,56531125,47893286,30569392,75777973,12664567,56484900,24733232,89499542,11543098,61982238,2917920,98943869,37224844,98739783,70420215,42692881,36812683,44479073,53084256,78561158,96420429,37166608,16380211,44983451,93515664,9575954,31126490,51472275,8651647,99226875,31727379,76960303,2607799,45306070,55247455,74137926,70036438,82339363,61712234,53547802,71316369,55602660,46870723,42947632,40534591,68694897,5267545,49328608,26139110,75986488,92867155,86118021,73222868,19376156,35456853,93057697,64098930,11923835,34044787,82327024,66611759,77187825,73786944,99125126,1197320,75135448,20568363,4787945,63628376,22450468,43045786,57248122,59318837,7919588,40356877,9829782,43501211,1375023,65851721,45617087,65017137,78884452,15075176,30218878,21070110,60955663,84349107,20122224,36312813,37659250,526217,18504197,7502255,34493392,18466635,81274566,17146629,86301513,67281495,67474219,30771409,68824981,96852321,32590267,41092172,34667286,39847321,11161731,77300457,85711894,65715134,88047921,30811010,21397057,66663942,76671482,12571310,62490109,2891150,14363867,33431960,7423788,44627776,59109400,55143724,36135,51466049,69697787,98948034,38256849,16405341,62762857,57020481,38341669,20486294,30543215,16684829,74743862,55960386,98130363,74441448,3235882,4204661,52060076,63506281,54014062,31733363,62428472,61141874,14723410,60176618,44473167,40152546,22200378,18783702,24619760,16595436,20836893,72777973,73109997,10597197,63015256,3233569,27187213,22435353,18663507,88130087,20681921,86460488,59614746,47887470,38022834,99333375,70782102,33553959,23740167,39986008,94076128,4806458,60581278,68939068,66667729,28787861,98653983,86242799,84293052,77072625,20642888,68316156,66322959,61623915,53648154,22405120,84166196,22879907,82726008,90654836,32426752,83155442,93359396,59910624,1569515,97057187,66045587,45794415,6157724,57240218,18833224,51507425,32250045,86543538,88904910,7066775,55770687,92803766,44550764,65047700,64157906,83210802,48893685,45407418,36396314,33123618,30366150,40781449,85116755,79821897,68041839,3088684,69786756,83368048,47738185,24826575,80246713,13468268,98648327,72357096,89699445,99604946,67227442,6038457,54606384,8634541,66903004,44915235,32151165,65880522,38510840,33935899,93053405,61960400,16099750,7646095,4091162,12024238,93562257,19457589,31491938,10649306,82427263,95010552,42237907,72732205,17058722,84840549,40781854,83302115,14731700,4069912,6153406,1239555,26507214,62232211,60393039,67124282,824872,55850790,6221471,27411561,17764950,76315420,39553046,71469330,36933038,81855445,37739481,6497830,95581843,33797252,8729146,76434144,89804152,92353856,79322415,13173644,45381876,35512853,91240048,69848388,78785507,6521313,31161687,77787724,62115552,43933006,37501808,56473732,26863229,44060493,91727510,47298834,62936963,59501487,25636669,50806615,18411915,71920426,57961282,59981773,4095116,61928316,72019362,99224392,69136837,17857111,48088883,17016156,45995325,48673079,54868730,4508700,99690194,28734791,90158785,27041967,84406788,68110330,37957788,96906410,9058407,44664587,70372191,37675718,24314885,26664538,56515456,9599614,16567550,24915585,32058615,20867149,61815223,90457870,17894977,15948937,91664334,50567636,53888755,56461322,97011160,36139942,49236559,82178706,97641116,71300104,10264691,30653863,96318094,43357947,67030811,58751351,53393358,36468541,17037369,92071990,78766358,33061250,82886381,18806856,45919976,85117092,33895336,65081429,71522968,93566986,38009615,176257,65038678,8128637,28796059,48395186,91141395,15064655,4199704,30139692,79738755,48658605,86798033,27625735,49598724,4978543,72373496,41380093,31904591,24168411,5073754,79806380,75407347,23110625,83269727,84904436,34946859,82651278,33249630,20140249,67513640,94711842,94595668,15902805,26102057,90004325,88444207,7182642,29635537,21289531,59371804,75128745,91990218,69605283,5970607,669105,66116458,61728685,17385531,3633375,3183975,94090109,49271185,34236719,6111563,41442762,62552524,32274392,97281847,22721500,22113133,30891921,54199160,67031644,19272365,56955985,33699435,59329511,1788101,63059024,78300864,42199455,77694700,38365584,58224549,55615885,29516592,59405277,99971982,92604458,92787493,4515343,28928175,5822202,92530431,7229550,90013093,26397786,49882705,17081350,21919959,73021291,99917599,44846932,83747892,68204242,30163921,81774825,68875490,2331773,57393458,85695762,20002147,77898274,1936762,95251277,72238278 +73124510,67793644,45407418,17764950,36505482,13173644,9886593,21993752,18833224,95726235,51149804,76540481,96193415,92033260,40152546,44842615,19939935,16971929,89214616,38061439,32426752,54663246,56461322,55247455,20765474,26139110,37620363,1936762,61859581,42073124,44481640,49641577,77284619,415901,14093520,40781449,15064655,97379790,86001008,82532312,53632373,31733363,61263068,72357096,59501487,76960303,63059024,54987042,52613508,73235980,75543508,8651647,78909561,30764367,44784505,91664334,55960386,99524975,72793444,59177718,35092039,73031054,8807940,45381876,14947650,90004325,34044787,24619760,84187166,45995325,88653118,91990218,10649306,14626618,62452034,81855445,89078848,15015906,55753905,99690194,73786944,99226875,90013093,16097038,93933709,4099191,84002370,35192533,29466406,32699744,71316369,1197320,36780454,44889423,55470718,47893286,24591705,18806856,7229550,85023028,56153345,17386996,18663507,2891150,66045587,66137019,99297164,92692978,63372756,9599614,88444207,31126490,95395112,29065964,26392416,87598888,71522968,18504197,22942635,57241521,68875490,26292919,41380093,79806380,44479073,51047803,64157906,20122224,93566986,62496012,53802686,508198,14349098,18466635,36808486,82979980,2917920,40027975,83133790,66903004,21070110,92215320,44664587,54232247,27665211,99917599,95290172,25842078,88130087,59197747,3233569,17976208,17068582,62430984,9175338,71920426,7011964,89217461,37675718,17539625,17365188,92867155,2204165,33123618,8733068,49328608,45667668,32159704,54427233,80014588,30569392,70036438,77620120,26863229,86460488,68816413,81274566,53547802,16445503,94360702,9623492,47708850,36135,6986898,66428795,10366309,7955293,66271566,36580610,87710366,26114953,37183543,78785507,62803858,22450468,66322959,55602660,45075471,2331773,39553046,42307449,56484900,24058273,92803766,39847321,27325428,50806615,45794415,16405341,80316608,76825057,57803235,74614639,30998561,99604946,58180653,19486173,28928175,44983451,43376279,61728685,68939068,51464002,82052050,18411915,22997281,40677414,98739783,12348118,69697787,6153406,66832478,37891451,1431742,82897371,64602895,97940276,72278539,48088883,36753250,99515901,82339363,92530431,321665,15535065,44177011,29959549,32058615,4095116,5640302,13348726,94539824,60430369,83533741,59318837,99965001,83269727,70420215,45919976,569864,64848072,54517921,65074535,65851721,46851987,91831487,78561158,75777973,29834512,85116755,90090964,30771409,51426311,91802888,31904591,60106217,17385531,33431960,16019925,40984766,65271999,18131876,78589145,16099750,87160386,74110882,40356877,53666583,71300104,32161669,41442762,69641513,34236719,31491938,72738685,38009615,71469330,56531125,49882705,37501808,41245325,1250437,70596786,26397786,4064751,26998766,11161731,42692881,9829782,36933038,8729146,43152977,75153252,54606384,36930650,22405120,62936963,54058788,77301523,4199704,10358899,85242963,72274002,83789391,77312810,58749,9259676,78218845,30891921,29994197,95957797,23134715,74137926,32250045,6808825,60102965,35456853,63015256,42644903,8373289,24314885,34295794,45790169,51507425,72373496,24826575,68000591,96906410,47792865,33699435,98948034,98130363,61928316,21673260,72732205,17957593,669105,112651,77072625,43322743,7687278,66663942,4798568,27625735,33201905,61141874,65275241,99971982,54868730,92787493,27187213,53648154,88251446,30543215,79922758,81898046,20867149,44846932,3633375,9575954,94076128,71657078,11365791,49236559,15536795,51472275,26063929,62552524,80246713,31161687,33336148,90310261,38645117,14731700,92554120,4806458,2208785,76330843,97281847,48260151,62740044,80555751,17857111,54762643,42782652,61982238,98462867,90654836,57240218,39171895,7517032,40781854,86118021,59371804,4119747,15902805,6505939,83210802,8634541,63967300,68644627,68316156,54263880,10309525,61623915,96726697,96318094,61960400,78602717,98648327,40534591,21001913,7104732,6793819,94711842,37739481,72614359,2607799,73392814,69255765,17058722,37659250,83150534,61859143,39986008,22108665,13468268,67513640,40865610,76703609,36396314,60955663,36812683,72019362,84293052,68694897,38658347,9058407,94090109,85117092,50188404,33061250,41092102,92353856,22129328,66667729,69136837,81853704,10961421,23194618,92604458,26734892,9257405,67474219,60176618,12571310,62693428,19891772,93515664,96735716,33237508,18699206,30090481,86242799,81805959,16380211,61271144,79136082,67451935,90457870,56424103,99333375,68204242,85571389,89996536,24953498,33797252,33895336,53842979,68128438,72725103,69355476,61373987,3088684,66611759,47361209,77413012,47887470,9188443,45990383,93053405,1022677,824872,99224392,8115266,70372191,30139692,99861373,14220886,99549373,34698428,10597197,99658235,38008118,55189057,64098930,70727211,44847298,73617245,36312813,50007421,39801351,93270084,526217,97561745,16054533,34497327,8696647,17894977,89046466,3773993,72777973,48360198,37166608,33628349,83948335,31727379,52261574,30787683,66116458,76434144,12416768,74724075,83368048,4204661,75128745,34493392,53888755,78766358,5482538,81677380,35996293,92998591,15163258,36189527,98371444,90272749,67030811,67281495,5111370,4668450,29188588,20140249,168541,44844121,43501211,10264691,45617087,87720882,73222868,5832946,54014062,4787945,26776922,28550822,72495719,50567636,20645197,50702367,49598724,3294781,65880522,66885828,65715134,76315420,58208470,9398733,79821897,51590803,61815223,247198,6038457,48892201,72358170,45848907,57658654,50668599,28787861,8129978,22200378,78549759,3183975,7423788,64055960,65017137,99125126,99729357,91281584,47090124,11543098,52204879,57248122,30218878,44627776,7300047,45237957,48675329,44550764,55669657,66250369,79738755,7182642,7685448,23848565,82327024,97011160,14326617,57961282,82178706,8099994,61741594,36468541,94911072,26275734,12664567,75135448,37435892,3509435,45428665,12024238,18783702,76671482,82427263,30395570,62232211,34946859,29635537,29510992,23110625,36685023,51466049,82651278,85695762,30463802,90158785,44060493,89811711,97783876,15148031,5970607,48893685,27411561,93359396,68702632,38022834,97057187,51715482,68102437,37192445,47298834,30366150,40686254,43045786,75986488,6871053,67227442,70004753,77272628,88047921,3233084,70800879,77187825,94595668,16595436,74452589,30653863,26102057,19457589,52293995,5267545,91141395,71333116,20568363,22879907,27185644,78884452,68824981,3487592,1788101,9603598,4069912,20486294,91957544,92398073,77694700,58224549,61712234,89699445,95581843,54199160,73168565,56473732,10453030,7919588,65338021,77787724,55850790,63628376,7066775,48395186,49271185,89637706,22766820,71965942,81172706,33553959,42967683,55143724,1204161,46870723,75407347,89804152,83651211,62115552,38341669,99021067,89419466,42947632,38494874,56955985,62051033,92071990,6525948,16567550,6221471,14045383,19272365,81774825,82726008,13819358,52060076,28734791,33935899,34667286,57393458,72238278,32151165,47213183,24733232,11757872,24915585,86798033,84349107,34432810,93562257,73109997,83155442,21919959,3880712,92692283,16387575,67124282,41092172,55770687,4508700,30811010,84406788,82886381,11581181,58751351,14363867,15948937,51315460,68041839,69786756,5822202,62428472,29932657,65454636,53084256,51588015,57359924,92541302,56515456,19101477,30694952,65038678,17081350,17037369,35512853,22721500,86301513,70367851,64087743,60581278,26507214,83302115,85711894,7502255,65081429,2717150,59405277,95251277,27041967,6497830,37957788,44348328,3235882,90061527,20642888,75229462,39373729,44473167,38365584,74357852,96709982,16424199,29029316,6111563,9860195,20836893,5073754,89499542,91727510,84684495,68110330,8913721,1569515,7646095,57020481,83747892,4091162,62490109,57163802,33304202,6521313,69605283,26493119,75052463,12303248,23740167,61897582,84904436,4985896,96852321,29819940,26744917,66319530,69579137,59109400,17016156,96420429,77300457,19898053,63506281,19376156,82779622,88904910,79191827,22113133,91938191,9860968,29516592,86543538,84127901,28851716,42237907,48774913,34698463,59981773,13231279,98943869,47738185,98653983,37280276,22435353,26664538,84840549,77377183,59910624,13470059,21397057,45996863,46540998,24168411,25636669,15075176,20002147,70541760,48673079,75820087,43933006,23569917,79657802,74743862,65047700,69848388,71083565,16684829,79942022,62762857,176257,93790285,13862149,33249630,28796059,62357986,59329511,21289531,95010552,45049308,42199455,86022504,32590267,53393358,21533347,11923835,4978543,8791066,91255408,44915235,32274392,55615885,39201414,49597667,37560164,17146629,20885148,74441448,77898274,6819644,874791,91240048,4434662,73021291,44245960,67031644,36845587,6157724,1375023,94516935,76170907,14723410,40197395,63152504,70782102,60393039,45306070,749283,38256849,8128637,59614746,4515343,23432750,93057697,17727650,79880247,97641116,86821229,37224844,1239555,29401781,30163921,8055981,84166196,79322415,48658605,88698958,41481685,83083131,18001617,20681921,36139942,67084351,43357947,43506672,80251430,33565483,78300864,38510840 +50188404,26392416,63628376,92692978,12024238,55189057,81677380,81898046,3233084,93053405,71333116,76170907,91664334,69579137,42692881,77620120,4434662,23194618,34295794,7011964,31126490,35092039,12348118,13231279,7300047,72274002,22879907,79880247,65454636,73235980,33201905,62496012,60581278,37560164,20642888,33304202,30395570,53666583,70596786,98943869,66611759,66116458,12664567,44245960,61982238,37501808,62803858,85571389,64098930,95290172,33249630,83150534,34667286,55247455,526217,67793644,93057697,31161687,18699206,95010552,99658235,29834512,47792865,79136082,55143724,22766820,79821897,39847321,27041967,86301513,99515901,66137019,247198,61373987,30764367,86543538,6525948,508198,41092102,74357852,70727211,17727650,749283,5832946,4064751,62232211,49597667,64848072,112651,57241521,73031054,16019925,4204661,78300864,38008118,48892201,78884452,74614639,68041839,96193415,39201414,2717150,45996863,51464002,43501211,81172706,26493119,39986008,50668599,72725103,67124282,89214616,40152546,98653983,25842078,59614746,88047921,65047700,17976208,6153406,67227442,66667729,98371444,22108665,6808825,4668450,68816413,83269727,669105,1022677,59371804,76315420,27325428,22942635,64087743,47361209,14626618,98462867,85116755,22200378,60955663,47090124,45794415,9623492,37435892,97057187,59197747,72373496,93933709,53802686,62452034,97561745,88444207,38022834,50567636,18833224,62552524,88698958,14947650,32590267,55470718,44627776,90013093,84406788,5111370,20836893,40356877,30998561,7919588,70036438,27665211,6505939,63967300,61960400,78909561,86798033,26102057,43933006,47738185,45237957,3880712,62051033,70420215,24733232,66885828,91831487,45306070,8099994,86022504,8696647,18411915,42967683,99333375,48774913,86001008,51590803,44983451,43322743,1197320,46540998,20140249,22113133,92398073,90457870,98130363,95395112,52613508,54517921,92554120,16595436,8129978,61271144,43376279,415901,15902805,97783876,31733363,20568363,84840549,62428472,26744917,54868730,95726235,99224392,65081429,23134715,55753905,59981773,38645117,10961421,18504197,30163921,70367851,73222868,67031644,73168565,20002147,17764950,13862149,9860195,31491938,44842615,62936963,40984766,88653118,62430984,35996293,51047803,30463802,65271999,4069912,17385531,3487592,7229550,66250369,10358899,53632373,95957797,29516592,80014588,76540481,37620363,6497830,30139692,68204242,2891150,40027975,57240218,36189527,37224844,68939068,2917920,77284619,5970607,61741594,66903004,34236719,74137926,54199160,57961282,42199455,36780454,99524975,29466406,60102965,33237508,30891921,77312810,24915585,98648327,22435353,92071990,56531125,87598888,22129328,16445503,7066775,37739481,48088883,49641577,20681921,81853704,55669657,8651647,99125126,79657802,19272365,15148031,70004753,45919976,7685448,82779622,89078848,27411561,36505482,29029316,77072625,37891451,70800879,64602895,30218878,77187825,61623915,91957544,54263880,15535065,54232247,90004325,29188588,61859581,75153252,42073124,96709982,83210802,14349098,83083131,26664538,89637706,45428665,8807940,3509435,72238278,17068582,13470059,20122224,36753250,30694952,21993752,59910624,99917599,42782652,82886381,17081350,29510992,2331773,89217461,84127901,33797252,49328608,58180653,84684495,33061250,75543508,61728685,40197395,68102437,92998591,85117092,69255765,26275734,9829782,56515456,59109400,89804152,78589145,15536795,29401781,31904591,36808486,91802888,82339363,14093520,19486173,77413012,39801351,96420429,4508700,19891772,49598724,39171895,60430369,76330843,57020481,69355476,67030811,17957593,91938191,80251430,51588015,54014062,99604946,19939935,77301523,36845587,57163802,72358170,6111563,6793819,78602717,59318837,22721500,97011160,53393358,6038457,82427263,30787683,40686254,48260151,44473167,94516935,18131876,10366309,9257405,46851987,90272749,11543098,72614359,16380211,3633375,8055981,48395186,32159704,84187166,16684829,1204161,53648154,58749,93359396,3233569,48675329,36933038,84166196,98948034,79738755,83789391,99297164,9886593,77787724,94090109,8729146,57393458,75135448,94595668,65074535,54058788,89699445,54663246,13173644,57248122,10453030,75820087,19101477,4099191,61263068,67451935,59177718,99965001,51715482,14363867,17146629,14045383,17016156,73786944,38365584,22997281,43152977,44784505,17365188,4095116,10309525,15075176,6521313,9259676,36139942,74441448,96735716,74743862,57803235,41092172,47213183,26507214,10264691,10649306,78766358,66322959,44348328,24591705,9603598,65715134,24826575,7955293,38341669,51507425,18001617,16099750,55960386,569864,18806856,55602660,8115266,48360198,29994197,82052050,78549759,90654836,16567550,43357947,80555751,14326617,67474219,91727510,20486294,4985896,81774825,14220886,87720882,37183543,6986898,68644627,44060493,44844121,26998766,36312813,36812683,77300457,77694700,66832478,28550822,68702632,9188443,21673260,65851721,92803766,59501487,44889423,32161669,62490109,71316369,96852321,92541302,4806458,75986488,17894977,88251446,3183975,83533741,57658654,29819940,16097038,44177011,89046466,7423788,76825057,28734791,73124510,30771409,91141395,99971982,89996536,5073754,82178706,9058407,76671482,45049308,18783702,69641513,39553046,7502255,69136837,92604458,72019362,1431742,92692283,26863229,7646095,74724075,93562257,99549373,49236559,43045786,38061439,33895336,47708850,21001913,45617087,58751351,64055960,57359924,72357096,33628349,33935899,48658605,24619760,36685023,85242963,17386996,21533347,47893286,29065964,79922758,17857111,99226875,49271185,99729357,4119747,62693428,68824981,45407418,66045587,30543215,8913721,33431960,23740167,69697787,89419466,85711894,94076128,84904436,53842979,73021291,97641116,80316608,71657078,43506672,34698463,26292919,8634541,45995325,49882705,53084256,90061527,48893685,66271566,75052463,36580610,44847298,65275241,62740044,34698428,40534591,9575954,92353856,89499542,30653863,69786756,34497327,55850790,38658347,72732205,36930650,71083565,95251277,91990218,16971929,16424199,67513640,68128438,4978543,14723410,5822202,22450468,44915235,62357986,42307449,83302115,67281495,93270084,75777973,35192533,75229462,15163258,23848565,33565483,35512853,50806615,1250437,42237907,23432750,36135,84349107,40781854,56153345,4515343,44481640,83155442,33123618,41481685,26397786,71965942,72278539,29635537,13819358,20765474,15064655,86242799,83651211,63506281,73392814,45667668,17037369,61859143,24168411,71920426,50702367,79806380,34493392,52204879,75128745,21397057,40781449,76703609,13468268,23569917,44479073,86460488,6871053,11923835,99861373,66319530,18663507,90158785,91240048,26139110,69848388,32699744,1936762,28928175,46870723,6221471,55770687,98739783,82532312,5482538,63059024,45381876,38256849,44550764,70541760,29959549,45848907,92033260,29932657,8791066,30569392,81855445,5640302,12303248,88904910,32274392,3088684,40677414,72793444,30811010,28851716,93566986,56424103,3294781,78561158,824872,84293052,59329511,54606384,19898053,9398733,67084351,80246713,45990383,82651278,3773993,92215320,2204165,86821229,61815223,95581843,51472275,61928316,40865610,17058722,75407347,87710366,68694897,51426311,21070110,97940276,7687278,4787945,97379790,1788101,16054533,65038678,58208470,51149804,168541,52261574,68000591,77272628,73617245,22405120,20867149,26063929,37659250,50007421,26734892,56955985,83747892,47887470,3235882,72495719,53888755,89811711,81805959,55615885,45075471,86118021,874791,44664587,70372191,12571310,52293995,79322415,1569515,91255408,9175338,24058273,26114953,63015256,74110882,20885148,4798568,61712234,87160386,71469330,83948335,84002370,9860968,94539824,93790285,78218845,63152504,44846932,41442762,2208785,48673079,176257,76434144,90090964,68110330,8733068,65017137,82897371,64157906,10597197,60106217,11581181,96726697,35456853,28796059,58224549,82979980,76960303,54427233,17539625,53547802,11757872,38009615,65880522,13348726,37192445,96318094,91281584,51315460,99021067,78785507,42947632,7182642,97281847,14731700,12416768,7104732,62115552,38494874,1239555,321665,30090481,69605283,63372756,39373729,19457589,19376156,92867155,27625735,52060076,51466049,60176618,5267545,30366150,41380093,61141874,33699435,15948937,41245325,32250045,45790169,11365791,70782102,36396314,54987042,33553959,99690194,37166608,56484900,82726008,25636669,54762643,60393039,18466635,27185644,85695762,81274566,73109997,6157724,31727379,61897582,72777973,79942022,32426752,56461322,77377183,71522968,66663942,85023028,15015906,20645197,34946859,26776922,8373289,1375023,16387575,82327024,56473732,72738685,34044787,21919959,4091162,47298834,79191827,83368048,62762857,7517032,37675718,66428795,16405341,11161731,74452589,6819644,4199704,59405277,2607799,28787861,71300104,92530431,42644903,8128637,65338021,94711842,33336148,96906410,21289531,68875490,38510840,36468541,77898274,37280276,90310261,32151165,24953498,23110625,94360702,94911072,32058615,68316156,88130087,83133790,92787493,93515664,37957788,34432810,24314885,27187213,9599614 +55143724,39801351,40677414,63967300,81898046,42199455,96726697,51047803,87160386,22879907,46851987,56424103,56473732,53648154,43933006,29994197,7423788,4099191,29819940,46540998,39847321,2717150,16097038,80316608,5822202,47090124,16054533,35996293,80555751,64087743,7502255,84293052,36580610,33553959,8099994,40984766,43501211,19891772,59910624,9175338,97641116,71083565,7300047,68204242,65081429,21533347,77413012,83155442,66663942,29834512,14349098,89804152,53842979,91727510,81172706,87720882,37659250,34236719,27411561,38658347,45237957,66428795,79136082,94360702,9860195,63506281,11365791,61960400,77898274,26507214,62740044,75820087,14326617,23110625,57803235,83368048,31161687,94090109,99965001,15064655,7182642,60581278,88047921,74614639,89046466,99224392,13468268,13862149,22108665,50188404,30139692,65454636,9860968,86821229,6157724,16971929,99515901,21070110,18466635,85116755,14947650,3235882,69355476,50007421,95957797,44550764,83210802,3633375,72357096,72495719,97783876,99524975,62452034,39986008,23194618,47887470,71657078,1431742,77284619,17539625,44844121,70727211,9603598,31491938,30163921,22129328,23848565,45990383,98462867,12348118,83789391,176257,61859143,57241521,66045587,59177718,3773993,77272628,18806856,62936963,33249630,44060493,82339363,70372191,67793644,60430369,75777973,5111370,89214616,91831487,79657802,96193415,44842615,61741594,65017137,37192445,15535065,32274392,3088684,526217,58224549,92398073,86301513,6986898,86460488,29959549,73392814,26998766,30787683,58749,48673079,30569392,54987042,30218878,76434144,93562257,6793819,43322743,75153252,52293995,62490109,24058273,37183543,94076128,33237508,91664334,7646095,96852321,20642888,41481685,50806615,65338021,20765474,29466406,90158785,62051033,20836893,40534591,68816413,85571389,73168565,55753905,32161669,92803766,24733232,17016156,17081350,99658235,36505482,89078848,19376156,91802888,36845587,67474219,72019362,38494874,19486173,12024238,49641577,31126490,90457870,1788101,30463802,53666583,59614746,82979980,25636669,57359924,74137926,508198,50702367,69136837,42782652,52204879,95581843,36780454,21673260,44983451,70036438,71316369,61623915,69605283,20486294,6808825,70596786,37739481,9623492,32159704,20002147,8651647,62232211,874791,40152546,94711842,21001913,88904910,35192533,77377183,16405341,23569917,6153406,55247455,89699445,73235980,3880712,2208785,98371444,5482538,68041839,28796059,168541,4095116,247198,19101477,53084256,3233569,83533741,39201414,27665211,62552524,76671482,37675718,74743862,78589145,63628376,88653118,34295794,22997281,1022677,73786944,57240218,43376279,33304202,8115266,80251430,45919976,76540481,5267545,91957544,67451935,93566986,17068582,4064751,37620363,3233084,20867149,51588015,9058407,47361209,17764950,30998561,4668450,49271185,35456853,86022504,74110882,91240048,94516935,26664538,89419466,36312813,33565483,78218845,18001617,15148031,15163258,45306070,68875490,57393458,99549373,4119747,51590803,93933709,26776922,2917920,61815223,63372756,69786756,68644627,8791066,13173644,98948034,40356877,17957593,34497327,36930650,42644903,67030811,76315420,36753250,82651278,669105,2891150,36139942,16387575,1239555,26275734,91141395,90004325,71333116,64848072,36396314,97561745,47792865,12571310,66322959,24591705,45407418,57248122,10366309,84904436,6521313,77787724,86242799,75407347,62115552,85242963,49328608,97379790,98130363,81853704,62496012,14220886,53632373,32590267,75052463,16380211,18699206,18663507,75543508,84684495,44627776,72274002,99729357,52060076,26102057,22721500,6871053,26493119,44481640,98739783,4434662,64055960,18833224,43152977,82427263,36808486,34698428,26392416,71300104,44177011,72278539,37957788,7104732,72777973,112651,68000591,6525948,4806458,10358899,92604458,82052050,57961282,49236559,45794415,58751351,46870723,79806380,72725103,7919588,24915585,1569515,93790285,78785507,94539824,8807940,77620120,33935899,18783702,61712234,99861373,53802686,90272749,5073754,70004753,61897582,84840549,76170907,82726008,89637706,2607799,66271566,28734791,97940276,14731700,79922758,72732205,22435353,99604946,92998591,30653863,81274566,35092039,55770687,82897371,29029316,55615885,48658605,66667729,97011160,4199704,45848907,78561158,10309525,19272365,70541760,79942022,13231279,63152504,75229462,4204661,39171895,6111563,94595668,75135448,44889423,9188443,33201905,45996863,97057187,27325428,53393358,5970607,65851721,78602717,86798033,569864,66319530,49597667,36685023,8696647,92541302,42307449,96735716,77694700,47738185,44245960,92787493,63059024,55470718,64602895,2331773,81805959,50668599,43357947,65275241,98648327,13819358,92554120,74724075,11757872,10961421,48675329,52613508,13470059,22200378,19457589,7685448,28851716,45617087,37166608,31904591,55669657,51715482,77187825,84406788,76330843,415901,22942635,12416768,14093520,99125126,55189057,56515456,42237907,62357986,48395186,74357852,82886381,76960303,44664587,73031054,83083131,95290172,47893286,11543098,7687278,8055981,8128637,68102437,40197395,15075176,38341669,48260151,59329511,17037369,21919959,66611759,22450468,66137019,2204165,60106217,30366150,30395570,99297164,38365584,51472275,54058788,20885148,81677380,44846932,85117092,76825057,32151165,96709982,88698958,77072625,99917599,33336148,61373987,22766820,9398733,47298834,18411915,9886593,11581181,93053405,1375023,63015256,21289531,28928175,78909561,59371804,70420215,5640302,68702632,80014588,30694952,82532312,60102965,34493392,19939935,89996536,72358170,92215320,67281495,14723410,22113133,48893685,99333375,26397786,36812683,18131876,24826575,51426311,68694897,56531125,16445503,54762643,52261574,26114953,38061439,32250045,38645117,91281584,30891921,92033260,28550822,26734892,20140249,59501487,4069912,3509435,24314885,4091162,68128438,90013093,73021291,3487592,321665,49598724,41380093,69848388,30543215,56153345,26292919,79821897,27625735,90090964,82178706,34432810,16019925,82779622,37435892,17894977,37224844,17386996,3294781,23134715,73124510,78884452,59318837,1197320,98653983,54663246,51466049,18504197,66116458,61859581,37280276,26063929,67084351,65074535,36189527,29188588,7229550,32699744,33431960,42073124,79191827,35512853,6497830,59981773,61263068,60955663,40781854,38256849,16099750,67227442,7955293,33797252,23432750,69641513,8373289,36135,71469330,55850790,10453030,83302115,4515343,10649306,65715134,65880522,54014062,6819644,55960386,83150534,75986488,26863229,51464002,56461322,80246713,44479073,17058722,24619760,15948937,3183975,6221471,59405277,11161731,84002370,44847298,95010552,98943869,20568363,34044787,1204161,59109400,93057697,33628349,57163802,40781449,42692881,64098930,45428665,70782102,95251277,50567636,73617245,62693428,78549759,44348328,92692978,82327024,45667668,62762857,65271999,54199160,95395112,9257405,4787945,17727650,8733068,93359396,31727379,21993752,72738685,86001008,88251446,12303248,99971982,87710366,62803858,85711894,20122224,66250369,61271144,66885828,9599614,16567550,69255765,9575954,99690194,81855445,69697787,42947632,79880247,95726235,38022834,90654836,27041967,68939068,84349107,33699435,45049308,83651211,48774913,7517032,93270084,71920426,33061250,6038457,48892201,43045786,4985896,93515664,14363867,29510992,14045383,23740167,90310261,44915235,91990218,45075471,74441448,10264691,78300864,81774825,41092102,11923835,38510840,68316156,70800879,37501808,48088883,54517921,16595436,85023028,51507425,83133790,15536795,37891451,17857111,44784505,47213183,29065964,67513640,40027975,86118021,65047700,30771409,92530431,39553046,61982238,83747892,62428472,76703609,62430984,72614359,53888755,13348726,99226875,73109997,34698463,749283,96906410,53547802,77312810,40686254,28787861,66903004,16424199,96318094,4508700,88444207,6505939,30811010,84166196,4978543,77300457,61728685,84187166,42967683,65038678,9259676,39373729,87598888,37560164,67031644,8634541,47708850,33123618,30090481,55602660,29932657,43506672,54232247,90061527,72793444,61928316,64157906,15015906,27185644,20681921,69579137,7011964,41442762,73222868,29401781,89811711,8913721,99021067,61141874,83948335,41092172,40865610,45995325,45381876,51149804,14626618,56955985,97281847,31733363,24953498,75128745,32058615,67124282,45790169,36468541,8729146,4798568,85695762,58180653,77301523,91255408,74452589,66832478,26139110,34946859,25842078,71522968,92692283,17146629,38008118,44473167,72373496,79322415,57020481,51315460,96420429,16684829,92867155,36933038,15902805,5832946,89499542,24168411,7066775,8129978,88130087,54606384,29635537,92071990,56484900,30764367,60176618,10597197,48360198,54868730,58208470,71965942,22405120,27187213,94911072,32426752,41245325,92353856,54427233,49882705,1250437,91938191,60393039,29516592,68824981,84127901,38009615,79738755,72238278,17385531,26744917,59197747,17365188,33895336,824872,19898053,34667286,86543538,54263880,83269727,1936762,78766358,9829782,70367851,17976208,89217461,21397057,12664567,57658654,20645197,68110330 +15535065,1022677,17894977,64087743,59614746,92554120,70004753,68128438,77300457,10309525,37659250,65715134,77284619,34295794,51715482,71657078,5111370,8696647,26493119,37183543,79136082,55770687,71333116,38494874,89214616,73168565,48893685,78218845,9623492,73031054,36505482,81853704,83789391,68644627,88653118,99658235,29834512,54606384,19898053,96726697,80246713,36780454,99917599,64098930,30653863,16380211,30787683,93790285,34698428,86242799,86022504,4434662,33628349,30139692,20486294,68875490,38658347,53666583,88047921,75229462,52293995,40865610,31904591,66832478,24058273,48658605,4091162,67793644,55143724,39801351,68824981,61960400,47887470,7300047,43045786,16684829,22879907,73786944,56424103,1250437,22405120,87160386,48892201,83368048,37739481,4064751,7687278,20867149,61623915,6986898,62740044,16595436,36396314,82886381,81677380,19891772,7229550,62762857,43322743,7646095,39553046,4099191,19272365,26114953,33304202,38009615,55470718,99125126,46851987,63015256,91938191,35092039,35192533,60430369,98653983,27665211,63628376,90013093,15075176,18131876,43152977,72373496,66667729,71469330,3633375,79738755,44889423,77694700,42237907,2208785,89419466,39373729,90457870,27625735,33797252,7919588,49236559,92692978,61373987,39986008,15148031,36930650,97641116,40677414,508198,53888755,30998561,96193415,52261574,42782652,59197747,44842615,24915585,54199160,30366150,66428795,13470059,99549373,66611759,65275241,82726008,91240048,21070110,70372191,43376279,63967300,50806615,59501487,4668450,75986488,2204165,99297164,47708850,97281847,27187213,76825057,6808825,1431742,168541,51426311,36812683,52204879,33249630,45428665,14626618,93057697,38008118,81855445,76703609,77620120,48673079,31733363,85116755,37620363,95010552,23432750,77787724,44245960,20885148,34698463,9259676,72278539,76434144,9886593,1239555,5482538,82339363,33237508,8634541,41380093,30218878,10366309,112651,29188588,49271185,68316156,43501211,34497327,8807940,21533347,874791,49328608,70596786,69848388,78589145,14326617,78549759,45075471,44983451,5640302,8128637,44060493,93053405,70420215,24826575,29819940,29959549,92215320,56515456,29994197,79657802,97940276,54014062,66271566,99224392,18663507,4119747,60102965,49598724,33123618,1375023,9860195,88130087,23110625,95726235,72495719,86001008,48360198,79922758,1204161,86821229,86543538,2607799,54263880,68000591,69786756,44847298,66319530,18783702,44177011,67031644,72777973,57240218,77413012,70036438,18806856,78766358,54427233,75543508,40027975,62430984,25636669,54868730,67451935,20836893,3487592,20122224,42307449,22450468,83150534,34493392,26776922,93933709,30395570,9829782,6038457,6505939,74614639,94516935,19101477,7517032,28928175,59329511,63152504,40781449,81805959,47298834,32159704,50702367,32161669,45990383,74110882,29466406,16971929,84002370,16054533,7066775,99861373,7955293,32590267,72614359,53842979,77187825,40152546,74137926,55189057,7104732,65454636,89699445,5073754,55247455,62936963,15064655,22200378,22997281,20765474,10358899,58208470,30163921,12348118,92604458,13231279,13819358,98130363,20002147,68041839,14093520,44550764,88904910,92353856,61897582,61859581,45995325,67281495,30090481,14723410,6221471,49641577,14731700,61741594,23740167,3088684,57803235,99965001,61859143,42967683,47090124,4204661,40534591,87720882,8651647,36135,62452034,26998766,83155442,72358170,73392814,91664334,76170907,569864,73235980,83210802,84127901,91802888,85023028,11757872,77272628,39171895,55615885,17016156,78884452,3235882,15163258,57163802,43933006,6521313,40356877,51047803,14947650,46870723,79191827,60106217,89637706,65851721,64602895,23569917,26734892,98462867,97783876,44481640,44664587,17068582,176257,57658654,5822202,50188404,19939935,62496012,29029316,526217,27325428,16445503,71522968,99690194,8913721,3294781,32274392,38256849,22129328,55753905,749283,75135448,52613508,2717150,67474219,69641513,61982238,80014588,6157724,75777973,60176618,26507214,70727211,26397786,17857111,65880522,9398733,16567550,64157906,17764950,20140249,6871053,66885828,4806458,59177718,85571389,247198,19457589,37280276,13862149,63372756,4515343,98948034,50668599,45381876,31161687,60955663,28796059,78300864,59910624,66903004,94539824,75153252,45306070,1197320,74724075,65038678,93566986,94090109,97379790,47738185,57359924,38061439,44846932,76960303,38341669,76671482,95957797,28550822,44627776,1788101,53802686,34236719,321665,33553959,62490109,32699744,26863229,68939068,65017137,99021067,62115552,66045587,34432810,70800879,86301513,45794415,79942022,45996863,26139110,43357947,42947632,81274566,53632373,32426752,42644903,61263068,18833224,78602717,89811711,34667286,16099750,20645197,98371444,73124510,6525948,12416768,30891921,92398073,27185644,2917920,7502255,72738685,13468268,67124282,92692283,81898046,90061527,7182642,90272749,47213183,89499542,95251277,69355476,96709982,5267545,61728685,15536795,28851716,5832946,80316608,82178706,30463802,37957788,6153406,68694897,41481685,79806380,23848565,66663942,77377183,61928316,82427263,55669657,10453030,18699206,12571310,26292919,21993752,31727379,16424199,24591705,96735716,56473732,64848072,88444207,30764367,29932657,62357986,63506281,76330843,33565483,97011160,17385531,9575954,3880712,59981773,93515664,45919976,94911072,57020481,48088883,17386996,2891150,80555751,99729357,37501808,51588015,14220886,55602660,94711842,18504197,51507425,17037369,9603598,54987042,10597197,31126490,61141874,51464002,36580610,415901,60581278,15015906,97057187,91831487,4985896,62803858,85711894,92998591,2331773,58751351,67227442,16097038,71083565,41092102,21001913,34044787,79821897,67030811,11923835,74357852,89217461,82052050,669105,68816413,46540998,24733232,18001617,56484900,72238278,73222868,19376156,43506672,62051033,98648327,3233569,58224549,22435353,23194618,17058722,7011964,70782102,44473167,33336148,83533741,75128745,16405341,8129978,42692881,11365791,32151165,69579137,29510992,77312810,82779622,8115266,99524975,85242963,22113133,33699435,82651278,47792865,68204242,33935899,84904436,61712234,40781854,75052463,98943869,84187166,36189527,16019925,91141395,10961421,86798033,17727650,44915235,90654836,17146629,62428472,45848907,28787861,92071990,56461322,54517921,53648154,71316369,92787493,55850790,69255765,17539625,39847321,51149804,44844121,48675329,22108665,72274002,13348726,16387575,68110330,68702632,72357096,30771409,34946859,92541302,66250369,41442762,26102057,49882705,23134715,82532312,10649306,51472275,6111563,30569392,88251446,57248122,50567636,8055981,15902805,77072625,85117092,65074535,21673260,53084256,68102437,21289531,3773993,97561745,8373289,39201414,36139942,88698958,92033260,90310261,9860968,93359396,45790169,95581843,8099994,29401781,45237957,26063929,6497830,3233084,33895336,47893286,92803766,66137019,45407418,65338021,87710366,63059024,9175338,19486173,35456853,20681921,78561158,66116458,7423788,61271144,47361209,17081350,59318837,62232211,26744917,33431960,83651211,38022834,37166608,81172706,26392416,77898274,1569515,69136837,44348328,61815223,99971982,84349107,90090964,56153345,11543098,95395112,4199704,89046466,48260151,17976208,91990218,57393458,99333375,96420429,22766820,25842078,53393358,83083131,54058788,54762643,9257405,86460488,7685448,82897371,57241521,11161731,40984766,13173644,89078848,54232247,91255408,31491938,96906410,20642888,42073124,56531125,65271999,37675718,3183975,73617245,9599614,84684495,27041967,37224844,78909561,36685023,36808486,17957593,1936762,54663246,45667668,71300104,49597667,83948335,41092172,37192445,93562257,14349098,99604946,71920426,44784505,27411561,70367851,50007421,21919959,36468541,4787945,40197395,48774913,4798568,84840549,14363867,82979980,72725103,44479073,58749,72793444,51590803,4095116,84406788,12303248,4069912,53547802,86118021,36753250,89804152,14045383,59371804,89996536,32058615,6793819,94360702,71965942,24168411,91281584,29635537,38510840,42199455,37435892,37891451,35996293,17365188,67513640,70541760,3509435,99226875,36845587,9188443,58180653,37560164,8791066,93270084,65081429,55960386,76315420,83269727,22721500,69697787,10264691,73021291,51315460,51466049,65047700,80251430,94076128,824872,83302115,29516592,62693428,72732205,92530431,38365584,83747892,38645117,45617087,32250045,83133790,56955985,36312813,76540481,81774825,75407347,33201905,24619760,79880247,4508700,30543215,74441448,21397057,66322959,5970607,77301523,82327024,40686254,79322415,9058407,78785507,85695762,91727510,95290172,74743862,36933038,64055960,99515901,8733068,69605283,67084351,12024238,33061250,75820087,92867155,4978543,18411915,8729146,22942635,12664567,59405277,26275734,29065964,94595668,35512853,59109400,28734791,15948937,62552524,57961282,98739783,20568363,24953498,30694952,24314885,73109997,45049308,41245325,96318094,87598888,18466635,91957544,6819644,72019362,11581181,52060076,84166196,26664538,90158785,30811010,90004325,74452589,60393039,48395186,84293052,96852321 +55470718,76540481,51047803,58749,30569392,26776922,39801351,17068582,50007421,18806856,77413012,36580610,76330843,49328608,43152977,8696647,34497327,68204242,66137019,6986898,63372756,99515901,88653118,68816413,72614359,45428665,85242963,26998766,44481640,15902805,78909561,93933709,44177011,14349098,21993752,53666583,8651647,20140249,65715134,96193415,26734892,89996536,63059024,35092039,38009615,58751351,74724075,69786756,63506281,7687278,86001008,61728685,9860968,95290172,9188443,72274002,31491938,53547802,77787724,27665211,96735716,4798568,57359924,34044787,82979980,62452034,29959549,36933038,3487592,30366150,93359396,97783876,92033260,16054533,62430984,14947650,3294781,95957797,43501211,49597667,69848388,77272628,20002147,80555751,29834512,6808825,9599614,89078848,77312810,99224392,96906410,99524975,57393458,61859143,65271999,50188404,2917920,9623492,35456853,75543508,75135448,38494874,50702367,36685023,83789391,4119747,89214616,415901,81172706,68000591,70367851,97281847,39847321,40677414,61859581,99861373,74743862,20836893,70596786,94516935,72357096,33336148,47361209,47090124,44348328,44889423,24058273,9603598,67281495,29466406,23194618,57163802,1431742,91802888,53648154,26063929,29932657,36780454,569864,50668599,59910624,77284619,72777973,86301513,46540998,72793444,874791,19486173,10366309,37957788,51426311,72019362,29065964,60106217,95395112,59177718,23569917,83651211,87720882,59197747,56424103,17957593,94539824,4515343,95010552,19939935,61263068,99729357,63967300,12571310,91957544,23848565,62490109,62936963,81853704,57961282,24591705,9886593,46851987,76960303,42782652,22721500,70036438,71657078,79136082,55960386,82178706,92787493,51466049,22129328,40197395,61897582,70420215,93566986,48893685,56484900,54014062,84127901,36505482,38008118,84684495,96318094,62496012,38022834,79942022,98943869,40027975,48675329,39986008,15064655,8055981,36312813,61623915,17539625,11757872,83948335,60102965,26392416,10358899,99658235,22997281,1569515,26275734,42307449,96726697,97641116,90310261,92803766,86821229,6038457,69641513,66428795,75986488,73392814,61982238,7502255,16380211,97561745,18411915,45407418,18466635,4099191,55247455,45075471,82427263,56531125,78589145,92604458,4668450,12664567,16567550,66271566,7011964,6505939,35996293,91938191,36930650,51472275,65017137,41092172,68041839,32161669,98371444,70372191,65074535,70541760,64087743,64055960,55602660,55753905,21533347,46870723,90004325,44983451,40152546,66667729,50806615,82052050,40534591,62740044,62051033,20765474,30463802,44473167,57241521,3880712,83155442,72725103,4508700,13173644,37183543,69579137,4095116,59371804,11543098,64848072,33249630,37891451,43376279,8807940,33061250,44842615,21673260,80246713,27411561,43322743,13231279,14220886,62762857,34432810,3509435,5111370,95581843,94090109,3773993,61960400,42692881,89217461,83368048,52204879,15015906,98948034,81677380,6793819,99549373,80316608,80251430,16099750,70782102,38658347,65338021,16097038,9575954,34493392,15535065,22942635,33431960,19891772,45990383,71469330,4064751,39171895,75777973,65454636,55669657,66611759,74137926,67030811,89046466,70004753,61373987,53842979,28851716,3088684,55770687,18699206,84187166,49271185,33935899,68644627,72495719,23432750,33628349,526217,79806380,20486294,77187825,16595436,1250437,14731700,37435892,17764950,62803858,5073754,91664334,37501808,88904910,32426752,48892201,42947632,5970607,33797252,1204161,33201905,27187213,34698428,48673079,49641577,73617245,92530431,3235882,14723410,9259676,92692978,43933006,5267545,73124510,38061439,33123618,88047921,13470059,21070110,32590267,669105,61141874,66319530,91240048,69605283,24826575,41481685,65851721,44479073,8791066,88698958,72732205,48774913,99125126,112651,25636669,36845587,40781449,42237907,10453030,4787945,51464002,47887470,99226875,7182642,16019925,33553959,29819940,26102057,85116755,57248122,37620363,53393358,78766358,75153252,35512853,90457870,30139692,84293052,51588015,33565483,71333116,63628376,43506672,26863229,16405341,54762643,22879907,30218878,64098930,18001617,66663942,17385531,97011160,54427233,45790169,68939068,82532312,45237957,18504197,27185644,72278539,48260151,32151165,30787683,59109400,87160386,79821897,48360198,51590803,1197320,7685448,11161731,91141395,44550764,53888755,41245325,98739783,79922758,93562257,19272365,6525948,13468268,58224549,68110330,38341669,508198,68824981,92998591,9257405,6157724,36468541,69136837,33699435,5640302,48395186,99297164,77072625,42199455,57803235,57020481,90013093,62693428,17016156,36139942,98462867,76434144,45667668,35192533,65880522,24314885,11923835,22766820,7300047,78561158,67227442,69255765,45381876,26493119,19376156,94595668,23134715,71300104,96420429,17037369,97379790,91990218,18783702,82651278,14045383,95251277,57240218,52293995,45996863,51315460,42967683,78785507,81805959,7955293,4434662,92554120,67793644,21919959,84166196,39201414,83533741,8733068,93270084,95726235,47298834,48658605,81274566,34295794,99021067,90272749,78218845,30771409,94360702,98648327,45306070,247198,58208470,40686254,44060493,9175338,28796059,49236559,97940276,20642888,1022677,21397057,47213183,76671482,30090481,14093520,91255408,8634541,84840549,2717150,92215320,22200378,26139110,31904591,99965001,1788101,51507425,10649306,321665,37280276,77620120,29029316,22405120,20885148,80014588,2891150,68102437,76825057,86798033,83269727,33304202,63152504,3233569,32159704,60955663,54517921,71083565,19101477,47893286,85711894,41442762,37659250,54606384,40356877,89804152,168541,96709982,6111563,90654836,8913721,55143724,6153406,99690194,15948937,77300457,67084351,8115266,82897371,62232211,45848907,29994197,59981773,30395570,12303248,31727379,97057187,8128637,87598888,94911072,66832478,22113133,9398733,6497830,98130363,4091162,24168411,2208785,73222868,7517032,59318837,45794415,30998561,36135,66250369,78549759,29188588,44627776,36753250,31126490,8129978,34236719,72738685,82726008,15148031,71920426,65038678,36808486,12416768,16971929,93057697,27325428,32250045,77694700,51715482,10961421,55189057,22450468,74614639,83150534,96852321,74441448,20568363,75052463,26292919,74110882,40865610,76170907,3233084,54232247,83302115,19457589,66045587,91727510,39553046,53802686,12348118,54199160,41380093,61271144,68694897,85571389,52261574,66322959,72373496,24619760,73109997,89419466,5822202,62552524,1375023,93053405,32699744,44784505,73168565,16387575,37166608,10309525,37675718,52613508,56461322,99333375,74357852,75229462,89811711,72238278,9829782,33237508,47792865,70800879,83210802,78300864,26397786,12024238,82339363,91831487,81855445,99917599,20122224,26507214,53084256,68128438,10597197,17857111,87710366,13348726,45995325,4985896,50567636,45617087,56515456,11581181,75407347,60581278,67451935,23740167,19898053,94076128,69355476,60430369,78884452,89637706,1936762,28550822,36189527,28787861,44664587,54987042,31161687,66116458,83747892,34946859,83133790,54663246,82886381,34698463,86460488,81898046,88130087,77377183,17386996,30764367,14626618,11365791,23110625,16445503,92541302,2204165,52060076,29635537,73235980,42073124,31733363,61741594,29516592,7229550,2607799,40984766,68316156,7423788,79738755,5832946,6221471,749283,92398073,79191827,58180653,37739481,84904436,79322415,7919588,43045786,92867155,64157906,76315420,86543538,61815223,2331773,82327024,64602895,68702632,38510840,77301523,28928175,17976208,26664538,8099994,91281584,65275241,68875490,24953498,9058407,1239555,73786944,67124282,71316369,51149804,33895336,99971982,44847298,7104732,74452589,22108665,30543215,44846932,85023028,30694952,7646095,83083131,89699445,89499542,94711842,84002370,26114953,79657802,29510992,5482538,14326617,67513640,6871053,44844121,90061527,176257,18131876,92692283,92353856,57658654,38365584,18833224,42644903,17081350,56473732,45919976,17727650,90158785,82779622,76703609,15536795,38256849,16424199,55850790,39373729,59329511,62115552,59501487,48088883,16684829,3633375,20867149,92071990,44245960,47738185,4806458,30891921,90090964,55615885,61712234,3183975,59405277,17365188,65081429,59614746,47708850,17894977,27041967,54263880,40781854,32058615,18663507,29401781,60176618,86118021,26744917,85117092,30653863,10264691,24733232,67474219,37560164,99604946,49882705,17058722,53632373,4978543,84349107,71522968,38645117,77898274,25842078,21289531,44915235,56153345,63015256,17146629,9860195,13862149,4069912,75128745,41092102,78602717,36812683,60393039,21001913,20645197,36396314,71965942,86022504,15163258,67031644,66903004,15075176,30163921,86242799,20681921,27625735,6819644,6521313,4204661,75820087,8729146,14363867,93790285,69697787,93515664,81774825,85695762,79880247,7066775,98653983,37224844,54868730,62428472,43357947,62357986,13819358,73031054,30811010,28734791,34667286,88444207,61928316,37192445,8373289,4199704,56955985,65047700,84406788,88251446,70727211,73021291,22435353,72358170,32274392,49598724,54058788,24915585,824872,45049308,66885828 +14093520,44842615,29819940,61623915,55247455,12416768,31904591,21993752,77312810,81677380,20486294,52261574,30787683,18833224,90457870,98130363,66885828,33699435,17727650,66250369,65047700,85023028,29516592,48892201,4515343,99549373,17764950,84127901,91957544,18411915,26114953,73124510,94090109,22108665,67031644,82886381,27665211,44550764,51047803,57240218,44983451,59318837,1197320,31733363,7955293,60430369,96420429,9188443,62936963,67793644,98943869,44889423,56531125,91802888,2331773,1250437,19939935,38494874,28851716,14626618,69355476,85695762,34698428,3509435,80316608,5970607,44784505,68816413,6793819,8913721,48360198,17058722,52060076,44844121,55753905,3183975,65038678,22721500,32161669,53547802,96318094,68644627,12348118,18663507,73168565,10309525,72738685,415901,73222868,69136837,9575954,33061250,68204242,59501487,97783876,669105,88444207,22450468,17976208,55770687,99524975,35092039,30366150,43357947,45919976,13348726,91664334,56515456,78766358,20885148,20002147,75543508,20867149,58751351,53632373,53393358,26998766,53666583,43045786,7104732,72373496,14220886,49597667,5111370,92803766,39986008,81805959,66903004,96193415,6505939,50567636,16684829,97641116,22766820,9829782,15015906,29466406,26863229,18504197,57393458,39553046,7011964,44473167,4806458,74357852,33628349,99604946,33895336,77072625,15535065,70036438,28734791,81898046,27041967,89419466,99658235,34236719,72019362,81274566,67030811,32250045,86001008,19101477,99861373,15902805,75153252,97057187,36580610,35456853,64087743,37183543,8791066,78602717,86798033,13862149,65275241,82178706,74137926,17385531,81853704,9623492,53084256,73617245,76434144,23569917,22113133,85242963,6153406,29065964,66137019,76330843,79322415,1431742,4798568,8634541,62430984,97281847,72614359,33304202,55470718,54058788,56473732,28928175,84349107,8129978,74441448,10366309,16097038,7685448,37675718,45990383,4787945,10649306,2717150,1022677,59329511,43152977,20642888,78589145,65271999,4064751,45996863,71965942,83150534,63506281,78561158,71333116,168541,83302115,83747892,44245960,6808825,39171895,55189057,61373987,77284619,74110882,38341669,10961421,98371444,68939068,95395112,72357096,30218878,51472275,59197747,50188404,64098930,25636669,29510992,19376156,79821897,4668450,20140249,48893685,29959549,49641577,27411561,60955663,60102965,42692881,36812683,78909561,93562257,50806615,3633375,4508700,80251430,21070110,81855445,54199160,7300047,7919588,90272749,65081429,44177011,24733232,94711842,70800879,92604458,80246713,91727510,13470059,76170907,15075176,37957788,64157906,89214616,80555751,32590267,61263068,40865610,82427263,93270084,4099191,22942635,93566986,72732205,85116755,63628376,3294781,83155442,7229550,95957797,17146629,18783702,37620363,45407418,91990218,39201414,65715134,76540481,51466049,70420215,44348328,1788101,68041839,4434662,59371804,35996293,38365584,72278539,72358170,13231279,4069912,51315460,88653118,98739783,26392416,33935899,99021067,40356877,89637706,12024238,71522968,79880247,34493392,67451935,82339363,44915235,24619760,37435892,3880712,70596786,5073754,28796059,91255408,77272628,92787493,86022504,35192533,77787724,26102057,42967683,72495719,40027975,47090124,29635537,17037369,26292919,11923835,93359396,96726697,33797252,8115266,51464002,93053405,21289531,749283,69605283,874791,67474219,92530431,84684495,91831487,48673079,15536795,48260151,112651,6221471,51590803,57359924,98948034,12664567,1936762,33431960,27625735,40781854,84166196,54762643,20568363,66667729,54014062,14947650,79191827,13468268,84904436,84840549,61982238,27325428,5832946,37224844,12571310,95726235,72238278,30764367,73235980,40152546,82979980,40534591,44481640,68702632,53648154,247198,31126490,96735716,97561745,17957593,98653983,18131876,70727211,28550822,42073124,72777973,99690194,16971929,43322743,98648327,59405277,82052050,5482538,33237508,26139110,90013093,36468541,60581278,569864,84406788,21673260,77377183,69848388,36780454,23194618,57658654,89811711,77300457,91281584,9599614,2891150,37891451,78785507,89078848,92998591,38008118,66322959,99917599,31491938,508198,34667286,99224392,17857111,99729357,29029316,57241521,96906410,62803858,24058273,34946859,63372756,13173644,90090964,11161731,38510840,31161687,92554120,33201905,4119747,84293052,88904910,63059024,33249630,75986488,68875490,68824981,52293995,82726008,90004325,14731700,23848565,95010552,6871053,74743862,17068582,61897582,77413012,9257405,7646095,23432750,66611759,36753250,23740167,4095116,66116458,1569515,45794415,19486173,8729146,89699445,47361209,83368048,71300104,78884452,79136082,62490109,32426752,6986898,5640302,8733068,50668599,79806380,24591705,38022834,9259676,76703609,73031054,22129328,30694952,88251446,7502255,57163802,66271566,59614746,38658347,36189527,17365188,20645197,45790169,72793444,60176618,18001617,53802686,49598724,71657078,37560164,40686254,92033260,30139692,94076128,79942022,37501808,46540998,8696647,83210802,30543215,41245325,9603598,36808486,29834512,30090481,54868730,57803235,61859581,49328608,95581843,99125126,57248122,26493119,63967300,66832478,75407347,16445503,65880522,62452034,51149804,526217,88698958,87598888,48395186,1204161,54606384,46851987,77898274,86821229,32159704,9886593,31727379,72725103,51507425,34497327,92071990,71920426,91141395,73392814,56484900,79657802,17894977,82532312,18806856,77620120,61741594,26507214,86242799,22405120,24826575,321665,83789391,45049308,67124282,46870723,88047921,52613508,62762857,38645117,36505482,70372191,34698463,76960303,63152504,26734892,95290172,19272365,49882705,30163921,16019925,29994197,80014588,16424199,32274392,65074535,44627776,65017137,14326617,42947632,44479073,78300864,38009615,16380211,10597197,36312813,77694700,7423788,45306070,43501211,56424103,96852321,30395570,16387575,54517921,66319530,75777973,68316156,99965001,34295794,26063929,40984766,21397057,47213183,4978543,93515664,33123618,9860195,21919959,86301513,70541760,33336148,19457589,59109400,69579137,39801351,17016156,65851721,30891921,99515901,17386996,30653863,11581181,37166608,62552524,17539625,86543538,24168411,54427233,2917920,824872,77187825,37739481,62115552,96709982,93790285,11543098,2204165,24953498,99297164,34044787,30463802,55602660,62496012,92353856,6497830,40197395,14723410,45237957,42199455,54987042,29932657,22200378,64055960,36933038,66663942,9058407,60106217,85117092,92215320,8373289,41481685,18699206,26744917,87710366,56153345,41092102,48774913,94595668,81172706,94911072,26664538,57961282,5267545,58749,98462867,16054533,55143724,67227442,62740044,29188588,13819358,92398073,18466635,89804152,32699744,37659250,42237907,36396314,99333375,59981773,93933709,87720882,72274002,55669657,30811010,64848072,90310261,8807940,35512853,14349098,89996536,4199704,42644903,19898053,48088883,79738755,75135448,10358899,82327024,8099994,4985896,15064655,58224549,61859143,40677414,20836893,61960400,7687278,99226875,25842078,60393039,14363867,42307449,90061527,51426311,49236559,62428472,44847298,3487592,62232211,74614639,9175338,42782652,20681921,94516935,10264691,89217461,74724075,75229462,83269727,63015256,48658605,54263880,36930650,50702367,41380093,26397786,89499542,43376279,52204879,83533741,41442762,15148031,69641513,6038457,3773993,68110330,37280276,39847321,45428665,26275734,44846932,3233084,1239555,38061439,89046466,4091162,57020481,43506672,8651647,51588015,34432810,47298834,69786756,92867155,47887470,16405341,36685023,37192445,86118021,40781449,62357986,91938191,75052463,6521313,77301523,6819644,65454636,51715482,8128637,22997281,97379790,90158785,45617087,84002370,75128745,85571389,45848907,48675329,44060493,66045587,82897371,61815223,47708850,62693428,67281495,20765474,76671482,36845587,2208785,69697787,59177718,97011160,69255765,9398733,61141874,78218845,94539824,23134715,71469330,8055981,68102437,50007421,81774825,82779622,68000591,78549759,92692978,47893286,20122224,92692283,5822202,92541302,68128438,29401781,15948937,3088684,4204661,79922758,44664587,47738185,30569392,27185644,99971982,3233569,66428795,14045383,73786944,76315420,21533347,87160386,36135,56955985,58180653,19891772,7182642,67513640,16595436,54232247,45075471,56461322,65338021,11365791,53888755,38256849,93057697,30998561,61271144,26776922,6525948,70004753,84187166,55615885,7517032,74452589,55960386,73109997,59910624,45667668,71316369,76825057,53842979,10453030,58208470,73021291,7066775,23110625,47792865,2607799,3235882,61728685,83651211,55850790,95251277,27187213,45995325,70367851,16567550,32058615,41092172,61928316,39373729,49271185,11757872,82651278,85711894,22879907,33553959,75820087,24915585,94360702,83133790,24314885,30771409,83083131,91240048,22435353,68694897,83948335,36139942,6111563,71083565,54663246,9860968,43933006,67084351,97940276,33565483,70782102,16099750,88130087,90654836,61712234,45381876,62051033,28787861,12303248,1375023,64602895,86460488,6157724,21001913,17081350,15163258,176257,32151165 +415901,98462867,55753905,86022504,29834512,17764950,68816413,68204242,67793644,68000591,91664334,54199160,28928175,3183975,33249630,89637706,72274002,4069912,69355476,11923835,25842078,64157906,4787945,30395570,99965001,81898046,52613508,3633375,43501211,13173644,24058273,39201414,24168411,81853704,5111370,14093520,77284619,70372191,2717150,67084351,18131876,95290172,9058407,29635537,84904436,8807940,20765474,67474219,92398073,55189057,33797252,36505482,96193415,52060076,83210802,59318837,97561745,99524975,9188443,48774913,38658347,20002147,82979980,87710366,85117092,38645117,63628376,70800879,45919976,49271185,21993752,29819940,6221471,7423788,40781449,83302115,49236559,92803766,1197320,10309525,99515901,47792865,55770687,35092039,43322743,98739783,5822202,65047700,55143724,26102057,3088684,21533347,10358899,62740044,10366309,98130363,40865610,94595668,72238278,36468541,66832478,37739481,19376156,27411561,92033260,85116755,55602660,6525948,4119747,14731700,50567636,75543508,78602717,68644627,90090964,15535065,33237508,32250045,40677414,23848565,15064655,61960400,60102965,66885828,66663942,98371444,60430369,508198,16971929,75820087,95581843,78218845,35456853,44842615,92787493,526217,37620363,61263068,74357852,59197747,54868730,54663246,72373496,57803235,63967300,85242963,27325428,96318094,62490109,30694952,30569392,50702367,8729146,91957544,26139110,9623492,44479073,72777973,53084256,6793819,33553959,65338021,41092102,17037369,75229462,7646095,3509435,92554120,36753250,42967683,66428795,76434144,18833224,80014588,45237957,92692283,40152546,25636669,30139692,47361209,95395112,29466406,16595436,44889423,23110625,45428665,9575954,63506281,70004753,58751351,22997281,29029316,55615885,68824981,15148031,30811010,80251430,99690194,9886593,38008118,55470718,99658235,99549373,29994197,42782652,669105,38494874,45306070,42644903,61373987,70727211,99604946,44846932,70541760,26063929,3294781,82886381,89214616,88251446,54987042,95726235,44473167,66116458,85571389,33431960,18411915,67451935,6986898,14326617,1788101,96726697,34497327,4204661,39553046,50188404,35996293,45617087,44983451,50668599,45996863,56424103,78589145,44060493,47090124,76170907,55669657,42073124,4508700,34295794,5970607,7955293,62496012,84187166,83789391,60955663,31126490,26664538,22942635,62452034,77694700,15163258,86460488,28550822,91831487,2891150,81172706,19272365,86301513,92071990,66903004,321665,43933006,98943869,28734791,31904591,38061439,72738685,68702632,46851987,14220886,62803858,30366150,30653863,51507425,17727650,16424199,46870723,51047803,37183543,96735716,81677380,16054533,9829782,81855445,7502255,83155442,82052050,73021291,48673079,97940276,39373729,2208785,65275241,66137019,30891921,77300457,82339363,12416768,56531125,18783702,72725103,51472275,69579137,86242799,80316608,56515456,16405341,61728685,92353856,48260151,2204165,13862149,56153345,61982238,40781854,71333116,1375023,44915235,92215320,76671482,23569917,69136837,18806856,68041839,36808486,6038457,53547802,37192445,73109997,17081350,4064751,13468268,14626618,66250369,16380211,61859581,6153406,27665211,9860195,93053405,78561158,24733232,71469330,34493392,8129978,22450468,26863229,61712234,64602895,24826575,26292919,71920426,72495719,34236719,8913721,7517032,79880247,68316156,7919588,73235980,87598888,57240218,4668450,24591705,44348328,32590267,61623915,93933709,15075176,75052463,3233569,16445503,8128637,62232211,62357986,26392416,824872,77787724,14947650,32058615,18466635,82726008,47708850,43376279,20486294,58180653,7687278,11581181,91802888,40984766,17068582,66045587,3233084,52293995,13231279,4806458,73617245,80555751,89078848,75777973,30163921,44481640,66322959,7685448,32274392,29401781,93057697,84406788,88653118,73168565,90457870,76330843,99971982,74441448,59614746,99297164,79738755,33628349,37224844,56461322,30787683,5640302,54058788,38256849,112651,19101477,89046466,34946859,41481685,73031054,6808825,54014062,83533741,4199704,42947632,79657802,20122224,58208470,874791,79922758,67513640,10597197,67031644,45995325,72019362,17146629,95957797,9257405,29065964,168541,34698463,31161687,2607799,84349107,78785507,26275734,82897371,38341669,37659250,17539625,74110882,7229550,40197395,30218878,62693428,83651211,9398733,94090109,67030811,41245325,56955985,99333375,90061527,57163802,71083565,12348118,62552524,34667286,89699445,51715482,28851716,33304202,82178706,77898274,65715134,77187825,76825057,48892201,62430984,63015256,31491938,77620120,22129328,36930650,45407418,8115266,14349098,67124282,48088883,52261574,72732205,75128745,8055981,74743862,12664567,8696647,79821897,1431742,75986488,59405277,93562257,61897582,74137926,23194618,20140249,3235882,17386996,7182642,44847298,78909561,70420215,89811711,93515664,36396314,4095116,54427233,55960386,65038678,19939935,5482538,54606384,48658605,19898053,50806615,79191827,34432810,37891451,1204161,84002370,98648327,74614639,77272628,92604458,59371804,17857111,65851721,53802686,90310261,40356877,21070110,49328608,8099994,569864,33336148,56473732,97057187,30998561,68939068,15948937,55247455,10961421,96906410,66667729,19891772,45794415,4515343,76315420,96852321,83368048,68128438,45381876,90013093,33201905,17957593,22879907,84840549,89804152,66271566,43357947,7011964,59109400,16567550,89499542,30090481,9599614,17976208,48893685,39801351,32151165,38022834,82532312,65081429,11543098,4099191,77072625,30764367,31727379,41092172,99917599,6521313,59177718,86821229,41380093,15015906,57961282,33699435,91240048,78549759,20836893,57359924,42692881,99861373,18504197,81805959,69255765,5073754,99021067,27625735,27041967,23432750,47298834,26493119,18663507,19486173,61141874,29510992,29932657,8651647,4091162,64055960,20645197,84166196,8791066,37675718,32159704,16099750,35192533,97379790,99125126,3487592,94516935,11161731,20867149,59501487,92692978,7066775,33123618,36780454,57393458,69605283,13470059,72278539,27185644,88130087,32426752,94911072,78884452,98948034,67281495,24619760,8634541,36685023,5267545,6871053,61815223,59910624,69697787,48360198,76540481,93270084,16019925,30771409,79136082,61271144,47738185,48675329,85695762,26507214,49882705,38365584,4978543,42199455,49641577,82651278,55850790,85711894,22200378,47213183,26776922,29188588,37957788,86001008,95010552,43152977,22435353,74724075,16097038,34698428,2917920,51590803,63059024,54762643,39171895,71300104,84684495,64098930,45790169,20642888,79806380,40027975,75407347,6157724,50007421,53842979,83150534,18001617,26397786,36312813,99226875,57658654,43506672,19457589,10264691,68102437,29516592,44784505,57248122,97641116,1936762,91727510,86543538,80246713,86118021,5832946,88047921,69641513,93359396,72614359,2331773,43045786,53632373,8373289,22405120,22108665,10453030,29959549,88444207,36135,13348726,176257,71316369,71522968,62428472,87720882,37560164,18699206,73222868,26114953,93566986,53888755,36580610,65074535,60176618,15902805,62051033,26734892,77312810,65017137,78766358,53666583,45667668,44627776,6505939,58224549,247198,71657078,91255408,90004325,94539824,96420429,70036438,92530431,74452589,91938191,70367851,65454636,41442762,61859143,21289531,24953498,53648154,78300864,6111563,97011160,37501808,12024238,17365188,61741594,76960303,62115552,14363867,33565483,66611759,8733068,44245960,99729357,90654836,63372756,42237907,4434662,81274566,76703609,62936963,21919959,20681921,65271999,61928316,83133790,16684829,79942022,39847321,17058722,30543215,17385531,33895336,72357096,97783876,54517921,47887470,77413012,77377183,82327024,83269727,31733363,13819358,45075471,45990383,81774825,57241521,45848907,51464002,37280276,12571310,85023028,21673260,67227442,48395186,9860968,65880522,6497830,15536795,94076128,68875490,36812683,91141395,69786756,53393358,83747892,64087743,66319530,22113133,60393039,9259676,14045383,75135448,10649306,64848072,39986008,9603598,32161669,92998591,44177011,89419466,28787861,1239555,22721500,73124510,51466049,75153252,82427263,38009615,51315460,17894977,44550764,54232247,37435892,3880712,26998766,98653983,58749,749283,22766820,20568363,63152504,91990218,51588015,59981773,84127901,4985896,91281584,9175338,14723410,84293052,17016156,32699744,96709982,36845587,6819644,11757872,83083131,60106217,35512853,94360702,34044787,62762857,92541302,49598724,1022677,1569515,88904910,77301523,79322415,68110330,21001913,72358170,90158785,20885148,72793444,26744917,93790285,7104732,45049308,97281847,83948335,90272749,24915585,36933038,40686254,99224392,70596786,37166608,3773993,30463802,69848388,1250437,33061250,73786944,59329511,51149804,68694897,86798033,11365791,95251277,71965942,89996536,7300047,44844121,44664587,16387575,60581278,49597667,42307449,36189527,33935899,12303248,94711842,51426311,52204879,73392814,24314885,4798568,87160386,92867155,23740167,38510840,23134715,88698958,21397057,40534591,46540998,56484900,89217461,70782102,28796059,54263880,57020481,36139942,47893286,27187213,82779622 +97783876,65851721,66319530,63059024,51047803,87598888,36505482,57240218,90272749,57241521,44627776,24733232,112651,4119747,82427263,93933709,87720882,34497327,4199704,1788101,8128637,63967300,13231279,60581278,55189057,68041839,16019925,55669657,93562257,89699445,95251277,28796059,66667729,75777973,10366309,80014588,12024238,92554120,247198,89046466,39171895,65880522,43506672,84293052,6793819,8055981,45848907,69848388,508198,30139692,80246713,17068582,45990383,4434662,43376279,93790285,3773993,97281847,16405341,35092039,40677414,30787683,72019362,68702632,73222868,3233084,18783702,44889423,98943869,8651647,30163921,33304202,59197747,77413012,22942635,34236719,30771409,21070110,37501808,55470718,3880712,85242963,83210802,13470059,23134715,99125126,98653983,68204242,81172706,4668450,62496012,1022677,11365791,79821897,9829782,91255408,98462867,36189527,3233569,57359924,42782652,92692978,14045383,38022834,70782102,40686254,26493119,68644627,72357096,55602660,91831487,62936963,45996863,27325428,14349098,26392416,44842615,40356877,7955293,94090109,48360198,19486173,17957593,94516935,74724075,67281495,70004753,16595436,38365584,60955663,44784505,69786756,7919588,14093520,15015906,79880247,90013093,37957788,88047921,50188404,3294781,70596786,39986008,59329511,85711894,6871053,17365188,2917920,8696647,73786944,72738685,11161731,98948034,52060076,43501211,83789391,57393458,71657078,21993752,38008118,18699206,28787861,40152546,85116755,569864,26744917,39553046,43933006,47361209,4204661,7104732,72614359,62490109,36812683,9860968,98739783,34698463,7502255,86301513,14723410,75052463,23740167,47090124,40027975,38061439,76434144,9575954,6153406,71333116,51472275,71300104,7685448,49597667,7229550,75986488,29188588,18833224,82779622,2717150,51464002,26998766,6808825,55247455,24826575,73617245,14731700,72358170,37560164,70727211,1204161,32590267,73031054,15536795,18806856,84127901,17058722,39847321,78300864,56424103,53648154,96709982,60106217,44473167,65074535,9175338,45428665,98371444,16097038,5970607,36312813,99297164,66611759,31126490,874791,89811711,70036438,28851716,88698958,58208470,64087743,80316608,669105,41380093,19891772,67124282,2204165,30694952,53084256,55143724,61815223,37739481,16099750,96726697,16971929,99224392,73235980,77787724,40781449,62740044,72274002,91990218,37183543,38658347,58224549,78589145,4064751,6111563,26863229,57961282,97011160,81805959,68102437,99021067,99658235,78602717,91664334,37675718,58749,92541302,38341669,92215320,36780454,44844121,76315420,23194618,22405120,62232211,17037369,61859581,99549373,71316369,22997281,97057187,78884452,34295794,9886593,34698428,44550764,67793644,63152504,12348118,83155442,27665211,749283,33431960,23848565,45237957,79806380,29994197,17146629,99604946,79738755,77284619,39801351,89637706,6505939,99515901,97940276,35512853,44177011,24058273,3235882,68939068,46870723,18466635,20122224,7300047,16445503,20486294,99333375,71965942,51588015,6521313,67513640,52293995,57248122,48774913,61859143,29466406,21001913,97641116,1431742,89419466,51590803,66903004,28550822,17764950,65017137,26397786,87160386,99524975,88653118,84406788,26507214,82651278,29932657,84187166,93359396,94539824,32159704,45381876,77272628,59614746,95957797,4985896,72777973,95290172,91802888,27187213,50668599,74357852,29834512,53632373,76960303,47893286,32161669,18504197,9398733,36930650,30569392,76540481,91957544,9603598,20002147,4515343,36685023,13173644,17016156,90654836,91141395,90158785,26102057,30395570,89078848,20885148,45995325,26292919,19939935,30218878,14326617,29959549,86242799,22721500,55770687,22108665,33336148,57803235,20140249,34493392,23569917,78909561,92803766,80555751,66137019,4091162,30463802,33935899,76330843,61741594,35192533,88444207,16684829,1569515,77377183,53393358,81898046,31733363,44664587,36139942,4099191,11581181,40781854,85695762,76671482,65454636,40534591,321665,81853704,71083565,73392814,3487592,99690194,21673260,4978543,86798033,35996293,168541,56484900,75543508,21533347,34432810,54517921,30366150,85117092,51466049,31491938,66663942,61373987,2891150,89214616,27411561,8129978,7423788,6819644,33201905,44847298,1197320,33565483,36580610,74137926,59177718,63628376,92398073,31904591,45919976,96852321,68000591,40865610,91240048,5111370,40984766,8733068,11923835,25842078,47213183,48088883,37620363,42307449,37280276,9623492,6525948,75135448,77898274,53547802,81855445,2607799,82532312,9257405,77187825,15535065,42237907,26664538,42967683,98130363,93053405,78218845,62452034,95010552,82178706,47887470,61897582,17385531,69605283,59910624,10597197,61263068,33061250,1936762,35456853,50007421,30764367,29635537,22129328,8099994,72725103,3633375,45617087,48893685,79322415,66045587,526217,4798568,92787493,8729146,83083131,86543538,36135,37659250,63015256,83269727,70541760,44846932,30543215,5073754,60102965,89804152,37224844,44060493,86821229,42073124,68128438,52204879,61960400,53888755,62051033,43045786,85023028,93057697,54987042,46851987,67227442,84904436,13468268,88904910,17386996,32699744,27185644,99729357,31727379,54199160,7687278,5822202,86001008,99861373,96735716,17539625,51149804,76825057,17727650,22766820,92033260,37435892,68875490,9188443,21289531,10358899,43152977,75128745,39201414,33237508,49236559,7066775,22200378,29510992,92692283,17857111,65271999,99965001,7182642,33249630,9860195,4069912,71469330,83747892,40197395,80251430,64157906,30653863,9058407,8791066,77312810,83302115,90310261,30090481,78549759,91938191,59501487,68816413,8115266,62803858,82979980,66832478,49598724,85571389,93515664,8807940,62428472,61728685,1250437,56531125,52261574,18001617,89217461,38009615,59109400,24953498,45790169,15148031,38256849,38494874,69255765,41481685,27625735,55753905,4787945,70800879,44915235,66322959,73168565,2208785,61982238,30998561,95395112,77620120,48675329,61271144,97561745,70420215,99917599,20642888,33628349,75153252,38645117,42947632,70372191,48892201,11543098,43357947,15075176,12664567,79922758,59318837,69579137,25636669,48673079,63372756,78766358,33699435,38510840,18663507,69641513,8634541,54868730,74452589,33797252,65081429,47298834,16424199,16567550,12416768,83651211,83948335,62693428,24915585,24619760,95726235,51426311,49328608,54014062,54427233,50702367,21919959,89996536,20867149,82897371,15948937,19457589,56153345,90090964,43322743,54762643,76170907,26063929,23110625,81274566,29516592,67030811,18131876,72278539,83133790,13348726,62115552,15902805,29819940,41092102,1239555,48395186,59981773,32274392,17976208,28734791,45794415,36396314,6221471,33123618,34044787,57163802,49882705,58751351,4806458,94711842,6986898,54663246,93270084,5640302,66116458,34946859,92604458,23432750,47792865,92998591,65715134,62430984,68824981,42692881,36933038,26776922,96193415,94911072,69355476,97379790,45049308,19898053,4095116,79942022,29029316,56515456,33553959,18411915,73021291,6038457,44245960,7011964,61928316,72793444,60393039,56473732,65038678,82886381,20836893,55960386,84684495,24591705,4508700,46540998,64055960,53842979,36753250,42199455,12303248,53666583,17081350,66271566,70367851,90457870,88130087,51315460,82327024,3088684,77694700,62762857,84002370,50567636,71920426,65338021,27041967,73124510,94595668,65047700,5832946,45407418,33895336,90004325,54232247,77300457,13819358,61141874,60176618,9599614,83368048,51715482,7646095,44983451,96318094,84840549,22113133,62552524,92530431,176257,14947650,49641577,26139110,44481640,15064655,48260151,96906410,67451935,16380211,64098930,12571310,61623915,66250369,64848072,99971982,45667668,68694897,79657802,92353856,16054533,60430369,93566986,5482538,31161687,58180653,56955985,415901,69136837,44348328,75229462,67474219,79136082,90061527,65275241,48658605,59371804,82339363,44479073,14220886,10453030,2331773,3509435,20568363,74614639,47738185,37166608,86460488,72373496,47708850,49271185,74110882,11757872,91281584,72732205,19272365,39373729,45306070,26114953,57658654,26734892,55615885,22879907,16387575,37891451,91727510,77301523,19376156,92867155,41092172,64602895,20765474,79191827,82726008,19101477,9259676,10649306,45075471,82052050,81677380,74743862,75820087,32250045,13862149,99226875,30811010,53802686,66428795,52613508,76703609,22435353,95581843,51507425,54263880,1375023,94360702,36468541,824872,5267545,57020481,36808486,8913721,83150534,24314885,30891921,37192445,72238278,41245325,26275734,67031644,83533741,22450468,10309525,84349107,6497830,10961421,20681921,29065964,32058615,66885828,15163258,69697787,21397057,72495719,3183975,89499542,14363867,54058788,67084351,68110330,41442762,78561158,73109997,28928175,77072625,86118021,14626618,29401781,74441448,55850790,75407347,68316156,88251446,63506281,50806615,7517032,56461322,10264691,17894977,71522968,87710366,42644903,6157724,24168411,84166196,94076128,34667286,96420429,86022504,20645197,92071990,81774825,61712234,98648327,59405277,62357986,32426752,54606384,32151165,36845587,78785507,8373289 +16019925,66667729,59329511,94516935,62936963,27041967,89046466,48360198,8733068,3880712,79821897,4985896,8651647,78602717,20486294,55753905,17365188,49271185,68041839,38008118,3509435,71657078,247198,93270084,66250369,20885148,35192533,36312813,42237907,22108665,10453030,35092039,33699435,10358899,34295794,85117092,26392416,3487592,168541,17146629,81853704,29466406,15535065,8913721,11581181,61373987,62740044,5267545,37739481,12303248,10309525,14349098,62496012,17037369,56531125,55143724,36139942,1250437,84349107,55770687,14220886,73392814,33304202,29516592,44245960,57658654,50188404,44844121,69355476,31491938,61263068,85242963,51464002,38256849,9829782,30366150,63628376,93790285,6793819,54263880,89419466,4798568,4434662,43357947,42307449,16405341,78589145,69848388,95581843,47361209,96193415,99021067,30163921,41092172,79322415,75986488,63967300,44842615,98371444,82339363,55189057,57163802,79657802,54014062,3773993,7687278,24733232,68644627,84904436,59109400,17058722,65715134,26102057,37891451,69579137,10366309,12348118,29065964,26292919,62430984,44177011,43933006,72373496,18504197,27665211,15075176,34698463,26664538,12664567,17957593,77377183,60430369,63015256,14045383,80014588,4668450,60176618,874791,59981773,5111370,26744917,65271999,54199160,9599614,14626618,89078848,96735716,89804152,54868730,14093520,38061439,43506672,44348328,3294781,36780454,93933709,92541302,27325428,55850790,60102965,24058273,97561745,60581278,98462867,67227442,22997281,22942635,7955293,99125126,40027975,749283,99549373,50567636,68000591,67451935,8129978,92604458,72777973,36812683,14731700,86301513,70727211,33237508,81898046,19939935,508198,14947650,23569917,31126490,62051033,36505482,57241521,56473732,20568363,2717150,38022834,61982238,78766358,32159704,29510992,76825057,73617245,70367851,63059024,6497830,52204879,5482538,96709982,41092102,99658235,44784505,86821229,81805959,70372191,59614746,17539625,70541760,68204242,16445503,71333116,72614359,48774913,98739783,36753250,97783876,71083565,69786756,57240218,92787493,66319530,86001008,22879907,89214616,1788101,5970607,86543538,23432750,42967683,47708850,72278539,91255408,7300047,60106217,26275734,30764367,52261574,1197320,4095116,112651,6505939,34493392,51472275,6153406,27411561,88698958,39201414,37183543,77787724,66116458,33797252,72732205,8807940,28796059,37280276,94090109,90272749,73031054,76170907,22129328,46870723,4978543,67793644,61741594,15536795,50668599,87598888,46540998,3088684,48673079,55602660,67281495,88653118,19272365,77694700,67474219,13819358,98943869,90061527,92692283,9575954,7919588,42782652,88904910,93053405,5073754,3233084,70420215,22113133,73222868,4508700,83210802,75229462,17976208,83651211,22766820,45075471,8128637,83155442,88251446,93562257,92692978,31733363,17857111,96726697,15902805,54762643,67031644,65081429,36580610,19376156,669105,72274002,80246713,30395570,22721500,40781449,50702367,6871053,99965001,33201905,34044787,82532312,38365584,6111563,43045786,18131876,39553046,80251430,13173644,59318837,19891772,4204661,73168565,91938191,40677414,33061250,69641513,99333375,86118021,89811711,84187166,29819940,45617087,59910624,77620120,18806856,78909561,92554120,13862149,40197395,66903004,22435353,77413012,91990218,44550764,80555751,71300104,39986008,61859581,83150534,47792865,16097038,85571389,36468541,20836893,76315420,95957797,7011964,18783702,43501211,74357852,79191827,18699206,4099191,45790169,19101477,61271144,6819644,9886593,11161731,23194618,4064751,62490109,7229550,35512853,32699744,42692881,57803235,47738185,30090481,89637706,74110882,5822202,7685448,1204161,45428665,42073124,65074535,2917920,49641577,95010552,58224549,49328608,27625735,74743862,90013093,24591705,84840549,824872,62803858,16380211,48658605,29834512,81172706,99729357,86242799,61960400,75820087,59371804,48088883,27185644,20681921,36933038,72725103,88130087,47887470,13470059,36135,89217461,66045587,4515343,26776922,79136082,77300457,61712234,29188588,15148031,5832946,70004753,86022504,30139692,53888755,16424199,7646095,68702632,20642888,526217,59197747,78300864,34432810,415901,33628349,71965942,66322959,1239555,21533347,53547802,52060076,87710366,59501487,569864,77187825,29029316,77312810,28851716,97011160,53632373,73124510,59177718,99917599,70596786,40984766,89996536,14723410,18001617,18833224,71469330,9188443,64602895,91957544,45996863,82327024,36189527,97940276,20002147,68824981,68816413,37659250,38658347,33431960,95395112,47090124,21993752,62693428,74441448,3183975,61859143,71316369,44847298,37501808,56424103,83302115,36685023,44983451,55470718,10961421,66611759,20140249,12024238,62452034,45919976,50007421,17764950,98653983,65047700,51047803,95726235,88444207,93359396,77898274,82178706,8099994,33895336,6986898,21070110,14326617,21397057,21919959,69255765,31904591,64098930,6221471,43376279,97057187,77272628,83083131,43152977,82886381,39171895,29994197,3235882,74614639,51588015,77072625,8055981,40356877,8115266,84293052,321665,16684829,36930650,37224844,62357986,88047921,47298834,78785507,34698428,7066775,8696647,65851721,33123618,20122224,66663942,99690194,55669657,62762857,44915235,58180653,25842078,77284619,73786944,69697787,16387575,81274566,36845587,85116755,28734791,92033260,68875490,51590803,65017137,7517032,30998561,48892201,70036438,51507425,2204165,61815223,82427263,45237957,48260151,8634541,57248122,74724075,83747892,9175338,44481640,37192445,66428795,84127901,4119747,99515901,23134715,1022677,17894977,33565483,76703609,9623492,18411915,77301523,26493119,55615885,68694897,53393358,23740167,39373729,7423788,26998766,1375023,32151165,83269727,66832478,84166196,26063929,90158785,48893685,29959549,39801351,53842979,13231279,33249630,78561158,87160386,16054533,87720882,48675329,4806458,44060493,30787683,58751351,63506281,10597197,6521313,31161687,1431742,20765474,92215320,6525948,26114953,93515664,33336148,9603598,56955985,30811010,95290172,83789391,17386996,45667668,53084256,45306070,83533741,75543508,46851987,53802686,17016156,92998591,28550822,23848565,53648154,68939068,13468268,10649306,7104732,91240048,30463802,67084351,76434144,72357096,73235980,44473167,56153345,40152546,34497327,22405120,51715482,40865610,98948034,24619760,99604946,58749,84406788,98648327,44889423,92803766,79806380,75777973,63152504,19486173,69136837,79922758,31727379,49236559,41380093,86798033,57961282,47213183,90457870,82726008,11757872,76330843,60955663,75128745,37560164,41481685,25636669,32161669,89499542,98130363,65454636,82779622,56515456,73021291,49597667,78884452,17068582,30771409,84684495,80316608,9860195,93566986,55247455,67124282,20645197,4069912,17081350,90310261,17385531,66885828,84002370,75135448,47893286,42644903,34667286,64157906,38341669,76671482,51315460,32426752,9257405,54606384,78218845,9058407,99971982,24953498,39847321,57020481,86460488,21001913,7182642,34946859,36396314,57359924,82897371,81855445,15064655,42947632,82979980,73109997,18663507,11923835,54663246,28787861,33935899,3233569,61728685,1569515,45381876,30569392,22450468,16567550,15015906,32590267,30891921,32274392,37620363,74137926,76540481,37957788,45407418,72019362,71920426,24168411,72358170,53666583,94539824,72495719,9860968,99524975,54058788,24826575,66137019,9398733,37435892,16595436,30694952,68102437,11543098,28928175,2891150,94076128,66271566,83368048,99226875,6808825,17727650,52613508,38645117,62552524,64087743,2208785,34236719,41245325,62232211,82052050,29635537,81677380,65880522,36808486,40781854,70800879,38494874,90090964,4199704,99297164,45990383,26734892,97379790,35996293,96906410,63372756,64848072,75153252,94595668,72793444,79942022,24915585,91802888,44627776,58208470,48395186,68316156,1936762,3633375,91664334,4787945,94911072,67030811,90654836,6157724,95251277,92530431,32250045,99861373,75052463,15163258,75407347,38009615,61928316,42199455,79880247,2331773,93057697,65338021,40534591,23110625,97641116,26139110,8791066,92867155,65038678,91831487,78549759,7502255,2607799,6038457,38510840,45848907,29932657,37166608,26507214,51426311,68128438,69605283,19898053,8729146,21289531,64055960,13348726,51149804,89699445,9259676,54517921,51466049,52293995,26397786,91727510,22200378,59405277,81774825,29401781,54427233,4091162,56484900,79738755,74452589,35456853,45794415,12571310,15948937,62115552,40686254,55960386,30218878,45995325,96318094,99224392,21673260,49882705,30653863,56461322,85023028,94360702,83133790,41442762,91141395,19457589,43322743,72738685,92353856,83948335,76960303,96420429,60393039,14363867,91281584,176257,49598724,96852321,92398073,33553959,44664587,82651278,44846932,71522968,24314885,20867149,68110330,44479073,32058615,67513640,54232247,45049308,72238278,12416768,85711894,16099750,92071990,65275241,90004325,61897582,61623915,30543215,37675718,54987042,26863229,16971929,8373289,70782102,27187213,57393458,11365791,85695762,50806615,5640302,97281847,10264691,94711842,61141874,18466635,62428472 +55753905,72357096,20486294,69786756,96726697,45237957,3233084,70596786,46870723,89811711,68644627,19891772,53084256,77312810,88047921,48774913,64087743,247198,65017137,55143724,83789391,23432750,97783876,17068582,97641116,48260151,4119747,61859143,6793819,40677414,56424103,12571310,54987042,6986898,48893685,44627776,20140249,22129328,27665211,40356877,29466406,89214616,25842078,63059024,33304202,23848565,74724075,42307449,75986488,63152504,67281495,4199704,75153252,14731700,14349098,50007421,78218845,74357852,8651647,1204161,59910624,73168565,37435892,88698958,95290172,69848388,4099191,74110882,26734892,14326617,67451935,26292919,94516935,39986008,569864,79657802,79821897,75777973,9860968,17058722,38008118,4095116,81274566,8128637,89804152,57241521,10366309,43322743,86821229,8696647,57248122,38494874,68816413,29959549,28851716,85242963,24733232,32161669,85711894,60102965,94090109,60581278,92604458,26776922,88904910,77300457,32590267,23194618,112651,6153406,10309525,33431960,34236719,15148031,26998766,21289531,40781854,92787493,77413012,43376279,9623492,94911072,65038678,99524975,89996536,89419466,43506672,45428665,65851721,72777973,30543215,46851987,37957788,59109400,71657078,14045383,20122224,13231279,93790285,64848072,82651278,32274392,3235882,22721500,55602660,3880712,27185644,68204242,41481685,43357947,36845587,7502255,1431742,89046466,41092172,80014588,93515664,93933709,84406788,76434144,3294781,62452034,73222868,23110625,20836893,51047803,14723410,82979980,76960303,53547802,19101477,87160386,40027975,92998591,874791,7517032,44550764,16971929,9175338,99658235,8791066,78300864,89637706,4064751,77787724,33553959,30218878,13468268,50806615,82178706,31904591,4069912,82327024,87720882,33935899,32159704,68041839,66832478,68128438,7685448,65715134,85695762,69355476,40534591,6111563,36753250,14947650,38645117,40686254,32699744,11543098,55247455,22766820,47298834,669105,1197320,16595436,62496012,84293052,24058273,54762643,33565483,99965001,78909561,49328608,61741594,45617087,66319530,73786944,92541302,84127901,7182642,87598888,13470059,33249630,43933006,81172706,78561158,63967300,52261574,98653983,37183543,47361209,55615885,59177718,83210802,39171895,72358170,29994197,74441448,91255408,77898274,67513640,20642888,98739783,77272628,92692978,36812683,74614639,79806380,35996293,48673079,15163258,70004753,93562257,58208470,96852321,7300047,34493392,70800879,17957593,1250437,54663246,60430369,29834512,37224844,66428795,49236559,90272749,65880522,88653118,92803766,90310261,72495719,18699206,90457870,22108665,92554120,85116755,61960400,19486173,66250369,2917920,47090124,73235980,99021067,27041967,36580610,62232211,28796059,37166608,76315420,99224392,56515456,4515343,42782652,30163921,2204165,22450468,70367851,45306070,42237907,38009615,30366150,61859581,84904436,23134715,62357986,29819940,9259676,80316608,72738685,35092039,10597197,76170907,49597667,72238278,79322415,6521313,45790169,3233569,14363867,99549373,81855445,91831487,6221471,70541760,82427263,31733363,75052463,77284619,27325428,44846932,66137019,59329511,15015906,7229550,80555751,6871053,77072625,78884452,86118021,45667668,62430984,70036438,96735716,37675718,8913721,30811010,71469330,68939068,26114953,6808825,66903004,83651211,77377183,47792865,57961282,63015256,33201905,91957544,94360702,29516592,59197747,48658605,75543508,44245960,56461322,24953498,7646095,168541,78602717,71300104,78549759,9398733,40197395,13173644,54199160,96193415,68102437,4787945,5073754,57240218,23569917,4091162,30463802,11161731,15064655,28787861,2717150,12416768,50702367,51464002,12348118,79922758,59614746,7104732,99125126,17016156,91938191,75135448,8807940,92692283,4204661,18131876,74452589,90061527,88444207,6505939,74137926,71333116,45990383,30787683,97011160,16097038,37501808,43045786,95957797,34698428,98462867,34497327,12664567,8099994,81898046,36930650,34698463,61141874,33237508,90654836,44784505,81853704,1022677,24591705,53648154,28550822,44060493,26397786,72278539,7919588,71083565,7011964,48360198,57020481,37560164,67793644,21397057,93057697,35192533,80251430,9603598,40781449,10961421,34295794,44348328,8733068,7066775,24314885,62803858,76671482,17386996,38658347,30395570,22879907,23740167,83155442,95251277,85571389,77187825,18504197,36468541,30653863,50188404,80246713,79136082,6157724,63372756,45407418,97379790,21673260,73392814,71316369,11365791,72725103,21070110,42947632,86543538,4798568,66611759,1569515,51715482,39847321,26493119,14093520,75820087,29029316,75229462,67474219,84187166,67030811,74743862,81805959,38061439,19898053,57359924,99297164,68000591,29401781,11757872,15902805,16019925,29188588,66322959,42967683,26744917,27187213,56473732,44177011,53842979,51507425,62762857,9257405,67124282,21533347,51588015,26863229,77620120,91802888,36505482,76825057,16405341,96318094,11923835,7687278,81677380,17365188,66116458,42692881,86022504,31161687,62051033,87710366,83150534,30771409,70420215,6525948,38022834,20867149,20885148,15948937,39553046,41442762,93359396,35456853,97281847,37891451,36808486,44473167,22997281,91990218,92215320,89078848,68875490,54058788,13819358,33336148,84840549,61271144,4985896,53888755,9575954,44842615,66271566,83302115,32058615,5970607,43501211,9188443,21993752,89699445,78589145,71522968,5822202,36780454,749283,51466049,16567550,1239555,415901,16445503,30139692,78766358,42199455,62936963,61373987,36312813,38256849,93270084,86242799,31491938,18001617,39801351,70782102,92033260,97057187,72373496,96709982,79942022,28734791,44664587,66045587,16684829,46540998,48395186,24168411,14220886,53802686,50668599,64602895,8129978,18833224,13348726,57658654,40984766,83368048,8055981,51472275,45919976,92398073,15075176,69641513,6819644,15536795,92353856,39373729,82532312,84349107,98371444,34946859,44983451,33699435,69136837,45794415,99515901,54427233,76330843,42644903,19939935,54014062,508198,15535065,79738755,27411561,58751351,83269727,82886381,4434662,79191827,70727211,20681921,30764367,65047700,26275734,95726235,5111370,65454636,57163802,66663942,17037369,42073124,99971982,8634541,62115552,84684495,21919959,54606384,26507214,9599614,72019362,95581843,82897371,16380211,62693428,49641577,9860195,98943869,38341669,65271999,55189057,526217,82779622,17146629,9058407,17385531,53632373,83948335,72614359,68110330,68702632,18663507,24826575,20765474,99861373,54517921,76540481,45381876,45996863,3773993,72732205,86301513,12024238,94076128,82726008,68694897,94711842,65074535,29635537,17894977,66885828,37280276,90004325,61897582,66667729,10358899,59318837,40865610,47893286,30891921,19376156,70372191,26102057,99604946,99729357,5832946,86460488,85023028,67084351,24619760,55669657,40152546,37620363,43152977,7955293,37659250,56531125,51590803,29065964,7423788,16424199,83133790,44889423,22200378,321665,6038457,62552524,32250045,83083131,49271185,176257,44844121,61982238,24915585,44479073,69697787,54868730,98130363,38510840,4668450,33628349,29510992,59501487,47708850,36685023,97940276,45848907,60176618,9886593,55470718,17764950,62490109,52060076,60106217,19457589,47887470,93566986,33123618,27625735,55770687,30090481,67227442,73617245,64055960,60393039,56955985,20002147,57393458,58749,6497830,1788101,18783702,65275241,68824981,25636669,75128745,22942635,73124510,75407347,52293995,10649306,4508700,65338021,30569392,18411915,48892201,55960386,26063929,45995325,51149804,72274002,59371804,96420429,96906410,36396314,83533741,72793444,3487592,93053405,94539824,16099750,52613508,78785507,61623915,20645197,95010552,34044787,60955663,13862149,71920426,31727379,97561745,79880247,91281584,63506281,49598724,17857111,17081350,3509435,95395112,91664334,26139110,73031054,53666583,14626618,2208785,35512853,44481640,36933038,10453030,47213183,22405120,36135,41380093,30694952,824872,9829782,57803235,69605283,44915235,12303248,53393358,86798033,26392416,55850790,30998561,26664538,98948034,50567636,91727510,45049308,32151165,17727650,29932657,90090964,99917599,56153345,8373289,58224549,16387575,58180653,51426311,82052050,88251446,47738185,85117092,63628376,22113133,99333375,61815223,36139942,69579137,51315460,34432810,33061250,82339363,19272365,61728685,61263068,1375023,5640302,11581181,39201414,99690194,65081429,1936762,99226875,5267545,84166196,83747892,17539625,88130087,49882705,3183975,3088684,62740044,92071990,76703609,91240048,48675329,2891150,8115266,86001008,31126490,89217461,61712234,84002370,48088883,59405277,59981773,20568363,3633375,67031644,17976208,37739481,94595668,81774825,56484900,98648327,61928316,4806458,64098930,32426752,90158785,37192445,36189527,73109997,41092102,38365584,77694700,8729146,54263880,41245325,16054533,28928175,18806856,45075471,18466635,4978543,64157906,71965942,33797252,89499542,68316156,73021291,33895336,52204879,92530431,21001913,44847298,62428472,69255765,2607799,10264691,22435353,92867155,5482538,90013093,2331773,91141395,54232247,34667286,77301523 +62452034,99604946,66250369,44842615,74137926,14093520,48360198,99549373,54427233,28928175,16097038,5970607,37675718,94090109,62430984,9188443,44473167,98739783,52261574,80555751,31733363,31904591,68816413,3294781,65275241,81855445,82886381,96193415,15064655,42782652,51472275,22450468,72777973,68644627,26863229,69355476,669105,60430369,36468541,54868730,16971929,84406788,17081350,9623492,20486294,48893685,35456853,32426752,4099191,37620363,46851987,33553959,19101477,17727650,26392416,43357947,99524975,38494874,19939935,57248122,66045587,47887470,44479073,85023028,41442762,43322743,70004753,66832478,37183543,69136837,49236559,55602660,68824981,72357096,91664334,415901,41481685,14731700,44550764,526217,77072625,70800879,98130363,29959549,40677414,2331773,99224392,71333116,70596786,98648327,85116755,61741594,59371804,10309525,10366309,4515343,29819940,321665,98948034,65715134,26139110,67474219,33628349,93515664,24591705,8913721,34698428,1250437,70036438,50806615,77284619,48892201,67031644,15015906,99226875,88251446,68875490,35192533,17385531,40781854,30653863,18663507,33304202,27325428,9058407,55247455,84166196,26507214,50007421,19486173,26275734,33797252,53802686,1197320,29029316,44627776,2204165,78549759,77300457,27665211,7919588,71522968,29994197,81677380,7502255,91802888,20122224,72373496,62803858,95957797,86001008,59405277,56531125,92215320,83155442,82427263,6111563,89419466,22721500,66885828,89499542,91957544,824872,57393458,18833224,33249630,4798568,84002370,54199160,21673260,32250045,24826575,29635537,4064751,56473732,53632373,58224549,50188404,73031054,63967300,32699744,30366150,18806856,99690194,67793644,86242799,93566986,40197395,97561745,78589145,53666583,6038457,17386996,88444207,48260151,20642888,73235980,3509435,65038678,32161669,80316608,79136082,24619760,11923835,56424103,59501487,57240218,4787945,20885148,54014062,64087743,53084256,569864,18504197,66322959,30218878,42692881,45306070,60102965,87160386,45919976,1936762,87710366,4199704,80014588,54987042,93053405,7011964,26292919,95395112,8651647,66903004,112651,2607799,28550822,61373987,84293052,17764950,95726235,38658347,39553046,12416768,82178706,39986008,64157906,77272628,79880247,27625735,7646095,3183975,6221471,73124510,15075176,81172706,86460488,34946859,45617087,20140249,90310261,90654836,24058273,58208470,36396314,37891451,91281584,22108665,17068582,28734791,72358170,56153345,75407347,60106217,47792865,2717150,16595436,9599614,4806458,52060076,7104732,168541,55960386,85695762,38645117,33061250,69641513,1022677,66663942,20681921,22129328,92604458,34236719,92398073,1569515,5822202,96318094,70372191,50702367,15535065,67281495,19376156,36505482,34497327,11757872,76434144,30090481,21919959,39201414,18783702,66271566,12664567,82052050,65017137,47708850,83789391,65454636,85242963,29834512,57359924,92033260,99917599,66137019,78766358,13862149,34295794,48088883,1239555,62051033,8733068,64055960,57803235,90457870,77377183,33431960,15948937,75820087,79191827,8115266,24915585,40984766,64848072,89214616,82979980,91255408,53888755,83651211,97641116,35996293,61623915,10358899,56955985,72278539,4508700,61263068,45790169,29510992,55753905,58751351,20002147,97281847,27411561,70727211,28851716,66611759,97379790,17146629,77187825,1204161,23848565,4069912,49641577,92554120,79657802,72738685,8373289,39373729,86543538,72732205,9398733,52613508,11161731,8634541,63372756,24733232,40534591,59318837,37501808,66116458,90013093,58749,63059024,43376279,60176618,77301523,79806380,34698463,71920426,7955293,96735716,33123618,14363867,12571310,47213183,25842078,99965001,9886593,76825057,77413012,78602717,73392814,30764367,7229550,61982238,16099750,17365188,22766820,20765474,8729146,68316156,38341669,67030811,65851721,55143724,36808486,21993752,80246713,68000591,43045786,48675329,51149804,7423788,19457589,23110625,45794415,11543098,2891150,44889423,61859143,73168565,51715482,6986898,83210802,3633375,9175338,4668450,37957788,6793819,14626618,74614639,74441448,38061439,42307449,30569392,78884452,21001913,97940276,38365584,45996863,16387575,84187166,86022504,15148031,63506281,75153252,9860195,18411915,26102057,32159704,56515456,75543508,23134715,33237508,40781449,37739481,6521313,749283,91831487,71657078,78218845,30139692,90090964,30787683,53842979,2208785,8129978,7685448,94076128,36930650,89078848,62740044,74357852,22435353,73222868,99125126,83302115,82897371,4204661,48395186,72019362,49882705,20867149,45995325,36135,8807940,82339363,55770687,62936963,81853704,32590267,29188588,89811711,49328608,33699435,46870723,1431742,59614746,66667729,90004325,88047921,76170907,30771409,44983451,9257405,55189057,51590803,36933038,17857111,73786944,72495719,5640302,97783876,96726697,88130087,44846932,9829782,91990218,44245960,89637706,42199455,29401781,31727379,71316369,76671482,51507425,26114953,18131876,68102437,26493119,30543215,75777973,14947650,53648154,55470718,6153406,68041839,26998766,99658235,17957593,77620120,44844121,74110882,20836893,42073124,99861373,62232211,45990383,56461322,61897582,72793444,92787493,33935899,77312810,10961421,27041967,99515901,247198,17894977,36312813,85571389,44847298,41092102,44060493,1788101,26063929,37435892,92803766,84904436,42237907,68204242,82651278,51464002,84349107,69255765,12348118,21070110,17016156,36753250,40027975,79821897,38022834,13348726,29065964,67451935,36780454,4095116,98943869,16380211,68110330,49597667,76960303,44784505,37166608,8696647,89046466,62693428,44348328,62762857,89804152,61712234,83747892,93562257,76703609,89699445,50567636,62357986,6808825,30811010,81274566,29932657,508198,16054533,36845587,35092039,48673079,53547802,59329511,45848907,83269727,14723410,71300104,94711842,92353856,86118021,40152546,58180653,43152977,22200378,5073754,95581843,57658654,91727510,81774825,38009615,51047803,93933709,50668599,75128745,62428472,13231279,4978543,45049308,45407418,22942635,78561158,14326617,65338021,17037369,99021067,40865610,83533741,49598724,94360702,98462867,38008118,8791066,60581278,77787724,69697787,54606384,3235882,30891921,17058722,8128637,29466406,38256849,18466635,6505939,39171895,15902805,54517921,71083565,82726008,68128438,47090124,54058788,3880712,49271185,51466049,55850790,84840549,60955663,36812683,14220886,22405120,52293995,41380093,22997281,65081429,95251277,96709982,19898053,32151165,9575954,4985896,59109400,34493392,6497830,31161687,34667286,95290172,38510840,61859581,16684829,54663246,5111370,76540481,40356877,5482538,69605283,64098930,72238278,40686254,13470059,68939068,78300864,65047700,11365791,93790285,42644903,92692283,86821229,66428795,37224844,25636669,26664538,92998591,67084351,4119747,83368048,65271999,62552524,94911072,79922758,51426311,65880522,39847321,44481640,23569917,61141874,10649306,16019925,3773993,45237957,24314885,68694897,30163921,17976208,32058615,83150534,99297164,75052463,52204879,69786756,75229462,98371444,87720882,45381876,85711894,29516592,3233084,83133790,22879907,4091162,47361209,10264691,1375023,13819358,77694700,61815223,46540998,36189527,76330843,7517032,99971982,13173644,9259676,7182642,83083131,79942022,72614359,51588015,6525948,98653983,94516935,61928316,7300047,66319530,61271144,61960400,59910624,64602895,44177011,15536795,93270084,31126490,72274002,81898046,74452589,37192445,6819644,74724075,30395570,47738185,24168411,96420429,42967683,3088684,84127901,36580610,54232247,89996536,54263880,43501211,92541302,54762643,63015256,874791,70420215,67513640,35512853,65074535,7687278,67227442,94539824,6871053,20568363,7066775,6157724,33336148,78909561,57961282,97011160,82327024,73617245,77898274,30694952,99729357,19272365,90158785,78785507,30998561,22113133,33895336,44915235,51315460,32274392,39801351,8055981,41245325,16424199,48774913,3233569,8099994,26734892,5267545,9603598,93057697,176257,72725103,43506672,18001617,59177718,75986488,42947632,67124282,18699206,33565483,61728685,36685023,44664587,73109997,57020481,90272749,30463802,83948335,91938191,31491938,33201905,26397786,82779622,93359396,74743862,11581181,69579137,5832946,62496012,59981773,94595668,90061527,86301513,70541760,82532312,28796059,16405341,4434662,62490109,88653118,20645197,87598888,57241521,91141395,92692978,19891772,86798033,23740167,47298834,14349098,92530431,80251430,41092172,96852321,91240048,43933006,23432750,55669657,26776922,81805959,63152504,53393358,59197747,34044787,76315420,99333375,55615885,45667668,63628376,28787861,9860968,14045383,79738755,62115552,95010552,10453030,34432810,37659250,92867155,12303248,96906410,89217461,68702632,70367851,16567550,92071990,47893286,60393039,75135448,21289531,27185644,73021291,23194618,71965942,16445503,48658605,17539625,21533347,12024238,27187213,2917920,85117092,88904910,24953498,45075471,13468268,56484900,15163258,26744917,69848388,21397057,37280276,10597197,71469330,84684495,37560164,88698958,70782102,36139942,45428665,97057187,57163802,79322415,3487592 +72725103,71657078,7104732,73031054,30694952,9623492,87598888,4668450,29959549,35192533,44479073,44784505,49597667,59109400,98462867,26744917,95957797,26998766,31126490,52293995,9398733,53842979,16971929,10309525,32699744,44550764,69355476,27665211,85242963,6793819,65074535,91831487,40984766,62452034,62496012,75543508,526217,90272749,20122224,54762643,65047700,1204161,15536795,75153252,83210802,38494874,33123618,22108665,47213183,47090124,7687278,93562257,34432810,89214616,10358899,44889423,62936963,1022677,16019925,61982238,96193415,33249630,45790169,29834512,89419466,59318837,38008118,34698428,17365188,88047921,99549373,34295794,1569515,67281495,29029316,8733068,30569392,99297164,7685448,66885828,70596786,45919976,18833224,36812683,16380211,73235980,13173644,30139692,38061439,77377183,99524975,36505482,60581278,16424199,50567636,20140249,69579137,79922758,57803235,17068582,99658235,35092039,82052050,80251430,40356877,14947650,62693428,66428795,24314885,96318094,415901,65038678,99333375,65715134,3233084,92398073,45075471,70782102,4806458,37620363,48360198,73124510,64098930,12571310,61373987,3880712,90457870,17957593,8055981,26063929,6153406,67793644,62552524,72373496,26863229,63967300,72274002,55753905,88444207,66322959,96906410,23848565,71965942,19939935,68694897,4099191,45990383,84406788,90090964,247198,44481640,49882705,61859143,64848072,6871053,15015906,37192445,74357852,91990218,98739783,54517921,79821897,17976208,31733363,36685023,39986008,39553046,98948034,44983451,12348118,41380093,37891451,81805959,61623915,59501487,19486173,14326617,82979980,51149804,27325428,55247455,1788101,25842078,6521313,57658654,92033260,83083131,93790285,36780454,15064655,51588015,51472275,38365584,47298834,71333116,80316608,12416768,52060076,4119747,57241521,53802686,30764367,59177718,16445503,10597197,61271144,6986898,12664567,40781449,48893685,48088883,83269727,68702632,44348328,31904591,51047803,69255765,65851721,7182642,68204242,32159704,30543215,62740044,94090109,91664334,33628349,17386996,4064751,71316369,19457589,1239555,15535065,26493119,39801351,33553959,66045587,37280276,97783876,98371444,36930650,14363867,9886593,90158785,61728685,70800879,85711894,37183543,98648327,66832478,20885148,5111370,65017137,45407418,33304202,9860968,90013093,30787683,321665,50806615,30163921,36753250,8128637,91938191,18504197,97281847,80555751,95726235,99125126,63628376,81898046,3088684,69641513,61263068,93933709,55770687,80014588,23569917,8115266,30218878,44245960,30653863,70727211,43322743,34044787,84187166,54427233,38658347,70367851,7955293,78766358,59197747,33201905,11543098,28734791,50188404,77272628,92692978,62232211,64602895,53084256,49236559,4515343,95395112,82726008,48774913,83533741,92554120,44177011,22450468,85023028,38341669,59371804,65338021,60102965,99515901,72738685,78884452,14626618,93270084,26292919,74743862,91802888,37166608,168541,10366309,5970607,18663507,78785507,6497830,74441448,32426752,71300104,99604946,4095116,91281584,66611759,26392416,66663942,69605283,75777973,24591705,44844121,9829782,56955985,68102437,33797252,89499542,28851716,82427263,60430369,54232247,85116755,45848907,92998591,6808825,99971982,61741594,33431960,87720882,60955663,86118021,58180653,508198,43376279,89078848,17016156,15948937,76315420,17058722,10264691,93359396,77187825,9188443,56153345,70004753,18466635,22997281,37659250,67030811,72732205,68644627,20681921,56515456,23194618,76330843,79738755,4508700,61141874,69848388,21673260,78589145,45428665,32250045,70541760,55602660,59910624,77284619,44846932,73786944,18699206,89996536,68875490,9058407,82339363,53393358,45381876,69136837,5822202,78549759,84684495,76960303,4985896,86543538,75986488,23740167,30463802,29635537,64055960,30998561,3773993,2717150,24915585,57248122,29932657,96852321,86001008,9259676,569864,72777973,77312810,36135,4798568,21993752,669105,81677380,8373289,2331773,8651647,29188588,24733232,57359924,71522968,86460488,45995325,56531125,32058615,96726697,29065964,36580610,79880247,44473167,2917920,91727510,42947632,28787861,74724075,79942022,40197395,92541302,99965001,4434662,11365791,81274566,30771409,6038457,39847321,42199455,22129328,7300047,96709982,45617087,14045383,47792865,5832946,2891150,83368048,1250437,63152504,33565483,99021067,79657802,34236719,99861373,56484900,46851987,10453030,93515664,19272365,92353856,51426311,84002370,76825057,83789391,75407347,73222868,88698958,14093520,13231279,33336148,54058788,112651,79191827,38645117,51466049,27185644,75135448,63059024,39171895,15163258,84840549,37435892,92867155,58751351,4069912,93053405,26114953,70420215,45306070,22942635,40677414,54987042,92215320,84293052,86821229,54663246,37739481,26776922,94516935,76671482,66667729,65880522,14731700,16099750,40865610,43933006,67451935,34497327,37224844,74110882,54606384,78909561,11161731,5482538,44060493,15075176,62430984,64087743,17539625,87710366,61960400,65271999,89699445,57961282,28550822,93057697,3509435,24058273,29510992,57163802,55189057,68824981,77898274,10649306,8913721,39201414,22721500,18783702,55470718,97561745,62051033,89637706,29819940,89046466,4204661,63015256,26102057,51590803,77694700,8129978,65454636,42307449,53666583,19376156,18806856,66319530,77413012,1197320,45794415,72495719,94711842,41245325,45237957,42782652,7502255,45996863,9860195,24619760,27625735,54263880,96735716,77787724,3487592,10961421,81172706,9257405,32161669,21001913,73109997,50702367,77620120,8729146,84127901,30090481,33895336,4787945,53648154,73168565,84349107,19101477,45667668,94360702,13470059,62490109,74452589,9175338,67084351,95010552,67227442,30891921,76434144,98130363,7646095,74614639,8807940,28796059,41442762,69697787,78561158,22435353,9603598,29994197,51464002,26397786,9575954,47738185,39373729,83948335,824872,62357986,26275734,50668599,48658605,57020481,72019362,82532312,75229462,8696647,54199160,49641577,46870723,83150534,79322415,73392814,18411915,68128438,50007421,83155442,27041967,47893286,6505939,32151165,97011160,53547802,3183975,42073124,52204879,92803766,30395570,59981773,55669657,23110625,61897582,60106217,92692283,68939068,2208785,44842615,73617245,63506281,56424103,11581181,68000591,35456853,17385531,78300864,88653118,55615885,6525948,62762857,26507214,1431742,62803858,26139110,58224549,89811711,47708850,73021291,42967683,88904910,72793444,89217461,68316156,61815223,82897371,6111563,24826575,34946859,98653983,16097038,58208470,20642888,16595436,87160386,48892201,8791066,71920426,91240048,47887470,6221471,80246713,12303248,36312813,16387575,57240218,84166196,36845587,52613508,79806380,55143724,85571389,81853704,52261574,44627776,22879907,8099994,35512853,69786756,749283,22766820,66271566,86301513,92071990,77300457,66250369,43152977,53888755,61859581,54868730,3633375,7517032,92604458,20568363,37675718,36189527,83302115,20002147,26664538,98943869,16405341,23134715,63372756,92530431,67513640,32274392,66137019,18131876,35996293,7423788,94911072,20765474,1375023,31727379,56473732,176257,51315460,74137926,2204165,89804152,53632373,78218845,82651278,51715482,14220886,19891772,81774825,83133790,86022504,65081429,3235882,20645197,99917599,97940276,57393458,43357947,42644903,12024238,76703609,6157724,31161687,90004325,16567550,23432750,17857111,27411561,68110330,85117092,26734892,4091162,33061250,20836893,36933038,30366150,43506672,14723410,17764950,41092172,40152546,48260151,22113133,70036438,46540998,97641116,20867149,34698463,49328608,34667286,76170907,72357096,7011964,9599614,15902805,32590267,55960386,71083565,7919588,92787493,41092102,5267545,62428472,13348726,44915235,97057187,5073754,82886381,59329511,47361209,94076128,40781854,95581843,75820087,67124282,3233569,16054533,66903004,14349098,17037369,33935899,29401781,24168411,21397057,40027975,62115552,61712234,41481685,45049308,60393039,13819358,3294781,91255408,21070110,88251446,874791,16684829,4978543,71469330,36139942,17146629,91141395,78602717,72278539,67031644,97379790,94539824,37560164,91957544,49598724,75128745,60176618,40686254,48673079,43045786,59614746,7066775,36808486,94595668,77301523,11923835,84904436,30811010,85695762,76540481,95290172,20486294,54014062,36396314,99226875,8634541,95251277,44847298,61928316,2607799,66116458,99729357,81855445,37957788,37501808,67474219,75052463,15148031,55850790,29516592,42237907,33699435,79136082,48675329,99224392,82178706,72358170,17727650,68816413,29466406,21919959,28928175,7229550,82327024,40534591,88130087,68041839,4199704,1936762,82779622,22200378,51507425,38022834,31491938,13468268,72614359,90654836,21533347,27187213,77072625,22405120,33237508,21289531,6819644,83651211,25636669,13862149,99690194,59405277,49271185,19898053,38510840,72238278,43501211,17081350,34493392,42692881,93566986,86798033,44664587,70372191,38256849,38009615,48395186,58749,65275241,24953498,18001617,86242799,36468541,90061527,90310261,96420429,56461322,17894977,5640302,64157906,11757872,83747892 +42073124,4787945,24591705,15535065,52060076,17068582,53666583,99604946,29029316,45794415,80555751,67030811,17016156,5111370,14326617,8913721,92398073,82886381,37183543,94595668,18783702,52293995,93933709,1197320,49597667,112651,35996293,19101477,2917920,5822202,36580610,68816413,22766820,40027975,526217,74137926,61859143,48774913,5970607,46870723,48360198,47090124,8696647,65271999,73168565,15064655,23194618,77898274,32161669,11923835,66045587,13231279,50007421,30395570,23848565,89214616,80251430,61897582,62232211,72278539,16097038,99021067,33201905,86022504,35092039,50806615,98130363,67793644,97783876,16971929,14045383,92353856,12571310,17976208,98462867,44842615,45996863,26392416,55770687,415901,14349098,62115552,168541,53632373,569864,50188404,36808486,97641116,17727650,53842979,85711894,68644627,89078848,77620120,47298834,66611759,70367851,19891772,91957544,85242963,22997281,1022677,61982238,47708850,39171895,2717150,65715134,48893685,64157906,40984766,29994197,77413012,17539625,96318094,4064751,45306070,76703609,43152977,12416768,70596786,4434662,26102057,7919588,56515456,247198,5640302,92033260,91802888,26292919,56424103,77312810,81898046,91664334,96726697,96420429,33431960,53084256,86001008,67281495,97940276,6808825,22942635,72495719,89811711,84840549,30090481,3509435,96735716,38008118,60430369,63506281,2331773,62430984,61623915,55753905,8651647,81855445,26863229,92554120,6153406,62740044,18833224,19376156,82979980,38494874,59501487,51047803,75543508,9603598,16380211,29819940,64098930,82339363,16424199,34497327,76170907,34432810,90004325,15148031,28851716,43322743,9398733,45617087,26744917,58208470,19939935,14626618,70036438,74724075,43501211,30998561,39801351,50567636,71657078,37957788,57359924,10366309,55189057,54232247,23740167,93053405,20486294,33565483,29635537,40686254,68204242,34295794,61263068,57803235,78766358,87160386,30764367,72793444,99658235,3487592,51507425,73031054,66667729,38061439,26114953,11161731,14731700,86242799,6986898,62357986,77300457,34698463,19486173,62490109,41481685,30694952,26734892,38645117,18411915,31126490,83210802,48395186,67451935,32699744,54427233,57248122,45428665,79136082,97281847,27665211,14947650,70372191,26397786,57240218,7011964,80014588,7104732,75229462,72738685,68102437,54058788,71333116,11757872,95290172,47792865,50668599,68875490,59197747,37192445,63152504,90654836,44473167,98943869,91990218,77284619,81677380,19457589,68939068,85023028,26493119,48892201,21070110,5482538,49598724,38341669,83155442,92071990,75135448,62452034,52261574,1204161,45995325,22450468,70004753,94711842,9623492,78785507,30463802,30569392,65081429,13348726,31727379,12348118,93790285,82779622,83302115,874791,92803766,6793819,51466049,11365791,73617245,29188588,36780454,7517032,20836893,88698958,60106217,4069912,73235980,24058273,56473732,7685448,17365188,61728685,508198,1239555,10264691,91831487,2208785,22200378,67031644,66885828,76540481,44844121,99965001,55247455,66903004,7955293,72358170,99861373,54762643,47213183,84904436,95010552,36812683,79922758,4119747,61141874,69641513,84187166,54199160,3088684,54606384,9259676,27411561,78218845,70420215,4099191,38658347,57393458,29932657,19898053,89996536,73124510,21673260,16019925,59910624,68000591,93359396,66428795,51149804,48675329,75820087,9575954,32426752,8807940,92998591,44664587,5832946,55602660,88444207,86798033,17385531,23569917,72019362,88904910,13470059,24826575,30771409,98948034,55960386,37435892,99549373,37675718,50702367,71083565,69579137,44245960,9058407,17957593,92541302,12664567,70727211,43357947,22108665,79880247,86821229,26664538,35192533,38365584,15163258,96709982,39986008,14093520,77694700,81805959,90013093,55143724,37560164,49271185,90457870,26063929,84002370,83083131,95726235,94076128,3233084,79322415,4508700,33304202,31904591,35456853,45667668,84127901,90272749,42967683,9188443,66250369,90061527,31491938,45919976,99297164,4806458,51590803,95395112,176257,1569515,33336148,67124282,64602895,86301513,23110625,63628376,13468268,96852321,15948937,30218878,61960400,69136837,75986488,63372756,98371444,6505939,1375023,7300047,60955663,45407418,33628349,63967300,36312813,16054533,77787724,36396314,77272628,32590267,22879907,40677414,20642888,86460488,72777973,12024238,10453030,59371804,36139942,97011160,75153252,74614639,42644903,59109400,82897371,99125126,57163802,21993752,47361209,89637706,82726008,17146629,31733363,26507214,78589145,96906410,13862149,11581181,53547802,57658654,53648154,44915235,7423788,69355476,65338021,40865610,15902805,49641577,51464002,91255408,28928175,77072625,67084351,99224392,30787683,6157724,22129328,28550822,9829782,53802686,43506672,65454636,36135,8055981,37166608,91938191,93562257,61271144,92692283,22405120,16445503,66322959,99226875,7687278,73392814,93057697,64848072,26998766,64087743,20122224,36753250,85116755,7646095,10309525,17081350,93566986,90310261,4515343,89419466,8128637,37501808,25636669,8634541,84166196,19272365,48673079,66271566,74452589,72725103,36930650,54263880,84293052,46851987,58751351,46540998,94911072,75777973,28787861,33895336,5073754,16595436,1936762,83651211,41380093,80316608,34493392,61859581,70800879,8099994,30139692,79738755,61373987,29466406,1431742,71300104,20645197,69848388,83150534,57961282,99971982,20681921,89046466,22113133,43376279,3880712,61741594,94090109,77187825,56955985,38009615,37620363,54014062,37739481,76825057,78561158,82052050,33553959,89499542,82532312,23432750,44177011,34044787,92215320,39553046,76960303,34667286,34698428,29510992,36933038,3773993,30811010,83789391,71920426,3235882,95581843,88251446,94360702,87710366,39847321,89699445,60102965,39373729,76330843,81853704,17857111,94539824,40534591,17764950,45990383,98648327,55615885,57241521,26139110,47893286,81274566,33123618,18806856,40152546,79821897,30366150,47887470,18663507,18001617,30653863,72732205,76434144,97561745,54663246,69255765,72373496,61712234,52204879,29959549,55470718,72238278,21533347,88653118,81172706,44348328,20765474,74110882,40197395,45237957,44847298,4204661,73222868,29834512,62496012,20140249,34236719,44479073,32250045,70782102,24619760,36189527,49236559,42692881,27185644,62051033,54868730,40781449,29516592,23134715,45049308,44627776,7229550,9860195,94516935,73109997,69786756,9886593,73786944,17037369,78884452,89804152,24733232,98739783,72274002,33249630,27041967,82178706,99524975,62936963,321665,79657802,43045786,3294781,16405341,8373289,64055960,6111563,48658605,9599614,37659250,65851721,68694897,68110330,18131876,92867155,99515901,43933006,10597197,3633375,1250437,72614359,61928316,45075471,41092172,16387575,59329511,62803858,77377183,68316156,67474219,6871053,86543538,79942022,44889423,48260151,4985896,51588015,22721500,27325428,65038678,72357096,79191827,49882705,33061250,87720882,51715482,11543098,2607799,9175338,52613508,17894977,78602717,51472275,99333375,44481640,21001913,79806380,84684495,83533741,41442762,9257405,95957797,8115266,6819644,30891921,21919959,59614746,68824981,3233569,66319530,66832478,65017137,56461322,6525948,49328608,53393358,75052463,4199704,4095116,20885148,15536795,36845587,83747892,14220886,33237508,33797252,29401781,93515664,41092102,37224844,68128438,63015256,15075176,62693428,85695762,28734791,42307449,25842078,8129978,10358899,34946859,44550764,55669657,96193415,4091162,88047921,36685023,29065964,26275734,91727510,8733068,44983451,44846932,75128745,13173644,57020481,10649306,75407347,92692978,45790169,39201414,59981773,89217461,10961421,38510840,85571389,27625735,73021291,80246713,74441448,63059024,97379790,6521313,36505482,26776922,33935899,24168411,40781854,35512853,99917599,71965942,60581278,6497830,69697787,7502255,4668450,824872,41245325,30163921,84406788,91281584,95251277,82427263,30543215,58749,40356877,99729357,45848907,84349107,38022834,6221471,62552524,3183975,16684829,76671482,42782652,91141395,2204165,74357852,58180653,31161687,59318837,78300864,32151165,32058615,99690194,42947632,78909561,98653983,68702632,67513640,65275241,59177718,24953498,58224549,32274392,20002147,62428472,66663942,20568363,71316369,6038457,20867149,17058722,78549759,97057187,48088883,83269727,18466635,13819358,51315460,54517921,61815223,92530431,74743862,4978543,8729146,1788101,81774825,62762857,5267545,4798568,65880522,65047700,82327024,83368048,71469330,71522968,56531125,24314885,83133790,14723410,42199455,12303248,66116458,22435353,2891150,17386996,21397057,24915585,60393039,65074535,91240048,70541760,56484900,69605283,42237907,38256849,90090964,18699206,37891451,45381876,44060493,87598888,86118021,47738185,82651278,749283,7182642,32159704,7066775,55850790,90158785,77301523,67227442,51426311,53888755,59405277,85117092,88130087,16567550,92787493,669105,36468541,16099750,37280276,8791066,68041839,18504197,92604458,44784505,56153345,66137019,28796059,14363867,33699435,54987042,21289531,15015906,9860968,83948335,93270084,76315420,27187213,60176618 +82897371,84187166,55615885,43322743,66832478,53547802,76330843,8807940,92554120,68939068,48360198,168541,8099994,24591705,67513640,75153252,45667668,28851716,4064751,29029316,84293052,37183543,59177718,67451935,92692283,51715482,64157906,62452034,3773993,38061439,68816413,47090124,55247455,48260151,99965001,62496012,12348118,112651,50188404,415901,33201905,82979980,85571389,26744917,4099191,1431742,70420215,98462867,68644627,33336148,27665211,69786756,8913721,45075471,53666583,46870723,92803766,14349098,54606384,70800879,23432750,77377183,15535065,43933006,39801351,3233084,15948937,63372756,99524975,16971929,82532312,34044787,58751351,669105,7955293,53842979,62693428,2717150,26392416,93515664,44889423,34432810,39847321,16445503,94911072,82726008,29466406,176257,6986898,61859143,62051033,77413012,96726697,17386996,40197395,50007421,21919959,23848565,4787945,61928316,8651647,76434144,71316369,30366150,70367851,83651211,24058273,29819940,9257405,66322959,69697787,32699744,18783702,73786944,75986488,874791,44983451,3294781,70541760,14947650,89046466,69848388,85116755,51047803,40984766,23110625,39986008,22108665,91664334,34295794,67084351,64848072,59910624,29834512,52613508,81274566,84840549,35092039,92033260,97379790,68000591,66611759,1197320,92353856,33553959,26863229,7687278,93933709,73168565,72614359,62357986,9599614,67227442,40781449,34698428,19939935,23194618,4119747,66045587,36505482,75777973,10309525,54014062,55753905,569864,91957544,4806458,77312810,63967300,88653118,36580610,99861373,79821897,39171895,62936963,21070110,33249630,38008118,92398073,84904436,89996536,47708850,13348726,23569917,7517032,12571310,9398733,95957797,526217,49271185,53648154,44479073,34497327,16405341,30569392,81805959,70596786,57248122,8696647,26998766,61141874,45407418,76960303,77898274,94360702,36780454,75229462,92541302,89419466,89078848,19898053,73617245,87160386,70036438,91938191,85711894,73124510,80014588,63015256,96193415,19457589,10366309,99658235,66428795,17539625,1569515,16595436,247198,57961282,66319530,31126490,5832946,1022677,44842615,79136082,99515901,65454636,83210802,44481640,68316156,61263068,80251430,93790285,93359396,29635537,73031054,10961421,22405120,26063929,11161731,71333116,27325428,95010552,63059024,35456853,41245325,64602895,89214616,96906410,61815223,32161669,51464002,55770687,43152977,15163258,50668599,82178706,36808486,78766358,54427233,71522968,27411561,60106217,15064655,63506281,56473732,54762643,77072625,54987042,85242963,59371804,99549373,47361209,13862149,69579137,85117092,37620363,83155442,91240048,8115266,49641577,54663246,30764367,76671482,65880522,35192533,84127901,9259676,19272365,68875490,6157724,16387575,19101477,33628349,57240218,50702367,37280276,30771409,17976208,36845587,7423788,75820087,94090109,20765474,57241521,11543098,40534591,68204242,65017137,45617087,14731700,99125126,29065964,57803235,45428665,37957788,61741594,8733068,62232211,58180653,75052463,71469330,77272628,42692881,22997281,68102437,12416768,47792865,18699206,47298834,26776922,36753250,62740044,74441448,26292919,8128637,55189057,38494874,18131876,40677414,20486294,54199160,65038678,65271999,83302115,72373496,22721500,38365584,40356877,50567636,13470059,1375023,56515456,98943869,4668450,47213183,18833224,44915235,36312813,18466635,97561745,56424103,70727211,81853704,1204161,67474219,90310261,14045383,41092102,72274002,77284619,16567550,72358170,60102965,30139692,9058407,17068582,33123618,8634541,99297164,51149804,51466049,7011964,48658605,26139110,30543215,62490109,59318837,86821229,88904910,37501808,88047921,19891772,59614746,43506672,53802686,32058615,35996293,43376279,90654836,17037369,15536795,38022834,83789391,38341669,59109400,62803858,89811711,42947632,84002370,15015906,96735716,33565483,93566986,56461322,33304202,37891451,7300047,36930650,45995325,85023028,28796059,98653983,40865610,15075176,90061527,36396314,45990383,72495719,78785507,33237508,37675718,92787493,97783876,3487592,6153406,61373987,93053405,4798568,42782652,90272749,79191827,87598888,7646095,12664567,33797252,74614639,9829782,28550822,80555751,95726235,36812683,65338021,96318094,83083131,59197747,3880712,67281495,86001008,40027975,42307449,20140249,55602660,5482538,81677380,79922758,65047700,76540481,27041967,24168411,4204661,66667729,91255408,30163921,68694897,82427263,63628376,2917920,48893685,16380211,28787861,11581181,9603598,19486173,48673079,69355476,47738185,321665,83533741,68824981,26493119,96852321,44473167,71300104,88130087,91990218,61897582,6521313,508198,42644903,2204165,3235882,61960400,66663942,25842078,49328608,27625735,9860968,7182642,29994197,31904591,66250369,49597667,68128438,72357096,86022504,30694952,24733232,98739783,99224392,74110882,84349107,22129328,99690194,6819644,94711842,71083565,90004325,4069912,30090481,7502255,30891921,48892201,86460488,90457870,45919976,33431960,76825057,44846932,79806380,9886593,61712234,42073124,65715134,32426752,18504197,84406788,34493392,78561158,56484900,23740167,10649306,77620120,7685448,33895336,99021067,31733363,99729357,45790169,44177011,64098930,84166196,88698958,21993752,99333375,48774913,70004753,4091162,4515343,20642888,17764950,81855445,22766820,20122224,73021291,15148031,66885828,38645117,29188588,37739481,75543508,65074535,78589145,77300457,65851721,60581278,41481685,66271566,13231279,53084256,26734892,17894977,69255765,75407347,24826575,99226875,31491938,5111370,38256849,51507425,29932657,3233569,17058722,44844121,71657078,60430369,54517921,2208785,30811010,36685023,61728685,9623492,24619760,80316608,53632373,54058788,14363867,38009615,76315420,52204879,45237957,63152504,94539824,95581843,17957593,17365188,45794415,48088883,26664538,97641116,41380093,28928175,9188443,11923835,13173644,24314885,55143724,6793819,4095116,29959549,94516935,95290172,62762857,14723410,72278539,86798033,45996863,89637706,23134715,22942635,93057697,30998561,25636669,53888755,89804152,18806856,97281847,26397786,15902805,98130363,7229550,44627776,60176618,65275241,93562257,73392814,46540998,20867149,16019925,58224549,69605283,9175338,29401781,55960386,79942022,6505939,71920426,6808825,77787724,67793644,84684495,39201414,11757872,17727650,20836893,6111563,27185644,7919588,44550764,44784505,22879907,96420429,11365791,72793444,49236559,80246713,17385531,67030811,55669657,9575954,76170907,90090964,78549759,64087743,70372191,16054533,78602717,69641513,67124282,83368048,22435353,36189527,17146629,14326617,20885148,96709982,30787683,24953498,51588015,74724075,3509435,30463802,89699445,45848907,58208470,4199704,78218845,31727379,86118021,1936762,17016156,74137926,45049308,67031644,81774825,18663507,26507214,36468541,5640302,61859581,30653863,4985896,52293995,81172706,14093520,30218878,37192445,43501211,94076128,51472275,10358899,32590267,93270084,62430984,44664587,91802888,14220886,1788101,69136837,56955985,59329511,70782102,72238278,76703609,83269727,92998591,73109997,57359924,40152546,45381876,9860195,5822202,59501487,18411915,10597197,97011160,16424199,5267545,92604458,39553046,19376156,43357947,3088684,31161687,44245960,5970607,2331773,749283,92692978,91727510,88444207,66903004,37560164,51590803,99971982,83133790,58749,78909561,68041839,14626618,40686254,95395112,50806615,74357852,78884452,36135,32151165,61271144,92215320,37435892,7104732,47893286,79880247,89499542,44060493,57020481,97057187,99604946,41442762,72777973,86242799,73235980,87710366,52261574,62428472,74743862,83150534,36139942,32159704,26275734,81898046,91281584,21533347,42199455,48675329,74452589,37166608,17081350,36933038,75128745,16097038,6871053,21289531,6497830,12303248,77694700,79657802,38658347,51426311,32250045,97940276,16099750,66137019,34236719,82651278,87720882,61623915,2607799,52060076,35512853,91831487,6221471,98948034,8373289,71965942,98371444,82779622,8129978,72725103,33935899,82052050,62552524,98648327,54263880,99917599,55850790,56531125,20681921,45306070,13819358,83948335,75135448,16684829,39373729,53393358,44348328,30395570,47887470,10453030,59405277,8055981,33061250,24915585,18001617,66116458,49598724,37224844,20568363,86301513,51315460,17857111,37659250,27187213,85695762,79322415,78300864,61982238,21673260,1239555,34946859,92867155,59981773,82886381,13468268,12024238,20002147,46851987,68110330,42237907,54232247,68702632,34667286,44847298,4978543,22450468,88251446,83747892,29516592,62115552,824872,90013093,4434662,6038457,3633375,72738685,28734791,77301523,6525948,43045786,32274392,26102057,10264691,73222868,20645197,1250437,77187825,72732205,57658654,92071990,56153345,34698463,65081429,8791066,42967683,60955663,5073754,82327024,57163802,7066775,55470718,3183975,64055960,38510840,22200378,26114953,8729146,89217461,4508700,21001913,48395186,54868730,94595668,40781854,90158785,86543538,79738755,2891150,72019362,82339363,21397057,49882705,33699435,95251277,22113133,91141395,41092172,29510992,60393039,92530431,57393458 +99515901,96318094,48893685,98462867,68204242,4119747,68644627,47090124,12348118,18699206,85116755,4787945,62496012,65338021,55189057,53842979,34497327,8807940,33797252,55770687,15535065,61623915,68816413,44842615,96709982,63628376,96193415,48360198,83210802,14093520,99524975,9623492,26664538,30366150,33249630,20486294,35996293,95957797,43933006,98371444,7011964,24058273,72274002,6153406,80555751,99965001,81855445,29834512,43501211,45617087,71469330,69355476,80251430,84349107,66137019,5970607,415901,80246713,4515343,6871053,92554120,11581181,16054533,65275241,15064655,20122224,45237957,82339363,35092039,42967683,99297164,27411561,82178706,37183543,49236559,34698428,20836893,65017137,86001008,34946859,56424103,7502255,26139110,55143724,68000591,42947632,4099191,59318837,51466049,74724075,50188404,77413012,42782652,32699744,34295794,26063929,48658605,98130363,55669657,76330843,69136837,47361209,10366309,78561158,73222868,27665211,77787724,14220886,31904591,76434144,67031644,56473732,71657078,73392814,14947650,61982238,94090109,72373496,96906410,64055960,59371804,86543538,62936963,40984766,14349098,70004753,78785507,44481640,19101477,29959549,63506281,7300047,10649306,22997281,22108665,18663507,16405341,38658347,65715134,56153345,17068582,21070110,91938191,10961421,28550822,38365584,88047921,18783702,4806458,96726697,40781449,51472275,66250369,36753250,18131876,33123618,6793819,24591705,69605283,21993752,42237907,14045383,68128438,92033260,30395570,91664334,75543508,43152977,22129328,71083565,61815223,61263068,77377183,58751351,10309525,34493392,65081429,569864,33699435,7423788,53084256,97783876,47708850,67793644,78589145,44847298,68939068,9188443,40534591,40865610,3294781,54606384,7517032,43322743,10597197,62762857,14363867,29635537,44664587,85242963,93566986,80316608,44060493,30694952,29994197,84840549,84187166,78884452,70420215,94595668,33336148,61859143,16019925,95726235,52613508,59109400,10358899,83789391,33553959,93933709,50567636,19898053,37620363,37224844,63372756,35192533,45790169,51047803,20140249,7104732,24733232,97641116,77898274,30764367,2204165,23569917,87598888,53547802,66322959,86460488,68102437,9257405,60581278,16567550,61960400,44844121,96735716,20765474,60430369,3233569,91727510,3880712,69579137,65047700,45667668,9398733,1197320,508198,8634541,36505482,44479073,41481685,26397786,73617245,56531125,71333116,90090964,67030811,13862149,91990218,52060076,83302115,12416768,24619760,9259676,66663942,45428665,50007421,77312810,18833224,75777973,64848072,57240218,17764950,40197395,45919976,27325428,18504197,73031054,44177011,62740044,63967300,84904436,41092102,58749,84684495,21289531,92398073,54868730,1431742,57359924,6521313,54427233,5482538,92787493,33628349,99690194,32274392,14326617,38494874,99549373,66611759,14626618,26392416,67281495,33304202,43506672,71300104,90457870,51464002,33237508,21919959,14723410,66428795,22879907,70541760,95395112,81898046,42073124,47298834,32426752,64098930,5640302,66667729,76825057,321665,66116458,44889423,55850790,38008118,51715482,1204161,99125126,8099994,29065964,67124282,99861373,48774913,36685023,44784505,20002147,86242799,29819940,44983451,60102965,91831487,88130087,77620120,55247455,39171895,75052463,88251446,49328608,57803235,66885828,72738685,6986898,93359396,89419466,3633375,49641577,1788101,62232211,36580610,15148031,3183975,24168411,13468268,98739783,669105,83155442,29029316,59177718,68316156,74137926,17957593,82979980,7685448,62803858,73124510,21001913,22721500,7919588,168541,26776922,47792865,94516935,69848388,30139692,74614639,40152546,94360702,15015906,65074535,39847321,55753905,79821897,30891921,79738755,30787683,45407418,36468541,76540481,4091162,67451935,15163258,5111370,26863229,19891772,30543215,3233084,41092172,92353856,82427263,53666583,14731700,70800879,39801351,33201905,87160386,26275734,62490109,52204879,54014062,19486173,89811711,91802888,33431960,83150534,90272749,26507214,63059024,44550764,86022504,13348726,18806856,8696647,88698958,38061439,13173644,30569392,66832478,89996536,20681921,23432750,91240048,25842078,13470059,78766358,50702367,8115266,20642888,62693428,70372191,82726008,58208470,46851987,79942022,37957788,31126490,64087743,32250045,77694700,3509435,11365791,824872,99021067,95290172,90013093,97561745,6505939,61928316,88653118,11923835,79191827,76170907,23134715,98943869,16380211,95581843,4064751,11543098,83533741,89214616,874791,93057697,5267545,97379790,52261574,45306070,64157906,69786756,18466635,17146629,28787861,4204661,84002370,89804152,24953498,57248122,16971929,62552524,27041967,67227442,80014588,59614746,69641513,65038678,29466406,92541302,72495719,56515456,3088684,78602717,21673260,63015256,68824981,16595436,7646095,8651647,66319530,30771409,24826575,61373987,93053405,93562257,39553046,44627776,1375023,55960386,70367851,62357986,5822202,67084351,2331773,86798033,247198,81274566,95010552,50668599,44473167,15902805,21533347,43376279,79806380,78218845,82897371,70727211,70036438,16097038,59405277,48260151,29510992,9829782,74441448,65851721,29188588,96852321,48088883,23848565,39373729,77072625,29516592,99333375,40356877,40677414,28928175,59197747,48892201,26114953,72777973,37166608,22405120,8055981,72793444,93790285,176257,47893286,92692283,83651211,50806615,2891150,28734791,35456853,41380093,79922758,6808825,36780454,17386996,77272628,6111563,55470718,81805959,36933038,54987042,55615885,66903004,61897582,85571389,39201414,11161731,59329511,54199160,37659250,45075471,89078848,85711894,8373289,57163802,1022677,19939935,45990383,61141874,66271566,81172706,68041839,40686254,30090481,46540998,32161669,55602660,28796059,29932657,32590267,10264691,7955293,48673079,45996863,37501808,60106217,99658235,47213183,90310261,1936762,4668450,62430984,36312813,72732205,2917920,2717150,30218878,8128637,25636669,9860968,15948937,71522968,68875490,3487592,84293052,75135448,12571310,9058407,83269727,22942635,77284619,60393039,98653983,526217,5073754,92692978,17539625,85695762,81853704,63152504,34698463,85117092,45995325,31733363,39986008,91255408,70596786,93270084,32159704,73235980,99917599,65271999,112651,87720882,8129978,59501487,36930650,40781854,74743862,3773993,38645117,26292919,54263880,17385531,53393358,36396314,69255765,75153252,8729146,66045587,86118021,57393458,89637706,82886381,73021291,73109997,58224549,34236719,17976208,77300457,20867149,8733068,87710366,4069912,13819358,71920426,74110882,51588015,34432810,73168565,92803766,23194618,2208785,62452034,37675718,78909561,72614359,60176618,57961282,72358170,86301513,56461322,51590803,64602895,36808486,94711842,82651278,51426311,9175338,31727379,73786944,36139942,98648327,92071990,49271185,17365188,7229550,61859581,22200378,83948335,19272365,30811010,8913721,9575954,17727650,62428472,6157724,44245960,57241521,27185644,16424199,67513640,9860195,30998561,12664567,83368048,57658654,36812683,42692881,82327024,89046466,84127901,54663246,7687278,9886593,72725103,97011160,82532312,23110625,35512853,72019362,38510840,99604946,9599614,91957544,54517921,83133790,99729357,97057187,37739481,86821229,91281584,68694897,4095116,88444207,75128745,26744917,61712234,31161687,51149804,41245325,40027975,49597667,79657802,4434662,38009615,37192445,79880247,99226875,36189527,42307449,52293995,96420429,37560164,6038457,33895336,45848907,75820087,9603598,7182642,30653863,53888755,17894977,6525948,19376156,48395186,54058788,20885148,97281847,37891451,44846932,22766820,79136082,47738185,10453030,88904910,90061527,78549759,16445503,89499542,46870723,26102057,84166196,53648154,69697787,4199704,99971982,23740167,85023028,33935899,1569515,43045786,16684829,17037369,42199455,17857111,72357096,47887470,31491938,38022834,4978543,58180653,81677380,28851716,13231279,7066775,84406788,75229462,4798568,6819644,99224392,71965942,30163921,44348328,749283,89699445,90004325,93515664,22450468,92998591,4508700,24314885,65880522,94911072,59981773,76703609,12303248,15536795,51315460,6497830,5832946,27625735,17016156,37280276,92530431,18001617,19457589,59910624,33565483,61728685,38256849,32058615,82052050,41442762,20645197,76315420,22113133,61741594,76671482,60955663,62051033,48675329,38341669,94539824,72278539,91141395,32151165,26998766,72238278,54762643,26734892,15075176,4985896,6221471,1250437,98948034,34667286,1239555,70782102,3235882,65454636,92215320,76960303,53632373,97940276,92604458,16099750,94076128,12024238,42644903,75986488,62115552,68702632,30463802,89217461,17058722,36135,75407347,56955985,45794415,29401781,79322415,44915235,49598724,49882705,77301523,22435353,53802686,71316369,95251277,61271144,74357852,24915585,92867155,45049308,81774825,51507425,90654836,36845587,45381876,17081350,83083131,43357947,8791066,83747892,27187213,20568363,54232247,33061250,67474219,57020481,2607799,11757872,90158785,16387575,74452589,26493119,34044787,21397057,18411915,56484900,82779622,77187825,37435892,68110330,78300864 +70596786,9860195,37620363,76434144,67793644,1431742,78218845,55770687,14093520,80014588,26114953,36505482,21070110,1204161,15148031,70004753,57803235,37183543,29959549,99549373,52293995,15015906,5111370,53084256,49236559,112651,75407347,9259676,47213183,76315420,29188588,79922758,1250437,68644627,31904591,75052463,54427233,44983451,1788101,92554120,66832478,87720882,61859581,74357852,72738685,52261574,70800879,27665211,34497327,99658235,89214616,65047700,75153252,3633375,26102057,12416768,91664334,38494874,55470718,91802888,18131876,92692978,59614746,95726235,60430369,11365791,28550822,20486294,66885828,39171895,30139692,42967683,4798568,97783876,1022677,10309525,30543215,62428472,62740044,4091162,43045786,87160386,77620120,15535065,20002147,32161669,38658347,74137926,45919976,26493119,68128438,16595436,35192533,77187825,86821229,42073124,31126490,8696647,54868730,50806615,749283,35092039,36189527,15536795,76330843,1569515,27185644,22405120,9886593,9623492,44784505,83155442,77312810,13470059,7104732,39801351,83368048,18806856,39986008,16380211,3880712,43933006,13231279,43152977,44889423,72278539,96726697,80246713,66319530,50702367,88653118,33237508,76170907,82779622,61373987,13348726,80316608,62936963,168541,62496012,874791,44550764,62490109,4099191,40152546,4204661,68204242,415901,93933709,62452034,23740167,61263068,21533347,68041839,44842615,12348118,29819940,8128637,79880247,14731700,10366309,67281495,79821897,4064751,88047921,90013093,66045587,50188404,18783702,12571310,40865610,34946859,38341669,96193415,9398733,68102437,88904910,72358170,55247455,48260151,63015256,33431960,68939068,7687278,55189057,83083131,19101477,78549759,70372191,57359924,27625735,30653863,42782652,7955293,36396314,526217,23110625,69786756,14947650,96709982,4515343,37739481,62762857,48360198,59501487,72357096,24953498,17058722,76703609,85711894,51590803,29994197,73168565,34432810,82427263,98739783,44915235,39553046,33628349,81853704,93562257,94516935,67451935,94539824,20122224,9058407,17957593,26275734,43506672,6871053,247198,68316156,30366150,97057187,71333116,38510840,30764367,24591705,34698428,37435892,33304202,84349107,32274392,8634541,33249630,85571389,36812683,69355476,76960303,26998766,19939935,23569917,37560164,75229462,99965001,17857111,3233084,29029316,61960400,9599614,72274002,71316369,60106217,37675718,29932657,78884452,8913721,40534591,79136082,40197395,97281847,65715134,95957797,85242963,7646095,18699206,55602660,4119747,65074535,64087743,78589145,32250045,14723410,61741594,19457589,76671482,6221471,78561158,81677380,55753905,3294781,58224549,3233569,86242799,75986488,26139110,16019925,98130363,54199160,16405341,17365188,62430984,66903004,5482538,54987042,48673079,66667729,65454636,81172706,37192445,20836893,66428795,57241521,4787945,99224392,53547802,30998561,5640302,2204165,77301523,2208785,90310261,99861373,70541760,54263880,48892201,45428665,36580610,92998591,84187166,15064655,51047803,98462867,44481640,42307449,1197320,73031054,45996863,98648327,40027975,9603598,34698463,93053405,19376156,40677414,77284619,58749,40984766,92692283,89699445,31733363,55669657,60955663,23134715,44847298,41442762,42199455,77272628,89419466,34295794,38008118,37224844,6153406,65338021,44177011,66250369,73786944,59197747,77413012,23194618,64848072,33123618,95010552,9575954,84002370,33797252,57658654,57163802,98943869,85116755,43322743,68816413,4434662,46851987,36312813,81898046,17037369,11923835,37501808,9829782,77072625,86001008,29834512,26392416,22129328,16971929,99524975,22450468,44627776,56424103,78602717,97940276,8791066,6521313,53666583,40781449,87598888,46870723,10597197,19272365,43376279,92071990,48658605,59318837,44060493,6986898,62803858,98948034,86543538,78785507,25636669,36930650,98653983,73124510,65038678,48893685,83302115,7919588,73109997,89046466,99690194,2891150,21993752,38645117,68824981,9175338,47090124,40781854,669105,78766358,4199704,73617245,47708850,6525948,4668450,10961421,30163921,26776922,42644903,20867149,83210802,23432750,56515456,14220886,52613508,30787683,99297164,73222868,49598724,94595668,92604458,65275241,72777973,3235882,36139942,22435353,15075176,64602895,26507214,14326617,95251277,81774825,91831487,21673260,39847321,51315460,9257405,51507425,99333375,92215320,82897371,92867155,33895336,321665,46540998,569864,50668599,74441448,86798033,89811711,96735716,76540481,44348328,63967300,8099994,40356877,95581843,88698958,44846932,24058273,71522968,62232211,8055981,56473732,27325428,75543508,82532312,33699435,27041967,25842078,2331773,1375023,81805959,19486173,19898053,26664538,93790285,5970607,89637706,22721500,6793819,35456853,24826575,22879907,32159704,83789391,68000591,37891451,72732205,44473167,57240218,99604946,30218878,26734892,17727650,84406788,98371444,77694700,93057697,55615885,508198,14626618,97641116,45848907,62693428,74614639,51426311,97379790,92803766,53888755,41092172,77787724,28787861,80555751,176257,20140249,23848565,7300047,51149804,34236719,26863229,16099750,56484900,84904436,39373729,62552524,18001617,59109400,3183975,84840549,75135448,71469330,84127901,62115552,78300864,5267545,6111563,42947632,30891921,13468268,26744917,93270084,79657802,8651647,16684829,93359396,67513640,4806458,20885148,38061439,28796059,82726008,24915585,96318094,36933038,55143724,8115266,20765474,36135,26063929,17539625,12024238,33553959,32590267,16097038,71083565,90090964,26397786,63059024,69255765,61982238,73392814,61728685,33935899,45790169,73021291,26292919,4069912,36780454,72614359,8129978,99917599,91938191,7229550,24733232,6808825,90272749,59177718,22200378,30395570,42692881,77377183,29466406,2917920,22766820,28734791,34493392,45995325,67031644,70727211,53648154,83150534,60581278,74743862,9188443,72793444,32699744,85695762,69848388,72495719,51472275,18411915,49882705,85023028,18663507,52204879,36753250,43501211,91957544,45990383,19891772,27411561,40686254,47792865,99515901,51715482,73235980,61141874,16424199,96906410,4978543,6038457,50007421,16387575,61859143,60102965,70420215,7502255,91727510,88444207,1936762,86022504,61928316,75777973,47887470,61623915,37957788,54663246,94911072,45049308,8807940,99125126,81274566,58208470,27187213,55960386,99729357,75128745,94360702,65851721,7423788,77300457,69641513,67124282,92398073,66116458,99021067,94076128,63152504,91240048,28851716,63372756,36685023,97561745,79322415,82339363,88251446,56153345,49641577,15902805,11543098,37280276,67474219,66663942,66611759,12664567,65017137,48675329,80251430,68875490,90457870,20681921,16445503,17386996,54517921,83133790,2607799,79191827,92787493,7011964,2717150,90061527,59910624,14363867,49271185,12303248,99226875,79738755,33565483,3487592,53842979,45667668,17385531,45794415,86118021,18504197,57020481,35996293,58751351,5832946,30463802,71920426,92033260,83269727,17146629,95395112,32151165,29635537,71300104,24168411,22108665,64098930,86301513,54014062,43357947,31161687,72725103,63628376,20642888,47893286,66322959,37166608,7517032,1239555,94090109,30694952,79942022,48774913,37659250,9860968,10649306,61712234,8733068,89996536,21289531,99971982,66271566,66137019,60176618,8373289,70782102,50567636,49328608,82651278,64157906,89499542,34044787,24619760,91990218,48088883,3509435,36845587,83747892,44245960,81855445,13173644,17764950,57961282,44664587,6505939,5822202,53632373,47361209,62357986,83533741,53802686,65271999,61815223,51466049,71657078,41245325,30569392,85117092,62051033,61897582,6497830,82886381,70367851,58180653,32058615,71965942,39201414,91255408,41092102,4985896,61271144,41481685,64055960,21001913,17081350,88130087,83651211,30771409,59329511,17976208,97011160,38009615,33336148,65880522,82979980,13862149,55850790,56461322,69136837,72373496,44479073,29510992,21397057,69605283,93515664,70036438,5073754,16054533,89804152,17894977,15948937,87710366,89217461,72019362,84293052,4508700,82327024,41380093,38022834,68694897,93566986,54762643,18833224,57248122,32426752,67030811,54058788,38365584,52060076,51588015,53393358,92541302,68702632,22997281,59981773,63506281,17016156,45237957,48395186,90004325,59405277,74452589,67084351,11757872,91281584,34667286,68110330,28928175,45075471,20645197,29516592,14349098,96852321,45617087,91141395,69579137,10358899,7685448,49597667,47738185,3773993,74724075,59371804,21919959,33201905,42237907,69697787,47298834,10453030,10264691,57393458,92530431,11161731,7066775,96420429,35512853,84684495,95290172,33061250,45306070,51464002,82052050,54606384,824872,65081429,72238278,22942635,14045383,16567550,22113133,3088684,29065964,45407418,11581181,76825057,31491938,44844121,86460488,31727379,7182642,94711842,79806380,75820087,56531125,29401781,82178706,20568363,15163258,6819644,78909561,90654836,36468541,90158785,74110882,18466635,83948335,89078848,45381876,30811010,17068582,54232247,92353856,38256849,67227442,24314885,8729146,56955985,36808486,84166196,60393039,77898274,6157724,4095116,30090481,13819358 +4668450,49236559,9623492,31904591,29029316,77272628,6038457,33304202,91664334,91802888,29188588,47213183,41481685,64848072,44177011,30139692,66832478,59329511,91727510,34698428,66667729,73031054,48892201,37620363,69136837,19939935,37183543,36312813,66611759,77620120,5970607,36580610,96726697,93053405,23569917,22435353,96709982,4099191,15535065,66250369,75407347,35192533,61271144,2717150,76434144,68000591,60430369,83083131,7646095,61263068,29994197,39986008,69355476,38658347,10358899,79922758,48774913,30787683,7066775,28550822,91990218,8651647,51464002,43322743,92692283,37891451,62430984,93057697,8129978,60102965,84684495,60581278,47893286,31161687,4515343,8055981,40197395,80246713,70004753,44844121,6871053,38645117,7685448,1197320,53393358,50188404,57803235,52204879,24058273,26998766,95957797,96193415,22450468,47090124,36753250,67474219,16971929,37501808,29819940,8115266,62452034,57658654,6521313,16019925,22129328,32161669,66885828,34295794,9257405,59614746,63152504,30653863,17068582,73168565,33249630,61741594,81172706,52613508,55602660,42644903,12303248,51047803,44550764,26392416,8373289,76315420,35092039,526217,4787945,34497327,64087743,70596786,1431742,76703609,71333116,17146629,51472275,9860195,1239555,29834512,61373987,11923835,45794415,16099750,65880522,31126490,61623915,99965001,11365791,74137926,88251446,77187825,53842979,7229550,77300457,12664567,29932657,86022504,40781449,55850790,42692881,6808825,53802686,68204242,63967300,71657078,44348328,85711894,78549759,55753905,9575954,9860968,64602895,62496012,73222868,53084256,99549373,24591705,86460488,9188443,20642888,14220886,43376279,8807940,76960303,60106217,59371804,6525948,74441448,48673079,66319530,56153345,36780454,96735716,824872,22405120,54427233,74357852,77284619,98948034,90272749,73124510,48360198,65715134,93790285,84406788,39847321,72274002,14626618,44889423,92554120,71083565,55470718,61141874,13819358,98739783,39553046,62740044,77072625,4119747,52293995,45617087,37659250,43045786,56424103,38494874,4064751,569864,46870723,80555751,30163921,59501487,20645197,71965942,83150534,17727650,19486173,75153252,4434662,77694700,67281495,54058788,7687278,91957544,61712234,98130363,49328608,61982238,76671482,91938191,19457589,54987042,19101477,37675718,40356877,43357947,84349107,8128637,112651,92604458,62693428,2208785,99604946,30764367,33565483,22997281,33237508,89811711,1022677,20885148,75229462,66663942,12416768,67793644,92353856,21070110,50567636,98648327,74724075,82339363,40984766,55143724,14723410,3773993,16424199,30771409,8099994,16097038,7011964,91831487,51715482,78785507,49641577,59981773,32250045,93270084,22766820,68128438,13470059,40534591,54232247,83133790,72777973,36468541,75128745,8696647,71920426,30694952,10309525,32274392,13862149,98943869,3294781,77413012,19376156,54014062,50702367,32699744,49598724,89996536,47708850,72373496,70367851,7300047,26734892,50668599,7919588,40781854,38061439,77787724,2607799,58180653,67451935,9259676,73392814,39373729,17081350,98462867,25636669,72278539,30998561,56461322,79657802,1788101,33895336,247198,4806458,6986898,20836893,45996863,415901,80316608,29510992,83210802,71469330,39201414,30569392,90013093,90090964,78218845,176257,60393039,38510840,65038678,68702632,57241521,27665211,67084351,82886381,23848565,2204165,22108665,79821897,65275241,87710366,49882705,48088883,82651278,42782652,96420429,66271566,89214616,44664587,26863229,93933709,64055960,28928175,92033260,37739481,69641513,73235980,24168411,76170907,79136082,59177718,72725103,23432750,54663246,66903004,44842615,45848907,36139942,49597667,29466406,61859143,62115552,19898053,61859581,51315460,81677380,33431960,62936963,55189057,84166196,29959549,57248122,56484900,33123618,26744917,14363867,40027975,72793444,88047921,16595436,1569515,32590267,86821229,30218878,88653118,2917920,37560164,13231279,81853704,70800879,54263880,74743862,11161731,55770687,57020481,53632373,99524975,54199160,36505482,58751351,27185644,68875490,60176618,90310261,92867155,30463802,95010552,30366150,64157906,15148031,94911072,84293052,26507214,24915585,99515901,98371444,92071990,5832946,68316156,89499542,33201905,57163802,15948937,92692978,10649306,32159704,669105,28851716,9398733,99971982,51590803,18504197,18131876,75543508,58749,4091162,25842078,28796059,168541,54606384,8733068,5111370,79880247,41245325,75820087,15064655,20765474,23134715,17386996,47361209,32426752,82779622,14045383,84187166,44915235,75052463,44481640,20486294,48675329,83302115,53547802,92398073,36930650,6221471,97281847,88698958,87598888,34236719,8791066,17016156,6505939,35996293,41092102,40152546,83789391,20122224,39171895,97011160,83269727,97057187,22113133,89699445,67227442,59318837,39801351,16380211,10264691,82726008,70372191,54517921,70727211,42947632,17385531,27411561,14731700,77312810,23740167,4204661,51466049,37192445,75777973,38341669,99333375,9599614,4069912,6793819,99224392,63628376,72357096,67030811,7955293,18663507,41092172,3633375,1375023,34698463,63372756,20002147,1250437,64098930,508198,8634541,69848388,99917599,23194618,51588015,43501211,87160386,90654836,37435892,74452589,68939068,8729146,36812683,43506672,17058722,45381876,15163258,30543215,36808486,65271999,82979980,36396314,68644627,45075471,89419466,6157724,3088684,94090109,42199455,63059024,69255765,97379790,26114953,95726235,44473167,11543098,47887470,56515456,83651211,33553959,68102437,97783876,36933038,15536795,42073124,3233084,91240048,9886593,53666583,65047700,96906410,52060076,34493392,84002370,18806856,21919959,4199704,30891921,79806380,50806615,76825057,66116458,85117092,46851987,93359396,61728685,34432810,44479073,44846932,22721500,69786756,34667286,85116755,63015256,55960386,38365584,14093520,40865610,24733232,78561158,17037369,31733363,57393458,20867149,5822202,14349098,10961421,62428472,73021291,26776922,75135448,92215320,94711842,66428795,81855445,27625735,62552524,99658235,65074535,749283,72732205,62051033,13468268,16684829,65454636,43152977,65338021,54762643,77898274,35512853,26275734,31727379,22879907,77301523,27041967,70541760,78766358,1204161,78300864,33628349,9603598,47738185,98653983,33797252,38008118,72614359,81898046,59109400,6497830,42967683,95395112,21673260,21001913,17957593,44245960,96852321,30395570,28787861,74110882,15015906,19272365,30090481,69697787,90457870,70036438,4978543,72738685,17764950,18833224,15075176,65017137,47298834,71522968,32058615,83948335,71300104,17894977,12571310,40686254,38022834,78884452,44983451,72238278,8913721,88130087,57359924,61815223,66322959,3509435,60955663,89804152,94516935,20140249,5482538,86118021,73617245,16445503,3487592,34946859,79942022,3880712,4508700,26664538,78909561,44627776,86242799,6111563,29516592,92803766,84127901,89078848,16054533,86543538,68824981,20681921,21993752,27325428,11581181,44784505,3183975,24826575,14326617,48260151,78589145,42307449,73786944,26139110,45990383,57961282,86301513,27187213,10366309,62762857,94076128,44847298,83533741,55669657,49271185,56531125,82178706,41442762,96318094,53648154,95290172,21533347,66045587,18783702,4095116,55247455,62490109,82427263,28734791,72358170,70420215,45919976,10597197,36685023,45428665,99861373,85023028,65081429,36189527,99226875,48893685,68694897,80251430,2891150,45237957,90061527,84840549,29065964,17365188,93515664,7423788,18001617,22200378,72019362,32151165,42237907,82052050,82532312,93562257,59197747,87720882,46540998,99021067,56955985,45790169,88444207,85571389,3233569,48395186,22942635,85695762,4798568,26063929,83368048,89637706,93566986,9829782,24619760,70782102,14947650,45306070,47792865,7104732,15902805,29401781,63506281,9058407,9175338,34044787,95581843,67031644,26102057,66137019,12348118,50007421,37224844,16405341,321665,99690194,99125126,54868730,56473732,77377183,5640302,61960400,89046466,89217461,29635537,85242963,68816413,90004325,58224549,6153406,17857111,26397786,7517032,11757872,35456853,91141395,65851721,79191827,45049308,81805959,91255408,18699206,83155442,44060493,16387575,69605283,68041839,86001008,81274566,18466635,94539824,23110625,6819644,17539625,43933006,71316369,21289531,5073754,45407418,88904910,33699435,78602717,73109997,51149804,94360702,38256849,92787493,84904436,21397057,55615885,5267545,36845587,51507425,80014588,4985896,59405277,16567550,24314885,62232211,1936762,57240218,17976208,52261574,99297164,48658605,30811010,12024238,97561745,37166608,7182642,75986488,45995325,13348726,97641116,40677414,58208470,79738755,62803858,33061250,92998591,97940276,18411915,37957788,92541302,51426311,37280276,2331773,26493119,82897371,24953498,62357986,67124282,94595668,10453030,69579137,7502255,53888755,90158785,91281584,67513640,874791,61897582,45667668,33336148,41380093,72495719,68110330,76330843,26292919,82327024,20568363,99729357,81774825,19891772,61928316,86798033,74614639,76540481,95251277,36135,33935899,83747892,13173644,3235882,59910624,92530431,79322415,38009615,31491938 +33431960,1788101,27665211,17857111,86022504,99604946,74357852,55143724,95726235,247198,38494874,42967683,56424103,50188404,14093520,97783876,30139692,55753905,74137926,34698428,93270084,36812683,70372191,73222868,5822202,23848565,98739783,45919976,18806856,67281495,85023028,5111370,84406788,15535065,49236559,61373987,62496012,35092039,18663507,22108665,39986008,71920426,19939935,26998766,95581843,52261574,68644627,48360198,89637706,5970607,1569515,1022677,29188588,45990383,37739481,34236719,25636669,91255408,1431742,28550822,81853704,44889423,68041839,32590267,17146629,96193415,16019925,77620120,5073754,5832946,74441448,4119747,43933006,58224549,2891150,83155442,40781854,39171895,83150534,9599614,55470718,71333116,67124282,63967300,19376156,88653118,50702367,6871053,86821229,80555751,72725103,74110882,37891451,26063929,91727510,3633375,88047921,57961282,15148031,75135448,14731700,29994197,36312813,83368048,82979980,71316369,55602660,82726008,18504197,43357947,37675718,65715134,415901,8733068,65038678,66663942,17058722,16424199,68204242,31904591,99125126,77284619,85116755,83210802,29065964,60581278,63152504,29932657,75407347,93057697,27625735,91802888,93933709,73031054,69355476,44177011,47090124,69848388,9188443,22435353,99658235,76434144,36505482,62552524,33553959,8634541,3880712,4668450,96726697,35456853,30543215,11365791,10366309,14220886,7104732,80246713,13470059,30653863,72738685,24591705,49328608,89699445,26493119,7685448,46851987,95395112,68000591,6038457,53393358,4434662,61897582,42782652,62452034,4787945,40984766,9603598,44348328,30787683,48892201,2204165,569864,63059024,24733232,17365188,9829782,6793819,4199704,88698958,67451935,12416768,32426752,36580610,21993752,9886593,37435892,75543508,33237508,44481640,20486294,86301513,77301523,66903004,62936963,57393458,54014062,44842615,84904436,78218845,22450468,81898046,66611759,62762857,83269727,4099191,87598888,77072625,26776922,89811711,17539625,62740044,63506281,508198,78589145,44550764,112651,68816413,42199455,82327024,55770687,8651647,48673079,29510992,51507425,78549759,16971929,54232247,75986488,669105,98371444,27185644,89996536,30218878,23134715,67793644,7502255,44784505,57359924,45848907,72777973,36685023,33304202,78602717,6221471,48774913,4798568,52293995,92998591,87160386,53084256,99333375,66428795,90272749,24058273,3235882,20885148,1197320,88444207,79806380,29819940,7423788,50806615,51047803,56153345,4204661,40027975,6153406,81172706,13231279,86798033,50668599,59614746,84840549,77187825,61928316,40356877,76170907,50567636,3233084,66885828,95251277,68875490,43152977,40197395,65081429,14045383,15075176,45306070,26392416,45996863,38008118,99861373,92071990,66319530,46540998,65851721,98948034,63628376,77413012,44060493,61141874,70800879,57163802,31733363,32159704,48260151,36930650,73124510,18131876,16387575,35996293,29834512,51472275,72274002,20002147,60430369,44473167,3088684,59329511,45237957,62430984,92604458,46870723,9623492,53888755,37183543,7687278,62803858,64848072,8055981,33895336,60393039,34497327,70596786,97281847,52060076,54517921,13173644,65017137,59318837,7229550,89046466,874791,19898053,36189527,34432810,59910624,77787724,48658605,79922758,38341669,89217461,24619760,2717150,90457870,99549373,47361209,92530431,97641116,12348118,11161731,96318094,28851716,41092172,8115266,42947632,62232211,36753250,96852321,93053405,78561158,38256849,26114953,29466406,43501211,54199160,20867149,53666583,6819644,56955985,65271999,31161687,61263068,75052463,80316608,56473732,87720882,69255765,70004753,6525948,61623915,8807940,92398073,9175338,44847298,34493392,81855445,33565483,95957797,99021067,91664334,168541,94711842,71657078,93359396,12024238,61960400,94090109,93790285,83789391,67030811,20681921,6808825,92554120,54987042,82886381,61859143,78884452,44245960,28928175,76825057,91990218,65275241,71083565,72614359,71300104,93562257,12571310,66116458,47213183,49597667,99224392,19486173,37166608,74724075,6505939,14326617,20836893,61741594,19101477,82052050,18699206,18783702,83533741,14723410,1239555,43322743,4095116,55669657,72732205,8791066,57020481,6986898,30366150,49641577,59197747,89214616,45995325,87710366,61815223,38061439,99965001,56484900,84127901,3233569,95290172,72357096,59501487,91831487,28734791,73235980,68702632,17764950,4069912,10649306,15064655,41092102,2917920,17727650,17957593,41481685,4978543,61271144,12664567,21001913,3773993,53547802,34698463,14626618,27187213,27325428,90013093,67227442,38365584,10961421,82178706,65880522,17037369,45407418,30569392,80014588,13819358,65454636,26102057,9575954,22129328,69136837,21919959,29029316,17016156,89419466,26863229,77300457,93515664,40677414,7300047,8128637,29959549,77272628,66271566,27041967,4806458,81274566,79880247,42692881,76671482,71965942,33699435,99690194,90310261,84166196,76315420,37192445,51590803,36468541,40686254,38645117,98653983,33249630,53648154,22721500,90090964,79191827,35192533,76703609,78785507,5482538,59177718,8696647,70036438,55247455,90004325,75777973,96709982,76540481,73617245,824872,72373496,37659250,30998561,56515456,72495719,72019362,66322959,79821897,84293052,82532312,47887470,38658347,31491938,86001008,3294781,53842979,32699744,40152546,69786756,64602895,30764367,54762643,17894977,78300864,85117092,45617087,8099994,57248122,23569917,47893286,40781449,94516935,4985896,69579137,99917599,83083131,43045786,44844121,3509435,16380211,54058788,4515343,56531125,99971982,92867155,84349107,44627776,24826575,1250437,83302115,78766358,66045587,77377183,83747892,59405277,16097038,89078848,98462867,23194618,9259676,68316156,15015906,99729357,71522968,32250045,31126490,62490109,37620363,43506672,8129978,66667729,321665,83133790,749283,16099750,64087743,21070110,45428665,10309525,51588015,78909561,9257405,33628349,68128438,18411915,72358170,95010552,2607799,526217,51464002,16054533,92692978,30463802,44846932,92692283,74743862,4064751,26744917,68824981,37957788,91957544,41442762,48675329,82651278,11543098,49882705,6497830,9398733,97940276,70727211,75820087,22766820,18833224,66832478,71469330,70420215,97057187,76330843,16445503,66137019,94076128,20122224,57803235,55960386,73168565,57241521,70367851,76960303,75153252,79657802,39553046,54868730,40865610,80251430,92215320,6521313,94595668,32161669,26664538,60102965,45790169,54427233,93566986,72793444,13862149,9058407,33935899,32274392,33797252,44983451,50007421,33123618,89499542,36396314,22879907,47738185,82339363,30771409,13468268,79136082,34946859,27411561,90158785,65047700,28796059,92787493,35512853,97379790,55189057,89804152,48088883,77898274,42644903,99515901,33201905,90654836,37501808,34295794,61982238,48893685,34667286,81677380,56461322,14947650,66250369,73786944,91240048,99226875,77694700,75128745,11581181,47298834,42307449,99524975,20140249,36780454,10358899,7066775,53802686,55850790,26507214,85242963,47792865,7646095,98130363,86242799,10453030,18466635,70782102,73109997,86543538,15902805,4508700,26275734,77312810,62115552,23432750,51315460,9860968,51149804,92803766,21533347,54663246,92033260,39847321,24915585,36808486,14349098,7517032,20642888,68110330,72278539,59109400,52613508,34044787,61712234,85711894,51426311,62051033,94539824,96735716,19891772,97561745,39201414,31727379,72238278,97011160,17068582,88251446,69641513,17386996,15948937,44479073,84684495,92541302,70541760,30395570,45667668,30090481,7919588,39373729,52204879,88904910,65338021,73392814,5640302,57240218,30163921,30694952,36139942,85695762,22405120,51715482,29401781,83651211,67474219,11923835,17081350,65074535,91938191,41245325,60955663,44664587,42073124,20765474,22997281,11757872,68694897,64157906,96906410,54263880,15163258,86460488,61859581,58749,45075471,63015256,59371804,67031644,16405341,21673260,86118021,85571389,8729146,58180653,64098930,16595436,82779622,79942022,20568363,40534591,18001617,22942635,88130087,84187166,57658654,61728685,1936762,26292919,19457589,82897371,39801351,44915235,98943869,54606384,20645197,74452589,43376279,24953498,8913721,32058615,49271185,2208785,36135,38510840,67084351,7011964,26734892,81805959,30891921,94360702,4091162,37560164,7955293,37224844,6111563,22200378,74614639,69605283,3487592,99297164,82427263,12303248,53632373,2331773,41380093,58751351,75229462,92353856,96420429,68939068,38022834,58208470,83948335,32151165,25842078,49598724,22113133,60106217,55615885,16567550,176257,30811010,26397786,9860195,94911072,59981773,19272365,51466049,5267545,14363867,26139110,73021291,23110625,48395186,1204161,38009615,69697787,13348726,42237907,67513640,33061250,81774825,29635537,17385531,28787861,79738755,24314885,1375023,62693428,15536795,91141395,62428472,63372756,79322415,6157724,33336148,60176618,8373289,37280276,64055960,24168411,21289531,21397057,47708850,7182642,90061527,45794415,62357986,36933038,68102437,91281584,3183975,17976208,45049308,98648327,10597197,36845587,84002370,23740167,29516592,10264691,16684829,45381876 +30764367,44784505,74357852,31126490,81898046,54517921,62430984,51047803,9603598,45237957,62496012,49641577,88653118,7104732,96193415,63967300,89214616,99861373,54058788,4508700,28851716,14947650,13862149,77300457,86798033,72278539,5482538,59177718,14731700,77301523,99965001,68816413,65271999,62452034,76960303,95395112,92692283,97561745,89217461,24591705,92554120,82651278,247198,34497327,40781854,34698428,34236719,4668450,61859143,44983451,57241521,95726235,15015906,3509435,64848072,91831487,13231279,65081429,78589145,44889423,73617245,18504197,5640302,65454636,33237508,1197320,55189057,33565483,89637706,42307449,99549373,65338021,92692978,3183975,48088883,61859581,66428795,93566986,10453030,97783876,88444207,64087743,99226875,67227442,55247455,54762643,14349098,63059024,21993752,74441448,70367851,95957797,86001008,18001617,7300047,61271144,51315460,92398073,10961421,38022834,68939068,77272628,2717150,31733363,74452589,51588015,27625735,55470718,37435892,71920426,90004325,8129978,81853704,44177011,79136082,32058615,45407418,20642888,9886593,29834512,76671482,50806615,54987042,36685023,82339363,30787683,26998766,35192533,92998591,3233084,9575954,29188588,53084256,69579137,30395570,61741594,17764950,40686254,45848907,35456853,44473167,51464002,67281495,96735716,92071990,26063929,20867149,20122224,54427233,87710366,85116755,37183543,17727650,6793819,63372756,34946859,29994197,67031644,88698958,95010552,38494874,65275241,67793644,15535065,54014062,42692881,43322743,8807940,27665211,36580610,62740044,32590267,6986898,62693428,50188404,53648154,6153406,112651,53802686,5970607,52060076,79806380,75543508,61373987,12416768,4119747,64602895,69786756,17037369,92033260,29959549,37957788,874791,92787493,42199455,94595668,12348118,66663942,68128438,86543538,18833224,70036438,97940276,14093520,52261574,49236559,13470059,56515456,47893286,90272749,99021067,4064751,18806856,36808486,26493119,60955663,79821897,17539625,50007421,77284619,22108665,51472275,62552524,28734791,22766820,80555751,16387575,63506281,9175338,99729357,38645117,64098930,12664567,19101477,71657078,95290172,36780454,37675718,47090124,77072625,17385531,93270084,53632373,17058722,36505482,87598888,18699206,27041967,79880247,61623915,14045383,36933038,7423788,99515901,32250045,19939935,38365584,65715134,77312810,83789391,1204161,78561158,67124282,65047700,92604458,33304202,99125126,53547802,60102965,19891772,77413012,415901,2917920,38256849,10309525,669105,84904436,81677380,56461322,26114953,68875490,88047921,74743862,92541302,68644627,78766358,33935899,44550764,76170907,73786944,62936963,9860195,36845587,58208470,39986008,44481640,77620120,45790169,3633375,56531125,85695762,2204165,6505939,8791066,9188443,91141395,69848388,59318837,30139692,2891150,22200378,64157906,92530431,57393458,8733068,57359924,21533347,14220886,75986488,72725103,39171895,30569392,49597667,59371804,44348328,66611759,60581278,41442762,82052050,25842078,79657802,72738685,23569917,61141874,38008118,80014588,65880522,62803858,35996293,57803235,34295794,46540998,99524975,4095116,10649306,26392416,33895336,59501487,54232247,51590803,20765474,8913721,72777973,99917599,70800879,91664334,34667286,22942635,749283,38341669,78785507,99658235,35092039,20486294,29510992,57163802,21919959,5073754,21070110,85571389,91957544,57248122,50668599,33201905,168541,44842615,31904591,41380093,89996536,27325428,40677414,12024238,39847321,15536795,9257405,18131876,6038457,526217,74614639,80316608,16595436,70596786,43357947,86301513,66045587,55753905,72238278,39801351,7011964,73124510,14363867,40152546,46870723,27411561,88251446,72373496,24953498,99333375,70004753,68000591,6808825,48260151,69605283,3233569,5111370,78300864,24619760,93933709,4434662,88904910,13819358,94090109,7687278,44479073,1239555,77377183,71522968,22997281,47738185,34493392,4787945,72274002,23432750,94539824,91802888,18783702,41092102,24314885,16097038,86821229,26102057,40197395,58751351,3294781,91281584,6525948,69697787,94076128,16054533,47361209,21001913,19376156,63152504,30366150,48675329,29635537,21673260,66137019,45306070,33249630,42967683,54663246,46851987,7685448,72614359,33123618,59197747,68824981,36812683,58749,8099994,72357096,23134715,83651211,57020481,89804152,9599614,94516935,19898053,55602660,55669657,10366309,30694952,40356877,98371444,28787861,24058273,40984766,4091162,72019362,51715482,30543215,58180653,71333116,29065964,72793444,12303248,43045786,81805959,51149804,17365188,66832478,4069912,23848565,508198,76330843,56473732,68694897,10358899,50702367,65851721,1431742,62762857,91240048,50567636,37620363,76540481,93562257,16019925,17857111,37739481,66250369,29932657,89078848,98739783,8115266,19486173,9259676,66667729,4806458,79738755,60430369,42947632,93359396,37891451,15148031,69136837,24733232,44847298,48360198,62357986,33628349,22113133,7955293,83210802,85242963,54199160,61263068,17957593,81172706,43933006,94360702,85023028,75153252,96709982,16099750,20002147,83269727,78909561,61982238,53842979,26139110,53666583,96906410,7182642,43376279,77787724,30218878,13348726,99297164,11581181,90061527,6157724,29819940,3088684,34044787,96318094,98130363,18411915,31491938,57961282,61815223,32161669,48658605,92803766,45667668,42644903,40781449,40865610,84127901,99690194,91255408,44060493,49882705,11161731,30998561,26664538,4978543,83533741,86022504,47708850,68041839,80251430,73222868,74137926,51426311,71083565,16424199,17016156,81274566,22450468,87160386,65038678,83150534,43506672,54606384,55143724,36189527,7919588,22879907,48395186,28550822,42782652,75407347,69641513,37659250,84349107,20885148,1936762,21397057,76434144,4099191,49598724,60106217,79942022,93057697,32159704,27185644,66903004,63628376,36753250,33797252,36135,45996863,26507214,321665,26397786,82897371,45075471,90457870,81774825,1569515,73235980,29401781,26275734,82178706,37501808,4985896,59109400,83948335,13468268,89046466,5267545,78549759,38061439,77187825,20140249,96726697,85711894,78602717,59614746,56153345,29466406,6521313,57240218,8696647,3235882,33699435,14326617,67451935,76315420,92215320,70420215,62051033,12571310,55960386,60393039,28796059,67084351,86118021,64055960,66322959,66271566,97011160,17146629,4204661,6221471,33336148,9623492,48892201,17386996,75128745,34432810,26776922,33431960,89811711,93790285,88130087,39553046,66116458,98653983,14626618,45990383,84187166,83747892,28928175,51507425,43501211,84684495,45049308,68316156,73031054,33553959,52204879,41481685,56484900,68110330,5832946,98943869,99224392,48893685,94711842,72495719,36312813,93515664,91938191,85117092,55615885,17068582,84293052,83155442,45428665,20568363,4798568,74724075,9398733,67030811,48774913,30090481,82886381,16380211,9058407,30463802,82779622,70541760,90013093,75052463,29029316,54263880,1250437,68204242,53888755,70372191,20836893,91990218,47792865,92867155,8055981,58224549,37192445,74110882,10597197,15902805,97641116,78884452,94911072,59910624,3880712,15064655,15948937,45919976,84406788,75777973,16567550,66319530,35512853,56424103,95581843,61897582,21289531,62232211,90090964,73392814,98648327,8128637,68702632,30891921,11365791,73168565,7517032,98462867,38510840,79922758,96420429,72358170,3773993,89419466,22435353,8634541,44844121,54868730,52613508,22129328,82979980,78218845,30653863,19272365,16445503,90158785,42237907,1022677,69355476,36139942,56955985,8729146,65074535,3487592,68102437,71300104,98948034,67513640,26292919,86242799,26744917,34698463,19457589,71965942,76825057,24826575,824872,42073124,99604946,39201414,52293995,75135448,36396314,23740167,18466635,66885828,14723410,29516592,80246713,97379790,8651647,11543098,18663507,89699445,53393358,9829782,44664587,45995325,49271185,6497830,1788101,82726008,47887470,30771409,16971929,84166196,4199704,55770687,60176618,37560164,17081350,61928316,69255765,49328608,17976208,24915585,84840549,45617087,93053405,71469330,79191827,38658347,16405341,62490109,51466049,63015256,16684829,44846932,82427263,82327024,25636669,45794415,96852321,84002370,71316369,24168411,61960400,23194618,37224844,6111563,2208785,26863229,36930650,87720882,33061250,55850790,7502255,17894977,31161687,72732205,95251277,44245960,5822202,59981773,91727510,75229462,7229550,75820087,48673079,97057187,41092172,82532312,86460488,22721500,569864,6819644,7646095,30811010,44915235,59329511,32151165,31727379,11757872,9860968,83133790,83083131,37280276,62428472,176257,40027975,92353856,39373729,83302115,83368048,6871053,90654836,57658654,2607799,43152977,32274392,37166608,8373289,32426752,2331773,81855445,1375023,47298834,65017137,89499542,13173644,47213183,41245325,10264691,30163921,97281847,15163258,90310261,11923835,26734892,77694700,15075176,73021291,23110625,38009615,36468541,27187213,61728685,20645197,70782102,79322415,70727211,22405120,76703609,99971982,32699744,45381876,62115552,7066775,20681921,61712234,59405277,40534591,67474219,44627776,4515343,77898274,73109997 \ No newline at end of file diff --git a/src/testFixtures/java/org/opensearch/knn/KNNJsonIndexMappingsBuilder.java b/src/testFixtures/java/org/opensearch/knn/KNNJsonIndexMappingsBuilder.java index 165dfaf64..15ac0c211 100644 --- a/src/testFixtures/java/org/opensearch/knn/KNNJsonIndexMappingsBuilder.java +++ b/src/testFixtures/java/org/opensearch/knn/KNNJsonIndexMappingsBuilder.java @@ -102,9 +102,17 @@ private void addParameters(final XContentBuilder xContentBuilder) throws IOExcep @Builder public static class Parameters { private Encoder encoder; + private Integer efConstruction; + private Integer efSearch; private void addTo(final XContentBuilder xContentBuilder) throws IOException { xContentBuilder.startObject("parameters"); + if (efConstruction != null) { + xContentBuilder.field("ef_construction", efConstruction); + } + if (efSearch != null) { + xContentBuilder.field("ef_search", efSearch); + } addEncoder(xContentBuilder); xContentBuilder.endObject(); } From e3200260e7360f6ff90600535ac759d11cb1fefb Mon Sep 17 00:00:00 2001 From: Tejas Shah Date: Wed, 11 Sep 2024 15:06:44 -0700 Subject: [PATCH 06/59] Adds index and query BWC tests for missing engines (#2035) This is done to make sure that KNN80DocsValueConsumer code path is hit Signed-off-by: Tejas Shah --- qa/restart-upgrade/build.gradle | 15 ++++++ .../org/opensearch/knn/bwc/IndexingIT.java | 46 +++++++++++++++---- .../org/opensearch/knn/bwc/QueryANNIT.java | 39 ++++++++++++---- 3 files changed, 84 insertions(+), 16 deletions(-) diff --git a/qa/restart-upgrade/build.gradle b/qa/restart-upgrade/build.gradle index 36054e64a..628b62352 100644 --- a/qa/restart-upgrade/build.gradle +++ b/qa/restart-upgrade/build.gradle @@ -35,6 +35,7 @@ testClusters { } } + def versionsBelow2_3 = ["1.", "2.0.", "2.1.", "2.2."] // Task to run BWC tests against the old cluster task testAgainstOldCluster(type: StandaloneRestIntegTestTask) { dependsOn "zipBwcPlugin" @@ -89,6 +90,13 @@ testClusters { } } + if (versionsBelow2_3.any {knn_bwc_version.startsWith(it) }) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.QueryANNIT.testQueryOnLuceneIndex" + excludeTestsMatching "org.opensearch.knn.bwc.IndexingIT.testKNNIndexLuceneForceMerge" + } + } + nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}".allHttpSocketURI.join(",")}") nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}".getName()}") systemProperty 'tests.security.manager', 'false' @@ -155,6 +163,13 @@ testClusters { } } + if (versionsBelow2_3.any {knn_bwc_version.startsWith(it) }) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.QueryANNIT.testQueryOnLuceneIndex" + excludeTestsMatching "org.opensearch.knn.bwc.IndexingIT.testKNNIndexLuceneForceMerge" + } + } + nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}".allHttpSocketURI.join(",")}") nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}".getName()}") systemProperty 'tests.security.manager', 'false' diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java index 9c1dfb018..1fbc3696f 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java @@ -23,6 +23,7 @@ import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.LUCENE_NAME; import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; @@ -51,7 +52,7 @@ public void testKNNIndexDefaultLegacyFieldMapping() throws Exception { createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { - validateKNNIndexingOnUpgrade(); + validateKNNIndexingOnUpgrade(NUM_DOCS); } } @@ -65,8 +66,37 @@ public void testKNNIndexDefaultLegacyFieldMappingForceMerge() throws Exception { addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, 100); // Flush to ensure that index is not re-indexed when node comes back up flush(testIndex, true); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, 100, K); } else { - forceMergeKnnIndex(testIndex); + validateKNNIndexingOnUpgrade(100); + } + } + + public void testKNNIndexFaissForceMerge() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + + if (isRunningAgainstOldCluster()) { + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS, METHOD_HNSW, FAISS_NAME)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, 100); + // Flush to ensure that index is not re-indexed when node comes back up + flush(testIndex, true); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, 100, K); + } else { + validateKNNIndexingOnUpgrade(100); + } + } + + public void testKNNIndexLuceneForceMerge() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + + if (isRunningAgainstOldCluster()) { + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS, METHOD_HNSW, LUCENE_NAME)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, 100); + // Flush to ensure that index is not re-indexed when node comes back up + flush(testIndex, true); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, 100, K); + } else { + validateKNNIndexingOnUpgrade(100); } } @@ -115,7 +145,7 @@ public void testKNNIndexCustomLegacyFieldMapping() throws Exception { ); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { - validateKNNIndexingOnUpgrade(); + validateKNNIndexingOnUpgrade(NUM_DOCS); } } @@ -126,7 +156,7 @@ public void testKNNIndexDefaultMethodFieldMapping() throws Exception { createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKNNIndexMethodFieldMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { - validateKNNIndexingOnUpgrade(); + validateKNNIndexingOnUpgrade(NUM_DOCS); } } @@ -150,7 +180,7 @@ public void testKNNIndexCustomMethodFieldMapping() throws Exception { addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { validateCustomMethodFieldMappingAfterUpgrade(); - validateKNNIndexingOnUpgrade(); + validateKNNIndexingOnUpgrade(NUM_DOCS); } } @@ -240,11 +270,11 @@ public void testNoParametersOnUpgrade() throws Exception { } // KNN indexing tests when the cluster is upgraded to latest version - public void validateKNNIndexingOnUpgrade() throws Exception { - QUERY_COUNT = NUM_DOCS; + public void validateKNNIndexingOnUpgrade(int numOfDocs) throws Exception { + QUERY_COUNT = numOfDocs; validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); cleanUpCache(); - DOC_ID = NUM_DOCS; + DOC_ID = numOfDocs; addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); QUERY_COUNT = QUERY_COUNT + NUM_DOCS; validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/QueryANNIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/QueryANNIT.java index 7a0ed3d56..79ccff187 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/QueryANNIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/QueryANNIT.java @@ -1,12 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ package org.opensearch.knn.bwc; @@ -14,8 +8,13 @@ import java.util.Map; import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.LUCENE_NAME; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.NMSLIB_NAME; +/** + * Use case: Test queries on indexes created on older versions + */ public class QueryANNIT extends AbstractRestartUpgradeTestCase { private static final String TEST_FIELD = "test-field"; @@ -25,7 +24,7 @@ public class QueryANNIT extends AbstractRestartUpgradeTestCase { private static final int NUM_DOCS = 10; private static final String ALGORITHM = "hnsw"; - public void testQueryANN() throws Exception { + public void testQueryOnFaissIndex() throws Exception { if (isRunningAgainstOldCluster()) { createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS, ALGORITHM, FAISS_NAME)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 0, NUM_DOCS); @@ -36,4 +35,28 @@ public void testQueryANN() throws Exception { deleteKNNIndex(testIndex); } } + + public void testQueryOnNmslibIndex() throws Exception { + if (isRunningAgainstOldCluster()) { + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS, ALGORITHM, NMSLIB_NAME)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 0, NUM_DOCS); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + } else { + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K, Map.of(METHOD_PARAMETER_EF_SEARCH, EF_SEARCH)); + deleteKNNIndex(testIndex); + } + } + + public void testQueryOnLuceneIndex() throws Exception { + if (isRunningAgainstOldCluster()) { + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS, ALGORITHM, LUCENE_NAME)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 0, NUM_DOCS); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + } else { + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K, Map.of(METHOD_PARAMETER_EF_SEARCH, EF_SEARCH)); + deleteKNNIndex(testIndex); + } + } } From e55e49b249feced18f3b39535969ea1cc5ad1798 Mon Sep 17 00:00:00 2001 From: Vikasht34 Date: Wed, 11 Sep 2024 15:23:30 -0700 Subject: [PATCH 07/59] Improving Defaults Hyper Parameter for Binary Quantization Indexes (#2087) Signed-off-by: VIKASH TIWARI --- .../knn/index/engine/MethodComponent.java | 36 ++++++++++++++--- .../engine/faiss/FaissMethodResolver.java | 5 ++- .../knn/index/mapper/CompressionLevel.java | 6 +-- .../index/query/rescore/RescoreContext.java | 5 ++- .../index/util/IndexHyperParametersUtil.java | 20 ++++++++++ .../index/engine/MethodComponentTests.java | 40 +++++++++++++++++++ .../faiss/FaissMethodResolverTests.java | 27 +++++++++++++ .../index/mapper/CompressionLevelTests.java | 27 +++++++++++++ .../query/rescore/RescoreContextTests.java | 38 +++++++++++++++++- .../util/IndexHyperParametersUtilTests.java | 8 ++++ 10 files changed, 199 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opensearch/knn/index/engine/MethodComponent.java b/src/main/java/org/opensearch/knn/index/engine/MethodComponent.java index 75e18a243..d456ea89f 100644 --- a/src/main/java/org/opensearch/knn/index/engine/MethodComponent.java +++ b/src/main/java/org/opensearch/knn/index/engine/MethodComponent.java @@ -11,6 +11,8 @@ import org.opensearch.common.ValidationException; import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.index.util.IndexHyperParametersUtil; import java.util.HashMap; @@ -328,6 +330,15 @@ public static Map getParameterMapWithDefaultsAdded( Map parametersWithDefaultsMap = new HashMap<>(); Map userProvidedParametersMap = methodComponentContext.getParameters(); Version indexCreationVersion = knnMethodConfigContext.getVersionCreated(); + Mode mode = knnMethodConfigContext.getMode(); + CompressionLevel compressionLevel = knnMethodConfigContext.getCompressionLevel(); + + // Check if the mode is ON_DISK and the compression level is one of the binary quantization levels (x32, x16, or x8). + // This determines whether to use binary quantization-specific values for parameters like ef_search and ef_construction. + boolean isOnDiskWithBinaryQuantization = (compressionLevel == CompressionLevel.x32 + || compressionLevel == CompressionLevel.x16 + || compressionLevel == CompressionLevel.x8); + for (Parameter parameter : methodComponent.getParameters().values()) { if (methodComponentContext.getParameters().containsKey(parameter.getName())) { parametersWithDefaultsMap.put(parameter.getName(), userProvidedParametersMap.get(parameter.getName())); @@ -335,12 +346,27 @@ public static Map getParameterMapWithDefaultsAdded( // Picking the right values for the parameters whose values are different based on different index // created version. if (parameter.getName().equals(KNNConstants.METHOD_PARAMETER_EF_SEARCH)) { - parametersWithDefaultsMap.put(parameter.getName(), IndexHyperParametersUtil.getHNSWEFSearchValue(indexCreationVersion)); + if (isOnDiskWithBinaryQuantization) { + parametersWithDefaultsMap.put(parameter.getName(), IndexHyperParametersUtil.getBinaryQuantizationEFSearchValue()); + } else { + parametersWithDefaultsMap.put( + parameter.getName(), + IndexHyperParametersUtil.getHNSWEFSearchValue(indexCreationVersion) + ); + } } else if (parameter.getName().equals(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION)) { - parametersWithDefaultsMap.put( - parameter.getName(), - IndexHyperParametersUtil.getHNSWEFConstructionValue(indexCreationVersion) - ); + if (isOnDiskWithBinaryQuantization) { + parametersWithDefaultsMap.put( + parameter.getName(), + IndexHyperParametersUtil.getBinaryQuantizationEFConstructionValue() + ); + } else { + parametersWithDefaultsMap.put( + parameter.getName(), + IndexHyperParametersUtil.getHNSWEFConstructionValue(indexCreationVersion) + ); + } + } else { Object value = parameter.getDefaultValue(); if (value != null) { diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolver.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolver.java index 90e938eb3..c976a0959 100644 --- a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolver.java +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolver.java @@ -65,8 +65,6 @@ public ResolvedMethodContext resolveMethod( // Fill in parameters for the encoder and then the method. resolveEncoder(resolvedKNNMethodContext, knnMethodConfigContext, encoderMap); - resolveMethodParams(resolvedKNNMethodContext.getMethodComponentContext(), knnMethodConfigContext, method); - // From the resolved method context, get the compression level and validate it against the passed in // configuration CompressionLevel resolvedCompressionLevel = resolveCompressionLevelFromMethodContext( @@ -77,6 +75,9 @@ public ResolvedMethodContext resolveMethod( // Validate that resolved compression doesnt have any conflicts validateCompressionConflicts(knnMethodConfigContext.getCompressionLevel(), resolvedCompressionLevel); + knnMethodConfigContext.setCompressionLevel(resolvedCompressionLevel); + resolveMethodParams(resolvedKNNMethodContext.getMethodComponentContext(), knnMethodConfigContext, method); + return ResolvedMethodContext.builder() .knnMethodContext(resolvedKNNMethodContext) .compressionLevel(resolvedCompressionLevel) diff --git a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java index cc80bb1ed..222e042b6 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java +++ b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java @@ -25,9 +25,9 @@ public enum CompressionLevel { x1(1, "1x", null, Collections.emptySet()), x2(2, "2x", null, Collections.emptySet()), x4(4, "4x", null, Collections.emptySet()), - x8(8, "8x", new RescoreContext(1.5f), Set.of(Mode.ON_DISK)), - x16(16, "16x", new RescoreContext(2.0f), Set.of(Mode.ON_DISK)), - x32(32, "32x", new RescoreContext(2.0f), Set.of(Mode.ON_DISK)); + x8(8, "8x", new RescoreContext(2.0f), Set.of(Mode.ON_DISK)), + x16(16, "16x", new RescoreContext(3.0f), Set.of(Mode.ON_DISK)), + x32(32, "32x", new RescoreContext(3.0f), Set.of(Mode.ON_DISK)); // Internally, an empty string is easier to deal with them null. However, from the mapping, // we do not want users to pass in the empty string and instead want null. So we make the conversion herex diff --git a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java index 9fe2ddbc5..5e57b4aba 100644 --- a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java +++ b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java @@ -22,6 +22,9 @@ public final class RescoreContext { public static final int MAX_FIRST_PASS_RESULTS = 10000; + // Todo:- We will improve this in upcoming releases + public static final int MIN_FIRST_PASS_RESULTS = 100; + @Builder.Default private float oversampleFactor = DEFAULT_OVERSAMPLE_FACTOR; @@ -40,6 +43,6 @@ public static RescoreContext getDefault() { * @return The number of results to return for the first pass of rescoring */ public int getFirstPassK(int finalK) { - return Math.min(MAX_FIRST_PASS_RESULTS, (int) Math.ceil(finalK * oversampleFactor)); + return Math.min(MAX_FIRST_PASS_RESULTS, Math.max(MIN_FIRST_PASS_RESULTS, (int) Math.ceil(finalK * oversampleFactor))); } } diff --git a/src/main/java/org/opensearch/knn/index/util/IndexHyperParametersUtil.java b/src/main/java/org/opensearch/knn/index/util/IndexHyperParametersUtil.java index af842788a..c88a28e5c 100644 --- a/src/main/java/org/opensearch/knn/index/util/IndexHyperParametersUtil.java +++ b/src/main/java/org/opensearch/knn/index/util/IndexHyperParametersUtil.java @@ -28,6 +28,8 @@ public class IndexHyperParametersUtil { private static final int INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION_OLD_VALUE = 512; private static final int INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH_OLD_VALUE = 512; + private static final int INDEX_BINARY_QUANTIZATION_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION = 256; + private static final int INDEX_BINARY_QUANTIZATION_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH = 256; /** * Returns the default value of EF Construction that should be used for the input index version. After version 2.12.0 @@ -76,4 +78,22 @@ public static int getHNSWEFSearchValue(@NonNull final Version indexVersion) { ); return KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH; } + + /* + * Returns the default value of EF Construction that should be used with Binary Quantization. + * + * @return default value of EF Construction + */ + public static int getBinaryQuantizationEFConstructionValue() { + return INDEX_BINARY_QUANTIZATION_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION; + } + + /* + * Returns the default value of EF Search that should be used with Binary Quantization. + * + * @return default value of EF Search + */ + public static int getBinaryQuantizationEFSearchValue() { + return INDEX_BINARY_QUANTIZATION_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH; + } } diff --git a/src/test/java/org/opensearch/knn/index/engine/MethodComponentTests.java b/src/test/java/org/opensearch/knn/index/engine/MethodComponentTests.java index 7730095c7..e247fb70a 100644 --- a/src/test/java/org/opensearch/knn/index/engine/MethodComponentTests.java +++ b/src/test/java/org/opensearch/knn/index/engine/MethodComponentTests.java @@ -11,6 +11,9 @@ import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.index.util.IndexHyperParametersUtil; import java.io.IOException; import java.util.Map; @@ -214,4 +217,41 @@ public void testBuilder() { .getLibraryParameters() ); } + + /** + * Test the new flow where EF_SEARCH and EF_CONSTRUCTION are set for ON_DISK mode + * with binary quantization compression levels. + */ + public void testGetParameterMapWithDefaultsAdded_forOnDiskWithBinaryQuantization() { + // Set up MethodComponent and context + String methodName = "test-method"; + String parameterEFSearch = "ef_search"; + String parameterEFConstruction = "ef_construction"; + + MethodComponent methodComponent = MethodComponent.Builder.builder(methodName) + .addParameter(parameterEFSearch, new Parameter.IntegerParameter(parameterEFSearch, 512, (v, context) -> v > 0)) + .addParameter(parameterEFConstruction, new Parameter.IntegerParameter(parameterEFConstruction, 512, (v, context) -> v > 0)) + .build(); + + // Simulate ON_DISK mode and binary quantization compression levels + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .mode(Mode.ON_DISK) // ON_DISK mode + .compressionLevel(CompressionLevel.x32) // Binary quantization compression level + .build(); + + MethodComponentContext methodComponentContext = new MethodComponentContext(methodName, Map.of()); + + // Retrieve parameter map with defaults added + Map resultMap = MethodComponent.getParameterMapWithDefaultsAdded( + methodComponentContext, + methodComponent, + knnMethodConfigContext + ); + + // Check that binary quantization values are used + assertEquals(IndexHyperParametersUtil.getBinaryQuantizationEFSearchValue(), resultMap.get(parameterEFSearch)); + assertEquals(IndexHyperParametersUtil.getBinaryQuantizationEFConstructionValue(), resultMap.get(parameterEFConstruction)); + } + } diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolverTests.java index ad466d4bb..3a33736fa 100644 --- a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolverTests.java +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolverTests.java @@ -136,6 +136,33 @@ public void testResolveMethod_whenValid_thenResolve() { SpaceType.L2 ); validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x1, SpaceType.L2, ENCODER_FLAT, false); + + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .build(); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + new KNNMethodContext( + KNNEngine.FAISS, + SpaceType.L2, + new MethodComponentContext( + METHOD_HNSW, + Map.of( + METHOD_ENCODER_PARAMETER, + new MethodComponentContext( + QFrameBitEncoder.NAME, + Map.of(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x8.numBitsForFloat32()) + ) + ) + ) + ), + knnMethodConfigContext, + false, + SpaceType.L2 + ); + assertEquals(knnMethodConfigContext.getCompressionLevel(), CompressionLevel.x8); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x8, SpaceType.L2, QFrameBitEncoder.NAME, true); } private void validateResolveMethodContext( diff --git a/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java b/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java index 07475109a..9eb302b13 100644 --- a/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java +++ b/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java @@ -7,6 +7,7 @@ import org.opensearch.core.common.Strings; import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.query.rescore.RescoreContext; public class CompressionLevelTests extends KNNTestCase { @@ -39,4 +40,30 @@ public void testIsConfigured() { assertFalse(CompressionLevel.isConfigured(null)); assertTrue(CompressionLevel.isConfigured(CompressionLevel.x1)); } + + public void testGetDefaultRescoreContext() { + // Test rescore context for ON_DISK mode + Mode mode = Mode.ON_DISK; + + // x32 should have RescoreContext with an oversample factor of 3.0f + RescoreContext rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode); + assertNotNull(rescoreContext); + assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // x16 should have RescoreContext with an oversample factor of 3.0f + rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode); + assertNotNull(rescoreContext); + assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // x8 should have RescoreContext with an oversample factor of 2.0f + rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode); + assertNotNull(rescoreContext); + assertEquals(2.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // Other compression levels should not have a RescoreContext for ON_DISK mode + assertNull(CompressionLevel.x4.getDefaultRescoreContext(mode)); + assertNull(CompressionLevel.x2.getDefaultRescoreContext(mode)); + assertNull(CompressionLevel.x1.getDefaultRescoreContext(mode)); + assertNull(CompressionLevel.NOT_CONFIGURED.getDefaultRescoreContext(mode)); + } } diff --git a/src/test/java/org/opensearch/knn/index/query/rescore/RescoreContextTests.java b/src/test/java/org/opensearch/knn/index/query/rescore/RescoreContextTests.java index 6d872ddb8..fd94667db 100644 --- a/src/test/java/org/opensearch/knn/index/query/rescore/RescoreContextTests.java +++ b/src/test/java/org/opensearch/knn/index/query/rescore/RescoreContextTests.java @@ -8,6 +8,7 @@ import org.opensearch.knn.KNNTestCase; import static org.opensearch.knn.index.query.rescore.RescoreContext.MAX_FIRST_PASS_RESULTS; +import static org.opensearch.knn.index.query.rescore.RescoreContext.MIN_FIRST_PASS_RESULTS; public class RescoreContextTests extends KNNTestCase { @@ -17,10 +18,43 @@ public void testGetFirstPassK() { int finalK = 100; assertEquals(260, rescoreContext.getFirstPassK(finalK)); finalK = 1; - assertEquals(3, rescoreContext.getFirstPassK(finalK)); + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); finalK = 0; - assertEquals(0, rescoreContext.getFirstPassK(finalK)); + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); finalK = MAX_FIRST_PASS_RESULTS; assertEquals(MAX_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); } + + public void testGetFirstPassKWithMinPassK() { + float oversample = 2.6f; + RescoreContext rescoreContext = RescoreContext.builder().oversampleFactor(oversample).build(); + + // Case 1: Test with a finalK that results in a value greater than MIN_FIRST_PASS_RESULTS + int finalK = 100; + assertEquals(260, rescoreContext.getFirstPassK(finalK)); + + // Case 2: Test with a very small finalK that should result in a value less than MIN_FIRST_PASS_RESULTS + finalK = 1; + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); + + // Case 3: Test with finalK = 0, should return 0 + finalK = 0; + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); + + // Case 4: Test with finalK = MAX_FIRST_PASS_RESULTS, should cap at MAX_FIRST_PASS_RESULTS + finalK = MAX_FIRST_PASS_RESULTS; + assertEquals(MAX_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); + + // Case 5: Test where finalK * oversample is smaller than MIN_FIRST_PASS_RESULTS + finalK = 10; + oversample = 0.5f; // This will result in 5, which is less than MIN_FIRST_PASS_RESULTS + rescoreContext = RescoreContext.builder().oversampleFactor(oversample).build(); + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); + + // Case 6: Test where finalK * oversample results in exactly MIN_FIRST_PASS_RESULTS + finalK = 100; + oversample = 1.0f; // This will result in exactly 100 (MIN_FIRST_PASS_RESULTS) + rescoreContext = RescoreContext.builder().oversampleFactor(oversample).build(); + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); + } } diff --git a/src/test/java/org/opensearch/knn/index/util/IndexHyperParametersUtilTests.java b/src/test/java/org/opensearch/knn/index/util/IndexHyperParametersUtilTests.java index 508b8765c..96eb41476 100644 --- a/src/test/java/org/opensearch/knn/index/util/IndexHyperParametersUtilTests.java +++ b/src/test/java/org/opensearch/knn/index/util/IndexHyperParametersUtilTests.java @@ -41,4 +41,12 @@ public void testGetHNSWEFSearchValue_withDifferentValues_thenSuccess() { IndexHyperParametersUtil.getHNSWEFConstructionValue(Version.CURRENT) ); } + + public void testGetBinaryQuantizationEFValues_thenSuccess() { + // Test for Binary Quantization EF Construction value + Assert.assertEquals(256, IndexHyperParametersUtil.getBinaryQuantizationEFConstructionValue()); + + // Test for Binary Quantization EF Search value + Assert.assertEquals(256, IndexHyperParametersUtil.getBinaryQuantizationEFSearchValue()); + } } From 18c26f3aafe41900dd5d8be8c4747acd9cd90a7f Mon Sep 17 00:00:00 2001 From: Tejas Shah Date: Wed, 11 Sep 2024 19:17:44 -0700 Subject: [PATCH 08/59] Fixes spotless for restart upgrade (#2098) Signed-off-by: Tejas Shah --- .../src/test/java/org/opensearch/knn/bwc/IndexingIT.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java index 1fbc3696f..6b0bab9ee 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java @@ -90,7 +90,11 @@ public void testKNNIndexLuceneForceMerge() throws Exception { waitForClusterHealthGreen(NODES_BWC_CLUSTER); if (isRunningAgainstOldCluster()) { - createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS, METHOD_HNSW, LUCENE_NAME)); + createKnnIndex( + testIndex, + getKNNDefaultIndexSettings(), + createKnnIndexMapping(TEST_FIELD, DIMENSIONS, METHOD_HNSW, LUCENE_NAME) + ); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, 100); // Flush to ensure that index is not re-indexed when node comes back up flush(testIndex, true); From 5d10d64c80f71f383d3e8690d8502d133859ffbf Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Thu, 12 Sep 2024 13:27:12 -0700 Subject: [PATCH 09/59] Fix bug where quantization framework does not work with training (#2100) * Initial implementation Signed-off-by: Ryan Bogan * Modify integration test and fix bugs in jni Signed-off-by: Ryan Bogan * Fix unit test Signed-off-by: Ryan Bogan * Fix integration test after merge Signed-off-by: Ryan Bogan * Add changelog (release notes) Signed-off-by: Ryan Bogan * Add unit test Signed-off-by: Ryan Bogan * Remove entry for release notes Signed-off-by: Ryan Bogan * Add null checks Signed-off-by: Ryan Bogan --------- Signed-off-by: Ryan Bogan --- jni/src/faiss_wrapper.cpp | 25 +++++--- .../knn/index/mapper/CompressionLevel.java | 2 +- .../index/memory/NativeMemoryAllocation.java | 5 ++ .../memory/NativeMemoryEntryContext.java | 8 ++- .../memory/NativeMemoryLoadStrategy.java | 4 ++ .../TrainingModelTransportAction.java | 15 ++++- .../training/FloatTrainingDataConsumer.java | 60 +++++++++++++++++-- .../memory/NativeMemoryEntryContextTests.java | 19 ++++-- .../memory/NativeMemoryLoadStrategyTests.java | 4 +- .../knn/integ/ModeAndCompressionIT.java | 43 ++++--------- .../FloatTrainingDataConsumerTests.java | 44 ++++++++++++-- 11 files changed, 168 insertions(+), 61 deletions(-) diff --git a/jni/src/faiss_wrapper.cpp b/jni/src/faiss_wrapper.cpp index 227fcb477..45548e0f7 100644 --- a/jni/src/faiss_wrapper.cpp +++ b/jni/src/faiss_wrapper.cpp @@ -684,20 +684,29 @@ jobjectArray knn_jni::faiss_wrapper::QueryBinaryIndex_WithFilter(knn_jni::JNIUti } else { faiss::SearchParameters *searchParameters = nullptr; faiss::SearchParametersHNSW hnswParams; + faiss::SearchParametersIVF ivfParams; std::unique_ptr idGrouper; std::vector idGrouperBitmap; - auto hnswReader = dynamic_cast(indexReader->index); + auto ivfReader = dynamic_cast(indexReader->index); // TODO currently, search parameter is not supported in binary index // To avoid test failure, we skip setting ef search when methodPramsJ is null temporary - if(hnswReader!= nullptr && (methodParamsJ != nullptr || parentIdsJ != nullptr)) { - // Query param efsearch supersedes ef_search provided during index setting. - hnswParams.efSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, hnswReader->hnsw.efSearch); - if (parentIdsJ != nullptr) { - idGrouper = buildIDGrouperBitmap(jniUtil, env, parentIdsJ, &idGrouperBitmap); - hnswParams.grp = idGrouper.get(); + if (ivfReader) { + int indexNprobe = ivfReader->nprobe; + ivfParams.nprobe = commons::getIntegerMethodParameter(env, jniUtil, methodParams, NPROBES, indexNprobe); + searchParameters = &ivfParams; + } else { + auto hnswReader = dynamic_cast(indexReader->index); + if(hnswReader != nullptr && (methodParamsJ != nullptr || parentIdsJ != nullptr)) { + // Query param efsearch supersedes ef_search provided during index setting. + hnswParams.efSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, hnswReader->hnsw.efSearch); + if (parentIdsJ != nullptr) { + idGrouper = buildIDGrouperBitmap(jniUtil, env, parentIdsJ, &idGrouperBitmap); + hnswParams.grp = idGrouper.get(); + } + searchParameters = &hnswParams; } - searchParameters = &hnswParams; } + try { indexReader->search(1, reinterpret_cast(rawQueryvector), kJ, dis.data(), ids.data(), searchParameters); } catch (...) { diff --git a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java index 222e042b6..0709239cf 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java +++ b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java @@ -30,7 +30,7 @@ public enum CompressionLevel { x32(32, "32x", new RescoreContext(3.0f), Set.of(Mode.ON_DISK)); // Internally, an empty string is easier to deal with them null. However, from the mapping, - // we do not want users to pass in the empty string and instead want null. So we make the conversion herex + // we do not want users to pass in the empty string and instead want null. So we make the conversion here public static final String[] NAMES_ARRAY = new String[] { NOT_CONFIGURED.getName(), x1.getName(), diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java index 02b480ed4..0bb8a556f 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java @@ -12,9 +12,11 @@ package org.opensearch.knn.index.memory; import lombok.Getter; +import lombok.Setter; import org.apache.lucene.index.LeafReaderContext; import org.opensearch.knn.common.featureflags.KNNFeatureFlags; import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; import org.opensearch.knn.index.query.KNNWeight; import org.opensearch.knn.jni.JNIService; import org.opensearch.knn.index.engine.KNNEngine; @@ -252,6 +254,9 @@ class TrainingDataAllocation implements NativeMemoryAllocation { private volatile boolean closed; private long memoryAddress; private final int size; + @Getter + @Setter + private QuantizationConfig quantizationConfig = QuantizationConfig.EMPTY; // Implement reader/writer with semaphores to deal with passing lock conditions between threads private int readCount; diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java index 2dfc5fafb..dd219593d 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java @@ -11,8 +11,10 @@ package org.opensearch.knn.index.memory; +import lombok.Getter; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.Nullable; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; import org.opensearch.knn.index.util.IndexUtil; import org.opensearch.knn.index.VectorDataType; @@ -171,6 +173,8 @@ public static class TrainingDataEntryContext extends NativeMemoryEntryContext listener) { + KNNMethodContext knnMethodContext = request.getKnnMethodContext(); + KNNMethodConfigContext knnMethodConfigContext = request.getKnnMethodConfigContext(); + QuantizationConfig quantizationConfig = QuantizationConfig.EMPTY; + + if (knnMethodContext != null && request.getKnnMethodConfigContext() != null) { + KNNLibraryIndexingContext knnLibraryIndexingContext = knnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext); + quantizationConfig = knnLibraryIndexingContext.getQuantizationConfig(); + } NativeMemoryEntryContext.TrainingDataEntryContext trainingDataEntryContext = new NativeMemoryEntryContext.TrainingDataEntryContext( request.getTrainingDataSizeInKB(), @@ -54,7 +66,8 @@ protected void doExecute(Task task, TrainingModelRequest request, ActionListener clusterService, request.getMaximumVectorCount(), request.getSearchSize(), - request.getVectorDataType() + request.getVectorDataType(), + quantizationConfig ); // Allocation representing size model will occupy in memory during training diff --git a/src/main/java/org/opensearch/knn/training/FloatTrainingDataConsumer.java b/src/main/java/org/opensearch/knn/training/FloatTrainingDataConsumer.java index d742a9184..292752945 100644 --- a/src/main/java/org/opensearch/knn/training/FloatTrainingDataConsumer.java +++ b/src/main/java/org/opensearch/knn/training/FloatTrainingDataConsumer.java @@ -13,10 +13,19 @@ import org.apache.commons.lang.ArrayUtils; import org.opensearch.action.search.SearchResponse; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.jni.JNICommons; import org.opensearch.knn.jni.JNIService; import org.opensearch.knn.index.memory.NativeMemoryAllocation; +import org.opensearch.knn.quantization.factory.QuantizerFactory; +import org.opensearch.knn.quantization.models.quantizationOutput.BinaryQuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.knn.quantization.models.requests.TrainingRequest; +import org.opensearch.knn.quantization.quantizer.Quantizer; import org.opensearch.search.SearchHit; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -25,6 +34,8 @@ */ public class FloatTrainingDataConsumer extends TrainingDataConsumer { + private final QuantizationConfig quantizationConfig; + /** * Constructor * @@ -32,16 +43,28 @@ public class FloatTrainingDataConsumer extends TrainingDataConsumer { */ public FloatTrainingDataConsumer(NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation) { super(trainingDataAllocation); + this.quantizationConfig = trainingDataAllocation.getQuantizationConfig(); } @Override public void accept(List floats) { - trainingDataAllocation.setMemoryAddress( - JNIService.transferVectors( - trainingDataAllocation.getMemoryAddress(), - floats.stream().map(v -> ArrayUtils.toPrimitive((Float[]) v)).toArray(float[][]::new) - ) - ); + if (isValidFloatsAndQuantizationConfig(floats)) { + try { + List byteVectors = quantizeVectors(floats); + long memoryAddress = trainingDataAllocation.getMemoryAddress(); + memoryAddress = JNICommons.storeBinaryVectorData(memoryAddress, byteVectors.toArray(new byte[0][0]), byteVectors.size()); + trainingDataAllocation.setMemoryAddress(memoryAddress); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + trainingDataAllocation.setMemoryAddress( + JNIService.transferVectors( + trainingDataAllocation.getMemoryAddress(), + floats.stream().map(v -> ArrayUtils.toPrimitive((Float[]) v)).toArray(float[][]::new) + ) + ); + } } @Override @@ -64,4 +87,29 @@ public void processTrainingVectors(SearchResponse searchResponse, int vectorsToA accept(vectors); } + + private List quantizeVectors(List vectors) throws IOException { + List bytes = new ArrayList<>(); + ScalarQuantizationParams quantizationParams = new ScalarQuantizationParams(quantizationConfig.getQuantizationType()); + Quantizer quantizer = QuantizerFactory.getQuantizer(quantizationParams); + // Create training request + TrainingRequest trainingRequest = new TrainingRequest(vectors.size()) { + @Override + public float[] getVectorAtThePosition(int position) { + return ArrayUtils.toPrimitive((Float[]) vectors.get(position)); + } + }; + QuantizationState quantizationState = quantizer.train(trainingRequest); + BinaryQuantizationOutput binaryQuantizationOutput = new BinaryQuantizationOutput(quantizationConfig.getQuantizationType().getId()); + for (int i = 0; i < vectors.size(); i++) { + quantizer.quantize(ArrayUtils.toPrimitive((Float[]) vectors.get(i)), quantizationState, binaryQuantizationOutput); + bytes.add(binaryQuantizationOutput.getQuantizedVectorCopy()); + } + + return bytes; + } + + private boolean isValidFloatsAndQuantizationConfig(List floats) { + return floats != null && floats.isEmpty() == false && quantizationConfig != null && quantizationConfig != QuantizationConfig.EMPTY; + } } diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java index 385572cb4..1720da1ed 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java @@ -14,6 +14,7 @@ import com.google.common.collect.ImmutableMap; import org.opensearch.cluster.service.ClusterService; import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; import org.opensearch.knn.index.util.IndexUtil; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.KNNEngine; @@ -124,7 +125,8 @@ public void testTrainingDataEntryContext_load() { null, 0, 0, - VectorDataType.DEFAULT + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = new NativeMemoryAllocation.TrainingDataAllocation( @@ -149,7 +151,8 @@ public void testTrainingDataEntryContext_getTrainIndexName() { null, 0, 0, - VectorDataType.DEFAULT + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); assertEquals(trainIndexName, trainingDataEntryContext.getTrainIndexName()); @@ -165,7 +168,8 @@ public void testTrainingDataEntryContext_getTrainFieldName() { null, 0, 0, - VectorDataType.DEFAULT + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); assertEquals(trainFieldName, trainingDataEntryContext.getTrainFieldName()); @@ -181,7 +185,8 @@ public void testTrainingDataEntryContext_getMaxVectorCount() { null, maxVectorCount, 0, - VectorDataType.DEFAULT + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); assertEquals(maxVectorCount, trainingDataEntryContext.getMaxVectorCount()); @@ -197,7 +202,8 @@ public void testTrainingDataEntryContext_getSearchSize() { null, 0, searchSize, - VectorDataType.DEFAULT + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); assertEquals(searchSize, trainingDataEntryContext.getSearchSize()); @@ -213,7 +219,8 @@ public void testTrainingDataEntryContext_getIndicesService() { clusterService, 0, 0, - VectorDataType.DEFAULT + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); assertEquals(clusterService, trainingDataEntryContext.getClusterService()); diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java index 29fbdb978..bdd8d7e45 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java @@ -18,6 +18,7 @@ import org.opensearch.knn.TestUtils; import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; import org.opensearch.knn.jni.JNICommons; import org.opensearch.knn.jni.JNIService; import org.opensearch.knn.index.query.KNNQueryResult; @@ -180,7 +181,8 @@ public void testTrainingLoadStrategy_load() { null, 0, 0, - VectorDataType.FLOAT + VectorDataType.FLOAT, + QuantizationConfig.EMPTY ); // Load the allocation. Initially, the memory address should be 0. However, after the readlock is obtained, diff --git a/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java index 16c657ac6..f8960472c 100644 --- a/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java +++ b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java @@ -8,7 +8,6 @@ import lombok.SneakyThrows; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.junit.Assert; -import org.junit.Ignore; import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.ResponseException; @@ -252,12 +251,14 @@ public void testTraining_whenInvalid_thenFail() { // Training isnt currently supported for mode and compression because quantization framework does not quantize // the training vectors. So, commenting out for now. - @Ignore @SneakyThrows public void testTraining_whenValid_thenSucceed() { setupTrainingIndex(); XContentBuilder builder; for (String compressionLevel : CompressionLevel.NAMES_ARRAY) { + if (compressionLevel.equals("4x")) { + continue; + } String indexName = INDEX_NAME + compressionLevel; String modelId = indexName; builder = XContentFactory.jsonBuilder() @@ -287,38 +288,13 @@ public void testTraining_whenValid_thenSucceed() { compressionLevel, Mode.NOT_CONFIGURED.getName() ); + deleteKNNIndex(indexName); } - - for (String compressionLevel : CompressionLevel.NAMES_ARRAY) { - for (String mode : Mode.NAMES_ARRAY) { - String indexName = INDEX_NAME + compressionLevel + "_" + mode; - String modelId = indexName; - builder = XContentFactory.jsonBuilder() - .startObject() - .field(TRAIN_INDEX_PARAMETER, TRAINING_INDEX_NAME) - .field(TRAIN_FIELD_PARAMETER, TRAINING_FIELD_NAME) - .field(KNNConstants.DIMENSION, DIMENSION) - .field(MODEL_DESCRIPTION, "") - .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel) - .field(MODE_PARAMETER, mode) - .endObject(); - validateTraining(modelId, builder); - builder = XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(FIELD_NAME) - .field("type", "knn_vector") - .field("model_id", modelId) - .endObject() - .endObject() - .endObject(); - String mapping = builder.toString(); - validateIndex(indexName, mapping); - validateSearch(indexName, METHOD_PARAMETER_NPROBES, METHOD_PARAMETER_NLIST_DEFAULT, compressionLevel, mode); - } - } - for (String mode : Mode.NAMES_ARRAY) { + if (mode == null) { + continue; + } + mode = mode.toLowerCase(); String indexName = INDEX_NAME + mode; String modelId = indexName; builder = XContentFactory.jsonBuilder() @@ -348,8 +324,8 @@ public void testTraining_whenValid_thenSucceed() { CompressionLevel.NOT_CONFIGURED.getName(), mode ); + deleteKNNIndex(indexName); } - } @SneakyThrows @@ -459,6 +435,7 @@ private void validateSearch( String exactSearchResponseBody = EntityUtils.toString(exactSearchResponse.getEntity()); List exactSearchKnnResults = parseSearchResponseScore(exactSearchResponseBody, FIELD_NAME); assertEquals(NUM_DOCS, exactSearchKnnResults.size()); + if (CompressionLevel.x4.getName().equals(compressionLevelString) == false && Mode.ON_DISK.getName().equals(mode)) { Assert.assertEquals(exactSearchKnnResults, knnResults); } diff --git a/src/test/java/org/opensearch/knn/training/FloatTrainingDataConsumerTests.java b/src/test/java/org/opensearch/knn/training/FloatTrainingDataConsumerTests.java index 27e02b46b..6e0410853 100644 --- a/src/test/java/org/opensearch/knn/training/FloatTrainingDataConsumerTests.java +++ b/src/test/java/org/opensearch/knn/training/FloatTrainingDataConsumerTests.java @@ -13,7 +13,9 @@ import org.mockito.ArgumentCaptor; import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; import org.opensearch.knn.index.memory.NativeMemoryAllocation; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; import java.util.ArrayList; import java.util.Arrays; @@ -29,12 +31,46 @@ public void testAccept() { // Mock the training data allocation int dimension = 128; - NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = mock(NativeMemoryAllocation.TrainingDataAllocation.class); // new - // NativeMemoryAllocation.TrainingDataAllocation(0, - // numVectors*dimension* - // Float.BYTES); + NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = mock(NativeMemoryAllocation.TrainingDataAllocation.class); + when(trainingDataAllocation.getMemoryAddress()).thenReturn(0L); + when(trainingDataAllocation.getQuantizationConfig()).thenReturn(QuantizationConfig.EMPTY); + + // Capture argument passed to set pointer + ArgumentCaptor valueCapture = ArgumentCaptor.forClass(Long.class); + + FloatTrainingDataConsumer floatTrainingDataConsumer = new FloatTrainingDataConsumer(trainingDataAllocation); + + List vectorSet1 = new ArrayList<>(3); + for (int i = 0; i < 3; i++) { + Float[] vector = new Float[dimension]; + Arrays.fill(vector, (float) i); + vectorSet1.add(vector); + } + + // Transfer vectors + floatTrainingDataConsumer.accept(vectorSet1); + + // Ensure that the pointer captured has been updated + verify(trainingDataAllocation).setMemoryAddress(valueCapture.capture()); + when(trainingDataAllocation.getMemoryAddress()).thenReturn(valueCapture.getValue()); + + assertNotEquals(0, trainingDataAllocation.getMemoryAddress()); + } + + public void testAccept_withQuantizationConfig() { + + // Mock the training data allocation + int dimension = 128; + NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = mock(NativeMemoryAllocation.TrainingDataAllocation.class); + + when(trainingDataAllocation.getMemoryAddress()).thenReturn(0L); + + QuantizationConfig quantizationConfig = mock(QuantizationConfig.class); + when(quantizationConfig.getQuantizationType()).thenReturn(ScalarQuantizationType.ONE_BIT); + when(trainingDataAllocation.getQuantizationConfig()).thenReturn(QuantizationConfig.EMPTY); + // Capture argument passed to set pointer ArgumentCaptor valueCapture = ArgumentCaptor.forClass(Long.class); From 7aedefd9f0229ce4ae3832b5dd7ae26e6e864983 Mon Sep 17 00:00:00 2001 From: Vijayan Balasubramanian Date: Thu, 12 Sep 2024 16:08:22 -0700 Subject: [PATCH 10/59] Add short circuit if no live docs are in segments (#2059) * Add short circuit if no live docs are in segments Signed-off-by: Vijayan Balasubramanian --------- Signed-off-by: Vijayan Balasubramanian --- CHANGELOG.md | 1 + .../NativeEngines990KnnVectorsWriter.java | 27 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70bcb63ce..c6ebc3c3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased 2.x](https://github.com/opensearch-project/k-NN/compare/2.17...2.x) ### Features ### Enhancements +* Add short circuit if no live docs are in segments [#2059](https://github.com/opensearch-project/k-NN/pull/2059) ### Bug Fixes ### Infrastructure ### Documentation diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java index dba0926ff..0d016a60b 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java @@ -244,26 +244,25 @@ private void trainAndIndex( final String operationName ) throws IOException { final VectorDataType vectorDataType = extractVectorDataType(fieldInfo); - KNNVectorValues knnVectorValues = vectorValuesRetriever.apply(vectorDataType, fieldInfo, VectorProcessingContext); - QuantizationParams quantizationParams = quantizationService.getQuantizationParams(fieldInfo); - QuantizationState quantizationState = null; // Count the docIds int totalLiveDocs = getLiveDocs(vectorValuesRetriever.apply(vectorDataType, fieldInfo, VectorProcessingContext)); - if (quantizationParams != null && totalLiveDocs > 0) { + if (totalLiveDocs == 0) { + log.debug("No live docs for field " + fieldInfo.name); + return; + } + QuantizationState quantizationState = null; + QuantizationParams quantizationParams = quantizationService.getQuantizationParams(fieldInfo); + if (quantizationParams != null) { initQuantizationStateWriterIfNecessary(); + KNNVectorValues knnVectorValues = vectorValuesRetriever.apply(vectorDataType, fieldInfo, VectorProcessingContext); quantizationState = quantizationService.train(quantizationParams, knnVectorValues, totalLiveDocs); quantizationStateWriter.writeState(fieldInfo.getFieldNumber(), quantizationState); } - NativeIndexWriter writer = (quantizationParams != null) - ? NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState) - : NativeIndexWriter.getWriter(fieldInfo, segmentWriteState); - - knnVectorValues = vectorValuesRetriever.apply(vectorDataType, fieldInfo, VectorProcessingContext); - - StopWatch stopWatch = new StopWatch(); - stopWatch.start(); - indexOperation.buildAndWrite(writer, knnVectorValues, totalLiveDocs); - long time_in_millis = stopWatch.totalTime().millis(); + NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); + KNNVectorValues knnVectors = vectorValuesRetriever.apply(vectorDataType, fieldInfo, VectorProcessingContext); + StopWatch stopWatch = new StopWatch().start(); + indexOperation.buildAndWrite(writer, knnVectors, totalLiveDocs); + long time_in_millis = stopWatch.stop().totalTime().millis(); graphBuildTime.incrementBy(time_in_millis); log.warn("Graph build took " + time_in_millis + " ms for " + operationName); } From 0df06131fa51aa5f5c714472d9bc7153e4d1818f Mon Sep 17 00:00:00 2001 From: Tejas Shah Date: Fri, 13 Sep 2024 14:19:31 -0700 Subject: [PATCH 11/59] Adds Unit tests for NativeEngines990KnnVectorsWriter (#2097) Had to separate out the common code to make it easy to write tests Mocking was difficult to do with the functional interfaces and it was throwing NPE in the test especially with the mock of NativeIndexWriter. Signed-off-by: Tejas Shah --- .../NativeEngines990KnnVectorsWriter.java | 152 ++++----- ...eEngines990KnnVectorsWriterFlushTests.java | 288 ++++++++++++++++++ ...eEngines990KnnVectorsWriterMergeTests.java | 240 +++++++++++++++ .../index/vectorvalues/TestVectorValues.java | 6 +- 4 files changed, 585 insertions(+), 101 deletions(-) create mode 100644 src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java create mode 100644 src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java index 0d016a60b..3f32003ac 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java @@ -25,11 +25,10 @@ import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.RamUsageEstimator; import org.opensearch.common.StopWatch; -import org.opensearch.knn.index.quantizationservice.QuantizationService; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.codec.nativeindex.NativeIndexWriter; +import org.opensearch.knn.index.quantizationservice.QuantizationService; import org.opensearch.knn.index.vectorvalues.KNNVectorValues; -import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; import org.opensearch.knn.plugin.stats.KNNGraphValue; import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; @@ -39,6 +38,7 @@ import java.util.List; import static org.opensearch.knn.common.FieldInfoExtractor.extractVectorDataType; +import static org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory.getVectorValues; /** * A KNNVectorsWriter class for writing the vector data strcutures and flat vectors for Native Engines. @@ -47,15 +47,11 @@ public class NativeEngines990KnnVectorsWriter extends KnnVectorsWriter { private static final long SHALLOW_SIZE = RamUsageEstimator.shallowSizeOfInstance(NativeEngines990KnnVectorsWriter.class); - private static final String FLUSH_OPERATION = "flush"; - private static final String MERGE_OPERATION = "merge"; - private final SegmentWriteState segmentWriteState; private final FlatVectorsWriter flatVectorsWriter; private KNN990QuantizationStateWriter quantizationStateWriter; private final List> fields = new ArrayList<>(); private boolean finished; - private final QuantizationService quantizationService = QuantizationService.getInstance(); public NativeEngines990KnnVectorsWriter(SegmentWriteState segmentWriteState, FlatVectorsWriter flatVectorsWriter) { this.segmentWriteState = segmentWriteState; @@ -84,14 +80,27 @@ public void flush(int maxDoc, final Sorter.DocMap sortMap) throws IOException { flatVectorsWriter.flush(maxDoc, sortMap); for (final NativeEngineFieldVectorsWriter field : fields) { - trainAndIndex( - field.getFieldInfo(), - (vectorDataType, fieldInfo, fieldVectorsWriter) -> getKNNVectorValues(vectorDataType, fieldVectorsWriter), - NativeIndexWriter::flushIndex, - field, - KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS, - FLUSH_OPERATION - ); + final FieldInfo fieldInfo = field.getFieldInfo(); + final VectorDataType vectorDataType = extractVectorDataType(fieldInfo); + int totalLiveDocs = getLiveDocs(getVectorValues(vectorDataType, field.getDocsWithField(), field.getVectors())); + if (totalLiveDocs > 0) { + KNNVectorValues knnVectorValues = getVectorValues(vectorDataType, field.getDocsWithField(), field.getVectors()); + + final QuantizationState quantizationState = train(field.getFieldInfo(), knnVectorValues, totalLiveDocs); + final NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); + + knnVectorValues = getVectorValues(vectorDataType, field.getDocsWithField(), field.getVectors()); + + StopWatch stopWatch = new StopWatch().start(); + + writer.flushIndex(knnVectorValues, totalLiveDocs); + + long time_in_millis = stopWatch.stop().totalTime().millis(); + KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.incrementBy(time_in_millis); + log.debug("Flush took {} ms for vector field [{}]", time_in_millis, fieldInfo.getName()); + } else { + log.debug("[Flush] No live docs for field {}", fieldInfo.getName()); + } } } @@ -100,15 +109,26 @@ public void mergeOneField(final FieldInfo fieldInfo, final MergeState mergeState // This will ensure that we are merging the FlatIndex during force merge. flatVectorsWriter.mergeOneField(fieldInfo, mergeState); - // For merge, pick values from flat vector and reindex again. This will use the flush operation to create graphs - trainAndIndex( - fieldInfo, - this::getKNNVectorValuesForMerge, - NativeIndexWriter::mergeIndex, - mergeState, - KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS, - MERGE_OPERATION - ); + final VectorDataType vectorDataType = extractVectorDataType(fieldInfo); + int totalLiveDocs = getLiveDocs(getKNNVectorValuesForMerge(vectorDataType, fieldInfo, mergeState)); + if (totalLiveDocs == 0) { + log.debug("[Merge] No live docs for field {}", fieldInfo.getName()); + return; + } + + KNNVectorValues knnVectorValues = getKNNVectorValuesForMerge(vectorDataType, fieldInfo, mergeState); + final QuantizationState quantizationState = train(fieldInfo, knnVectorValues, totalLiveDocs); + final NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); + + knnVectorValues = getKNNVectorValuesForMerge(vectorDataType, fieldInfo, mergeState); + + StopWatch stopWatch = new StopWatch().start(); + + writer.mergeIndex(knnVectorValues, totalLiveDocs); + + long time_in_millis = stopWatch.stop().totalTime().millis(); + KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.incrementBy(time_in_millis); + log.debug("Merge took {} ms for vector field [{}]", time_in_millis, fieldInfo.getName()); } /** @@ -157,18 +177,6 @@ public long ramBytesUsed() { .sum(); } - /** - * Retrieves the {@link KNNVectorValues} for a specific field based on the vector data type and field writer. - * - * @param vectorDataType The {@link VectorDataType} representing the type of vectors stored. - * @param field The {@link NativeEngineFieldVectorsWriter} representing the field from which to retrieve vectors. - * @param The type of vectors being processed. - * @return The {@link KNNVectorValues} associated with the field. - */ - private KNNVectorValues getKNNVectorValues(final VectorDataType vectorDataType, final NativeEngineFieldVectorsWriter field) { - return (KNNVectorValues) KNNVectorValuesFactory.getVectorValues(vectorDataType, field.getDocsWithField(), field.getVectors()); - } - /** * Retrieves the {@link KNNVectorValues} for a specific field during a merge operation, based on the vector data type. * @@ -187,84 +195,28 @@ private KNNVectorValues getKNNVectorValuesForMerge( switch (fieldInfo.getVectorEncoding()) { case FLOAT32: FloatVectorValues mergedFloats = MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState); - return (KNNVectorValues) KNNVectorValuesFactory.getVectorValues(vectorDataType, mergedFloats); + return getVectorValues(vectorDataType, mergedFloats); case BYTE: ByteVectorValues mergedBytes = MergedVectorValues.mergeByteVectorValues(fieldInfo, mergeState); - return (KNNVectorValues) KNNVectorValuesFactory.getVectorValues(vectorDataType, mergedBytes); + return getVectorValues(vectorDataType, mergedBytes); default: throw new IllegalStateException("Unsupported vector encoding [" + fieldInfo.getVectorEncoding() + "]"); } } - /** - * Functional interface representing an operation that indexes the provided {@link KNNVectorValues}. - * - * @param The type of vectors being processed. - */ - @FunctionalInterface - private interface IndexOperation { - void buildAndWrite(NativeIndexWriter writer, KNNVectorValues knnVectorValues, int totalLiveDocs) throws IOException; - } - - /** - * Functional interface representing a method that retrieves {@link KNNVectorValues} based on - * the vector data type, field information, and the merge state. - * - * @param The type of the data representing the vector (e.g., {@link VectorDataType}). - * @param The metadata about the field. - * @param The state of the merge operation. - * @param The result of the retrieval, typically {@link KNNVectorValues}. - */ - @FunctionalInterface - private interface VectorValuesRetriever { - Result apply(DataType vectorDataType, FieldInfo fieldInfo, MergeState mergeState) throws IOException; - } + private QuantizationState train(final FieldInfo fieldInfo, final KNNVectorValues knnVectorValues, final int totalLiveDocs) + throws IOException { - /** - * Unified method for processing a field during either the indexing or merge operation. This method retrieves vector values - * based on the provided vector data type and applies the specified index operation, potentially including quantization if needed. - * - * @param fieldInfo The {@link FieldInfo} object containing metadata about the field. - * @param vectorValuesRetriever A functional interface that retrieves {@link KNNVectorValues} based on the vector data type, - * field information, and additional context (e.g., merge state or field writer). - * @param indexOperation A functional interface that performs the indexing operation using the retrieved - * {@link KNNVectorValues}. - * @param VectorProcessingContext The additional context required for retrieving the vector values (e.g., {@link MergeState} or {@link NativeEngineFieldVectorsWriter}). - * From Flush we need NativeFieldWriter which contains total number of vectors while from Merge we need merge state which contains vector information - * @param The type of vectors being processed. - * @param The type of the context needed for retrieving the vector values. - * @throws IOException If an I/O error occurs during the processing. - */ - private void trainAndIndex( - final FieldInfo fieldInfo, - final VectorValuesRetriever> vectorValuesRetriever, - final IndexOperation indexOperation, - final C VectorProcessingContext, - final KNNGraphValue graphBuildTime, - final String operationName - ) throws IOException { - final VectorDataType vectorDataType = extractVectorDataType(fieldInfo); - // Count the docIds - int totalLiveDocs = getLiveDocs(vectorValuesRetriever.apply(vectorDataType, fieldInfo, VectorProcessingContext)); - if (totalLiveDocs == 0) { - log.debug("No live docs for field " + fieldInfo.name); - return; - } + final QuantizationService quantizationService = QuantizationService.getInstance(); + final QuantizationParams quantizationParams = quantizationService.getQuantizationParams(fieldInfo); QuantizationState quantizationState = null; - QuantizationParams quantizationParams = quantizationService.getQuantizationParams(fieldInfo); - if (quantizationParams != null) { + if (quantizationParams != null && totalLiveDocs > 0) { initQuantizationStateWriterIfNecessary(); - KNNVectorValues knnVectorValues = vectorValuesRetriever.apply(vectorDataType, fieldInfo, VectorProcessingContext); quantizationState = quantizationService.train(quantizationParams, knnVectorValues, totalLiveDocs); quantizationStateWriter.writeState(fieldInfo.getFieldNumber(), quantizationState); } - NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); - KNNVectorValues knnVectors = vectorValuesRetriever.apply(vectorDataType, fieldInfo, VectorProcessingContext); - StopWatch stopWatch = new StopWatch().start(); - indexOperation.buildAndWrite(writer, knnVectors, totalLiveDocs); - long time_in_millis = stopWatch.stop().totalTime().millis(); - graphBuildTime.incrementBy(time_in_millis); - log.warn("Graph build took " + time_in_millis + " ms for " + operationName); + + return quantizationState; } /** diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java new file mode 100644 index 000000000..ad72f5b24 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java @@ -0,0 +1,288 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; +import org.apache.lucene.index.DocsWithFieldSet; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.index.VectorEncoding; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.nativeindex.NativeIndexWriter; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; +import org.opensearch.knn.plugin.stats.KNNGraphValue; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; + +import static com.carrotsearch.randomizedtesting.RandomizedTest.$; +import static com.carrotsearch.randomizedtesting.RandomizedTest.$$; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RequiredArgsConstructor +public class NativeEngines990KnnVectorsWriterFlushTests extends OpenSearchTestCase { + + @Mock + private FlatVectorsWriter flatVectorsWriter; + @Mock + private SegmentWriteState segmentWriteState; + @Mock + private QuantizationParams quantizationParams; + @Mock + private QuantizationState quantizationState; + @Mock + private QuantizationService quantizationService; + @Mock + private NativeIndexWriter nativeIndexWriter; + + private NativeEngines990KnnVectorsWriter objectUnderTest; + + private final String description; + private final List> vectorsPerField; + + @Override + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.openMocks(this); + objectUnderTest = new NativeEngines990KnnVectorsWriter(segmentWriteState, flatVectorsWriter); + } + + @ParametersFactory + public static Collection data() { + return Arrays.asList( + $$( + $("Single field", List.of(Map.of(0, new float[] { 1, 2, 3 }, 1, new float[] { 2, 3, 4 }, 2, new float[] { 3, 4, 5 }))), + $("Single field, no total live docs", List.of()), + $( + "Multi Field", + List.of( + Map.of(0, new float[] { 1, 2, 3 }, 1, new float[] { 2, 3, 4 }, 2, new float[] { 3, 4, 5 }), + Map.of( + 0, + new float[] { 1, 2, 3, 4 }, + 1, + new float[] { 2, 3, 4, 5 }, + 2, + new float[] { 3, 4, 5, 6 }, + 3, + new float[] { 4, 5, 6, 7 } + ) + ) + ) + ) + ); + } + + @SneakyThrows + public void testFlush() { + // Given + List> expectedVectorValues = new ArrayList<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + expectedVectorValues.add(knnVectorValues); + + }); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when(() -> NativeEngineFieldVectorsWriter.create(fieldInfo, segmentWriteState.infoStream)) + .thenReturn(field); + + try { + objectUnderTest.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + }); + + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + objectUnderTest.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + assertNotEquals(0L, (long) KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.getValue()); + } + + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + } + + @SneakyThrows + public void testFlush_WithQuantization() { + // Given + List> expectedVectorValues = new ArrayList<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + expectedVectorValues.add(knnVectorValues); + + }); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when(() -> NativeEngineFieldVectorsWriter.create(fieldInfo, segmentWriteState.infoStream)) + .thenReturn(field); + + try { + objectUnderTest.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(quantizationParams); + try { + when(quantizationService.train(quantizationParams, expectedVectorValues.get(i), vectorsPerField.get(i).size())) + .thenReturn(quantizationState); + } catch (Exception e) { + throw new RuntimeException(e); + } + + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState)) + .thenReturn(nativeIndexWriter); + }); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + objectUnderTest.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeHeader(segmentWriteState); + assertTrue(KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.getValue() > 0L); + } else { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeState(i, quantizationState); + verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + } + + private FieldInfo fieldInfo(int fieldNumber, VectorEncoding vectorEncoding, Map attributes) { + FieldInfo fieldInfo = mock(FieldInfo.class); + when(fieldInfo.getFieldNumber()).thenReturn(fieldNumber); + when(fieldInfo.getVectorEncoding()).thenReturn(vectorEncoding); + when(fieldInfo.attributes()).thenReturn(attributes); + attributes.forEach((key, value) -> when(fieldInfo.getAttribute(key)).thenReturn(value)); + return fieldInfo; + } + + private NativeEngineFieldVectorsWriter nativeEngineFieldVectorsWriter(FieldInfo fieldInfo, Map vectors) { + NativeEngineFieldVectorsWriter fieldVectorsWriter = mock(NativeEngineFieldVectorsWriter.class); + DocsWithFieldSet docsWithFieldSet = new DocsWithFieldSet(); + vectors.keySet().stream().sorted().forEach(docsWithFieldSet::add); + when(fieldVectorsWriter.getFieldInfo()).thenReturn(fieldInfo); + when(fieldVectorsWriter.getVectors()).thenReturn(vectors); + when(fieldVectorsWriter.getDocsWithField()).thenReturn(docsWithFieldSet); + return fieldVectorsWriter; + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java new file mode 100644 index 000000000..440e8bbc5 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java @@ -0,0 +1,240 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.apache.lucene.codecs.KnnVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; +import org.apache.lucene.index.DocsWithFieldSet; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.MergeState; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.index.VectorEncoding; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.nativeindex.NativeIndexWriter; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; +import org.opensearch.knn.plugin.stats.KNNGraphValue; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +import static com.carrotsearch.randomizedtesting.RandomizedTest.$; +import static com.carrotsearch.randomizedtesting.RandomizedTest.$$; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@RequiredArgsConstructor +public class NativeEngines990KnnVectorsWriterMergeTests extends OpenSearchTestCase { + + @Mock + private FlatVectorsWriter flatVectorsWriter; + @Mock + private SegmentWriteState segmentWriteState; + @Mock + private QuantizationParams quantizationParams; + @Mock + private QuantizationState quantizationState; + @Mock + private QuantizationService quantizationService; + @Mock + private NativeIndexWriter nativeIndexWriter; + @Mock + private FloatVectorValues floatVectorValues; + @Mock + private MergeState mergeState; + + private NativeEngines990KnnVectorsWriter objectUnderTest; + + private final String description; + private final Map mergedVectors; + + @Override + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.openMocks(this); + objectUnderTest = new NativeEngines990KnnVectorsWriter(segmentWriteState, flatVectorsWriter); + } + + @ParametersFactory + public static Collection data() { + return Arrays.asList( + $$( + $("Merge one field", Map.of(0, new float[] { 1, 2, 3 }, 1, new float[] { 2, 3, 4 }, 2, new float[] { 3, 4, 5 })), + $("Merge, no live docs", Map.of()) + ) + ); + } + + @SneakyThrows + public void testMerge() { + // Given + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(mergedVectors.values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedStatic mergedVectorValuesMockedStatic = mockStatic( + KnnVectorsWriter.MergedVectorValues.class + ); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + final FieldInfo fieldInfo = fieldInfo( + 0, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, mergedVectors); + fieldWriterMockedStatic.when(() -> NativeEngineFieldVectorsWriter.create(fieldInfo, segmentWriteState.infoStream)) + .thenReturn(field); + + mergedVectorValuesMockedStatic.when(() -> KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState)) + .thenReturn(floatVectorValues); + knnVectorValuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues)) + .thenReturn(knnVectorValues); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).mergeIndex(any(), anyInt()); + + // When + objectUnderTest.mergeOneField(fieldInfo, mergeState); + + // Then + verify(flatVectorsWriter).mergeOneField(fieldInfo, mergeState); + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + if (!mergedVectors.isEmpty()) { + verify(nativeIndexWriter).mergeIndex(knnVectorValues, mergedVectors.size()); + assertTrue(KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.getValue() > 0L); + } else { + verifyNoInteractions(nativeIndexWriter); + } + } + } + + @SneakyThrows + public void testMerge_WithQuantization() { + // Given + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(mergedVectors.values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + MockedStatic mergedVectorValuesMockedStatic = mockStatic( + KnnVectorsWriter.MergedVectorValues.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + + final FieldInfo fieldInfo = fieldInfo( + 0, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, mergedVectors); + fieldWriterMockedStatic.when(() -> NativeEngineFieldVectorsWriter.create(fieldInfo, segmentWriteState.infoStream)) + .thenReturn(field); + + mergedVectorValuesMockedStatic.when(() -> KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState)) + .thenReturn(floatVectorValues); + knnVectorValuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues)) + .thenReturn(knnVectorValues); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(quantizationParams); + try { + when(quantizationService.train(quantizationParams, knnVectorValues, mergedVectors.size())).thenReturn(quantizationState); + } catch (Exception e) { + throw new RuntimeException(e); + } + + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState)) + .thenReturn(nativeIndexWriter); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).mergeIndex(any(), anyInt()); + + // When + objectUnderTest.mergeOneField(fieldInfo, mergeState); + + // Then + verify(flatVectorsWriter).mergeOneField(fieldInfo, mergeState); + if (!mergedVectors.isEmpty()) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeHeader(segmentWriteState); + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeState(0, quantizationState); + verify(nativeIndexWriter).mergeIndex(knnVectorValues, mergedVectors.size()); + assertTrue(KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.getValue() > 0L); + } else { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + verifyNoInteractions(nativeIndexWriter); + } + + } + } + + private FieldInfo fieldInfo(int fieldNumber, VectorEncoding vectorEncoding, Map attributes) { + FieldInfo fieldInfo = mock(FieldInfo.class); + when(fieldInfo.getFieldNumber()).thenReturn(fieldNumber); + when(fieldInfo.getVectorEncoding()).thenReturn(vectorEncoding); + when(fieldInfo.attributes()).thenReturn(attributes); + attributes.forEach((key, value) -> when(fieldInfo.getAttribute(key)).thenReturn(value)); + return fieldInfo; + } + + private NativeEngineFieldVectorsWriter nativeEngineFieldVectorsWriter(FieldInfo fieldInfo, Map vectors) { + NativeEngineFieldVectorsWriter fieldVectorsWriter = mock(NativeEngineFieldVectorsWriter.class); + DocsWithFieldSet docsWithFieldSet = new DocsWithFieldSet(); + vectors.keySet().stream().sorted().forEach(docsWithFieldSet::add); + when(fieldVectorsWriter.getFieldInfo()).thenReturn(fieldInfo); + when(fieldVectorsWriter.getVectors()).thenReturn(vectors); + when(fieldVectorsWriter.getDocsWithField()).thenReturn(docsWithFieldSet); + return fieldVectorsWriter; + } +} diff --git a/src/test/java/org/opensearch/knn/index/vectorvalues/TestVectorValues.java b/src/test/java/org/opensearch/knn/index/vectorvalues/TestVectorValues.java index 3bf79b004..0f15d5240 100644 --- a/src/test/java/org/opensearch/knn/index/vectorvalues/TestVectorValues.java +++ b/src/test/java/org/opensearch/knn/index/vectorvalues/TestVectorValues.java @@ -184,7 +184,11 @@ public static class PreDefinedFloatVectorValues extends FloatVectorValues { public PreDefinedFloatVectorValues(final List vectors) { super(); this.count = vectors.size(); - this.dimension = vectors.get(0).length; + if (!vectors.isEmpty()) { + this.dimension = vectors.get(0).length; + } else { + this.dimension = 0; + } this.vectors = vectors; this.current = -1; vector = new float[dimension]; From 8277bf0f57a045112dfa7098f64b26dac4b63652 Mon Sep 17 00:00:00 2001 From: Vijayan Balasubramanian Date: Fri, 13 Sep 2024 14:53:03 -0700 Subject: [PATCH 12/59] Fix native engine vector format test (#2103) Previosuly we were not creating hnsw file on segment flush for faiss engine. After successfully integrating hnsw file creation, we forgot to update unit test. Here, we will confirm that required files are being created based on field type. Signed-off-by: Vijayan Balasubramanian --- .../NativeEngines990KnnVectorsFormatTests.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormatTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormatTests.java index f1e48c3a1..90ed18d0d 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormatTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormatTests.java @@ -81,9 +81,9 @@ public class NativeEngines990KnnVectorsFormatTests extends KNNTestCase { private static final Codec TESTING_CODEC = new UnitTestCodec(); private static final String FLAT_VECTOR_FILE_EXT = ".vec"; - private static final String HNSW_FILE_EXT = ".hnsw"; + private static final String FAISS_ENGINE_FILE_EXT = ".faiss"; private static final String FLOAT_VECTOR_FIELD = "float_field"; - private static final String FLOAT_VECTOR_FIELD_BINARY = "float_field_binary"; + private static final String FLOAT_VECTOR_FIELD_BINARY = "float_binary_field"; private static final String BYTE_VECTOR_FIELD = "byte_field"; private Directory dir; private RandomIndexWriter indexWriter; @@ -220,11 +220,11 @@ public void testNativeEngineVectorFormat_whenMultipleVectorFieldIndexed_thenSucc IndexSearcher searcher = new IndexSearcher(indexReader); final LeafReader leafReader = searcher.getLeafContexts().get(0).reader(); SegmentReader segmentReader = Lucene.segmentReader(leafReader); - final List hnswfiles = getFilesFromSegment(dir, HNSW_FILE_EXT); - // 0 hnsw files for now as we have not integrated graph creation here. - assertEquals(0, hnswfiles.size()); - assertEquals(hnswfiles.stream().filter(x -> x.contains(FLOAT_VECTOR_FIELD)).count(), 0); - assertEquals(hnswfiles.stream().filter(x -> x.contains(BYTE_VECTOR_FIELD)).count(), 0); + final List hnswfiles = getFilesFromSegment(dir, FAISS_ENGINE_FILE_EXT); + assertEquals(3, hnswfiles.size()); + assertEquals(hnswfiles.stream().filter(x -> x.contains(FLOAT_VECTOR_FIELD)).count(), 1); + assertEquals(hnswfiles.stream().filter(x -> x.contains(BYTE_VECTOR_FIELD)).count(), 1); + assertEquals(hnswfiles.stream().filter(x -> x.contains(FLOAT_VECTOR_FIELD_BINARY)).count(), 1); // Even setting IWC to not use compound file it still uses compound file, hence ensuring we don't check .vec // file in case segment uses compound format. use this seed once we fix this to validate everything is From 004fcc0aa93476ce20e9940baf24d69f62a8f47b Mon Sep 17 00:00:00 2001 From: luyuncheng Date: Sat, 14 Sep 2024 10:43:39 +0800 Subject: [PATCH 13/59] Add DocValuesProducers for releasing memory when close index (#1946) Add DocValuesProducers for releasing memory when close index #1946 --- CHANGELOG.md | 3 +- .../KNN80Codec/KNN80CompoundDirectory.java | 63 ++++++++ .../codec/KNN80Codec/KNN80CompoundFormat.java | 2 +- .../KNN80Codec/KNN80DocValuesFormat.java | 2 +- .../KNN80Codec/KNN80DocValuesProducer.java | 143 ++++++++++++++++++ .../knn/index/codec/util/KNNCodecUtil.java | 31 ++++ .../index/memory/NativeMemoryAllocation.java | 43 +++++- .../opensearch/knn/index/query/KNNWeight.java | 26 +--- .../opensearch/knn/index/OpenSearchIT.java | 124 +++++++++++++++ .../KNN80Codec/KNN80CompoundFormatTests.java | 4 +- .../KNN80DocValuesProducerTests.java | 130 ++++++++++++++++ .../index/codec/util/KNNCodecUtilTests.java | 18 +++ .../knn/index/query/KNNWeightTests.java | 3 +- .../org/opensearch/knn/KNNRestTestCase.java | 6 + 14 files changed, 569 insertions(+), 29 deletions(-) create mode 100644 src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundDirectory.java create mode 100644 src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java create mode 100644 src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c6ebc3c3f..1a8d808f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased 3.0](https://github.com/opensearch-project/k-NN/compare/2.x...HEAD) ### Features ### Enhancements -### Bug Fixes +### Bug Fixes +* Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) ### Infrastructure * Removed JDK 11 and 17 version from CI runs [#1921](https://github.com/opensearch-project/k-NN/pull/1921) ### Documentation diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundDirectory.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundDirectory.java new file mode 100644 index 000000000..0821b19ef --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundDirectory.java @@ -0,0 +1,63 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN80Codec; + +import lombok.Getter; +import org.apache.lucene.codecs.CompoundDirectory; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.io.IOException; +import java.util.Set; + +public class KNN80CompoundDirectory extends CompoundDirectory { + + @Getter + private CompoundDirectory delegate; + @Getter + private Directory dir; + + public KNN80CompoundDirectory(CompoundDirectory delegate, Directory dir) { + this.delegate = delegate; + this.dir = dir; + } + + @Override + public void checkIntegrity() throws IOException { + delegate.checkIntegrity(); + } + + @Override + public String[] listAll() throws IOException { + return delegate.listAll(); + } + + @Override + public long fileLength(String name) throws IOException { + return delegate.fileLength(name); + } + + @Override + public IndexInput openInput(String name, IOContext context) throws IOException { + if (KNNEngine.getEnginesThatCreateCustomSegmentFiles().stream().anyMatch(engine -> name.endsWith(engine.getCompoundExtension()))) { + return dir.openInput(name, context); + } + return delegate.openInput(name, context); + } + + @Override + public void close() throws IOException { + delegate.close(); + } + + @Override + public Set getPendingDeletions() throws IOException { + return delegate.getPendingDeletions(); + } + +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormat.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormat.java index 0f51bdcd5..24dbfb78b 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormat.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormat.java @@ -41,7 +41,7 @@ public KNN80CompoundFormat(CompoundFormat delegate) { @Override public CompoundDirectory getCompoundReader(Directory dir, SegmentInfo si, IOContext context) throws IOException { - return delegate.getCompoundReader(dir, si, context); + return new KNN80CompoundDirectory(delegate.getCompoundReader(dir, si, context), dir); } @Override diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesFormat.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesFormat.java index fe329eb1c..7e45270b6 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesFormat.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesFormat.java @@ -41,6 +41,6 @@ public DocValuesConsumer fieldsConsumer(SegmentWriteState state) throws IOExcept @Override public DocValuesProducer fieldsProducer(SegmentReadState state) throws IOException { - return delegate.fieldsProducer(state); + return new KNN80DocValuesProducer(delegate.fieldsProducer(state), state); } } diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java new file mode 100644 index 000000000..0cfd9c668 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java @@ -0,0 +1,143 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.codec.KNN80Codec; + +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.codecs.DocValuesProducer; +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.NumericDocValues; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.index.SortedDocValues; +import org.apache.lucene.index.SortedNumericDocValues; +import org.apache.lucene.index.SortedSetDocValues; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.store.FilterDirectory; +import org.opensearch.common.io.PathUtils; +import org.opensearch.knn.common.FieldInfoExtractor; +import org.opensearch.knn.index.codec.util.KNNCodecUtil; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.memory.NativeMemoryCacheManager; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapper.KNN_FIELD; + +@Log4j2 +public class KNN80DocValuesProducer extends DocValuesProducer { + + private final SegmentReadState state; + private final DocValuesProducer delegate; + private final NativeMemoryCacheManager nativeMemoryCacheManager; + private final Map indexPathMap = new HashMap(); + + public KNN80DocValuesProducer(DocValuesProducer delegate, SegmentReadState state) { + this.delegate = delegate; + this.state = state; + this.nativeMemoryCacheManager = NativeMemoryCacheManager.getInstance(); + + Directory directory = state.directory; + // directory would be CompoundDirectory, we need get directory firstly and then unwrap + if (state.directory instanceof KNN80CompoundDirectory) { + directory = ((KNN80CompoundDirectory) state.directory).getDir(); + } + + Directory dir = FilterDirectory.unwrap(directory); + if (!(dir instanceof FSDirectory)) { + log.warn("{} can not casting to FSDirectory", directory); + return; + } + String directoryPath = ((FSDirectory) dir).getDirectory().toString(); + for (FieldInfo field : state.fieldInfos) { + if (!field.attributes().containsKey(KNN_FIELD)) { + continue; + } + // Only Native Engine put into indexPathMap + KNNEngine knnEngine = getNativeKNNEngine(field); + if (knnEngine == null) { + continue; + } + List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), field.name, state.segmentInfo); + Path indexPath = PathUtils.get(directoryPath, engineFiles.get(0)); + indexPathMap.putIfAbsent(field.getName(), indexPath.toString()); + } + } + + @Override + public BinaryDocValues getBinary(FieldInfo field) throws IOException { + return delegate.getBinary(field); + } + + @Override + public NumericDocValues getNumeric(FieldInfo field) throws IOException { + return delegate.getNumeric(field); + } + + @Override + public SortedDocValues getSorted(FieldInfo field) throws IOException { + return delegate.getSorted(field); + } + + @Override + public SortedNumericDocValues getSortedNumeric(FieldInfo field) throws IOException { + return delegate.getSortedNumeric(field); + } + + @Override + public SortedSetDocValues getSortedSet(FieldInfo field) throws IOException { + return delegate.getSortedSet(field); + } + + @Override + public void checkIntegrity() throws IOException { + delegate.checkIntegrity(); + } + + @Override + public void close() throws IOException { + for (String path : indexPathMap.values()) { + nativeMemoryCacheManager.invalidate(path); + } + delegate.close(); + } + + public final List getOpenedIndexPath() { + return new ArrayList<>(indexPathMap.values()); + } + + /** + * Get KNNEngine From FieldInfo + * + * @param field which field we need produce from engine + * @return if and only if Native Engine we return specific engine, else return null + */ + private KNNEngine getNativeKNNEngine(@NonNull FieldInfo field) { + + final String modelId = field.attributes().get(MODEL_ID); + if (modelId != null) { + return null; + } + KNNEngine engine = FieldInfoExtractor.extractKNNEngine(field); + if (KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(engine)) { + return engine; + } + return null; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java b/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java index 51100a1e0..84c7c4675 100644 --- a/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java +++ b/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java @@ -6,8 +6,15 @@ package org.opensearch.knn.index.codec.util; import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.SegmentInfo; +import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.codec.KNN80Codec.KNN80BinaryDocValues; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; public class KNNCodecUtil { // Floats are 4 bytes in size @@ -53,4 +60,28 @@ public static long getTotalLiveDocsCount(final BinaryDocValues binaryDocValues) } return totalLiveDocs; } + + /** + * Get Engine Files from segment with specific fieldName and engine extension + * + * @param extension Engine extension comes from {@link KNNEngine#getExtension()}} + * @param fieldName Filed for knn field + * @param segmentInfo {@link SegmentInfo} One Segment info to use for compute. + * @return List of engine files + */ + public static List getEngineFiles(String extension, String fieldName, SegmentInfo segmentInfo) { + /* + * In case of compound file, extension would be + c otherwise + */ + String engineExtension = segmentInfo.getUseCompoundFile() ? extension + KNNConstants.COMPOUND_EXTENSION : extension; + String engineSuffix = fieldName + engineExtension; + String underLineEngineSuffix = "_" + engineSuffix; + + List engineFiles = segmentInfo.files() + .stream() + .filter(fileName -> fileName.endsWith(underLineEngineSuffix)) + .sorted(Comparator.comparingInt(String::length)) + .collect(Collectors.toList()); + return engineFiles; + } } diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java index 0bb8a556f..635bc3883 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java @@ -15,6 +15,7 @@ import lombok.Setter; import org.apache.lucene.index.LeafReaderContext; import org.opensearch.knn.common.featureflags.KNNFeatureFlags; +import org.opensearch.common.concurrent.RefCountedReleasable; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.qframe.QuantizationConfig; import org.opensearch.knn.index.query.KNNWeight; @@ -81,6 +82,26 @@ public interface NativeMemoryAllocation { */ int getSizeInKB(); + /** + * Increments the refCount of this instance. + * + * @see #decRef + * @throws IllegalStateException iff the reference counter can not be incremented. + */ + default void incRef() {} + + /** + * Decreases the refCount of this instance. If the refCount drops to 0, then this + * instance is considered as closed and should not be used anymore. + * + * @see #incRef + * + * @return returns {@code true} if the ref count dropped to 0 as a result of calling this method + */ + default boolean decRef() { + return true; + } + /** * Represents native indices loaded into memory. Because these indices are backed by files, they should be * freed when file is deleted. @@ -102,6 +123,7 @@ class IndexAllocation implements NativeMemoryAllocation { private final SharedIndexState sharedIndexState; @Getter private final boolean isBinaryIndex; + private final RefCountedReleasable refCounted; /** * Constructor @@ -160,10 +182,10 @@ class IndexAllocation implements NativeMemoryAllocation { this.watcherHandle = watcherHandle; this.sharedIndexState = sharedIndexState; this.isBinaryIndex = isBinaryIndex; + this.refCounted = new RefCountedReleasable<>("IndexAllocation-Reference", this, this::closeInternal); } - @Override - public void close() { + protected void closeInternal() { Runnable onClose = () -> { writeLock(); cleanup(); @@ -179,6 +201,13 @@ public void close() { } } + @Override + public void close() { + if (!closed && refCounted.refCount() > 0) { + refCounted.close(); + } + } + private void cleanup() { if (this.closed) { return; @@ -242,6 +271,16 @@ public void writeUnlock() { public int getSizeInKB() { return size; } + + @Override + public void incRef() { + refCounted.incRef(); + } + + @Override + public boolean decRef() { + return refCounted.decRef(); + } } /** diff --git a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java index b1ba9de59..1c31ed725 100644 --- a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java +++ b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java @@ -5,7 +5,6 @@ package org.opensearch.knn.index.query; -import com.google.common.annotations.VisibleForTesting; import lombok.extern.log4j.Log4j2; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.LeafReaderContext; @@ -27,6 +26,7 @@ import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.util.KNNCodecUtil; import org.opensearch.knn.index.memory.NativeMemoryAllocation; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.memory.NativeMemoryEntryContext; @@ -43,7 +43,6 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -272,7 +271,7 @@ private Map doANNSearch( // TODO: Change type of vector once more quantization methods are supported final byte[] quantizedVector = SegmentLevelQuantizationUtil.quantizeVector(knnQuery.getQueryVector(), segmentLevelQuantizationInfo); - List engineFiles = getEngineFiles(reader, knnEngine.getExtension()); + List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), knnQuery.getField(), reader.getSegmentInfo().info); if (engineFiles.isEmpty()) { log.debug("[KNN] No engine index found for field {} for segment {}", knnQuery.getField(), reader.getSegmentName()); return null; @@ -312,6 +311,7 @@ private Map doANNSearch( FilterIdsSelector.FilterIdsSelectorType filterType = filterIdsSelector.getFilterType(); // Now that we have the allocation, we need to readLock it indexAllocation.readLock(); + indexAllocation.incRef(); try { if (indexAllocation.isClosed()) { throw new RuntimeException("Index has already been closed"); @@ -361,6 +361,7 @@ private Map doANNSearch( throw new RuntimeException(e); } finally { indexAllocation.readUnlock(); + indexAllocation.decRef(); } /* @@ -378,25 +379,6 @@ private Map doANNSearch( .collect(Collectors.toMap(KNNQueryResult::getId, result -> knnEngine.score(result.getScore(), spaceType))); } - @VisibleForTesting - List getEngineFiles(SegmentReader reader, String extension) throws IOException { - /* - * In case of compound file, extension would be + c otherwise - */ - String engineExtension = reader.getSegmentInfo().info.getUseCompoundFile() - ? extension + KNNConstants.COMPOUND_EXTENSION - : extension; - String engineSuffix = knnQuery.getField() + engineExtension; - String underLineEngineSuffix = "_" + engineSuffix; - List engineFiles = reader.getSegmentInfo() - .files() - .stream() - .filter(fileName -> fileName.endsWith(underLineEngineSuffix)) - .sorted(Comparator.comparingInt(String::length)) - .collect(Collectors.toList()); - return engineFiles; - } - /** * Execute exact search for the given matched doc ids and return the results as a map of docId to score. * diff --git a/src/test/java/org/opensearch/knn/index/OpenSearchIT.java b/src/test/java/org/opensearch/knn/index/OpenSearchIT.java index 1ea7ecca6..ded3a827c 100644 --- a/src/test/java/org/opensearch/knn/index/OpenSearchIT.java +++ b/src/test/java/org/opensearch/knn/index/OpenSearchIT.java @@ -483,4 +483,128 @@ public void testIndexingVectorValidation_updateVectorWithNull() throws Exception assertArrayEquals(vectorForDocumentOne, vectorRestoreInitialValue); } + public void testCacheClear_whenCloseIndex() throws Exception { + String indexName = "test-index-1"; + KNNEngine knnEngine1 = KNNEngine.NMSLIB; + KNNEngine knnEngine2 = KNNEngine.FAISS; + String fieldName1 = "test-field-1"; + String fieldName2 = "test-field-2"; + SpaceType spaceType1 = SpaceType.COSINESIMIL; + SpaceType spaceType2 = SpaceType.L2; + + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + Integer dimension = testData.indexData.vectors[0].length; + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName1) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType1.getValue()) + .field(KNNConstants.KNN_ENGINE, knnEngine1.getName()) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .endObject() + .endObject() + .endObject() + .startObject(fieldName2) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType2.getValue()) + .field(KNNConstants.KNN_ENGINE, knnEngine2.getName()) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + createKnnIndex(indexName, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + ImmutableList.of(fieldName1, fieldName2), + ImmutableList.of( + Floats.asList(testData.indexData.vectors[i]).toArray(), + Floats.asList(testData.indexData.vectors[i]).toArray() + ) + ); + } + + // Assert we have the right number of documents in the index + refreshAllIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + + int k = 10; + for (int i = 0; i < testData.queries.length; i++) { + // Search the first field + Response response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName1, testData.queries[i], k), k); + String responseBody = EntityUtils.toString(response.getEntity()); + List knnResults = parseSearchResponse(responseBody, fieldName1); + assertEquals(k, knnResults.size()); + + List actualScores = parseSearchResponseScore(responseBody, fieldName1); + for (int j = 0; j < k; j++) { + float[] primitiveArray = knnResults.get(j).getVector(); + assertEquals( + knnEngine1.score(1 - KNNScoringUtil.cosinesimil(testData.queries[i], primitiveArray), spaceType1), + actualScores.get(j), + 0.0001 + ); + } + + // Search the second field + response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName2, testData.queries[i], k), k); + responseBody = EntityUtils.toString(response.getEntity()); + knnResults = parseSearchResponse(responseBody, fieldName2); + assertEquals(k, knnResults.size()); + + actualScores = parseSearchResponseScore(responseBody, fieldName2); + for (int j = 0; j < k; j++) { + float[] primitiveArray = knnResults.get(j).getVector(); + assertEquals( + knnEngine2.score(KNNScoringUtil.l2Squared(testData.queries[i], primitiveArray), spaceType2), + actualScores.get(j), + 0.0001 + ); + } + } + + // Get Stats + int graphCount = getTotalGraphsInCache(); + assertTrue(graphCount > 0); + // Close index + closeKNNIndex(indexName); + + // Search every 5 seconds 14 times to confirm graph gets evicted + int intervals = 14; + for (int i = 0; i < intervals; i++) { + if (getTotalGraphsInCache() == 0) { + return; + } + + Thread.sleep(5 * 1000); + } + + fail("Graphs are not getting evicted"); + } } diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormatTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormatTests.java index 0ecabcce6..6001a9729 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormatTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormatTests.java @@ -49,7 +49,9 @@ public void testGetCompoundReader() throws IOException { CompoundFormat delegate = mock(CompoundFormat.class); when(delegate.getCompoundReader(null, null, null)).thenReturn(dir); KNN80CompoundFormat knn80CompoundFormat = new KNN80CompoundFormat(delegate); - assertEquals(dir, knn80CompoundFormat.getCompoundReader(null, null, null)); + CompoundDirectory knnDir = knn80CompoundFormat.getCompoundReader(null, null, null); + assertTrue(knnDir instanceof KNN80CompoundDirectory); + assertEquals(dir, ((KNN80CompoundDirectory) knnDir).getDelegate()); } public void testWrite() throws IOException { diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java new file mode 100644 index 000000000..b9a85bbcc --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java @@ -0,0 +1,130 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN80Codec; + +import com.google.common.collect.ImmutableMap; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.DocValuesFormat; +import org.apache.lucene.codecs.DocValuesProducer; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FieldInfos; +import org.apache.lucene.index.SegmentInfo; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexOutput; +import org.junit.Before; +import org.opensearch.Version; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.KNN87Codec.KNN87Codec; +import org.opensearch.knn.index.codec.KNNCodecTestUtil; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; + +public class KNN80DocValuesProducerTests extends KNNTestCase { + + private static Directory directory; + + @Before + public void setUp() throws Exception { + super.setUp(); + directory = newFSDirectory(createTempDir()); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + directory.close(); + } + + public void testProduceKNNBinaryField_fromCodec_nmslibCurrent() throws IOException { + // Set information about the segment and the fields + DocValuesFormat mockDocValuesFormat = mock(DocValuesFormat.class); + Codec mockDelegateCodec = mock(Codec.class); + DocValuesProducer mockDocValuesProducer = mock(DocValuesProducer.class); + when(mockDelegateCodec.docValuesFormat()).thenReturn(mockDocValuesFormat); + when(mockDocValuesFormat.fieldsProducer(any())).thenReturn(mockDocValuesProducer); + when(mockDocValuesFormat.getName()).thenReturn("mockDocValuesFormat"); + Codec codec = new KNN87Codec(mockDelegateCodec); + + String segmentName = "_test"; + int docsInSegment = 100; + String fieldName1 = String.format("test_field1%s", randomAlphaOfLength(4)); + String fieldName2 = String.format("test_field2%s", randomAlphaOfLength(4)); + List segmentFiles = Arrays.asList( + String.format("%s_2011_%s%s", segmentName, fieldName1, KNNEngine.NMSLIB.getExtension()), + String.format("%s_165_%s%s", segmentName, fieldName2, KNNEngine.FAISS.getExtension()) + ); + + KNNEngine knnEngine = KNNEngine.NMSLIB; + SpaceType spaceType = SpaceType.COSINESIMIL; + SegmentInfo segmentInfo = KNNCodecTestUtil.segmentInfoBuilder() + .directory(directory) + .segmentName(segmentName) + .docsInSegment(docsInSegment) + .codec(codec) + .build(); + + for (String name : segmentFiles) { + IndexOutput indexOutput = directory.createOutput(name, IOContext.DEFAULT); + indexOutput.close(); + } + segmentInfo.setFiles(segmentFiles); + + KNNMethodContext knnMethodContext = new KNNMethodContext( + knnEngine, + spaceType, + new MethodComponentContext(METHOD_HNSW, ImmutableMap.of(METHOD_PARAMETER_M, 16, METHOD_PARAMETER_EF_CONSTRUCTION, 512)) + ); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .build(); + String parameterString = XContentFactory.jsonBuilder() + .map(knnEngine.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext).getLibraryParameters()) + .toString(); + + FieldInfo[] fieldInfoArray = new FieldInfo[] { + KNNCodecTestUtil.FieldInfoBuilder.builder(fieldName1) + .addAttribute(KNNVectorFieldMapper.KNN_FIELD, "true") + .addAttribute(KNNConstants.KNN_ENGINE, knnEngine.getName()) + .addAttribute(KNNConstants.SPACE_TYPE, spaceType.getValue()) + .addAttribute(KNNConstants.PARAMETERS, parameterString) + .build() }; + + FieldInfos fieldInfos = new FieldInfos(fieldInfoArray); + SegmentReadState state = new SegmentReadState(directory, segmentInfo, fieldInfos, IOContext.DEFAULT); + + DocValuesFormat docValuesFormat = codec.docValuesFormat(); + assertTrue(docValuesFormat instanceof KNN80DocValuesFormat); + DocValuesProducer producer = docValuesFormat.fieldsProducer(state); + assertTrue(producer instanceof KNN80DocValuesProducer); + int pathSize = ((KNN80DocValuesProducer) producer).getOpenedIndexPath().size(); + assertEquals(pathSize, 1); + + String path = ((KNN80DocValuesProducer) producer).getOpenedIndexPath().get(0); + assertTrue(path.contains(segmentFiles.get(0))); + } + +} diff --git a/src/test/java/org/opensearch/knn/index/codec/util/KNNCodecUtilTests.java b/src/test/java/org/opensearch/knn/index/codec/util/KNNCodecUtilTests.java index dbea6375b..86e22cd88 100644 --- a/src/test/java/org/opensearch/knn/index/codec/util/KNNCodecUtilTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/util/KNNCodecUtilTests.java @@ -6,8 +6,15 @@ package org.opensearch.knn.index.codec.util; import junit.framework.TestCase; +import org.apache.lucene.index.SegmentInfo; import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import java.util.List; +import java.util.Set; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.opensearch.knn.index.codec.util.KNNCodecUtil.calculateArraySize; public class KNNCodecUtilTests extends TestCase { @@ -28,4 +35,15 @@ public void testCalculateArraySize() { vectorDataType = VectorDataType.BINARY; assertEquals(40, calculateArraySize(numVectors, vectorLength, vectorDataType)); } + + public void testGetKNNEngines() { + SegmentInfo segmentInfo = mock(SegmentInfo.class); + KNNEngine knnEngine = KNNEngine.FAISS; + Set SEGMENT_MULTI_FIELD_FILES_FAISS = Set.of("_0.cfe", "_0_2011_long_target_field.faissc", "_0_2011_target_field.faissc"); + when(segmentInfo.getUseCompoundFile()).thenReturn(true); + when(segmentInfo.files()).thenReturn(SEGMENT_MULTI_FIELD_FILES_FAISS); + List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), "target_field", segmentInfo); + assertEquals(engineFiles.size(), 2); + assertTrue(engineFiles.get(0).equals("_0_2011_target_field.faissc")); + } } diff --git a/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java index 810f49c15..f92f32406 100644 --- a/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java +++ b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java @@ -41,6 +41,7 @@ import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.codec.KNN990Codec.QuantizationConfigKNNCollector; +import org.opensearch.knn.index.codec.util.KNNCodecUtil; import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; @@ -1318,7 +1319,7 @@ private void testQueryScore( String engineName = fieldInfo.attributes().getOrDefault(KNN_ENGINE, KNNEngine.NMSLIB.getName()); KNNEngine knnEngine = KNNEngine.getEngine(engineName); - List engineFiles = knnWeight.getEngineFiles(reader, knnEngine.getExtension()); + List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), query.getField(), reader.getSegmentInfo().info); String expectIndexPath = String.format("%s_%s_%s%s%s", SEGMENT_NAME, 2011, FIELD_NAME, knnEngine.getExtension(), "c"); assertEquals(engineFiles.get(0), expectIndexPath); diff --git a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java index fb974b6e1..8c033ca03 100644 --- a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java +++ b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java @@ -357,6 +357,12 @@ protected void deleteKNNIndex(String index) throws IOException { assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); } + protected void closeKNNIndex(String index) throws IOException { + Request request = new Request("POST", "/" + index + "/_close"); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + /** * For a given index, make a mapping request */ From b6d6ec520f87a2465a50254072cb30b14d52bedc Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Wed, 18 Sep 2024 11:47:58 -0700 Subject: [PATCH 14/59] Remove outdated comment in integration tests (#2116) Signed-off-by: Ryan Bogan --- .../java/org/opensearch/knn/integ/ModeAndCompressionIT.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java index f8960472c..8f0f78753 100644 --- a/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java +++ b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java @@ -249,8 +249,6 @@ public void testTraining_whenInvalid_thenFail() { expectThrows(ResponseException.class, () -> trainModel(modelId, builder2)); } - // Training isnt currently supported for mode and compression because quantization framework does not quantize - // the training vectors. So, commenting out for now. @SneakyThrows public void testTraining_whenValid_thenSucceed() { setupTrainingIndex(); From e3cf8140674f91055f1fcbba20d5866fab781031 Mon Sep 17 00:00:00 2001 From: John Mazanec Date: Wed, 18 Sep 2024 15:33:44 -0400 Subject: [PATCH 15/59] Change min oversample to 1 (#2117) Signed-off-by: John Mazanec --- .../org/opensearch/knn/index/query/rescore/RescoreContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java index 5e57b4aba..ea4e92215 100644 --- a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java +++ b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java @@ -18,7 +18,7 @@ public final class RescoreContext { public static final float DEFAULT_OVERSAMPLE_FACTOR = 1.0f; public static final float MAX_OVERSAMPLE_FACTOR = 100.0f; - public static final float MIN_OVERSAMPLE_FACTOR = 0.0f; + public static final float MIN_OVERSAMPLE_FACTOR = 1.0f; public static final int MAX_FIRST_PASS_RESULTS = 10000; From ccc59b197125ffbba14428310419b0989f0c9bae Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Wed, 18 Sep 2024 15:36:58 -0700 Subject: [PATCH 16/59] Add log message for quantization state cache eviction due to size (#2120) Signed-off-by: Ryan Bogan --- .../models/quantizationState/QuantizationStateCache.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCache.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCache.java index cc5a34bcd..f057026b9 100644 --- a/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCache.java +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCache.java @@ -107,6 +107,11 @@ public void evict(String fieldName) { private void onRemoval(RemovalNotification removalNotification) { if (RemovalCause.SIZE == removalNotification.getCause()) { updateEvictedDueToSizeAt(); + log.info( + "[KNN] Quantization state evicted from cache. Key {}, Reason: {}", + removalNotification.getKey(), + removalNotification.getCause() + ); } } From 0421cdc907b43e4a930bd5a51454e5efea8413b6 Mon Sep 17 00:00:00 2001 From: Tejas Shah Date: Wed, 18 Sep 2024 16:09:53 -0700 Subject: [PATCH 17/59] Integrates KNN plugin with ConcurrentSearchRequestDecider interface (#2111) This allows knn queries to enable concurrency when index.search.concurrent_segment_search.mode or search.concurrent_segment_search.mode in auto mode. Without this the default behavior of auto mode is non-concurrent search Signed-off-by: Tejas Shah --- CHANGELOG.md | 1 + .../org/opensearch/knn/plugin/KNNPlugin.java | 7 + .../KNNConcurrentSearchRequestDecider.java | 65 ++++++++ .../search/ConcurrentSegmentSearchIT.java | 141 ++++++++++++++++++ ...NNConcurrentSearchRequestDeciderTests.java | 65 ++++++++ .../knn/KNNJsonIndexMappingsBuilder.java | 15 +- 6 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDecider.java create mode 100644 src/test/java/org/opensearch/knn/integ/search/ConcurrentSegmentSearchIT.java create mode 100644 src/test/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDeciderTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a8d808f4..e8e5e6d7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased 3.0](https://github.com/opensearch-project/k-NN/compare/2.x...HEAD) ### Features ### Enhancements +* Adds concurrent segment search support for mode auto [#2111](https://github.com/opensearch-project/k-NN/pull/2111) ### Bug Fixes * Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) ### Infrastructure diff --git a/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java b/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java index efb4bdf93..ff079031f 100644 --- a/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java +++ b/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java @@ -13,6 +13,7 @@ import org.opensearch.index.engine.EngineFactory; import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.knn.index.KNNCircuitBreaker; +import org.opensearch.knn.plugin.search.KNNConcurrentSearchRequestDecider; import org.opensearch.knn.index.util.KNNClusterUtil; import org.opensearch.knn.index.query.KNNQueryBuilder; import org.opensearch.knn.index.KNNSettings; @@ -95,6 +96,7 @@ import org.opensearch.script.ScriptContext; import org.opensearch.script.ScriptEngine; import org.opensearch.script.ScriptService; +import org.opensearch.search.deciders.ConcurrentSearchRequestDecider; import org.opensearch.threadpool.ExecutorBuilder; import org.opensearch.threadpool.FixedExecutorBuilder; import org.opensearch.threadpool.ThreadPool; @@ -349,4 +351,9 @@ public List getNamedXContent() { public Collection getSystemIndexDescriptors(Settings settings) { return ImmutableList.of(new SystemIndexDescriptor(MODEL_INDEX_NAME, "Index for storing models used for k-NN indices")); } + + @Override + public Optional getConcurrentSearchRequestDeciderFactory() { + return Optional.of(new KNNConcurrentSearchRequestDecider.Factory()); + } } diff --git a/src/main/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDecider.java b/src/main/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDecider.java new file mode 100644 index 000000000..92b0a3e3c --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDecider.java @@ -0,0 +1,65 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.search; + +import lombok.EqualsAndHashCode; +import org.opensearch.index.IndexSettings; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.search.deciders.ConcurrentSearchDecision; +import org.opensearch.search.deciders.ConcurrentSearchRequestDecider; + +import java.util.Optional; + +/** + * Decides if the knn query uses concurrent segment search + * As of 2.17, this is only used when + * - "index.search.concurrent_segment_search.mode": "auto" or + * - "search.concurrent_segment_search.mode": "auto" + * + * Note: the class is not thread-safe and a new instance needs to be created for each request + */ +@EqualsAndHashCode(callSuper = true) +public class KNNConcurrentSearchRequestDecider extends ConcurrentSearchRequestDecider { + + private static final ConcurrentSearchDecision DEFAULT_KNN_DECISION = new ConcurrentSearchDecision( + ConcurrentSearchDecision.DecisionStatus.NO_OP, + "Default decision" + ); + private static final ConcurrentSearchDecision YES = new ConcurrentSearchDecision( + ConcurrentSearchDecision.DecisionStatus.YES, + "Enable concurrent search for knn as Query has k-NN query in it and index is k-nn index" + ); + + private ConcurrentSearchDecision knnDecision = DEFAULT_KNN_DECISION; + + @Override + public void evaluateForQuery(final QueryBuilder queryBuilder, final IndexSettings indexSettings) { + if (queryBuilder instanceof KNNQueryBuilder && indexSettings.getValue(KNNSettings.IS_KNN_INDEX_SETTING)) { + knnDecision = YES; + } else { + knnDecision = DEFAULT_KNN_DECISION; + } + } + + @Override + public ConcurrentSearchDecision getConcurrentSearchDecision() { + return knnDecision; + } + + /** + * Returns {@link KNNConcurrentSearchRequestDecider} when index.knn is true + */ + public static class Factory implements ConcurrentSearchRequestDecider.Factory { + public Optional create(final IndexSettings indexSettings) { + if (indexSettings.getValue(KNNSettings.IS_KNN_INDEX_SETTING)) { + return Optional.of(new KNNConcurrentSearchRequestDecider()); + } + return Optional.empty(); + } + } +} diff --git a/src/test/java/org/opensearch/knn/integ/search/ConcurrentSegmentSearchIT.java b/src/test/java/org/opensearch/knn/integ/search/ConcurrentSegmentSearchIT.java new file mode 100644 index 000000000..cc64cfe9c --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/search/ConcurrentSegmentSearchIT.java @@ -0,0 +1,141 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ.search; + +import com.google.common.primitives.Floats; +import lombok.SneakyThrows; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.junit.BeforeClass; +import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.KNNResult; +import org.opensearch.knn.TestUtils; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.knn.plugin.script.KNNScoringUtil; + +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +/** + * Note that this is simply a sanity test to make sure that concurrent search code path is hit E2E and scores are intact + * There is no latency verification as it can be better encapsulated in nightly runs. + */ +public class ConcurrentSegmentSearchIT extends KNNRestTestCase { + + static TestUtils.TestData testData; + + @BeforeClass + public static void setUpClass() throws IOException { + if (ConcurrentSegmentSearchIT.class.getClassLoader() == null) { + throw new IllegalStateException("ClassLoader of ConcurrentSegmentSearchIT Class is null"); + } + URL testIndexVectors = ConcurrentSegmentSearchIT.class.getClassLoader().getResource("data/test_vectors_1000x128.json"); + URL testQueries = ConcurrentSegmentSearchIT.class.getClassLoader().getResource("data/test_queries_100x128.csv"); + assert testIndexVectors != null; + assert testQueries != null; + testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath()); + } + + @SneakyThrows + public void testConcurrentSegmentSearch_thenSucceed() { + String indexName = "test-concurrent-segment"; + String fieldName = "test-field-1"; + int dimension = testData.indexData.vectors[0].length; + final XContentBuilder indexBuilder = createFaissHnswIndexMapping(fieldName, dimension); + Map mappingMap = xContentBuilderToMap(indexBuilder); + String mapping = indexBuilder.toString(); + createKnnIndex(indexName, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + fieldName, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + refreshAllNonSystemIndices(); + updateIndexSettings(indexName, Settings.builder().put("index.search.concurrent_segment_search.mode", "auto")); + + // Test search queries + int k = 10; + verifySearch(indexName, fieldName, k); + + updateIndexSettings(indexName, Settings.builder().put("index.search.concurrent_segment_search.mode", "all")); + verifySearch(indexName, fieldName, k); + + deleteKNNIndex(indexName); + } + + /* + { + "properties": { + "": { + "type": "knn_vector", + "dimension": , + "method": { + "name": "hnsw", + "space_type": "l2", + "engine": "faiss", + "parameters": { + "m": 16, + "ef_construction": 128, + "ef_search": 128 + } + } + } + } + */ + @SneakyThrows + private XContentBuilder createFaissHnswIndexMapping(String fieldName, int dimension) { + return KNNJsonIndexMappingsBuilder.builder() + .fieldName(fieldName) + .dimension(dimension) + .method( + KNNJsonIndexMappingsBuilder.Method.builder() + .engine(KNNEngine.FAISS.getName()) + .methodName(METHOD_HNSW) + .spaceType(SpaceType.L2.getValue()) + .parameters(KNNJsonIndexMappingsBuilder.Method.Parameters.builder().efConstruction(128).efSearch(128).m(16).build()) + .build() + ) + .build() + .getIndexMappingBuilder(); + } + + @SneakyThrows + private void verifySearch(String indexName, String fieldName, int k) { + for (int i = 0; i < testData.queries.length; i++) { + final KNNQueryBuilder queryBuilder = KNNQueryBuilder.builder().fieldName(fieldName).vector(testData.queries[i]).k(k).build(); + Response response = searchKNNIndex(indexName, queryBuilder, k); + String responseBody = EntityUtils.toString(response.getEntity()); + List knnResults = parseSearchResponse(responseBody, fieldName); + assertEquals(k, knnResults.size()); + + List actualScores = parseSearchResponseScore(responseBody, fieldName); + for (int j = 0; j < k; j++) { + float[] primitiveArray = knnResults.get(j).getVector(); + assertEquals( + KNNEngine.FAISS.score(KNNScoringUtil.l2Squared(testData.queries[i], primitiveArray), SpaceType.L2), + actualScores.get(j), + 0.0001 + ); + } + } + } +} diff --git a/src/test/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDeciderTests.java b/src/test/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDeciderTests.java new file mode 100644 index 000000000..53fd520f7 --- /dev/null +++ b/src/test/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDeciderTests.java @@ -0,0 +1,65 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.search; + +import org.opensearch.index.IndexSettings; +import org.opensearch.index.query.MatchAllQueryBuilder; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.search.deciders.ConcurrentSearchDecision; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class KNNConcurrentSearchRequestDeciderTests extends KNNTestCase { + + public void testDecider_thenSucceed() { + ConcurrentSearchDecision noop = new ConcurrentSearchDecision(ConcurrentSearchDecision.DecisionStatus.NO_OP, "Default decision"); + + KNNConcurrentSearchRequestDecider decider = new KNNConcurrentSearchRequestDecider(); + assertDecision(noop, decider.getConcurrentSearchDecision()); + IndexSettings indexSettingsMock = mock(IndexSettings.class); + when(indexSettingsMock.getValue(KNNSettings.IS_KNN_INDEX_SETTING)).thenReturn(Boolean.FALSE); + + // Non KNNQueryBuilder + decider.evaluateForQuery(new MatchAllQueryBuilder(), indexSettingsMock); + assertDecision(noop, decider.getConcurrentSearchDecision()); + decider.evaluateForQuery( + KNNQueryBuilder.builder().vector(new float[] { 1f, 2f, 3f, 4f, 5f, 6f }).fieldName("decider").k(10).build(), + indexSettingsMock + ); + assertDecision(noop, decider.getConcurrentSearchDecision()); + + when(indexSettingsMock.getValue(KNNSettings.IS_KNN_INDEX_SETTING)).thenReturn(Boolean.TRUE); + decider.evaluateForQuery( + KNNQueryBuilder.builder().vector(new float[] { 1f, 2f, 3f, 4f, 5f, 6f }).fieldName("decider").k(10).build(), + indexSettingsMock + ); + ConcurrentSearchDecision yes = new ConcurrentSearchDecision( + ConcurrentSearchDecision.DecisionStatus.YES, + "Enable concurrent search for knn as Query has k-NN query in it and index is k-nn index" + ); + assertDecision(yes, decider.getConcurrentSearchDecision()); + + decider.evaluateForQuery(new MatchAllQueryBuilder(), indexSettingsMock); + assertDecision(noop, decider.getConcurrentSearchDecision()); + } + + public void testDeciderFactory_thenSucceed() { + KNNConcurrentSearchRequestDecider.Factory factory = new KNNConcurrentSearchRequestDecider.Factory(); + IndexSettings indexSettingsMock = mock(IndexSettings.class); + when(indexSettingsMock.getValue(KNNSettings.IS_KNN_INDEX_SETTING)).thenReturn(Boolean.TRUE); + assertNotSame(factory.create(indexSettingsMock).get(), factory.create(indexSettingsMock).get()); + when(indexSettingsMock.getValue(KNNSettings.IS_KNN_INDEX_SETTING)).thenReturn(Boolean.FALSE); + assertTrue(factory.create(indexSettingsMock).isEmpty()); + } + + private void assertDecision(ConcurrentSearchDecision expected, ConcurrentSearchDecision actual) { + assertEquals(expected.getDecisionReason(), actual.getDecisionReason()); + assertEquals(expected.getDecisionStatus(), actual.getDecisionStatus()); + } +} diff --git a/src/testFixtures/java/org/opensearch/knn/KNNJsonIndexMappingsBuilder.java b/src/testFixtures/java/org/opensearch/knn/KNNJsonIndexMappingsBuilder.java index 15ac0c211..817684f89 100644 --- a/src/testFixtures/java/org/opensearch/knn/KNNJsonIndexMappingsBuilder.java +++ b/src/testFixtures/java/org/opensearch/knn/KNNJsonIndexMappingsBuilder.java @@ -9,6 +9,7 @@ import lombok.NonNull; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.common.KNNConstants; import java.io.IOException; @@ -26,7 +27,7 @@ public class KNNJsonIndexMappingsBuilder { private String vectorDataType; private Method method; - public String getIndexMapping() throws IOException { + public XContentBuilder getIndexMappingBuilder() throws IOException { if (nestedFieldName != null) { XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() .startObject() @@ -40,7 +41,7 @@ public String getIndexMapping() throws IOException { addVectorDataType(xContentBuilder); addMethod(xContentBuilder); xContentBuilder.endObject().endObject().endObject().endObject().endObject(); - return xContentBuilder.toString(); + return xContentBuilder; } else { XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() .startObject() @@ -51,10 +52,14 @@ public String getIndexMapping() throws IOException { addVectorDataType(xContentBuilder); addMethod(xContentBuilder); xContentBuilder.endObject().endObject().endObject(); - return xContentBuilder.toString(); + return xContentBuilder; } } + public String getIndexMapping() throws IOException { + return getIndexMappingBuilder().toString(); + } + private void addVectorDataType(final XContentBuilder xContentBuilder) throws IOException { if (vectorDataType == null) { return; @@ -104,6 +109,7 @@ public static class Parameters { private Encoder encoder; private Integer efConstruction; private Integer efSearch; + private Integer m; private void addTo(final XContentBuilder xContentBuilder) throws IOException { xContentBuilder.startObject("parameters"); @@ -113,6 +119,9 @@ private void addTo(final XContentBuilder xContentBuilder) throws IOException { if (efSearch != null) { xContentBuilder.field("ef_search", efSearch); } + if (m != null) { + xContentBuilder.field(KNNConstants.METHOD_PARAMETER_M, m); + } addEncoder(xContentBuilder); xContentBuilder.endObject(); } From 30adbe415fb465e6ae89b772e440d76efb0840fb Mon Sep 17 00:00:00 2001 From: Naveen Tatikonda Date: Wed, 18 Sep 2024 18:57:53 -0500 Subject: [PATCH 18/59] Fix BWC CI for 2.18 (#2124) Signed-off-by: Naveen Tatikonda --- .github/workflows/backwards_compatibility_tests_workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/backwards_compatibility_tests_workflow.yml b/.github/workflows/backwards_compatibility_tests_workflow.yml index c20201dab..ea534d284 100644 --- a/.github/workflows/backwards_compatibility_tests_workflow.yml +++ b/.github/workflows/backwards_compatibility_tests_workflow.yml @@ -35,7 +35,7 @@ jobs: matrix: java: [ 21 ] os: [ubuntu-latest] - bwc_version : [ "2.0.1", "2.1.0", "2.2.1", "2.3.0", "2.4.1", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.9.0", "2.10.0", "2.11.0", "2.12.0", "2.13.0", "2.14.0", "2.15.0", "2.16.0", "2.17.0-SNAPSHOT"] + bwc_version : [ "2.0.1", "2.1.0", "2.2.1", "2.3.0", "2.4.1", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.9.0", "2.10.0", "2.11.0", "2.12.0", "2.13.0", "2.14.0", "2.15.0", "2.16.0", "2.17.0", "2.18.0-SNAPSHOT"] opensearch_version : [ "3.0.0-SNAPSHOT" ] exclude: - os: windows-latest @@ -114,7 +114,7 @@ jobs: matrix: java: [ 21 ] os: [ubuntu-latest] - bwc_version: [ "2.17.0-SNAPSHOT" ] + bwc_version: [ "2.18.0-SNAPSHOT" ] opensearch_version: [ "3.0.0-SNAPSHOT" ] name: k-NN Rolling-Upgrade BWC Tests From fe1d3e427349b108f0ad270fb45978dba4b18d29 Mon Sep 17 00:00:00 2001 From: Navneet Verma Date: Thu, 19 Sep 2024 09:33:09 -0700 Subject: [PATCH 19/59] Remove benchmarks folder from k-NN repo (#2127) Signed-off-by: Navneet Verma --- CHANGELOG.md | 1 + benchmarks/README.md | 4 + benchmarks/osb/README.md | 478 --------- benchmarks/osb/__init__.py | 0 benchmarks/osb/extensions/__init__.py | 0 benchmarks/osb/extensions/data_set.py | 202 ---- benchmarks/osb/extensions/param_sources.py | 217 ---- benchmarks/osb/extensions/registry.py | 13 - benchmarks/osb/extensions/runners.py | 121 --- benchmarks/osb/extensions/util.py | 71 -- benchmarks/osb/indices/faiss-index.json | 27 - benchmarks/osb/indices/lucene-index.json | 26 - benchmarks/osb/indices/model-index.json | 17 - benchmarks/osb/indices/nmslib-index.json | 27 - benchmarks/osb/indices/train-index.json | 16 - benchmarks/osb/operations/default.json | 53 - benchmarks/osb/params/no-train-params.json | 40 - benchmarks/osb/params/train-params.json | 38 - benchmarks/osb/procedures/no-train-test.json | 73 -- benchmarks/osb/procedures/train-test.json | 127 --- benchmarks/osb/requirements.in | 4 - benchmarks/osb/requirements.txt | 96 -- benchmarks/osb/tests/__init__.py | 0 benchmarks/osb/tests/data_set_helper.py | 197 ---- benchmarks/osb/tests/test_param_sources.py | 353 ------- benchmarks/osb/workload.json | 17 - benchmarks/osb/workload.py | 18 - benchmarks/perf-tool/.pylintrc | 443 -------- benchmarks/perf-tool/.style.yapf | 10 - benchmarks/perf-tool/README.md | 449 -------- .../perf-tool/add-filters-to-dataset.py | 200 ---- .../perf-tool/add-parent-doc-id-to-dataset.py | 291 ------ benchmarks/perf-tool/dataset/data-nested.hdf5 | Bin 74496 -> 0 bytes .../dataset/data-with-attr-with-filters.hdf5 | Bin 2404096 -> 0 bytes .../perf-tool/dataset/data-with-attr.hdf5 | Bin 306896 -> 0 bytes benchmarks/perf-tool/dataset/data.hdf5 | Bin 527648 -> 0 bytes benchmarks/perf-tool/knn-perf-tool.py | 10 - benchmarks/perf-tool/okpt/__init__.py | 6 - benchmarks/perf-tool/okpt/diff/diff.py | 142 --- benchmarks/perf-tool/okpt/io/args.py | 178 ---- .../perf-tool/okpt/io/config/parsers/base.py | 67 -- .../perf-tool/okpt/io/config/parsers/test.py | 81 -- .../perf-tool/okpt/io/config/parsers/util.py | 116 -- .../perf-tool/okpt/io/config/schemas/test.yml | 35 - benchmarks/perf-tool/okpt/io/dataset.py | 222 ---- benchmarks/perf-tool/okpt/io/utils/reader.py | 84 -- benchmarks/perf-tool/okpt/io/utils/writer.py | 40 - benchmarks/perf-tool/okpt/main.py | 55 - benchmarks/perf-tool/okpt/test/__init__.py | 5 - benchmarks/perf-tool/okpt/test/profile.py | 86 -- benchmarks/perf-tool/okpt/test/runner.py | 107 -- benchmarks/perf-tool/okpt/test/steps/base.py | 60 -- .../perf-tool/okpt/test/steps/factory.py | 50 - benchmarks/perf-tool/okpt/test/steps/steps.py | 987 ------------------ benchmarks/perf-tool/okpt/test/test.py | 188 ---- .../filtering/relaxed-filter/index.json | 27 - .../relaxed-filter/relaxed-filter-spec.json | 42 - .../relaxed-filter/relaxed-filter-test.yml | 40 - .../filtering/restrictive-filter/index.json | 27 - .../restrictive-filter-spec.json | 44 - .../restrictive-filter-test.yml | 40 - .../release-configs/faiss-hnsw/index.json | 27 - .../faiss-hnsw/nested/simple/index.json | 35 - .../nested/simple/simple-nested-test.yml | 37 - .../release-configs/faiss-hnsw/test.yml | 35 - .../release-configs/faiss-hnswpq/index.json | 17 - .../faiss-hnswpq/method-spec.json | 15 - .../release-configs/faiss-hnswpq/test.yml | 59 -- .../faiss-hnswpq/train-index-spec.json | 16 - .../filtering/relaxed-filter/index.json | 17 - .../filtering/relaxed-filter/method-spec.json | 9 - .../relaxed-filter/relaxed-filter-spec.json | 42 - .../relaxed-filter/relaxed-filter-test.yml | 64 -- .../relaxed-filter/train-index-spec.json | 16 - .../filtering/restrictive-filter/index.json | 17 - .../restrictive-filter/method-spec.json | 9 - .../restrictive-filter-spec.json | 44 - .../restrictive-filter-test.yml | 64 -- .../restrictive-filter/train-index-spec.json | 16 - .../release-configs/faiss-ivf/index.json | 17 - .../faiss-ivf/method-spec.json | 9 - .../release-configs/faiss-ivf/test.yml | 59 -- .../faiss-ivf/train-index-spec.json | 16 - .../release-configs/faiss-ivfpq/index.json | 17 - .../faiss-ivfpq/method-spec.json | 16 - .../release-configs/faiss-ivfpq/test.yml | 59 -- .../faiss-ivfpq/train-index-spec.json | 16 - .../filtering/relaxed-filter/index.json | 26 - .../relaxed-filter/relaxed-filter-spec.json | 42 - .../relaxed-filter/relaxed-filter-test.yml | 38 - .../filtering/restrictive-filter/index.json | 26 - .../restrictive-filter-spec.json | 44 - .../restrictive-filter-test.yml | 38 - .../release-configs/lucene-hnsw/index.json | 26 - .../lucene-hnsw/nested/simple/index.json | 34 - .../nested/simple/simple-nested-test.yml | 37 - .../release-configs/lucene-hnsw/test.yml | 33 - .../release-configs/nmslib-hnsw/index.json | 27 - .../release-configs/nmslib-hnsw/test.yml | 35 - .../release-configs/run_all_tests.sh | 102 -- benchmarks/perf-tool/requirements.in | 7 - benchmarks/perf-tool/requirements.txt | 37 - .../faiss-sift-ivf/index-spec.json | 17 - .../faiss-sift-ivf/method-spec.json | 8 - .../sample-configs/faiss-sift-ivf/test.yml | 62 -- .../faiss-sift-ivf/train-index-spec.json | 16 - .../filter-spec/filter-1-spec.json | 24 - .../filter-spec/filter-2-spec.json | 40 - .../filter-spec/filter-3-spec.json | 30 - .../filter-spec/filter-4-spec.json | 44 - .../filter-spec/filter-5-spec.json | 42 - .../lucene-sift-hnsw-filter/index-spec.json | 27 - .../lucene-sift-hnsw-filter/test.yml | 41 - .../nmslib-sift-hnsw/index-spec.json | 28 - .../sample-configs/nmslib-sift-hnsw/test.yml | 38 - 115 files changed, 5 insertions(+), 8080 deletions(-) create mode 100644 benchmarks/README.md delete mode 100644 benchmarks/osb/README.md delete mode 100644 benchmarks/osb/__init__.py delete mode 100644 benchmarks/osb/extensions/__init__.py delete mode 100644 benchmarks/osb/extensions/data_set.py delete mode 100644 benchmarks/osb/extensions/param_sources.py delete mode 100644 benchmarks/osb/extensions/registry.py delete mode 100644 benchmarks/osb/extensions/runners.py delete mode 100644 benchmarks/osb/extensions/util.py delete mode 100644 benchmarks/osb/indices/faiss-index.json delete mode 100644 benchmarks/osb/indices/lucene-index.json delete mode 100644 benchmarks/osb/indices/model-index.json delete mode 100644 benchmarks/osb/indices/nmslib-index.json delete mode 100644 benchmarks/osb/indices/train-index.json delete mode 100644 benchmarks/osb/operations/default.json delete mode 100644 benchmarks/osb/params/no-train-params.json delete mode 100644 benchmarks/osb/params/train-params.json delete mode 100644 benchmarks/osb/procedures/no-train-test.json delete mode 100644 benchmarks/osb/procedures/train-test.json delete mode 100644 benchmarks/osb/requirements.in delete mode 100644 benchmarks/osb/requirements.txt delete mode 100644 benchmarks/osb/tests/__init__.py delete mode 100644 benchmarks/osb/tests/data_set_helper.py delete mode 100644 benchmarks/osb/tests/test_param_sources.py delete mode 100644 benchmarks/osb/workload.json delete mode 100644 benchmarks/osb/workload.py delete mode 100644 benchmarks/perf-tool/.pylintrc delete mode 100644 benchmarks/perf-tool/.style.yapf delete mode 100644 benchmarks/perf-tool/README.md delete mode 100644 benchmarks/perf-tool/add-filters-to-dataset.py delete mode 100644 benchmarks/perf-tool/add-parent-doc-id-to-dataset.py delete mode 100644 benchmarks/perf-tool/dataset/data-nested.hdf5 delete mode 100644 benchmarks/perf-tool/dataset/data-with-attr-with-filters.hdf5 delete mode 100644 benchmarks/perf-tool/dataset/data-with-attr.hdf5 delete mode 100644 benchmarks/perf-tool/dataset/data.hdf5 delete mode 100644 benchmarks/perf-tool/knn-perf-tool.py delete mode 100644 benchmarks/perf-tool/okpt/__init__.py delete mode 100644 benchmarks/perf-tool/okpt/diff/diff.py delete mode 100644 benchmarks/perf-tool/okpt/io/args.py delete mode 100644 benchmarks/perf-tool/okpt/io/config/parsers/base.py delete mode 100644 benchmarks/perf-tool/okpt/io/config/parsers/test.py delete mode 100644 benchmarks/perf-tool/okpt/io/config/parsers/util.py delete mode 100644 benchmarks/perf-tool/okpt/io/config/schemas/test.yml delete mode 100644 benchmarks/perf-tool/okpt/io/dataset.py delete mode 100644 benchmarks/perf-tool/okpt/io/utils/reader.py delete mode 100644 benchmarks/perf-tool/okpt/io/utils/writer.py delete mode 100644 benchmarks/perf-tool/okpt/main.py delete mode 100644 benchmarks/perf-tool/okpt/test/__init__.py delete mode 100644 benchmarks/perf-tool/okpt/test/profile.py delete mode 100644 benchmarks/perf-tool/okpt/test/runner.py delete mode 100644 benchmarks/perf-tool/okpt/test/steps/base.py delete mode 100644 benchmarks/perf-tool/okpt/test/steps/factory.py delete mode 100644 benchmarks/perf-tool/okpt/test/steps/steps.py delete mode 100644 benchmarks/perf-tool/okpt/test/test.py delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/index.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/relaxed-filter-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/relaxed-filter-test.yml delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/index.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/restrictive-filter-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/restrictive-filter-test.yml delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnsw/index.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnsw/nested/simple/index.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnsw/nested/simple/simple-nested-test.yml delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnsw/test.yml delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnswpq/index.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnswpq/method-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnswpq/test.yml delete mode 100644 benchmarks/perf-tool/release-configs/faiss-hnswpq/train-index-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/index.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/method-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/relaxed-filter-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/relaxed-filter-test.yml delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/train-index-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/index.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/method-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/restrictive-filter-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/restrictive-filter-test.yml delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/train-index-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/index.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/method-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/test.yml delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivf/train-index-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivfpq/index.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivfpq/method-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivfpq/test.yml delete mode 100644 benchmarks/perf-tool/release-configs/faiss-ivfpq/train-index-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/index.json delete mode 100644 benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/relaxed-filter-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/relaxed-filter-test.yml delete mode 100644 benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/index.json delete mode 100644 benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/restrictive-filter-spec.json delete mode 100644 benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/restrictive-filter-test.yml delete mode 100644 benchmarks/perf-tool/release-configs/lucene-hnsw/index.json delete mode 100644 benchmarks/perf-tool/release-configs/lucene-hnsw/nested/simple/index.json delete mode 100644 benchmarks/perf-tool/release-configs/lucene-hnsw/nested/simple/simple-nested-test.yml delete mode 100644 benchmarks/perf-tool/release-configs/lucene-hnsw/test.yml delete mode 100644 benchmarks/perf-tool/release-configs/nmslib-hnsw/index.json delete mode 100644 benchmarks/perf-tool/release-configs/nmslib-hnsw/test.yml delete mode 100755 benchmarks/perf-tool/release-configs/run_all_tests.sh delete mode 100644 benchmarks/perf-tool/requirements.in delete mode 100644 benchmarks/perf-tool/requirements.txt delete mode 100644 benchmarks/perf-tool/sample-configs/faiss-sift-ivf/index-spec.json delete mode 100644 benchmarks/perf-tool/sample-configs/faiss-sift-ivf/method-spec.json delete mode 100644 benchmarks/perf-tool/sample-configs/faiss-sift-ivf/test.yml delete mode 100644 benchmarks/perf-tool/sample-configs/faiss-sift-ivf/train-index-spec.json delete mode 100644 benchmarks/perf-tool/sample-configs/filter-spec/filter-1-spec.json delete mode 100644 benchmarks/perf-tool/sample-configs/filter-spec/filter-2-spec.json delete mode 100644 benchmarks/perf-tool/sample-configs/filter-spec/filter-3-spec.json delete mode 100644 benchmarks/perf-tool/sample-configs/filter-spec/filter-4-spec.json delete mode 100644 benchmarks/perf-tool/sample-configs/filter-spec/filter-5-spec.json delete mode 100644 benchmarks/perf-tool/sample-configs/lucene-sift-hnsw-filter/index-spec.json delete mode 100644 benchmarks/perf-tool/sample-configs/lucene-sift-hnsw-filter/test.yml delete mode 100644 benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/index-spec.json delete mode 100644 benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/test.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index e8e5e6d7b..fd7182e65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,4 +24,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Infrastructure ### Documentation ### Maintenance +* Remove benchmarks folder from k-NN repo [#2127](https://github.com/opensearch-project/k-NN/pull/2127) ### Refactoring diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 000000000..2e642d41b --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,4 @@ +## Benchmark Folder Tools Deprecated +All benchmark workloads have been moved to [OpenSearch Benchmark Workloads](https://github.com/opensearch-project/opensearch-benchmark-workloads/tree/main/vectorsearch). Please use OSB tool to run the benchmarks. + +If you are still interested in using the old tool, the benchmarks are moved to the [branch](https://github.com/opensearch-project/k-NN/tree/old-benchmarks/benchmarks). diff --git a/benchmarks/osb/README.md b/benchmarks/osb/README.md deleted file mode 100644 index 0d0b05f8d..000000000 --- a/benchmarks/osb/README.md +++ /dev/null @@ -1,478 +0,0 @@ -# IMPORTANT NOTE: No new features will be added to this tool . This tool is currently in maintanence mode. All new features will be added to [vector search workload]( https://github.com/opensearch-project/opensearch-benchmark-workloads/tree/main/vectorsearch) -# OpenSearch Benchmarks for k-NN - -## Overview - -This directory contains code and configurations to run k-NN benchmarking -workloads using OpenSearch Benchmarks. - -The [extensions](extensions) directory contains common code shared between -procedures. The [procedures](procedures) directory contains the individual -test procedures for this workload. - -## Getting Started - -### OpenSearch Benchmarks Background - -OpenSearch Benchmark is a framework for performance benchmarking an OpenSearch -cluster. For more details, checkout their -[repo](https://github.com/opensearch-project/opensearch-benchmark/). - -Before getting into the benchmarks, it is helpful to know a few terms: -1. Workload - Top level description of a benchmark suite. A workload will have a `workload.json` file that defines different components of the tests -2. Test Procedures - A workload can have a schedule of operations that run the test. However, a workload can also have several test procedures that define their own schedule of operations. This is helpful for sharing code between tests -3. Operation - An action against the OpenSearch cluster -4. Parameter source - Producers of parameters for OpenSearch operations -5. Runners - Code that actually will execute the OpenSearch operations - -### Setup - -OpenSearch Benchmarks requires Python 3.8 or greater to be installed. One of -the easier ways to do this is through Conda, a package and environment -management system for Python. - -First, follow the -[installation instructions](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html) -to install Conda on your system. - -Next, create a Python 3.8 environment: -``` -conda create -n knn-osb python=3.8 -``` - -After the environment is created, activate it: -``` -source activate knn-osb -``` - -Lastly, clone the k-NN repo and install all required python packages: -``` -git clone https://github.com/opensearch-project/k-NN.git -cd k-NN/benchmarks/osb -pip install -r requirements.txt -``` - -After all of this completes, you should be ready to run your first benchmark! - -### Running a benchmark - -Before running a benchmark, make sure you have the endpoint of your cluster and - the machine you are running the benchmarks from can access it. - Additionally, ensure that all data has been pulled to the client. - -Currently, we support 2 test procedures for the k-NN workload: train-test and -no-train-test. The train test has steps to train a model included in the -schedule, while no train does not. Both test procedures will index a data set -of vectors into an OpenSearch index and then run a set of queries against them. - -Once you have decided which test procedure you want to use, open up -[params/train-params.json](params/train-params.json) or -[params/no-train-params.json](params/no-train-params.json) and -fill out the parameters. Notice, at the bottom of `no-train-params.json` there -are several parameters that relate to training. Ignore these. They need to be -defined for the workload but not used. - -Once the parameters are set, set the URL and PORT of your cluster and run the -command to run the test procedure. - -``` -export URL= -export PORT= -export PARAMS_FILE= -export PROCEDURE={no-train-test | train-test} - -opensearch-benchmark execute_test \ - --target-hosts $URL:$PORT \ - --workload-path ./workload.json \ - --workload-params ${PARAMS_FILE} \ - --test-procedure=${PROCEDURE} \ - --pipeline benchmark-only -``` - -## Current Procedures - -### No Train Test - -The No Train Test procedure is used to test `knn_vector` indices that do not -use an algorithm that requires training. - -#### Workflow - -1. Delete old resources in the cluster if they are present -2. Create an OpenSearch index with `knn_vector` configured to use the HNSW algorithm -3. Wait for cluster to be green -4. Ingest data set into the cluster -5. Refresh the index -6. Run queries from data set against the cluster - -#### Parameters - -| Name | Description | -|-----------------------------------------|--------------------------------------------------------------------------| -| target_index_name | Name of index to add vectors to | -| target_field_name | Name of field to add vectors to | -| target_index_body | Path to target index definition | -| target_index_primary_shards | Target index primary shards | -| target_index_replica_shards | Target index replica shards | -| target_index_dimension | Dimension of target index | -| target_index_space_type | Target index space type | -| target_index_bulk_size | Target index bulk size | -| target_index_bulk_index_data_set_format | Format of vector data set | -| target_index_bulk_index_data_set_path | Path to vector data set | -| target_index_bulk_index_clients | Clients to be used for bulk ingestion (must be divisor of data set size) | -| target_index_max_num_segments | Number of segments to merge target index down to before beginning search | -| target_index_force_merge_timeout | Timeout for of force merge requests in seconds | -| hnsw_ef_search | HNSW ef search parameter | -| hnsw_ef_construction | HNSW ef construction parameter | -| hnsw_m | HNSW m parameter | -| query_k | The number of neighbors to return for the search | -| query_clients | Number of clients to use for running queries | -| query_data_set_format | Format of vector data set for queries | -| query_data_set_path | Path to vector data set for queries | - -#### Metrics - -The result metrics of this procedure will look like: -``` ------------------------------------------------------- - _______ __ _____ - / ____(_)___ ____ _/ / / ___/_________ ________ - / /_ / / __ \/ __ `/ / \__ \/ ___/ __ \/ ___/ _ \ - / __/ / / / / / /_/ / / ___/ / /__/ /_/ / / / __/ -/_/ /_/_/ /_/\__,_/_/ /____/\___/\____/_/ \___/ ------------------------------------------------------- - -| Metric | Task | Value | Unit | -|---------------------------------------------------------------:|------------------------:|------------:|-------:| -| Cumulative indexing time of primary shards | | 1.82885 | min | -| Min cumulative indexing time across primary shards | | 0.4121 | min | -| Median cumulative indexing time across primary shards | | 0.559617 | min | -| Max cumulative indexing time across primary shards | | 0.857133 | min | -| Cumulative indexing throttle time of primary shards | | 0 | min | -| Min cumulative indexing throttle time across primary shards | | 0 | min | -| Median cumulative indexing throttle time across primary shards | | 0 | min | -| Max cumulative indexing throttle time across primary shards | | 0 | min | -| Cumulative merge time of primary shards | | 5.89065 | min | -| Cumulative merge count of primary shards | | 3 | | -| Min cumulative merge time across primary shards | | 1.95945 | min | -| Median cumulative merge time across primary shards | | 1.96345 | min | -| Max cumulative merge time across primary shards | | 1.96775 | min | -| Cumulative merge throttle time of primary shards | | 0 | min | -| Min cumulative merge throttle time across primary shards | | 0 | min | -| Median cumulative merge throttle time across primary shards | | 0 | min | -| Max cumulative merge throttle time across primary shards | | 0 | min | -| Cumulative refresh time of primary shards | | 8.52517 | min | -| Cumulative refresh count of primary shards | | 29 | | -| Min cumulative refresh time across primary shards | | 2.64265 | min | -| Median cumulative refresh time across primary shards | | 2.93913 | min | -| Max cumulative refresh time across primary shards | | 2.94338 | min | -| Cumulative flush time of primary shards | | 0.00221667 | min | -| Cumulative flush count of primary shards | | 3 | | -| Min cumulative flush time across primary shards | | 0.000733333 | min | -| Median cumulative flush time across primary shards | | 0.000733333 | min | -| Max cumulative flush time across primary shards | | 0.00075 | min | -| Total Young Gen GC time | | 0.318 | s | -| Total Young Gen GC count | | 2 | | -| Total Old Gen GC time | | 0 | s | -| Total Old Gen GC count | | 0 | | -| Store size | | 1.43566 | GB | -| Translog size | | 1.53668e-07 | GB | -| Heap used for segments | | 0.00410843 | MB | -| Heap used for doc values | | 0.000286102 | MB | -| Heap used for terms | | 0.00121307 | MB | -| Heap used for norms | | 0 | MB | -| Heap used for points | | 0 | MB | -| Heap used for stored fields | | 0.00260925 | MB | -| Segment count | | 3 | | -| Min Throughput | custom-vector-bulk | 38005.8 | docs/s | -| Mean Throughput | custom-vector-bulk | 44827.9 | docs/s | -| Median Throughput | custom-vector-bulk | 40507.2 | docs/s | -| Max Throughput | custom-vector-bulk | 88967.8 | docs/s | -| 50th percentile latency | custom-vector-bulk | 29.5857 | ms | -| 90th percentile latency | custom-vector-bulk | 49.0719 | ms | -| 99th percentile latency | custom-vector-bulk | 72.6138 | ms | -| 99.9th percentile latency | custom-vector-bulk | 279.826 | ms | -| 100th percentile latency | custom-vector-bulk | 15688 | ms | -| 50th percentile service time | custom-vector-bulk | 29.5857 | ms | -| 90th percentile service time | custom-vector-bulk | 49.0719 | ms | -| 99th percentile service time | custom-vector-bulk | 72.6138 | ms | -| 99.9th percentile service time | custom-vector-bulk | 279.826 | ms | -| 100th percentile service time | custom-vector-bulk | 15688 | ms | -| error rate | custom-vector-bulk | 0 | % | -| Min Throughput | refresh-target-index | 0.01 | ops/s | -| Mean Throughput | refresh-target-index | 0.01 | ops/s | -| Median Throughput | refresh-target-index | 0.01 | ops/s | -| Max Throughput | refresh-target-index | 0.01 | ops/s | -| 100th percentile latency | refresh-target-index | 176610 | ms | -| 100th percentile service time | refresh-target-index | 176610 | ms | -| error rate | refresh-target-index | 0 | % | -| Min Throughput | knn-query-from-data-set | 444.17 | ops/s | -| Mean Throughput | knn-query-from-data-set | 601.68 | ops/s | -| Median Throughput | knn-query-from-data-set | 621.19 | ops/s | -| Max Throughput | knn-query-from-data-set | 631.23 | ops/s | -| 50th percentile latency | knn-query-from-data-set | 14.7612 | ms | -| 90th percentile latency | knn-query-from-data-set | 20.6954 | ms | -| 99th percentile latency | knn-query-from-data-set | 27.7499 | ms | -| 99.9th percentile latency | knn-query-from-data-set | 41.3506 | ms | -| 99.99th percentile latency | knn-query-from-data-set | 162.391 | ms | -| 100th percentile latency | knn-query-from-data-set | 162.756 | ms | -| 50th percentile service time | knn-query-from-data-set | 14.7612 | ms | -| 90th percentile service time | knn-query-from-data-set | 20.6954 | ms | -| 99th percentile service time | knn-query-from-data-set | 27.7499 | ms | -| 99.9th percentile service time | knn-query-from-data-set | 41.3506 | ms | -| 99.99th percentile service time | knn-query-from-data-set | 162.391 | ms | -| 100th percentile service time | knn-query-from-data-set | 162.756 | ms | -| error rate | knn-query-from-data-set | 0 | % | - - ---------------------------------- -[INFO] SUCCESS (took 618 seconds) ---------------------------------- -``` - -### Train Test - -The Train Test procedure is used to test `knn_vector` indices that do use an -algorithm that requires training. - -#### Workflow - -1. Delete old resources in the cluster if they are present -2. Create an OpenSearch index with `knn_vector` configured to load with training data -3. Wait for cluster to be green -4. Ingest data set into the training index -5. Refresh the index -6. Train a model based on user provided input parameters -7. Create an OpenSearch index with `knn_vector` configured to use the model -8. Ingest vectors into the target index -9. Refresh the target index -10. Run queries from data set against the cluster - -#### Parameters - -| Name | Description | -|-----------------------------------------|--------------------------------------------------------------------------| -| target_index_name | Name of index to add vectors to | -| target_field_name | Name of field to add vectors to | -| target_index_body | Path to target index definition | -| target_index_primary_shards | Target index primary shards | -| target_index_replica_shards | Target index replica shards | -| target_index_dimension | Dimension of target index | -| target_index_space_type | Target index space type | -| target_index_bulk_size | Target index bulk size | -| target_index_bulk_index_data_set_format | Format of vector data set for ingestion | -| target_index_bulk_index_data_set_path | Path to vector data set for ingestion | -| target_index_bulk_index_clients | Clients to be used for bulk ingestion (must be divisor of data set size) | -| target_index_max_num_segments | Number of segments to merge target index down to before beginning search | -| target_index_force_merge_timeout | Timeout for of force merge requests in seconds | -| ivf_nlists | IVF nlist parameter | -| ivf_nprobes | IVF nprobe parameter | -| pq_code_size | PQ code_size parameter | -| pq_m | PQ m parameter | -| train_model_method | Method to be used for model (ivf or ivfpq) | -| train_model_id | Model ID | -| train_index_name | Name of index to put training data into | -| train_field_name | Name of field to put training data into | -| train_index_body | Path to train index definition | -| train_search_size | Search size to use when pulling training data | -| train_timeout | Timeout to wait for training to finish | -| train_index_primary_shards | Train index primary shards | -| train_index_replica_shards | Train index replica shards | -| train_index_bulk_size | Train index bulk size | -| train_index_data_set_format | Format of vector data set for training | -| train_index_data_set_path | Path to vector data set for training | -| train_index_num_vectors | Number of vectors to use from vector data set for training | -| train_index_bulk_index_clients | Clients to be used for bulk ingestion (must be divisor of data set size) | -| query_k | The number of neighbors to return for the search | -| query_clients | Number of clients to use for running queries | -| query_data_set_format | Format of vector data set for queries | -| query_data_set_path | Path to vector data set for queries | - -#### Metrics - -The result metrics of this procedure will look like: -``` ------------------------------------------------------- - _______ __ _____ - / ____(_)___ ____ _/ / / ___/_________ ________ - / /_ / / __ \/ __ `/ / \__ \/ ___/ __ \/ ___/ _ \ - / __/ / / / / / /_/ / / ___/ / /__/ /_/ / / / __/ -/_/ /_/_/ /_/\__,_/_/ /____/\___/\____/_/ \___/ ------------------------------------------------------- - -| Metric | Task | Value | Unit | -|---------------------------------------------------------------:|------------------------:|-----------:|-----------------:| -| Cumulative indexing time of primary shards | | 2.92382 | min | -| Min cumulative indexing time across primary shards | | 0.42245 | min | -| Median cumulative indexing time across primary shards | | 0.43395 | min | -| Max cumulative indexing time across primary shards | | 1.63347 | min | -| Cumulative indexing throttle time of primary shards | | 0 | min | -| Min cumulative indexing throttle time across primary shards | | 0 | min | -| Median cumulative indexing throttle time across primary shards | | 0 | min | -| Max cumulative indexing throttle time across primary shards | | 0 | min | -| Cumulative merge time of primary shards | | 1.36293 | min | -| Cumulative merge count of primary shards | | 20 | | -| Min cumulative merge time across primary shards | | 0.263283 | min | -| Median cumulative merge time across primary shards | | 0.291733 | min | -| Max cumulative merge time across primary shards | | 0.516183 | min | -| Cumulative merge throttle time of primary shards | | 0.701683 | min | -| Min cumulative merge throttle time across primary shards | | 0.163883 | min | -| Median cumulative merge throttle time across primary shards | | 0.175717 | min | -| Max cumulative merge throttle time across primary shards | | 0.186367 | min | -| Cumulative refresh time of primary shards | | 0.222217 | min | -| Cumulative refresh count of primary shards | | 67 | | -| Min cumulative refresh time across primary shards | | 0.03915 | min | -| Median cumulative refresh time across primary shards | | 0.039825 | min | -| Max cumulative refresh time across primary shards | | 0.103417 | min | -| Cumulative flush time of primary shards | | 0.0276833 | min | -| Cumulative flush count of primary shards | | 1 | | -| Min cumulative flush time across primary shards | | 0 | min | -| Median cumulative flush time across primary shards | | 0 | min | -| Max cumulative flush time across primary shards | | 0.0276833 | min | -| Total Young Gen GC time | | 0.074 | s | -| Total Young Gen GC count | | 8 | | -| Total Old Gen GC time | | 0 | s | -| Total Old Gen GC count | | 0 | | -| Store size | | 1.67839 | GB | -| Translog size | | 0.115145 | GB | -| Heap used for segments | | 0.0350914 | MB | -| Heap used for doc values | | 0.00771713 | MB | -| Heap used for terms | | 0.0101089 | MB | -| Heap used for norms | | 0 | MB | -| Heap used for points | | 0 | MB | -| Heap used for stored fields | | 0.0172653 | MB | -| Segment count | | 25 | | -| Min Throughput | delete-model | 25.45 | ops/s | -| Mean Throughput | delete-model | 25.45 | ops/s | -| Median Throughput | delete-model | 25.45 | ops/s | -| Max Throughput | delete-model | 25.45 | ops/s | -| 100th percentile latency | delete-model | 39.0409 | ms | -| 100th percentile service time | delete-model | 39.0409 | ms | -| error rate | delete-model | 0 | % | -| Min Throughput | train-vector-bulk | 49518.9 | docs/s | -| Mean Throughput | train-vector-bulk | 54418.8 | docs/s | -| Median Throughput | train-vector-bulk | 52984.2 | docs/s | -| Max Throughput | train-vector-bulk | 62118.3 | docs/s | -| 50th percentile latency | train-vector-bulk | 26.5293 | ms | -| 90th percentile latency | train-vector-bulk | 41.8212 | ms | -| 99th percentile latency | train-vector-bulk | 239.351 | ms | -| 99.9th percentile latency | train-vector-bulk | 348.507 | ms | -| 100th percentile latency | train-vector-bulk | 436.292 | ms | -| 50th percentile service time | train-vector-bulk | 26.5293 | ms | -| 90th percentile service time | train-vector-bulk | 41.8212 | ms | -| 99th percentile service time | train-vector-bulk | 239.351 | ms | -| 99.9th percentile service time | train-vector-bulk | 348.507 | ms | -| 100th percentile service time | train-vector-bulk | 436.292 | ms | -| error rate | train-vector-bulk | 0 | % | -| Min Throughput | refresh-train-index | 0.47 | ops/s | -| Mean Throughput | refresh-train-index | 0.47 | ops/s | -| Median Throughput | refresh-train-index | 0.47 | ops/s | -| Max Throughput | refresh-train-index | 0.47 | ops/s | -| 100th percentile latency | refresh-train-index | 2142.96 | ms | -| 100th percentile service time | refresh-train-index | 2142.96 | ms | -| error rate | refresh-train-index | 0 | % | -| Min Throughput | ivfpq-train-model | 0.01 | models_trained/s | -| Mean Throughput | ivfpq-train-model | 0.01 | models_trained/s | -| Median Throughput | ivfpq-train-model | 0.01 | models_trained/s | -| Max Throughput | ivfpq-train-model | 0.01 | models_trained/s | -| 100th percentile latency | ivfpq-train-model | 136563 | ms | -| 100th percentile service time | ivfpq-train-model | 136563 | ms | -| error rate | ivfpq-train-model | 0 | % | -| Min Throughput | custom-vector-bulk | 62384.8 | docs/s | -| Mean Throughput | custom-vector-bulk | 69035.2 | docs/s | -| Median Throughput | custom-vector-bulk | 68675.4 | docs/s | -| Max Throughput | custom-vector-bulk | 80713.4 | docs/s | -| 50th percentile latency | custom-vector-bulk | 18.7726 | ms | -| 90th percentile latency | custom-vector-bulk | 34.8881 | ms | -| 99th percentile latency | custom-vector-bulk | 150.435 | ms | -| 99.9th percentile latency | custom-vector-bulk | 296.862 | ms | -| 100th percentile latency | custom-vector-bulk | 344.394 | ms | -| 50th percentile service time | custom-vector-bulk | 18.7726 | ms | -| 90th percentile service time | custom-vector-bulk | 34.8881 | ms | -| 99th percentile service time | custom-vector-bulk | 150.435 | ms | -| 99.9th percentile service time | custom-vector-bulk | 296.862 | ms | -| 100th percentile service time | custom-vector-bulk | 344.394 | ms | -| error rate | custom-vector-bulk | 0 | % | -| Min Throughput | refresh-target-index | 28.32 | ops/s | -| Mean Throughput | refresh-target-index | 28.32 | ops/s | -| Median Throughput | refresh-target-index | 28.32 | ops/s | -| Max Throughput | refresh-target-index | 28.32 | ops/s | -| 100th percentile latency | refresh-target-index | 34.9811 | ms | -| 100th percentile service time | refresh-target-index | 34.9811 | ms | -| error rate | refresh-target-index | 0 | % | -| Min Throughput | knn-query-from-data-set | 0.9 | ops/s | -| Mean Throughput | knn-query-from-data-set | 453.84 | ops/s | -| Median Throughput | knn-query-from-data-set | 554.15 | ops/s | -| Max Throughput | knn-query-from-data-set | 681 | ops/s | -| 50th percentile latency | knn-query-from-data-set | 11.7174 | ms | -| 90th percentile latency | knn-query-from-data-set | 15.4445 | ms | -| 99th percentile latency | knn-query-from-data-set | 21.0682 | ms | -| 99.9th percentile latency | knn-query-from-data-set | 39.5414 | ms | -| 99.99th percentile latency | knn-query-from-data-set | 1116.33 | ms | -| 100th percentile latency | knn-query-from-data-set | 1116.66 | ms | -| 50th percentile service time | knn-query-from-data-set | 11.7174 | ms | -| 90th percentile service time | knn-query-from-data-set | 15.4445 | ms | -| 99th percentile service time | knn-query-from-data-set | 21.0682 | ms | -| 99.9th percentile service time | knn-query-from-data-set | 39.5414 | ms | -| 99.99th percentile service time | knn-query-from-data-set | 1116.33 | ms | -| 100th percentile service time | knn-query-from-data-set | 1116.66 | ms | -| error rate | knn-query-from-data-set | 0 | % | - - ---------------------------------- -[INFO] SUCCESS (took 281 seconds) ---------------------------------- -``` - -## Adding a procedure - -Adding additional benchmarks is very simple. First, place any custom parameter -sources or runners in the [extensions](extensions) directory so that other tests -can use them and also update the [documentation](#custom-extensions) -accordingly. - -Next, create a new test procedure file and add the operations you want your test -to run. Lastly, be sure to update documentation. - -## Custom Extensions - -OpenSearch Benchmarks is very extendable. To fit the plugins needs, we add -customer parameter sources and custom runners. Parameter sources allow users to -supply custom parameters to an operation. Runners are what actually performs -the operations against OpenSearch. - -### Custom Parameter Sources - -Custom parameter sources are defined in [extensions/param_sources.py](extensions/param_sources.py). - -| Name | Description | Parameters | -|-------------------------|------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| bulk-from-data-set | Provides bulk payloads containing vectors from a data set for indexing | 1. data_set_format - (hdf5, bigann)
2. data_set_path - path to data set
3. index - name of index for bulk ingestion
4. field - field to place vector in
5. bulk_size - vectors per bulk request
6. num_vectors - number of vectors to use from the data set. Defaults to the whole data set. | -| knn-query-from-data-set | Provides a query generated from a data set | 1. data_set_format - (hdf5, bigann)
2. data_set_path - path to data set
3. index - name of index to query against
4. field - field to to query against
5. k - number of results to return
6. dimension - size of vectors to produce
7. num_vectors - number of vectors to use from the data set. Defaults to the whole data set. | - - -### Custom Runners - -Custom runners are defined in [extensions/runners.py](extensions/runners.py). - -| Syntax | Description | Parameters | -|--------------------|-----------------------------------------------------|:-------------------------------------------------------------------------------------------------------------| -| custom-vector-bulk | Bulk index a set of vectors in an OpenSearch index. | 1. bulk-from-data-set | -| custom-refresh | Run refresh with retry capabilities. | 1. index - name of index to refresh
2. retries - number of times to retry the operation | -| train-model | Trains a model. | 1. body - model definition
2. timeout - time to wait for model to finish
3. model_id - ID of model | -| delete-model | Deletes a model if it exists. | 1. model_id - ID of model | - -### Testing - -We have a set of unit tests for our extensions in -[tests](tests). To run all the tests, run the following -command: - -```commandline -python -m unittest discover ./tests -``` - -To run an individual test: -```commandline -python -m unittest tests.test_param_sources.VectorsFromDataSetParamSourceTestCase.test_partition_hdf5 -``` diff --git a/benchmarks/osb/__init__.py b/benchmarks/osb/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/benchmarks/osb/extensions/__init__.py b/benchmarks/osb/extensions/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/benchmarks/osb/extensions/data_set.py b/benchmarks/osb/extensions/data_set.py deleted file mode 100644 index 7e8058844..000000000 --- a/benchmarks/osb/extensions/data_set.py +++ /dev/null @@ -1,202 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -import os -import numpy as np -from abc import ABC, ABCMeta, abstractmethod -from enum import Enum -from typing import cast -import h5py -import struct - - -class Context(Enum): - """DataSet context enum. Can be used to add additional context for how a - data-set should be interpreted. - """ - INDEX = 1 - QUERY = 2 - NEIGHBORS = 3 - - -class DataSet(ABC): - """DataSet interface. Used for reading data-sets from files. - - Methods: - read: Read a chunk of data from the data-set - seek: Get to position in the data-set - size: Gets the number of items in the data-set - reset: Resets internal state of data-set to beginning - """ - __metaclass__ = ABCMeta - - BEGINNING = 0 - - @abstractmethod - def read(self, chunk_size: int): - pass - - @abstractmethod - def seek(self, offset: int): - pass - - @abstractmethod - def size(self): - pass - - @abstractmethod - def reset(self): - pass - - -class HDF5DataSet(DataSet): - """ Data-set format corresponding to `ANN Benchmarks - `_ - """ - - FORMAT_NAME = "hdf5" - - def __init__(self, dataset_path: str, context: Context): - file = h5py.File(dataset_path) - self.data = cast(h5py.Dataset, file[self.parse_context(context)]) - self.current = self.BEGINNING - - def read(self, chunk_size: int): - if self.current >= self.size(): - return None - - end_offset = self.current + chunk_size - if end_offset > self.size(): - end_offset = self.size() - - v = cast(np.ndarray, self.data[self.current:end_offset]) - self.current = end_offset - return v - - def seek(self, offset: int): - - if offset < self.BEGINNING: - raise Exception("Offset must be greater than or equal to 0") - - if offset >= self.size(): - raise Exception("Offset must be less than the data set size") - - self.current = offset - - def size(self): - return self.data.len() - - def reset(self): - self.current = self.BEGINNING - - @staticmethod - def parse_context(context: Context) -> str: - if context == Context.NEIGHBORS: - return "neighbors" - - if context == Context.INDEX: - return "train" - - if context == Context.QUERY: - return "test" - - raise Exception("Unsupported context") - - -class BigANNVectorDataSet(DataSet): - """ Data-set format for vector data-sets for `Big ANN Benchmarks - `_ - """ - - DATA_SET_HEADER_LENGTH = 8 - U8BIN_EXTENSION = "u8bin" - FBIN_EXTENSION = "fbin" - FORMAT_NAME = "bigann" - - BYTES_PER_U8INT = 1 - BYTES_PER_FLOAT = 4 - - def __init__(self, dataset_path: str): - self.file = open(dataset_path, 'rb') - self.file.seek(BigANNVectorDataSet.BEGINNING, os.SEEK_END) - num_bytes = self.file.tell() - self.file.seek(BigANNVectorDataSet.BEGINNING) - - if num_bytes < BigANNVectorDataSet.DATA_SET_HEADER_LENGTH: - raise Exception("File is invalid") - - self.num_points = int.from_bytes(self.file.read(4), "little") - self.dimension = int.from_bytes(self.file.read(4), "little") - self.bytes_per_num = self._get_data_size(dataset_path) - - if (num_bytes - BigANNVectorDataSet.DATA_SET_HEADER_LENGTH) != self.num_points * \ - self.dimension * self.bytes_per_num: - raise Exception("File is invalid") - - self.reader = self._value_reader(dataset_path) - self.current = BigANNVectorDataSet.BEGINNING - - def read(self, chunk_size: int): - if self.current >= self.size(): - return None - - end_offset = self.current + chunk_size - if end_offset > self.size(): - end_offset = self.size() - - v = np.asarray([self._read_vector() for _ in - range(end_offset - self.current)]) - self.current = end_offset - return v - - def seek(self, offset: int): - - if offset < self.BEGINNING: - raise Exception("Offset must be greater than or equal to 0") - - if offset >= self.size(): - raise Exception("Offset must be less than the data set size") - - bytes_offset = BigANNVectorDataSet.DATA_SET_HEADER_LENGTH + \ - self.dimension * self.bytes_per_num * offset - self.file.seek(bytes_offset) - self.current = offset - - def _read_vector(self): - return np.asarray([self.reader(self.file) for _ in - range(self.dimension)]) - - def size(self): - return self.num_points - - def reset(self): - self.file.seek(BigANNVectorDataSet.DATA_SET_HEADER_LENGTH) - self.current = BigANNVectorDataSet.BEGINNING - - def __del__(self): - self.file.close() - - @staticmethod - def _get_data_size(file_name): - ext = file_name.split('.')[-1] - if ext == BigANNVectorDataSet.U8BIN_EXTENSION: - return BigANNVectorDataSet.BYTES_PER_U8INT - - if ext == BigANNVectorDataSet.FBIN_EXTENSION: - return BigANNVectorDataSet.BYTES_PER_FLOAT - - raise Exception("Unknown extension") - - @staticmethod - def _value_reader(file_name): - ext = file_name.split('.')[-1] - if ext == BigANNVectorDataSet.U8BIN_EXTENSION: - return lambda file: float(int.from_bytes(file.read(BigANNVectorDataSet.BYTES_PER_U8INT), "little")) - - if ext == BigANNVectorDataSet.FBIN_EXTENSION: - return lambda file: struct.unpack('= self.num_vectors + self.offset: - raise StopIteration - - if self.vector_batch is None or len(self.vector_batch) == 0: - self.vector_batch = self._batch_read(self.data_set) - if self.vector_batch is None: - raise StopIteration - vector = self.vector_batch.pop(0) - self.current += 1 - self.percent_completed = self.current / self.total - - return self._build_query_body(self.index_name, self.field_name, self.k, - vector) - - def _batch_read(self, data_set: DataSet): - return list(data_set.read(self.VECTOR_READ_BATCH_SIZE)) - - def _build_query_body(self, index_name: str, field_name: str, k: int, - vector) -> dict: - """Builds a k-NN query that can be used to execute an approximate nearest - neighbor search against a k-NN plugin index - Args: - index_name: name of index to search - field_name: name of field to search - k: number of results to return - vector: vector used for query - Returns: - A dictionary containing the body used for search, a set of request - parameters to attach to the search and the name of the index. - """ - return { - "index": index_name, - "request-params": { - "_source": { - "exclude": [field_name] - } - }, - "body": { - "size": k, - "query": { - "knn": { - field_name: { - "vector": vector, - "k": k - } - } - } - } - } - - -class BulkVectorsFromDataSetParamSource(VectorsFromDataSetParamSource): - """ Create bulk index requests from a data set of vectors. - - Attributes: - bulk_size: number of vectors per request - retries: number of times to retry the request when it fails - """ - - DEFAULT_RETRIES = 10 - - def __init__(self, workload, params, **kwargs): - super().__init__(params, Context.INDEX) - self.bulk_size: int = parse_int_parameter("bulk_size", params) - self.retries: int = parse_int_parameter("retries", params, - self.DEFAULT_RETRIES) - - def params(self): - """ - Returns: A bulk index parameter with vectors from a data set. - """ - if self.current >= self.num_vectors + self.offset: - raise StopIteration - - def action(doc_id): - return {'index': {'_index': self.index_name, '_id': doc_id}} - - partition = self.data_set.read(self.bulk_size) - body = bulk_transform(partition, self.field_name, action, self.current) - size = len(body) // 2 - self.current += size - self.percent_completed = self.current / self.total - - return { - "body": body, - "retries": self.retries, - "size": size - } diff --git a/benchmarks/osb/extensions/registry.py b/benchmarks/osb/extensions/registry.py deleted file mode 100644 index 5ce17ab6f..000000000 --- a/benchmarks/osb/extensions/registry.py +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -from .param_sources import register as param_sources_register -from .runners import register as runners_register - - -def register(registry): - param_sources_register(registry) - runners_register(registry) diff --git a/benchmarks/osb/extensions/runners.py b/benchmarks/osb/extensions/runners.py deleted file mode 100644 index d048f80b0..000000000 --- a/benchmarks/osb/extensions/runners.py +++ /dev/null @@ -1,121 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -from opensearchpy.exceptions import ConnectionTimeout -from .util import parse_int_parameter, parse_string_parameter -import logging -import time - - -def register(registry): - registry.register_runner( - "custom-vector-bulk", BulkVectorsFromDataSetRunner(), async_runner=True - ) - registry.register_runner( - "custom-refresh", CustomRefreshRunner(), async_runner=True - ) - registry.register_runner( - "train-model", TrainModelRunner(), async_runner=True - ) - registry.register_runner( - "delete-model", DeleteModelRunner(), async_runner=True - ) - - -class BulkVectorsFromDataSetRunner: - - async def __call__(self, opensearch, params): - size = parse_int_parameter("size", params) - retries = parse_int_parameter("retries", params, 0) + 1 - - for _ in range(retries): - try: - await opensearch.bulk( - body=params["body"], - timeout='5m' - ) - - return size, "docs" - except ConnectionTimeout: - logging.getLogger(__name__)\ - .warning("Bulk vector ingestion timed out. Retrying") - - raise TimeoutError("Failed to submit bulk request in specified number " - "of retries: {}".format(retries)) - - def __repr__(self, *args, **kwargs): - return "custom-vector-bulk" - - -class CustomRefreshRunner: - - async def __call__(self, opensearch, params): - retries = parse_int_parameter("retries", params, 0) + 1 - - for _ in range(retries): - try: - await opensearch.indices.refresh( - index=parse_string_parameter("index", params) - ) - - return - except ConnectionTimeout: - logging.getLogger(__name__)\ - .warning("Custom refresh timed out. Retrying") - - raise TimeoutError("Failed to refresh the index in specified number " - "of retries: {}".format(retries)) - - def __repr__(self, *args, **kwargs): - return "custom-refresh" - - -class TrainModelRunner: - - async def __call__(self, opensearch, params): - # Train a model and wait for it training to complete - body = params["body"] - timeout = parse_int_parameter("timeout", params) - model_id = parse_string_parameter("model_id", params) - - method = "POST" - model_uri = "/_plugins/_knn/models/{}".format(model_id) - await opensearch.transport.perform_request(method, "{}/_train".format(model_uri), body=body) - - start_time = time.time() - while time.time() < start_time + timeout: - time.sleep(1) - model_response = await opensearch.transport.perform_request("GET", model_uri) - - if 'state' not in model_response.keys(): - continue - - if model_response['state'] == 'created': - #TODO: Return model size as well - return 1, "models_trained" - - if model_response['state'] == 'failed': - raise Exception("Failed to create model: {}".format(model_response)) - - raise Exception('Failed to create model: {} within timeout {} seconds' - .format(model_id, timeout)) - - def __repr__(self, *args, **kwargs): - return "train-model" - - -class DeleteModelRunner: - - async def __call__(self, opensearch, params): - # Delete model provided by model id - method = "DELETE" - model_id = parse_string_parameter("model_id", params) - uri = "/_plugins/_knn/models/{}".format(model_id) - - # Ignore if model doesnt exist - await opensearch.transport.perform_request(method, uri, params={"ignore": [400, 404]}) - - def __repr__(self, *args, **kwargs): - return "delete-model" diff --git a/benchmarks/osb/extensions/util.py b/benchmarks/osb/extensions/util.py deleted file mode 100644 index f7f6aab62..000000000 --- a/benchmarks/osb/extensions/util.py +++ /dev/null @@ -1,71 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -import numpy as np -from typing import List -from typing import Dict -from typing import Any - - -def bulk_transform(partition: np.ndarray, field_name: str, action, - offset: int) -> List[Dict[str, Any]]: - """Partitions and transforms a list of vectors into OpenSearch's bulk - injection format. - Args: - offset: to start counting from - partition: An array of vectors to transform. - field_name: field name for action - action: Bulk API action. - Returns: - An array of transformed vectors in bulk format. - """ - actions = [] - _ = [ - actions.extend([action(i + offset), None]) - for i in range(len(partition)) - ] - actions[1::2] = [{field_name: vec} for vec in partition.tolist()] - return actions - - -def parse_string_parameter(key: str, params: dict, default: str = None) -> str: - if key not in params: - if default is not None: - return default - raise ConfigurationError( - "Value cannot be None for param {}".format(key) - ) - - if type(params[key]) is str: - return params[key] - - raise ConfigurationError("Value must be a string for param {}".format(key)) - - -def parse_int_parameter(key: str, params: dict, default: int = None) -> int: - if key not in params: - if default: - return default - raise ConfigurationError( - "Value cannot be None for param {}".format(key) - ) - - if type(params[key]) is int: - return params[key] - - raise ConfigurationError("Value must be a int for param {}".format(key)) - - -class ConfigurationError(Exception): - """Exception raised for errors configuration. - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message: str): - self.message = f'{message}' - super().__init__(self.message) diff --git a/benchmarks/osb/indices/faiss-index.json b/benchmarks/osb/indices/faiss-index.json deleted file mode 100644 index 2db4d34d4..000000000 --- a/benchmarks/osb/indices/faiss-index.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": {{ target_index_primary_shards }}, - "number_of_replicas": {{ target_index_replica_shards }} - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": {{ target_index_dimension }}, - "method": { - "name": "hnsw", - "space_type": "{{ target_index_space_type }}", - "engine": "faiss", - "parameters": { - "ef_search": {{ hnsw_ef_search }}, - "ef_construction": {{ hnsw_ef_construction }}, - "m": {{ hnsw_m }} - } - } - } - } - } -} diff --git a/benchmarks/osb/indices/lucene-index.json b/benchmarks/osb/indices/lucene-index.json deleted file mode 100644 index 0a4ed868a..000000000 --- a/benchmarks/osb/indices/lucene-index.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": {{ target_index_primary_shards }}, - "number_of_replicas": {{ target_index_replica_shards }} - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": {{ target_index_dimension }}, - "method": { - "name": "hnsw", - "space_type": "{{ target_index_space_type }}", - "engine": "lucene", - "parameters": { - "ef_construction": {{ hnsw_ef_construction }}, - "m": {{ hnsw_m }} - } - } - } - } - } -} diff --git a/benchmarks/osb/indices/model-index.json b/benchmarks/osb/indices/model-index.json deleted file mode 100644 index 0e92c8903..000000000 --- a/benchmarks/osb/indices/model-index.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": {{ target_index_primary_shards | default(1) }}, - "number_of_replicas": {{ target_index_replica_shards | default(0) }} - } - }, - "mappings": { - "properties": { - "{{ target_field_name }}": { - "type": "knn_vector", - "model_id": "{{ train_model_id }}" - } - } - } -} diff --git a/benchmarks/osb/indices/nmslib-index.json b/benchmarks/osb/indices/nmslib-index.json deleted file mode 100644 index 4ceb57977..000000000 --- a/benchmarks/osb/indices/nmslib-index.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "knn.algo_param.ef_search": {{ hnsw_ef_search }}, - "number_of_shards": {{ target_index_primary_shards }}, - "number_of_replicas": {{ target_index_replica_shards }} - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": {{ target_index_dimension }}, - "method": { - "name": "hnsw", - "space_type": "{{ target_index_space_type }}", - "engine": "nmslib", - "parameters": { - "ef_construction": {{ hnsw_ef_construction }}, - "m": {{ hnsw_m }} - } - } - } - } - } -} diff --git a/benchmarks/osb/indices/train-index.json b/benchmarks/osb/indices/train-index.json deleted file mode 100644 index 82af8215e..000000000 --- a/benchmarks/osb/indices/train-index.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "settings": { - "index": { - "number_of_shards": {{ train_index_primary_shards }}, - "number_of_replicas": {{ train_index_replica_shards }} - } - }, - "mappings": { - "properties": { - "{{ train_field_name }}": { - "type": "knn_vector", - "dimension": {{ target_index_dimension }} - } - } - } -} diff --git a/benchmarks/osb/operations/default.json b/benchmarks/osb/operations/default.json deleted file mode 100644 index ee33166f0..000000000 --- a/benchmarks/osb/operations/default.json +++ /dev/null @@ -1,53 +0,0 @@ -[ - { - "name": "ivfpq-train-model", - "operation-type": "train-model", - "model_id": "{{ train_model_id }}", - "timeout": {{ train_timeout }}, - "body": { - "training_index": "{{ train_index_name }}", - "training_field": "{{ train_field_name }}", - "dimension": {{ target_index_dimension }}, - "search_size": {{ train_search_size }}, - "max_training_vector_count": {{ train_index_num_vectors }}, - "method": { - "name":"ivf", - "engine":"faiss", - "space_type": "{{ target_index_space_type }}", - "parameters":{ - "nlist": {{ ivf_nlists }}, - "nprobes": {{ ivf_nprobes }}, - "encoder":{ - "name":"pq", - "parameters":{ - "code_size": {{ pq_code_size }}, - "m": {{ pq_m }} - } - } - } - } - } - }, - { - "name": "ivf-train-model", - "operation-type": "train-model", - "model_id": "{{ train_model_id }}", - "timeout": {{ train_timeout | default(1000) }}, - "body": { - "training_index": "{{ train_index_name }}", - "training_field": "{{ train_field_name }}", - "search_size": {{ train_search_size }}, - "dimension": {{ target_index_dimension }}, - "max_training_vector_count": {{ train_index_num_vectors }}, - "method": { - "name":"ivf", - "engine":"faiss", - "space_type": "{{ target_index_space_type }}", - "parameters":{ - "nlist": {{ ivf_nlists }}, - "nprobes": {{ ivf_nprobes }} - } - } - } - } -] diff --git a/benchmarks/osb/params/no-train-params.json b/benchmarks/osb/params/no-train-params.json deleted file mode 100644 index 58e4197fd..000000000 --- a/benchmarks/osb/params/no-train-params.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "target_index_name": "target_index", - "target_field_name": "target_field", - "target_index_body": "indices/nmslib-index.json", - "target_index_primary_shards": 3, - "target_index_replica_shards": 1, - "target_index_dimension": 128, - "target_index_space_type": "l2", - "target_index_bulk_size": 200, - "target_index_bulk_index_data_set_format": "hdf5", - "target_index_bulk_index_data_set_path": "", - "target_index_bulk_index_clients": 10, - "target_index_max_num_segments": 10, - "target_index_force_merge_timeout": 45.0, - "hnsw_ef_search": 512, - "hnsw_ef_construction": 512, - "hnsw_m": 16, - - "query_k": 10, - "query_clients": 10, - "query_data_set_format": "hdf5", - "query_data_set_path": "", - - "ivf_nlists": 1, - "ivf_nprobes": 1, - "pq_code_size": 1, - "pq_m": 1, - "train_model_method": "", - "train_model_id": "", - "train_index_name": "", - "train_field_name": "", - "train_index_body": "", - "train_search_size": 1, - "train_timeout": 1, - "train_index_bulk_size": 1, - "train_index_data_set_format": "", - "train_index_data_set_path": "", - "train_index_num_vectors": 1, - "train_index_bulk_index_clients": 1 -} diff --git a/benchmarks/osb/params/train-params.json b/benchmarks/osb/params/train-params.json deleted file mode 100644 index f55ed4333..000000000 --- a/benchmarks/osb/params/train-params.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "target_index_name": "target_index", - "target_field_name": "target_field", - "target_index_body": "indices/model-index.json", - "target_index_primary_shards": 3, - "target_index_replica_shards": 1, - "target_index_dimension": 128, - "target_index_space_type": "l2", - "target_index_bulk_size": 200, - "target_index_bulk_index_data_set_format": "hdf5", - "target_index_bulk_index_data_set_path": "", - "target_index_bulk_index_clients": 10, - "target_index_max_num_segments": 10, - "target_index_force_merge_timeout": 45.0, - "ivf_nlists": 10, - "ivf_nprobes": 1, - "pq_code_size": 8, - "pq_m": 8, - "train_model_method": "ivfpq", - "train_model_id": "test-model", - "train_index_name": "train_index", - "train_field_name": "train_field", - "train_index_body": "indices/train-index.json", - "train_search_size": 500, - "train_timeout": 5000, - "train_index_primary_shards": 1, - "train_index_replica_shards": 0, - "train_index_bulk_size": 200, - "train_index_data_set_format": "hdf5", - "train_index_data_set_path": "", - "train_index_num_vectors": 1000000, - "train_index_bulk_index_clients": 10, - - "query_k": 10, - "query_clients": 10, - "query_data_set_format": "hdf5", - "query_data_set_path": "" -} diff --git a/benchmarks/osb/procedures/no-train-test.json b/benchmarks/osb/procedures/no-train-test.json deleted file mode 100644 index 01985b914..000000000 --- a/benchmarks/osb/procedures/no-train-test.json +++ /dev/null @@ -1,73 +0,0 @@ -{% import "benchmark.helpers" as benchmark with context %} -{ - "name": "no-train-test", - "default": true, - "schedule": [ - { - "operation": { - "name": "delete-target-index", - "operation-type": "delete-index", - "only-if-exists": true, - "index": "{{ target_index_name }}" - } - }, - { - "operation": { - "name": "create-target-index", - "operation-type": "create-index", - "index": "{{ target_index_name }}" - } - }, - { - "name": "wait-for-cluster-to-be-green", - "operation": "cluster-health", - "request-params": { - "wait_for_status": "green" - } - }, - { - "operation": { - "name": "custom-vector-bulk", - "operation-type": "custom-vector-bulk", - "param-source": "bulk-from-data-set", - "index": "{{ target_index_name }}", - "field": "{{ target_field_name }}", - "bulk_size": {{ target_index_bulk_size }}, - "data_set_format": "{{ target_index_bulk_index_data_set_format }}", - "data_set_path": "{{ target_index_bulk_index_data_set_path }}" - }, - "clients": {{ target_index_bulk_index_clients }} - }, - { - "operation": { - "name": "refresh-target-index", - "operation-type": "custom-refresh", - "index": "{{ target_index_name }}", - "retries": 100 - } - }, - { - "operation": { - "name": "force-merge", - "operation-type": "force-merge", - "request-timeout": {{ target_index_force_merge_timeout }}, - "index": "{{ target_index_name }}", - "mode": "polling", - "max-num-segments": {{ target_index_max_num_segments }} - } - }, - { - "operation": { - "name": "knn-query-from-data-set", - "operation-type": "search", - "index": "{{ target_index_name }}", - "param-source": "knn-query-from-data-set", - "k": {{ query_k }}, - "field": "{{ target_field_name }}", - "data_set_format": "{{ query_data_set_format }}", - "data_set_path": "{{ query_data_set_path }}" - }, - "clients": {{ query_clients }} - } - ] -} diff --git a/benchmarks/osb/procedures/train-test.json b/benchmarks/osb/procedures/train-test.json deleted file mode 100644 index ca26db0b0..000000000 --- a/benchmarks/osb/procedures/train-test.json +++ /dev/null @@ -1,127 +0,0 @@ -{% import "benchmark.helpers" as benchmark with context %} -{ - "name": "train-test", - "default": false, - "schedule": [ - { - "operation": { - "name": "delete-target-index", - "operation-type": "delete-index", - "only-if-exists": true, - "index": "{{ target_index_name }}" - } - }, - { - "operation": { - "name": "delete-train-index", - "operation-type": "delete-index", - "only-if-exists": true, - "index": "{{ train_index_name }}" - } - }, - { - "operation": { - "operation-type": "delete-model", - "name": "delete-model", - "model_id": "{{ train_model_id }}" - } - }, - { - "operation": { - "name": "create-train-index", - "operation-type": "create-index", - "index": "{{ train_index_name }}" - } - }, - { - "name": "wait-for-train-index-to-be-green", - "operation": "cluster-health", - "request-params": { - "wait_for_status": "green" - } - }, - { - "operation": { - "name": "train-vector-bulk", - "operation-type": "custom-vector-bulk", - "param-source": "bulk-from-data-set", - "index": "{{ train_index_name }}", - "field": "{{ train_field_name }}", - "bulk_size": {{ train_index_bulk_size }}, - "data_set_format": "{{ train_index_data_set_format }}", - "data_set_path": "{{ train_index_data_set_path }}", - "num_vectors": {{ train_index_num_vectors }} - }, - "clients": {{ train_index_bulk_index_clients }} - }, - { - "operation": { - "name": "refresh-train-index", - "operation-type": "custom-refresh", - "index": "{{ train_index_name }}", - "retries": 100 - } - }, - { - "operation": "{{ train_model_method }}-train-model" - }, - { - "operation": { - "name": "create-target-index", - "operation-type": "create-index", - "index": "{{ target_index_name }}" - } - }, - { - "name": "wait-for-target-index-to-be-green", - "operation": "cluster-health", - "request-params": { - "wait_for_status": "green" - } - }, - { - "operation": { - "name": "custom-vector-bulk", - "operation-type": "custom-vector-bulk", - "param-source": "bulk-from-data-set", - "index": "{{ target_index_name }}", - "field": "{{ target_field_name }}", - "bulk_size": {{ target_index_bulk_size }}, - "data_set_format": "{{ target_index_bulk_index_data_set_format }}", - "data_set_path": "{{ target_index_bulk_index_data_set_path }}" - }, - "clients": {{ target_index_bulk_index_clients }} - }, - { - "operation": { - "name": "refresh-target-index", - "operation-type": "custom-refresh", - "index": "{{ target_index_name }}", - "retries": 100 - } - }, - { - "operation": { - "name": "force-merge", - "operation-type": "force-merge", - "request-timeout": {{ target_index_force_merge_timeout }}, - "index": "{{ target_index_name }}", - "mode": "polling", - "max-num-segments": {{ target_index_max_num_segments }} - } - }, - { - "operation": { - "name": "knn-query-from-data-set", - "operation-type": "search", - "index": "{{ target_index_name }}", - "param-source": "knn-query-from-data-set", - "k": {{ query_k }}, - "field": "{{ target_field_name }}", - "data_set_format": "{{ query_data_set_format }}", - "data_set_path": "{{ query_data_set_path }}" - }, - "clients": {{ query_clients }} - } - ] -} diff --git a/benchmarks/osb/requirements.in b/benchmarks/osb/requirements.in deleted file mode 100644 index a9e12b5d3..000000000 --- a/benchmarks/osb/requirements.in +++ /dev/null @@ -1,4 +0,0 @@ -opensearch-py -numpy -h5py -opensearch-benchmark diff --git a/benchmarks/osb/requirements.txt b/benchmarks/osb/requirements.txt deleted file mode 100644 index a220ee44f..000000000 --- a/benchmarks/osb/requirements.txt +++ /dev/null @@ -1,96 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: -# -# pip-compile -# -aiohttp==3.9.4 - # via opensearch-py -aiosignal==1.2.0 - # via aiohttp -async-timeout==4.0.2 - # via aiohttp -attrs==21.4.0 - # via - # aiohttp - # jsonschema -cachetools==4.2.4 - # via google-auth -certifi==2023.7.22 - # via - # opensearch-benchmark - # opensearch-py -frozenlist==1.3.0 - # via - # aiohttp - # aiosignal -google-auth==1.22.1 - # via opensearch-benchmark -google-crc32c==1.3.0 - # via google-resumable-media -google-resumable-media==1.1.0 - # via opensearch-benchmark -h5py==3.6.0 - # via -r requirements.in -idna==3.7 - # via yarl -ijson==2.6.1 - # via opensearch-benchmark -importlib-metadata==4.11.3 - # via jsonschema -jinja2==3.1.3 - # via opensearch-benchmark -jsonschema==3.1.1 - # via opensearch-benchmark -markupsafe==2.0.1 - # via - # jinja2 - # opensearch-benchmark -multidict==6.0.2 - # via - # aiohttp - # yarl -numpy==1.24.2 - # via - # -r requirements.in - # h5py -opensearch-benchmark==0.0.2 - # via -r requirements.in -opensearch-py[async]==1.0.0 - # via - # -r requirements.in - # opensearch-benchmark -psutil==5.8.0 - # via opensearch-benchmark -py-cpuinfo==7.0.0 - # via opensearch-benchmark -pyasn1==0.4.8 - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.2.8 - # via google-auth -pyrsistent==0.18.1 - # via jsonschema -rsa==4.8 - # via google-auth -six==1.16.0 - # via - # google-auth - # google-resumable-media - # jsonschema -tabulate==0.8.7 - # via opensearch-benchmark -thespian==3.10.1 - # via opensearch-benchmark -urllib3==1.26.18 - # via opensearch-py -yappi==1.2.3 - # via opensearch-benchmark -yarl==1.7.2 - # via aiohttp -zipp==3.7.0 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/benchmarks/osb/tests/__init__.py b/benchmarks/osb/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/benchmarks/osb/tests/data_set_helper.py b/benchmarks/osb/tests/data_set_helper.py deleted file mode 100644 index 2b144da49..000000000 --- a/benchmarks/osb/tests/data_set_helper.py +++ /dev/null @@ -1,197 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -from abc import ABC, abstractmethod - -import h5py -import numpy as np - -from osb.extensions.data_set import Context, HDF5DataSet, BigANNVectorDataSet - -""" Module containing utility classes and functions for working with data sets. - -Included are utilities that can be used to build data sets and write them to -paths. -""" - - -class DataSetBuildContext: - """ Data class capturing information needed to build a particular data set - - Attributes: - data_set_context: Indicator of what the data set is used for, - vectors: A 2D array containing vectors that are used to build data set. - path: string representing path where data set should be serialized to. - """ - def __init__(self, data_set_context: Context, vectors: np.ndarray, path: str): - self.data_set_context: Context = data_set_context - self.vectors: np.ndarray = vectors #TODO: Validate shape - self.path: str = path - - def get_num_vectors(self) -> int: - return self.vectors.shape[0] - - def get_dimension(self) -> int: - return self.vectors.shape[1] - - def get_type(self) -> np.dtype: - return self.vectors.dtype - - -class DataSetBuilder(ABC): - """ Abstract builder used to create a build a collection of data sets - - Attributes: - data_set_build_contexts: list of data set build contexts that builder - will build. - """ - def __init__(self): - self.data_set_build_contexts = list() - - def add_data_set_build_context(self, data_set_build_context: DataSetBuildContext): - """ Adds a data set build context to list of contexts to be built. - - Args: - data_set_build_context: DataSetBuildContext to be added to list - - Returns: Updated DataSetBuilder - - """ - self._validate_data_set_context(data_set_build_context) - self.data_set_build_contexts.append(data_set_build_context) - return self - - def build(self): - """ Builds and serializes all data sets build contexts - - Returns: - - """ - [self._build_data_set(data_set_build_context) for data_set_build_context - in self.data_set_build_contexts] - - @abstractmethod - def _build_data_set(self, context: DataSetBuildContext): - """ Builds an individual data set - - Args: - context: DataSetBuildContext of data set to be built - - Returns: - - """ - pass - - @abstractmethod - def _validate_data_set_context(self, context: DataSetBuildContext): - """ Validates that data set context can be added to this builder - - Args: - context: DataSetBuildContext to be validated - - Returns: - - """ - pass - - -class HDF5Builder(DataSetBuilder): - - def __init__(self): - super(HDF5Builder, self).__init__() - self.data_set_meta_data = dict() - - def _validate_data_set_context(self, context: DataSetBuildContext): - if context.path not in self.data_set_meta_data.keys(): - self.data_set_meta_data[context.path] = { - context.data_set_context: context - } - return - - if context.data_set_context in \ - self.data_set_meta_data[context.path].keys(): - raise IllegalDataSetBuildContext("Path and context for data set " - "are already present in builder.") - - self.data_set_meta_data[context.path][context.data_set_context] = \ - context - - @staticmethod - def _validate_extension(context: DataSetBuildContext): - ext = context.path.split('.')[-1] - - if ext != HDF5DataSet.FORMAT_NAME: - raise IllegalDataSetBuildContext("Invalid file extension") - - def _build_data_set(self, context: DataSetBuildContext): - # For HDF5, because multiple data sets can be grouped in the same file, - # we will build data sets in memory and not write to disk until - # _flush_data_sets_to_disk is called - with h5py.File(context.path, 'a') as hf: - hf.create_dataset( - HDF5DataSet.parse_context(context.data_set_context), - data=context.vectors - ) - - -class BigANNBuilder(DataSetBuilder): - - def _validate_data_set_context(self, context: DataSetBuildContext): - self._validate_extension(context) - - # prevent the duplication of paths for data sets - data_set_paths = [c.path for c in self.data_set_build_contexts] - if any(data_set_paths.count(x) > 1 for x in data_set_paths): - raise IllegalDataSetBuildContext("Build context paths have to be " - "unique.") - - @staticmethod - def _validate_extension(context: DataSetBuildContext): - ext = context.path.split('.')[-1] - - if ext != BigANNVectorDataSet.U8BIN_EXTENSION and ext != \ - BigANNVectorDataSet.FBIN_EXTENSION: - raise IllegalDataSetBuildContext("Invalid file extension") - - if ext == BigANNVectorDataSet.U8BIN_EXTENSION and context.get_type() != \ - np.u8int: - raise IllegalDataSetBuildContext("Invalid data type for {} ext." - .format(BigANNVectorDataSet - .U8BIN_EXTENSION)) - - if ext == BigANNVectorDataSet.FBIN_EXTENSION and context.get_type() != \ - np.float32: - print(context.get_type()) - raise IllegalDataSetBuildContext("Invalid data type for {} ext." - .format(BigANNVectorDataSet - .FBIN_EXTENSION)) - - def _build_data_set(self, context: DataSetBuildContext): - num_vectors = context.get_num_vectors() - dimension = context.get_dimension() - - with open(context.path, 'wb') as f: - f.write(int.to_bytes(num_vectors, 4, "little")) - f.write(int.to_bytes(dimension, 4, "little")) - context.vectors.tofile(f) - - -def create_random_2d_array(num_vectors: int, dimension: int) -> np.ndarray: - rng = np.random.default_rng() - return rng.random(size=(num_vectors, dimension), dtype=np.float32) - - -class IllegalDataSetBuildContext(Exception): - """Exception raised when passed in DataSetBuildContext is illegal - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message: str): - self.message = f'{message}' - super().__init__(self.message) - diff --git a/benchmarks/osb/tests/test_param_sources.py b/benchmarks/osb/tests/test_param_sources.py deleted file mode 100644 index cda730cee..000000000 --- a/benchmarks/osb/tests/test_param_sources.py +++ /dev/null @@ -1,353 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -import os -import random -import shutil -import string -import sys -import tempfile -import unittest - -# Add parent directory to path -import numpy as np - -sys.path.append(os.path.abspath(os.path.join(os.getcwd(), os.pardir))) - -from osb.tests.data_set_helper import HDF5Builder, create_random_2d_array, \ - DataSetBuildContext, BigANNBuilder -from osb.extensions.data_set import Context, HDF5DataSet -from osb.extensions.param_sources import VectorsFromDataSetParamSource, \ - QueryVectorsFromDataSetParamSource, BulkVectorsFromDataSetParamSource -from osb.extensions.util import ConfigurationError - -DEFAULT_INDEX_NAME = "test-index" -DEFAULT_FIELD_NAME = "test-field" -DEFAULT_CONTEXT = Context.INDEX -DEFAULT_TYPE = HDF5DataSet.FORMAT_NAME -DEFAULT_NUM_VECTORS = 10 -DEFAULT_DIMENSION = 10 -DEFAULT_RANDOM_STRING_LENGTH = 8 - - -class VectorsFromDataSetParamSourceTestCase(unittest.TestCase): - - def setUp(self) -> None: - self.data_set_dir = tempfile.mkdtemp() - - # Create a data set we know to be valid for convenience - self.valid_data_set_path = _create_data_set( - DEFAULT_NUM_VECTORS, - DEFAULT_DIMENSION, - DEFAULT_TYPE, - DEFAULT_CONTEXT, - self.data_set_dir - ) - - def tearDown(self): - shutil.rmtree(self.data_set_dir) - - def test_missing_params(self): - empty_params = dict() - self.assertRaises( - ConfigurationError, - lambda: VectorsFromDataSetParamSourceTestCase. - TestVectorsFromDataSetParamSource(empty_params, DEFAULT_CONTEXT) - ) - - def test_invalid_data_set_format(self): - invalid_data_set_format = "invalid-data-set-format" - - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": invalid_data_set_format, - "data_set_path": self.valid_data_set_path, - } - self.assertRaises( - ConfigurationError, - lambda: self.TestVectorsFromDataSetParamSource( - test_param_source_params, - DEFAULT_CONTEXT - ) - ) - - def test_invalid_data_set_path(self): - invalid_data_set_path = "invalid-data-set-path" - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": HDF5DataSet.FORMAT_NAME, - "data_set_path": invalid_data_set_path, - } - self.assertRaises( - FileNotFoundError, - lambda: self.TestVectorsFromDataSetParamSource( - test_param_source_params, - DEFAULT_CONTEXT - ) - ) - - def test_partition_hdf5(self): - num_vectors = 100 - - hdf5_data_set_path = _create_data_set( - num_vectors, - DEFAULT_DIMENSION, - HDF5DataSet.FORMAT_NAME, - DEFAULT_CONTEXT, - self.data_set_dir - ) - - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": HDF5DataSet.FORMAT_NAME, - "data_set_path": hdf5_data_set_path, - } - test_param_source = self.TestVectorsFromDataSetParamSource( - test_param_source_params, - DEFAULT_CONTEXT - ) - - num_partitions = 10 - vecs_per_partition = test_param_source.num_vectors // num_partitions - - self._test_partition( - test_param_source, - num_partitions, - vecs_per_partition - ) - - def test_partition_bigann(self): - num_vectors = 100 - float_extension = "fbin" - - bigann_data_set_path = _create_data_set( - num_vectors, - DEFAULT_DIMENSION, - float_extension, - DEFAULT_CONTEXT, - self.data_set_dir - ) - - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": "bigann", - "data_set_path": bigann_data_set_path, - } - test_param_source = self.TestVectorsFromDataSetParamSource( - test_param_source_params, - DEFAULT_CONTEXT - ) - - num_partitions = 10 - vecs_per_partition = test_param_source.num_vectors // num_partitions - - self._test_partition( - test_param_source, - num_partitions, - vecs_per_partition - ) - - def _test_partition( - self, - test_param_source: VectorsFromDataSetParamSource, - num_partitions: int, - vec_per_partition: int - ): - for i in range(num_partitions): - test_param_source_i = test_param_source.partition(i, num_partitions) - self.assertEqual(test_param_source_i.num_vectors, vec_per_partition) - self.assertEqual(test_param_source_i.offset, i * vec_per_partition) - - class TestVectorsFromDataSetParamSource(VectorsFromDataSetParamSource): - """ - Empty implementation of ABC VectorsFromDataSetParamSource so that we can - test the concrete methods. - """ - - def params(self): - pass - - -class QueryVectorsFromDataSetParamSourceTestCase(unittest.TestCase): - - def setUp(self) -> None: - self.data_set_dir = tempfile.mkdtemp() - - def tearDown(self): - shutil.rmtree(self.data_set_dir) - - def test_params(self): - # Create a data set - k = 12 - data_set_path = _create_data_set( - DEFAULT_NUM_VECTORS, - DEFAULT_DIMENSION, - DEFAULT_TYPE, - Context.QUERY, - self.data_set_dir - ) - - # Create a QueryVectorsFromDataSetParamSource with relevant params - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": DEFAULT_TYPE, - "data_set_path": data_set_path, - "k": k, - } - query_param_source = QueryVectorsFromDataSetParamSource( - None, test_param_source_params - ) - - # Check each - for i in range(DEFAULT_NUM_VECTORS): - self._check_params( - query_param_source.params(), - DEFAULT_INDEX_NAME, - DEFAULT_FIELD_NAME, - DEFAULT_DIMENSION, - k - ) - - # Assert last call creates stop iteration - self.assertRaises( - StopIteration, - lambda: query_param_source.params() - ) - - def _check_params( - self, - params: dict, - expected_index: str, - expected_field: str, - expected_dimension: int, - expected_k: int - ): - index_name = params.get("index") - self.assertEqual(expected_index, index_name) - body = params.get("body") - self.assertIsInstance(body, dict) - query = body.get("query") - self.assertIsInstance(query, dict) - query_knn = query.get("knn") - self.assertIsInstance(query_knn, dict) - field = query_knn.get(expected_field) - self.assertIsInstance(field, dict) - vector = field.get("vector") - self.assertIsInstance(vector, np.ndarray) - self.assertEqual(len(list(vector)), expected_dimension) - k = field.get("k") - self.assertEqual(k, expected_k) - - -class BulkVectorsFromDataSetParamSourceTestCase(unittest.TestCase): - - def setUp(self) -> None: - self.data_set_dir = tempfile.mkdtemp() - - def tearDown(self): - shutil.rmtree(self.data_set_dir) - - def test_params(self): - num_vectors = 49 - bulk_size = 10 - data_set_path = _create_data_set( - num_vectors, - DEFAULT_DIMENSION, - DEFAULT_TYPE, - Context.INDEX, - self.data_set_dir - ) - - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": DEFAULT_TYPE, - "data_set_path": data_set_path, - "bulk_size": bulk_size - } - bulk_param_source = BulkVectorsFromDataSetParamSource( - None, test_param_source_params - ) - - # Check each payload returned - vectors_consumed = 0 - while vectors_consumed < num_vectors: - expected_num_vectors = min(num_vectors - vectors_consumed, bulk_size) - self._check_params( - bulk_param_source.params(), - DEFAULT_INDEX_NAME, - DEFAULT_FIELD_NAME, - DEFAULT_DIMENSION, - expected_num_vectors - ) - vectors_consumed += expected_num_vectors - - # Assert last call creates stop iteration - self.assertRaises( - StopIteration, - lambda: bulk_param_source.params() - ) - - def _check_params( - self, - params: dict, - expected_index: str, - expected_field: str, - expected_dimension: int, - expected_num_vectors_in_payload: int - ): - size = params.get("size") - self.assertEqual(size, expected_num_vectors_in_payload) - body = params.get("body") - self.assertIsInstance(body, list) - self.assertEqual(len(body) // 2, expected_num_vectors_in_payload) - - # Bulk payload has 2 parts: first one is the header and the second one - # is the body. The header will have the index name and the body will - # have the vector - for header, req_body in zip(*[iter(body)] * 2): - index = header.get("index") - self.assertIsInstance(index, dict) - index_name = index.get("_index") - self.assertEqual(index_name, expected_index) - - vector = req_body.get(expected_field) - self.assertIsInstance(vector, list) - self.assertEqual(len(vector), expected_dimension) - - -def _create_data_set( - num_vectors: int, - dimension: int, - extension: str, - data_set_context: Context, - data_set_dir -) -> str: - - file_name_base = ''.join(random.choice(string.ascii_letters) for _ in - range(DEFAULT_RANDOM_STRING_LENGTH)) - data_set_file_name = "{}.{}".format(file_name_base, extension) - data_set_path = os.path.join(data_set_dir, data_set_file_name) - context = DataSetBuildContext( - data_set_context, - create_random_2d_array(num_vectors, dimension), - data_set_path) - - if extension == HDF5DataSet.FORMAT_NAME: - HDF5Builder().add_data_set_build_context(context).build() - else: - BigANNBuilder().add_data_set_build_context(context).build() - - return data_set_path - - -if __name__ == '__main__': - unittest.main() diff --git a/benchmarks/osb/workload.json b/benchmarks/osb/workload.json deleted file mode 100644 index bd0d84195..000000000 --- a/benchmarks/osb/workload.json +++ /dev/null @@ -1,17 +0,0 @@ -{% import "benchmark.helpers" as benchmark with context %} -{ - "version": 2, - "description": "k-NN Plugin train workload", - "indices": [ - { - "name": "{{ target_index_name }}", - "body": "{{ target_index_body }}" - }, - { - "name": "{{ train_index_name }}", - "body": "{{ train_index_body }}" - } - ], - "operations": {{ benchmark.collect(parts="operations/*.json") }}, - "test_procedures": [{{ benchmark.collect(parts="procedures/*.json") }}] -} diff --git a/benchmarks/osb/workload.py b/benchmarks/osb/workload.py deleted file mode 100644 index 32e6ad02c..000000000 --- a/benchmarks/osb/workload.py +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -# This code needs to be included at the top of every workload.py file. -# OpenSearch Benchmarks is not able to find other helper files unless the path -# is updated. -import os -import sys -sys.path.append(os.path.abspath(os.getcwd())) - -from extensions.registry import register as custom_register - - -def register(registry): - custom_register(registry) diff --git a/benchmarks/perf-tool/.pylintrc b/benchmarks/perf-tool/.pylintrc deleted file mode 100644 index 15bf4ccc3..000000000 --- a/benchmarks/perf-tool/.pylintrc +++ /dev/null @@ -1,443 +0,0 @@ -# This Pylint rcfile contains a best-effort configuration to uphold the -# best-practices and style described in the Google Python style guide: -# https://google.github.io/styleguide/pyguide.html -# -# Its canonical open-source location is: -# https://google.github.io/styleguide/pylintrc - -[MASTER] - -fail-under=9.0 - -# Files or directories to be skipped. They should be base names, not paths. -ignore=third_party - -# Files or directories matching the regex patterns are skipped. The regex -# matches against base names, not paths. -ignore-patterns= - -# Pickle collected data for later comparisons. -persistent=no - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=4 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=abstract-method, - apply-builtin, - arguments-differ, - attribute-defined-outside-init, - backtick, - bad-option-value, - basestring-builtin, - buffer-builtin, - c-extension-no-member, - consider-using-enumerate, - cmp-builtin, - cmp-method, - coerce-builtin, - coerce-method, - delslice-method, - div-method, - duplicate-code, - eq-without-hash, - execfile-builtin, - file-builtin, - filter-builtin-not-iterating, - fixme, - getslice-method, - global-statement, - hex-method, - idiv-method, - implicit-str-concat-in-sequence, - import-error, - import-self, - import-star-module-level, - inconsistent-return-statements, - input-builtin, - intern-builtin, - invalid-str-codec, - locally-disabled, - long-builtin, - long-suffix, - map-builtin-not-iterating, - misplaced-comparison-constant, - missing-function-docstring, - metaclass-assignment, - next-method-called, - next-method-defined, - no-absolute-import, - no-else-break, - no-else-continue, - no-else-raise, - no-else-return, - no-init, # added - no-member, - no-name-in-module, - no-self-use, - nonzero-method, - oct-method, - old-division, - old-ne-operator, - old-octal-literal, - old-raise-syntax, - parameter-unpacking, - print-statement, - raising-string, - range-builtin-not-iterating, - raw_input-builtin, - rdiv-method, - reduce-builtin, - relative-import, - reload-builtin, - round-builtin, - setslice-method, - signature-differs, - standarderror-builtin, - suppressed-message, - sys-max-int, - too-few-public-methods, - too-many-ancestors, - too-many-arguments, - too-many-boolean-expressions, - too-many-branches, - too-many-instance-attributes, - too-many-locals, - too-many-nested-blocks, - too-many-public-methods, - too-many-return-statements, - too-many-statements, - trailing-newlines, - unichr-builtin, - unicode-builtin, - unnecessary-pass, - unpacking-in-except, - useless-else-on-loop, - useless-object-inheritance, - useless-suppression, - using-cmp-argument, - wrong-import-order, - xrange-builtin, - zip-builtin-not-iterating, - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". This option is deprecated -# and it will be removed in Pylint 2.0. -files-output=no - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[BASIC] - -# Good variable names which should always be accepted, separated by a comma -good-names=main,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl - -# Regular expression matching correct function names -function-rgx=^(?:(?PsetUp|tearDown|setUpModule|tearDownModule)|(?P_?[A-Z][a-zA-Z0-9]*)|(?P_?[a-z][a-z0-9_]*))$ - -# Regular expression matching correct variable names -variable-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct constant names -const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct attribute names -attr-rgx=^_{0,2}[a-z][a-z0-9_]*$ - -# Regular expression matching correct argument names -argument-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=^_?[A-Z][a-zA-Z0-9]*$ - -# Regular expression matching correct module names -module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$ - -# Regular expression matching correct method names -method-rgx=(?x)^(?:(?P_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P_{0,2}[a-z][a-z0-9_]*))$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=10 - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=80 - -# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt -# lines made too long by directives to pytype. - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=(?x)( - ^\s*(\#\ )??$| - ^\s*(from\s+\S+\s+)?import\s+.+$) - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=yes - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check= - -# Maximum number of lines in a module -max-module-lines=99999 - -# String used as indentation unit. The internal Google style guide mandates 2 -# spaces. Google's externaly-published style guide says 4, consistent with -# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google -# projects (like TensorFlow). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=TODO - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=yes - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_) - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging,absl.logging,tensorflow.io.logging - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub, - TERMIOS, - Bastion, - rexec, - sets - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant, absl - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls, - class_ - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=StandardError, - Exception, - BaseException diff --git a/benchmarks/perf-tool/.style.yapf b/benchmarks/perf-tool/.style.yapf deleted file mode 100644 index 39b663a7a..000000000 --- a/benchmarks/perf-tool/.style.yapf +++ /dev/null @@ -1,10 +0,0 @@ -[style] -COLUMN_LIMIT: 80 -DEDENT_CLOSING_BRACKETS: True -INDENT_DICTIONARY_VALUE: True -SPLIT_ALL_COMMA_SEPARATED_VALUES: True -SPLIT_ARGUMENTS_WHEN_COMMA_TERMINATED: True -SPLIT_BEFORE_CLOSING_BRACKET: True -SPLIT_BEFORE_EXPRESSION_AFTER_OPENING_PAREN: True -SPLIT_BEFORE_FIRST_ARGUMENT: True -SPLIT_BEFORE_NAMED_ASSIGNS: True diff --git a/benchmarks/perf-tool/README.md b/benchmarks/perf-tool/README.md deleted file mode 100644 index 36f76bcdb..000000000 --- a/benchmarks/perf-tool/README.md +++ /dev/null @@ -1,449 +0,0 @@ -# IMPORTANT NOTE: No new features will be added to this tool . This tool is currently in maintanence mode. All new features will be added to [vector search workload]( https://github.com/opensearch-project/opensearch-benchmark-workloads/tree/main/vectorsearch) - -# OpenSearch k-NN Benchmarking -- [Welcome!](#welcome) -- [Install Prerequisites](#install-prerequisites) -- [Usage](#usage) -- [Contributing](#contributing) - -## Welcome! - -This directory contains the code related to benchmarking the k-NN plugin. -Benchmarks can be run against any OpenSearch cluster with the k-NN plugin -installed. Benchmarks are highly configurable using the test configuration -file. - -## Install Prerequisites - -### Setup - -K-NN perf requires Python 3.8 or greater to be installed. One of -the easier ways to do this is through Conda, a package and environment -management system for Python. - -First, follow the -[installation instructions](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html) -to install Conda on your system. - -Next, create a Python 3.8 environment: -``` -conda create -n knn-perf python=3.8 -``` - -After the environment is created, activate it: -``` -source activate knn-perf -``` - -Lastly, clone the k-NN repo and install all required python packages: -``` -git clone https://github.com/opensearch-project/k-NN.git -cd k-NN/benchmarks/perf-tool -pip install -r requirements.txt -``` - -After all of this completes, you should be ready to run your first performance benchmarks! - - -## Usage - -### Quick Start - -In order to run a benchmark, you must first create a test configuration yml -file. Checkout [this example](https://github.com/opensearch-project/k-NN/blob/main/benchmarks/perf-tool/sample-configs) file -for benchmarking *faiss*'s IVF method. This file contains the definition for -the benchmark that you want to run. At the top are -[test parameters](#test-parameters). These define high level settings of the -test, such as the endpoint of the OpenSearch cluster. - -Next, you define the actions that the test will perform. These actions are -referred to as steps. First, you can define "setup" steps. These are steps that -are run once at the beginning of the execution to configure the cluster how you -want it. These steps do not contribute to the final metrics. - -After that, you define the "steps". These are the steps that the test will be -collecting metrics on. Each step emits certain metrics. These are run -multiple times, depending on the test parameter "num_runs". At the end of the -execution of all of the runs, the metrics from each run are collected and -averaged. - -Lastly, you define the "cleanup" steps. The "cleanup" steps are executed after -each test run. For instance, if you are measuring index performance, you may -want to delete the index after each run. - -To run the test, execute the following command: -``` -python knn-perf-tool.py [--log LOGLEVEL] test config-path.yml output.json - ---log log level of tool, options are: info, debug, warning, error, critical -``` - -The output will be a json document containing the results. - -Additionally, you can get the difference between two test runs using the diff -command: -``` -python knn-perf-tool.py [--log LOGLEVEL] diff result1.json result2.json - ---log log level of tool, options are: info, debug, warning, error, critical -``` - -The output will be the delta between the two metrics. - -### Test Parameters - -| Parameter Name | Description | Default | -|----------------|------------------------------------------------------------------------------------|------------| -| endpoint | Endpoint OpenSearch cluster is running on | localhost | -| port | Port on which OpenSearch Cluster is running on | 9200 | -| test_name | Name of test | No default | -| test_id | String ID of test | No default | -| num_runs | Number of runs to execute steps | 1 | -| show_runs | Whether to output each run in addition to the total summary | false | -| setup | List of steps to run once before metric collection starts | [] | -| steps | List of steps that make up one test run. Metrics will be collected on these steps. | No default | -| cleanup | List of steps to run after each test run | [] | - -### Steps - -Included are the list of steps that are currently supported. Each step contains -a set of parameters that are passed in the test configuration file and a set -of metrics that the test produces. - -#### create_index - -Creates an OpenSearch index. - -##### Parameters -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| index_name | Name of index to create | No default | -| index_spec | Path to index specification | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end. | ms | - -#### disable_refresh - -Disables refresh for all indices in the cluster. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end. | ms | - -#### refresh_index - -Refreshes an OpenSearch index. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| index_name | Name of index to refresh | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end. | ms | -| store_kb | Size of index after refresh completes | KB | - -#### force_merge - -Force merges an index to a specified number of segments. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| index_name | Name of index to force merge | No default | -| max_num_segments | Number of segments to force merge to | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end. | ms | - -#### train_model - -Trains a model. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| model_id | Model id to set | Test | -| train_index | Index to pull training data from | No default | -| train_field | Field to pull training data from | No default | -| dimension | Dimension of model | No default | -| description | Description of model | No default | -| max_training_vector_count | Number of training vectors to used | No default | -| method_spec | Path to method specification | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end | ms | - -#### delete_model - -Deletes a model from the cluster. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| model_id | Model id to delete | Test | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end | ms | - -#### delete_index - -Deletes an index from the cluster. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| index_name | Name of index to delete | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end | ms | - -#### ingest - -Ingests a dataset of vectors into the cluster. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| index_name | Name of index to ingest into | No default | -| field_name | Name of field to ingest into | No default | -| bulk_size | Documents per bulk request | 300 | -| dataset_format | Format the data-set is in. Currently hdf5 and bigann is supported. The hdf5 file must be organized in the same way that the ann-benchmarks organizes theirs. | 'hdf5' | -| dataset_path | Path to data-set | No default | -| doc_count | Number of documents to create from data-set | Size of the data-set | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Total time to ingest the dataset into the index.| ms | - -#### ingest_multi_field - -Ingests a dataset of multiple context types into the cluster. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------| ----------- | -| index_name | Name of index to ingest into | No default | -| field_name | Name of field to ingest into | No default | -| bulk_size | Documents per bulk request | 300 | -| dataset_path | Path to data-set | No default | -| doc_count | Number of documents to create from data-set | Size of the data-set | -| attributes_dataset_name | Name of dataset with additional attributes inside the main dataset | No default | -| attribute_spec | Definition of attributes, format is: [{ name: [name_val], type: [type_val]}] Order is important and must match order of attributes column in dataset file | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Total time to ingest the dataset into the index.| ms | - -#### ingest_nested_field - -Ingests a dataset with nested field into the cluster. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ----------- | -| index_name | Name of index to ingest into | No default | -| field_name | Name of field to ingest into | No default | -| dataset_path | Path to data-set | No default | -| attributes_dataset_name | Name of dataset with additional attributes inside the main dataset | No default | -| attribute_spec | Definition of attributes, format is: [{ name: [name_val], type: [type_val]}] Order is important and must match order of attributes column in dataset file. It should contains { name: 'parent_id', type: 'int'} | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Total time to ingest the dataset into the index.| ms | - -#### query - -Runs a set of queries against an index. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| k | Number of neighbors to return on search | 100 | -| r | r value in Recall@R | 1 | -| index_name | Name of index to search | No default | -| field_name | Name field to search | No default | -| calculate_recall | Whether to calculate recall values | False | -| dataset_format | Format the dataset is in. Currently hdf5 and bigann is supported. The hdf5 file must be organized in the same way that the ann-benchmarks organizes theirs. | 'hdf5' | -| dataset_path | Path to dataset | No default | -| neighbors_format | Format the neighbors dataset is in. Currently hdf5 and bigann is supported. The hdf5 file must be organized in the same way that the ann-benchmarks organizes theirs. | 'hdf5' | -| neighbors_path | Path to neighbors dataset | No default | -| query_count | Number of queries to create from data-set | Size of the data-set | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- |---------------------------------------------------------------------------------------------------------| ----------- | -| took | Took times returned per query aggregated as total, p50, p90, p99, p99.9 and p100 (when applicable) | ms | -| memory_kb | Native memory k-NN is using at the end of the query workload | KB | -| recall@R | ratio of top R results from the ground truth neighbors that are in the K results returned by the plugin | float 0.0-1.0 | -| recall@K | ratio of results returned that were ground truth nearest neighbors | float 0.0-1.0 | - -#### query_with_filter - -Runs a set of queries with filter against an index. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------| -| k | Number of neighbors to return on search | 100 | -| r | r value in Recall@R | 1 | -| index_name | Name of index to search | No default | -| field_name | Name field to search | No default | -| calculate_recall | Whether to calculate recall values | False | -| dataset_format | Format the dataset is in. Currently hdf5 and bigann is supported. The hdf5 file must be organized in the same way that the ann-benchmarks organizes theirs. | 'hdf5' | -| dataset_path | Path to dataset | No default | -| neighbors_format | Format the neighbors dataset is in. Currently hdf5 and bigann is supported. The hdf5 file must be organized in the same way that the ann-benchmarks organizes theirs. | 'hdf5' | -| neighbors_path | Path to neighbors dataset | No default | -| neighbors_dataset | Name of filter dataset inside the neighbors dataset | No default | -| filter_spec | Path to filter specification | No default | -| filter_type | Type of filter format, we do support following types:
FILTER inner filter format for approximate k-NN search
SCRIPT score scripting with exact k-NN search and pre-filtering
BOOL_POST_FILTER Bool query with post-filtering | SCRIPT | -| score_script_similarity | Similarity function that has been used to index dataset. Used for SCRIPT filter type and ignored for others | l2 | -| query_count | Number of queries to create from data-set | Size of the data-set | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Took times returned per query aggregated as total, p50, p90 and p99 (when applicable) | ms | -| memory_kb | Native memory k-NN is using at the end of the query workload | KB | -| recall@R | ratio of top R results from the ground truth neighbors that are in the K results returned by the plugin | float 0.0-1.0 | -| recall@K | ratio of results returned that were ground truth nearest neighbors | float 0.0-1.0 | - - -#### query_nested_field - -Runs a set of queries with nested field against an index. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------| -| k | Number of neighbors to return on search | 100 | -| r | r value in Recall@R | 1 | -| index_name | Name of index to search | No default | -| field_name | Name field to search | No default | -| calculate_recall | Whether to calculate recall values | False | -| dataset_format | Format the dataset is in. Currently hdf5 and bigann is supported. The hdf5 file must be organized in the same way that the ann-benchmarks organizes theirs. | 'hdf5' | -| dataset_path | Path to dataset | No default | -| neighbors_format | Format the neighbors dataset is in. Currently hdf5 and bigann is supported. The hdf5 file must be organized in the same way that the ann-benchmarks organizes theirs. | 'hdf5' | -| neighbors_path | Path to neighbors dataset | No default | -| neighbors_dataset | Name of filter dataset inside the neighbors dataset | No default | -| query_count | Number of queries to create from data-set | Size of the data-set | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Took times returned per query aggregated as total, p50, p90 and p99 (when applicable) | ms | -| memory_kb | Native memory k-NN is using at the end of the query workload | KB | -| recall@R | ratio of top R results from the ground truth neighbors that are in the K results returned by the plugin | float 0.0-1.0 | -| recall@K | ratio of results returned that were ground truth nearest neighbors | float 0.0-1.0 | - -#### get_stats - -Gets the index stats. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------| -| index_name | Name of index to search | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- |-------------------------------------------------|------------| -| num_of_committed_segments | Total number of commited segments in the index | integer >= 0 | -| num_of_search_segments | Total number of search segments in the index | integer >= 0 | - -### Data sets - -This benchmark tool uses pre-generated data sets to run indexing and query workload. For some benchmark types existing dataset need to be -extended. Filtering is an example of use case where such dataset extension is needed. - -It's possible to use script provided with this repo to generate dataset and run benchmark for filtering queries. -You need to have existing dataset with vector data. This dataset will be used to generate additional attribute data and set of ground truth neighbours document ids. - -To generate dataset with attributes based on vectors only dataset use following command pattern: - -```commandline -python add-filters-to-dataset.py True False -``` - -To generate neighbours dataset for different filters based on dataset with attributes use following command pattern: - -```commandline -python add-filters-to-dataset.py False True -``` - -After that new dataset(s) can be referred from testcase definition in `ingest_extended` and `query_with_filter` steps. - -To generate dataset with parent doc id based on vectors only dataset, use following command pattern: -```commandline -python add-parent-doc-id-to-dataset.py -``` -This will generate neighbours dataset as well. This new dataset(s) can be referred from testcase definition in `ingest_nested_field` and `query_nested_field` steps. - -## Contributing - -### Linting - -Use pylint to lint the code: -``` -pylint knn-perf-tool.py okpt/**/*.py okpt/**/**/*.py -``` - -### Formatting - -We use yapf and the google style to format our code. After installing yapf, you can format your code by running: - -``` -yapf --style google knn-perf-tool.py okpt/**/*.py okpt/**/**/*.py -``` - -### Updating requirements - -Add new requirements to "requirements.in" and run `pip-compile` diff --git a/benchmarks/perf-tool/add-filters-to-dataset.py b/benchmarks/perf-tool/add-filters-to-dataset.py deleted file mode 100644 index 0624f7323..000000000 --- a/benchmarks/perf-tool/add-filters-to-dataset.py +++ /dev/null @@ -1,200 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -""" -Script builds complex dataset with additional attributes from exiting dataset that has only vectors. -Additional attributes are predefined in the script: color, taste, age. Only HDF5 format of vector dataset is supported. - -Output dataset file will have additional dataset 'attributes' with multiple columns, each column corresponds to one attribute -from an attribute set, and value is generated at random, e.g.: - -0: green None 71 -1: green bitter 28 - -there is no explicit index reference in 'attributes' dataset, index of the row corresponds to a document id. -For instance, in example above two rows of fields mapped to documents with ids '0' and '1'. - -If 'generate_filters' flag is set script generates additional dataset of neighbours (ground truth) for each filter type. -Output is a new file with several datasets, each dataset corresponds to one filter. Datasets are named 'neighbour_filter_X' -where X is 1 based index of particular filter. -Each dataset has rows with array of integers, where integer corresponds to -a document id from original dataset with additional fields. Array ca have -1 values that are treated as null, this is because -subset of filtered documents is same of smaller than original set. - -For example, dataset file content may look like : - -neighbour_filter_1: [[ 2, 5, -1], - [ 3, 1, -1], - [ 2 5, 7]] -neighbour_filter_2: [[-1, -1, -1], - [ 5, 6, -1], - [ 4, 2, 1]] - -In this case we do have datasets for two filters, 3 query results for each. [2, 5, -1] indicates that for first query -if filter 1 is used most similar document is with id 2, next similar is 5, and the rest do not pass filter 1 criteria. - -Example of script usage: - - create new hdf5 file with attribute dataset - add-filters-to-dataset.py ~/dev/opensearch/k-NN/benchmarks/perf-tool/dataset/data.hdf5 ~/dev/opensearch/datasets/data-with-attr True False - - create new hdf5 file with filter datasets - add-filters-to-dataset.py ~/dev/opensearch/k-NN/benchmarks/perf-tool/dataset/data-with-attr.hdf5 ~/dev/opensearch/datasets/data-with-filters False True -""" - -import getopt -import os -import random -import sys - -import h5py - -from osb.extensions.data_set import HDF5DataSet - - -class _Dataset: - """Type of dataset container for data with additional attributes""" - DEFAULT_TYPE = HDF5DataSet.FORMAT_NAME - - def create_dataset(self, source_dataset_path, out_file_path, generate_attrs: bool, generate_filters: bool) -> None: - path_elements = os.path.split(os.path.abspath(source_dataset_path)) - data_set_dir = path_elements[0] - - # For HDF5, because multiple data sets can be grouped in the same file, - # we will build data sets in memory and not write to disk until - # _flush_data_sets_to_disk is called - # read existing dataset - data_hdf5 = os.path.join(os.path.dirname(os.path.realpath('/')), source_dataset_path) - - with h5py.File(data_hdf5, "r") as hf: - - if generate_attrs: - data_set_w_attr = self.create_dataset_file(out_file_path, self.DEFAULT_TYPE, data_set_dir) - - possible_colors = ['red', 'green', 'yellow', 'blue', None] - possible_tastes = ['sweet', 'salty', 'sour', 'bitter', None] - max_age = 100 - - for key in hf.keys(): - if key not in ['neighbors', 'test', 'train']: - continue - data_set_w_attr.create_dataset(key, data=hf[key][()]) - - attributes = [] - for i in range(len(hf['train'])): - attr = [random.choice(possible_colors), random.choice(possible_tastes), - random.randint(0, max_age + 1)] - attributes.append(attr) - - data_set_w_attr.create_dataset('attributes', (len(attributes), 3), 'S10', data=attributes) - - data_set_w_attr.flush() - data_set_w_attr.close() - - if generate_filters: - attributes = hf['attributes'][()] - expected_neighbors = hf['neighbors'][()] - - data_set_filters = self.create_dataset_file(out_file_path, self.DEFAULT_TYPE, data_set_dir) - - def filter1(attributes, vector_idx): - if attributes[vector_idx][0].decode() == 'red' and int(attributes[vector_idx][2].decode()) >= 20: - return True - else: - return False - - self.apply_filter(expected_neighbors, attributes, data_set_filters, 'neighbors_filter_1', filter1) - - # filter 2 - color = blue or None and taste = 'salty' - def filter2(attributes, vector_idx): - if (attributes[vector_idx][0].decode() == 'blue' or attributes[vector_idx][ - 0].decode() == 'None') and attributes[vector_idx][1].decode() == 'salty': - return True - else: - return False - - self.apply_filter(expected_neighbors, attributes, data_set_filters, 'neighbors_filter_2', filter2) - - # filter 3 - color and taste are not None and age is between 20 and 80 - def filter3(attributes, vector_idx): - if attributes[vector_idx][0].decode() != 'None' and attributes[vector_idx][ - 1].decode() != 'None' and 20 <= \ - int(attributes[vector_idx][2].decode()) <= 80: - return True - else: - return False - - self.apply_filter(expected_neighbors, attributes, data_set_filters, 'neighbors_filter_3', filter3) - - # filter 4 - color green or blue and taste is bitter and age is between (30, 60) - def filter4(attributes, vector_idx): - if (attributes[vector_idx][0].decode() == 'green' or attributes[vector_idx][0].decode() == 'blue') \ - and (attributes[vector_idx][1].decode() == 'bitter') \ - and 30 <= int(attributes[vector_idx][2].decode()) <= 60: - return True - else: - return False - - self.apply_filter(expected_neighbors, attributes, data_set_filters, 'neighbors_filter_4', filter4) - - # filter 5 color is (green or blue or yellow) or taste = sweet or age is between (30, 70) - def filter5(attributes, vector_idx): - if attributes[vector_idx][0].decode() == 'green' or attributes[vector_idx][0].decode() == 'blue' \ - or attributes[vector_idx][0].decode() == 'yellow' \ - or attributes[vector_idx][1].decode() == 'sweet' \ - or 30 <= int(attributes[vector_idx][2].decode()) <= 70: - return True - else: - return False - - self.apply_filter(expected_neighbors, attributes, data_set_filters, 'neighbors_filter_5', filter5) - - data_set_filters.flush() - data_set_filters.close() - - def apply_filter(self, expected_neighbors, attributes, data_set_w_filtering, filter_name, filter_func): - neighbors_filter = [] - filtered_count = 0 - for expected_neighbors_row in expected_neighbors: - neighbors_filter_row = [-1] * len(expected_neighbors_row) - idx = 0 - for vector_idx in expected_neighbors_row: - if filter_func(attributes, vector_idx): - neighbors_filter_row[idx] = vector_idx - idx += 1 - filtered_count += 1 - neighbors_filter.append(neighbors_filter_row) - overall_count = len(expected_neighbors) * len(expected_neighbors[0]) - perc = float(filtered_count / overall_count) * 100 - print('ground truth size for {} is {}, percentage {}'.format(filter_name, filtered_count, perc)) - data_set_w_filtering.create_dataset(filter_name, data=neighbors_filter) - return expected_neighbors - - def create_dataset_file(self, file_name, extension, data_set_dir) -> h5py.File: - data_set_file_name = "{}.{}".format(file_name, extension) - data_set_path = os.path.join(data_set_dir, data_set_file_name) - - data_set_w_filtering = h5py.File(data_set_path, 'a') - - return data_set_w_filtering - - -def main(argv): - opts, args = getopt.getopt(argv, "") - in_file_path = args[0] - out_file_path = args[1] - generate_attr = str2bool(args[2]) - generate_filters = str2bool(args[3]) - - worker = _Dataset() - worker.create_dataset(in_file_path, out_file_path, generate_attr, generate_filters) - - -def str2bool(v): - return v.lower() in ("yes", "true", "t", "1") - - -if __name__ == "__main__": - main(sys.argv[1:]) diff --git a/benchmarks/perf-tool/add-parent-doc-id-to-dataset.py b/benchmarks/perf-tool/add-parent-doc-id-to-dataset.py deleted file mode 100644 index a4acafd03..000000000 --- a/benchmarks/perf-tool/add-parent-doc-id-to-dataset.py +++ /dev/null @@ -1,291 +0,0 @@ -# Copyright OpenSearch Contributors -# SPDX-License-Identifier: Apache-2.0 - -""" -Script builds complex dataset with additional attributes from exiting dataset that has only vectors. -Additional attributes are predefined in the script: color, taste, age, and parent doc id. Only HDF5 format of vector dataset is supported. - -Output dataset file will have additional dataset 'attributes' with multiple columns, each column corresponds to one attribute -from an attribute set, and value is generated at random, e.g.: - -0: green None 71 1 -1: green bitter 28 1 -2: green bitter 28 1 -3: green bitter 28 2 -... - -there is no explicit index reference in 'attributes' dataset, index of the row corresponds to a document id. -For instance, in example above two rows of fields mapped to documents with ids '0' and '1'. - -The parend doc ids are assigned in non-decreasing order. - -If 'generate_filters' flag is set script generates additional dataset of neighbours (ground truth). -Output is a new file with three dataset each of which corresponds to a certain type of query. -Dataset name neighbour_nested is a ground truth for query without filtering. -Dataset name neighbour_filtered_relaxed is a ground truth for query with filtering of (30 <= age <= 70) or color in ["green", "blue", "yellow"] or taste in ["sweet"] -Dataset name neighbour_filtered_restrictive is a ground truth for query with filtering of (30 <= age <= 60) and color in ["green", "blue"] and taste in ["bitter"] - - -Each dataset has rows with array of integers, where integer corresponds to -a document id from original dataset with additional fields. - -Example of script usage: - - create new hdf5 file with attribute dataset - add-parent-doc-id-to-dataset.py ~/dev/opensearch/k-NN/benchmarks/perf-tool/dataset/data.hdf5 ~/dev/opensearch/datasets/data-nested.hdf5 - -""" -import getopt -import multiprocessing -import random -import sys -from multiprocessing import Process -from typing import cast -import traceback - -import h5py -import numpy as np - - -class MyVector: - def __init__(self, vector, id, color=None, taste=None, age=None, parent_id=None): - self.vector = vector - self.id = id - self.age = age - self.color = color - self.taste = taste - self.parent_id = parent_id - - def apply_restricted_filter(self): - return (30 <= self.age <= 60) and self.color in ["green", "blue"] and self.taste in ["bitter"] - - def apply_relaxed_filter(self): - return (30 <= self.age <= 70) or self.color in ["green", "blue", "yellow"] or self.taste in ["sweet"] - - def __str__(self): - return f'Vector : {self.vector}, id : {self.id}, color: {self.color}, taste: {self.taste}, age: {self.age}, parent_id: {self.parent_id}\n' - - def __repr__(self): - return f'Vector : {self.vector}, id : {self.id}, color: {self.color}, taste: {self.taste}, age: {self.age}, parent_id: {self.parent_id}\n' - -class HDF5DataSet: - def __init__(self, file_path, key): - self.file_name = file_path - self.file = h5py.File(self.file_name) - self.key = key - self.data = cast(h5py.Dataset, self.file[key]) - self.metadata = None - self.metadata = cast(h5py.Dataset, self.file["attributes"]) if key == "train" else None - print(f'Keys in the file are {self.file.keys()}') - - def read(self, start, end=None): - if end is None: - end = self.data.len() - values = cast(np.ndarray, self.data[start:end]) - metadata = cast(list, self.metadata[start:end]) if self.metadata is not None else None - if metadata is not None: - print(metadata) - vectors = [] - i = 0 - for value in values: - if self.metadata is None: - vector = MyVector(value, i) - else: - # color, taste, age, and parent id - vector = MyVector(value, i, str(metadata[i][0].decode()), str(metadata[i][1].decode()), - int(metadata[i][2]), int(metadata[i][3])) - vectors.append(vector) - i = i + 1 - return vectors - - def read_neighbors(self, start, end): - return cast(np.ndarray, self.data[start:end]) - - def size(self): - return self.data.len() - - def close(self): - self.file.close() - -class _Dataset: - def run(self, source_path, target_path) -> None: - # Add attributes - print(f'Adding attributes started.') - with h5py.File(source_path, "r") as in_file: - out_file = h5py.File(target_path, "w") - possible_colors = ['red', 'green', 'yellow', 'blue', None] - possible_tastes = ['sweet', 'salty', 'sour', 'bitter', None] - max_age = 100 - min_field_size = 10 - max_field_size = 10 - - # Copy train and test data - for key in in_file.keys(): - if key not in ['test', 'train']: - continue - out_file.create_dataset(key, data=in_file[key][()]) - - # Generate attributes - attributes = [] - field_size = random.randint(min_field_size, max_field_size) - parent_id = 1 - field_count = 0 - for i in range(len(in_file['train'])): - attr = [random.choice(possible_colors), random.choice(possible_tastes), - random.randint(0, max_age + 1), parent_id] - attributes.append(attr) - field_count += 1 - if field_count >= field_size: - field_size = random.randint(min_field_size, max_field_size) - field_count = 0 - parent_id += 1 - out_file.create_dataset('attributes', (len(attributes), 4), 'S10', data=attributes) - - out_file.flush() - out_file.close() - - print(f'Adding attributes completed.') - - - # Calculate ground truth - print(f'Calculating ground truth started.') - cpus = multiprocessing.cpu_count() - total_clients = min(8, cpus) # 1 # 10 - hdf5Data_train = HDF5DataSet(target_path, "train") - train_vectors = hdf5Data_train.read(0, hdf5Data_train.size()) - hdf5Data_train.close() - print(f'Train vector size: {len(train_vectors)}') - - hdf5Data_test = HDF5DataSet(target_path, "test") - total_queries = hdf5Data_test.size() # 10000 - dis = [] * total_queries - - for i in range(total_queries): - dis.insert(i, []) - - queries_per_client = int(total_queries / total_clients + 0.5) - if queries_per_client == 0: - queries_per_client = total_queries - - processes = [] - test_vectors = hdf5Data_test.read(0, total_queries) - hdf5Data_test.close() - tasks_that_are_done = multiprocessing.Queue() - for client in range(total_clients): - start_index = int(client * queries_per_client) - if start_index + queries_per_client <= total_queries: - end_index = int(start_index + queries_per_client) - else: - end_index = total_queries - - print(f'Start Index: {start_index}, end Index: {end_index}') - print(f'client is : {client}') - p = Process(target=queryTask, args=( - train_vectors, test_vectors, start_index, end_index, client, total_queries, tasks_that_are_done)) - processes.append(p) - p.start() - if end_index >= total_queries: - print(f'Exiting end Index : {end_index} total_queries: {total_queries}') - break - - # wait for tasks to be completed - print('Waiting for all tasks to be completed') - j = 0 - # This is required because threads can hang if the data sent from the sub process increases by a certain limit - # https://stackoverflow.com/questions/21641887/python-multiprocessing-process-hangs-on-join-for-large-queue - while j < total_queries: - while not tasks_that_are_done.empty(): - calculatedDis = tasks_that_are_done.get() - i = 0 - for d in calculatedDis: - if d: - dis[i] = d - j = j + 1 - i = i + 1 - - for p in processes: - if p.is_alive(): - p.join() - else: - print("Process was not alive hence shutting down") - - data_set_file = h5py.File(target_path, "a") - for type in ['nested', 'relaxed', 'restricted']: - results = [] - for d in dis: - r = [] - for i in range(min(10000, len(d[type]))): - r.append(d[type][i]['id']) - results.append(r) - - - data_set_file.create_dataset("neighbour_" + type, (len(results), len(results[0])), data=results) - data_set_file.flush() - data_set_file.close() - -def calculateL2Distance(point1, point2): - return np.linalg.norm(point1 - point2) - - -def queryTask(train_vectors, test_vectors, startIndex, endIndex, process_number, total_queries, tasks_that_are_done): - print(f'Starting Process number : {process_number}') - all_distances = [] * total_queries - for i in range(total_queries): - all_distances.insert(i, {}) - try: - test_vectors = test_vectors[startIndex:endIndex] - i = startIndex - for test in test_vectors: - distances = [] - values = {} - for value in train_vectors: - values[value.id] = value - distances.append({ - "dis": calculateL2Distance(test.vector, value.vector), - "id": value.parent_id - }) - - distances.sort(key=lambda vector: vector['dis']) - seen_set_nested = set() - seen_set_restricted = set() - seen_set_relaxed = set() - nested = [] - restricted = [] - relaxed = [] - for sub_i in range(len(distances)): - id = distances[sub_i]['id'] - # Check if the number has been seen before - if len(nested) < 1000 and id not in seen_set_nested: - # If not seen before, mark it as seen - seen_set_nested.add(id) - nested.append(distances[sub_i]) - if len(restricted) < 1000 and id not in seen_set_restricted and values[id].apply_restricted_filter(): - seen_set_restricted.add(id) - restricted.append(distances[sub_i]) - if len(relaxed) < 1000 and id not in seen_set_relaxed and values[id].apply_relaxed_filter(): - seen_set_relaxed.add(id) - relaxed.append(distances[sub_i]) - - all_distances[i]['nested'] = nested - all_distances[i]['restricted'] = restricted - all_distances[i]['relaxed'] = relaxed - print(f"Process {process_number} queries completed: {i + 1 - startIndex}, queries left: {endIndex - i - 1}") - i = i + 1 - except: - print( - f"Got exception while running the thread: {process_number} with startIndex: {startIndex} endIndex: {endIndex} ") - traceback.print_exc() - tasks_that_are_done.put(all_distances) - print(f'Exiting Process number : {process_number}') - - -def main(argv): - opts, args = getopt.getopt(argv, "") - in_file_path = args[0] - out_file_path = args[1] - - worker = _Dataset() - worker.run(in_file_path, out_file_path) - -if __name__ == "__main__": - main(sys.argv[1:]) \ No newline at end of file diff --git a/benchmarks/perf-tool/dataset/data-nested.hdf5 b/benchmarks/perf-tool/dataset/data-nested.hdf5 deleted file mode 100644 index 4223d72810785f11ba801ac0fe819b0a72d0ada6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74496 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 MpQk>7-@+&G|0YWOpa1{> diff --git a/benchmarks/perf-tool/dataset/data-with-attr.hdf5 b/benchmarks/perf-tool/dataset/data-with-attr.hdf5 deleted file mode 100644 index 22873b06c220b5cf9feb60c69295374c50b70ff7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 306896 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:`IP!@g(lV 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

|mC?y}pziw2{B&>>yn@d(Sg^hs)JbL(V=}P2P{Q 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%0DnR`Mji4lVp% zH>mS>4`=WSy<;$3zKw52L)S)dW4)8OnfhwZZh4+wKfHnS^_p-Y{)w}%S zetrCi`_!l6cllF30GG(6F#r!^G0OY3b+`h0W17#M$!BqOE{>NU;uDc~@n3kB-g9^bZ>Y0# zwUP7XccYdVf?IKoejOZ*-;np6v$-`6$4Z|m!iVBGRKf(kb9f_<&~AcXD>#&-gq2%z~U3pUac*x_S=Up{{xhH$dk69Q9RlW=MV>?d9x7H95~` z6AZ+=$j`MJuSTBLL-=#fGx;E&iW2f3)IwQxd(L_PKKDSLyJ09G=ibb$?92ICejsn< zd{0lvnJ3@z1a-dmd~fgYa^$e@p2M@&WLQqIT&(HW1Z7jd5F z{&MDN&cDo&OZC3OVB~o`9=mY_j>W4!Q<|sY72Jal^fE(NaOQF5Yo5XHT|1Y%a^`dP zn3d}KxCFVEpWvLGxhFFxo|ZGW265(8&X?|T175<oa%HnXn4P1;jah~I^ zQB1Cl2XKq}ByPqt&=wb~mmts39XuYN$fY@FQ||4_@(nzeJ8@=Go|DJrlF0d#8Cp^A z8Jvuqsh1ORiN9;zm4%-^2p>QLf94 z@vFRt3;T1NDc3_G^+U+>dOuFl%QKpN`Y|q}&Od*mxSSc3`C6Xu()*B~#C6CS^n>1Q zn6CaE1Lf0sIC9p_;LgYlTZ6gijrTE5KYP^6@)+bDB{MsF&qf}FAAF_*mdV=t&b)KD zi6ZlUm}`sGS8;aJqH;sjRL{m&I1EemF5shZy*!UItA}vr{_}d-MSqqHp}D#%e}hK) z1=Le;pn8AxOfIipiM!=4$h*%d9Z3V3Mb7B#D!;0)(D(1Yy|0sSqFm6mkGLG}#Yl|B$FAqh zYk~S`g}hg`(0@VB`LIU56h(2U-gUSHw<2dxGYm%dgUWof{sDXke~OZF_Wd*E-E#K) z$#Na}CN9WrIJ?_ixi@Ob=lNV=9-*GdmGBPU#(Bv5NH5oV%A5Jbd=5vcmvMHvyLdfv z{$?M`zHyf8^VK=q{~t~E0siIu_J90GJCf0`wWkV|hG-9?(h#y*lD)E{p(T>Y9xZ86 z8b&Hj*)x=kCfOsQCHLdiaompI^Z$IE=k=+(zMtzl-{;l;1+J{AK*3o zi^pR9oB1Q&%eP_=rl_m%KYT7m%DeF_cA}Qv-Kc~sk=^q^J{9@*gS@le?{imUrQV@< zLf*v<`FL*6c~3e)K1F^Wb>;u~UVfWT#!zfh7xmdD@_Kxro{xjD9qrIVe<3Q%H)E?j z4kyax{Mr?A861iJ_)q^t{qb`4wCmJ0afA92?#-u>IJCpT3L05;wN-C z{>B5yS@S9vK%U9mTlp{ZJb(8E{5dj1M{}Nwob@>`mdlwZ&&YWuav#*h^?EroGZQm6 zvVY}1ejfSGpGM|?1>_vv#hHgUah`!u_!YVDaz8bdGYdJ<^MF<{ZlB;XT(T z@fDnV<2BCDHTT>S&i&tta~`zT%l$VSZ{s24S?q+#a_;91D21Zx%+Sw}@9uiNGJFwM zBhOwXzh|C_Vd}SdJ67Qp^&H;8Q<1ZA8$Ya$`Q)d=r_bP&%8}I68rk3T$kaKRD&*dC@osUA!r;m{t zlv&-(XENt=58s9Je0DMC+1`yjqi16i^89CRzQO~L8ThCEIk*bn<30U6AD`j~bk=*0 zGou^uN7#Y;_4B-C7C*{4M|1WZAkW6*n5WKjm}j<&x&r^gwedc(`(#e_L=)VJq4>?U zZdfm0$PKv%f5q8PkK;bbY#oH0@0sK8$eGpca25VjUyIH1I?Rw?!d%q#`RvDI^rs+m z@HYJA+812UwKusc&cXf2xs>^}hBMRZ-~?n|tmM^r8Do4V^F8x2=Vi`^<@&w#n?bqn ztRKja=wE|+@&vw#3v+hH=9nnor*|?wL09z)yo4*b-bP-Fjd&W_9Ww7`x?Y@HV!AvQ zU*RBB)ccGx4`0P_JgavBAHc2AME;w9L@9irK9r9|-Ysw74%mQduC3u?kh$FgcjIMb zmpIw=r*M|~5o8zY%5Nci#l5b5$G0PA!G~OqTj+m+j#!J9$WB|+_1mzY`VG#x(VWlZ zycgwtv!r~m-XQ)OWpOv^Vj+&lT;x3|JIO8bqg;>6bI#b>ybw>KmbzGww_q$TKn=hC zbp0RXygywl|A>$T?zyb6EH+5fVez9IjicMKQe-;o{s zKXqSu9FD|N90Bcp@3yVLp@;Cp!^u93I!d~`+~^*^`= zXQ{77-Xq@TR`^A~3J#XDBNgIp=%H5_hhd!h${>G@%5qU0jmGN5*ed6}{U`Zb^up_S zN527QpYJI@A)kn4n1p}z`k@NWP_M(`az)gY55(W{i?~o8&!_Sed>HhoUKVp@rT}{0qL5b2jGO%+K~A^&dD?9+J#CKXaeXQ!mGz z^5c9JzrdL%Iomp;yI%II+?ylhoCA~PA}D}7!zbylmb>u|Px&di3}4G9==tBi zGiCzkZ0@apA^wp^pdJR|3gq0(E>{7U_{>(!)z6Gvfjdx7??=qXMe4q2A!qKFwjW%ENGhJcEDY&(RNikzMyOz6k4GKO0ZV*_ZQPHCVk~ z-Hg9NS>&vIns3KFuKkMr@rAlJD$AMkYcLc4;%@y;sW~(CKzS9i&pyHT>KDVU^4*vy zzlLj32V?YdUcAZ|s6R*c?HXKIFT2L+a!GkE3dx<2Go+OIH+dzm;_Slb%Go`ttMeX` zUF>@KO#Sy!6*uETz1Q(G`l`?6+c@XtvHUQK;AB)scG*u|dj-?gcXDgJ8S(%ep?(n6 z^x7il)GqYI^VsY&*WoXD2sgk)&d6jovHBj^9@Onp_73<@2yn9?74fsQe-( z;4JkV?t#m2H8$&&M}5CnL-mbvKh%=5&pyOc)F0qrxij|DKTw@_qPpr+<-_?StdpPR zulY?}Dd(ML17CwS$oom&2XhwR>N7vQ+ zaq8!AAFfAjT&~{(%a9%PPrWZuFxFejt8tF}9cK@nCBGvtK_$62+RM-I2V4eU;1pb@ zw;vzHy>OKL6|J1=PU6su29!Ub9pI#ms|S1E6V+oQ64v_ zAH$_`AHJO%aXFve$`#ew8DHQ%>fiCMJQa=Qy2!5fpt^$GgEM3DjO3Zm_57LhUF7%A z_nE)XDCD~-fvIwS26@i%J>{Nk%pa)pGtYmycMsq@)H%cQ=gyzC0A~i|&vl?WbK!I3 z=azdhJ4C+g+%MnjWd^+^=X-k)`58}E-;B(f{48>RKB;al=XuS{xrnE6=5)TZ^W>U1 z5f{7GopWC-!avB*r-y#-sd9J>`FZDi$upMcC^NDH@-xnNI*T*M9^#WZJ4>FcTh&La z58~VxH*(JZHr!r)H!>S?4`f#6jLfsP1V7_#oR8)DS7VmE6S*fcFU~~1zuX(m{Jxvz ze5e2aEdHhcxL)qJjmY=kiu2t6g40s_e4eYE!7m5-Ue0~LK+Zfn1`G8v$KS_I@5i75$o=XFM~h8S>0M$0bl&?u5*Z>{!+GzQx7rmyk1cJ|EAye{YgoVzm4a@~q^3 z{{Q*)uDTsEXD4x-~kL@&LY;dm}sj+v@DZ+37N) zpU}?^a3x=lN%9exgUpx)_*Gtp!MGbm{MzUB>&ZEr50%GYv3g*x;{s&1^yV(eJgKM7 z?s<>;1&l(@%IvtOyO#Hjrrbe4XWVXN@7$=)ykEs*&_wRVc}F@;-hivr$MSn9gI}>z zFFR{VzF)lyuc1HYVWR#r&a8X}Psv{(?++E!FJc{r>%EGTklBBdo{#LC>-))c)L*4~{;7?rsnpdJ9X5$vUo}9gHy1Ygn$5VJTYRJ#xH#z6~be^qV zhspRHKcE`|{KgX+h0=CN!aCX+b=ba~CrdJ(j%RljL+!KA|_4rEO#kFuJ zE=55G?DIpBeeMp^FYl*o^!`>~ z$a7FpZxMg1w}R_)U#ynzLf+{+ae0iyVXpnjP5BlSmLK6WxC*M{5`3)pA%@=7&9$!QJI~K8^Wqoe&zyTX-&>x^sp^CIM)a5S z^UZUYd!rok9lWfrCAY`7_+6cuTavFr?#s-a(|8s#t8*Xb49Y#zQLhc>%*#ER8B>IF zujQF9h&^&Y&VHDmQJ&2&^u{1FA#-FoXU^r`$bELX-bCd2euVSv=4{LTQ5)CjFXueJ znWN*8`{NoO;o4|qE@rN*(p!srklpGwo`8Jkd4_t*nUT$rGco7J4RW5vWAQdt`ds#v zJgXJZO1%+}BhO8~%LX`Fo&Bc^=lRS0xE}`~b1=`}PP`)L-pFp5dn@zwCGLew^>ZH$ zMO``1!5zGn|9{8GGu2n`X}!~V38u=sxHzxp+=KqNZ@0)CJy32zO#Fp< z$eEfM{iW+0QCoeVdK&J*67<$@$rG>``{5J4%$Ch^6Xf~qt~VRq|!;s3_1U1^PW{wZ^uTI$ zd2Eo&;Tb%Jy?Rx7B5sy<<93{lxAcxe-h1Zq8iIe43=#O{RWB3w2mrp_&zpgiD zf1R(tKz=yC4qwS_^{dNUu|<71x6(Tk56TPh1J1$*6u~e|NA{C7T!p{lu6}(degKck zFQAotL6Gl2BNX%bfAz*9`}uV|UcVwr%HQ(g{1Gq37wCY!xE_bP_95qeqmO)rTvjg1 z+tCbtP}#Lxa1u^M-ih1k*N|s$YrKX^`jzzl#`PGWSDVk`JR5&=c96`NJR7;+a^J7x zoIU-JXJ8s<&adHF_y^gC@*HhJIV?f;k?a%M*T{WA-yq+?^Ln}0iXh+fuiOQ1 z%egm~%ej{_YjPiF$LhxU`L0HGv3!5|E{m)4Jy*j;*oMrK%!AEx?&Z;33i)~FyI&+9 zfgkZI@=P6!+{a^(`S&||%b9_h&AFeZA1ZXD*?a8!{u@{O2^gK-TC;2Y$5UBS2E zTV%i5U#}!8sxz-D@P&9%Zp}v`JMx+8Hge{2Y56kFyv{S7{V~s6p6NW#lac2)&(AeH z6Q|4T@d&b~+@tq3GM~TDd!I9RZ;O$8GIKBUDfjfxa%TP$Xr`ALll%2u*Pi9u9hg^;;>m7&tkX`XE-sIX{$UJ!(nfsri33jSGa?XyNE$7Me zk$K%-y)v1*W4Am2zsh@XmE4efVh(Om_vZqB?Lf}lxRh_u&um?fJJe6{ARHoJ%~x_q z|3mibkHqXZ8`Ou( zd2jfGf5K&YyZC2b$aU~ArmM^I0ek_Uht6_Myn;pQPRKcVC(7xagz<9DuD|5sa%+Bv z3+bIGUx`BMxwu$vga>dmu1DTm55g4JFHq0mgp9Lh}089j*$=l9{vippi|MNXhK<@J+(Hd7G-@&_n zQ72r3e4qJEGQTt1Z$vAtJhKIG9da(cg51k3xjXLixw>2onQe>J>v6Q4=dmpEJU7KP za_+T!zu67*Y~>!>ssAbG9LQOjd7AtFTNF^|-py<|k290{W2$^6%FEeNtI1uEGpD`Y zGw6(*r+EgmJLmrT3A2!0Av0~EyaA`mS6~p1K8jX6?qRn!{x{hw?+RqY{cz)nSFDRJ^Dw! zU;kcy8<|~@*KZDrcGcVvIsgv`ou`jv4U2I3<9%+xi! z7ainVIrI7-`6JvbKg4D5lDvvra1nlxGh>(VN>q?D&sO0A`B^T{*;n4;g~)mIv^x7( zFO)!bkY{)}E=C{ME9#BsN$SO%b9JO#j&Hz0@>!@N--p%mrTh{oy`6m=bGn|2^ksUSrXhWY_ zp?;Nn<6#ugZ>d*WZiDIalUR;R&=K3w*0n{LgICoDB75O;+#Ln=-{9L&AJf&3;4oCi zpZEjWfhzC^ocE>Pe5g9_Lx*tom~-)@+!)1?-7ot<6UmF*ai^ z`lAv)bNzk31>Yij!2rF(SK8fU(a=Lp?oLKK=#j*xEmT_2!6mnxE-y1{{PJczHg@_tYiqvfhNTmO2lq?f(>GI@aB z2CSB|%XZ?DyjL&puhrH0x10L>0P=2>`#1M(p1C{pa+WUSoV(9)?xT)8!u9;z@_#pf z|KE^5<5ifdpPygmS?*&m-qgo`av|!aNx>Kg*qw`*R-GMoXN9oZp$xInQ$sWIp7+KR_?D z^)}9Z|2-Z-?%$DmnG1PNaxeakJfpX|)&nKwQ+PP%nW}@#1XMO~J!;i>W*v$3n+!J|^*ufv-XjH&tpSu>> zdox$M;AQ{v7AagZO0bi_EXQYpm9L3a_g3u2%)wZQfA##+mX7e232r-v@`GfO;H`mG4Gn`Az;4ucNnmDIdo7@G=~MGI&MrH2#b~!Zm22E{6&@ z4=eRDpN~Zc6xJJ!8!!qV>&@ZpR^{Yd@vgcJU%`2=x=tP`?4pxaz5rfd`m2X`M1TK>nrddCg}~~-}xZk%bR%?e}iXn8%{(G{hZe`_#JgQ`2*gF7J4V(6S=G{e?U9AJPwr`q74?RXYze0D^JB` z=%hXy2Vsx8D+8& z{*mY530#d`7>d_idxo#%yZ9w+!ryoRMg2Y-UHbqZso&!H#dJtctrO#cWmFFXW zulyXcJLK;$9qlm#nHTx~mLO+Z?%ir~W^B%w+@Ir+`#9fQ?#DYY6aVUGKINW%gmX{i zS=%2u7sl!zf&s-_sIx?!BIJ?#T%#hULgi{uS@w4%ah-|B<_J?$24A=jmNg zSw0t!sWT5=#&I|r59%Gk^EvZ2dsFV!wR&gpTRf0=@D%(gKh8Oua_`rabMKc#59GP> zzkPez1^fqc?w-tuivyY6Cdn0E=c81*FcjF}2@=Ryex8@S+Tk)Sfh%ewv@C-i0IeOiY-79lF=ho}U z96o^aOyr!Nt8S>yTwKgecs;gYt2*cFb~&^53VD>=5t+?e`}W+N4|)Cyx;~iC;LL?) z$SyTQy;pt%rz5+^e7)=C>yYy&&*}kk_SHPI%`jS>cax{(%!%JIO)qn{Ic`%wz&S5+ z=H}huR=vi0JCOaW9Ny3?j&gdx@CtsDf5Q+tyJ}`_=FatccOrXbcDT&{?5nHf68f1v zvpBnb1!P~Xh?n%TD`n5_CjX2IScE6>zF&752FlN2GycL@y)|4G_aW~Ehv{X{T+2oD z&gID%j|EtzmvgihH^C=*Pw@smk1yd~T%M<*v)rApz^};8_dQodc7vU+*O2e$8~H{a zj<#}R&KI5ipny10eHETZZ*_5;BoD(fY*Sa^Z}}UXi8=TXndfDYxt%j{gM5qrLe4wx z34F79HWxz9y%*HkZL-Teq*oUw>RpBIawj~C!dR@gg-^rhxLW-p*2$k@q+A$H<)e5% z&Y4kB{*fDSJ3Nm5I1rt%%eBfpf-B((`C%T3B67~t6XlLLP9Db#xhPlX<8dvfsjH&1 zd=GM-HTU;=md`@o?@xFALiEGgdgo&+M&NzD&$$mD!Px=Nl(R<;<*)P}EFXe_-}p$ zry#rY6P$OlJ^U&z^y^;7rO1w1PH#6JQjgAOxDr;%7x6{>J&r{UoP*u^rTyBExhDQW zBmKOGtl*{UyYK91ZoZ<2(6Ld@NVtb*L;KgR=4o_!`+? z59a)P$xU3}^`ZPOj+c9IYuBc5-nqx{#aM%bT>p|wqoiv;sEzx-KZfV!s($SRK2-e)s-vI!b_|g(N8U}oQ)lPOyKvr72B;Sy``MHHfzQq4d-0Q8 z53A+u$94Dt^(fpg=igg?;XkogZ!>?(#q?k2%&7cXa^~dcc(r^qGAFxno~y!mNv?(N zcosRQG8_LwW^tZ@JQw-SGHdeZX~UU&`R;P&oWVIObMI$H#pLtRcMYWGbW>jX{ zCj22E!lgLRWbVa()%kl?MSJYTa^#-KGm-Bz-%kZ(&ODB>v{HzBeNsV$spdv zW07Yf|J^Uw1hb8spSK+zR^JR{DJMPAr*oft> z__H z=2j8zi9T|6q~rKPb!JXUJ_P^MTSYnZ^Gsw0R6zlK<-RjG=kgMD8LW_>!3FqS{Uy#v zX4_o7ypPP6pG0QvJk*hMKFsCp8#_4jF=xYUoS0w7m*I4|Dc`}FOA9buJq>TknOUF8 znS)LEF3$Ykil=cD{?=>E@1uhJGVkS^_+p$VkLDG4LB0jw$&Yh29*L#cj;4C8@tItL zkHI_09`Q40?>K^I;~}4!hmP`rd>dzP+rYCh1DQQ7T+9BO{b7-uxpfIL%iiPBoZY5} z{Is0?IWumOJPmd68_K)hjSFCboH?B{bc#GdZ!JH>H8^|V;qnOlgY57%_+?~={*Kq< zZJ*71%awB8^%ko?lON%nD?{X*PaQbB&ntSr;W>3X^$7VVIXmp%a(12>e1qOA7=S|R zukgEkIZwnS%tA%ImKY*m#MuiT*4&+F7@^Sjt6Ux}Q%r>H06G4&qq%jNZd=ezMB zu0cIi)-Q~gd~Q05=&i($@(w(Ya_XTxoo_=aEL4BNjqtggJ)%6HiqrMJMP;nQi+U}P z{q%PAHF0Jq8`k)7yW^%?mbzlx5ij&E=*TH|Qf2jgS;VQ#?pB0J8d+=stGMfpZv zhO^~6xfr&|l{oL)&&g%wL0lWxqb^$MJ%;x3Q9O#f^TqfVcc}9&@?t)V3231n=vrHJ z#YcL7;xM^_Yg@UwI{V!l@*4SM&JOm5e5u@(PvY!Zukku`(mRgF^M`1L#_B6LfA>$3 zUFssef8_JU%h|_z_;H?!nh`X5K;As9uk~ z@^8pJH~}@~oXgn_e^uw6X~K)p-L+Qg%JO zGqqdq6S*gr%9D9H=lSc+xhKCyp7lHfqwyq8!!4+Tsrft}mm4E9?pywhb2c4_p7Jj^ z7|MP3$M?ce`7Gp~JX1Xo$78(uNKBM-UglX_Cg+UI9`GyYIl79oH#U>6L}uZE$h;}< zT6W7bcsO=q8ZJY2O2{uD&uR9UQ}|)^Vic9PAv@<@9;uhzZl_!cbJUq*>v$5E<=6OYd?#OwS8$$s zKhEqpnycY9WR~{ek|>6;>e;wn&iu<>Ux$0Cv#ZpVACl+r0i1L4CV2(EQkUdAxSam| z@+7$yF2K#|Vfakm!Ka}j_NdRtsThQ1dO4TI$YXJ_yq#Mk=hJ3>LhlwVkOy&F{*SXK ze1Oc=xA8=JtKSPaJ_TY z*$ZdN1JDZBqmq7gbi@tnnm)6g4_0TdoXS@q=lDqd|H(UW7xI4BNNc&AGwyBv7enO7 zI6KWzoU`Y0ej0zd_8gbvoLSkSEAT_;ia%Xz!=>;Nnz=rU-_*~p(nr1omDTMzXMEn* z4&`BbWB5)Sjbreh-e4Y$=JFDpBd^9&^5@)}JMfo08%5+txscy?l$>*XGZ*4U_)c!Z zA9CKsuH%(B2pdq*wL|m^%R}W`u@+0zPa%8Y7wX#hM?D!^8SGaB4@8UgwNn3cs>dtJIT8~Q(C@(e@8p{KK_Ph@L^bi`_$P*o{;kn zTaHiQ`ud~fdodW()p^fv%;VK1_$ThiOSvI-$uDv>pUeB#M!7vYV4Z8(orlO<v_KK`$A{>Og05faa}7`(ckBJcA941uJjfI2Zxl=)r{gGO&#S@L`RrkwJ^WOD zP5mFb$eqwvet@sWrE*oD+nnp1KhurK-z(2pp11rQM32pUr@8{kfEoY9ENB-_PJM;H0fSj>!`CRV7{gK(e3kM)S%ep9s zJJlB=&*wwx<;c&knYyNaEBP7Z-YTiivyii66Q&{G&24&_o4HSF%9&?RbH2l3a?a}E z@;7n?&OO~l-YMst$}|1GoSB;EzBvxo+sosT?>zTF?d408M#;A;kS4oPC@SdefP5dtLUY74F<@W3mZ^c{XJ(MT`6CJ z9>{a>6X#jV%=^w~n&B$>Q2Zrl$IJ7d^YRLQ41RrRu)T#W1F zC43tGK%VQ<^!9K^z6KA;mGKBVs82@bZ7ILkZuwD+R8K zz(>fO&)(UNhq`tXGP}BRExi`}EdG|WYvjM|GubyYQ@_^B49cbA_(V9CvoC(YnP)jy z55|+O%|hO}F5-n)gq*)WVygTtFTksE6Fi0Np?BzI@5{Md5;KwWA#?c>xtrXDf5BPu z_jp9k88crl;B%+)bo`F&Mx9-&h|AGlFLV9?TrdB|J^2Mbn(yYZSSp{x<^1}2@`-Zx z)xumIHzMyhhq;z>u%5apepPSgv3xR~lK121`CJ~z_w!E7#P8}M{4nw!IA49WJc7&d zJv@h}a_0Ud{y{y28>656DHrCWPzkNok8xRE#yfZ$u0jp<{hV|3WByZpCI5#q*s8t) z*UL|$7k*ZE=WFq=JP6sd`p38z&$+e?*-6gNXZ2R_t;lZCTHPH7Vzk}}F3Rulm;658 zj)Txdy@R)K_Sg>cC=A0wy*8X3^h-WdeGPxmJhs?+PEv*;f{G_KZjMcjG$b z-$RD!Ka8#TO|OXF{-}V@^}f=}J8S+up^y9{@^1l6IXmNa{sM)3wm0U=XW}_D#s)M) zZ!B`XBDX@`wMVKapryJBTIcsuUyK9sE*k2O<0E-BH}{z-a!1^Tb;wRTkR2@8<>E#KWkXolRYnG2b< z`TOR($howf^Zj&@^WEnDc%1V*=Q-QLPpk81&rEKCoTY!OUyMe+P5zrD!+uoP*pEy zXYP}`)S1Q8IcG|~pQ-A($UJ=&i(Jchml;_^{TUa=|>}K~Ipr74if_gpYzRu2&ohq|Cv+`~}3Df1AyLFJ~F!x&* zy#i>7oPq81a&PCJS|g9)>>o|!+`sG8xv#PdW?r{O&aEmK?fR?8eOnKiwbPJUklFPf zGAlEGa!!@RllVkG|DA?^&s&)CtZz$08rFq9#te07EpWY0f!96igeiGj!Ga)l;h5i)Wp?)2W(N_O+&dkoU-a36b&tA2B zUVSNl#AA?Wb%XjT-huIQIj)J?D5X9o_&r+Tb@@j9LvSi)Bkw`O^}oaQ*oM;RhTX`1 ze-dBlvv2ZooVj`|Uc?DFTmK7`!#L!;&wMJ#tJMW@o%$@4#KUN)mwEj&#>&fthm7%~#~-k^OlvXHItG*Z3B0$~hPQk~2SV<(|ksbPCSF6)$Exi_!nVVs%yja&-Zxw3a;?l(Xk9EO*1EY9_roP!r(CaPnke$MOr@H=YYAHC|x zj+7alS)3jBAymd6_|5h6@HDcs&ez+BOOTyCJNgsyQFzB^-bZ$auhb7<2wuj~INh}$ zu~UAFC*nlI{(zrA4#8_^DSGRFjs5lRQs+IPD4Od1 ztKNmYYhTU%kez3#Yt3*9I-&=@#Cxu9$KfdCb1!il{cBMk3sDh=>VL;KAn&QatN$lo zNo`S1ZDm%n5 z&fooez5w0i%$l6lHRb%@KAW524>^C{;ux-Ojm)lZc`$O%pQ)b7nX#APALJg){dp6X zqMUj-m*C8;oTa%Rvjf!Ae*{0^RJ@Cvftd+6`AqhJr}gq2k44Vk=hW-*D)Jn*)6e}o z8996I;LN>}a?YXr_bjr5)zDuo=jWUKVhA@xX6Oa3&F0TJbM6P^+`Eb=VL9fg$0E2`+tKxYM((`@ z$Q;kC&K%C3^ssBa)tSK;qXS-4=N>-{$EY*&rgHY~4>`|Op7G3y_s|e^)CG{4eiq-x zPhpjO4nC8!!*AvG>X-O2tdO(6Wd7vbzLGNwv(J=LKQFgLaeR*^$eer}nM&j zg+hAYBIm{+y|2(k{XQyS6z)SM{ReQZJP?l}GvF%j$q(|e{1gAr@3n>wz-GN3d^V29 zAY|4o(|-o#efCoI7BrLd+@FtETx-GC<3#yMzMM11-oR^e_VH?*eL3eP|AZJAzzh`FmZ?4^m3dsKajb0xds@{XcDA)(D1bLnSnp2c9dQJ&)SJae@qV}!Idk$3)lR=Q&exmI z)6f#1qKw`(_*K3iKgpk<3yP@o&XF^&1x{4wUGqNIYRdnjGwxRxT@xT)`cYGj_D!W3t>GFUZq5=l&aV8+@m(s@^Wo#!Piv{ur(0{rL_)fxqCg zyb5{0FVD~D_2N-@7ayXS-Xi=b--D0jiCBOG)T=Q;-iGWW*;jYUPwQu|%)cw=rac5( z@FO~5nRZ`va=jNHr1t{PP1gI2AJHojnA*af5`Wm`TDb*XE*mm&c6Tfo1An19qypcGmxL>A9!3n0_Pz2UVg@zJ0<0L zTo>)-`FsX4TNbPH%;w&k&t3GhH{2tSMPX##)WKxeb|QPvCF-N(JR|kwvrtvNit~Kl zBA>@Obh6uAuFkz!RI7;kYB{sHGG=0m{>%7Q&MY|v&&jVL_jhK<6wY4txcn)uQs2&X z_)YGDrFaXOQG<9TXU>+AbHC?&UM7Dk-_3ajmtlW2Rd?sv{5LYcYjI`HGnzB=Jf4H1 znBm$aeh4-2tUBjOXU-Y*Di_wzF7_2KM`rp!{qN+hcpguxzvQEld;ej+1kcHXc_rt3 zX(MNboXw5YnR$hH8s0^Ap3J|y^$TJKD&j_rbNy5lm*2-4Iqw_&`Fmvcwnm=W_2}(0 z3pumsIE;`F=bVk1bJmT+c!&c-)5bLDM0^P_@#v-}qS52qvNOA+N4at&V0UnA$~tvF5ZK7Iz- zrO!}j#^gQW0lkUxshn9g8hQ6Pj?ctX$T|17>vzeS>1XpItkoONPvbA#i68Y2L`V5} zK9O_YJ;QlNXeV#NmFjcxC^AFy{?tP5h9_`=&(uQBro2o1pjUy)T{Y({{LO%c=awEfNJ{fcnE%%f59kx zp#Fy^^H5}m&Dq&cZVPeWd*#PI^Q}Ap+vGzz?`%0kYRen+cJi&Lgz9)#?=gHS_uxIq z`IPye_k-5z_s|Sq==H_Rauu}4PwM7;Gy25pfjk#i;63$p{s5QDU!fSX*X8~7HvQ~e zkLW#&mMDXb_{_BdXp3WTj@~P{P<{p3;s4~fajAZ9OhOBF2fmb-qLj}SP~Ra}9zvEGuj=DHe?^m3Ovg+!5F`tf3xDQ8Th<;^$0okp}tH&U_Sl)FXly7kD8~&OP z<>xu?3nkG=T@Pz8M!y^%#Mg0GK9=9$laP15nfxN!V5)0p;e5G1w#c2Z8jaNl@oStN z;vGI5uj}P`s3zaZXW)FyRWHEf7^}{%)s1srt>-z|;Nr z=F)f+MeeDbzxN|&(ldOh{@cj8-9V~L_N$QQ9XKJF{TE2{D;b*x8wqw3} z9CEJBQ9p~F$hmid-jj09+*{;QO#628>`d9c_Sd*sFFSNg&YaBiwi20dgK;_vySA9u z@MPo}%Cqw$=Qqv#T7l1znQ|8n#|C8QyUWLJ#ccI1Waf>=5)@bW=U&KpF+E+L;I zzlZEGc~8o&n!RnUYtt|if4KfJ?vxMXg(%`PnUU-Djz;EWN%d9oY7CXP;$!&-+=zL| zUYT8Vv7C9^8x`@V&(`DYr}xO;%lYprxh1ZZvlD;H12Ijn6(5Mc=%;=aMs`*6P2DynFu0yY)Kcn)-Df zhY$2S;9O?zQBGsM%{*UK33)5)feJP z`D(6@(@@3ru6niPuQ=~A#rO|(bu5q{ZnU|_Q7xEoT0zs zH{_k`F)qg4xe(srfpZA-h2vJSR8sxeBN+_rZNQ7uiR%Z$IbS8|p{+5L_!C#1nWKH{y@@1upONqxd^@ z8#GrhQm?}{_04=K7Rq@iyh;8N*~RYF>&8?0WL%E^u4PC1MIMYQ>g=h{au0Paxfy2{ zxtI%bbG`vr`pgx$NPY};aTu}-jp4?wmE#xrGVaUMald>pcjdD=`(Sr@X8Jf&Z#DYJ zlTb!3%dI%?0aH1<_J?}wP*9zBgE!%GJ;mdyzA7dU~$qoER<-LThBtcu#+m+?#V2<@wAr zoA2!|JcG8#dAf`D-RE-d$2$t_Wv0N6z3 z`|t=Hsy7xnBL*PP&u(LF)l=1yk$qv|DSC{N>;`4ddRx$4ic25)1jUJJ~?Unqb> z^hfg~WKK?2=WNNjcP7T@WzOfUoGcHKbFSvh$g_SM3gZ*~!pNN7hCEw!ID1qT`B&uZ z_>KRM2KxXXseb!7{x4e;p`yr^kx(Sc9%UpnwUDHNR4NUj&{9e%B(rFsK}jkzQfbnZ z8SR}kJ+F`Jdb)Z)@6Uaoqvzl6ch3F2Pua%`@&@&-oSAgBoH<-Wo%#MhWQQ!yvz0lI z%gEWi*KuFHoV_{ovg<6=nYnjiP!3W@I?8Vi3 zg|HcCqZo1?oQ{*R!aZI28P06=|7PDV*@^4wZNWU`96CVnOyrz7gC}AM&T##ER769} z*Q@KEQ+XJ&Zw}Dki0o4%IeS6Aahk{v;8FL@V1MuXM#%ST6~*B=RIe(olaI%1cu)N| zcFTkLB+Nkes^|4?mS5zTaW)I>wSKJTFlx_lYFlqcd3xh?06 z%+7UTa@^Kf*;1iXq;{#?%YnmAIu8rg%!@dvJbhwK^y)q~~F`7@jl)Xi~> zyg$FftM~$3F84-Dxg2M|Tg26I6S7B*cCC*56b8wcqMCdI=Ns({xsrSwc4DOZVxEJ$ zu>@uGe&U~aG7gmUU3fp2RG)^{@|V0GUF32Yg6wp)dAa__cvBwFJvjUFGP%F}Ev}MB zaUE2aEA!#p8q?&hoL%=ad97T7FXr`}Z>9R&L45}b$<6pq{)CT3_R;a`?0^H*+wqtB zQDi>moO*~ei{`qPd6pez7C(U=df5wp;G8Ed^rj+nIA?m^`TYKGLFVHmyyg1W$gJ(7 zzJia%IcS7ddaeA~%+EW~4$mQTXB>LT|KUYs?yW^3xi9BDYKtG_TD$=H-Q^wqgfrvv zEayzgIaff=Gm>ZUM!6rdC%&!Dj#Cv!$T|O}%Ny{RdJ;dunNc%wqTC#RV+H2wy~CML zBamk*=XRdcW3X7A-RfQWOZ=?PJD0iIPF)zcVvqiVoSo%r&U-cnui+Hjr?;8g;|uv@ zpcSwBcFUh$7hLzVkRUC41>8&hvebes;WH_-5pp&byv7tfxA& zKYyK#oH0-09y!lp=2Ui$(VR18vTK=P9dNLmv-y2)?OJB-mE00t_2%$HxKPgdkeT}< z*W%)QIcLXuhX2I%dLJRPrHZ-@Iwj*l?5Dn(|G;&*=Fesh{4T$PzmeJ5o@?hG7#JzG+-k(RJ3_e$%#6MyuGLO&I+sm1I!}(V%MrU<0w?rpNL~z>!i*%zELKjCw1!mG$P!)hLb`|@Y_T7HfXK{>f6%FDO%37m89Wce03 z=iD~=P(Bk^$#?Ld{6C&SXYtWofeZ66^pSH8ZIGABIlJm1JMnn+O1TH0f>QWUy@a!i zuaiHNFW_tWNq(8LJ51we)F1IO{sbT6LR3am>~?K4F2F9FqjwiRK!1FX7xV}4cRUEk z%h}m>riYtQLw^sx#R&ED$X@&pchM_?)8)JP0M41Q1CPqPum!X6EwTqK=bv4Fm%qUc z_)VR2cs}pt{`@aj#cT2mERmnZaqY9l?#qp_Mek|89r<>DfTy6Xe!lPTke`-s=IZJ$oL#UIpMboSIa^zZ zr(&IZ@5j+-jzYLdKi_r-@?q-p`7bP!^Ier)D?3gdz3F&X|4RHWSL8K#NnX$Mcpg{A zH@F@7wkfDTLEg=+xFC*4clG&v4eBBPEhD?@V{$oUFE51b(ucTrnBJM(0FC9rd@3K# z=W=$Bv$+~N=yk-?f78j|1 z;;r~hzJzOFjywPZQ3wCx5&hY`9QmHep7n;@Q9pb9CGwqUjo#Q_?_k$T@t53L?*?>0 zzJE&V4MH`PK=!2Hcrx6wZ^z4ClzsFw^(f8kh1rp6$_Jt)R=ai^Z|6#UIXA>`j6-ky zr$3(8qqIN2iC@v1#NTrhK7ccGhau;7H~t}6e-@YJ=6o|3;IW)Jp5JfIp>cBNT;@cc zy(P%AlXo=lV`l6*oV|wjowJ!&c}A{LXK%}M^cUtL=RoFXJJ<5_-O2~4+i(x${k&0q zHLjI2Px6k=;4k#@v$}#`Q|DZmz@H$qX(aLtt#UmxssdWM=Sr@RKQR*}UC+GA^LT}N z5Ar+DjGT_l#>}Ih{5N09IfHldQONHozxRKTUEvSTY*@>gnIHKx9XZc*_O3jy^-)Uy zS3ZRo;vN)GpTl|Hat@W}ThLuUyYB+dPIQ-ETV(zng#kDMnNMH4=V`ei|Hpl~2p`Hd zT+exMB(kf0z?pw{q7Npi??QIBJkJNq?K$VjbonE6LNC4Hyqrs7syqOhU-R%1hN>?_ z=1TU6Cdho)!Fg{7;R*LG;v#qy+tir{D^N`RF!Ii{R%egOJDGDJ^Kc5X!}jM!sDYd{ zBX~RNqN#fy$9MAGyob*~Z)AqA;6;2MUlwB{GIw_1RGfx>sfi zOVI#VyY@Ptgx7GN`g43G=dYKs1&1Je*BGveLy@^t9uLd2cpYcXX9l)FW_rF4GSi#O zm2tZK2yaA9WUu;HFXzx&9*cUI>Dq(Ho}IaodA(YFq@2C6w0wcQ886@$RK~sfkKr;r zj|22J@N>Kh@5*Okv7EW|HtMMl!*_CaxoMm|pItuZoWJ*- z$@l1G<`zbFzGi%)YrD7s*G1<3Y(9=xVgjnD2jPG6TPTIh^Xs+lM-TPETn^pj8vHjO z&Xw@0+=XxDDtH2C;zhm1JOf+tnED-L-an)sgQ@DA@A>w~oW4(QFpib4zko^;Qn_yhCNO0PPv z=P$S_zl?+AAzY2S;%NCdWN$f3-5Yh(CHMo3MqyOY`w72ein_J?tMV@Vpw|^|%0FWW zu0m_Q4yYqP$+bA&WXt4Ra0!Ota%3O6gS)%uZO*smMf|BcXaAS-t#Tvml}|=z`9I#y zXJR+Hx%UXYR`MwPgEi{gFj#(^7w~l$iidC^vWpkMwXQc-KgG@X8ScqTcrw4p@9{v+ zH^@5PggN+Le*>~F-N83uB~C(d9O9na_ze7t^>_-;=|96CBD-BDTqIXSb=NB4Vq^z9 zmXFb|EcfN?R%Q7H{VQ=dPD6IXid+k?yZ$E^#plSrQHDG4IM=e1Oyp&_86%K={8B#E zeXsIp?uY#Mf^*eHk^lDbwB9?&KH8S^jZoUPXHiS8f*a&V@Cvd!wRGPDI2{-3J&5CQ z0&3$E{c`R*hTG#^y_@&}&OV**yF&7KY{J*r&-H%1g{vXoQ3chX$@vEONp8=L)xXKB zagCh8)0Agpsj?HYPi5CDs5cLn=-tnm4ViCa)wA%eI%h?mkJWPC@3GjZ&cELb&hH}g zI%jcnbqzVcv&{F*i+A+$OuoR`BfjGN&R5_H(_=gA(`}f8k6Vho6x5yAQHo72#9z&*AJ- zoB31q8073|h3thb)NOE=>v=~?B4^Y%&a*NQdA8@^FIN7Ae_@QA&E@%QycE=# zu{-7LT5HwW5yq<@mKSio16IigA-mi^dNc8@dMjrRltj+y%(}jMqtO7F135!7A0ES0 zoUY!CQ_%;d^)iDpJF+)^pqH63U%w*S$dB@T{bhI#IR~@%{jQ(Ce!za}U)7maqts8} z0Qqn{gSXXfIOpJHXe57`J|0jvK`(hEGM{orpQ!hVx+kW|nTM~*IpcB$3_xeSE7Wu4 zVaUA7yxpOf-SrcG9m{d8e$I-^Faaa53-93_+<~9n*AQY>X59V9B80un>x-I%)4Km|T)1M(P!O8Ngdu zxA-W$A@AnOoZaD1l*MQGRPS~!$IW;r{*ZfPI=)dKf(__|dDx@>7TU`9qMJO9Cvwh_ zoV&y2`#ERQ8|bLc?wfg^^PmAL=^cX4k#Cvo!1d&uS9iL;2034*^B!z-?H8_x$@2cV zOU`-mkbIt;v+HtPk489OzrO27%h_Mc^9H@nI2zBZdt*Pj9cL$ak+W|cf$jQN@d*4Y zAI9ssGyjnuvST*Y%XePRsW0S@kX@x6+H2pWegUW82pq5X56;9S^$mDRu7%3T`ME{! z5&0W_9_L|-`aABzg|QApkUjoE{ha4h*x&nh@5x+Ev5njFBA$uWxD?IxZsQM;?~N~c z5Kh+5x9-z&Yl!>yne5M7<-)EnM85kA@)Eu5L=(A*dL*BK_fc8D92Z4B`2b$*o=x~# zUW%)6iMk>;9wQJ?N``pMOI?xfK3Geu&r6D87#y^I$xQ+Un=HCN7k}$Due7*-szj<6Zj?1?6F= zB4>x|z%S!+Ohf7X8E%P9@_KwASK@(u3IB@E%P-->hO8OYh3XSje~o|C*c_aV>3 zwVdZUXL}R%h3ejT6sO}&y|Xw!*ZkbyR_FZ5ysRhZoyxP6ee_hl{2mu_eusUK=Vy%i z3GT@qIWyx0&Y5}y?>p!9=E@uK4ZgyAI9Y!tN}`qfU*TQ)d+|FmNAjL^!ZFy1j(E|v zWqdqV$$7?)Mc$)}uuU)TVxH?Kk=^nUb>5%6f7wOL${%8f`fJYl+?F$!@>ib!emtE& z;u3fZkE`?ijpdX0VGNTC;4$P~?}a_Nrq1l0%bD$~aWp z|IDN8_$PB$y-7TYZ|A+-motmT$(bpce69-Q}bikv-fAU9P1 zgS<=G-ye}r#76aN*o6`J2k&7TG7n#I&k&x<2XN+P1!9dZJa%~xO_F1q5-mx^y9X!55vE5RW5>`<@x*^ zDq)_wJ9=R|zSYZ_`Ir1P=bNhnf1tjMKjK>W7z6M!X6esB8~IF3ldGYgT*RNxJYFIH z%!T&aN;{u7%d>%=WJG^O%jCQ)PH1PRD8LZ_rboh~oGI zo%NRE0Xcin&Dlef0(MsW?%72Y29ic7l~Am0a9sIxZ`^=-`)9k94t3*?JK^NPr-h28JvK2n2aN^-nAn+yZAlw zrSe1^BQM~5dpykr)kSy;*G9g7vj^6c@6`X2C!hryt5@@66he25*4v6wsEAMWTJqf} z;Gc82`fiLzGnCWo;@Y>^jWsB!ca>`cajZO;^KEveYsbs^<=jOFab+Hb^IV(9EAYO2 zG1uk~at)>N5H7)O`gQP*T!{;!oO~}f;az0E8o-TF+4ZG-Fz@7i^VGsk@~arHpZ|8S zUH%@&srzFTo>ISoFXZf5d2Xg4=VUSTm2=jW!bfO{yvsv4@A`Z465fNHpF8+o&Usyy z+o?0_uEqK4zj-?6?8sjBshnr?L;jB|Am_(sK824$=0x^{KkzzoM!o0yIhd)wn$Jh( z>ss}%$UJ)mnc10-IhV)DJ<&ui!I{e?kr{Cj@+@b@{J=l*ecS`j;2(8m|NKMcx8!rt zQO|-iEf|>XcXW)PO-_TZ^ zjOW$!(Ow?Ie_<@Tsb}z;{2iu3xo;<2AY3nBj^E^yu?3rOJC^Hz!0Y%c)Waj_p!Y9l zFKNZkqlsSTbN1gm)j4JHHB^!F-2Wiofk|>b z&V0yDo;@PF@BR9jl}~Wa!=KR|FCeo!=U&eF*W{PbQGF$k;JiD}@?vhz^|?IX#z){J z`61Mhw_rLRROgI5n=7gF?&VzEfPu)o&imX8IS&TtWhdRtFClXx@8dStzeIcaJkI?6 zUe3(VtjV6WiyzZFgm>p2EJ97aoV9~F-v}S-&BbRJuJCHha z+<+7H#-gx$-p_Sphsl1j)Ah3Im-%hp#nt#+OvDOxZ$5%6^8r|i?9;8ej(!*946B4& z^-AJ8`FE_u!|Fc#A|HejSg+3hbc_6^{0?f$lW`#yslUZe`D;{j?Xa_ApXD`?sIwHIbKybO?`?wyT~-T7+RnuCZIxa ztvBC~>}->{v_CV7U&Iaa9a+P(p3h%$eRP#?#0Wg1E{A0psP2U8@|)W)4yrJo%*`%epb6Ry!O&c~sQ zyqDj_zjF4L$K+~q9ljgcZ!Y24de7kwIlI;@v{1NoQ1(ykJIp# zYscX#xh}H%?o}_rcx3aD)&hC36KY^R{PUq~|T{*kr z0KJpY2u1WpqMzIhry_gVtGpW7Wy^3!eu}dro$CIdf){I0)^F$g1(Gvo{R<>#N@ z{pa030-e-(E;7GzPGz=c9+#CLMP^T5y*v}yC06qcy;0a9FThdAIr4(ucJ9l0pYr=2 z#(B4AaXUQWTAqzOa|_j%AkXM!_yKM8H*wD1$?_w3Oujs#y?Ek}D`&Hie{U_JKX!UP=Fy_j~Vx4>de~iqYI=n?MXU1+h z?`!5*&YXtmhQj)D*rk2DV`fm!!%F%?xGlHDN;$LQ9r;K3RIHIpBJ+NzI&-fjzrfd^ zwmcWtU==p%jldE)^Q8fL;0>Ij_bGqOz4$(E!T;heWM1Vg`WLhCBx>r<#7nqH{Sb1N zW#0b9rPN*dS^f)?Yc$~A?NC1o`LoH zOL3|E9=GPL+z|sXR(%uq#{%rewYX5f7T3US$XQ=i?@c*7?yGWrtjGO&`Nr7A&#ANb z_mICwS$V47BKc;{9+sVPkNgl;Av;WVxxwhT&yc1RMzH9HHAs$!1 zjn!C-gYc*RG<=Iv{%l7+hEH>CzPykdBfDi?K0)s>p1}X$DfwVN2PdM4`!?#`Bj15S zauFPZyKp|9*6+<{b7TCDLopsV>rdnd&W6tek40Je zN=%YF<92xkUx4G~d-!YK!1=B}1Q)66;WNDvcpp=6o!$-nH^0Q^a~s}^!I+MGKnbx zv~|2w-Ijkx-n|)|=dmzyj^>@bf^%L>%YB@4C3{5Po1D$9(A2d_xEn3?^W5);$?BrW zJkNVqPA|`HW=%n5-OXQg?uh*CJ9Bz}3;H~I}x>%t1GUu;rk#{7sB+t}e z>b(C|VOYtbXQbcJr?CIe1=uFkgf><>B}Nd9U_&-xpjQ zmmu%xdiTxdx#2(2w)36+`xb%=$*ioZQRh^>RjM zHuP5KJeY&e)erJKp3mDj^YT<)fb1Q4crs)E7LRgI&WAhYhmrUGDlUucb>FB*%ZpJJ z3)R^pC&+on3#tbp`|8npId7EvcAU(MPZhJU969?(qLzMURo?w4@R43={sqPHIBrDd zZDwfpliRRGotbej|A*`KYVg%K4A~zZ<7f4ACicPg>Mpz;nZ-}5a~_-}zben;zE~<( z(Lavcst@9Oc_hEWd(Z-7aF^a({sWI7`*(KB%=y;1&-Hu{6yqN;&^^;pSZ>Nk@>-t7 zMX(VAkh5k9zw26dh!5p6)|Qa}KsgHMs(oBj-fU zf!SP$|HWaLf)RSpqC2j{F?wThx}5K)GkGTt)yoWTpqDv)joePZ9CqL>^?`gpK0`y) z)cYFaaV)a$UC!CNvy;9rKaP{#yPkVAsSGl{fPZSSvq?(ef^Ch2|)T!tT#*(ZsdJ>fQW0=R3MKuTytN z_L((&jNW9ftbRwXA#X(mT&~VHTOB#y*k7q1lHWsBb!9#stMwnm`MHi#dM|L!+hNH5 zvw<(v8;*r|554qm;}Sd*H^`^(lNgGex%vLex8@bDwZOr8Ib)l1&ga+pLcHYK&ln-! z!iR8nf_JzFI_Z6Y*7804E*_Ax59J%ag!)^w)5|XKQm&z*-VWZxH*)shi@6n^!pnFV z=ek~moAH1AD^|+4^Jf?$U%U=6M!Six`;$#eg@<4w(I#l*XR69vv;lK{0tW9t&$gTTV&Q{ZshlocWEQ?K0ksx^|IsS z{K>nWS^K7(=Rd#GJd1@mGxR5{k}twkWahMTJ=KYSyv=P!_VwL0>g<~+?im3J}ca%NGUqoq6_mGye6b7p;noK2mPnUFd9 zuHNOy%y|T#;D4^=897Klvn4aHB%Z}iy|yTc%;%i5o45}$$8r|DsF!)Ozr0QU2YH6` z>_4wJ0^>0Tujzftc@D4Ob(kn$fJ0FnnKfVQ_u;=W8BK~AIBJ<=3UW2B{d;STQ%jY0F?s1&I?$F=>&WtM9jTP#4$Tv(W zJ{OrA)%j+=4%zd*S1*$PMiuof`~foO4sopuPL|K)yLc;S#%5mT8)28c7}+D&axYws z0_yJWnI>naY9tp&d-ci4?9W+nlis6T0N=~MqapsmUd+(H7Z2kp^+xW^HP9FrtIy(` zLDTq7e5_ZW%U}cUMLoUKF-1O+FT)_*hu8JK!3?Za_r?PGD9*ggob9Dmy`WrHuFG9H=g}VSiR1O=A>XOr zso$4xKohwdHp?GkoZOVJ!HMXm&Ns+S>GSz|73I5-UE?@)cljmUEnl8KhGDe2x_ee~ zJN%?qnqS0NIp3c-Q%53aTpKQ|zaHJ>>}1(huTqak9W=udjCH*(zsgtPXZc&4hKcGc z_*Bf1J7PAjRafTg_-_19egU70Ex8;z%1_`DxfA!}+USqL>LL6)pN~TRw`F&@Qm+Zl z#Xxme?$6olPC+qsGxWfD$$CpTJHb8N0ngz8e`Xnae z{D?~G@+c&qjhXUq{3<_>M##6xFP!gygI%j3566>Oi0rx-={J@iK-XJ^~Qx4AxoKjvCkj(?GF?NKrIV6}U4X62k{ z#gq6ky~B`aKJVnsdW(=}?ol4inaeqI3dotuE#&M%S95+R+mL6uxB3F^jQiy8kvWpR z<`MZT{qj7YGsCxV&Wv;PUPb2RUbI5yPI1mW&3s)b4^0o*-6rrDy>&blnXNek@-Ez@ zzlyKtoB2e}yO&v%XDYvsGmz)7ItMIm*y>c;UXKbX`P`-k5K4eDaz0bb+ zFz>`&n5=&YW*~p9!5sYM+AJ)PbB4aaH>8iu*Nv{-8PsQU=GWI;AMKI-A?IdO*Ymvg zl1uOyY()0OA9>&VF86W0FpuMHxYf1FTmgB%&Q*`#JZFz%o!k`fA~X18y$9qKTn{tl zXSpFih_$(foFCKlyI{V&mFMy&ya;trRh?PVlmF&F&`d7F)j01#W=dvSp52^v?d8nP zL2BB!_kYc^kh3Q$WBEAF8GNK%U%m$i z$b&e0__zG5I`ix<`A=kqPT}nI!}PP~XMfDO(H0*g-xV$Ra@R}aO}Q(w>n%`MlKWF9V~$aml#jsxbilR98PyUWx}G`zI&!XT=Qg;>wJ-1} zN~j-0&YOY!628ULu1&7@ei3usb0faQsp_2f zedKH8^SL}$%aB<0EbK;ifBtj67uVrE zoQLi1dmXiKxB3ITfHvw&agclpzk_^Bw^cuboP#Y<5Xb5FI^XA4$o=(4;c$5rD(kON&y%l4cCwLN8V}+;G{C#K-+i0-cCNtx zq6aFeKSpz0tge;o{yEu8pHjC|FX11#E3T5?;nn;EPv@0f2y<|P`VzGD=Sy*8WEbu1 zS|_;+ABbObFP_nBhMc)2)FrVKov;j5UE9bn@U{FSXOGDqbgR4@tI))?J2~fn&h)(Z z->R$QWW0@S?#cU@=k#~<#*H}1wfw#hL*BQsybSeF0xk5i%k|>?zTVKgmCwSNn5M4F z+1FaiW6)V%q4$;iE$4k%&m}qU`QgZWzYsOuyO;CV&-(K*6OXA!<1FM%{GIR8-;P!| z26OZ-;_O_f^G$dP&GqvxFOtj1fAD6mgJA zeIVz5U%4wzbnP!>&&l(fbLwHdCZCV&=mR+CbX|VLwV`|`XHWk;_u^l?fg#9ww#_|_ z_z&L8J$Wv2uAi;WOd7+PMTh8bk*`7r`8&KT=N(^)W7V07=c2rNEuNHf{x9bC>Uzj~ zo|$%;TtogzJ{oVTPr-aS3*2#(^Cxri75Qtvi?ffMBj>%If#T?aA^Nj;Gd9W-k#}-E zavrqBE@VDu&$&mx1v39$*2}w}IW!d=)p=Lf$rJc3Opq&cS^fyi<+a?=pF2c;07LO7 z2I^nVIn(dsoWI!}a`qhTTE08J*UKFHMZEz3s56_l$`z3ra}-zLa=1fof+F%B93kfn z8;5hT1ew1%e`mRtGdtf2r|M<)wURT#o|ngB5ONM&f(P`w@z;De&X7;%Rh+Y_n_L9L z> zN&ip24#(p(RMh*KZ$Q3x&f)2LZIJz~lzI{7sB`W&m8;4PxF_F?qtIJD9m_EZgOT0g zG1qdwWtTopUWrHXpWX*(jN$70IA_!qa%VJBe})yveszN0MRI=dr&7-0_j$a!71!V| z`BR)I_u@4uApebb<(!{?;{w#zyBqm#$X=0etV7k?UAqZ4BIoaqdO5Fi{#DRxjn{Dt zcBIzNcUx)q?T>s1`}===@gfT9O~s|SSKY?NkNG@xQLe<<8y@0cafRN0TnL5bS8<&@ z8^>d%x+b^fPjI9>7~OH9`X78L4?wc`ab>}WAyTDZISc*7vVh1 zU+d>N&(Cau`gP9C&ChXyIx{xE^E`LmkasITgPwR7nG1RU^Guw>&F~iXV6Mmgiq6N#rGr6VSdgF z^?D)Cd3zqohjV6I&br4@UT+A_lAp(8$h)6+xs06Il6R^f^1I!sznb&T_LFlSPM0%B zat3^@&hI_vd7kZG@C5SwosApi8;~=)9h&OpotcU3N-wGNtYo%lK0m9UIhg0YZ!$K> zc_(vb=lRTBs)q06IhgEP&YV1hdEOV`we(!eewKHw50>kd#xcnI`xP=vW+F3k9g3r- z`d;L(&Nu>j@0WAl*-6NI))(19TdMz+Kj5-hgY%K|INt*k*|_$a+QFjRh$8)KpTFg`?i z^(-ERU*ycXUUE%TmYecHJR8O2dyw5Bv%d%5hGX=U`_AlqpPZtXd9xXPaHMOO@ZGow zUn1vN=G8E~BG<)mWT(E7^ZiuIwf*E8JeRZAo-XISug`blCcXFZ2;Rm9y)Q9Aev)tD z+I%oFqdr&ng?yO`5Np+ z1^0Z)6_MGRS)TppPyM@4MgMx#s`^!~ zjI*#p{SiOGm!XaP3IE7f^WnUTv#&Jd)#{H?LjDZnaFP09&hE8RE+lV6c8TJ=2AAo- zj(n#bq0XFN!iS+H&QO=aK=}szfz#A2@PIs<%W^~h1o?*hmao+-iRb0cdgFMA`X4@l z>+(-{0Xg?7VTzn@(mLEkeIXyhkMdl!k$dxX{67q*a(owOPdZe-LoO^YlN-p#b7y3~ z+Mk>2wc?*}61riAUR&IRztx-gJpKSD$uII`p2n^5ubiFtVtzqg2kYg08(%Ijk*jk( z{+&-mTfDE%H`;u8p!^w5Mt3}fZFmLOxIUko;Sijzeu;nOpV3IJ$M5kYm?-BvIp08A z)px4%tvyB_EO$gT^v74J^>4)>c`?Q#|NUcZdU!^?3BSwF^3nV>-^LZNP411ie+PJr8{=toas59O(r=3cH^4) zkY`{6y6U&VIy|NxnI7`o9g2U^(zWk6bMs)%88uzbUYT~0P_vC%d?=J6tF)oapkC}aWcXDp^l5>V+F0IzfO!{8^9RGl` z<^G)ari6R|zClrS=5Ef6bJbZZb z-{KZI&wu7y-sjW#2%IA48J;QMjqBt-ocC{yoZa*uz7N@@&)`}33^@mPxUVTnqnzGC zUdTCjF2~z=5jlfC$2$30p3m1JXL;sZ-oN7d&G=w0$(hUd;T8D_+=xc%_GlsJ9LqUA zT0NR`_MObX@XcI|kKr-siR?<-^jgZ(I5VjUGOKciXNSF4Jq7pc&Ed@3qp(@dUb~4q zBD1$RzmKovEf^tpVz=$vRm!M8MkVANDWIS4pH`fik=bxG@?G$vI&(7b_kTQFKl5y= zoSpp%?uEbfUgJG@Ri4C)QNlgvsc(~a^IhnUe7E%BH}zMdmHI{=sCN-^241GlSvQB@ zK^Ogh_;K`=+oGd^u1msE+L5E7dEo z)V0j_f^xnCa^{rR>%dcZt$xn1%=A_AdwLz^)8x10oN-m;a`M%fE5C#5<%WDD=i7F% zyi>l6=X1`iukpG1Os?#n%lTCGyL=NbLQVa1)UD*f=px^WjrdZ1H44kG@diEtW#ybZ zGvvd$2ws-AaL%MQ{+Uzdad;55ksas^*Q&{rF;?D+yX8-Dt-KZ=$bTX`_j2_|a=r~u zmD}?pTv_h{JdSH{B#y(InBe+QT!~HSiAVJZAm5Hxsk?DMZlG6{v*W%lKP+$O-uy9M zz!G)-s)q&g>9|vVlJDU`Tpi!b?fHD1gQwMn_$3~Q-g0Yx2HE*<;4d&4_q+B1&&51> zJeJ`w^$`4lTh$BrEZ)wapcW2M*T6|~BmRa*@lV{Ef5ydFjdyXpes=82TtQujzva$+ zs{YgR4&19g8J+a<4fLPhOPGL-sP0}6= zN^Z=naI8Fz3nBkq;XH1L?5WvL_j5hp9o6*ikUQ`I+>BGO2qjS)-O&u$Q?q|%mwVWs z8L#&#*319XyNm9WvtMSnDx*wI0JdFj>dJ!JDd0E z201_X3G$ho*)oaq442{AoZs2=a=G-7_ao0_et&uA@~mc0x<)@|KxSD5Y*rtFoG&xg z&+-q*vs?-}<1#1njOE?Sdzg9hJ5S6#{0nmSnR+Gc`NJmB>4KE3%7zpw4@r z_vtmvMNhqx@Q$4G>s)!coM-ra*!~3A4dkU+wuhv2dz3gkPkeO6k zomn?So*=iymvYXa4>3x;g@^G;oP8sEY%#epF2dg!>-x=TkE^i}LP>*afBB@aUb48d!zEkP%_8js=r=!rkoP0$}Z)j4Ob;OXjoQ`Y5C>Ye-} zKfuj-6N<~NaI;(jd$3%+mK)+T`Exuh=lkPF`9o~S2poVfk#D-}@N48l`C~o`=OE|O zB#gtG>Kgni#-gFRDtF*2T!*Kk9qOv*^Ak8rp2bJ==g7{N^P&dUse7Xm=Ho!Ug}fE_ z$~jL4^PlRj@>2Q#zh$<`7wi9qN994h3vc6f^)C1$`@Rudb9U!0dO5pVsMpI)coTom zJ9sePf$!uec_O#L9L&R3Y``)ULuvQC$j|X^^uXyTis`uAwe0gnz>@=tH zC+fHO5@e6rWq4Ztl?(7hX2%mV3E9;yJHaAx&K1F zYw*}!Ioz`E+b`e2H!2QAW%+FV_fS^-Jm1bA zp*DJ>pWc4%JA$88|AuGqnfg*ZDObYjI29xGs^K;id@&cy(| z!}$x0L(Zq={3+I8o$Ft5p1JHCGdO2yea@^q4SkSjq6cy&mT`a1<&B)5cV*7+{zhaT zf3BCA_8PJStx)$#&Dlpv%9$qz;W)XKUY^@y)a`jRW?+JP7;^sajj>8U=SWx1Jk6Z! zjg`prcDQ?Lx}NuKK5kGi#I1M$XXx$6?a@;%!>#d{ya0L6vPagGbEcMe?IbzB<25L;eN3ak08Jev}uWF)l%AJccT+6~fJOX6P*WW8~Rfq<1z};Z9^P zo2=gqwd6iRXz+q^u|C8%q%U>Yx?gcXow?gr{u90A zk(_g-Ag|{oyakhSfO;mLl5=hh<^FgAwf!@<>YXAN;J@%7PDakxZ@C=a#x&fI0`7f| z-$5PuB%C6@iLrQ6eJ$_ceE0qjAIcx#Rs4;6v@3FU{Y{+x@NB*Da?a(K>+o`M{&NX zrpWmYdcn2D@*lWP{u%R-|He>{E8}0+N};NJFaLse@*(WDedlWtJ`m^NMAs%F-&)u5 znfM>FgBIgXXsgwo@5d7PBXpJrqlcWm?=G%~>~2QCn`x&+syX6m%U8b8l z``podlU@V)KY4|GJm1C_qM`gg|HIjbw()_Og9~sRuE9g@*^WcyDwr<6&yBbuu0wXQ z!8`)l!Jg;Fc-eh5_+WmD>tceu2${dHa^}Q1tU}(S>?76WyyrP<^Bx_Et?HY(KF&sF z=xdzcRp#q$*o>a&jeqg6Kl2GDV?Ija9Q{0?hw}h5R1V~;aFl!>|H*xLf8^}1q~1?H znKQRkeOQn3-KLFW3B7i zVa}4*$i1-+*(1y9^Yj>zzKLoU61`E_q|tL_*A|8 zm2<2newSZB=2PZj&Zqg9iafu^>3=LA%U|IVc`;{?y+_V|_>H=U{0L|Ft}54;^FBW> zk3&^;&hTgD2XH4|ME3f%{G;oiAv1FkM(bV0FCp*d3+mr^3@^vexK^FHJ%~?KHw@|( z>IQQ5<%#?{cInOKbyzNEeifFt;AZ(!J_vbt|3PNgM6ScLco1jyUBS=sC|-p#<=?ms z#$q-G>Mh~dI5R!-Xt(+>{DdR$fL>eP!sW3Mv(%Y~9q@*nIoXlB^8&qx<$SZ`T$-xR z3@w8-n4|uK$D@GU6yM61apqsXo3axo}3(EI#=G8n5QRh43Q7(o? zXpGfp@A@zv%1yY2`*M!Hi6`+5#=4e0q$S?Qxq8cRqMUi0Gb}SXyTT^@oI5%1vzwPw zmsOX*Lj0}X%{g!S<3e>S_uVUBjSrDsqZ>EJA+BGCll5At|B_3fu(}yPu6HDIwq3z< zae@9y43nS5Xk;HfRxfAe`#clXknfMzU9XPM)!BpVarU<dvU5z7rqGukeRlh%Z1n)J7Rh(|?%1!2Q^X z8}za-cfg_Yi+WY~N~V4L)CM8nNsn_a6x08J$MC0I3J1#BAG61wAy?t0JOUM6&u)Js z&%=9qL-}bwgR=+bjLz=UfQ#xq#KXC&UL`(BT?3`?D_ZMyK^xcmashsqvnN#M{Exqs zPWV3>>;wGE`R&8_pQdO?Mnrp4X-J{Hl(dbE29;Gdp=hb>A|sTBN+DaB4YZ6xh@zq> zGqWOjUhj_MaX63jeO>pr==Z(v>vP@l75EC-Vc*qz0Y%g&aB<$n=V6ar16$Bhy`Jyn zOZYkrlE-5w2B4wdNN$a@(Lvo6r{EU#Fy4w1xCRUHGkUw$0^2b{J(uV3?`SIji&nTt zJ%%sjX*_`Y@EWW}Pj&XFC*^Wzq3+2A(FU*L3w)@52lmRJVYQt1#qRPnIe&LCm)}u; z$M0b?N@J8>-Y@Rw2i1*{{kRH#laJQ#%=P(mz8BBRm-6AM ze4dN^d2`>lP-m{?T7Xz~gfM zyqN=?)%i2!9@vfiymD{md3>33ALq}IXKxQy$vIc^z2~uXl$^y9* z>Z%XKGC8|gp1<5*Q`P4n&)d5wiZ$xj`F_-pU*MdRIfomo%c=AIf7Z`@&Ha`$X9jYA|KeJ{w}a)O=!Y}a`9Aw2 zyIr35-g=q41<_KS`IVV{2ChZs#75ULr!sGHULGZv#ct$2%G|m^&b-*cc}DZx=Ugm^ zkL2roW+mFm-=s#)VE^vhMe}^VCg=G%2bq!o@+Q6KkhA_VJ`!{Av1_eyyu3fx<;>X& zi9_VfvCN#ln5y26%)tg2qL+D)`FIRp zjO-_usdFCQpk6AU%zd~JD#)4ftN2Rnr}rQ_$pbJ^J{ozC_(=UF9>);9@A0;rvu_Ye z%WojFVH(O}IkvfW9%mnTST2EPxKwW(e}~M{I#`0?>J!jR&OFY!+#%QW*5Gfvgj4js zLQ}k^euTf^QJi_0U1PTTMEno^aGQ88&qDT{HoR5uOKy&<G%MP@ww|K zawYzra~5aD&6B%hv0Rh0bM%#8mM0`2{ zl>bBaj>-HT za)w{)+88-|>78=U_Z9L=xr}@aXHRIv^U=q(50Uq?yZJqwi(YsOKl{w5coW$%vmgE{ zkHu!x_L*XQCb!~SQBrQqzi?MB%$K7Wu2g@{O?f=B6W_-3_0Hmn#-oxj*Rvy*l`*^x*$78HK3we*bL){s*aFAXF&hUGYu8{x1U3eSq^;Y9h z`8@b{-+o?RxEROzb+uzWxN-^MYukn#Al%p{!?#7 z-h+3j*W(%WQoaj+<6-pFtA}CopZp}(ZjE;IJ?lD@uxhfAt6SPMmpV`Ehxb`jbzE)b@k#AJzeK_w}AIn|ztKcwsFJFZp z&=M2$Mj$)bC!D?O0sSiS?L3Z$;{g5a@hj!rLwWv|$ocaR!l}qU^dg^te_YEwnEUhp z|DI}wG#wuhsJQ2C?^8JmwFmzsaNp( z=#K)(cbQ%HK<=Ubfs1noKAVeTo7|T3oa7lgPrVtLMSGCvdpR=Se#Rv}b36CtEnE)8 zy7)>c|US zdx;C9g5C!H8`)tRxOR=4=lx$yl`|_B<6gX}z6Y71N2+JZnW63Fy4(|6<;J6JvkIoJAfbNxH`ME-!Yw=b3V%IoQ+X%K zAm>9*{gb#VZ%5|*4|otIki9+c49Cg)W4F&-h(qND7>b-3-|#!gT>T1p2kWiQd6##U z?5um_qOL8#wQ_c$Ieb2DMj!p>@P~W`zlI0UNS#?-M9w=$&hcCIMym(nKIET^&>EMz zR)J69PxuS;#w2w$&Ym_xei(PFufRj{(`YQ8g~Q~_FjvklRaMT;e3bfbc{a|MvwO_s z9qMZ6D!+hw$UanybH1O#yIdd3ck=HjAveKgcuajA9>8t*6Ibik#hthW-Sn=+YYWVS-gtZak}0wSSF9>ydOL$pDTCgoaL{{1MsUl z?_JaQa&>m#Bl&*yJg$Pzum#tnnf~>tj8W<(1_*&nhK?Nw(-yh?8bdf-XDe9;H+Ay|!;>TcTO(OJC^Yvdw)s%u^OBRq@W zkX>y*J{bkkR{cD`h3qsR`?aOz>iicU%%^cpehb4;2uJ5L$lr~$;R})X@s6&wLKAiN zs$+P7dM;1m{9QvOydyu2hWgq2zLe{u5t?EUCZL^bPjYsu{C&b|c@*;hmCs~;9M2CU zXIq}roEtd@GB$9G&r^ z&*kU;INBkzEkCP#|M?C}Bll(Q=lq#7hx48OD(7DQgY(ShKF`l_sycIWBIoCj*_ZPv zb38xuvYdN1-+P|je0O7z=dcD}gs**WEoa`$mtT@U!4x?^!%>|3t^yCiO4M~d-%rl8 z+@Iy-6=)$}i_W_p=h7jS062KgL0-*W-EhMXCnnNbu4^>QX`mUC{5=4*K< za^^pVEy(lM7kQ2|*Eh=dAos;8o{R%s8;H#F+~awUf8xxU-0u_R-0$1uJg0f4nxn5; zd*58jeV;w%OU}$5#H(D(IaP_zQD<)CdHDi$RCo|#^IeWt}&KX%* zKhNoz$jp0!GjlueShVn&+juW}>SxwJgFHvaa#5UtJa3z@5lz)?T+bexvv`qSX6?(! z*^#qs0cYmSz>nDE*IbRv#NqruWR~T5FD#EhRrOQ3mS4|#zTW3N`>pXkvZK`2Yb)=; zPsj|*J4`?QrE=aOzT!FPgUsFYkTc^n^;G#jE`Vk7FWeu;;cA?v_cSv5@5OSx=lE}A z-rUDIZ}XnCU7hncXGhNeZt~eEFJHZ;%1%+Oj~5?8tQJnG8x_%Jj>-t%(q zeutb_Wl#cb&;tvR89CGS3(#D>ge&OXAa{|6U_N@OuS52Oyl-XR|Ct_lM1Sm3zlr1J z?4&vK4p6t}#+);uzWlR%DAvh+uv(sn>=2)-^G@?JchD=r=kta9HqTAZ^(yM&@=P2m z&p;#jHQXo{^qJ4)r?6Z741di7_+(U)PvITth=cGhTKjcBy7rfR0_R-3i5ue;T!weB z8E4}|6hT#d>$A^ex|}nrfqa~N5Dt@nz#H-v$Qk_=*TXvfkNG!jkPGo5jF)%tXZ!^| zkjEis+cv&UZzNYh1010q&Bc-PZnpYTzLg*1D{(!t*PfvF55}v{UssRYsMlbyJQANEJ5ncP z&$v)s#`Ww;U6K8yl-@OZd4DV}zk~ceL3Z^gd*0!-eKxT<;C(#cvWu4cidCwUr(y{zyH-(uAX=*TQ~xeclk?tl3xBH~!~da<-ht{_@;FS8yYdw{ z9NpF5#dup@jYH%rxJ1ql*p0LEROh@q{Krl3ug_eBNpe%3%k}wvRK#p`Z~hby%ID(; zJcjJ*TlqKFUc^Z#i`(@EpfhUXA-x%V1M=U?{h4R&9BfqQdwNcO1Z{DzdKKsXE-AO> zoSC`jW+La->%2-Yf9`yrv()(x@(kv_$TM<1@*TC|b8xe~6Px7Bp`4-lbLRQJ2Q}oJ zLzxd7)Hx%6lsC$`|H|_F$T`~_`L0(W_ihi)eV=)jb0FW{qsVueIlGc`&xreWp?Bmw zn-}0{>{kDR7m;W9G`(IpM!ugH;xQbdeuEbw&(43`lV9b0$Gf?O|48nqd?)WAds=2v zzNgHUJTpV^nfwxl;TLu8iJT>W$(g_T&bQ%d{oF@4%1@ydwjj@9zVpRcCQrp$`4z6r z3wS7Jc6Gv~a(O%k;scR9{;T#?^KL-~7d!P&dM;yZD*Ugq3^$eEVCqJ_K+9WX?% zBfh|VbtT+~CF-1|nFZMy2di`LELUegIa$s;KM6IE-R4$2gugLb??>c)BkvBk>(y58 z;dQt}J|Ec~wyQsvk4E<5^Z0SS%$l6rqtR79p8w#I$U8~qL}$IsuBGzbI7)sE7s&lF z5yO#L`4n>QjOUy;PxxG4-oPIqv*%w-kuOBvPo}HOp|rXmiekPxyJq%*W@w-^LFPtoI;+!qnw-(C%<%!rX z&qdDP>{u)LKumWn=ht&QR-N7QEIuCFP#BAl^Y|^F`4Uee?`C~D@52kYB}T4 zzb5aY*T`?mQ*gHY9v^^Cavcni@8nXMqA_@%=xrGe~qPj-|>SOEZ@TI@s-?^C-G1&!)M}W*}waC%|rQW zMRmX5Kk`(xQt!oGa`uzid>AI_Whc%*+3E8RR9*f8Ifp;zyfgg4CnE3hxBE5Ocgm_i zmJjAK>YUf-svkuuwATNPYvE$~P2~M}2S20N1SjAS^wR%{iy%8{W1p#pzttCGr`(0J z8x`Wa)%WrYev7MNf?S`kz#924-rwi<G@IXUw_#n0~&u%=nj(@3Iw+&|ibG$a$0b_yp(AobUc_ zWJa#l%lG{ddBNQ3jhva2 zxt(+TB>8OQ%*g$o-C#Ip@5>yU=yO$(v*KsY`CU<7kKDuk_3~^iozKZFW;wLz}#Qxk^{Rh`W z&Vh2+jI;DN;dpdIp0U5N6M4R#@|o;)nco|c9iss6L1yq=9*XVAY}@Yh@AGTC6h5+V zR+QzuZ!FgPQ67)dsEJj03g^1^9Bz^yLC&{N)hDBfx;X!mK6mB|c?>ekzu-&tit=dA zjGc^yxB^S`ujDEC72m3#M&@@j9v5SZd=4LsQ{@xzsaz6uF-TpG$MTz;9i+1V{z-h1 zUe1i>JPwuga#k$Dt#WppgXEH!gAb7z*avlyz5Z?f!DrvaV06bkT#ox)JCw7tej>ku z1Jo7ML*DNOan7+#=pw&>>`aIAGpHuF;YOG#zmA2-KSy!q@f4Ijbje=KmW!&9Cc)HF81DEdN!` zxpM$Nfb2_)TpJ+|!FO`@g`B77sk`zW{5#IUeK^}^I&(uF&C9S2wb2O==zouUW&iGb zZ+@0@-rmX6G`Db1K8#PpI(Yy-Nyd82#0EU0{~LEg3#?H0!WN9gDSG2M@0YWF@J%9@{cHlx76LZ43@|ZP+o3~TjU$~ zV|*oNN4rVh5BIBY#%%0Ww|4CVd6%3WcQ!wyew8Qj3S?h+R(%9&sq-#&Bezwbi9&K; z9)?l!A((+y*s7Nuwx#^DoLys>e7gK6S5#lbd7r3*()z!l1pd>n$@B3SZp04s)XzK0 zlbm;*u6&vPH{22J^?p!~l&i|W$nE636K4;dtFDY~dKcqz`CC4Vvm;gKtJJrlGAiI! zy;u1>Tr7XiJMfPDG`Hd2+=93A?K}#t{C?%t4dwgzF&@s(b6-9RpUK&mFOw(8+4EP+ z=cAVTPMoIqrMf+iR-eu%pu1d!yYd7+l$+u%`9f5cU*kG_8d{K?oi{pCE92V)VMtFOb= zXp4L|IiHV_vvYi}&dySpU&bE&p~wuF%ReIXdo9|zzLG!T1>A~n#{bZdj>MPp6`beh zCN9JWaCW-PluI1d! zv;PnNRcGfJ#E0Nfy|?%Qu7`_JFj?<3ev)%WW-nc?z6ZTg3b)`a{DO+8?z1I1XKri0 zO8qJShKVSHjVR=LS=XMGr*IQ&LFQUFJ_5V(gn9&a$akWId>YT;({Y#Ff-gkQz0BQH z+(zYJBaQ z*1jEhJ^!bdvms~Ag?u~r<$*X#F2}R63mw$^@e*#$3-B50VyxbC_z)kd&%kz6Mb5OG zZF}S&Tz?bU<8n6STrI(eA@2)=T)RSk1-&pJdd1xNBhi~_(na2KSR#gXpx{EBEB0e(%2WQhck<`{ZW%C0wlD!N;Pgd>@bIhxtnWl3zw2 z`CHCj^&oFkui?B?t>&8Q?7r8_c}J_tL-bC>N%H&LjuC z7vjaZRlXO8$=Ok}C)LAF_3irEzrx%F7xxu zeUR@U_gC)kv7DKad**Ncgfm-PW1PAU=ljV0m9rqT=_}5ky|LZ{ob#?YPgCE6{5&&X zGIze?H<0IGIA@-`j67$V^#kSH^QX&0_+WKr_)=s(=RVGvm+vq4eCApy`3Gc|%CmL3 zJQMj@&*#jo>@HvOpU8dNp35Wm(iG&Z=y(TaQ0Q zGfYNV+E_LQ)=J|_qOWY{8@Y$Rd zc~8juMh(4RTpPl9Kgj%hLSCYG9Nv}x!g@J#>3hBe?c{ab3%|)9ad*zSlpScS{0L^r zy^%Afhq@Uu>ki^PESW7giASTU+zpq?-(oq=MrQhMzS*^$Q<*0@H*+?2mY3i_bq9Qn zZK$ZXhs&X#d>=;QT9n0exWKhjIqyzu`^Z`FA0Mheo;Py^ewwpi6heJ<&a_|oDBghwFdLWZ?d6j>b8`@P=gi<^ zaE1CkG>|Le0{J}5lQVbc;>n;sfnVj;oLxKrEXFSN7S2wV`Cdjn17GRQMfRN^d6V8@ z$Qk+}f1%d|AIWzjXV7i97%!;TaxLzSN92h-g&*XrxCQq?&esL%&#+v*6Wj5q`c}@n z<#l`_s$!u2JA5lwN6wHMT!(Y6|05rc>rg@c9$$-6a^7S7yYEbVnAi9<3;9!C!LRe- zC@de0+E}Ze!FBm9ydYp=aTkrw8B0I@cKEbuu zuu|@c>_+|Z26~_kM(OXyBwUQVr{rBbXZ)?` zC<@{&{DnhYyBFCfKH==*qxCEDxgudrbrNDe@M+8;au>$gUAbxHM=T%Yg4^|&8J^@ib9T&Uj5z5JTI>s_h7Q2hrgsoU~7dQWrq zxVAW5o`)qk3J1FW8+OTyP*%?UklFg8oO?FULl-&!y|d&^oO^eqoO^CEXAk&6&i{9R zoQwnYbALUA+&?)h^W0<>oyob6^0OGnIVYNO?$6A$S@KW#7N6?nnfoYK--z6I7a;f4 zshI9sZOp)Wv_WUI@O$O?Dj?^%&-}?;d^x`cXCUW7?!}+=|Ksd~wK>meJW?#nO5`k$L;DerEXXyj(BOO`h?7 z$i4n1_vJ@0SH7C_{5D2r!yCxF+m6ZU;bu9rE;B9Xb#|WHF&Lfn%b~ZN9WpcHI&4&L z#j(ijyGAejxPSNUcgL&WmNSbo7Y;&O6jJ9ryGFhP*Q#rx2lDJM(91l{+?Xl9kL+YK z)K%pxc`W82b6^T?(a-b$u{@vi-jbQL5P3$A;0|~IU#Tl`JzR#r)rHYRJ{(8L=W~5t zhS~Bt*n+(0^XNIb0Dh4V=dY1j^B`xIX2&mr zJ#ssA)c5bc{pTvqd&S+Hv+o%fzLQ_&e#kjeMm-C?(ASeoj7~y!TajN zIA_^e{I0qfN+9nlH|qT*jg-57QkSFn}K|Ybo zxR$+c0ROIjjk~FHe$|z?^O1NNAL9@V({G4Ua%CQfujPk$7@vZZva!#M$3i0P3 z|8epk@?tzM58yX&h5R{=m%rx<{42l82csj_tG_@s`9lPXR2V;i(D;}2fz2=$y8*Rm1*n*rF>+~`! zGw(9T^Zoqe+8S=dE%e9AdA9qjC*orDCm4f?$n&;>4@91k%#V8Lf_x{}apqT9Uc{N9 zKVh&s-$h9|bFn#2K<>|PInPg?(KZ+%H$%>>+&7QP_3#VwY>eZ6cn}+LgT59>xo3OxOdiDBk>{`--^wrY2`DFL9u>wd$hk9$^E^C*Jfk_EbB1Il7vjwM`rHFG za4VMRtwg^2Gm-oL58ln4QBA&q563oiQ9sL>Egj@ms3HG=1^7x`6>lK-)&00e|3&2a zKABf>?(Jic`*|sHCQswJdY_^wW~%$~P@axz@>Xm>o|htgw*Fe~#d#)oAUo|jdM}_9 zMxhKc>$AsZR_u|NaAws_atq8sGvs;9*_LzqT;%@0(dYg}&ecuoipWlvGxlJ)l51z+ zbX=^?vo)O;p$9T&*6Q7j%-}!LlMm$V1Ru$#$u0Q~ejEklHn?5x&Nca{^l_8AApe5P zdM(mvzzCvJ3!r9e*`jDe&*KP zP5(@JJ2DfqQdKYI0zJq(If8?8S7KW=coA#op`WBwV<9IQT#8mlf zbd|Gf9nPiIS7Lv88D};%moLS2n2pTtoM-pBmUp(*JP<|X%U!!!F2}|B63#!b@PEku zocDou^>W4y*881b#+~x}ct(DW^Y@^~%h_oPs>jM7;|ci-+%G?XdvK6C=ltpNo5XWx@BvEWLcLG$7nJ+X&CNpI_ZF%vA!qjhy_4_*@*dny z|6jDnVxk!Y zJkHlUpD*Cb{1SJ?5cwhG?*;O%n?1_E`}Wpv^k0?V#$CvcGKMSg4(^17KGO$zUpR(O zMt*~*d9j#xhGE=LT@y#h>oGxI!P%|;lpjO(o6k7!llO5QY{eYp9iyAie}L=JTJK6M zz+vht_y!f#kMNVoj{H7X##6{UMI$~owaDZ9J_310@~rm8WVG;meyTT9&Y5-@pRCTzYbtL?7r9&duIHJ!TfIyDBRAvI_$tZAGv`5G!2cjS z;;sA)va9@rqVm{$*0mz?CS;Cv)%#gqjcv&HG#p#x>b#p@v=f&Op3v%Dh z(7%w+#x3$PZo|FtEppE_(93MQfHON^LZ0{Oe4o#pfWq<$9$17HxCV1^2X^{Q_LZ&j+ww>}B6s0a@QIvx zewTbX=R7Hj!hX$K^-#G09+Ru-?d3O-eehU5-?i+~HTgZX(ksS2k=>yoU!k{~H{wKj zD1MdqoqIlWk^Bk%)vv-;`F+m$@CHvocC`825?8u@DHr4x+z2l3+dcCBHcWjyvSSUx zD%WzxJS=CH&*i$jf%~Gf{3O=lCmgPKH6Mn|@=5AT&`-Vr6>+TkXkLjgP#@>&?ZHU- zFTMq5$fxtOXe^J%JUQ<_)8(8~pCRYZVEvZz#TbECFb>r)3MG7|q|bbZuhj+B**PAU z%jtcL`{k$5T0Wk;;s=z%r+UZn7(6e3h5xV`3o!=UT|0y)@l*UKMx(m=LyW}^bqSQg zrRwa>TjXcuC$JnptN%f3`8n>08}T*%(7Ti8^XI&W```!pd#=h;xHR^YufgYX5w67L z@iP8IcfBL{F&@nwalJf*55Q76=lyzqTAlsxdCrb;gkIizn(#BY5}Wng^Cq-{fA{To zdDs6|aj<#;*Fqh+3{I18=Ik90a(mo`9eB;P!rYwS=3dy}=Q{8QdVTODvL|L&SSkPQ zT4mnJ74Q`n;}jf<&s=+*^FB96ZYr1OBl$~yl=tU1I6Gzbq~FyOa1sXVSHb0SL)Ws4 zc9xgpDE(4=Dz`)S#J6}V_UdOReONvVl`&o~JL7xuK=~?ufWP5OP*JYMSMg?)kn=+w zM6YroWFK0geoJn|7a@CDO+HTVFRq~O#(B^iP3c0w(v6K zXPFtE=QMLab1e6BUGzhqvu((ClmGYptS>{(+B{GBS?7CvTW*aOa_*TttGVYh4>NnG zah|cV$ZXG!a~p3)?v2ZEiEH@|@*L!QeFF3K9^i|3Cwif;x(Dy!-}o-HmNNqnLGGK% zJR5m#FW}7X+`B8~BAh)gXJ5{ii;?Fx^P(rZ%A+wGuOr__zT=$5xt9v)_2Zv7_v1U9 zGj)+(_K;t26EbTu7kVT2&l1eke};3WG?RZs_NdIj$FTz6>*cx6d_P6bebY+57J2qE ziyz|LL$B)P-p=zfpMT&Jcn*djJ8@I4gkxO$ncHxl`R|cu_6%hI%b8V59_RYId;{OY zb@({W^Kd;X`drT22J!}sa_v0MUYX~$F!D_0K7Sf5GE z>Q&*KDKGGWxJmAa_wgA%&|AQn&!^)>^*Ov0v+xh5qhNVpV z&>aVcgb6M7P6b=on(?;IrTtv zl|R7kn56!c3vu38dh@Nw&hV@L3~az^z2mT3p2FYpdwdTX;bipmYmQ9K^?0Dq9Ed;V z3dr7krMd>HsgK8-@_Md_w(@SCgJ*H7`W()gTbl2{dcFVfk^CjM%72UZBD>HU{StCx zl)+GS-j8!GpD)+c%MG4)gImRM$bRz!=kE&&x^^jgVlO_&>6qyHQ{01}MBc;x;B|V- z@wMCmc_%)ayXa+~`bIuO9?UyXA5GM`39}2lDZhpnv_|sd=zv9Ng+qMiPS*A!XM*B>{1uy#XN$q;VV#8&fhy+ARj2_=axTfeioDQnqGd^d4_UU zXC@cs{P%KB=VzGzZqC3toM&(a3L?)}o`d|%^1M7C=jXA7Gb3)0^E1c{eo@YuoA0v+ z@=Rq`G>}_xo`V*8gZWzCgayd6liAvzGdFWqKBCUgC_jVjTC2D%Zqm!1(}iDAx5HX_ zE!Rae9F5G{!}uB3@?B=$|08F9js7bMJ4EvoB}A_(jefo5-0bnWJ^&%&E+|+1!b9zvNk1g4~;Da?aQ@@VfeMJb^vx zKloa-k(Y61{6-XzcVL#BIW_~+)HkA_{48?j=GnhmOG-goHJ<%>Z!AfpTM2er8qlL=HCGMC|oUH z!?&U;a=u@ILi#@;bE~I%IOpu`$766L4#6X?eacVp$Gim_@sm0;F6U1r+>f)7IaP>n zcI_&@9ytrYQm;qez1DFB6m#tuZpvl3D~e+&dSRP>8LUF~lX?7}e&+e5sE(5AMLd$T zvrdthVm|KG+sa>|xtw>toM%(jIkU3==G=W>o%hBm@-6aZ$S#pHa1bAz8rciW@rmex zFYqXaVkD{spIOO8_*B2PmwW~<uNfxcV`NA8O@A9M!6vNN zAA-m60}A`hYF_QyaNfx`A-iB@J_sM`H^(nH1l{#=hMdQHIqwj)<;QTSdM;-N%RW(4 z{Vf{dbA0LAzx)nf#0To^ICsgX$Q}4{j7A>PkLU(u{~W_FqYkp4_pB zl3Y{%PQD!D)PJFm{1RWqdA}GTuaujhy?hC>|F&0;#~!?eF33ATXKv*)dFOgS_V2#; zz$%<3#d*F9D=S=27=0x_MRrwr#L3Y5Ljpv{(vP<00 z|M>hlD2XLFOYeA|&ab1bd?CilyZI5mnup>*%uxTwIeWLu8!-!+GubP%vrSgt>N9n? zEY6cRVGgFMyWvCRIl2Th^q=6P(JA(O%+{NTv(Z&Ad(hK-fjYDBI_`wb+P>;(D2~(B zFYqGdImn)Rs$3MexR%+O9pFdxjdFI-1w0&?^@nqB*Iq&wInVyJ_+C8>*(r8%Q{0Br zF$CEo@8g`ocXR%^3A5$Pcp~@0V&oY)1HEw{{zY5AzJqHw=Q@VtKxC$0#5o7DV`Y|q zrLKpKe%%HXK{IvEkb5|@_-?(zypEs3Ggyfu^;U6q`gL+{jQ@_zl;|7oslyMmt=F_t6jYajefRLnV0&vbVJ6=Qw9a4f!{;S7-M8 z%7eKD59OJhv-S?WjLf2+xU&8y`~m0Of0@5PQ7qAamLI}G^jBYrBjlc3j(_F3d?y#c zhjMm_ycayEo~Pb}fylWyPwzdxfRDgu$ewfc=kx0Fcuc++ zIkWqtH11O84EdCA#8GwiAp3XrpgQWuU27*dl#j&i>XUJh{0OSbAEP?%SO07*`}V+Z`3+1*A^lT$DQB0eAZK6yoGb8Ge2Qy7@MwOVZ^qAZ_V2&s zuhB@o2~FkfUTgdKRTisFq1gnv= zxgA$W8`lb|7t1+6_g7~JKbNo3%lmZ``A_+2d@CQuOVLLz$dmX7WG_CEAJ!Ym`>R*- z!Rqx~iyLwYz61?iZ^akmZ;Wy6G)$5Y<(=G-vlG;p=gL!%9qnDd6g%{fM)rg37KP-l zxYe~Uk$3qb?B9L+;9zdAcnXKg&AAl{;w5!!d?Y{53;3U8)Iv=Z*B^$T@SXaB^!zun zhde1)!AZCZg?*+XUykx9jc$5l@v2-7b8x46IrrhK&;Wm`uR-2LzvfYTry{%5<(xlI zO?ne&;aPPlyoK!0W%Nd11+p*StA9OSSHH%CxCt7evw9$&lpn+dIeXJZa^7<{tG|)Y zO)cj^$nH5nUZb3VcjOj&T{(NhSpE&Kx%MdEfm`Km$nG*xeUsb+IWwknXSBr{*E0Jv zw{s3(re1|l_3{kuz!o_>Q3H7#Rv_~_^R2Ah0&{%kN8Ex%I9)H#OrFQ=ZY}u?bu*rg zoH=LmWb{Msq0Gw4JQL035u9f;&rvTPj@N(ehF7YU;Y0uA2V?PGQacOzr@9`M1B!x$p_*lWPi!*>!M$hkH!Ld5AsZ9 zFUhWv8S{p^r~Y%CXFj`1=Hf2Y(EAU+$iw(RKAAV59-65OUZ5oaZ<@=p^+s z$jm=V?+e_eUWPU}3cHb+^#q2?e{*-NmH*`nxf*Vkr(zeb!vlJaIOjk=Is5Z??u6q| z4oABF2QqU$#|*uLcsqW<+sHmKSbv(Ff9}S;n6ECL&&Zc@=4)%tJI7icgLknS*%6-b zxeB;lo%84%{si|S?=j8!A=gjB?{X#n1KBsSfBmTUo%%r@#c!d5+{U$$@+wE56-~% z$hnd8XR+Li+wsN7F7mVb0t`jYi64-6hU{Lg_3p>ndarP443IbRN;Jj}e5f}ASIgPW zyU97ja;|4jxLSQIy5In`#b9KA%=^qSKC?tUjwd7g#cf;?uj_ZlwjeP_sGxlW^6%AbQRPnhB^()JV^DC%{Bh_tPt0b?H+w%xM8rR5Q<7Rn(Zj58)TX-SAfGg!caSy7i zUqut_@ArI@HzRw`OT5WvF6Rw+K+f)UZ+a-DcL|P@>+>CGD4)m;@Qu8lKgJyST(02P zXGiWTpQQIXKh0(Iy2ijo%$y<=~AZJtNMfQqk)Ol_<;08Q|!|QL)S8MTFO81ulzP<;VE@yUgkqvJ`PRsA%4M&t`Eg^a(2;j zav5Z9HQ*V1EZ#-t&KtN9EqwMHba#rnC??E*@ zqu&Ubad~IROnX)TH)I}Xu4Pw!PW=xK$L09b=d!=d;v3bYF;jj4d8c|^eYU&`nVmP_ z54kKl$a!zbyvq4>AO4ZwL-x65{0Zt~l52H1d*%83kUD3=Hu(@f28&Tlow>P~H*?O< zA=s-vfU^S~#`AH3-e|lpU(VUxipvMc{dg+hjQdc~wZd4V|D!tRV_~_MdV7y~-I0ICAM#iDOnwt5$mie(IeTIi&W_qZ?_tbWXEzzn z58|zSo)==C+y>+2O32wz*=KL(&-E`uQ8~Ns34AbecDL7GFQ0^oI9Yu%-^FjBo}9g4 zqC6ABF%}#3DupAMkgD7Y<@CSC zFu4zE;a2taI0@sC_sA3Vm&*gWGFs^!q&`%x${qM&l$4)G{s%|VrFy3#JJ-8>i{4+D zif`4ExhF5dzj#Mo1cT8;eLt7R1o>vZGN0oy$o{^8t8(^@)vo1z;Xl3ZD6PJj3u2Ib z39_G_&h7D%{tFQI{T<0JVTC*lRZtP-ak%~wI7>bScj7Je#dr-bsUs*oq^R z)%h_Dz$*1O_z3gVEAb!xR#)y6~>!}awW@CfdM&ib!ng4`9E zSx+MOSN@#2?{kLr<*W4a{O12N|DC0J`SWD9&Xn`B%Fif!N9Iveb>?yI>q>HFQ2w7Y zH`>WLKQbH7)Z2-M@&nk0{2B5y%RRLb`Ce9VGvsHMKU?mr%$dzLuQlD03@w=^*}*Z$UHUKFB>fAK5jtoAlRz8?Wmv<9uiN zuCmi^lV{6$Hgd1`z$jdZ%E`|cFZ zo|R`V=h{R$JN!bHY zQ}O?3v=8tv=l1{OH%&!~24%D(4I!bTT^dT1ifAbrk;CKeGtL zeYUH|1NnGYZK4aHzZkW92PqBa40*4ra@=bUd{c`WaS%lqC@r|=V4=2}}m9`!Ixon3!5 zZ%|K0Y55`^hk@!Obum%k-bbyYeQSg2&X?;C{Il3d)^$7@vt#up0S$h%b2_y14#5U&yWa zAufl_n4r$?ccz@bi^%&yZTVVcAKuFy@in&l>;t%4{(yhQ1o?cFlW)dT!{QV#~R*+`uGt2Q3L<^Y#G;g@)NjU z?>%(K#QZs!ipA>e-?g|pw&|7S8hS(JyXEYP)8z4ZTzxyggEDe8+<hw%}(TwcnTB6Hz&b!O_Vcp5A9J8;g6IdTj6Ut~7q9^5OB=Imn|IJ?6P z?xdglrZ#7Xxj|knzsIlRHTiTNfINS>r@unZi<41PK8SmA=IR@A=G^5dgjISgaig4b zKhIlsuzV+ZCOTsw{`Q%{d>^LBeUKeAXJnq0?1ZoCRdoFTOqbh0d*7M3g}=a0xZm~X zInPXH&T*V`+;3xfCueq_j^6l6uL+mo@6cY(J-3PH;zYDj=bk?h z$05%^X4Uz~?wB(nvoFudXMCB@m*Wr6LmtJ;Ip@T1&V86?w;*0qKgb8*2l--jmS-R{ z<23arcpiB!o8U;-GNTXX%%#k>7W%#UNi@~ljqQVKj*zT3pXS0Gu2U4KEn0v8ktdf zU;32Kga7S2?=r(OS88aiK?h{kwZ=d^qR#oA_omC#+3o*O&z9fk^Z0r$iCS{@qEC2` z`X4?F6Xd!$2^ZlE+@XIAABl72-|&Kb6)(pE+@a1n^%m-3v|bC&?Ci@uxH-Sfw_%KY zD9(_xOP1gj>MEEi&qp))ROB4WdGUu{EA^NB3t!ETVFQlBXL=WN9UO{oI7)9b+9Lb( zA$oJ=xA{$Ee|m|ZMRoo0$h+ubUWQ}Q3qQF2ElSJxbI!`f@`uQtm3cly?uS0O%V&<~ z%>1tMHS!*uDmTL>xrl2!`C4`MniF^e@~$yo{|VfJzfeH`0`89{sD|u{>$xn7;B8F7 zF+N*~f5fA51J1kGpYogXIJA^=)@2`RE05BvjqGtl)OnZa#rNoS;fpX2{csiz!i%m= z$B*(jY?OClGAgTE;Tq)qbC}*w7^!{-JLE;YmW$ydbXPa^*`s+H%HnAKr+5i3<}dgJ zJb-i57111d2U^G{^DD0XCl|wS^0la}zeD|xd=fv#4`Pk{4F8TL^4A!QD=|~=K7JCf zprE>?KPP+cGwQtaWRI95Ux50^yV(s~55J?m-}5+fcGu!gxD#9O0gC#}&%6$K537jV zR zH9p3>>aqA$einIW$h*)8y`Jjt_*lFnXLp{;*}?AC+a_O$|4>MM9rFJ70^hDzgIC}x z`D$cGyjfjUeuDFUnjP>xxfN#@A0YR~44=s}lJ7Ua{~YAc%QIXU1#lPg^Ui&ef2TZO z`RC@}w=z%S{CDM<`d-e@JkLYs-CX_bM|sBctmQt)3>&V_z5503mh(((;yja^Irq_D z@{(M~B)#g$ci$DcC+qT0I0mn}HVuEvxo7jt=3K~qknbQ}X=ftjqnMXYF{DmQUfuC?FroQ;=DDK{6lC`=hD+31=Q=Zj_Vr zJblZVUo-SBmp{T#tW#&6pU-c{=!ZJ$lkf>0@nP?_|fXuyu>deHdayQ(nuD~04G_pgk;LPsq=tsErvivr##FyBKgK;0u zcKtJ6&N)x>p7W{tbM;Ti?7Rpc%5AwZ=X}Y{ekAQeX6iruKDN0217;!T!3!9v-xHbt zf8ug^8E(U+$a~+X`n~b6I`jTGz7u2Rhwz6y20x=L?!iX=^SB3o#F6Tkv0Cnj+8C%V zgih$C&O6{`^0V>|{tvD7Zc*nQ=>~pXFYitR_%JNe>x}p1AJIxK&DqVD%jI#fdI2`z zf2cq?d)9LHkgj^w_yK-S?*)8?O&E@y`ls>;43huF2KhLy==ZJWJJdJvi##38ggmUya|f6sMt~{wOZ$&pC%{s`HL|EI+O8%(wG5Sc&XYH*sTB zbL|rDgmH2u?#R#KOZeZuU8}$F7;eKoXo@RbyO0a}^YX6voBCq)x%douw>XOT=y$_j zT&n(%*YbEY!Cg;V#a(g~dFT30~>b!eYRZm0* zbv~c-Dreep>f9T*AouiSWHx3#<)4{9|43w3{DC}+nGty&Xx~3Cvmp0PJ=BtOU*+CB z8~N|gKeG`k;X<%RQd|jy1>(&A{FVz59`$ z_i`M7pIsl1mymlq_hE54GbZ23-hp&H3Iw<*CTe zayW0q5&D@Qc@FZO=6=Zxd_m5=*$+9N7N7u@A@^7hzL0aDSLXk)0GR{1H#0lCq9Pip z@8)@!nc8P2a9_+qc7fK&j+8m`u>1`7z$QGV9?F?HdA3?0zjAID;XJcDxFf#z+4`LO ztUKqPZ6fEao}zwFo!v0cz`g1`OU;9v=cp*oSO4GhI85DGo#*`?c|P(?uF@Ne^|%0) zu^QQ(>3`di9V^eoLX8(uR(>2S}I34hLQa2q~W=WK5$pC#Ae%+539%80^tI9eK}a z$s_f?;-8S+XDDyRbnJEQCa#M-zd5Jw*QOnsUzAS@IlY?<|4}$lmmW-i>%xeF(SJ`-#h` zGfOw{4>$+eL!agou-mnjd>hZjY&?a_(M!J%vbUV1zFp32%ud!oUMF|J8#ohH^|qor z?p8PB+Bi-whH|)A-OBH;!=u%A@fOZ|-L1GgzW5pVj;#Nvve^( z>i1;de}X%!=ks;^0N#|dhm4coLR(}M)+)h?#VfCujLzfE|s_&?s?pHoS0k4tbG@}7GqZ*jdOf6WtkE&fI?RPvcu^skazA^S{rwPo_dxWl#V zd$YMSj@KK&WAHze#TTgIGpB0h-SHmvK)i#`_1@wkd<)je75PxU8by(J>1*^l^LTak z;ZM}{@B;2b8T{zlt(^CUuKXQ(>(%9L_**^^)#aOU1@cZ-fk*0JiG$>|{3QR1)woZ6 z0T<%Jd(fqnF_}`4Jo==iYmYkKsI{b&-4i5AKDF zFaa;={olQl=P*C>oSE;s))qbVXy5tYK`0=%z-!one#kwY=kZtkt3I4BMb4W_d>kG@ z?uEv@+-GwBY~cLi*>9$b{~_n*dwR{~%4mcF>g=BjSd44Y+i|( zxD}b(c@7S7tqHoxkNQ0ekb5nAZtj^pSM8AdyeDTa&c|W+3Ykw2;(ByM&b0=9-%)Zy z%$A==U)+Msx7qq@9_iJ3Jy&qm--oS>iCI16X0 zD*d<6)x+u7gKl~)xhWU$nVjq2sekA8+y}4AUAPT~U_K@zv#}ML;XR+p zo>oiF%)17q@uxcbQwO;VisDYa!8{P>;trJ4%iMep)8zM&XS)ZN!gaV9*WyzB>^Rvi zGqcL7f5HlUs?PcNGG{hsmQ=-VxdrF_|r@yX1KOceKd+<9lp^!hcnaj&hv%( zA7qaxhs?{dd@mn>*U?0M7P7na;W@|*zTCB+u}FP0|Ap;#h(kk^Q2P z>qp8rqqF=!y`1+e#ZKzHH$Tnu)Ft>XOu=4s)mz4!c`#qgA0X%cuUrfj^-tmZuu-0l zpXIN4E&szW;6n66_Ml=|EcbFf`)@({6Zr=|oeT5rIM=mt>Tl(TP$O1%!U#Eg=Tv?e zr{a2)M|RNc9T)iAG}Oa>`m2$5qr7_@t=AvNBYSUqJTI3*K@9TQYCHtZPy~x{wfk*nZh9I3vAH*wxehRHRtR(&@&zyxHMU9VRkPoO69?$Q$F zUB6G=5I4x<`4H5VvlpEqXLqfyu7K=*Z}C9=tMD9NP=CXB;9dC&WIsEb|I-_V>T=!@ zKIZRml-_ZC879gv^8KjiGyT=S$RDB%PEu!g{9L{Xk71bZv;- zl8-?*IXg_|%NXSR`itkg_BZE@eVKE9xVD( z{^7$pGpiB`A@^imz1GNcJBK^+W}eRZuBM`bx(0GiWVYt4$^Me>Cf|GRmF$-b)mP#q znv4Vfi-@V9(3XRpgV&Uw5QZISb0rrwEMn*Yb!ky*4K4o1$g{5lYo z)R}pi^}oplu}q!&aXmk#&i$D^Cj0Qw{5=M`_F($_G{43tb9V2{nqTG7cpf>U$K><) z4b9O-zX7h1Gv9JnUB}r^Gr!BKH((Ryp|)NDF2i!e={nbQtSkv(`PAE#f3$8zRpVVr?Q>b!$2 z=heut+RLMGu(B^_J`a^YlzSlaCNuLR+=I-XR=hu7$LDYdo`FkI#pgfO%lY~YS3wiG zwrfAiTafc4vu252cE)m?vv!GIS4I6uEwL-j8%ACe>fgNc~tl3y`jHT9)|3nf2lho=jk7MvoKA4FY4kl zbq#(A|6w>T(p!pSklkVse}Yq78_$2@M!5+ei&ycfdM)n4t;mdjS3kSs7jj8dht|Gx z>o_ig$r>9_Le9S0LSBqV)LU^Ua>l)?zE;;l&a)rX9kEf}fzRY7m@40dJ@PG_GyFoli<;`Q&>rR0 z|Duh2BUYk14#lzh%dtQ{4E}Nd|Ns9aWOw~ceHspM?MdVv^I1MrZvub9_oF)ssqf&@ zd>QXT3)E5Hh6m&iaTYFCFT_{!T7DQShs}T#HiZigWRb zYX{*3`7`c>>|E!l&y*YCdi<`wg9~#0o?xQT+h{yC#<>xp%RDG_A>~YzvC(1{;UV*b4XKy}RK1naLAopFK z^PC@oTRF2LXKcQ+*Eut9 zFmlf3p39E4Q~n7(kv%E1?+DkP#z%606vtdl(({phJ3#KKsmOgZnRDNlmfLae+uS41 zV~9HUWA3}N)HfsdMiX`BO&j@SJS=ZQOLSL%&pr4&bdbk!FK)%lk!S8t%!U8$JNuRi ze<3rXk6z|!Eo5H*$Jbzg`8qCu%$Ga|4{}Q$%6)kW=Nwqay)hiS^&23&^S!)7uLLsF z8X>zwVg1avH!uh}KS$_okyqjgxi%NkuPf)7se-Q9tDk4(7R->VpeUNFZ{)v`=cb{$ zCqBSfRK}h9IlD85^E_UqzJPB+=3@o!s+T!5jx+OT;YIXSKg)TBEBfpM>db`w(N^ys zb@r&tm9;odFY~t;&&D;#y!t`!QaLjxGqWS-og-&g_Q#o=U*$P-`#1S|e2ko7!}N-A z6Q8||pHgQwtjCdZBm624!|llYt%uHXcCFs>7@V%oUhynHisE`-<2d;-o`U7_H~3QC zgayd^PG-(g^5d>OkJIHMoEiI%{5wX=dB-XwpN7oxSM}=RL}YKtyHOX{)}fQ$srVha zQE$cNn2s9w4llbjkT2qAkUi@_uEs}j-pfjH_Qykgu9-X^Z_2B;D8p@yJN>suL_#@fx{lxVZSdXXD<0JVD&O2ru{zqMgYpF-dIVabsTjM@;_LxC( zV_b-(dWDhq-Iw@Xy{>X$e5n3So&D(vxgf^lV7>S86)wRpT&ll{`|*DK4iDj3{4;iA z3`*k>TQXRnbJ#qxdxw$HCTwe%WU9Q zyw&x_a`ye~dj;^lUKza`cri}J7TlzFA7|%3n)~2vy%*70&U;{XNc7S{}&Q?$3mpQx4@$%c8ccO~&h1i2v^bY6td?JpO|K^yk$>cByo)E|4s29+Kz8c+ ze30J7n2xK}=kU82DHrsaB$@+0(|#P31%R zJ#0g5{crTLZ=I>m?sytstXGJ~bAR-f^K9k$+khYBRrpdq7*FF~>_&E&oHzT+xks}% zyv+G7@-xl6%Q>8TeWg5G&MbXR&RNtQb>)1oE9CqPGDDjp_tomu{189Po$;ysH|omy zz6x=k&CG&)FGG=YrX+7b&V<6Q<-Y07<#|4D#~NhjeU8$|OuF6mN%9zE9#m3iUiFdN z%Psj$-hv15EZX2RWaj2fnZt9rqTiR@aj`o4-GRIixj#O_6nQ^rIfpNIy)EbY$+MBUbRx3Xov5GlxC?SGHAJ4roC%pjZ{Q1L zM%2UW^8MV62P5}g?$^xw-0RsRvio$@uZ7HrhRA%$?45xt)Op@2^9+27%=P9mUPpF? zoM*Y;tGKomx$pC=AAme3m!XwjX7p&}3|OT8mK)&=?!#>u?D_&eoM+)+c^vP+e7QDu z<12L4%bvH42jB$cY|U)Q%>O{n9K8nlRS`3hooS-$Yvr7QnbQL~&-9C&-C!+GRp*?| zEW4e1s=w#oIWxR0XI_`m`wG*MS(G_7TrQUDoH>~@=4&phJ_uz|5_|PBQ)bFluwKqN z@q>I3mf{a&Px+4zbiF(e;hZIp^Krb0GjC4e$=nOq;xKjQRCV;l*LvAu-{!*VPMCro z>gRAYwyDeEc{%$-=5+qvtES#7dKcgU`DVVKE8qsXINrli>YluYJK#m^o1y-UfAzzm&hjPI)J{#3s2jAB(%>>ht*w+$w*Ft8g-!_~+*g{#7o3%|3G-x8cd$6+g)h_z{_4zsIVV$l2Sb$|dAe`Boeu_vhcZe)>LBlt<|;z)kW-uB6|IyQ}kF z)>s}Rx8qIx3;N3Ua(m9Z+z$Cyxu)Dto`r(y5qMh8K3NAhsXxQd_yC8vb_=dVW!KNv z%PyF`Y(9+}VL z5&1|QAV10jaSl4Dci}9V_U(}qgfHdn$OAb0_(^(~A@6kK^s*lnQ9ma)!`b*#J%W$q zTX4Gk1Ukyq_$bahKt;X^C+KCDzk{>Kw$R%skHnMm$6OGlFcFWTk^U>3dp^%?p8L$J z{Il}>fHBvZtqa%=d}`hFjVh!&b^e`v0Tp2st4xa2jpJ()aSBCe!;J* z^PC^VC6VWII#yF5laHXze>^>*(h@ zyq~vn=1@ichI23HIqrn)n=k6;KFO?EjLh!bGu7om`ag5_yWw0`z3<+ahsd)~7?stT zeaHG-?wQQCI-Gkf^JKi7=RUJ}gljnmGh=ceE|rhtM>uo&JIWI!yN}i21@<>#W8*$EpoO|=tnG+9Uo!(A7EazDpBxhD-Uf-_w zG9QkA{-{L}aL-yMB`q@_|s;|Hn6w`Z&oAA{b zEzjfa*k9hmm*F3*R_E-xUGBKr*f;zJ|JMlJoHKyn{#D03c)idSn5-sGf zIWxVfyg+`9^Xp!@CN4&1;fKhXR8`#t&Eyq)1`p)VQAJ+NbFm*L;ZB^Ue+`67T#|d>GI=xF z$?cJI=tk_qCpZq>^y^_4&R6Fhx-B1%oHaQM+UoViD!sz$&*UTUDh|`jj&cl-$8RW&ob7jW zD`cNO%4Z*w*P**y8f&o>WAu9AV7aKzKE-+Wsn2)m|ARB|1;(MF{$75U55Os?g{ScV zYT!9+zzIH=eft+K%^83X$+fT^-)UXM2jVt4d&}osLY?#U0(psClOI9eOIxF_-Ye>! zJeE7-4>ZR2dW%pG^>Mb|8~hPk`n`GQ{z0#@`dRLSgRlzO6>ICCC}+QWlCM>ld`hM@D+yQ^oqL{N88PEx0nS zmNz1M&JNeFMN#~S0XWsQwYXP)jxR+&`5yio=VAaJ)LW6y<4ydfSAw(87UpUwq&F3_ z@gY9Y%lqGGenR~?uf#QIsXl=x;ysj8AI2y14qPRF%&qtr{3_?2`~~?I~uZ5-?4HPc@#gzmG}@emEXdp@=R3p+3dqN%T4rK;~jjY&KL7M%DTRtC!-SHaj7F0 z#a1j+Uxb_F-}y=&!Oi`?269#YFV^dgrZ`yr5^`?*qb|jn4}Ij^M>&h9^R38zm!Iht z`A5##)={p5!?7Ai=Q?uF73Mrso%wn`gY(_BLI-s-uE?2#`PpVaC@*JjDdp7d@58=$9QJ9TMSc%&DGx!E%hUGb~r1vsr$;a?%ST1L# zW$(=WmHXpHF54`+aOh?w9Ye z5dZqj5d17>rzjvdkOyHA^6X`=?ZJPdq3d6B&Zwv5Zpf^ipC#yI&Ip2KVKH}d?<<4g3HqCGa_V!g7+d67AmIhq-mXLT3n`CTAq zM*qN5kmu+g&e?pXoZU7%VfNaS)m>3Vp2{!!OmiHI%(V|WGb6iw=INct`$V4IL$RN_ zfI72e0rD(gq4%lWn2Ym!T!lB`a=AbMhEwF-$ozUzoq3r#GE)7o`e8Ie_MM!?Utv7X z!bUXFzkoBNvnS=A-OKL;X1v}=q^8qwRjUfaX5y%b_6QRcOmCZ(NA$o1l5H-|C;CpmI_R{Jz6_qE;LvayqL-v>p`2p8f z@u?^x*W=?b0Z-sE{EShqwoaa_({(0R+0Zh{tlua zH^Lz}3OAv>{?25-C;NN$vlHaJ$7G+LD4*o|k$6nL7Wq4f>{;K-Q}uHeufbO2=Xa-G zGcJRh<%zgn&J4{z`zd)SzLN{UAGPnCSt;lKpMuYj|JK}Zxkq1<_m>OF%{brDwY(U) zH_pND*n`ad{OmH{ORHa3=U#eIE-$~#Im>cJWzOYU7$RrBWai~NdlhG@oAWZ{`U`3_ri7hEFuM`mIf zJSgXR&9l$|o$-k}Gv*`YyU%>hb8(Bh7Sh0U>V+Y?Erj@ z?a0}h9eIoVDKc*|kEh8Uk^7)KPSDT2RtI@zGf%(8BJ@PgiXuL@7$2ZCj>b6sa>)HY z34P?;gE>>~lIJ0_b%)*`oOyjXXXpA$??1T_XJ%%<86tN>W@C1uqvTtWc~b*dBJ(HD z!8iPgx*@MYp8M>aljOq4b5#-_BKvvmd`dMG8ue=a9+^KQku&N>9)tq857S)#jvv80 za(0Hg@@)KxrRvP>%6{)_TwE{b{~7$3`fF^*2DH}OfF1IyI7aS@gXCYhFGgXox)RpP zr*h7>7v-zvMZ6I2p_sZ5ev`lA%+1~MaGuGB@e(e@IUA0ZkH8>#InU;*=!~328+jYv zN6vuLedZEgf{GZSF3&IXLHM71GChFI%hT1_!-vbyqa6O$`jVqAdxW4NZK~AIh&y<`Ue1m`xE)`K{CXc7 zF+iR7k^8uVdMYZaS8{gJN<0{O_qm-%`do87ik|8PydTbzFGEvYhwO{9IPVKLx}Lox zJK!$4t^RnPj+}LKcrBj7QrGfMT8hubJiLH$xWe^HuD!s|qX<657W|9sEcg0c6%<6? ztqjpYPP~f&=BNcr3n<-{FVR)#qBO z_vaJTHMxiSX1Ru(b9|z_4!`20*!ACeDChm-M|red7oBjm`X&CKYXj*vJc{%2Eo$p; z2fD`pkj&U8jxKdb{Mj@7$yQSbmawarULR<+*r8 zeGX2MFGVZY-cX+>7eonp0FFoYuOIbpz?15q`9oy4DuJ?c6*R?1XzVlB@K&6Qi`A2P z6;6}S;(ol4mvb#{&0k@qya->*hjZ?!1LS;1IXk=Z7g*}rYupR@esYg>$4~mXmybct z_;dI$5$?--XQcgSaH}-?sr>Fa~)(=DOaP`*IuP z8K16xoO7nXfjlRJ)R|e6@G0^gXK%^bn^{;={U(+m=UN$jj*Bn=`T6GOUKR!9Z8%xZ z9?+9>|1?K__L*A~_!9kF@Q}Qd@8{hA2gmBn@9UAhpqO6H^VRC|*oyP@+G3TQ`Ihq_ z&t>k-?3zuurG93>E;+NPJa{QMf-cIiWc{U%!_aigw6ZHZ)&;QAI7&+q` z>h+fM{C>dWa5J(CHE=Cw!8e?H_d7g|8?jO^v$C7KKicD364%6nP%L<%#???!@=%mpSL#uM0`7geUZ|4@g0?n`(gV7(iVue;YSFZ^!#?|UM_yISl-{b>O&7W6X zeLV7xvQF$M{v=!$)JgJfF|zf6+#Mg1hpc+>*Ov z6yC$_dIdS}U}wtnkTWH_SkC6WXCB1^^)BVNxv5^xrz_Q~)%(dcoYW zSGYctck#V=8Xu~&<9;OHB8&UZ#9QS8ic;#cB9zk{dnKa@f<+=c9U)3~WiN8&hK zgw802UtRkQJ26sy6?S72>iNB!^xu&OAiG@;eqJv-_4}M%WIsL;Z{gA4Gv$ySG5_NA z^!~()I6^D$XBWs7kR5Ajdh$^?4%stWa3MT{i%}IXA@8DjCm-nd6hhv!HgaXo?(&pe z8c+D_9PWnJ@{h6WYvp5c3O>|(0awXoT$>=@ff{%QEA&s|t$Z$aAiLihy;bsFY{7Fl z0cWGWYnAvtJ{%k6QG5=vJ08u`agYAHoc-t#xsQAu=Up}L6=T&u;}@KyUsbOIKdZhL z>*e$KW*m%yXp8Ugq-*{0rThr`$qyp$zIn$xRlW}+ag*!W!-{fcbtk?W*+={F3CMo= z3LoNncFH31i#QDHjc#wVk!oO`)DE>!2d z{f>8Xe*On=&X9xnab#~ekq`Enp158<86B|z?ey-)n{wtt_N?qm*&)u=tIg*j_eRc@ zi+GIwPrRJ7b4=x&@7L(%nfV1T>t$9AlXD+Wz+?DYuK+j3rSg?r3x{KaI=kLyoLO@x z`kkE&sMHwKYf);su%K^JR421L7j6o_xMn3le=*Czy|VI zWVg#K&P*<;9)j^cvz#-t^DJ*sUx|<9Px%QqFGv<8n07+kwp6oL8UAmm<$n zUA~QbBWM2_b#r8PU0U3xcThx`o}^0`&~r}{FqM9!}> zk@MyXbio%m8*?zlXR-qpmoJg`aB)5jIR|FoKDjSG*k(64?j?eIq`T=B@FRWfK-;R~Y{BOYj>Mup!aqm)JEpO!B z{02Y3yU_xdVU6B=UXAQ;E7V=(leh`S%XcH^{m-1Uu&e%Rl#@TzTPNSj)o>kdQFq2n zypKtG>rp{&$rJb^>_T_-@jMWDcWb169dDtuUf%PwpWUH;Rh@G%JHg-Tdfbp7Mpb0z z?Zi)G1&(w*yUTESt$Z==#q*em`gq*68@U|bk&nkAD1~S7Bwj@JnQ1;VS)PdtkGkcexopKz6*cu1}J)<75ZjEDyj>$oo@vxufMixEYmw{$PFz{qc}` z2%p5G(O53e7vo6y?fZ7l!rTeRYwV|A6o05E@?0D)&%?W@fg-pTBQOftLE8A-{qiRm zhV!uvL-q5HcouI&cAo4O+4-^;jq{o9I0pq?AIY0IJIz3@fogikp(y&Pi}5tRf@`8K zZomt84Bugd>)ChC;JqlMw;Pr8K2$G}&&FNyPW&q82l5NOjkS6gaew}tPvjT*SllDG zxxV@;j6y;66MQIcl1HZJb7ka5<@<07odM2BXD@RgGb=MN z^ZY2i>39u!rmO4kln>%{yodALPLVh8C+IJ4K`G?S%DvY^K97&bM{=I8JeSAdCESY4 zuguR^eC9^(i5ulU$n%+*cpS#-=RTh&SLV#qLFlGF3HQqWvHRxc!Ri)@%$pD8%t#d9n=)(HP(8<+;tQUCck~_2quN3TMf~(OX``A92o`JjXfHe&9SK zPwHKO%$~i-S(Nj!DgKZz2#?__FcZDhS7VucDW1k| z^@GR^xrrC(Wq*BL{!0FrA3~nx0XPDq)uWJ|IrDnHUPWxdY;<+4Id?%zIs49=^5ybq zuE0b1Ej|u&a5p|i&e})$8rO5)7LzA&=GUjle654-XzNf0`r+!+U{p99+hP$ZG zL3YYt^}dv|8-K!?o!Q?qdry(?z@c*H>q_kB+9a;V8~HTc=lVOGb1-MtE_oZ4pf@_{ zJ;LwvU^Ku4E?P3ngHBCeOOMb7Y9+=~z3`|+;)0Gi9$8Ls1; zTirSDY4x}~R_cx9zqq>o(eec3S0{X?Ka)?ykH{{N_qLbypTQ-_zIi*>!xcWW6gfN2 zQ16E?aI@ZXC@QbwyrbR2->a*kfO;sG)_at%;otd7>_v8|&U#nkWA*>g5gWw+P}cRT zD5>`#s_ET__3{J!0=D7=^(b`4XX@K{BXa%~;kVFOewIH#Uz9;3EY$x1dyt)Zyxvwc zm-FuVn%vH{m+>10pow1IOKy>u$cJ(>?#5O4G1PaxKmUVcQQfud9#w*Tq25(on%}@Q zIq!ybc?Xu`PW`X>DU8E=*rAtw?hyHHe50@d;&M+5H?9$oU8>$aO{{Ew_epOtmei!+Bk&D!=<)3&2pNZ`Kr}C?q znCn;}*W&zL%v!z@h4l`?C8&?udgJgnvRCH4q!6a6uf^v+lf!8l)Mq)xGf*vcqJi&XqU0R*G{TcFrkgqc4%xYu z$$ORak-3rQcsb_`n8ORvOE2>$Khw0QuQxc4a@7!=PYC{p3J$wPC@Rw+>@DKFS=F?xi9nVj>A~}^YA*}Q0ELTE@wZ<^Yt{c7yiq| zkZ1jTpF16!k$HW&UOWDSU&gn{Oniw8@GyKN=h^Jezw!aR3V+M{adyt^n%}FNaPI9Q z`k7NL<(zX3cs2gQTdp0>*_nUfHR}6$4`*j@%sJCCNBZlJ=Fd21Oy<&RWH+sd;d(j0 zGuvL3^N!GlSNcphRF<#dMm&i(@C*!)SMl?>OkTuaAu}$scZZxapd{xR{*DjAR4hSr zUwm=fkj5?twGp@w|vL>mK9l)Y)BT%6;*$I_E$) z&a6K*xSsj;6EZ(C;tbBaN;|#EsDdw0R6o0QC2p=B#maqibG|zBurRV~W+yySuQW#My^Z?vY%G^o z@FVCX=PVi{=bWg9IzewQ=RKy7&wPqA)dwN7`B8OIZiZv3ya_2tN!l{4}jUZV7b zIp^x1u3yYw@B^qR55r>l5Hygl;5jHG=S=>cZ^b~|qJJ_zL=ilQJJHm&Z;<`zZ1%r> z?*RRUtvCtU8UEz4uHA&<>e4(>ubF(I+(GWn8LFx9@SiX3==UyeJ+l}(driX2vhKi-f7r^k;p!gJ^EeO zYVg-Q60J}`-Pvcplw0z(+!b%i&tZi8A6LXna!uTd`e>o|9-q&5pt3*fUiByPFWiJX zp^IG2wHtV`x;2-b86&2Fo9ypnNHwz$3UxuOp@+ zJJqS2zjt_tpL0F$iRKDd*q$ z05tJ?{cqpDSN=VBpt%0`oS$Xxlg!)ka{e9nxVD|kaqfi~ock|7@66KoIM2%nJc&m! zQa@)`?&&6S?z7CMx8*-L^CUm_X2{QE5{By!=ggCZ@=(sanI-4H{UZ4sz6*KQ^K9RY z{7j~C4bJzS?>P5&etv(UB{D0Y;ir-NFXz_fyhfexr5-ZpvIl&}f9Pk%%;3#vEcfR; zTd(s^m?J-oJnyd|v*L4g&ec4#*?qD@@_>|I?4xe?(gNuJ-Z&+mvh$s55s6CGSent4LYgw z4CK5kB{x82b!KDspX{A|)Q9N5%10p2$0p8vIROQb=ivXI>5E)zqrQ`G<-ajRF2Y|R zGd|B#=6dFdxNmQMO_(O<9KMRDA~S0#Zj>`KGn+DFpGFrf(LbEC=arBv$#Z!#58>Jv zh$8BW$S(SjdJy*DUA;HZ9WP>`-Zj{Oe(G`j2p7c)yp1#TY9Y^mX6Z8Si&1h9&P-p8 z>@0buS4Fe z4%QnXZ{@c53+JhS!{w-n+fW}j;aHr77kut(9*n%3_2tPpUjJ@BojdRxEI?-Z{b-2B z*n-UB$GI40;~DkKm@hwzL-8ou;Y$3B=Ux8^b-XTiN7lrV`WR>|-c#LYw&7v?j^}ZkYx8hD zrlXMF(Wr=F>I-o$vdf*Xw^Lq(>GFp>2ph2*=jeTgx$=EjE?%)Me2W$J?|-X zVY#6EGC!uy+1s8c;xxRfpZDt*xq z>xo(T6&v&i<4yT-E{hY<1RvpN9Oqg+)R8a6PWfc6jel{E`UjrJ*^}DIU*RrwVRdD$ zfP#A2H?QOQsDgKJb*}5LlCx_LP+x+Y>Zbe#n#^HE(}r!aMJ&WX>=wo3eP$4B3~a#`EbIWgySod!3-j3B-QC^Y@n7?;^}Eis z?t5lGAm@Gdo|%mY&?D#(G%;TjfffauTSk#D0S#do?1Ro=ccR%7s2uoQ`2J{p z;^ydI;&LzsauVAe!G5k(#P;>JKketcM{Y4{=gHok?eS&AbHRFoeLg3^J_p+y{`k$T z+52k=*6$vZ+k-EN+B;5xuL3XeZ^1rO`~R>#VrPCKxsj-y8QZ^^pdPXPtarga5BvPH zlegYs=hgOUBS=CnIeGwH3f8x+r`x->J#Oc>KI~_0Il2$6gW8$0yY2?bukd!x?A=X9ZBO+X&*3#b936{J zMQ4NU9jlSndyZQkp27ym4Clc1rqx(Ge^yskpf)pA2dnLKQJen?qt>rp!b!Z|F*a7Tw0B?x?r=i_4Oje@rdnQuf!L`CxRyUFR0b>5~%fT>y2IUN5PZW>ajoG zfsVw-Q0wPK(Ee}?{~Fri)1tG`Eie;)zz6vMS=i2gVdCECCG;zN$1j4zPz#QO&6rVW z6^P=QpJ)fvYH=snj^7P7%bXz&$Nz>{e0;PhB*&LUFN5`w=U}s-2h;}Z8^6dk#+L-E zQCXl7SY7#pK7gNi>xmsvtE~c7E83!ga1ZVh|3L?%0qAPfYQS*xD};j8Mw?0U!!BU? zfAjKM%xpLS707Kvt>0T+sS7rDSY7FkzYnQduZr5NVl(O^;)%r8*Vd!fa|@tWcPf*! zzS|w$MEnD+p1&oo00)Wx!49}ed>ieHeutf4HQMe8mr*z3c~Fu#A3C4hC#Xz(9eqzO z25o`1g1yiLPJ#7un{PhiC!=}MU~*&dx$uJ_o8@5%oF{IMmgkvtc)Ls3UB~KZY4StK z<%er<2)>ZBS@S=97qEFh8vRAS82SVzLSo`nVDr{MG?H8(`VDOc^C5t>0%&~l!yqFh z1M3&3%(Lc)FOLty7eco}Hhc;6G&&Sbj9Sn7fWHY#h_e$X!uvuR;+<%J@Ph`#i&2~N z65^5n-Oa2I*?q4c+6gkkBi0t8CBYZ0KbAo~!2Ub|>)q9eeel-TZ5~aJ76 za-cRN&%i&!|A!_8>(OB_h}>_~2VDW>p*4&mHyr){p80~fBC*{aY`!grw>e-0`8@C! zGLWj$fOsR=oNcqS-ECG8FJR5)jGE{jNJwrC`WX#| zA^2!?4%q#?9NHTE$WH*9JJ%6UgH^-{AU?UQXjXE$(MrTNSF|LqL>vKv9}oNSc1K8n z&j)jeAHrU|%{dv+evqBqFzAeLjP^p)z*)!z(;*J|QV zd}fRBHs{qK?@oLI{r}(T4IzF5y~x!-i^DB^5}1TvigrOequtO!um)_7-9;`IY_qRM z7hogcF}@hO9a2IwXa_sUw}*{TmbeWB@wFB0d*L60owGp@19rx(w%b0lb8CIY`p9tz zz}tDO$-2z~_nbH zO9fV!E~8d2qVRoDtBd8p&fZ62dk0>4!B0VLugrppkO(}<*?hJPKNkiQ??LOLzrgy? zU9es`8qTp6A6|gX5i7_ohTmZI!s?0b)ynV+Z+$KWelS!ZP5`IiCRh)sjwS)?e@)SU zP{f{vASevB&z?gc@Pzkp1`dGD2X@~3@O9SjtrrFpw*xn@dRU*eRjBnbt1q^{c0+CQ zRs*)-uj4gNKyo%0#QrP0agN$|j1&ntzu z+Ex(%2X&x1xjpadZ58{mLHRnHF;=5Ppd}QAvbGLwU<>gLSO(=F2RZ9sJ@9p47z~FZa0{#k4(A!0qg$bA zp#VNFYumu;dq;9F(Uzzm#Ne$5UBg$x`@?MbM{NBuK7I~96PgNk;zLmfB0!1lUQiZ4 z310)9hju~}pf=APL>CfUzqeYw6g@#a6m5@gMFSuq{s&qVdVt+4#*iC`--}wE{)YdA zo-9 zCZ5ZU9|{F6NBjsvk+-;e#AZD3;qO}1MQBkMeW|&4b2Em$axbtN2d`Nhtc?QumQ4x&CmzQ`+)uV zKyEv%friA5(RrxN0CpdWL(G5wyMG+Q6ofLw&0sA)7K*_ruo=$gK zC$W7l@!=t{z5gK8J}28#_V-xr`G$5u?WI}0w%)Xo(B7BL7`CTVqlr+fX;#1Nv#@<< zb;90>oxAN|wR-}wotu`Z)i8S>R_A>1_W9dci-*sETHmprX`e+4VtY4v(HH1N)b^&G z&$oCxXWvk(U$*yc4;@79+}N3}jkhym`^G-ID-esHjs6Gro@T=}XbtvWZSPKml`xsu z&f;DOz*hi!ug{<|zCCK^!)l53?P6k#Z20=NnKGKn#?NRFiGx2t2GlT8#_^6!$I}bbHDc)*)O$fwW&)bGt z{j}ODXiHQf3Eq13QM~QhrNl$gGjI^!3%w0iXYB0S-YrEw7M2rlM(aQXSe*|eXSL1h zyv+mC@eZ6P-xI7iSRGgb<%#V)*jYOSFX1-ygu7t-y+3*j_Q3zw89Q@D!0O{+^aW%B zt2vj+S+6Ms8^LO`)r@G=YQS&QYTpA$kGI+7F#3Vm`bP{DA-1|d7px|oB3Be)G+^@YB)eXhpOXMB(S5_tCUqebs7o2n-=#h1_CvKjgu; zfiw_EZ1Z4S{1zBUoEt3-`(QU%&#H;qJ#aAVb72dlg6HH9z(;&G)aJxw_>5>H)B$gJ zP5cQ2?*~s|3REX&cP5+9(-Y^08RV=_je`4N_oWf!M#3N{4*4Jv`JJo}!+XI<{3qCi z|AVfB$IyZJCwc)b1NHEapcQ@*%z}n+h};rbi?0CZ!S27+$Yq0##M98D=v}k`B*3>v z+rkET4#~lT{4wyw=Y)6oK)zQyd@#Nwx)1CQSsiUhZaq2;JqZcO+nk(^+)d&xupFv^ z-6h76-v*hp*i3xe!Jy)rXT(~J_!DS2XQ3&5ffX-z)=nd`_^wZ|B?IS5lZwu0IsQ+uv`s+|I7`hK=N{ zt}Mb^&$s<<|9@7)t$vh7t!^GhZO>YNwmog{)%K8m_O{pU{SPN+XTtVdLNpoJ>|p!f z`n1&s`>bq_IItc64y-mO1FPea1py$Z4Pa;5>XW_qR1ie`6nzG^&+ZXhp9#a;d$RiW0&ji$C)jzh{cg3z z&Ysn<$*>Y%1oc5}26>CNC$|1*Gr|bs1o%~8`}#2P0(2YL+*%ZkPtIyz3p6!qd;JvN z&dvzp1E}?{S@<)k?ax~HH822v5-$X+u~rW*Wg#m_(+ft{fO=qqy8?`@C9fc3ML zsO|L-)au44u)0tcY!+#S`lELKt!CE&o4Kq8*erek-N(AshW)SuhQSB0a}b6WhU{Ry zVhfrAwKIGVwZ5|rTEc5$n+vR_+KkYK*v|M`G%s;3)M~2rq}(u`*!uci{BE$heG9oB zc$;@Sp;k9+X1<8O%vvfm4{H6@1MN)ghX%lLu)6#N-3)Erl_eITkzjWW ztI4lmAMsqY7n&J;h&BYP$5w0n@%bT^wZ3RMSPDtuD%gDX7@Fa8z$usyFUT!JH$e@2 z1-K5Gh|fb5SYMiiwgsE953(MLw>mZwpAEGdJ_P>*6rT;Xx<4DWndu6-J@}JQ5&r{H zkPjrDj{gPU@MEAFG$$?q;rM9O8#2Qgu$o^EzTwM))w?ys8Q>Q5A^rm=@E5@5qCLbV zU_S&xMRMO!H`ZsO?d>`AAsQF`4b>oqxFF9?z<7V(H&s5y(4jB^cKv=zX9v# ziY7thf!!B+qAkJd^c2>9!5!iTFb7&g6-W-jta*d=fG@-W_|H%emckS056z%FM8JLc zk7w7z35X9R$?Zmuqeszh=rpK+x8833sU29qtPSbmEb(nfi?`lX73~Gq>obzi3opU$ zQW?n~!du_5JIEno`(wS%?g(e`Ss^jZ1iO#>@VwnePNUPwT|`@>mC*!fM>G?96Sdj$ zJpLKj{Vxkz1Z;-?gFfP!)ldqq5O;u5P@eT+U{G=dc5 zBG9p@%|$El;V_Z-2sDAw#Mf*ca^wG^lc5bfg(NVLyv^3v(L%799P+<=&pfm=6oai$ z7FI!19;gYykO9&_dcH>w)EB(*bJ4*(y8!GPR)U?d5I(^m(l(pe{p2pO&3yIAb%&SG z8;X(N1rP9EkR2Zj>F~ZV9@Y_GfV9w)cmw($>V|fOGWZ-&5TBI4BO&A`?ngWceFR0x zH6gx@zl7SXbq!5Pd=C8pLo8362U0^N=tj=&8(;CEuofyqJaVI0yMw=vMxvk5iBJ)r z2pU5)e1c=-FQHw)?w&J`~`o5jxgD@Vf#@KsH3M0WjqiDQ^ZSQG2UwOOuX&;SzxoC&5wQYR*y!o_5*LV!}gZd zE33n{=NsT#g4OV$V6~t=Sd9t;t3y`z_v7sx+ZnO?FaovuYBR0P&UW_geAzrO4Sfn1 z!R9(UYgRk|p;pIirm;G0=Xwj+xv(12kTt85QK;4C-QvwE2Xb%QNn=kYbTldl0) z@O63C&Uaqo;l%$?JKsMbJ>CPXF3%^nbDABsGg%sK4;{hk?Mc*nr5_rI7KL_Df%qWU znLkOq57y$(pcUZ?R3mPTT2HV(SP1_Lg7H=ptxncK2f=!XB3=Tq_-C*KzY~J+o8U11 z1{?vKbIy<(X&!8@x=4Hz?A~BCJDS`gcn1D(3#^Cwpf;;?LWjXuSj5*|g);c5@CH5; zPe%)*R<~B6sfo{`{b4bzCH8_;@Rm3^l!wa1*5h1gHmJbYPbJQczYkWMXP_6r=D*Eo z71%?51I)%3f!1I>Z#lUJ@B!iyhr?WW2Q^_o`G#mBc!^KI_pAiRh-(n{L+_ynz`+kg zo1%A7n~B?^R>xaHPZ-3S&5F+MjNQxL5ZnE?7J3`j!Bc1sHal+P*(cBd-v<2%FIWqN z0GLnQ5B(1H;S})$$cq0Bm%;86qtI~Z0CicfgqDQeP>%Qp8h~B~o0WPK+nv55`iER^ zxCtJ_o^TzWfX$<;(dsahb(=ZIpw{P-;V0sY<9niZZ?}2+J^mATlRpm`@yVbZp5_1D zF+QQg$)!hYqczdxXe4xnfy7@RKYkk84NBot!Zmmc<@jbvP@4%dqx|>3d$-MJZSc9# zTxekyT&Nd>g3X@k(9-0Op^qRKAA=5oDd0zZ01`t7XaqaSXMk1sS?DCNIVy;FEc796 zg$9zdnI;SV3c01|A2bBrh`vVcUOXB<4&MZw0XAzaBd!j&i1VO6( z)b48E@#FBF;Szow41sje2YNs)ILCSd^doA&tC^0z2fIsWB_AKHN?Zb84K_kM;Z01Qqhu>+Icaffit&v7OOjs3&Um>NePUC_o$^wa|1@Y z8uc3{KzZT?=oOgEbGC1+&h5ZkjroPzH?X~Id+<3tAbtk6Cpw|F@2u|Hx%mlI$Yn+C zEL$%)4Bv>Y7g;^AelZYld%rf`-lf&K6Y!IKci06Dh?m1iyw%6Q_?BR`YA>uNZ|BB( zkju5Yh5&LSz~%+3$AeI-dq<%i@ocm&x&*a-XY$2IElCZV11w` z{Da>RMt(OM3q|pEzV^aHh$0sc&4pG%?VM)=yBAo^wmzGR*lNrR^oOnS+!xf&^H;pp z(~NM@Jlq32yFsiwcn??!*U2S@JoqPIeaO!IQ~Vs%>O~-aF8&?dfEUD8uY6IPeb=K_ zlkbvyWqEQHi4}$u+j*`4R&00y+8yewDI5%1x-Hc{~CirvE1aCd%0)7+T6ZHnWque3hhku8*h3xQ$ zI3;w(+su#{oeC=;HGBr^IbC?BDYSu`(3AWKC<}|A0$hdctogxlC{AqsZYJLP_-^79 z5RQ+8PtY7fAt!m8XNKbY!Xe_;&<&r1yv;!E@$29&egZlPoe5j;`JtmhY`wZ1-WR$O z*FlS*xzY4c4RR3MO!OaqD83w6-~UMb5x)wSf%W4J=nt@-=)-ziI0J{^2081c<)9co znA}=ehhK&cK@~lN+ANS3{RhWjHTgv3_M?f>3*-{Qe^3~Xkhk947CjBq$^C+Ua2~Fb zvt~96{{|WT1I3@8sG#YIMb|>qBCV+zEzrsW)X5SBr;NP=mb7vm>Hkbr`?0L8ZP}@0MA*sI<^M2`tTaHJ#1%gFL;u(^YIoA5Zn8n zfwyyM@1g_VK0`Z$RxfVBWb)ZjtEDH>TWb!C-sY2dxe^6I?+{g9kKX&GuOW z)Xr8`bUK>XPj%8yTqKZtGvn`eIz+x_SwvDIINT@VH%;XBmknJwVpw?bE_1Gb0a zpb5d|umL|GtVh|bJ{z@qWoK>;bigM@2cmW^exTE! zA^d_OtXVDEgSVPzb*3Y6HZ&bt4s51~Pi*z$3Gr_@hqv1D5Ufs}C)W<&9z6zW@K(QV z=CNA+2>%+aUgbw0!$15K)aDVZ-MP?V#8%&45`RG(q3_X8=svUnT*X_BzKRck;$U;@ z1oA1s<_4?VRs&|ER__kN3h*R10^IOE5D7MiS=|_fw>o_kvh&O%7>qBATHOu7Uw|aU zb5ZLvOYtorH4GuP`_+4V8L+yQ%<@nNZ~eFv`U2d+=DkI%1;Qq{3_;}DvUUS+HTX1G z&7BH1hg%=|2{w;*N9{gy7Y&CPo@oaSpc%0j`VZ#8BB%oW$y-hJMQxURgJuLj2nCx9 zsz6R?4cB?rHgq=R8*wW%Et(Z><86Mox#1S^X{Z5}$yY>_Rgqw$r{5pW!z3+Ce=pm|a2Ep^dzP??nU{DczZ z?CxOwtQh_n zsLk68&g*YAbf*|5c z=l~c44sl}Ce%G)L{|w5*WT-{{Cj7#Gf(!VsXaLWy#@E89gdF(aV6#I6@e}++^ek$# zT4A&}M4A#;1e+!E62IiR+^7fI7R`)Sg!Xv5Cv-wH5Zk<(6mR!TH*`EXZ}czv8MPT} zEZU6t1X>E7;uoMB(Jl}Sxu6%+faY+X^-1UlG#YiG8PWLA9N!rp;s>Ey(O~`_`(4U6 z{48=g(f-f{e+`Q@4S-dkvCi!8!Cu z(P~;Pu4tu$x%JxG@iJ<`5sCjO)QML%P|qb#qPg9AY1GHZ;?=l-|GA1UT~C6mYIuXT z+P2^ZSvx1Qj#(M*3P}{AH7b0RjwOAiRgO2Z_h@D*@w2k-uM?%c$GwsH3EIoF|Gf3u zx=>AZ(py`U&Z@OzJL{AU{<=MDS4}cui!<|kGNk&mX@Z@qi z7dTl?`91UR)F`{2ys}jmM0S+=OY3TyH?8zmz)fj4d#jYq99MgI=F(DS$IHs5ZDi8c zo09K#ejRpnpbj}4=(s+n(I30xs#EBYj9I^5;$7G$$ZzOSkB>#P z;IoBNy4WO%EFYzAMUOan3uMut$L%z>v1*ZFpPiFGddb@Fs%brRSpx`e#)Q4eLKeDn@#0 z)rrMi{i5n?#@Y|%N0CDMxXNWo^tOgJ>D*Hnc)XJRNhipe?w(ry%n;c;aE{|Uc8^oL zU0*FUG^^aWm`U=rDJYdHJ&^)~lBnO&8aiv!0L@arw9Xm6NwPH0BLP){wN;ZZPSw`c zB>Vm+uD+jq_4C8_dUxYPnNsV#q?@r^manR<{RaJ%e#ctN(ugWL^Fw#ZHa%J{y{)DX zTNTqzK7}+_{I=Tsa$0@YFia;+=%o2uW!H-PZo59VFQt+siCiGRVq_|9dMc&XdgiO$ z4;0YFZeg0RV5GLL(^{7n3)Y7Jy6H!Mk#Dn(i2vNvax~dv85)#9eRjN%nB=MhJ>RZg5A_0{FU)imz@ zlA8OOr`EjE!RguNqtmF7OX~!c%Kp zc-Gyycp{UW+%sMN>`Sc2SA23#dk@efKG$TY_X+38_0CShS3@M(&6HYW<{{a8wuk;q zxlnRkye8xSHPuvJJ!N0F=i*tix7LmBs!t=@>#K5ZI&@EOb+6;0KMICucvv%a+wee= zcIv4q?yeP&A4_HG!e6rT(;C;eEL&vNsqxOFcwyQs&nFr4y1cv&S>qH4{VuH&RMfcZ zW;pjxIr{9$GAG%}^4g=?R*C+XRO61#FJ)$=(re3qNRMi7of^*`%ZkboTIuZx349o= zgHkru+#%cLbcRy;EUUZb%Nn7D&Te)(e~T+)vN`OP+|J)`Dmk|P5O>(|5=CVw>WJ1L{PKs~$dfgf4RGr;S8y7317ZR?N z8Wk?c!k_sxQL@!i_f~G%y?B|-^_(Q*Gv1O$!^>!kl{uZei$iom`Jb+9uPf_qr;r|A z?(5Wdb&+F((x`XNAI^FYA3cyFu>^WW>hbg)oo;EvWKZr6+N^n~PP>#upVaQF^J51~ z5WQpXn5;7My0_$=pIG-!ORd3Snp zaGus(>Uj2!bybhaFALrk)vMiVtMBq#k}G#vO%m2#N3HYKnA@JZba@47_aw1yT#;Yv z>}jMyuWIP8cX?&qf7P_`Es%y4( z=OyX%%C6^us#Uj@*Zun@OS{ie`Z@6n$+Ne;Mr1rC`zz0rEOq|Kzlat(Z-0I5d@8l$ zULitbXZGR!7uD|b1NCaYC?`qjfjaDKEAf4CN*b*5(;QU?$e)Q5}IN)GiYm>-rO-LkjPd+>eTC=ePazh-U?-se63Ad+oZ6?O#QQ*WMu4e`L}+&Ex3U zQ)8t5k{r@6QG88MC|a)^z9~nZCD!m~FJ#G?%sRJ?YWx!;C8F~lSC6n3`s&m`c~&!4 z`glEbd;*8M9-WS-lco;PB{ga8tG%?Z6RlYbMQXUOzj_Bg zb6W0CsMpudbfOxyRnOKTy2+=w({4#2O_sQ^jw{JQwx=V`mh^Jq2E_A%s)e+yzWwmC_ zo*KR@KzpVvqzQL6k**snOS4paU5n~pk=DDW%aZN`^+V7o$+k91?)Z4=;7ZY&#Ph9a zp1zvk&^k#``-7BwT0thf8Y}aXhHF5BLvnLkPAAote!6nuC0X5nymb6`R7P(tsoB0Y z(UC9X>!s9x;a{c07@vibgLM~*HPO}o}ivqJ9x1P`C;Fyv+D_&J6aml&Pj-q>< z+)uk{9ap3-tLmxk%7y8!bxoz_SC?ilc1x<>O0N66=GL2!;_AfS)%4w-3G$#}Ux{w) z;dsYha*8Ktr!Us0&?dpDG}GCHT47sC89Te0&T!l4+VXO&{H)hoS9JI!fgT?Ec>HIH z7@J+I&+eylvGKG|f*}%l=&Ng)WY$V${PgYDRC;qy zGF{#3kW@&x%#~}>B#F3NTp!Ger-yFel6@E4HO>B`((sq+{kI=o9`pO^i>d>(Pw6YN zeN~u#O<6(99B8czw!z>r9F?IzgPjANZ%VuwZ>4<7h7#{x zI{m_o()?F8z1%*N_NfvnQLQG*Q12B|d4x+g_r5EG*45PD(8*5itqpX|i&1jmZW?`X zW2vlqT}LNJ4swq9=g{tZCdtG!&GbY_GRJM#Kppxsm3~VWBYrFLI0;q`&_89zO1H=; ztu!rMQxvPNquP|zMgvRf=H&lL`vR)5m)=VMfGjfn%L)0ntg2Kk<0Av}CeVU)#>%## z)igRyCaE;1t8Vg+)QuH=G;Q1d8n4xMkPxr+j13gCg*t}vB}%((7LO|BjpfzI{Bp>dsAJ#6Hk@&?hmEe{YKj0 zYFWJ=n@-mR2FidjzFMO8cqjSWDN?s-77YmBDY-ZojhjA_VZP<{c+(cL*X_2v{k}my zO>U=~UzO9_$Fl0kCGqu6h9a7#R!RLjq_$?-mqcscnJSyUpK!Wtj@0X!!gYPCc)Bl5 zM=j@9NxgCx(ys+8YD>4?daLMAY1FN>o>&~Ll8`!{t7@jLzA1PI9eOOv3gvbY@r00iCsX@r_bp#vs|dvz?aP-obGS)RI>T zF3ZV`v*p_F5*jD#P&t~TnKsy+U*~kmB85Ud)HNqq{-pF%zj^62;jwJm^hyRj*!P!w zPqJI4ME2H=Qyc5NO5OGLnwj!3p_d+78lff9wpO?K6}4APdHs=OfIfe4&A)$6)u!nW z%8$Ai<$+r|{{`7R^`GAvIlrsFy6;J%2_{w6M|BT7X?C2H5%ZdA+YIv@uk3fkGixhd zasGjPpYmIFX89=@_Gi+(PqsM==11s)jehL+5dC-HnuLX%lg#doG(m;_I@0I7OgSq$ zLZ&&JJ@aYfHa}(R>ICZkvxx5Q+((OqN6FA;L7Hr226^1rOWQ5&BinoR)VM+2^!}9O zx_WRHU9jS!Q{JP8R;e*Tw(p-KIWwG-C#~bCtJDIyk$1mL{eD`uF{=&kQe4*ib#xNt znFEB&r^*E897Ir+Dban>fNuD*RT>tHP^Q5(NV_2Al?!gsf1s9j3$ z<=rHe(#4AJ#6o>dZ8&Rafp!_i=omabKFyP2EmEk&$UOO1a-XbkJij@qOA;PaoRn ztjyu38!B~lW{fB#KJE+U&6+ye+=-+6`=!&rBuzfLz10cL;JCeUXYXaxh&aFH^&&@q zhg^_|BP}&P^WWuO8Kh~s92&n!dtJUNSRU`pqz?~umkNzSo#tb>PbSZ$i~knVR_)xh z=yp#Tx+vP^a`n}5M+$3=2sbCu%dYyS?;BaN@TPQ6epiO)57N)+8cUiHZrZ>Y&&-GATBhB@dTx1 z^*)!DJ$%M_bgjL{QpIxsl9bUu4by!Y3Y$<+($qb60wr(AL!JS{@q{*>1?=~HSekE(jB>SQUoZMKuI z=W%(PsF5a2-CJv&imR!*CeRAcg4Dn1H7999Z=KS=qHb7oPZG~9C}X|iXp=G@<-(pF zaw^U==k8BG$e&7%YT zyUQo;)W@e)*OiZUOPZh>PTCP4T?0xK)RmRrNsacYq{N#ldVX`LHfR&Bvi5=O;rumy zQAn2ldn_LgiC(K*%{A|Opd24QP<;+2))||!=n&7sveSKz6FoSS{<$zZ2eUd3LAEe&V3G#GWLP`F>UuQ4RA;}i?m1`{v%9qh$ zn)_lpJw#0ji#aI;HWt$gD>CW$g@vWg-p)FDAHQ?)t*!Uscxd}Oza;;wg!-=3UD>&9 zhBS!qmnr&Jb|w|Q*KV{kZRIP8Zr)zquO^f^Db728{EkV|BROQ)I(I#Oj?XL_dSy(SW zuk3uy(oC|Ztf|-LMrq{33_7#xFUis(t5i+7z*&BaJ4V+nQt@D0{k$cWzzH#->2Opi=-Osh|*f6JMfju(&dw>iI>ZiwNx-?mu#`-3EEgk)=g;TEQ z6sdHmxlUVMOy`&UD4TL-m#za|I>U=)*ZzOv>FBYUb@Yb3@{xXis;rc$Qbr3EarD-X^jiB*Lf=HtbEM1m7?@lI!AxJt*d97RMCf7+iT{4tgb-sY8P}OugJYu~oNvRMW_~@?Wn>bzu zPns+vPR7WKkI{OZyH=8oIqCb!^hApBvbBGVjJc9jyWi?6tLJ3XPrE*ee_S^`zO}9< z$>gOig7-P~%fFIojce<$NMAjBxV8GV>8WE!uage9mdccmQyq_>*D^nDOWAyTwj?V% zQhH=5D_J(bloUBLY0j-(rG5J3nrhfNxzRD&shzpG3^?*l#+>rfA?VMwa%V~+I zukx$(csbwcraX8bM|vG9>+C3YR?3YYBrO-GcCPO$rB6b8Xq!GgG}q(b60b*Q4L;vn zs}I;MtvhDYaVsCl;VJLr>$$o*K3QGu)3$`pqz6>GcTmE1)Yp^q+_lK9Z!*g>zD^$@ zF4w5b@@;w*JrUhR4@@i|5shl;+q`dO%B~uEcaVoZPrcREuzE>xeL3U&*RQDhRIR7U z_Fj=qg+x29zv-m$80%`j_>vrHpItr-@Y26w4<+KxTj?`!k_=n0$u;+HuqIjFQx>%J z*Ham0IA_zhb7lSMuQ}7Dr*?mn;ZIv@#pX@*#qeIvy**cC+lAB`G%&Z6jDJnaUQVYA znm&~LJM!t${LAE2bg<4IkXnltF0VbK(rP20Kn?k<^802SsWrBTeBJV$-?7KlVyj=t zf|+;aTGCD$zkFql?ekT3HtDNhiVt=TKQh+Iex-q~Kaf~b%v>v(-uP>u+Q~KOVIj@& z;+2$bbyP|`d+($f|H-*A{f^97sZ+&ov^<%&RSqBRt67$E zPr6sqai%5E;-ix};{wU01JuOQ(~%#v^shEQ*kAB(>Svu zwD*U8dirGsZSrEJ#HpD=S04ZA9E>_3wIUu!O!acoppTC(FXiZP`eBDHKjdM~;ncLA zTKwA&$&}w+67{Je*W6 zC+Me#9;xH5*9Mhh9_05=N4n|lr2W;a$R9Z#7+=QT?xLRab7_trO*J%02kG;VN6(%yrMpxEJ8(VAU33c`6iYam~ zs-VUZM1`FjWZXtk7bcNxeMQ>0f(?PQTNPJdK3^>7sykADZ5E9=!N21*&Zo%+)z^nV7CSqdwotz=UG(mlVmhhl5tjt_ zR$Vkrs@C}?hw`V?V<$4`=&8e`NW&MBb^l9=-`HKZt|=xXzWs9w&8efS|4XC!kA>Stq|wHPpvftLYT~XuXv-vEC?ELqDHL zBQw0Vxti5D=A7?ZM6Y)EBg0O|lecqwYp0Scq;}i~^7ndP?xqE_#)0!PW_~tjeujy% z_C^DZ9++C+bo?VB-VOEi>)yJ}l~|g$tfc4tYsh7+&x8?|(C?VU{%gZ)1<#_E= zPK$sbz4@n=9_ig$es@|fy;28iuC>W!WzG0faKavE*XsVx!OxL8yILzbT{znHVr(a! z;n_f@BwIz@TrY3#*3g{XF{6hi(H>X4HQD$~8kiWF^Cc{Q0bR3mgrxk^OalhB&{Ju`c~8-L??+q5t6nAb`LC(Inh>nZrzdh! zly9I5((IIUi+bzBuSs>x>tGEGd?9gr*U{vzIQlhxX3hO_nIzs>S+6XwuEmCxbUnOv zP}T>Ya-ut?(U0>Zb?%tX+HT_~+4bIC&qrm}u5Pbo`jrtfWWaKfOnvlrwxLq6@*(-z zw4SD{Jx8nKhzcHLbCvt5bTyD5quDx>{_lyOz%DrwevgcfB0xuAbX! zYdyCp2{@Ea@AL0=&Z}@e^RuW{%;Tw_cSLCAseU@V%us2bqqA0CI9RUVd@p~iw$^#e zL-oz33fl8S8;u^&L4RJDE79?a>GdW-x_oCteRw08{8uKf7L5PR<-g{D^U!0iG?=%{ zdEpjM^EK?LOE$&V6;-x7@uK|nRj;E?P`C8@a8_dV+A>(OJT2^W4DP2zN8XjYuLn!U z*=2RO`xDtxYMYE5*IpO+g=@=Z@pR?4Mp|!Oek~YVLvsuZ(6;k#NWrrOB&2l>oiOpL zQ)*A77RfP98hgi6--Cs(J!*3Zu}=#h`jb<2xWlKGjpylVc;6>#v9)2>JcIoG$aHZHy2x%`KJ zJ5AuJBX1PYdcDeMwVGj4^-oo)+^oBvj4h=5!#ri=(NK+hI;R#4UN2$Idpa3zR@bEb z8%xi(g>}T=MUrfB3LTxRmH(a9zFNP3T79vlf|hWuOWEV!q|n_8dOqTpe16_rr@4Z3 zK>mK}IVXd*&3{J@ObS(xscteNvX>5CP+oEj?WSX16xA*%SIhgHS0r}L5oi5>MP&6; zAFVttzMjw8S$)0F$ak-^@?&0cy;^6Vyg9i*mb(?#C7)Ni+I;krhHtuPu?tl+dHxsD ztmry{yp^B)J9q;V=uity1Ldn>Cy)!gEelPL^^f+J!$71rg?u& zac-xoDNmng)^r8p%g?Zcl5$_ivfXh7Qgp z<2N-CCupFo?=oEq_bDb@GsKtQDGJM*?z?3Dgb=MXwFjT^LZ{n}5Pkctx30fiO-H}) zq5Zn^Z?XHDY8rlzIY;Vi{6HT)%^j#oWBz@(ZanF`*-c0Piqv^CKT53=4Q1caK=sR8 z)0sRcp61+;UZ=-2*Pj0FTFl=|&kWw@RP;_QMP6snL(4mB=Ai$azegWCJ1Vr+d+SnZ zn+GH0dd2}-vT-79*65Sm?ByY`M@#Fvf4Ma5>nJ%E;il_PU69?|AIq<+Ep$hzS(3ih zDT(*x0>94+*TI=%WMAg`+Ab!CY>o2K*i-p+;LFt#R%EF(Jl z!(+7Z9$IdAdr(#@<>u z-WK`wG^uVM9;`$9wUUsvjWwZE(Nukk$kWu*<>I*d5?ykDCSOrW7v^fuFkV#4EpDMH z+hviEGOaaL!@OE^ZEhJ|^8YwG@3@}7KZ++ZB57Hry@V)=>T}PLQqe@EC=FUF6{(C0 z$;yoET_U?-W<+G~z4ArbWXt~D$M662&inIz-+N!@JkR;4(){vABxrW#)Wu_XaL^T$ z7eB$3vR3qNn=4fN`0&ln2wrM2ftfFRuu1k3?0DqJ0-YmpR}zTN?#Ag=zKpe725s9x zeAgJmAprphYw5}dS>&px<7q!c@*<%T{N>P;-}Q17cGH!3>(W95?c0tiqmE#}Y(MD( z45d%322HsDCC@w`LZPW0#e?alEFAR+{hMb{`;sbG zx<5x*KOcInH$p{zJ4U+&aNS!l&Xp|Azk=&#a^`N`nb}=WK)qlXdxi=|6spts#srN1 zdJ9=r_MGsqUeWBRzC7gjg|90tMCuAJX7=5JHqqA1_2~?|86U*S;ivI?>Ui;VMgr!vy^cADS0mCkQ&Aps z0&BIhIVWr+E^Ypfhf`PM(%pd=ePliktXqz^AA2LVM>4dXGP!ooOhg*_v$8RT14~Y! zTieIDP?HG<)gky^QYWIu#)`W)qG?#~#KoJ(QvXi}_S`s@D%n~5J>LyqE~PN|#dkR0 zb{D75#0%}4U776E1E)`W@dKL zJ?gvT!lfvBuK_~`sf)E$<*;3T5Uv$c|ILZx-*HKtpRgO2)-Q1J={D&8d@7o5G3T6v z`NHMt4t!L3f>!-*-R-uTosk~{tlL*>Bj(8#EPN#tVm-)Tb4 zDESN)jb_V(o-mWzp6h?hd#eXnq_$usng{eh!hDZQ~=yqP6L zRBIifwsZ#EW`9Ogw-&4mv17|CWyCdOE7|4tb8eu(K^I&=H82F3Sn z-kkR|lozV~sNJz137hq))GM5Zwkph(?`l%ZXx`BN1{Kkis@{Xyp;(iPubdVOb}vA+ zPCvAg>qX|FjhInu$|{?7yx0E_jB_S&d|EN8Xhr9CgIKHd7S?{wc%|^>_6`f6*wtR@ z#fRX!WC9;;Sq``H9@J0VC(PZYKb7Rf6SuF!+W9`r>pl40{)b}Rs+OF#J55-J3}Er@ z2`H9~Ld&-Uu*9xGq-w1f>uaau&l0JJCp^T!$K{G)n;+sx?O5h!P2{a+gK%P-0SDL} z#FB$HFg>it`Il>utr(5KgRimwrar@~Z^GzHbN1NUmlI|vnEu$Doqtth^ThW!`%a#} zu8tKJbz1Z)vtsWXf!yL9BAKqABFJ^QxIXbMoWmslQ@2Qw?`npgJO9J;U4_D1YFTOV zPEZb5jmz(K_;C3Q1fLmBrC;|I4jn$>!(B6a?lNck%rWfG2O`BDe52GED#rpi($||8 ze&}+Lz8k`Zzd?Sf^bZ@Z<)0)v7s5FJY4To{V;f~x|u}sVy{RpoN<-TehjuG4CbL6)N zn{GQ|bF+$#p2)7}$v)PKL4JioQ zIE*@3TXD|)B1{Tg=n~eBiw=Cl=I^ucsG$zm{3kJT-UneVxvrk4@8Q*ol_;q5WMP~o z!V|tgHF`1!DKEvG3lZG&vj_cD{=(~l1Mh8`gxfuzioFZk((-Ev7gg%hrDg(;jf!JM zyCi0{9mAjoU+7C;{Z&IUj}5h;;;$Qr^nHw#mQ&>K9L5kwV-#t2qPy*Bachtk55!KT z!32LsScJjXNEL5GXJ90C8Nb__-Il5IZ&5C?5A@@neNT`+_B}q|p2+IC-7&PZGp;TO zWtqWL_HGQPS(Ymc9~jWDWGUw#26q|T(?u1v&U8NrddZfx7*3KnOlbN@se54H@KFL*VHu(Szll|B@>m{ad>m@w_saa3@j!kN9(X{F$*dUoy z>oA`6E5YWFa1IEF;Lo4O;D5Fso@u7zMoDK{=iEe2&Q}Dq8^}QqyU^0efYrm=)5qf< z>Wizzw$}4;t)Dg5>0HHOts#7PP>G}Uh#xQG=%ixA&(~rwGR&3MPT^d0sF#>DFPLpx zCUSLQGpAP?y?Nzm63ph^gMY7RzE={Q6W^XAjJt8?pgoxLp$1=dn{w^@LcG{_1`3lO zIJ190iod0!q~4y!TD~k;(oh4~>}r5!jy5!%y8ucz zEji0VlO-Ga(Pz3E8;;pZ-)@zn-M^94llrrthO5}HPcWux2%SP)*nIR0vF>&TBT`56 z*NJp4oB0{j-(>Jxw@AjP1n|W3E%^L!6mte$MM;1mU576~kJC;JJuLO&Ely0`*aL@y z=VIcrK!%tv5KFqP6z?K>2@i`0h-_W}jY+1w+-A9AmQEU{={R%6+V|)sb&iAPsT?*c z6tmjgMy~{Q@!;iLVG|-(tE;N~;B^R1OY7lw<|cA;rlEBCcQJ5=Bllm}CSJAJ0ZY?J zy07>xM6@NnzW1Q(h&`h6>rWWW_&oN zxSP_AS8kohkQXHw&7TdvVGtTikl`OW369uzs;4i#l4e<)leC z?i?pFvh^k3>?Y^9{yb@y$VSQc=yZ$Y+Q4o+q;AF&sljaC_!9a7%1mCeA8}#j!ZBwc zM}8{D^W$T=$7lvN7ka~LViayoxg?$^n6dCju_%6b7%!WTViS25_m`aU%J!+$nH|KN zTU3~>*om21&H1DtgiCZU2%q?tEHu*=#^<|o`nS<^Q2BwxPh;>iFPMY(j^LyhJ8{J4 z7e2jp!4z{_)bJKscGJe+_m0qcoypM0!_Zyl13qedvv2iBRIIv=p-oStOXCK_?pq{0 zF6QF;;=9-zJ{4mQX>m`p74U24LDP9zwDdj(r`NX36!9E-T9bFL>*MzEceowyML)Ie zLOAS)?z&hm*_276Kg8|E_H3v~;kl2waB?!`6`KhN@OPBl>>qJ~f zQ+QQ%j>k@p5Y_)J$0XIW;%h@+ z4lqwdn>oL5y2Vh=jTpxr1y$HS!XEFUGU>7P8J1mpjw_YMoL)9cln(uYU*lY;x@a(` z1g%5l$pwxXpIacnGlf0|zI^feKQwVx=f5pIsXaMQbXho@!RdFLTFLXDD11nDrhTg1B9GQ_V_9;ttb74F9{S6e&UM2JIqwDB+WhhsA+(gD} z$p^mfi3Gy{?o+-DrQPdL|4vt&UvGo2DU*tEVQ|m?l@6o7#y9Z}iS4wYCizWtID4E)xh8EV?k?~0EJai6zHf2zG zzX_l8r||iWwHOtb!37=9W0%x@9~o$JkJNm=V zek>f1TgGLI-!>{(aAY`P5XM1iJE8qMSM;9I0;=znc-6Qs$2^SXio3q7?dZd4hxK`( zei5S2PvI4}fjpU>LCv`$^KMMGT?8sb*&SmRNq;wunQ`%4WZMQ04=fagb9+D` z^Bn!UDCi>J`Hki-O#9FkM`vylS>Y|Yw^bQVwja)n`~A?vP;#HwM=~ZlT{O89%#JIp z8FX|2|Mb>m(v`t9%C}6Vs0JTr8DOc)8nG_Skd{{SF<@IU&fZMs*u_dT8`_bD zI(A$-JB$Bq&m?MYV^+-n9=f>=nxHuO}+*DKu%M@yN0Lha{%GypQSk z&!VT9^u{+C(lDVPt~~eUqHTK2doU2&`inSLNRdUA`ZAR8;M*z@3x6-W9wSdN*E(B{5BCePfAO+PDi(riQRYBa*jn??j%1I@4x1XMt}DoWhgDxU_nV z-J=1Mx^&LV`Hro*>1@$yHngvP5V~Kpcy{|^OzLnHJ9!G{0$!s*`slVRH_ClSvVX&! z8DYN$4=pOtEl8Pa4_Apr`2+a!)J#R{{QwTWb4Fyu1~D(Q3MVu#iFnBhUVqV!J;IgQ zpwXN*f7LMeZXlm@PQ)a6PuC{?#O7~JX>xQF&nwNtUbB5znPW>mLmv*?n1{DzlW}~Q zpq9lGJk4GKcjr-@Gq+0kmn_7x`^TJ$Hf_cqojzE&#EHSCW%yV36_smF;o;@e_>fzV z&*v^7XT=z9Uel2mtlb!@84sOK{tS8J1GQY(%G%y zCh7{5_}gJDC%@f{-ysGNpM1rMyEovxh(p))c4xmvMwLn#|B0G@9XQ3Z zEu%fWc`q-GX%`*2#O4QTKNTx%quto@WD6*YN?~~S3Dm5VxYtIXueR3+6MY}PlIM@W zr=t<9J`+<8+*O3i=SnR+gqmfAIDS=n9B-4vA)_k1z#r+wM#H-?h^}>GIpsqPi=VjA zrpTG^YSXwjv^OU;jeyFXX~HhBJElI7{cloSVn7MutTRx2D8}sh;&uGdKZZ=eT zy$xON2yVHZNv%(k-O3LTQ^)^PI39QAwHe1mz@~>NXon1 zqi9_-iaYZpho&O^(M)x0yRs6#buH*$1orD2!)Y(*f zY)OSpMKBld--duAllkL(9ex}?gfBN9Ve!>`tj)DU@aO*gcXkV21{H`sYEg7w+nKS={_a^4sYh;;Hll_gWg_^=4pM#w4157=sqAUSL&1Tdpc=$1cl8 z@q?usw;goi{^^Foe8WTRC{Co(C&>hfb|S{AJ-3yrv3-p*jcS`9err8eyAFcG@nDoI zNzTzClb>c9^4aL`=-RyudK@oxOX7%Q8az{2fuv)j*y4KuUQSbCt>IQt;|K*_>OP7WX9HSe4`0 z*<7A8RsWzdeFn-}lf6cd;1Jyae$TUp`$CzM8$3&_KU@WsO(85bi9&<_K6G&pq^a3> zK9~?qP31)PQeKRwTl>??XCj~F^9l3U z%<`Hi^2ZLN;bm|3Uz^Dp+R6O!Y7%c8nuC#vncP2k5}$1yPuqN3W)*he(GCSTW#`Bj zM$Ki`)SUj8nsI{6D=DQFBGdT0=wzjXQPyhw*x8tx!JEX4Ge$g=8c2E0|#x$$2TdSeG;pT6dqL`;SO= za0|fFfNJb^|A;GVW7zPv6R%a?#?(&P7<&FA9)4Q`-FZH&cyG@|A&YQLbm#jHBcYel zgX(uCu;F7j$@z9;uSc2mS!j$|XDJmIktO{_a$VT&(V zm8iw1rY$(_=4Wg<+YPqL-I1_wBKyu)#pbhDkU4v;VuR#W!!K*oeM%{;%-mV&X)ctd z-r+pB5RG1&kZBghj8$cdz2-Ynd}KRvdb{wyx(Ga**bJGDDtJ8LH^imS|Ihs8m;Qn3 z&b^9^D{u*(p6^BW9CNCf22fpDdX;XQ#jkrK*x#T%gYSFtVXz8jMm<3P5!+xW z*@N1wZ004H(lkuYnJUhViEl)gnswrH#TQh+$%oOR4&3=>6ceYcRTLGbGwGoY|J;kD zXOrcawtXn~e|?VFK3=q(KaSljJXonFb*JI3G&tu#)9_p9GxQCf#qL3F|Mt9|eHxF8 zy_nd0Ae$Kb@yy~PQL)%Cq8Q&CUqI8u3iH1Xp+-Xs_FEr8 zqdmJ&k`>O~WB$ReYXz2fb>xk2x-8eWVR8K_M65e1Uj5vF9ao(&rPhuMrDoTlcLB$9 zvN%HHBF>bJrj43CN1yD?Ar1{^X422B)2ezM)%d0~~H*O8qPY zzPLV-EA!?2Y-uJGdnR*Kw-<7*Q{v}oMlhC|(BXP59$i*~*C#u2kZX4iPEzA5M?t@H zCDJ#~$5vB0b6wP+!ME0&|8@yxm&=Su$w>C!Xvy^OIY3K|+ z-QS4vjo=O!eU^UcAnK(4shykzn{_8}6#KB>H4lAK>HZkenlIW zNUrzVvS>DRzK0p#-oj#>CeO9$#_boB6qEe2c=1LvwrgU}FSFO<;9(7n`u!M9x0-TQ zmjwPYw8ic3BL8bgWyR4`ZgEsy{YEw^6?3aXWa}&k~{l@%(V>x8naj18ig{@y7 zi5p{!u;rfvCO((5%l>X``Pdkp!>jP_@>d*?vy4yVIG%HE3%{OPyz=iYIvlg2_qYZO zn{I*WIfi88d6+Ki%88y!5qZ^~A1<_~NSTAf9vzrDkMw(G%lL9zoZQ%z^Q_yk!yF~L z7JKrgj^x}P%y!r?Vl+N@`0~Tf4dR*1_!zyj7GoQ&G5wP#_im6pNnyFdLSE-bqaiJp zXHjdH5(BT#ksSh7yjzpOrMgYnZ{2C!$~uO-i#ovM{&XZ9Yl~(x99S@WG*{jYH< z@Ov;1v8SeT`i5~#*-?d;^8y)vv^C5=B%$9{GscExIz4j;;g9PUe7dzO?>-sGgYS+CuKI4ulTF4e`o;{VyWuFw^lgC@YWZW)R65OW&y<5h*{7oq#x8M#UScvs z$F;?ZU@tljP~iiqRrOJ?kXh6hnDb1ROAhVAi3yf`(Au9%8_hU3xK6nBRB+ySH>S>t zV8^k?VH_}-%`|#&eAO)^wRWUU)miMGVjwe=1>)yH9nLLj#TozoMYgvco`=4M(b?7< zy2lG|PG|Fp-bk9IjVJtfh|QjnLch~{+?{O((XS1cv{)s6^_iw?$77Pnlj5(nOVo2Bxm8uXB&b!A7kPA(TlmR#%!53 znRANI;c<0;u2?6tELM49@x|+6{YV9)YWnk&??4poipR7)L-;k@T9LS+0NV#X6c&#R zcz;098 zML1jE>BO8dS?nqK>p#DZ#kz<(G2Z4AMu&S-!~GBNAsBsaoKXA5jvp1_y!|1RF(rK^ zpKQXH6N?%nFYG-N(XMP5$W;!F0`Td_43k+-KC`$bVHR-!C=H zuWy_x*O_9>twU(+Xw8cqWZvm=K1MiP2UmL0TQb=GZ<@36$X-z~$OZ>H>9d(q4T>BV z;>Wk8C~DuGjoOlDRP)DIPgk~UdRaUfw;c+%P7JK*$@im2vY=tRm~r(pu7p|8sf`6P zbbIlUlLK3RFN4|le%veh{?A!@>?gT?$D@Zth4xJBObO*RO**JNjlSQOwfPV83bneVA0p9T&dTEnLXp>-sFbR{>Py*+@Ho92eQ)oHhg|+GcUF^ zFJ7Gut<$r`x-GBJuFR8tyT3pm*&ktw)fl^xYxo{Ae#Wv?7MA zPvznddMvH~hL8#23~F@^13Y9`M5#5;t^0)i)zYg_pUSkTFLGYD=K7+?$bXuOtGSan zOx>D4Bs1VK=`5DHRbZAVK6BggZjyv;P`kS4h;|Bh40;I zHpv1f?DYBQ>@4h#?8P4EmqB^RBiwA-3Vv?;vEW)XO%pBXSsF?EdwOiur8!$)ux4Jq z9o#D?(C~Qz?;Cta-KH^AH)zRDd%~#xOnSK?i($0VgCE~zqs5baF=Og+sLJ!~{AT6*5$~lVE#7F4h62(W?sraCI3{~f*(x$r#?##E~s>o?jQf;EwM4Egr(3S@ptU}w)R9NEW= z4-_(k^X4EHJfFf}N1O42%u@JH?98>#&SR-k6jL%hI6f|rx~H4cYg9O&s0?7kLYb*N zk;JhEW;9A2$KovT;Q0{P%&E=OJ%%=0bH%S+GjQj&8>@yovacg6*A*( zbx7(Y&v!t_q8RhH{=>8%BXP3Wf|o{BLbr`6x8#TOsB?e5`R@b1TK8oAxlzb&*$mM? z;o`tq^5&1#eta@H51s{mc*?p~%noV55%0rr+;vu@MWrF)q8p1;LYRLaI{!=Kh=3?QKd8*U zxxHv%(SfBsDo`osri-gu!DabD@oQ!Tt<|nVLoLiH?%pPNsyFAeJq>8>Ta6YUI%DR$ z9*n&0!qoMJcva`cMwhC1+#c>QR7hD!CyC%UU zN}iLp_{ljv9pOFmpy(j`NZt)*y;ziZIpt_5bklCwotG?we_gPqh+udFDCy3DX87HJ9XsJ`5>cOh2Z{3$LMEyl2K zCe-=UiTB?pbIYPPibjnXZjvl|{pzmV6xEBpoSa0ci8hW(zoSUYM`j0A`RmkaG?$;- z4h^K%jZLke@aOaLY#* zv_2!V4Z3|ee{BRSEwc}>9n{HgCw{5UT-xlcgXC@ z-~?8ue8%Kr*;n=;jJFqz;x~=XjPUvlGZSateH6}KRSsO%eG;49PiE(ohr%~3jLo<0 z72^0DC@<@e)!$o6J);V%I@vKPEf!Hzq@Ulx9ZiS!;7B_Y-ibC8;|!go_Su`mj-_z& z@<`0-<%0?PJF|c2c;49b0>9pkX3p<;DDRLAV%-4jcT467b!|9#WXKLXspVhl$XeH? zyqJ*0-S5m0sdWpHJ!-_46Bi{PVvN&KiQE}3-`6#*aDVt@4v)QvOATR+Z_|;wvYTeN z;uH+)vf%S^sgt>SsW7_V7K_>>v%XS8_E>eqr#79DJa{FxpO56Dz~|!afMjm-+a+ve zcgee;aolG)QnDGbGz?k@E7J)qs91o~+`D4Yb5jNxmgD&R+tAGF#IWG)2s0tQ?7~Gi zlfjJs>dzFJ`|S5JAK%{h;Px-4&_4b>;%Dfgq=yQVep~X_+dN>MCI_Eei1s%_@g~og z9p%hrxZsW8{BYWAdWPd(S-8_*lZ9idVf8T@-wu1jT&D%blp8QcuI)@8w_}LSvhVsJMFaddpCFw z9MAbxj*PqDNIU)ET-L{$cAJd3x|7tpK8}Uj&NX=9(g3Zw12|@JJewBC?4(sI#Fr&- zi*66DjvLRJUn4}zZ)?$G$|$4z=5c4CQfI+77bqB(t|YSr5!rdz1fsLdCuHwoQ_;|cfLwbqu zHIZ4S24eUNGTc3qYJN|!^!X-a44jUD&#Fx9YDWBNz|0K;aIm*6Kla><*dje9y7Z%d zkTH|)JXF+s7h#Oe2274?$7SW?gs+ApU&>tih1J$5uF1gZKN?I9vgL-{A1Dj^BM$DK z%v_$l$8eZRGip9C)fDOG=`D$`|-p#y&cLSui{%!b z(P?A+Yq9A$akY(30^ji-Jp7W}RgN>=}{M)Hj@vS)D3LLcUj-iy8eY4g(6 z>3I3`EJCG+(ROPuM&&t5|2TqaYwqLOKDoE+8u4`YOS~&CLVcT&96v?oxnzH3?7e|> zKIty?T{G^t>cs zTiY->e;;xx#<4{417okqJiqs4bS-e@fbd?>w(9`D4Izs6yM~B%dpt41Xgu9&sj%8s zieC@T3t{NY`v)6^nVJTDV}|oWS)O>*wYyv^yYN)qI9>`}jF?q(@b`-zo}P4rLTWF= zKjn(^MzX`s^tqTi#0z?_eVClvMe2)cY@_u|xCDJe?vOwDWIKx1y@IIUd^FE#WpKm& z7w8!=f@lB6v3LL09Gzp$mIVPE^`tFhYiFYVYcqOWTCPayo{$8@B z`D3N8o4_hv?8lKz{M`r9bO_mmisTHs z=WG=x%tm9hA9=n+_S}?CgxUBhSQXes9QyhIjbmlcgd!9BlPZuKb{G18{6(uy-^GG= ze)KDm{`%D;_-&F%)%`>0rn(QBb4oGSqZTpxF&JfX6jnZx&wTF0sF8I}ZzSJTZ!wnM zhF$3}HHa&ZU&H3Z4M6*Q;z!r6_)o`>+mbu5@5Q;|)Wp^Jx8D{EPyQ8lF&R85{fR=! zua~9_Q(Tw1h=!|Gu$Ej}?Xo<~8vYWeWmZi|*GgsuyqNmpIQ$xAN61EFHv80>eb$Y_ zoQqX>Qku!btpmJg}=2lMNc-_ADMn=j>UwJ{PO~0wH8R=HloG)~sw#>mEBr@cq>|aV(U-{%CXi zRAnxk7%w%l?dauSg(i~=@o3OYyx%I%j%hi_v4~7wU6V|{r?vNrY>X9g&6vWXbTM_y`oB3ZhpWz;gS5Nx)ZG=RZZnall0m$r>PNi`>0*zbFUO8uCH5F$CY7HbR99zI4aSkRgm#RxrfK3%MSVkepS~p1x+Ml+`a@B zy-U!|YA`dBmSO+j2DE(XLzOLo*kVwMz!uM-c4H4#s<+~>Z%>@guJq>k73a{h_aKU| zd5Sq4Bxn1<9IWa{Ey?DqjbDMDQNdIk>?}UbJAml-!!RNn#^t{#8i<>9Tk`o-IcJ|(m>o%Tqiu4%84$&@LbHG6a$o%rM;1RsF<_!)R^kB^2HTX9x zn}&~@(CNWRq2Vk$6dHBeu2rm9-=ZfzZa)B@$1kDPd7Jpv&4Sg~h7C1|d^{;h?g^7= zxz~p)G`|X^HFn(i<{@%l1XAx&pk#dvsj0sfYfVSctAh${W_M)Ad#A;h3KjZpTZfPp zzU=fb3!lTR*fBur$juz#mzT((2$`F|uT7<8ZRvetGH>hlWTn2$x|NgDwMNp)xid}# zo6u%^TNW0}UKcMjbZy?8&&Nq0<*C#X)Q*a0->rGZ>ywz490C`qx1Brfj5{vpoQzs_ zWkXRN?sUB^v!q0nsKVjIe%yVq7cPFe2}jk(&>nadGu9<>@U#q8D_?Lj`!$*NkpsE6 zaWFKZIxzW(0&hmje!TF$cp`P4y+;RO=gEV3+NU`e$ehk2gAC>lkHkXhtNKTk;f$*% zGt?w^_;0A}q8!BQ#+n$fDRX)~vaqImGFNLq#dtvC+DnbfU7BpSxE zu(}A7f*tW#KaDkg=VI8+2H9Dyjn7r<;njmI`BRM-iyvTUa1)l{i5MK2%()s<>F}o! z?HU?Ty!46PF&{ z4n_HzD`HjXN#Xf?4^EAr%B*@jar?VC!&UGqey+h@!=Xv>p( zLl|)HIusRFJlrXQz4j*&*(yA6RrWq?)L`J4b6EA+m(v|$sp9kw+iUK@WZ(!MJJXp) zfgUvL62LpRro-l&>@Zuk14*;26*oVRX3#oE&TKlAKUU2G_KjxMhraOb8OUM1L%CHi zl9A6=>t)F8vs%ON**NyKZ$j7HO>lYh9EI&7s4u%PW=Y-a)}sh6 z*!LWder6!&-2|3Y7K?rMZ(ylzD7uH5@OGI7d_GTP>v!u=wLcJ@?&vaTvoEu+?m{=| zeGhbN%>!?g88M+Jb>p0PDJ+|2p5{1rD2v8Nyu?n~Z{O}!7pk3|%xx`7U=`slE>E&# z%}4{D&1uQ1nxVY5I2YkVeP~%Je_!oo4E@uRsrv2s*-@Wi8>UkEnJ0VwaG^z5q|D-u zrO48yd+}ucd*saW%?4t#JQH~-569iV?RfTgSN_mRW-I4(mdFm%9m?G#6J|@b-kUJ? zpBtaglbJG&4T^IagZbk`FZ^?wfxb_!AflCmkK}hE>>GI(Gq!26EVlq!b}MncOBw_NY$Xg&U=-VAkZ72=A#0- z95cs>tP|tuEYDMEiyavHvL%aTkK@>bdtq=aRAH~-hK)-#rdYa(Dad~%+{1c z1YU;Ot1WOosGwO#Eo@onj=Z&Z@cE@SOAlu7{NP16Xpz8~vWqv>Mwxd{=<)QX>%y#P zKbqf^OxTw!Y}Osan(l+qVa8@`JsZi_A?a*dKAy{d$oVHVhT&D+`1Ni+UcPU|LW618 zx!r~1TK7bUdByVkBZB$!dW6gX>QeE$QcOFu660#qdHVQx*lNO)vb)4d_N^>0ROU;Yw$$1%g*J(w z#nInc4A?t}>4$G%xwj_O?%8qh6?fh-c#262^f>-wbN+H#Cwo^$^V_4%PHiGOaPt)d zQTJ;n))!=Ri0lxnTm2W^;{`tT&=YIDThn;#S}b0ug{Ph|bj28k=&5sDvx&6uS&l+| zYrfxA0_5+;KqN5TA(gwfwBqj_Kg2e~03jggoq!oxNu>r!x4<5cz!`SK+(#Bf{D} z5mp5@oSKp%+Lp*=&3FqEI%i{WMOPN~v|{Fw6n6S+%5jDXto`Cbht{8Q;d3YxUv+2c z?jU|uzc1>`l^A|UgAb)1^!3L}@$1GtVbdd;Drx7CmZUB_xYpy;Ie*TzAIfm){XdQF z!YvzZIAl?*%o~MpZ-E`|^t9wH(|-KkV}bNHZP0ye6-LCH@?pOd7_QtKJ^#C?$eP<) zJenfAWal^Gy_vFe-fa)=7{9{wl3CcCkR>eFWC^wM9WX0MX3maerr$GCq)qdrxyl?d zy+Zai%TDYmsjn59Yuz|Cp$Z9g@f>C?yJU-0xJ7>=_g?FZxb10lc;SYF4`p}a#|iAE zsfp=o!?>w=K8lAv7uNqQx#>&<>t2U5Ep!*gs7BzCeJqDNY{i7)<}|VN=1iA6;?#pW znJHa~z|_;2Z+!$easoK~M0f7Ekjdl2&4qg97SX~k9~IM@aj3myRKq>6wV+yDue*nb z-`|U86aP~zZlBFhb%Hfd*W-zv0~-=b@FYVMkH5ANv(xhM%S5tQYIE_@@+B18lBrgG z3%%C`GvGfR#L8#(*kfZBPWmGL*|$g4@B*B5-6+D}C9-g4TPE+4>sWmZFDsXeuXjgt zspQ>HE}bgRRLKbbKAGxrk7+fqN;I2#6)%6C7Y*`RQfV8`%ST3_yrKnE9M0nCN_)mU za}k|Nzac62EaL9%Lh=77IuE~``!9^QHAyAy(x#F&b$`xPN>&=0q@kh|?Wtw&k)0$X zGZL90du9~bo0L7`vDfeX{s6Dn<8j~L`+U#2uJ?O#3|hJ#q*>kPQxD533ik0t{q>n* zHb0-z|4oMWEDg-a*g|=($*8H=BKmE$!@}eVoVSdjK`A|G-2yG>eIAY9qfN1JK^qUdp@jh3~|5{`{3)nNOuTH>hEuJEkU= z;FH9XxjX_+b<9#rdnQimN8@{1CAnJIQm9K`6r8D{mSeq1Lxvf2my2*?x05imVivBQ zIv%gHLiF9Y^d)*9RmC{tVD&57V1Jx07wVGofD?2oXt}s`N)|(#b+CA%6|OUT$5}xY zcV?JDj9*9&SAuZ=D?5N9-SMk!jbuD$z|0@n)3UAI5NmXcKFVuhd&C90oH!6Z9b=Gc zl!vn^)5WbC4V*4?!T?Vv3X+rtLNSreAL3+$U%<=sp9Cmex zG@iHgTl!%h^IGl~l)$~zhb}Wi+Gbgb@T*Cq0O@A({Lb&8!j@j!w^Qq;?)XnW2J7~F zira?w#d$poXeOA@AyAt5JH?lhbW&Rzz>J#N;;E4-=IkuR*4~=%JUJGjDaObj z&?;>=%~$!p;DB3AFGZ{3ciL3x&OV8MBp-2>R-G%Ozn^+h$aCguY6IT=vN7qL z2N@O|rDyVkG1zZ16yS?)vJudE$63o$(U|7lL3cJBqw&)>lg{0hEv?5I7;^5zp z_ZQ!b$non}$;&D=49s`Lr!q4bZj8tAVP_?7|JBf_e+3xzPz@iqcwmv)7fPG4ggW>3 zC&OFa;m>`8@j0CJ;C)>N`wAj1uO)rnd9~l@ikT7%)Rc}y+K1sV%#olexd-_!mcg^K zt8{U+3Tp3o(c}BFI67jzXuPys#O(LNsjvR%-8`MfKWm`HBU&UOoNo!yTTZX|cjrsm zVE1I+OV)L$5tDFG+f9+=R98|Y@6BbKR#2B&p*S(T7&d((gk}FFWV?p*rX{2Bn?H14 zGI!ASLM5_mlfvt6Q)t;91L4rNn1)|GM60X=u^ojk9`9hOY!!8rDW#HZq%b8 zQQV3BN>!ZOGaspm>uctV%2n1FlbVHp30KMNpQQ+?c;FE~D-XA$1MuK;Pcd{?5*|O9 zP2~zP_*VLuu6(>nHf_7;y!t?6H>u!AcST{|pSch_z3}Fb1Oa}lg_-jm z(LXsy%v<(RY~=fy$_aD!JQ>6KT0Rb$8DN0YQ^~jumr2;XrC#0F(E4fmsEa8PdZuwO zxykQ=DxtW=@B3W`2I5j4=VCU+LCbSCseD{bhx@mQnX49wo6Lzb;N6?r;2Y$SZh<#_ ztuf@4HZ&9aVaKW&q~axq7m*53Y+XWuowm3#q=qhk)yBQyf@Gd#B2luR{ypj>DQ^XQ z)V6@%ehd7y%Mxw-4Kd8SQ*w+MIiE|rL+)QU9NspEq?7puI6V=oKE|O?{l27HX%3B& z3PD4MA?$J*=v=XkWPe!}nqTqRq6erSQC13n4GK`t=NOQ0-MEo^oj0o?&ouz8k2 z-~7hFr!1bF@3qs`EmiDnbHo^{Ad!~xnRcH{LU$Vv&Q~ZPpV=8cv*dA^yAOTly{4GI zp`_45um{E%o+q3z^V!mxzCG~WT(Q$$iCxB$%5i4i-gbb)(qj}Hl#90+OGxRtKHBvQq3rg9 ziuMGMJMRbYjxoj*8!0>sxl99?fxL2vBJ4B`De1lk_RR(IE(If|)d+5Xlu0f>6Kxrv zm@9LN0xpci`r{f{%D<_97q^jB{$6HOJK(U!H?m)=hKee7!c3S+*HXGsLnE`shKG^S z^!)Bl-pv2r~Sie<@99enm2j;_+CaEj3QzGFd8T`!S2zTcuMspwOQ7(Pu9~j&TQ>?&>u}}dC$)rWrO7JBz4ORQgv}? z{hJDzWM|qkVY9H5-zq*R?4rYoQ;2GuVLayrnZ}HuvSm2q;S5M_>XrB&6YFi!QU%1;RAc+a<+>}^@{NAJpezq9U{|14fOB0 z8Pyi^yr%V&-X{`0PP|K#zuKbbP%GR@^`bVDH0Db^6?4yWPJOE$DM&x3;Gc5%>7tCA zrm66DOcS01$Kq_q2rQhD?QtgA2|jN6$WARm@mEu5r#Fz<_5p}X*i2-rPj8|sh3ctZ zLe41!S+lCh^-(MZy&eJQhauIT6PINH-1SU znSHcKvj{E)UfA9o$?t*O56R4j=Bf8&9>87VGfDIyC>ztrOLTBf)Y`F%A42(WU-)m_}X3|c=RooN{J*ves`R9@`xBgJYj#hE^t`4#$wvujB z7;2q@pvm{oS5KeOodsiDl@%T{uU9%;n=-8A5Nqo+?R~w`Fs)C|Hm`nVpSYp zUWkSX?&we~q`&+5UAAsNJso32O+829jhPghI3v`X^JBQVea&!^y_s zSXLiJD}))!o83|1uZ{=oD!6xveP*8A9bu-N^7=;-Sk7%sPf-uZ_M{gT6Ce6=>sE&D_I`?IL=UluB)Wa-08-nDqz zz&*o89DSq&rGW)dN@^ijo*~aZE5f)#I+*Oo-SKX|6m8rEXQot8{UdK`e0Yttuj~+a zb57I9#x=A-Q3d+3%GBc}^Gviz+Bca1qT}4+ms$SJ5w^VxDF-#p)Xd<5#Jw( zdfr7_Y=}XO!edHp?uF?*ms^k)28@`9l6_hD>0Tx-PT3$_RIbo=o;6>csiN@VEo9Qv zN$c5taXu;+-2Yyh*`pHL-is2Fc)VnsEM4P=aYBHDphr6J^=B&{0W~Gkd~Y zRuc_V>!@n>DQfIkL-kde=c4SDz_i?zA^5GZ_cE-wd7 zTi9dONi^Q>Hktgj!|I#1G*e+ZwK}uU@9JZESH_IykN9Wzn z`rXpFKI)XP3^tHxEJ~t3FPI6n?VltuDi(L{Sz-xij3@T;mS{WY(D3U+;V@Q~-$ABO z=e{C@Jk3Ummk!ESHcBdfTR>s0HGbMM8zRyiML#>)&GU|4smZ`@uOn>YCSd{R^Bn?9J|_lzi4SVC2Ym;wA=8WV;sq=>o%@pT91xc=xN z?oI~&^yvdrt*#hK)2J)Io0mHA{!HZ+$=sbo!~2~USufORgm^{gTyp8tiZDuS>yW%U zXGMRf@_XL0F;r+VhAvL%kL1-3So_k0TGjeslwWV|X-XmRIxu@2b538`(auY2=zZ8s zkG(#!BJbu;@*ZUf^*4by8*qf3Fv*;8SWD68i)j8keQa3SgB*8j!Y%ELczXIJ?cceX z+++ff@%#gwigSV9(h11>5QL}gp~y(-hQ5x*B4S!7Y$tWWd(UyuIs1?H{qjVx-f;N2 z-V+9#p`I(Xlr*A#2*({#w9z~P502lb+eWuV#Nw0U+kOe%p5-p;H3p;7v5>iFDR}UJ zI|$vrk;T!0m^-JZkYT>$#g#TFnq-e%)@D#-*8k>zU1(~FKSutlq`60z&@!hE3Xhvh zrCqPmx~Hp!g`P4BCS9g$y^7IL?h8%-Yow*Ph5GfhM$BPZROG%8=eO>WWS`(W3n_{T z1GDI)Bo8gCH`3+R<8kcuNwVznndCP0$EisJVW!qdw*R8h%GsC7MFViPYm|6>&;Xm4 zRY-of=wVZS99>^uh{FyApH9Hqw;=H-NPI`=4g-|cYW6Lo#irTpI{99s~Qj872COQkc|mD4fxKI zgL4ahlNIwk?22ady*VHK+j~L9p*LvKc(Hq<4}Cbbk|fM8i%8f)<{OL2uR|02UYOy- z7j?)q2TGEO^T4~l)1MVPNb)fnfr>E{Gb0cB4SR%rejaif&5(aM83!U$@YSvuV@w8M zX@(A!+7+PjK`o`MGez7W4dz(rLw8D*c)VjS?YNMJ!-EA5yh_I6zJuu4c5|2no~MwK zDO6^XjZ+b|61Sp>NI3J1)VBqo>0UIhT$jh8#pU$S#U347i?EMB*W)CU>BFpE^qB8@ z>aTdGXSj)KxT_I(u8R7qYvIVa$+)p{jpSacHyPecVwdbX%D-4is+xmQH%tvTk450_ zDLV@7T$0KUK8jk){gf}~? z($xlGjBP2+UVehU?)pLRJLF)OSucicabW%tXMpG3qbt89sJs-9n{VR9^GidR|64~r z?aRdbHg&%7ts--cL=@_5pm(~pw6Bk)D7~mpGlvhQ$R}GRJHJw@EgVdQHoQ5x8&$6r<7EU=rt7^Z| zngQ163t$scB#MA(=b71vs=~faI#30nRgzK=XSVS+0{ovvDrg`fA{+e<%Gt zY=NoooDhBJKU#Q*e=~U=G4DMT|9-k*4DU9^Z;8g?^#c$;A{{6FL$US8Wm?sjISelT z_^6tQeIpW($+usfsMB=Wif>ZV>ev>?|7Ko`gvZRDWIWc{!%JEfuIx1UGxiA`?>z** zt()jVR!>ZrwbsL$-BRn7#>47MPm=OeK-xFGWG{~HD6PkHEQ&ZdgYR_gTY#%#lH)a_eO1TIYzYbM8Gl@HHpZu2Eqme|tN8{v37 zue+#c%OG_0@uj;jRS~@` z1scVDD6#K7iG{)ha-71R;w7o##=v~6ZZd*l$S?A0`9wVjt)-d=6Un!~3hh71o&Cf= zqWs(%seAWuNo?WnBpIvd!N5jdcLC8)_hN|=vs^Y!P;Sc7B8)A;Qdz`e>vrzLb6vH`l?v!kf ztdVELf19q*-_L^mNM9tja?@#{yDwgDW@ipZ1z!HAhlBuT;v1i(te4t27pH>X%#yjh zCK5ISbD-X6Pxt#=_h@Q7Kn|}15Y#0e&zlWVu`CNL#f9q^vJuf?VPW8k{E zg=}tI;+$MJ*u6hQdJcPdr|^~e8s&UTMuh{<*`1f@N00$_UUsC`ltIFZncyBuYS_gfHh3ug5h z4-41vIH$INUJjUuaRoEPT+QzIvHUO{lpYI{BT=-z>lfN8m56Ho&9a|sj&(gpQ=bec zoO?YK3%8b{dgcRC$!I6NdBhIkMzV3yC$n=`NY(R+2u$b{MeXdc3%gBEOjnAnhT7s9 zQpkZh>q)*r;>{iIAKn^-6F#e{&vs^NxE~@9tx*_JKLTo1OK1b%#M2iRqm*~aGnm^P zsp<*WxenC3pjr%697BDRbnxQZBl=2K2>rH}b7tS^`sJ}G{N#aEI$NmSpo3bJ6tQO3 zZWOE@4r}>ILE=TeK1xnIY}97RWZ0uk6J^F+2`viyykxs z6C38z#DpcxDI7xwpYv|%nNsC7I)?2%Mh<3A-7Gjrm1W>aR=U8#CP-n)i{(s8(9mwO&k% zh!W*HGBNn8G?|_LOP9Xvpp|;FX=e}aszxwdCnp5?XRKhp!x?=uhG6K78oKvF6^5L5{5r5|xJIbmV>t;>X^h9V3TBBb+&n z6M%)YLi+Cv?S<+EN2ss;SlaleJES+WOGwTaS3@GO-$;U(aomkaaKjO|`yNUe@-(t^ zyNJnm!S}Egq_IX9Wsa#BG9eWq2i@>;Yyx6OpB%8Nx;w^KxstK80_3Nc(XwE75(oJ2 zIg|G}b!TXost-QJ4aMV+5{ww*0$ZC3S}=_rS3Ta+n*E+=-=+lL>QGo6)1iEod9-Zl z3_34k0f%mTB$1sxST^0s43;$f=gVy8tQ*u49!TpQyCbt_1RQch@aS|PW=HoI9h>Da z{b-=%nqC1^3wZC|dj!vOU2)dLlnglIDLZf|<}*)mwuK^EcV}YL!Sy1cPziks1x%KC zliANU`m>R_aym|s8dgbO?W5TZ@QI8Z*HQpG`PRraQf6-%{CvKFLLX~lUf67LFsv6; z)3=aWs}`z{<->tHbVvFBgG>D}nw`m}p9VRd zA*Wv<$gRFFwtwI|N7XZ$Yh{n5HVJ12bf~yvJ}p1Qy_Z*B)YNf+o-gW+*hixh<}*>2j~Z#1$4R4B?(4$@B5lp8KUxpzdfOCRd!`iB(K|KpzPC=|2jHS;;|qmq(w zt9UWJD@;K;yTktOmBXY)9rO(F$Iqj%zNeD+9BxS49cG@q0`$k=xEJr z$-6Em*~xjAUM3HK8fTlTxrg8R^C3k(kQE7!bE)on2)(*cN2Bd5@sasR^8SYOVTr8R z)R2vsGv0JGD~vME=_4SeA6@*Wf+^Z^q&VCLPDc;Wu~6pbh6Ui$bp>R8E})Qg#n^54 zgPdd7!E|&aI*Q`R;bbXlwWHCp;|O_qoDfehrHKjS@}QJ*f;#?;f}fl%WXD9HZ+NkI zeyUk)Pb}a$^qv^g$^3#%GSIlXh3fs-rQh;=9NCWRHos`m=brZycaZS;uFC*fG*K=GxQX}nJ~ebcbRE6863~z|sL{>F0s{G-LZPoOzf6 z@3m5x$un9dcZhy-9)4Z4KV6PLMj8(s!z0TWCC9tYDYwQ#t(jvY3KO9?~$? zT3Q=6iw@m5Br2IezZFKb1b4l3I7}4A?kM3PB#Lq}?QqN;w-6V4; zKD5QFFD8(_>VfZO{&XZ#nJzDRK$gRu5EB}J>fUc??+<63Fn>*(dznLreV7+k`oeaf zH9A9+DR`?j-c@pL{Jyn#)@>XXy?;eMWtuSXFf3TlwJf(($0dV zh9?Z_L(p8OPbuA;;C!qA*vT)+M8F%+!#K z!Vtw=sQl7`=AR`rvcMh|+IQ$zU@zqRWW#iKFcMs3&`(j5{R&aE4X?OB)+_!o{51ZeT z>kU7;^)duGuJ$;iDUVjAZWudf2=XV|(tFo3vdf<-)*m=aXNE>m_M@NTiq1`8ZspI8 zwD07zYa?BF$~>b{eW^!e1vzcE#y@63eE)AS-xd0Z7P~}z^>3&37aFM6elFGCpFuC3 z{Lr;FO8mY%`p76)uJwd}V7&-T-$r*rjgVcyJI7z|>E|9DI=^}W?bUB4 zm9K|MZLvB2Y;B`ggE@o0qKPusKBdpq$Hhu^U%nY&%x?7g6tXv#EHBB^;HJ^Y=X`0M zXN{;f7>9nwx{zDvi0^6^h(9+7&vq=51Zl3PrgM5?$Kf1QXH}BaH!~>bBvE63<{P(n z(xtL#ykk=5oA5`G_x=z>=nWwdYHT^fAA3$OF8(Gj=4*cHzC z(C}b1{B=XL+z!fDxE3#1`>mJUdD{&GDrbw*3*~g{ zNhutZ`r*>@ouatOmP&iPrl2u~DAC|7sb@YJhh;M7{Wj%!^};Wg)wERmB-vHAP+!h+ zymK{^WBcK-{adm5m{7%UHl%L*|$?zXADPEiac`# zo5-S_^JOxn=+zKIaaZIam6=a7hCGv`?Cy$9-77^(M>;d@gUR@(3SM{Zik@*WHT%-#nn)6GSkj>M znH*C*F=j>)!uGzW`Tq>W+J~9AbxjRhr+CAN^BS_jJ1O#HC96Y+iNwZ!8B(?2{XuiNLQGB_r>?YoV7S9u>yFXN)30e!i#pWiZlY-? zmkX`O2T3n=6zx`!r}Al~(9ln$b9G&?_vbv?T78-ZeKw+h+xS`NNLZ>glxBFCQ)hoE zO6pTaolE-T_7Qn{d2)5Ts7EE>YQVk`|v&) zbeN&@R41*L$sU^tK$0#KjV+|EbTN6Xk05cf6s>69$7M&AB1Uv;RyT_1D~p3m|y9Q&%8(4 ze@hvv*NySSE)YF)%Ej}yzRYxtLr`H7db)H_M<{o4SCvw@q=lUFBP2_QY$0uPSF?)HxwiuT+q zjI6z3^InrO-sLj)&5DY>){2Ks37EJ0Deb-zM4}WhyV`|P??|DXx!w=fB?z@Q%%B@S zmCia2hFyIo4L2Es=+@I>W`+cNZ%!vJT8L*UHT0&TFE)xyEDLNBj@B)bb#F$}rM?y9 zYg`cmbbb7Uw$EEjj!k?!bl*ik9^McywC1xL`MxkHX13*uSX6!M zgB7L6Xv7%<(PfV(y`QfLp9vax%>3!;>#x$sqo!my={%*@+JnyC6bb2Rc*dNukDM2* zXRo_@!d`MnSVxPQ7cs=RkK}D%!e;r0G_K|{m1Xg);ckHqin8cFF98E}Pm6!6`jOwr z2}s?bfdD5(thSCreDe~TXJLgHuRj#E=q%}uEEL~f_CP6_(9St8>C9v;jQEe8!OJUX zqvRF!9LOx*HIBHH%05b-4-B8kVCUp`NuLL5qOTg~G}b+)MO*feHZy)+KN^jI1M#%# zKi)aWCBn1uz3}gPn!@V~sWmDN`9(d+`_}@B&?kJEmX8Bz-)O#dDV&41(dI|4n0Wj! z_i8(6XyR!6Irq?GaAJVua@i^|W`Q^ET3e#<+i+Y?VWb6j*0@>U(jSM8{n zpK0fI7NDW_|IFSaG$!E*8TE;WwEO{TQwhhl_Dq_%u1@H8$kTRqxOE1U3$2!NIyd43 zRWx$$amsg^UYCwI_HMkIaEv}4D!^Z6km2EUx>D31v#Qe(bEp>rDy~WD0`Ji2)e?Lj z`k!c=lM8pBWcXETp!0qi<>>QF^Kg~uyV^p;>kp&(GyIXVqKw+ETVv|`aj4I@L<4TH z8*exBm%121r^*tqJasT`Ul1zvxx2PF6kAUJBS%+vjMn$Ypq&b2cKfne^Q(#Wb^N1K zOCsTw)<>L^*unMp2@kn1Mkzr$J(OxKJvc9SMbw>>6TW;W4|~Jk zolF;ice6VlrBOI@Q7)qYn=;bax% zOV}%QnLo2Oo9QdN<@LJ{MIp`!x8GmHlgs^Sj&HL>e(?w9HMvo>ygg3p4xl8{o*3lh zM_t*OXY}D7z4#c5;UyDrJu(t6wm5?ROQ9?6zSzBEFcwy1!?7-$;>zTa`?F14%2C6( zo6%Ugl=}>(p}03)mQI}Lh3C2KQ#Xl1@%*_Yb>$@ux$6o`&Z-~(HBNG9Pa=+n&!JOG zO_1ldh)#ZGM$+HE6ccHR@wI&sXEqx9T4vI(^uf6JTo;Ra_M+vIThw(jclVR65w-az zIm*Z31alugYQ3eXT|@Evvma(nc~0(z-SKKZ_v$L%iNX3)$@oAZWFPzEP9pagKUGu3 z@U6mWlM2qYrx=tZL~~J97>Te|q>GUPAg|M(jh4hI-W}%HZ2u_|joG zQNxY^g)A()rvo+XO4`5esKh{8A7Oej_%-dXDDL)KOnuyspJfBkKR`{W{tU$=TSKfF zf0s7=Q=@wCZ{#|m2d0Eq(U6r%I3u&j!)U51Ml4N++K@=h^3B7XysgycrARLv+9=?` zc&M(s!5vZyx)MDayJV%wG1nN1-%n9}k3MLee_L#@d`oRp^2KQL>2z<)3R*BrpDwO{ zDQ*s#Ayi#BGqg_+x!Nrh&FBJ;Da==z>;d%=Jm)SbqQT71nX=4Gq&o3jtTPHxrTb~Y z=dY3z$;`UFr^cSU$29*e`)lS+qdK|I6ga5_O(%beUNH&uQb&Wfn+KyWzb|Nf><@)- z=Kov)U}1-6cBYb7qwTQ5p^RpYPp3j>P3S(=#Ntb_=zEWFeg%6qt=V6BOcr*QgYh@l z8k?izkp9ja8<$KG5!{Q@KFRy}jRm;h>Vw0kcPVWS&!icubli9t`7xJd|9v_1l`^D# z!3uctD+)`e{Gnl452?;|B^^xgr{CPe-E;AQ=o6xd)1r#Z^tI_=R~6pN&8FW2KTxmE z3$%FE8#1bJ#P&W+T)hO$Fj2ywkv(aLPY_vXpQd3s$7r%hlWeK`Mar}G)3}jckUTB~ z7qo(~h%>KK*M;I>zj>sh97=~{`k_3XZ?|)LLbkqBNKy(>J};PdSh*sh;4X!yE+;i9 z;PenZj2_tF(P!jW3LlmOt(l>8q{@$Xq#tPG>uJ<*c?%gOWgz0UEEa9f#OoK2#pF^g zsMX0}WymLH$LrB{#r|~WYCdG&cflC_Zrt(xLgABM(z$sz=#CFRM`YONd07Tesywlb zca^K>7Q>x$C~~WNa|YU)cYvG3f2#v9_~KFOpE-d>xU-k+cROuVt&uFtP^5ruW9dO< zEzNvwK(#-a|2fkL-A1v?)`P^^AxlJ&lN8$2+0W<^h}Z=;X-kDUW^2fx;Z`s+lr~77 zaaO->^d=FWYDDKxb9b@O1+I6tP!E3g7jbu_{9r7cdor^irHK8}^^%F_Ysl%NGXD5? zgQ{^9!gVU>!{$`OdAr4u{iM(*_Drn!vF=yvAZ@V)F08x+L&mkfxpjVap}thWLa@W z{=FsL?zfhCaVBWDNP^dce0X`Bp~s4fBo(2L{ff1;a{EBYXT-xViT9^k%Sm}rDc(HX zDM@1HJFZIJ%#5MpLT41#0=?Bj@VwU| z%KR-$Gww!s7_&hI=F8xGd@Rsi2tc9@ua*1!W#rsM%T#gC9!c zOSvBg8>jR0k2wbH3mp=`YnVyZ;>!vd;o(>zvM0z=<6{@`c(MgiBXp8exsyZdEj{mmNCZMI*p+GIJgG(egW2sUP!j_C*YWyXg)|Q?)bBr@Lb7hnb&g|9;Y$S-82Ai3_YmKEE}^&FfZnSA~o&ohN~GdqOk|F zdunbkg3whdwe_z|Zsn{ny}&Gu)$Fm&*QFcBUNlWH0VF zE9gZpqFc|Iak2Y^sLZINF#Q&0&h&@>CNt5b?tqUy$BXi;Cc3>*3Z*}`)7$;eL~mUs zYF(WS#nmo|@LWn8K6qp53+^8O8wgou=X!kThFebpB}UH%P*qYC>bMhk`u0o;|7$L3 z7;lWDb4Oxf%Siemm5P9~kICu40PGquOPG%OLi?w7((-C`kz>4owzCVf^DJ{ex9yO) zNk-yWtQBgV?|ZyEs)Uj|&Ej6h2fA^~gjq{VX#XNv_d}mDP-V4J42e>~yu%X_dAl2J zU3Psi;-H|hu3uIPtb-8B)$xy+PVsw9!{ zJ8ktj8rWS8^?Tpa)G~qe*)J&WgEw{47(gw*>V?>^iJq%XsPsRcQ%9sid&C0r8S9R# zcdT*7>pwD$P{h-v(a`SK7cW0Pp`oS&;V_7w)lS@@oT7>wpkw@1!5pHi$b3J%?iIEzUPQ;XUNkOh_&Q$Hw*Mj=&vK)0!h$(jUG30C#_w**Cz|@<{PR*uQGcMCp-J@i|xp&*dY)l=hO1Xz4 zvFLX+dMQ`Yl4*m;eDFbu3h|w0tq+p()7djW77h#3v9E*oh0&Z(DeR5!@m-+v2nfs8 zz>mXDWN>HG)D1TouO32$T@MN5D*PW)BE ze2qUOTa}EB@q6i{cQ%CvjzJS=d+T&l$fJIiWZz9@oX&YiYu}W>%i0Tut<3nfw@0e> zWZbp5KsxMwty39=&Nbl(F7kllj#m_Q@`E_AVm$qe;wsAKS@1cK3#8fK==D0^kOPAy0=B*V{tBQFK?!Li6su| zE~V?wGhq8wf{`;eQr*6?w}i(gn`lJ{Q|1%8udqrt_QL z1#k{`g$2}y-X)o9<>dB`iFCE7H&r^@LDuLb9ndI6 zf|@ouUl0ucR8iH}25QV44x8I^Y1^7?cuh7XHHv^3;e({F7pa0j^EEZ=L|}s=#oc`( zNjp%0t6CFr)X4*DE*~R}5uEe89*io^t$uhfgOqZGSPK3QDm-2W~Llbb8CsTwPgmM%sx8^rf!IF7r1l2rF) zAJ<@J5apkcxSrOLw76yA3wI|zHxA}op%NsDb_n~^3&%@UVVCHDat}+qjn2iZDqyX} z91(EDAFpMZ4?AZTIXB&B+3yVYayJqDG++#@Jss5)aR2A~n|x@|F+i)X%wO z7oCH_i;;aWtYABp{oY9Cj!$XY z;7QQ+%@a#{w}_3W|9M0z&ZZuG+ZfNhzhBP9Sk&o@Chx&$-PbJoY}SOMdT)ql{MjmH zCu@QemDzLOSZ*vHmX?bpmO5zX9=v+?WYkP`LD1|{gjts0*~YuHvC>T1K7^C(`rDtEl4SPP#jiC}C>6h?e5p zr+Xvmtl3FPr(U19W|`|PC!66A#crIwC8r1?8VWVaCp^>JsP zdVh?Zx=#3AVixj5Cur~5OgC5c#)@AplprOC(*emi+P+J&cq4lsm25HVg*-J(ctdV? z-C-3JOV_WirhiXHk!vGR+b0txmOU^gD-s?tC78uIweOq<|JUOwjlZ#1jEa?_6|cXE zDYN7t=ffV)2VQ8~90BjP1iEs<3)`-)5c{qV7tN6+h!6ZqtyO#J=^y4^-g`oa7TV&{ zCf-+eE5PzY2J|{`5%q4IBmVmSrP|$tQ6!y&q1kR2a3_l-!-!%_){)2f6s*)S#FNVl zgtglRYX0cK{ zZ<&m?wFBX%+e{ZYqm%LJ5j7N~BjCN2aQGC9E81JBzON%P#+hNgEZ(Ev2`LC%l87_^K9OGENjRtNBuR@`gmDK!$!UX_wOmyUzCMv%)!}q{%XV@d zvWqHCD`R^>DL&lUNf{G;Fd?avA}zGpQ^{_`sg{_r@;}NOHH9vodP%hfL7b&xALv)A_m?&&x@f0{${ zRvS^)&mDAg=Qr^+#08qEX85|+lFnYKpaW|SP&{BE#c%jU_q3;qkhu={E0ryd?O|SH z-#W=(`*K?RR*zyg`=ae=5ze3Liz4PTW1t-Aaz9~NMGm%>l%R$)Lys1!kcW3Nx(`x; z&Gk`G-l~Tsn+)+0CN%$)3?9AckDZ6vl~cDs*dJ0w!LZ?&+2a-|h34z^d**|8~NZ*DIWC40UJ{%L>?G$Y31vg7ixG6rSmK+6K`u+div?GlSU_PnGKn$j8 z@nF*+Dl(`b%?rF+*pV!DE`2E6SDzP)ZYZLM)KK^*kH*1|i%90N6xjyk^9;C*Bztzz zyPxfJVY(hls+6ETQXwt`jbWsjDT~*g zV=%0+jYie3rFE}!;e0!lqM2Q)+|eCfrfcKMh9d0O4FLdSqErm{Tc6&r(LYZ52&ZYNkCi_LA4+`vmr4wj6VUR|orDD;APY z6Z>g+w{fTa2s?+n;a$HNjQiIe@i|Jks4_}8KmIKiN!_ICo64wAxk~f)E|K{0jZcDUN6E&Irz- zRTW`me`$Jm>nwdPJ5OV6rqlb$JRA2@#Z&VfG2qQi>i&m&=a$vC*9kmiMo*Nv7#+2UQ6m_)X>82urFr~sM|r_f%Cpl$(r3c zJWq8WPzdLte7Ep;NTm}ml790}$;3QgGR}3t{k)x$^q_;(e&L0Ii|LkV?CwTB?^B@^C5P0S>*D>nWWMz% zV(T03wex%N9Tim=@C~Z}^j@4nPsIANITUwa8hb8VU`3zda0%>(3sYW`7WY9u4frPH zLQHYn{Rdq)lEUZ{DksxOpq1!G9|S_6R|=Nip8#DsUd@B+Yh= z!w}wqX0Mqc{G^z*JuCCmx&5ku+6apsRH}(+xFXrwo5T2Qj;? zF&vJ8>#3L0U~K$ifi=hCNYcX$^ZHAph-WX|`bXp)&kR=YBz&G0LWlc~aj!bt8=s!J z)94o^bV@xKmC17`cT)_i4i>|pLJ_48{@32Ug=$(>aRC2jrc9;?(jsBR%7QF0=G*7Z zIPVJ@btB>-$^#J|wvI45p<|O!X+&rm(T$;W5JiZb3`LU;P>@8>MMV&FQ&CxnAfi}y zy6Brdv;V(EExxY0+jBAVe((C$>A%*q)_R`zoxNwjBlf+I3}5@mfqh@Tac%tM?59us z=JDA(8)rW~JiL7Gu=Jkq4tM|R!u`)Z_2O{&JA1}GUzz=Xf}^wFJ^Q7bhWpPuYkcL; zKOR2!nHPqmv!B7j;qi?)&ylC-FP8%-x=fe2HnX{ia_`sXTNB_7u-u|=6{=Yqa-~Kxv zzkc}oA0C|b|I^369(a1V|E}ry*=z3`e)NSahDTod@cundoj-o-^0$oVeRJm5!bi`& z`%`xf&)qlsIYqB|X86lRZyUaP`ZN2FTzATN!N1-)9KGwnc>n9)HD3C<+4Gj?FCJg{ z;;F-1KlFy-nq5c67ytgi@YucoSpV&_`^K}+oni8gmxtSbu{=Eb?N85s$HLX)>T53@ zAA0!A@%ktKG(0i;t+dyD@CU;=PnVR*_Rm?zK23J)JbvS{otqyg zyN>@e`OnY0CMR#Qo;v&b?%8)Z13Z5ViTwDYNe% z`*Td&4TmnfW-|Hs=I8Cdwh`DyU>kvL1hx^_M&SR4z|{wD-8e4&>KhIp-njYL_3Cs> z-}N_dY}~Z@0d;XfSAfZrzkSnJlj~)T<62zHRgN(sDyRvaxY^ z^Y-%67PZqmUEiYKym9S`OE#}WGTvfUcsqaBL&Zxnk5G9&jb4(A-r$V4qw)UA5~FD(7poHe)rLy%T5>_@$Do}V{}0>SgkgaNs&24FC?S4Lu0jzV0cBJ zzw|C9+l$`VywXu4nZL!VU=^b#o#cg}0V^?DOXjdyjHZboUetxyjF;qTqZe;Sg_X5X z^ahQ!=*2v!!c_iDd{St+_R^5^qttGGMMO_V*WFD=j=Ae?IUa=V! zFCsQ9|SDh?YK{9Bdx>`)ec6xQIPOpB1%4S%# zl9N}kV%2iu>t2a|hh!?lTKp|~L*-GYcQICpz6fT4*1C}hE~mCbgZ1*cSQXx-CcgY# z2pS#lN*BTS8;9C%wV14fhP+yBkVKs}{<2vw>(Edc=*7DhqfrZzTjgrf#Yn9ySY<7U ze&KDRPi_im*Qk9<#UM`g2aRXhqB({7c8 z8e)k;=aC3PqpN16ZZy%yt9tVib$ttrVikYk-0?0p*-o-)L~ihRwTob~uIpB;BDq)< zoFjNrkW6l@Ck(8DC&7b@Pws7Q`gOQ9BzLR|>XBtvo`5iB_wtJ>)c zHI;#65$tvZ^r}}KZ#M;zY`?%}o-bu8K&5vn6}XFFm+g4X8=d5}xFn*RHKdjz@L`9U9#pht1~6B3bm)%R0$UPCDvpa#Ggu$b^iS=c+eeHtS`@ z=Ac1x$+~znzal}BE%bJis_(&>UR0+LY(}+?pix#zs%SwY8Ee`syg?&NuyjdLSWdkv zlEb@}=(E(-{9318@z+{t=Sd!=5keAg8eKc(svGpOd7<$L&Pmv||E|O;I73p`U^Bhl zJc+s##U!IzM?9j}#7HlXmXbLgua!b?kvz9MM{va|tIX_O@+wr*CMti`SRS#e>ylPz z!rSHste`=!YKB^nOz$q#tQ1|NL$a$3JaXp779QEP!HQK@Xt63dt97XKcKry|PE!=v zr-$;_3Z)k>o4ZPgx0@$fMQ?}3ax#{`B&*(qs^)~ZcoeECN-wL{!rMu_ z=QMy-B?>W%WYm=`QB-=(Dm;Q!>rwK`l5BMs ztAb>k8;QQjoHm)$)~oQgdz^TbqEg~ZN4FNlSFM92-uYcp*0sD_g|Mv~Y8_r^K(eJK zb7Y+u+0zwXNM;@3rB|243k})so}4tPcGR}R#;PpA+EYSRRb(9~gR@@NT9qNGc$6s2e@^FFIjiJ` z)y+#+&Nt}At4IcU0aDhz1c9ZS$v%%6)Gf~N?3S1YIx-p z$vn~ryC~SF6n`U`&9GvXT^qe{79&`Z;$1boY@Qz@c||h4^Lgnl{w9LD6|DGc8Y#TV z4O0QDc$CyA&&yD)P-y7vx-+XFNir&%N$#w&J1)G5ezzZlvoqBWjc#IOmAyvCYwZ`S zg0me^!3s4MxRb2%qe9ZYn_e-phs7aAtb#MF?D+5&$*~!)@9FHUDv~n=;f1rlPadju zL^7*VCfj$JRE83TtRv^Ka32faF7@hDN~-dGR?IY}~qbsQeyHMbYuM6i1YgXEOwHeBws-n{K>_N1o8IRShw6FC)3#FGRA_0P;kE1^ViH!Y>vOsV2k*g8Bks6?C!bp#H91rgv=vY$(&+yR@wZ$q|RP= z<8S+hMyQbNqR>r!{7r~z^ds5oF8;0VS8Oi4$w~7SkMKJEU=^w}D3axs zxxL^VkMwTx#Md)ER*|fl;dN!or5CSvnQ+KwJ(Ndq*2@Yt5wucZh1axMsNr>fRV2eI zsly|ZGcwdYYQ@t3$%6p8hRcT0tnlw%`SR*WS3faBbm)SVzatYc$1T9zcsHyb;{I9 zcKU%T`qPx8&LewvRu!8wtP~^@Ba-pb+o1vHEV5dkBROMEosohwtVD)h&wyCPqppXd zS}CwXZC7ZlvKH{N&jbZYlEoJXtdh=#O2MY1kgsNwad z1b@{W)w{#WbGnY#dz(Chl@puJs&2mzNtLkZjYsy~MRL$^E{YeXIvpy#U3_^YCp&+2 z$xgCoPc6#Uw1BIsQ;daXh0mt_8mK3-H4GG2P^C3!@zX|wRgBbA>=u3*(! zg^I(KUPdpfeQ1|Crt6L>+sP|f1!!pao}D$T@Os9F!Ww3kea}9$#3*^?4Ef(ZK?SV< diff --git a/benchmarks/perf-tool/dataset/data.hdf5 b/benchmarks/perf-tool/dataset/data.hdf5 deleted file mode 100644 index c9268606d224befefbc07e7222bd4eaaae061703..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 527648 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

f(4p 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

    nr5;9Rh0JoL`3=@C!KeKFS>rA3y9?H#x#Lcd zE6U$a3(}dm7mdW7XlC)d`1O1NY~<(C)9GgV3LfDrdF}xIQNAy(#68Fya|M0c+5&nG zitwk=lkfmu!FBi@L#*YlbTR!~{3BjOE!4#*`4#kg+8XotohZQ{iQE@@iw~!{H`GVw z(3j=@v;GzBhTi-jx&&{cDQ=W|0>kng8u_}+;wSUpS-%CvQAKJCP1XVou20hFA z5AozFcc^YjS5hV^6d5`Qc5JBZ9#+xgM*>BWn%8u|PllB-V- z#{hl=Zs$`&^KZY(_r=eA0r*z?`pkSj`+pR^L;kPXZ`q?C@cZ+H`1&;O=vF!%*&D6# z7j_|aKc8LddnxgU;_Rij`J?#vXnI+CRO(#LLLWZ$B{eVaY$=+F8_?H~_csxhF-v|7 z+T(I@S(@HO_Vv?uXctt&E@c0dKBfT{< z@+y8EQg2d^pWs^{`=C8em(O0ShMd{d(`}fBPDrmP;<@Sg3Z+mM_sSoE?DvuA%4ZLz zr##HRfYjCvayx12bs3tO>mRxQ_;YD$X6Bndd^@BzW+pn0zZxiNvuWY^HcH+hGUal?h6(8=lK;lmCxDD**XRXB4@vuT1Jzn`JecIF-KgK-jB@h=Zm-UU(n1mpVJ*k zok)$Di$;9T`CS-J(i(~+%jz7m*NDzEsFCyXg3T&Z*h6N%b!Ej-`?hL z=BLpqSc#(ILim&aiKcFD<_FSe>5+6MJ%pw=oX8)8Ui=(7o*s`@NH1DW*WgQ}2b|}b ztLaRX!yxfd^gUV}|KrajPb2m6JaOjmf&5!I0{_bWPIHgz!KcsH#i7V-aj@Jl+#!A# zCHXIC9eMzAhiops0_R{MZa`+^n%0lzGpFt455q^uJ>z5gCK@7h^P8S4feXb4&@1sc z{{Xt6iD!n;Pvra2TKJ8>gyvqHemq(H01Ba!_!s03@gSWfm)^0HHm5ftf4;Ek2##rOj?qSpVSeQE9=zw$%*>S&3Z#qZGnS-XbZgBQ^e+i;5f8afnv zaUmW>Wna?*jq#-TG8FXe>-2rOuC#&pI{qc>79U45Ph@7hh_8aWxCe{zm}fquuV4>< z5IXbSk(qfaJsM}opBcq%Xk)D~j^!&@J05rPmF0fqbKiMP{0%>YcBYw^KIJFkeQ|4? z&0mYg);G7@U zkw{-mZMhnmoANG~q8YLmzw@=#Al%nq{$f(ki_?I^fCR(pUyV0h|8Glmz zGEGna2stOc#Hm>$u?~61nOo9(QwvLqKgLX?uN{u>aV2^ppKm_*(kQ^M!I^yKfKD{~ zrvdWWr*1t!ua>_H&+(h+SepG`A}UV(z8#qhipr&*FBBhz)#xa90T%G7Z|Mg)m)V<{ zHS5ww@~Hto@~K5f(UvHMv*k0BokP>Zo6CL0kEg}ySfplsE1t~f{GW*lNRO{A*M-md z{fgd;0mv+LmbLVPtu%Z0XUsx>%$G~8Y{&193vny5ci)rGJk^}0E**pC_)};d^-e2>w|Bem^hRP}u7Wn-L+Pp|~FDV=pN0)6fX#CKkot6jI5qn} zI!|sJ=JKmhlYfLZrWI-GCb^XGD0!Pk`=$RCUw#e>id=b{#d z$-j#Z{J*ps9Yf2}^ufdV%oyoEt>n_HZ=sz~SZ+K%#r-IcKal#GIyuKP4d^X+oIeY% z@&C|=>3y^zeG~OD9lg;5ccYK>x9Af{pZF8M;UAoc%vGuRPt!?sBE1K<@pUj1g~h4! z|Iuf~yD$p5L#CH+m#-kMgv0oY=?LuMhoUd;$E9+0aTRVBKaXEm3xUUrMqZZtmh}uOXwanEE;wZI2;X ziTmUV(%iwC@h>1fC9_z1bMBeP(QD+crJvGza_LVuiWiFa)iI z{w)2GK8{&fE6$AjHGdb+?(2zn^92Nlh%e?JqrK^8coqM{A*hdskvZ=PTF=t4I0;vx z6^h~yYu{lL28(aPW~{(zzIKKDXM9g&w(CIOlFLl}CCx0dA3Xz~;KksXBaj&}Klo{K zf8%W&BbEEv<^0jej5Rws{y3b3%oz=7A-s;Oa6H~Y?xMMqU*l^EA@{81v;xiS@+w~n zuXy%(+7338|f=o z(qZzy(K$3T$7q`Vev4er%nrOKms-`2&pv(tFJX&Z0a^#w@;B0xQ3lJznf1P-sWlIw zD{6}$MNxhnGCz$IXJ-2U-|OXkHnx`ebSW(&*Fz8 zvt4R&YH~?&AKdSmIW#ppXL*(QMtsAsqpx5ze+P1|4;636^{6Yi0jam?SKss3B4?>O zy@z%|djBGE1Eh9jFHc0ptckBiIsPJ~t{+5G8&W%a<0IsJWQJ}h-x)86Q(KzyU-6kO zhw}CKhBSRD=ky!+G4Jjc-P@|EeubT%GD`bq8rck^Fc zdyY28HogNM;cum>kAI^addr=S+^udGAIblY4{;cNlpBBz{8n1Xa|`I-;_J`|>0ix| zescrbU?VQU^BCos%z(xCtNC5D7_Ei$fiZZB??x}eE&PQz2Q%;=o|Hcj>ES=oXYd;C zL3-!obg$>m!)^GVcr|j*`H!a0oRW1ueR3e5xnl+0EzX^!BmD#4$i0E|%k+eD*1q7= zlQM(fB=Bb;WpeQZiQ#@ zB}U3E!O?sp`T+eJKcc<(RC*0^cRN@7AwEGVx!m(JpFJpEDo!8FOz^MxG+LWZ!12h; z+mg=0n>f~bW|x8dyZlvn9B*L+YT{*U{pk_-j6W5J;t;%n8F(9+XP)rPqx`eDoc{!w zpQh7)C11GV^4%$jq0y=w8pPM{5+cKA5hcnQ5+}RZvOpBpiV5;-d5kdIPPB z>bL{*@DhH;AnTcT&Zm1(NNzLA%dHj9;*$-EsIzJFOd#MAdS*d}k z=f}xCi4Tx7U0Hq;Uz{$cyJ*hsD1I6J7Crb?I0Wfg*?S%M%jl{2n$P*lxjX^y;BKUT zrGCEenf|mB`tx0p^O>4>67HAJK7WC)KvPe9p`Ew}9_RhD`|4&1aYI4s&00Qn`)@2l z>U-*DX5XCW^pEtDHJE}r*eaKEn_4@a{wmjvcBcz)F+UJp_-XWOntqdWoSyaz%^8^? zcR5mf_9DF~{jnZ)^4HPKrti~on23V%nZcXU)RDe&WoREdjNX7}(OG;mUgNLDENm7( zgVc~a>14UguOIS1@q6fV$XV`*V=z=a1euvruP4cs!zzqNTWbwy8#LlG?|jT(&kv+;BlrgCwJ*|Jtf$`=>)3bi$*I*{PprzbY z`X%j+v+w}Y7pl@HaVawU9z=V4wi*iYOXwn;h4XQt+`*W^XVyP~ABFvqo^g`$-PK#p?}dIuos!7TFKprZ^ZvY3oK**Luu>B zqomw3s3i9kmhey0d036p#Y4~v--+*`%aQ(9gnoiL{2TNubi?5|7gOZF!Y*WHzF%%N z8t}P$e!w@k_8$I3Pt=vmz2q)_CVv>MPutOo^d;1^-h=MOiKuKXvq#0iUmhHeaSG>j4=bxkJ;7a}#Y~nA% zt0?FD4VSx$&n#JmZ;H}5N-nc>X7<|RBaz>KTp)iut`&cd{9fcraTESGI*2wy=Kgc& zQjEwt-sDfF`CZJr^hOkxD~_vC6Q{`Ci+_>1GWU`~7%jdP-+Ly_={1tO<|XuUoQTY? zf6?RdpJ#8z2tM!s6k1x`1wSD(Ols;xeucF|X!=3+)_HUc(tkG7J~&mrG0ogGfaYh& z%=H?-S2zi&8#%{wXnMf&bP77lrT*kIOh7;dt;>GN*}hIbvqL`r zr?8uUoIZ_w_NiT|PpPvHBmE$I@FM;~q^{h9amdW@|2=4+XHuiDMhk1H5!p)x(HiM_ z#gKE8`g1W-7tf^GUuPrxE_*Wd>uqaAk$su7doPB|Uxp9ykvKiL7@zqh=W7-+7w(}& zk+a^>bLZg;q+VYy*PQO8?_nEK6W^f)Xg_?-=WMp8f6xQz0{qMGM>BI~*4!?xOS89& z$fr&<;?w8Oq6_g4KCxDYW@i3{E)tKWyJ%+iIy5~kb)<*npOc19J zw4R@_U38X%z4$bCIMq9oBhVf4zy)ZTTce)xAF+rSr zSYMjHnt9@Un!8GKxeBO=jW|F)vvqmeKs!$gG)}@L0K07%KNEYVzYT zhkuh!MN2-tXc(V9Q3=(8++Lb{Od-##Lo;!4q&B}OK7iK833ye!g+5A8qvdGku~+yH z`8G7OQ#XDPKNAPxS}c*9g-(3>e}8(m_z@g|YjBv{75Edki#uUH-az`pJo)sryXYd? z2lw&6qdS_3dm+6qy{#^v8Lg|e%%;`EHLy&adEkFEb4mKq$HLv>8T1$W6nz(4aiO>g z9^=>G8UB9!fp5guBRwlUvNc^SpS$87em!Q0f27~huV^d$iLv7D^aa|J7NVKko}i~9 zbK>VTef4kaSJ931X;kI=VLE>(&f;&N&*N}DJ^3$sH?F~5@@HZ#ir^VMgnHJtBJ~^Qmq5Os}JPFFAYp{2Sm| zK0ot;sOxL}-S>0l=iGo|^4n=X%j}cX+xz+ajJvF@r$^B2g)ubyFQ0d6=}MaOG6*y9 zA_mK+cV$o4<+IOHn?B`t($tfD?)8z+WF!X452UFlQ~16#^=2HO|NE7EYx*#9)^oN8 zAfL$-vjvbvg^#f9YG-(?#OEm(!3smpR~Px>G(iW(@rTb@(1M zXX``y8=mJUAm{x9q*i<{PG8NL&FqsIB4>FWGQVc;W?%p4nH6$x(D^tQIX|ggugV{Y zuaF)yS#BeW@u@TC(!S#J=m|IynQJ!4wcv}>?C&|qo?U{>%jvcMLqGB?Qqvy5BD56e z45Z&3!k>i-;?%~>KbbqbiVu}vN{>O##|oPIaykki=ivX(^p)0{iXWnP(|^&2FG4pW zH9qGlbv^Zj-8VOXz&ydH58p&bBQs|ziIlGb+MIQ~zXnwGh?FipMsgYL1`mS)~e9lQ;> zSCx<(#IL3o;2&Hj{u9@uD(*o|+=&x$G3I%03+;{E&AQP?ajN_y^gMbIeIApMnm!h_ zQ3tD#TKqCCit%_|{2nIpZ{RSzh~~HvzvC_Izo9y6qn+HF$n2Ist1(=DG0j~43V$n# z;x)OF*iZg!apuM^>5_Q)+`Z~yHeUiW<)5bMEoJ%4?dcmg@Rw%|nLSdQf8eL%8srXp zmt6Yvo#N4`fz;_M{=!?h$Jz_H z9Z#Z=Tv?PuKk*f~6q)5Z%5CDO;Yofi?SA{@6ngU>Ae@y5vYZw^0{BXP0teFK-bW6a*xu?8$Ibk=xS{`YTzhrllul+vhKOk za-A>^e_)w>Z+y(ZOiSYo)Wz5M9Vb~k4b}K-v57yEo`OAiO#BOdfo4u>&i{mm#f8Nc z=+P)BmwEFxItdl=8E($H`~p6+W>4|es4A{UKSBfkXPPG3iO=sz?xUkH6ooLt@;02q zUxwVrPNrwb-9ZmVDIA3_<<6&{(9X0v?Ll|YHkgkKF-)!sF2-QwPWX%b@%;BRcZ3IM z?hm=E?L-ocH?-RU6D>`$A}#d2G*9#>-(e#HW7#W4@L z6MiE1AaYM#DVO`fH?$TWlh1u(IR7KA!$W8#e-*u%<_=Ynufz|aZN;7Gf8u($AIFPx zN661rLY%M9{>;y}PHqCtdCI%X9)CeR7U>E3d_F)vhwP2o{48sEe_fG&w^43Be=^NG zF3vZ@7JfVOndbkIJ(4s3oZN7lGn4&$6xxXMvwn+w9{K#s%cpnbyk?(v#DQ`L(S>v( z(znyo^RBbUbAH=mfwf8W9om}aOl2?RbG(4hd+EYAMLxriY37^k&xfpKKaQg<#MdKd z;&56Lsb`r{w(>b=d3Pge_GDkVw#YttndZ#ChADUtWzZh8tzC%zI2V~oa+WeDWpAan zXFuk=rUuN!<4B*$na;bf%#Wto7e8Se-;F*%N6|6J-bsy2?^&8We!z4bgXPHHOpVR? zOI?{vGc#R;)b_D7^ItWZezgT3@~KJF_?*iRY0iK4cFtvLRO&)z<@ELH;{C-r*HieD z`NHVL|4CEJYtr=W^v2%&Vw4f59@pmU;0Ey$ntooL4!{zA8?NC?(I@F_Jb{1k8~%S5 z&e^XfzMXzf|H9w=c)W*8uoRgw`_X3T=b2sfdYW3?3$OF@kXdG#xEsG4ukr=xsW^zQ zMZZA$$WO>DcoB6VdC{SLeM^u!xzYU_DOt+<-j$2xo~-b3%E zb?J*VHDDBFx_N$VHT%qp2t zzZX9uPG6fw({oRvsXI;O(s%pNnc{ztdcIxU2#dsf@dmyWe@*+-zcCl7(YYslK@Si= zjWfk3(r4s$ps9Ej{Z(!p?Lx1@+vtRkkY1ko=68N1J%KityPvPb-;J`#<9U24?o1nc z=5RiDiQIKkN6(hON3JTq!n^oWF0Cqi9NNzW+KqsRidf;BUq4fWI=4NqYaqbS8Z|m`y z1BS_0!GAbHF87xQ_(`}#{2(od+-(-qv*pU+BHSgOfy~*NopZODDt^{l=8P8f8x)dz ziT**GV=#XUeG0js*P}g9L;hi8?tDf37^aE~qJZ3S^f6Y-RI1K#E{=Nv)rM0vR(xRJk{-bN3_C#ZzS@jv;qupf%zT&zZ2OUrQ;J`oSZ zt^B9>j$eV?OEP=D!oM$n7oA1x%dMaduu`rws$ib@GStDb;tHNSfd7NvKwIJmTq@3- zx0636F`qf_V);YFAJG5*o!(&a&$vzQVtNMF@&{rBKat);dV5{-|~4+v-vT|*-XznLhg4=5$F81 zq1$l1Tpx7ga~|vRU*Z@3eLRkq_(`rc-A>QNdZfNTA(xt*cbGkt8uKJN;%K=NNL~7Z zre5^p`_t6LddS)PU7UAtG=CodD9v7Z5|5xT%E)Ctdx0N|yT$YArSxB<4}FXD%KNa~ zS^;cE=7`C16R;bp7pW)Nt4*<$PoFE!-;GA%g7^q)kREUmJrL=CSJ1tvo@dbj)sTJm z6YfA6{EB5*h|C8$?|1mR^!xO}=HhE`08$UzTANDK$5LOizviKpd}_cPeigri9!6*2 zaE!ucWX9@3AGE%NmZMVpX6fwdoSD>~oXKANbUyp{Z=@fWMb6;>@ix8^|25x|k)Re-tf&xqN$C2iWlGqQoT5n4; zca+8g{(cmde;#Z3iL??ujV?uI=R?J__*L{%dL^BSs{CchT=N-y5Z}rFLI0pv)3Y%Z zr7%tI6M89~Mhnq{ks07zx=ZdM`aNAi-=lBf5!{92@QnN|^d&sXr^la3uf~_yjqRw9 zrk;5h&*CO=W1NH@DB$Z3knhHSk0JO@oSEPUdZ+kS{K9`k};W8&*V(lF9!Sq)=fS-}yn+%{g;!ynSnG@*~D9Y!4UYb9RuR=Fq7;Y1{Kr=ow zWM;x8;>*Ps(6{gq|1svFl=bp*pP;e$czP}Uh8Cu|_hil~&S$oMlYWk;@EZQ~Tuu5H z*7GarD;SQR$b7#)GJ9{Zo(6Ra9gX9Knc)jz5Z^>@1ig{|hs@qL$IJCazCQIL|A!UG z&zPS*H6nc>`zxQ%E^$7W0@xtV`)@$=Ib~1f-%0J+NpGTgX{p!gO;dztE1yO7UFt;MNzUEtNbP=DoO9EariSHxq|Q~~^ZDnj?av=U(|6J{^I3Ee=iQt@ zH`5g~do$;AJD+p5lcs)U?`ID!qB%D?)2;ZNk?fm%b}R8JKZd@Eyr(hv3O$hbn!Wo7 zreLHvXYp;+@mpxE9T7xUS(6UFz?Wmv@DM!!btOwMlh?pgA$Vv=|kZH?|oo$nx*+Lk(> zdEjxr4nCE?73mGB1J9$OIOic}?Okla*SHnmBKv&+U5okn|2mU1R~@O3@6pXT3aL3? z$fehuf*D9{PR+Q5rUvY$seRv}G@se!J^GtC{bL-ei&OWrevIFaf=-r5^o3(~lm- zNMr{4jdnw7#Z>E=W!j3@(Yt6}`P88@{DE{8Zbt51&FSA*YHc!#@Y~Q5xd(KSdkvj% z3=T$dYw4jM(x&3v%UMo_~dZoOY&-=~?KrJ9GxP-y;$8GJdJfv~-{UHN zJf1{5yes!SUgjI&Q{?`8pSH;q0-;ZBZ7zn?@$Apk+MJ_`v>#1vNKI~s z{#cBM@tSAbAbX|-`tYeI+xYKj_V-Xe{pW2u9XZpfPkHYp&_MhH{Snz`>%{3Z9r(Pb z)VIxi`t~m5ykx(pw&d)khK&oUc$eGyRb|A98zOb55C}2E8gZu(~ig)I*D$T zOTW(^9f$O@DKvY%B~9J97pV&uAhXC-v^LH8Pt9(P%v`AfnZ*~UnRiPP7=<>w)D z`)s+seCFL=G<730^9ue8Yb9tEn*Mqiy-{3)*2Pk!E^nfWqQO?m+BjdS>lIGHc%-zbEt;ycA7==V5Pu9Ns{{&SjH>nnP&csczI zgOe9mK}j^h&2qVK{KdD&%V>)Ihdw$k+^h?}^ zoQ<6QJ~U?~pH=oxW|$M`2K<7Yi~Q_4d--g#ud|0=qp9H+U^CM9bB-6`A*9CSy%oi9 z^Soxm#3_ zOT9dh9)Q`%dEACWuOB6@%&$jk z^Jw}RGXKq@%`jhnI>zwT(F5suljJVLZ`e=V4P)^OT3~^EJ6af9_=3J?6D$#5EWVSj zqYF`oA40F7-_p#)SJTw-uDBJ0tYudG2?vW`M=2a2o=kJ!?L#xOucz;zWnwh)%vrPm zZG%dlDJvd}tvD6^aSAr#F3%i8zonUBaxeQ?ocr%3^lQ9=AJ7?@9p`v<6E5R>(%-S! zT7A^TGvYhx-)M`E#NXjKemA~A?i2UYZnz$oT5m$n#5~j!ucmeB7s#x1yEu3H#&nO| z?O21u#AUDwn~-_*MS2mQu%4Ooewsd4jDLhbgTIyL-ky2-SAGY|$$yF?`C_QYTi$oa z*g=QNm8Pv|3p$Bji{2P0{u5RC$LP&Ci$54&VLR&iW(U&D1jkT+_uacQpY`M`(@M0O zg>&iAXphXEhtsp=m(cIgoF7LA<5AQQFGLYskB*ote*~uTPtp;{9MwSl0PYZ9MeECD zra6{hDL0Ysp)KhQx{c;ud>{WH--(V!W{nrb7h$cq3aubFg61A~DD5DB6Frvp!ufpW z*t_ZLcon&i-)-#>6hiLL&&glG=YE@c{5o+Daqj%J_(yRfipjN=dy9?}UrKXV` zMGIm%KN$`2zBpf#dU_>V;sg0X$hocS-ec}z8I+>p( z^(vpuUGkYVvj5V12B4u_`fqCcvwZqk_T*qb`!M}CpI>?nyKn9)D!&uYi0{ODWKP>e z&y!ED%e$M6F34w`GdhHpp{ZA2Bj@2HaRHjoz~6m+JZC@kB{ga{9!5j)vveg!dM^7W zb?yy5HRfNMZ;-v5J@^y86aR?piC#4OE_FBOW*3^t9Y=GP(+l3iPH}os>Ph;=Kt6lF z6`yyRI`;u~$@jrqxJ*0=!}-+5|M;#*t$G_T%IDmq2W2+Ro=WX*in?;cka;2Xco0q9 zTa0VOV`zW+Jk7q#y!0KNiLdYxvR}s`b$5bz5o+@%(7yN$Pm5C@CnELra`7Uh{-@rh zZe5A&<-4t?FSQl7qnYP(zNhi)@FAc6kUnrL_F^|W$j_s%;#5B8>uqd6Ke_#BCEA4M zoR>xJ1*zHTvn9l-F`MZ>S@YbVH0Sv*KK1lStcb@t|2&(X#kUo?% z{{#ONO}(hkkL7>CYHSv#UY$lW`%a^&$=}LtOLG{Up94&1_$g{{$7pALDyGg!xGQPThKmE=50NR%%b5 zLS}{Bf4cbk1CjnV6@{(Mr=QTe$h=b!C-M_;i?uUpIdtTQ(7E()+=kasN$weR;M>uU zFrGh-9!~q)89$RR zL(3s|lyAiE@W0TjQ67852jfOQGeZ$N3X@S1e;_^QCeK`fo>+rh<%gjb#-kCwM0sm9 zuoP#A)9;?()5qtDi=!KVEq0(Y+T(cn%rp1!{qe522X5vo%4Y_;j(-K;@(dXvA0uZQ&g-^IW4Q}F`QkEhdrke*nQ^UdX6#w+{_bTB=iE~A+RO3}Sof*0ir%PpWq=x1_;@FuF^efjj}tLevh zT<%}oiBIvRT$`n*J+#fUVmBu@AQ{Q@Gh$X3op`y!-5{cQFgcTFc%}ZO9oK z#}CC1$h*xvwbe5xiF2+}pR=Fx{&EiH;%;OwSD+UmGr>xFHV#8aYuRT7Y0lPh^l^F+ z@{Uu7s`5qoMRX1_&;BFM{b+?a_4jsJgL>+^V^VmRh4eUUj9*< zc_g*F5*;E=ecK}blXj%P(jD|2dJ?|mQ=`A+>*5S#Zhct3I5Ib+Zl?x}p{aL^Fd1d! zhT#Cd0R0|8q;3^U z9)LNjkZ{i%D7#=8ts9DkRG~{wvq2ZFA(>m3&gKtKk-2EM|}F@47?@o zg!}j=bQqTMmGBJz9X)}jw_Hk>qp4ha;yk`3twR5ggR_9DGX1(Zh@F4iu|`K7yT^{- zYj<~d$Eag>cee-zpfu9motN%zkd_ve@A$daUgtdTz35sjyk|dq?}LZnn)JW#k$0tk zCcTx^-g53NeOEz+oCivMDzFxGlJoERnk#*T)Q18C>2;*W3vLL$3&zN^-*<=K^K6v# z-h$6^9xHW^)J{@o3C2r*M)3P?I!16`dLMayQEG^wO8R7}zxl9*)Kh|G(*Nekrt;eF z*)8S&?{I6R86pUh^Y6Pztkef`ZYK3VK|8^3PI^@833=X2>Tm9PPI_JGCklT5xh6;# z+!JWYYjXq(rT3LOQ0i~K{Z8sEL6P8_Jbx)@B>gDCFX?NfY6^A;3ZC6h zgiBu~Rgw1^Nxw|`)`C9L{}BA<4m;(XE&U^@DyhG@+Ayi31^a(jInNgS=8^;C{8irf zx77Ah_e$+9b++Ju^uO;3hoyFw^KZV|T>9Vl%%)Nw$g^Hje@U&7`kQ0jle$ID0aC{a za-@GJ)m-Wk!2&@)!D+!_!B#<-y#82fnbea~Crj-jRYR~<`hNu((qEQpBXxoNJHNG* zYUy9gv%XR<2#!b}E7&A>FL01&zxleOR1?8Uc{Wn&d8tbT3DPT4*GZiwb-KKFv-I}T z_m%!Phs=_G$>|N$Y~M`TqNB8oI>)TT7l~?cg$FmTuyD#X?!;ZA`Gy+|uDn4-kJ~JY zx=U}fM?`#o#MbVQ$)5L^6$_pa5%`3heNVaQ@|1UOPl@{eoUG&*ydCqB{6#PMc=Q#% zi(hji@(nRLZ)h`3laa49@ix_@dy*z8ZQqjS`IdQwZ#npf7H)^MSb0Q?PDizvXQD+` ze{F7+X`|lp9R~)yBk0FF9v=KaKZ6gr9Qug&P91`;>frl8hhxDy?D(QfZI~__$LR6v zj2>~-ddxTZL^tD1kfmov@KH!@?4lNqkwW;_ixVt}~0DJF|MG3+?B+@L`?{gI!(t=;6Za2p1N|y701~-An@o;C8mpc*Z z?v&@cb91T(K{_66^YgK#fwFUy%_D}#YrD87Uz1=vWYj}dw3JN)*JVO-c%p==HMG|GT(Xg z@q;(|Ufwi|^TsIKo2lJ>*f-4womW1z$njxDCtt3d6=eBhm+Q-z27WXf>__EvKi19h zhF~wG1bMzy-)DL?#eIAa80AmOKmN2_>d(bp z{yaYJkHs^8KAHOCWbIFf5Py^if4XV}(6(^^Gnxc2b$S5SO9J?{DS%B|11LTpK<`@t z+_)XUsb>LH>j#kU7r?-X00yfE^0HANT5SVqI5d##S%LJM8_45zfjm4O$la%bEL96) zSc4$qn+NgCI*75}LF~&5Vq>+SE{MHq!QAW?O!nGf{9Xk!${?5pUcu-G2J?4DFz1Sb zSyL5E!uMc2wuZ3wc?fFSA?Uh?aJi+5haFX@^;3~QLdEoRDt25~Vfb9d&^IdjX{&J8 zQ?c1fMTnh>JSP=v+*I80RPjb|F=0j19$lMJPcA zq0B1^tq=99m1*T6wcJX;hdfs&c(UmTsas{-j#4%4a2c@ls+V!m1W`d z>J!1OQ4x%s9D&J-2=w+x@bAG0_S}h}w|NAA*hWzA89@)P2m(VRc$O7`x1jrgNaml2 z#OGlo=j$Vx**=QfMo}a9fsBO2>d(L8+@O{o^Vc~3s*2;*k2se0j_2&i zczliGIg}WWxkdsn4ks`#DuHe-6InPqk?pe*X|XC1U9&`jTodW7O2oHw5;~)j$UB(C z1;rVyx}ic5=BdiF`hb5AOB4y01?J{4Q@R5s?P z@^_~+dUZ>qM&Pn1jqx|qFzuO+(f)Mo52my3P&!p_)9LD%&ZMYxE;PvCqmsdLqYPSl zWU!+ugTywObRV0E{o+irv@$8O%)~1olU`|=v>BJhl0{j3Tc5>^omu1`%3`5W7Ri2D ztotXMFPpPDzb~5;+Sx?4%Hi{j9Hwo_Vey$9=J@3BE+&T#opSMt1;6tNAE>9`s z;_*VB%r9cw$|96cMY!r0(cZ9#HD*QVIu~K&Qp8BNB4)=G@o{J|GjA8;l~+uw+G2Kh zD4}Rx3H#QUu;N?^3v^0QsY>WpQNrWVrJO%nO6R~*tnZeQq*q3ST^Y|E%H%V=jEU{O z(XIbCR;>R<2fJ_h)P3XX*m9m8D`(Ela#r6f$JwNuXD=%_WLiOwzzS@ZR${xUlIs^L z`TV?+ua=c`cCD13)hf!qRnga`hL&YDSZ}LE&$X8Es#<=yekalMJ7ZVXp>e&AzE*Yo z=<$Qtr9Wu@`Uh1HfAZ$#Pfj}hB;evN2HyY0k&X4}3Ptj7r>3m#p{7h6rlt(qr>2x$ zQd8PlswpvcYRWl(HD!uQO>wxTt~4`MS5ku2l|lIpl)QZ!%Ady@Dw*nylo`t#DHjGd zR>A_BDt6Y*lm`2oE7pTrD_u9YR_s0hrz~1+0^uvdQ!+M~utv|6 zwxy=jRGHGNry1>^nBf#-MnbYV3pQF{HPMpGvn{DOW69faOM(YkF?6RDnpdpwIb==a zqt=YkwC1b5HPd~piRorTUJo0Z#oDms4_lsWw`J`uTZTB;a{7OE7+kmG*lRoXXW0>T z&7S-L4xF3dz|H9ntl8;+Wt9W&H4ZF2=7?FYBMoXCnbXdRF=L%LApBxWne^SAG3e!t zFwmb9eku#U9o#~V2Ohi8yc1(4lr*Mr)(Jow0cA@*9u3Ve$N|WWT1RA(vp>jo; zD12hK8_O@dp%d>$=R!B~=epDJygOYUyOZbOj`MI27L4#9b*Bfd(mj~1=E=8>p46Td z&hXHa7SBBCB3NMTNqLed82M)zIySY(2KSe!Xa)5N6__VkfAqA-Mrb9;LXDE zK13|`!P(A-@akctC-hA4y(* z_VK3gFSP0LHutV7Yw&K28D1 zhZEMm0c_kA$l7axDAxmNWEn_tpCGIU1~Fqo5IdI!F>!Se{Wb@2>`4%79D_Jj8pLga znV=KQT(@8nBZEmE93tE_1j`8_99yj=!c+g9m3O?5O%7oP;aH8dVq@Bi7M`% zRnhFO3YQlu>SS*3HdK)kreasNiXW{*X(2P&cW5Z@riL<=Q0k}4Onw#0B)d>LxP}s3 z7fR>eVazoT!1MUrkE$z8Wd_NPSRs~#mhH;Pmx3fntT zvo|%A#c_RP9RHgXNAb2eehD@mh~wd-I2P;1anvi0FA*|( zTgMZw8&ADuJnNj}>691G($aYB|487E_X(7mC*Wm~z;%}dPRA$ksB0o?mI`(xlJ{RC z_NNj_y^u&h`$S@!C*jvUiCeRhc(yc&7t4~Eay^L|pOR=+mqc)nWDX5XX28N^ayKV) z`*1QdER$*XE19>0Qdmj~AE&3#Vs{GDZ>CVIox&vD6r4;`NHb4iYHAAS(o-nQkn@aG z*33!8XGtof)}>;4B9*cCQt^A5ik5pSeHEF%8`4m2rZM+U8kLXJNP3w@@T)Z1>!i^w zC5`*-(&f&V&h%^PYzF6A9_nZGa>!kF{I#7_ujiHU@b-G|Xd=Z5{`*^9ZfT!>&Uck^lVKA&pye5UpN z%BbaE>3#PrM!H{V*RudGrGQDZ3wXP!fYn9?%yucDiCX~!(xp}uFztH*sf`O+*S(PX zK85r-R46}th1{tqWXP-{nh9T+rCWrBUJ-fbMfh12Vc=QB1ivDlbuDJ`YQzM6kS!}1%S zLceh^=^K}(ma{}D=Z;S~X1?V#4JfB1r<@iYDsb?sppAD0A9__XaZx2JuT;{_v66jG zg7#Ic?^A`RU^`XREU%*R?kb)Pp`_b>+22 z17$?>21h54V2~G8z|pzHc&JQ8Yutkp`rLr(@@5J)KFIHYAE6U8p?)w4V7Lu z8Y(U98!8Rl8!AV>H&jYmHB!EHY^1zB+(_Bt)JXC0Z=`Uwv9fto6Qy)b6D9CX6GhLs ziP9yciPAHyiBdPZsp7Q0sWS0cQ)Nh8Qzda=GeviHGv)C8W=dd8Gv$8+n=5}bYN_n& z-BNMu(^3gt)l%_`ZmC$WY^Aha)k-N?+*;}TcN--n?SIOSp6!%DN=K#H>`uzz*PWEG z{A2XJbDB~q6^h-ivOM<&$5!2>*6}9yvTst*=`L%g+$G}0Lmc)$Vq484g4#bO zuE!JFhP>qX$X5jNiqsvi3ETIYs!p0rovJA_Nt3!unjF~wmcx2)S-V1u&beCb`>ajc z!FRlgd&khDAJ~=ofu))sd1w2PZ=N5?OZiC4bvm39w7joF%28d?8tYNgLl28RpBZ!H zGm86XivH5)ZLB`?Q}h}1&Hzsb1M0mDCVLuNGX@P&p`zF>Ow3p-4{(9Ze`{vKaA z7xjftt<32=)|{V{%-M0@obD#(oU$_~y{!cvvn<$p+JcKk7W`^uNp(+4F7CFZmy;#S zoGqE@ZON#=ttf40jd4e7HombYx2FxihTHI2u_1Z04M{s~&~>n3otNlO6*j!8w4v~C zTQ=;qB|p*@pIloW*4c8ammQ}!*ipO3j%#=9=-fzjs44aoueax&zdd_%?TOy*fS-mV zx=kDzbHkDAnU0Ka=!9QOCprs1%ut+&KIz1Sn@;q8?u3Q26X7mS1iCw+o$JJ$hR(ci zkcK zxxe0(w0ExbbatipR5$uBb>q%vH@tVb@#d`?raEq<>bfy8)J^WKqE)525!=%pjY;mb zP`P8+-UIDU9+-uBaJ;W4#?w5xy~LB*dp+57$&={jUUJX!;@L1SO7?lN@qia|FMDC2 z=_Q|?UNl#EVJ_U#x1Tp%-;35Hc#z^v&*na;jq%~1Q$F%J>%;r|K3uc)AvoTL>&ZUs z%=6(}D_<_P_vJ=sU;dcs%bPjAs80Ffam$ybcYM*&^d;ZMm%vzGZYKKDx5Ss|Reo5m z@e@r(bf^1%9C_r&kY|2;Hu2+7x*r-PeyErG(Yl{Mz5n&+a*1eB-~G}2=}%mn06qx^ zZM-&saKVlB0sMV2fP_Z@jQkKlRAd0!$pNevZK?dPK#~Rok}x8WQYDbOi-9=Y2;}C? zK=!^2q|KW^ei;YyUqB#>TZ+!qJ&4l2LG+#&MD@lX*8LYmx7R^rIg9R;9mIIio3tAR zqpJindV4VLH-lOACYWxn!B7~?{a?Y{X%a&C+z|de6oTRP5W3zDp`A|%wy7a#k5OST zLnZfj6@3@0=(Sr#k3%Xh2xm1m7Y-XBT2!!#wvj4Q^Hf+?s<7`J%8C-A3@5? z2&R6HAR{UQhk^)_+eLEbpGc-1j>N%4^diehG#w*Z-GsOH zJQ_pz#Ted-j#Ts{hADb6T(OPew08{3w-~h5Vp-WKmY)4$$rDW}@?|VWZ(=ziy3#P6 zSms&BlI{`9eN`+WiLtnTkLAiAar{^i$FfCnX;ejJyC>rOL`V^?4t z=R@Q8w;+xvjd%(s#`E-4Je6nTNxK+No?|?#GUFLMJb_!I6WBE-0oU0HWGolnyEOr` zI|)oQNnpQe0{_cRkh@|6RTT+z{v(lf0}|;mBoWV$L_Sp}QvNHE4u2-GbD`i;5?dc8 zam+o5>HbL^O;6(cfMjBKCiC-9GU2zANxGXXd^{N|^JMbdq@dL&g#r30#FnOD(k+#U zW2p=|m&%gQseE)u<=l)k_RJMNJ3ozwi_!?%okrQIG^RXD3>UB!ids84M1|U|2~8d77DcyJX^*lgVL?EYjL$ zq3p||?r;`O9%k{+(=1ZFviL77i%;LPXfZXLoa@>2yp=6?4$+*nvZ)Nqrgcd+FIwl2 zvm}STcXBvqp2IH39J)s2kdmE4*tA?cX69mYBp06NawH=c<9T^h-OS^1cpk?K^0050 z&$C_mWS`GxmsUQH1t-JvnHQH&$J~6nZ~RK|*{>Koe&u7~S9Vu?Wnc^8pSuc}{=9%o zjs>_o7cjcEfG+b3c`2Op^@TzXx)lmvD&*2Q(V&zf`aLhA<=Y}61B-Z4RfJkQ(W5#S z)4qGL{QoaTb+?$qiN&-lDCTvK5>ls>u;xMuStcdqyO+?Zw1g8aOWAg%losR5xU{JZ z+x=yz^vhT-8dcNqGEQZd5xL^K| zR#k9vO$Eg{75o}gMd89K1_W0#D?xZ*T{ZtSsiE_v8hTx;;lRThZfMjp<6$j2PiuM7 z;yd2|`_3Ns@6=ANKU0|&xVz1O8IUzWo-*}rKF9zGO4q=vPrb5;nq^U)RmAJb!EVj2FeVl z2Ex}GDEq1#D6cwcC==T>QXY?Mqzv(Hq=e3BtW@7_tn~C~tUMdsL`h9-qAcmyRJpRK zsp7Y}sj~l%7Rsw>Efg!iR>}b9Ic#rrg}qa*l5_F~9fVW08GMU>r{AGq+I=>id&qxn zpRlpZ6W;89#v<$I)Ym;{?4%d;@qEe5$FG>%^$lkIG-)$i6ZN@o8Q)rqbAp3$TB7%9 zlhE!1V}wV2GWx(gzYm;xtV5ok4g)&t@-$YLJ;U@^TcpR-6`#1ZPQ1IE&#bEcjP?e7 zzWL}=i2*jt4R96z?t-=ft7eHNcHWTrO^k58WJE>05xcXEIN!jSz&^(89AV6xRmO~O zVnWltChW2^p+S)e4{A)<(!~^$>8AMYGiCS|Gqzke!}q5d7Rnc%&G=7dgH zXM%P)6Z^rL0&i!|_&am4i3|UpcfqLCh29-psp#g)z1gm;-{4C19#=E1qSp z9B=2wsJU(o&2q!A!i|uw?i^O!dAG|Q>nrZ8HFKxJ(H*l8cbbQ}b2-kPai#K}rXHj% z@W5@I2Zwiii09}*R}&9Hwc{22lC*xjt^2aMr8YX(uHPw@K-#yv)!;_HS zUKEV+V(&sP{tk}P7P*woLN`+r(1oEV3AZNA&a^ZX+*R%p@`6&>Sv_QG<1X58G$mNAW+}sw# z4WjScAe_8{xEdJ5>5w37BZBb06HI~Vd(Wza(WnomW0w%l_X(k| za3lxK5Ik)~<8uh%lWPc>Ng?>vg>d{273RZ*FD+H^d6kNzr^J8LQekGGV!xkwZb2&f z*%s|DMm#(5-hK=UrQhOEY%YYN|1^~C#-S8=g%aW&N@_wV4b{WQIvvKAt6_}47sd?3 zFnk=tM6U?L#Vd@`z%bEh!pMmZqhnzh(~84rRvX6UR^eO|J@DzMaE46|=i}mVI1Gyxmg#^pF<<)yD@?X=OQ?AJ_3KU2zFXT;3<4-P|Ha2+D1~>F_M9Q zMbheIBpp9Sa_WDg{|TCQjiO4ha&8p9D}-+yh@#!0DDtmIaj00HHHpT%c{G2GjAq%? zXnG3Yy1h0Uoh{KgABg7Bp=fMQMAObAnxepH&R0frv?`jy>S%)c$DlqdhS~FC$Xg`d zo^Y^{+A;Kqh@p}Ad5cqHI9M7(gnF##;IUNojHO~&ELLM3#A7Edozc-KYo z@^*}fBX36>EguT!GKgbdQXHqUX3kA-(NPE6xwqjWs4h+Zg~qDpkbrA;$9F(iWrOERcB zlEKWo8FFvT;9ZhnwRn8nGP$)UlhyxaqW(G)*UC(olEnkE@L8Qj?0;Fzxt&GMqb%-P zWN}0-8-4NkeD-9s{B<@J&e_b1%w~7T974N^J~%jszMFE$Fv($FP7X;ebFm$nOQS=% z^naGiox)rkgl~O6n1|-Sd9**1N2*;O=COG!%gN(@VIJk*^GLsv&*f|RWPZ=bw&7Q@ z#(m|j@|An$Upeae74sbhcw8#rwP^tcp#?Ih3u(2ikkzq;KpTgXN@TS#t!vzESr}za7;OG z_LLKIpq$s@1-^Jy&JY{nTq)%|>|cTQf(jb#tiVG&L965nG8AWvuki(TEmo*8Z5rmh&NPA z^@my-zT{-YXUD0gZK)E`qfpTt217%^2Xlu(fl=2)6WwdugW&6d(N>M~( zB`v&(a{OphZV zLCTF=WSZRJipD+39NpvE`g@G?yT{6f_xXJIp=hCxIKT2SKa?lv?s`IMjbyP}Jmvb2 zr^LN|M!mr^T-!Y7?fK`JJ$a79)E8`g{eoLLFPPuqCF8cgq~&elY13a}pnA>xL2pPE zsFlBA%|T6`x@z)0Rg>j2-*WM`7NeZCn6pTmH@CF;6{^id$z=I9c*l@F?@&MU4wcJ0 zo>sr7>BtYvdH#V5-9IwJ;3Hw8i9P+F4$sVVD0I|eSyNrCTkA5kgD&oZCWg9$Qj|JcL@EHDyzEeK2W78+`9zNl5>Jvp5Katw+Gdl)-=F`^C=ylY`d96M@ zU+D9PojyYg^%?e;0ZkVe@a~`i2jdNx)XPxrgNAfkZO9IPL(T~@gAMV?HYBFRkcEwm z*fZYqxP2PPg*h^=mlG@hc4F>G zCqmaa@y~WAS|4?yEYXQ&DNg(^(~04gPMB6XG4>B_@^_uo1D4x(3xQ2t!F8p}s`)^ds(xACvX{xMl6f4|_j`diXKS*N-L1ev$+A zqhp01TEc~w4fDrlxIa53v-bA1KMt4t(ZA_W*c*R71o?ApbpSmN1<>!`0R9sHdW}Z_ zOMV29RUg2-{ejGSC45)(Kv&_p?L`x8F1)tQp&&HPf=CuFd%sCAxot%Mlib;o!=nG4 z4#wbWFvo8M^XNk`S4@Jj^9yF#=nyofi{`g7gxdxo%rg&RmuP@LG9`P~LdDL>DppTd zF-Y{lbD{_8Emz4bRB`L23JuZ!x;GBRqD3fo#KYF_6)N}2P}0VQvSvXjwL3$(eI%5p zCqw!7K`1`LcZWHKQYBnBG9Z*)8p2_lhhf<{jL@lJSR4r>#2}2S>M#})PSDnHVs?Zx z^m#Z3Ux%~LIh=QH;k-(g=fflDvN(b*Ya~auGlErm5yUvivuMekB}ZWPN_4@`k?b=U zjnFO<9YG^iBoh)N`A{86ixE*Y8xuv^g($g$MzPd8ibBbn)%A&H%;{*}-;O5TLi}y- zXwpKWxfvVHfUIcN7K(oOBbw%tH*2(3bivIrbl($0oo)=y&Y}-y$MCa2JnxaQlEsbX z*7;a0ZDR3qiRF&)(aEFY*gjdb!1-~U-V;aY^Eiy!#&fQ7JY9~(V*@PxABsGBq(e|vCB=T#S=zA+AceW*wjlPL^ zwMnAq#3cSBi9v6Zc%UnJv)##52>(pBN~VQpGH1U@w(LhTuEINepHJbMV~S`^DKw2v z!B?_mONOR$`$a0Xk{z3&o624PRF;;ck|7+^bx0a!)6)2`GL7`?G~%k#=sP)G@|D6Z z7o~GxTRO$N)0q;J&Yik+eAj0XdOd^5@fplb7jBu6!JGOF4i3#EV_qf!D>7+*Jd-zd zSxgcBm~<&yKAW>~7TwM>BAbCfvbonVhv~u}cYhK6E;xtLk`FVupG)K1TrTg*V~1uQ zNx~6(bQdppU_N)&=cD&EpGPJ6*oh{m9Vxn;AU^Lab?pmi*tLLm-3s_aFm8T5D&?T6l->=$(X{C|mNffD{?%_7MwfHBTLlwVRPZLK0{v^1 zD5I*d`dY=U;wns9SF=W`ra-R-E#*6D>UFH~tiyfq4`#RjN&o3T`7-bqcennc@$-6G z&sS5l{#I88k5*UocQjBQ?$l7syEIl_4{NMs#O&d9i+%hx^dN70{KplibF>M+#5VIQ zWDmbeQNVQ!yl-H2_a?1x-{P$PEevyRF`?5vj_i-z?-H-XCC!S%;!A#JzNAcfMb`zdB!ln@uj*I$ zb$iXp0dJVR=nW$dzoEx}Z&!Ob2gLIhwMTdUH;$2qiu(gLS`Qvms zcU%{@)4CkFsLS|TUCP_)acG(z>kai-^hJ**V?L2F^AqQvf1;VeC%O#zEZ?Kg)Jaxt z$m+7N!ug|=%`i%a=0E@p3s2ys+|CShFxx#=g8w^C} zHNd6NfS*$hIjC>QTz5l;#u&0$$B4;hMtsOO;-!OlqVdMGPBdokS7Wwmn8=;Wgux?C z*f`sS?n_KW|25(2erI%`-xRH_rreZlToYqc9+{bv z8)!T^wbfz>!%S9BIALkv)eTF+J>v`V~hGUvuQ_Ek{hO9qAI`DB6W1QN@lJ z)jG2Emm`IpoH#egiQU5U8qIQI&p{`A&Wo?x%$XH!ooPSanYkC7nXKh3nQ>=6dpNUM z_+0N`XWB(MvnSq}PU+56PITeAaJj?FTo}2=g{X@zJbvIpg6toheeKE(eOK%PU5V=G zM#H{t)Q)mv%Q49ao|N-xH}*buW4d^}%d{msXyb-ffEx{h-0(?tqj!-T3tGBk*T$W@ z+a*JI#GN8PcQQJ7pw+_z-FY4~+~k4zaSuG6c!)mc!73*Y4AyvJFW#@$Nl#{+^2FOh z_+Nl{!O@;P%k(6vy%$M}7YBEHG2&nGfKPdmXXnMZ3gLKdyd}f$O~@K=EG~OX9>ANy zX5Pevdy`)4%|GG^@9FEqqmhy|6kj+g+=o`VqF*-jrEH=v8|M3RP`qD@cfK^%@s&HA zaJf!?-22l{_H+E$Bc8AM4cSW&-}j1eIX1K^ByYG?%b%tC{&x$pIR1z1V z4+3fJ7RcM6Kw6aqvh7i)geH6@0 z!(g_j2Q$4Sn8#B?L`x7Z_bvo|lMwz%58-Z62!qvB^lq)SQWv?u92csM^dpVk_E>i8F4w16}KZ<{4$dEwvm|j zj-pflD6)n`5jQ@H>C>a=vLK58iKdvdCyJ+{DRMN5;ipAw6i@erX%rv7MDf!xir&K0 z^4mt^I4l~~*l5~L6VA3i8l!8`^sGKedC2p=1i#QB*?IExQE<3$oz(~~%zpMn|ypM0FtQgf;X}YbcQvJE9(q~L_WpMThI+vfpYWD@y zoUd?;Ys@sb&9Xo5V&(sU@ug4M+gWz}CTp@MU6W&Jknv~dmYYo(q-Q$J$lX5qxla#9u4_~o%SaZ zZ9e1b|CzfgeO5LyU``(cM!uB%RI%*!4Kc*rQ*g=%(=a1OY&T}}Yhy0NNQUaR3E`sW zWEPvye4;7&GW#74n_+j`FJHmqfSYqH|$^8gRGHS*D!;1edThU20n;UA@v>b1Z&vI+-owVk-oizsn ztVxua9@gE4uET5ydThh0S2lG1XhXES4UPS5sEfD3>t9>Sj@Y8=W5-`p?68<;hjxS= zNs^gLUnRQB8hhHr*wbsM1A8YrFm;v#d+HtNtM16`QI1U6@5twW9ocnUbd}{!JQ7du zzP=N7sZOlU7tC{J@O)=Z>=s?*oHMVioQ3Z=Gg93Jy>+6E>~~@7F&FVFT?l>U!aLDG z_KM%PysIm{{&8hkS2uo1w%$T!=X{x!TO|**S>~j-N^(&pqKo`+qxpXx%s=NreVzwb z3p}V4SiSS4la0*4j$Ztn;>E7D;>(FQmvGmM>-W9*7V5>{WnOF+UoLxw=oYuUslO+h z#y4-8-0~sU$OrwwzO)vulbtL-igNv^&-bII+K->LvWF)c#pcQWk`?mjhIn_mEB#q3GjNQh zKknkyos9J7NB;n}tq5S(jR0QX6@9`ufO7HbLW2X)2@9Z;n(WXG4a96#AV!MAHpxawbIlu@FWDhj3anjEW&DCf-(wuP5`)RK+z16+a^+ zx0I-&N!w6%&k#9!CH3VQhUChGG)N*iGU5 z@iv?_pTgN17>-+7I8DEXQ}au(D*~5e5u7*^!JPLIWR*r>^&@kvSb#jOEyWeOV`>*(`ZaIc{8Hv zurZnzucFcNjpkBXG{f4*P%oLK&DUdSBHF`q*BEZa#!yozIi`WJOxhZY^^RDM+>GV9 zWRyG&WXH`=_Te&PX)M0j%=4l@xW%#2J&qGmaZJpOCu_Ml!qqNT#u5@SiSE zW=~u)oAZ-N9h}1C5h*+#o5GtFDTJI%VdUEssz1sOob12#4^F`{K7|usQ@Ad>Z^!;f z?$u3wL z3v1J8BJ*;}{B&mQOUG1p;@ZhhT-Vp>ypz2BKNaa5uTMu^X6NU28BFb!fy4L=J}i_y zIl+xp8O*vNewt+Nqeo`S=c)K;M>9zgePhMROujqI-ki+O=SAYH&CX)-_AD&ki>LN6 z3#};0*#FETyLmP@x@J>zEStV(vYBp?ja~a3hTqNMdTtJ;WjPdUV0 zSV+XhLK2_J#-(u~y-W+ad$b7ri|F}BG5h-!(^)*V`TE6-cPqxlruhyi5 zZ5buJs3~DieTnP{mr^{nlnygXv6xkgno%kF`K6fEm2&&9GPZ3mW2;_Cjo=aGlua(D=v+Bzm&)lK zT2Arm3dy=vu;*w6lmDyW(#Z;TCRFfmvq~Bauf%hFB`;4_vh8&x!%QkU=2S_pTP1zE zRG1-x?MN*04OX zhPtmc6tAjf&&FDQ?ylwN#airIeW!nq@3NaGaF%_(aN!d-E(!)5#r%xC;ykor$z z8~ozOfM3j5F1e`HzvzDR7ZY@U;k>P$REv7{7uTa!QIBOCHRWb!HO0NFnqoatO?kXj zO|g5frbIhP^-@!Apsu`CsVn7!8Yp8YG*E8cYoN?=Y@j?#Z=fuxZ=gKssi7Di(omY+ z(NLD$*HF3`YAC9D4Q1+~hKiL*L&Zg~T-8u{v#ODj@v4zhc)78%=0jtp&A=wgVfQA= z{4q_H&(E4FG3}cv&(1YdYHl=B(r-3XW_vbM$^)AzM$?)rVQZT!>phw)OZD zfFq%jIa=1l2@NYJ&Z^11y}5WX%bex+IyjSX%bD&=UAS;Taz!^>sMzJoKQ*pAYVF3( z4Q@=h=Ef>3H+r^q=RtROE?Bvv?(fd}5_j6ky?dCZ2Szy_^7nh9-^Y{K%bt?8@svFf zPqxKKE~t?gSC4s7EO+b8j$W+m?~TDsZ!VwmX8RRy-rx0Rf$)ru5#H>*=tDPOAJoMY z8C~GRt}ebbT;@x2ZC|{+ec2rE%NpSq*E;)gZK9trKH(Cp{P@${k3ntx(d!_4_#yr> zul?CSAb@Fug(u7nVB8Y%HctN^NoO6^b+&eKT2N8!ZXLU^V}Dh|ZtPaa9#IgFj>7EZDD>-y!mO?+tUkqVy>>K~88Q2u%();X8h0}3f0RaZ zevjtt%z1#B9fjN&gh<7rc5^H~zKF$jW_8}S#p3&tIH+%qqZSp1?8|X@LEl2+ZX8bY zJuI3Rhms0@T@{B*%r%G7v$%dN9u+?EkROtO0F4CPuu6ah=YhQ`;Mf2fyl-PtKHUyy>d=N_QGI{!2rKQaUuBrsL7ObPVMke_1>OMVHxIzn=ls7ktCM%Yc4j29EOW+AuK_ zz3Z4IKAwr)CVbO6GZ&MdiOSRT6y9giv&}*ayX^P=%f=XidEpIw&)&_(zVF%iYMzaq z*4bE?&G+rV9H_3!!PSj9P^9PZ@;-a+PjgWEo?SYJ92^U07N(AOeNPVd&&b8q`OLWR zj_Z2j*jPL_Q|v z<>Om>KKe~8fb|OY<{S#pKfC}<2?dCd<9*CGtzvB<2B{RG?H#*rAB&)FU4)ChMYwmn znD6sqB++|_kS&4Z?h@>JT!QycN+1(bLQYC4dR0oXU$qpMG)occUW)C3rErZY#kG!7 z{2o|_uX1G=F{})}qsr(_mBD=%zur@Z*H_A*`GEe!t1?8WmEkh)=+DMw_~pyLCanzX zTlx8~47p>=vBsP4-}M!!zg&UGuPU(nQw6$eD&QwsiMsxkXkN#+@Tp4de_V-S|9cm+ z<2EO#5@E5G7+YD16}6SPEXTgv^eUWGsN(yfikX@!#D=l!mR-es2R)6cyrZ{OWAM>x zSbwd?V$EtyNUw%-RW(L*S7V`E4eXaN5A(eSC*5oK-$f0KP>U3uTC9zzh0KIH$S$gb z$+9}wtgOSzRdrZv#J*cZ9S-xDHr8RVEW2&v>M?J6J)D@6$)X<;a-|-P2K+j)9ub-K z=u=#exZZjM4rxH``UWgJ)qvgXxD9n@$1RKbFv?S|M7}3a_?S*luk@T}c~cd)nB`ZAbFpcKGxDFTBu>JiT`8GHpkVLpz2C zx8q!4J67E3z?i!on8*%X!qZOpnRP;;qZ5UFyD)M@7pAT0g8Pv!=wIm~f29i(?sg$a zm*2PS0z$iRJEn_yUiRfU4>%v`MxSHd*m1iXvM;-l_=frEh;E$E>c(hhcywp?;MmR{ z6rJwD39}xoN$Ek%)_?eZjQzR0|Df`eU;F;Unf!nBEqjr?PfpbZdgm6SjQh0b;Qb<3~`GiM7O;X5^k`menNeKsrNC_*(NC|@=C5Ud8 z5{e#535tKDglGSxgx7uh2(M=J5vD)yBglU4Ba8{|BRmn27H*A@7J^1g3&-|K3(1$H zgi+llrlH*iT6P z-e0IG?=Pqgl@+?T%L)sR$O=ec+f5lc2_)Wy~vx`e;}?- zeT-h7ccBr+)31~HXGHJF7$NG$Sfyi(K_n1e#u)vY>NfduG5dpX6F8|B^-h*ktA;g$pcpO)vO?X!WvzdtdTEmgB^Ws&@sUV zf0(V;xWTTXwGB3<+F;sZTYRMMw)=xEmTKA}&BzuZR<;ngvBeZ8TU@3NCp@%c$IcF4 z&FmmwZ3pj9_E^8b0r@K&upqzz+cO=os>uOi%-(+*>4<_+%ukJRguy~bq%CvAnjMZ< zF6o5MYfjkXW@>xlK6p8jJ;(7rkiO_czsm<(KGP4=q@JhagI|t5s1#w}QHl%= zIbVF6>WfYP`C`!wU*s7{+~!%-7Ic$oPinkUGLo|?ZO&h+ue5IJU{Ciuf{yFa}de|8-F5%k<2W8e5= z_jmr-!5^2V(Q4|7UkNSqJh?4@6r)AhePK5naK4z_B2_I2(jiX8$E~f}kK43}c>(e!)05 zAQ;Dn1jC2CuaC!qvE*zpzN-gg3bRwA3xaXKf_bXyVE(;=;UvZ`0NEavN+Gy-Is}hD zhQR7`2!@-5V4O<`PWyylY%=?pNZ7SBi|YaPej1was<+%BXBZ?K3z@(Zpuc&X%;=XC6Q1g+w0EL zNX(%I=^;wJaSgiy>!VP{eAO8BC`>VqLhE39Z_A^xa$PhgDMrKUP&A~DMPpS_G>YfO zz=WBq#s@K2MJ+PEE(Sw7V{lw77Im_*kW^p~U{fr9vM*40J(j&VW~%Tb)=5T=X)eK|jwp*ppV?2uFG zFs4_x{Zl%5PU)D~#*CF@24>C9z}ih2usWQ91=KJro@Lyw9z|K;KE;yldcyXI$Z9=pK#NT6T$^H@FtPSCgeO%6y-K12%gF|?9rP66r;7C=eA z04E~~a3-e!yUGjTN!{^6Vj+$*S2b~Z5$t#@mlPp?M-g5!Q`LC82uai#7ffeIa$PYh z&ljWb^J1)}SNHR4F?xRN}uDjYsq zh1_>lm{U;&rzYm{N7kTAz6Q&8)?lep4bmQxsqvV+4GZ=i`R)j5tijxUwRo#s%ekT! zdWY-a#++5Xc^%G|)uDrLjIlfF@t;*a3{NybsiOfR){SV-X++N?_7^X+x7fcKZ_hU) zf!f~md(9Xc!Tw?yHNJ{w=ymePDJ_^$*#hM;tvGeQ6;mF!qV7X09DcQ8x<5G=nXTwQ zxs7?{Ht5;5!LOkmVu{Sl(;Gae(1|hsbwPD)53(z$zfI_cmXxUAaal~*Iz>XL)|M3R zab6tCS#SRHP5AQiGIMsfaYcBF6D_YHC8CPf{%SCZQA1m?8iGsIV5I#X$GhL-?4A#3 z*Z&ApsNSS$fd8Lhm?%H_%hPf&o9o#L^!Ms<$V6j9O3)OVl8P)~E@who(4~jd<8ojB9&D7H{ z^*4HE|Hj$ZJQMVBM$m^P&*>0m+b5dl(iJv1&w1-K^Gsg(HYn|53zh!1n7qUm z*7Q8@eYVA=U$(fFL=Uvy7TdSl!Qr4C{@Z4cVXEYlYT9FnojsgI9WZny84;@;Fi&v6 z==BZ=x#fV*dJf2{a)45!158IaV&!`BBAz%RLCX;v|2U$`%n@5%9GRnWgiSl=GtOh- z%sXkEcEX!8PI#^DglJtS7+5>8YvzPOvh+n)J0n)v8CucKn3>{?RbnoXI7Z&YZ5Nzn zwtYCW?Q-v3A!10q(U!fz^I`6g81D|%74Dd`*&PYW?y$Mw4$Zsd zpQgCORn`NQ6FGa$^T47V9@uh^^B1#B&pCVP*RwM@nOUS$p6GAjiD{F(uy3sw@-KQ} z-hD5`_hyl|_-3x>13sTO#{j*JO^4bEPiyUHEB5lAmJFVq{FsotC;`}5Z3U~UumXAV_`Xdx+%%T^yhQhWd6eY4@n6V}d9qYnyds7&09S%dYb{JgQ z!@Lk5hPno_RJVsilbQ33kKy=a5Dux>a5PPfK-km>s9cS}ExiaxGIJgl%y}tcePt6HKE z*cpX&y-~29%(-b+G*Wj)1 z{;pFTO#a28rcXSS#>YcyT0Aw$cJi0%~?7 z;K1z!w0=#XznTC8>bqq<2~eiKd!t_>rcX~K;~)_)_9sI3bRyo&PlEr_BphC!gv8DK ze8br)E(v=JlHgvF1dyqgD3uJ`qsh$hCu1Ld-(qUO&DP2IXqyZPmt^eWiO)?Yqca%` z<}iCcKLuEpf)IriG@ngj2aODi<`k4oW)E^oDh95l{`)=^5!8fzWtlM_o(B8rX*jl$ z{mAQSu)0OBm;P^03H{*uG^|{hj>whiu>YA3#~Sk0re#23X9jj0Ay@4#ecm4#i0{h4 zLNVscmu0f6nu(Frf7b?OVs30E+OjgCOJ6toUKUP0$%0vL7M_pF#){k7c%YdLDW7ck z1(PYk9Mhk|Yz%#vgGA#TJhaR~QDzRJsP(E0%|-8qTs%3Qi@}$3;cc3WCG0oOOkl1l zHy78cbFmM3Sn`E^MvpwG#N}5-zW^b%1(11H2r+{~T+S>+crjTLBZ?rgvIv8DJoi!u)+$1`MG@pu zr~?-k!LX6@(tu)!Z!3o8$zs$!E=JMF62yHb@2tE8#hiuC9xBBX^35KpmqI+G6uu+N znB^_Q0>v^isLK%lh0N02GN_#{N7-$%Nv{~*t%puwJ)X8ula*vP>T?5(5*jdVNFxTE zY(#@oBSyqD;_s{`^f}#x$?uvVvA-EzhnkUntQm^GnxW#OzaVtFckpZ!`4Tt}>A&)asr&Ak*d{bxmloj*l{Ff;0jULwNUCFFH&6BGOni3!Vts0${D z3&YGM1j7mm!Fh(HU^%gmkS8f8gzP?qX9Z_*rT8_TcfY}uX`c|g?lYd5euGWhH~L)P z@#VyKNR0S_4VgcXzxXHa)&9ihotn%IXfj))gP7gF5VKzwB8&CVWT!_K!*BTN{YFOM zZ&XG9#==Xw+n7MzT^@yCSK}Kj^VuYGzBOEwFjp>asA`Y9NB=-M%d*8R3Lh+g@ z?s=O+GuRYErOj}1k{SLjHbc~AGh82Tj!J4wLj&2*8)Sh|LoJZF%>tJ`S-{VPdQzbU zRLL_{q(|BBmnBRcErD!HNHVw8=wO8!>PeqJFwbpi4X=29=3C>Zq77P(*+5FyhFlUG z46P@JUCI`IrnZoBu*K#GYD^`zs9R@;1)OOvow8&8(+(Fe((k0Uq7|~MB8PpLCw*Cd z-tz?BdqT#=6IP*~c*nk+9JAHlWu7>`!3**Gy|DE-^`QrxVN|@J_S6dsv0l)h>WwgJ zMHAP1LxJ76aWT}4;=FNlm=C@x`XKv+53Z>*^X2J7zP}G=S!Ti}`Jx~7pLGttcoybM zj+rke*O1*V${9q#4_of~;fj_YGPC`dm-ok<9sVe}>yHg8{^)t{k2nK=q$l{3lM;Xh zBLbkgDge?70chGvedlTblA<_oBn6;6n_u??U@938K??%`cHL4P~vl5d<({zSpDAPN_TMw3Gm zjf$($h^23=xbwmA4`DO%LG`Pljq=>fFaDBoh?j&LtoAxgP6NU zBF?O1-g+~c>xPN&izeSSF%b$4iO`ymgn7#3V!chmcZVbt1tc;5nS|hoBrKoEob}6O z`k%>|ZJ&(gZpmniNXDxo^0KOu@w$O?$jB57dz*sp?AnG^k8XWBxG>JC>(m*uFGuWA4nBTF#1} zE*%?&q~q&Ua<2}hW8t}UnBPgqFy^i8zouiaSvp?0GKcL$MpguWT}C>8@22BupA2@| zGGH(#0}mH6gT|R9+aLppwi$TiOr}F!2Kulox0#GAt2OM!F^i_~Ig`vn&Mx7ZSd@^7 zP}wYqvM(nqpM_-#Sr~IC3xl3!VT*be=38fBQFs=VcogKbA-*{qnOm}PdV4l}US;FV z7jk2DvN6&!8!oQdun6GX6O;|ZP<}5r8)9TT6n>@dDml?W6X?^Y~OAb4T>GwaIZP&%=wQWM&EZkT{!QP?np6O>2s@;FtaUQg6mUDFzr|g{9MRva4W$QANt)nC0IC(+*Wq%mbg*# z8CM2uD8t7KWw3Q9LtJJVy+C^03guY6sT?Ld$;4vsE;PHG{svjB!z=L4y#nq_$-mO5 z#F1~6SmsoTs64XS-&A3oUKOt9RpIO~=EhbrCx&YDzg`WkcVx6%RKvxp8c!OlF}%GR z*<#d#*4L27Q3JClH4q~Q%kge4H15}8oLMb?F+cYHB=w-5oO$x$&{;dc>D^jG}kdWF2hgt1tF=)q$(hhh^bYiqrCwB*QV(eCC zwk~$URjvzB3FKRebz{KiZWI`I!zi>Hk0ZOWdQA^P4)$QZXAj;8|DZah7q0TXXnEF) z*cZKcJAe#!MG;}uTM;2fj+}){qC$7LsPH>dROnwWDy$qLCR}_eCR}D#Yg~tzpfFfm z_%&BtXnQR#46zm$#FE5?!V?mL%oz#c@-qoRC0asQRwp4yK~h+)CM8S?ml6yE`v~%h zeS||}qy=+n8KIx7jBsL-jIiaPj9@3#S1|k0S8zxgAQ+7tNX*(D+{(C%RILa6vq23Z z)TcVmen5orC)^(W1>Gw&Fzv}#EQzo4~559#OivGu1u zG|Kex#M}Urat+Y9fxSF?Lqy&BgY7^6z;v|{X1z3mZi*4EJvGMRz9v{EX@*NN<~V%) zFZAk|Wj|$woH5pTIL;b|&#W=`8@W|I)O7~hV(3#l9O<%y?<9N3`P!p>xC6AQ$t+MO z(=*Tk<4!puu7;hnx6b6XFn2!21><(HBX-vX{?t%DO1R-tKQ|nrkEO839b&)fR}J*Q zoZ}u)AU8@qjyqtoJ@A~~lTWTEq?~wPXXR2 zj_`&z{ifsneb6p zVb|9nochUZ7w>*#1z{@LnHzc6zhpn^@|X}DSsa4rt3r^pIRvA>hQP&}{!C2>EV{X? zMUENr$-K9ZhobvbC=zal;^f0nj5Q0z@v2bN(4%=vjbZNTF#J0khSQ(IaFia+Xg@MB zgZRCEWVFu>M=ZPUa_Zr*peJLzjdwcl@M)VO(Qr8u*VV~tBI9xzbKlQ+UpGo{f6BNh z?ByBB{!_pz=D&B4&rU9T-nl5;Js*Wr*P^iP9T}MPW8P6?sHlpCsS8 ziN-W`qb6&xC&g~m<*I0e@Eqcm0bvZ2BFC2Y<+IFOS3C zZg%0>Yqyrz%p47y{q&FUW<|nYzLakvHneCes z=ocqI{!#)y*d*WgD>G*uJRWt)csVWwX6sXM zTrUOf5h>`YOTm^=saThkifu*oY-*S%?`E%kW*SBuNyBWlG$iV!QLjoP!#^EUc!wW( zL&lP7ItKI3jv>p^HZL9N)D^Pmy$p@afMt3H-10MUj@rU##({6y2u{w1L~AxYCgtGf?i{kGa`4~J98A;8L8lG#Xx`K$e7IAE z8MK3~IatiR*`4p?x$EVk$0Zk?LAiLClZ%7-xoDZ32iqxmxVDCR#I8K3?asrE6xF$MJRDD z!mWTJ%q%IwGoE!r$$3A;?3;QqtiBcFMnEwc9VOUjS%MKgB}jc+idOGZ)K!#1Wl9-_ zttrFa^<|i}sSKl;ZQK8t{3fF^vLnkds;&$Z{*_^dVmU;V%Q5;|IWq2-L;FQJw!S4l z^L;tS$Cab2s~r7%%GsN#z{ztJsJvSNQ@si-_pd;|+zQMss6Zq8U;SrPQe&;eiF=i} zsKdOQcO{AvD)G3k5{*h#czlRH(u*qOX;e|etipn!)##c~jmu}MQN>KUId{Ia*ztRA z)!6GGnxLH>RtA6#kCs)s;QaxVnsfQ-DmkaOf zQJz5dW;!`h8TD9@%g^q5{GQnWTQ71qa~p6=vJu_G8!>)EBgXbL;&4Ca*H$*ce0vlA z>}i7TktXh{;@9LtE%s<4*Ru&1(wY!9s2P8^HABs#86C;Zn32~5vkqE4ofSZyS5^&vK3Fs7HAvXhWV4)uy6|V>Sx=q=0zLK0^9I1stplg z?PPk8&Az)6la)Jh_<1LuyzhjuO(&U!olxA~h0RyH;NjPW;HWOLsk#u?(}g$vyRn>_ zPWkU{jIiy-;?!Ekc*L{w%Ri_aQ2(j_hpiKPp}M^n z@h5umCyW`hoL+2VcKzlw5n-{Ys4$(3&&4@pdp3v)Sv;k!qJpECn4n1B)cJShPML`b zb8Wxu52;nm995;$*E$2-yoI1f%B?!UAmxVSuiLpq(NiL=BJ>7B7|* z%2!AVGqy+y1jNs`mBOD8o5mJ(6gaHf5bl=}skl)i!&`s+nyin^e zsA%^WMkMtY8deMt?wAe~N@Ir#-k*+O$?;3j8+H=`X3tZ67y)@qm{{|=(B-=TfxN9b68#J=K>=+*dyk@=sn==o<1pR0j8A2p!Z z^bP;ce24Nw@<^S2P%HTfarvLz_wf^!fj^OcOp}>#O_U^RVOftB^v7!B@(yjZoY%&} z3~fjs&_(iBUEFrog)#JS;kh2v()I9kp+1JH=;IYnBjWBzQ5dtq8!RWpbGUglO&{AWF-8Dv}r7`|U zn?P!#3Cw#;uyCX)qCS{HA;1*l!c7sHVTvnj&EP}N@U=bpr=Dim>T8BC$!6SHVvdFt z<_NrNj$Rvc$Yhygk>g*?bpH#xjK5IL{0mtr3#?gfL4Jk>v^H5lc83K{wOHWM3`?Xg zx5R;!WXzwi#KKdSP`_gd`B#?gno@VOwuC4(xV;sYm@Yzws+bivOy!Otbt@e6w!)f8 z)(D(ojnFODICRn)mkc)N3r$PObL?cllH9?`q)VRXeF%5V7TXb*GtE+ty*VSU{J z&u=>*lKNcvbM8re?|?Dx@{skCc?Fen^biN0i(0$nn*RDGuzRC&h zBb`z4!5MuuxYH=q8KH&FXd36joQezQ5puGwvy;gjn8Y_1ga*3cd5H^p$X3|6#FhMa zS1hq`MQw~LR=2a8Ii0#)7U$=2?&zKM|9<15HSVZVWajG}J;&?r(9&>+l9f9~+PLF7 zy~n9XJdpd)1Ia2LxbwjS5C3?ei@sxSmIn&jJ)kAxiL?=(Fd#=^)O1e-&+)|0)t-pu z`LUgTfzl`+M2_=8&J;3USCgTz z&IisHeE7eeo~4@)p6B`?s?7%q{e5w6tS?qC^Tog&zU15c;>9Un9O7(W{){@|2Vb;N zFPx?Ci>L@+e25_vR@@KvGJbeD%nv(9`q9t!!)6E0`uX%Km-wTUKIO@E{%E=9kC%`A zv0c+2^KASP(B=>Gf8;HYvk<~OnDm?g*s=4WwKo8plmobTDgf_8x&JXQ07q*B;I$+W zeLMpZ5*vu(v_Sk+AX8y`5a!db{C+D4>+h16MGowtRB{*k2E&BeufjpW&>9vDi$lRs zycG;F@?fn$^ZoEG7!!1YvDGmck>T9+$Q;x=#h#*>$C{8a5vO_>WJ!&+zUm2^M5(8wPg`l+8F_dsgYQ5HWKfYBVqTN zYz7nhp41d$V)%XbQN0I7lf@T}Q=6ihU7~M!G#Z6^JPy&YE{VoWc1~jUkQ;lD`=Fd+ z5W%yQEC%f}v6#uu$ruA>z}(5v=X+vlV=Qtcxc_N?9QRk!x4aXFk^jqn)yBcTjrySs z-wb?5#7~aLuX%hkER4tCW$`fF#|}y}Sy_V<=u0Hv!I}hEDUhdclz<%(31lksT~U&N z1p^cDK}bZyZgx_$$a)>hT~E{ov4UKNm?Z8`NP^02@)VY_k7AGvQN9hLCCO+VPT%oe z3SRM?c%OpVb}0~iQ_vKgf}=SpQ0q)V*rrqzoK1!GlT@ry2Ov{hwkTeR9U4Xo9_p;_H+yo z%Rs@#48)LupQDiho39yY56OUHNd_jdZ)&xdj0M$9?D)v8sYfR6H)q0+tOb>w?5F5v zVZUJ(eJy@1LME$NHg58r@N7~x0{Ko@N^Nf-dn>71v+?{W-wMC8G0Y>I*@|pt=Ch$% znvGvA+4%A(2X23JFw`~&vdnc!r0~ta^OXMOsm-|v-j$2O{&_G58Cm=DpnNe8DG&KJ zc$LRaWgc^lc`$X$qvk`+uOSb&=Hz1;vt7}A16+#A$63z%Z%!1zi#?S@rvk{HD1^Rg)=bt*b!prV1!$SHO{7PXlpwMwBYC zO1ToLKRIU`R^q8!C6=Ut>$*T-=63Bipg@T08lG?bs*Zfde0?sTp+Pq)9i~(A`*J)l2_OL@>T1A~c(b z2&brrxzZz)c`GiQ))E&ckGT zJoyFnyEV}E^(!3wzQgmWCX$tO&}5?n>!@F3spw);v@Uu?$Y^K3(0ivo9_=zjdF>zA ztulhm1|tM08^Pj}5fWUCaF6rLcybvUUz^~+2!8f4h4w{LgejWgVmp1mo#qG%H75tr zoNu+ixZd#>)#ok9tgt`?GfaA;ExBvS60bH{!EzP%D4ScupLwOFDmM5#)D|D)ZK31N z9m*rfTvy;en|F2?{oM{Go_25wv%{$w_PG6s9Cm$s_$xA3#QWahoD)jUJK-De`jj>& z$cQ_`__{O3+;T>yiZdR5CQnJ4cRO{kU5BZE{dGa5r3*TIU9dda1%t-A;!2JyUKhFI z!XRe3AGkq^S?)cdZpe&ugJ=aAn6mC<5s|;Zdp(c$`Jg;^7|VKK;A0QYJsv30C;Re* zCt7ZLayKP;>ZM*}xOrpzT5t65?)70FTPC00TLHbcg}hH6`Opvd!I>exuvkk!Z42+q z?Y_`@;fo2XzBswT4-d&sZ@W&ef~y}E7W$!A#2@2k`eT5aKPtuqUIX%Ife1aY5r5avz}#;@b- zKVA$*wrMb)bq7OY8uhVHA+U9#C&o;3b7Tm)_}nu?FU+P-D60B%zs!~KgIhog?irzV_i zW8Q7MBcP>2pG!XiCO#3kGCqZfoiNjy!mpcVA9+|y4!~1KTV?5@IC7@8AJB61f;QG1*4BDT7 z)Ab41*2~<@_5a@)Jbhde?vW*=${mF#jo70kzj0o762?>ClAlWNYB6&(LNe#oWDMO# zj>5%cocKd-tb6ky+&#-t@NOQm{NN1#@y!nAc0eC3Y=$ zu1LjA-fQJ|Q<42O75&+-yvcs$?m=mIvM~*sr_-SIg!_WuGnX8ghWUkQcvPN-O|5D0 zoXFiR%qNdpoDRv&)WZIxb}IoL#fE8HOmYsz!DPljyA_8iE_=AvRsF1j|cTX}&x7qzbG*0~sxmdg%i zE^|D&D3H(NpKE#Cy~H~$CJ$#?^01m6%d->6cbt}wxzw{(9^k&hhull}nb{leD|~04 zk1c-WjVJKuMGMI6r7y*t@~0&QP`g-woAE@nNxn%TL9Nhh0xnkh=B(S@tL`t zZ{G_cXpuMMNj79@Aw(JqF?txY$|H;LmfWGDx%925V`=Ou!j>0B=>M?@GFC-6W?O_W zWD$J{;of1r{i`7)~*x0H*Kc(EAsbc%7=jJ-}jvK7LMQ65zcvsmshTu!#+ zni43kW6yJc2^8#0aNDT_?j0p)<#{VgK2d+NCC8US`75`0i=LQ$HInnHF{85@ zR#G+C#rszMP7O>nY9Loy12+*e7N*wXQm23tA$N< zEjd`+NhnL!f?OSr{j7s_eI4W)>d@7~-GzPY(N2D);rDv7pX-rQP>;oR^>EqHfEF>xvK@T`&zK{PzxgNaM$ts7A*PK0tb;+yqUy3gm+qz zaK9C^p0r}7ZYw*ltr%?9if>(HWxBNCst0F=q3y_8+>Yn5?TAch$HDq`1kUTgo?{*8 z|D*$hB088Y?8HlIS=FyPF-Wr$_ojEja(@@x?sviDK^KO9>w<_b2pV^6MV_(C;BrmrMqke>lGCAJSI$l3(9T z&5l}@2K$p!M1*56nQ2lL5r*iA2sb#hTo)4+9<3D>)+$rWVmI=Ig{UykT~zoQCMsm| z6u%J@TGYja)nCMfKRlMEV#0WKBzua)gaKV*Lf$_yVXT?bJy?kC*a)L*c>AuDWoI8bnZHBbmO zA0%|Y9x6OH9W7X#J%EX2FEHoGEAEbXjUqKwWR6iobNPFu+*L=CuR0dQe!{vtpRs<1 z20E5~MX}g-n8|*J!T#^~FXTIN%YWdc(@#ie|HP3{O)SmSL`9nxcZuq-_pO70KXouX z_7^>lUl^*Pi)u|>vaED*c84B9na|2zrjKbo`fwa+fa#qE*!Ir=E4LV8u#F*JX#c^> z-`qiHXoNQA)5p{sA*#`coH8TW^)<%b`&70vjIm3Bd2~k;xVD-=_n!$2PMKoe6;qVP zm_i6QL+o;MJQDkhCyIYzcK9#%cK?M;*Aeo0Ie0>k13B_O(P;vnAYG zEZLvHmV}#5jRo zr-38pS~+55wj&x!93frihAzXmSwr(>M3>wZ@HoVwHwZQa)u0cgJYE&mPomy&j5E+&v3`g z4EgAo=D&0iOJ_ZK@%KXuX@7snJ2EMdSZ8( zCsKMm$)xmx|!-3~=;D z0q4<)x!%Yv^u}5C1A}M!;1509)^|P-^JO-D9y@>sd=Yep-9U9;4EW?r_M|UvhWnx* z+829Te6eqoAF3AnL7Lv}_BKBlFZ0KV-TvsK&#s?^KYnr_;-zGNoG#+dpMl(mxH*8l zfB=N<4?whf0CQZ-e2D~N6Fpt+<$;J@#a!3wKqPDlgx~%^j5rpEEy{tAO=SLy=W_}f z>FI&csR)EiYanXGf^cI+5TrmoXMGTIH;|D|HhSyzAUNL$LgXE4JT*bo`GR4yh`W9s z1>-$CedZa#2#^jzq#W6qE1C78H(d0N8FYIdazEtr=o>SO?$i{5jt=J2B|U5fOOYrCWHmp<~`vC)uWuTO>gj^CMRYRu7?z&Y6}BN|6Dqw%khd9jr-Sh9v} z&0R6bQH_DO7I%J|#2`OB22Rqk+&Rp>h-YIl;0YOHunX~f#&N!@GOb!{CMc*FJzH>b0o{Gn!i}85HbLCb%B4nwuW*K(A-2+>`KI-BV>}jzthgWoFuwjwC_ODG7^%nH{T4!X0)5 ze<>s*<5)7BUNAG}$elpG$+(b8znN!mI=?TGg7sUtckv*1{hVfo>|qL?zoc%I${ZQz zY~5k0DCUtI%b9x-IqF+dap+Jg#P!(|%p}*Oj{6olbBq0#hT>Uim^3#Hv7EjAnJIg5 zkJ&QyG>jExZtPk*q^TeMt4pVLmyQM9>FC!t1G|Q1V3d%7zlSr>Yn_49^D<#X&1lBa zOzug@#QTCwTwI<7?Mqqcy_|))*QgzF?ymZfg%u`Qcpu3;SSGVz6Sm+HfTtiE^*3@L|0xG64A=wAphnb0&4~H0Lh3}TZ|1^RH5Z9P^6+bZ z9)5GS{!fpYcEdcZb;-jk=G*V|o8>+mF*5cH}($ z@H&rf0etBPk2T{=?N@-9r~*VK6`;3?-1U?~ESyI+=Yb;hxy%{5o_i9l$rEGVYjRF8 zJaWl4A>YJfP6=kIaxY?234gDYLUJhiC7PUh=a%92<}&g+%dl=(IaVAk$6ETghW6#S zR>ppxQUzXKsX*_O3jAVbeejzKOx3Bt<_DEf@vB5Web=d+Usrl@h7DwvE4UgDHq{`Q z*{vP6HFzIfgZw!5{N!q3yPy_7ACm#5T8n1-tZHNHU=~TP$l!V`npKaYdG$DQq8{(n z>XG@ISuW0@%3B*?sMA2U6IowL++%pM5sTCt(Pi6+@V!mUAvB@Hp$V&Gnjt>38RzFT zV|aEme)TqEt8@#$--7+KTVR#ng59IY^jOh~zxl1$Sx;}YwH2Qxw88!g86Ku>IG5Lk zy+v*C=Xqbt{W+X3|J`NYE215y!#Yqfx&w`M9k}Y(i6Z+h9GcOMADkE0WppEQR1a2q z^dKmx2b*GhaP@gF?gaJX>jV)Y!a_ti%Z}jY8KT0~g`$Gd9Z?~Di@&ya``%Ddig~oPS_^s}`)sY2$&19<-k6 z!y#WE*1h!PCK_PscLV4>F~quNLj<-OqEqG%avuDFWbz-BRR6&x8DreIYK)O_#z+t| z!OD{+D7NI@nM4yjE#Ta9(hPrZbHAXv8RXi`&`37IIC2o2c-E4C@Pk=(>pXgA)T|<> zSfcbc_X~2T;K}(`IFo9HiD_1NmuH2ov#rVXA#YqDYkZS6f=#XQB+CY?*4uK&j4hTu zwZ#d}H+n&n74Q7MyW-k25YYW4@8uuGmk`IO6S$FOkkDtaV1t2p0q|aDmkh7bqILz|-6X zA$Bg@pXP!|oJ(fUCEI(g8{|H?VU)TX^8UC%)0SEEcsDo|xxr3}bIEaT={iZaXe2*H zJupF4BqUj;7P6Qaa;_$a9|1Qt`y9Y)^QUl2@?6 z3vHTSWGZ>#k%%`e&UnMY-y8KsWO~<|PWIj!*pchz+@a`;akqT& zoc>X_jW1$csEvjALMh4@FXMb+*FcSIx*z=?KTL>V?wm8n!EOFHzn62zZ-2!5`(tB@ zKMJJi1uY0bv04BKX+=dw0DejaB7%K3+>pnB%8e{_KaaSNus9i9Q&=XoJ7J_-4B?c}J;ZEHU zXpA2HnlO%56R4ouUN>g z|Ee%VI+4rm69&VsFr-S5KRPuW%`?OCU`IGoAB1B~ZaAtt$Rynxfw9bt4?7JEl<^gPYXD)=!E>6SLt1lwzT6%pEDrh3{*j zzjHAT=k?e>3yvelkQ^~;VD{_ck#mIk@8|Kz(~rkY^LUIXW*%&30&?Fb;BGrJ(Cc3O+ndf&XjnGx^Coni=nmmJ~RTOy&D574pht zj=ds3TRRo2|D+I=Q*oCb&ylcHJP1$4vvhK`L58*}J7&y;jryF1D9to{@*-oD zJNO2Pq~mzB&VzNZ=JbF55%<8!ep%SJ zJ_~nLvLNqB?TlHmzHwR1{$)X$9?&b9Y%Cp;jV+_dD_cRprZ`|=#zf0BdIra2HM<)W9lu*cNPs_&3brk9JUow-n@PImk_`)Swt)biM`%ELH* zj{mKs=hK^qwX*E+%jLsoP(JcU=OcVfKFau+e%3A@ozeMtTapj8Va#~VDuDma0-TE} zfO}g3)^rtM;-EsjWv1&ccT##k6k_eqLhQ5QCZB&HOhOCMQ(uV0PVzjQBmp2r4SZab~>7O=8BIdn%Kud&#Wk{_(>STuv#$YkvOQ zQ16mr#;bmLDKd7J;`#^P0{*4A6J3hLX=V7qo4|Nk8BX6QV-Kke(Y|Hmv6ew%XgO?- zm80fbIhMR(uFJI?VuLD}>94@R#TEF?Jl8#=3fzeyQ&gl9F$0(ZA5w|)lPYoGU?l>$ zjlAG%C4$2%p)6a43nR${SV|piWfh)ia<4D43WLZA$ZxK~X}xOfkF3Ut8#VavRSo{+ z*TA>A28GnTiuTsx@!?whI$DdlFKRK!pcW_ie%sVybOJNt>12%N)^cNuo5@S-u$tMe zyt{P>e8P=nC;C&)b!6n%VQe$Gqtf+QKfWGf6Y6npK|S1#*TdMl9@~7$Fr$BEI-voh zd4J?8vynSjjaXRQgadrB(#<%|eAabuvPCnRkvO-7UUdsz z-)Z3{LkkiW$qu8Q_55=yuKZ;#E3XxEsApyDZsT|SHn=FY!9=SKxy)l7t!RU%2sN!? z?WpEkI=Y=6T07F%tMi@S0V(QSvPV0hVAg@muntJ3bJtI{6FI9pQMZTQSxzTHmUcnx zOcyTR??Sa>7f!^GC6?aB&!jG_oYxK4*WEbi(+%C2fB3bchdkIGWPAPZ=}hXyry0FC z|DYFk%DtG+otX_cLL&^2p zASMhwDJEq96%)24i3vJ|VuE9tnDBU*xbS<1xDZC)?#MTB!P|~}uT2ty!(9oXfLzfJ z0TO~DwX6142_a8TQiz!&DfC^!?PfB)vQ9|~gP%$Y(r+Y%PBlrPxK~OrTqrFZ)Rh*_ zRrL|3bdWVVKvp;dSz(%uoUp{TpOCHDU$~#&U(n1MC`k1iB&cT$7D~m22#$Y-3!C0N zL&o;EXbgIfO{|Pf!`C!0=#~bKCuzV! z{tv8`G%;ttHvYS#jq<5~5x!p!A}jRqw!;9~^9`}*v?1168sft_BWT|;LOJg)OA%vk z${NFRgbDmVnIQbI85F$C*oQKMR=yd;zL;Z2qB)-FSnz+(64t(!h(BtDkYiR@<7x#5 z3v1LiTI1>tZtWS_vR`G(-3vQ7_T%23BH5*{?a1x2!?--^Mh$joma@mW`S#d&+aA3k z_IMfKfX(w9xyi#0-+4zodPAP+XeSI<;so{U+}3;Ogd>B>et+o##b5Ldf4jiM#s%H=E|@%%8+wafF@FykQHHMAl|(&A(haJ8-SB0e z8;;*{gYhRf+_>wG#gE*PAL0&`Ge%iu}{RZAp_q{yuJlq4zMtH)R?19P4+3RD* z$Mvlz&PIBYFX_qNju(`jykIcg8*fK=Lv*D#7H{^(j=kKu)AWY(UvG3;dn3Er8|zxQ zlhW>uFJe9rU*H3YA3m_w^ukFn8zlp#o_VAQ7A~2vd0^d5Q&rFKMqUn*`iy#}| zBR7~WB9RpwiJ9CpnKwELUGt-`_iq%kYojoHU^GtClkBsQ%+?#!T%JWE-jus^@zDrK zkA_}(H1gV_;oTdJG3lp*TX(Boe5^=|tFE9}UnZ1dhnS?)N13dgM32HCc$8$--kIW=Ymq^C-e%zp= zw$eS48GU-2d(V;sa6TCt#>t2YA~!aK*_;G=oy_Nanw5g1$GM?=np^<-nMYrezf{8f zKJT|%<9NRB{Jhx{Yh9u+gk!~A}id_Fe#=HvW`0?eJtEdHAU>>*nq%(?(CZO9pjF2K$- z`kRu4n6bYQsRs)2I-?NH6Pc-BT!husU?!d@!f`%@Q$^^yT!i7*i(sr(gup0f?_+tZ z#ZjN>D#Ety`u#?lMLxcIslM(>L;?pHDV=wA-eEyk)jYic?FV@T>%D zzLLviSAsDPC0OoQg5+-U;l)a^W=Sbj_m*P%(^6{rrI=$`ild>Wuum_A>2$K;H`B9J zF2m8PGJZ~!(zv-?-H1zD zxc~C8iQhSzpg*J;Tfxn})4bE(He+@cbMz7|Q2Kul@_GvfyllmT*R427eW;j#*fSL}q$qfYKsb>d-nC;ssT3SDU3*o7S4W1XwJ;cH0GF{T??^Z#Mzt$#S% z@ee;o^&t6R4>#L-m~HEUUuh3kSN5R2r3b;Ada=Q#7m;MVyIv3xcBzX9_Z>t8WwPB9 zokWG4x9g z`wQiSm7DqrZ43Jg@{Rokm-ha`Q_+FKszLvu{=frn@Vv$d-FNtM_!9zslp*y03hOst z*&F~VC!8?_zl-$_C*Vd!P*$RRfpbyF1KIw zaPf#fHwg@||CIqV^vsY~7-0`z?h|8Fy)cHVhB0!cnc(d~6RaF%hDo>0P?cf^?co;i znq-Mf=PZ#`X$g7expNL!A-Bj1cMn>l>5DZSRk)95ZH@XoYsBZ-Kuyw?-6UIVJ;lvC zFI!w7A8N%Id!#M0$FarsSRY|e{;~s-oUL-jf6QEsVdl!jky|(P$c{Fo(TNv$~Zwej4lxpJBe}UEvF()!dHT>5G8V^uFkcO>^}{eW5S%R{D{#>WA)oen{)uM< ze%!f0eC3^F{~{2Nz6K(hyTFcNf#{|_<;WaY>RI|)?}BjnBRL1usMbUV!89QV(Jeu^ zw=Wo0*MjlSJs90db>U;#|{fNLJ(+J#+i@>zD2+SNF35iXS`28gkTIP{RVwP)^ zByXlw+oxlApA*iJq0fd=~iy9pq15 zr6(0*9fQNvsT2apquk8fXgjmVPh#}9XlhmToYwIs`gA`DP0V>! z8YH34CkYN@7C4SihPXj8w@8w)D2mL2*kttWOormL6z;TAyLymOio#*MZ5FbxxSAU{^pHj>q~XAeG-Umxzto(DY1FCKQKOo=CLPPq zbLUNk`7PCSEFynMuDolR4N-2)3E{8=Z-OWm$N*Eej&MxC_UAUwwm;q$kqRD zn}xkmS=`pkLS=pyUX7*aG%g!UK4#;OK{ob!W@E5#HkR{k_G1<-BpZr_+1OB=4ZYDh z@IRb`oJ%uEd43oMn^64kUugXujl7ObZb70Rr8s($;Yy+e0-yxwUj-^ z5r+y;LN>u+HFg@A`7$P_GR>3xr8@qzzJ+)`wGf|96{6!7H7wOa>N1st9qi z+{PJNgx<-#mxLmmJi}h1LJ`g~-*tm`(zNBwbAg)H#$u$9%XH*eF;*%T+yb_*QDslNG_j2Bo)vrwUfq5nM=t@kSP=&cd6_l*1a4(&A6Zr>cw(wRm zuZF&DHO#ZBF}b7~qleU>X;cknPOQPWWi=SSyarEqkb6K6?fl^yT+FS(cRo=6YUrs! z*oa!(W^ZxG?po;A*JAb%_7#WL;XAdiG~QJU|JLD?WgR@->hQ#?4*tP)D66bvf2R&F znDu%^B*jH(R^+e{m z6k4G|{ma6s6?d9j5jCp~PGsnh({0107-qq<+TbPA4xK3%+s0dJ2RZr0-Kf9v z4}kbKFioB#y8A`pX zlHI}!Qi9G4DdA$Sl<;bfw6JN1v@p(FTG;qcT3FqukDxlckMO~{kC2ttNBErHN7yhx zM$n0t5xy142+!CbtcaHtdK8(%8rNS)^&23hP8%q!&l)5+g$@?ZL=6!fwmw2g$7}Qr zdxO?tpP@D5Gg{rg;_Q-d7;g6sp++j`y7Ln?;i_1jqzc^`YFMyH4a)v%aO(Pv!YS(b zvO%5s0Cg;FR!8A*4Fsoa0Du3Ws!S6mleJK#qlHms+E}tx7t$uW(44Lh=TdzHM;kzF zksTOLF5M$q%@gev8E{`0!$G;f$Xc9W*Fvg zhJlS{_%P8NDtFBhXJC$$6m!f$JtNp%q7~RYK8;s z9Ub82;egIjju0+7LiP!@FmgFf#GJ6EuM_ zx7}XYHOz*I++85~~{IFgiFLR3@l(&*sb;l1Puh~a?&yAsX5$6=4We<{f6l z3=*?PhXcak#``RXx7n@CFpMQf+^JtUTrP&gmrv$eIBxuAUo1ZyQAOeOdzeX*iNI`T zl77r(e~i9jBzt3`A0o(%jG!+U3AKtytmB;)zn{BA%o;7wi6RS+`@NY_I6E&IZ@Ja$ zb|M;2Zbn1*PBb2Th(^<&XqebXqa}+!%c2oj8_liQ7<`)^gHN+#P|Uo&t1Is|?iGEW zME2B{SctGcw)PftMZvN7o*0YfqFCs5#-f{ zz=@0m81wnHkV!Q(5g$$_!sAyW`p1$pRho#zZtfmRB*B6=+~6rm*t9VTshg6RQ%u5` z_eq%jEr~m)-2Bx^!sK1#PFzaH==;g!#V|*tLk`v86u8TC^Jq>A9xP2k_`VcGKVk1o zHwC9WnALAc!7|xYZ10@hAhQpJYJi z1v5h)888jXz{Ys$TG_n)CS+m)^{Xj+GBN6KCi*?eM7U}uV)Qa0uTP)SArr11%o!DN z`>-Sv@im!9@6N=HapYR8%0l`&@+_`pVWdSC6cV%0n3sjmyz%n*_OxapYz*`K`?F!D zo{cs7*|0F>%@@TRFC!c7)V5Cd$-&@hIoNY52k-CZAn$Pw^yyXZcFci}6L*v%a!^r{ zgV((AjJtC%Wi&HLOW0N0!Yw7bF>C=nOZt_|67w)`Wj^dSkw>wGJk1N-P14Lq zNI0|pHDptW7od9u857S6pu_BbZchQa#*v{poea$xg$UFvgf4waao0kO^(aJ5TOn3; zkV7@32;UwT;rYiRT>esof2!O&^ew_`f3nD<$fIB;Y1q+X1Ya%2nA^pW|5S`U{_M8p z6|-km!X4)lyd;lGRi^~idd%tjl%Oi61XjHzNaLHlq7<>@Zl3;9in+`x-MLeSuKQ(p z{+xZcPh}WyR)*r4<*3_Tj-oT==zFmoBd${0;_X(gRgNJc<#<aAYU#PxVtQUJ0=jAuwWbayw$}~bfdY~RW03lvpkFW9#2z}Rpt9lKnYHomYW+RRkl8N5Yh@n%PFji>7 zyx(L<7&PHipJu2E%~(L*r_)3Fl{U>d-O~)=7IzQdwNS5Vfl3K8Q}r#}*K0-Sl2-IP z)`~vYT2XDm-QX?>G16Has+L$D*`$94c(bRHY8A(dfV%#}2GY z>3~mu2b?>&u{@;{yQqcfBzM8Ivt%azU_cK{7WLqzWe>(Q_CRA> zFKSQqV%{hb;nOk^VXhz|m~z8#k+!I?wvU)#Gf+&hIwvMHM2ZPQvzSoFJk@b!2|<~9 zmWm8{4t$pPC57`2yvKYbg(&9Vm!(PzK70EJtq1!EXR7-My>c%XsxeYGGzOAF(QwBXGwd{4eM6bAmqzVUxC zDB&+g^yuK>C2|+8lFR-`9|;-y_^D=q|Hxd>oM?!gDTZhqZiH1Mm;pOtgudsEVE)Vq zg(r<6uf(jos|gm0m||FwDffy^;W)?)XB5p~WnhK~b2I4LneqFTIgak-o#w$CP0|9J z7FgghnWYs`7RVPTH({J5l;tfkPt_8)buDr6uobiwte~!8g>8<^h1GHwr`-zC@2pXh zV~w?gZ7^h~4Zc!iD_&rWBTH?e-^UIjTkVkl-3}jP>@ZQz9;>ObecfgcIp)HYKiYGj z&>k+%_GqlOM@F|jO8ayFW+itTuR8F1nF9(+9bnk$fD-26pU~%2IpK&`yzvsAIbx9v z`J_{wuxX_e#;ZEvjgJ$iq&Q(;rxVupGXKtgq_UwibiABlGS~&rmXUdI!UfA-yI_O5 z3(^hAF->-XN|Or;7rA2cI#-A@18>Y5@YqRLNPcp~$4*yln&yV+CO52Wb7R)k9UkxK zdwz4rBS&{kb9SfhOCEb0Gw$u~614?!C2=OjOCHRaES?q zKl5nO9l=<$kvw&6vd`R^Z>M)^9YNiXY=TMCL$Ty68K&f$=?5@}7RNk$S|~y@xl7Y0 z48CK+aAa{9q_%~@Q#A}hiD7t>P0o6C7%UsZU^O=!e;1KmdY>8g$KkvW!x3j4j_1zd z*y0wBcJ@3M?Da@vQW6nL{Wdw$Ni@-#7JX2jFuum!y*JL75AVlKX(Ma4r z9SM1dNc8oL#8~>LPZXkX`Cb&9m}S?tjY6Jp6t31pA(u>o9Vh9V>XAXjzL#TAdo>2qUt?gV8iSS81~r*eSD!%b?|3ZYUh=+t8;dO7lsc7UtkWwUu$5W$ zb8*nV7KaHhctlibHT*9L{nd(@icOS?8HoXFk1=H|4pP@hBqK zbhkD)wNvBaEz4f#gamY-NkGS&1bk9YV7?^*1?35FYD|DlGd0521nirYh_Oo(QN1IP zw<0}MzU}`dVw*xD7Til@Z#)t0d^3W`J{3tqHTA=Ryfa7f#$0W|o$cZzeC|y`?+|9v z!;+z$MZc6z9Qxpl6y)Tj;9enlXJnpzXi7mu|5RvwPeq4L zD)jAAVaX@Tyju@B1}7Gzp@f>@9%j$i{Yk_3I5JHW(qNgFhTirxOdXt#y=0oEO(R!* z26?8d)A5lS;x2YT)lJiJkQ(CSVcebhF9R(%G9bs?+fd$>)lJO3No7KJY9>l%W+Hca zCg!fEnsOx*^}jNqqs?s^=S;{f&4MTI$6xeCS3b&uihdTq&oiqY!!7OfEUYQZf)-!< zGP2KBXJh+1ve0&Bqtb`m((-J?b!Owr0qTF($thK$9;lVW?ZX`IN9I5?gnV|9Tu8j& zo%lPK8>xACFe?v@tMX7qp1}|MJlqP*!>PhNbdSwPtU2#SmwfKU7GMzyFn$B`Z1)Rr z?NI^d{UWnWrvP)T3Sf1<5GOAdGOtvK{dWtwQC$daYJX$NEA>n-gqKtidZre^i5lOC zy~Vgq4>Xy#VJm&mcjS=;E0^GOG;?a@>}PU^W=fw@XiP4}=aZ$}>MDh?Q7Q7Llp$2$ z=Jnn(6r3Of;UsrBnPETLOZ|>~(ya>RIFUq;le{r?fxPtt+`7J1fdzjn&}vr!y_O32 zP`6u7-A-4%62Fb8+qqX_Ms_7`%2(kaw`&46Rw3#SeNOW##Cues+P4Y{)m6}ttLA=G zHTFKJ!EMzVY^|%omF2bAf2|gNV!Y4p)Zr4fHhI;0oacR(nNW`}3Jv&V)BwfA22?gO z)85knnL~~IJF*c2Z*s@-C-*J08ev`8i1i{(P#15)!XZrN5JxvgDtD91^AEGhaG&HOoBOlZy#69W2+3;X27h1s*j1&P_>!rSxWg2p3pL9efb&^29BxU^16 zI9wzpe6E!ehMTc>XxK-X+AJ#=4U`iGOT8j5NEts3zd-cAABY&P0;Lzfs86cHR9yoB z^R)2AK@V}UdN_MtpF7D0SWbS_;BZ5HOfkX|9bu_gmASWr~fiub!T@a}6f+1I3xl`&2%bjlc;o`>cUT%;R;ph4&cYG~%htR># z`C%T|MP{h_C{J8m;R)*iPoxg?g6R$~`iEZdB~vJTjt^?~`{3kBA0)ow=A5Aq=FMim z>?w2IhQ4qv=jL;hFV-w$=IE>+`NV!0Ez0iLHh+w`L?$60pChQA4bVJUOn z{7gMRIuKPG*$aCe$j_BPM9DH!w3@plu|a5`5e(DY!O$kp=eB(?S^|PGZa96g@zhwR zgdk9ZIqi@T9H|Jw3Nj5I4-18fdMI)dLy@wEd`|`Tx7g=erWOXV-(dhdU3*KyP{dsJ zz|rCS4i*m4RpBsG3CB|L2r`NyP(F{$9e&2G$cw}cNC`Xje;US12@@4(JNq9$T%7*7R(6o z^KM3QG*(nIzs=mx^+o)wTT1TE?HJs$k3nc-3^Ply7*5aY=I>ZUxW?j8SuCcC$FUb2 zhl@AJ-U*Mxc5Xf|m5#@@2l4nAK~6z5`P_VpG4TkKPC%+0ch;UH!1R3rR*} z6RqdyS*c{=o=qm!(x+J*eaPs4>mQV7Gi6<&*4WzIsRj zwhtu>b8P_zBo{zYocqob3bAcUA#d?QOxaWjW9D+?uNPv@<3ji|M}1C>d;#-981l0x zrMnR4`xn7&Z4s{1Z;IYS&BCwfetzT9F^FtL_`?Ug=+7%d;QUQ&Dm2iAki4=!Q z>~^Z;-vL#4dZP-7|Nj{@kqqtG)!072n)y_+0W{gaV(vzUxtlRD)ws;JDzO?{QkkEw z;Ad5BHHJvk;3PGN;Xj$hF{;6c9krNeRLdSv9ga?{!-`3DXeI0NPgWfi%IlE5nEcB1 z^*FM>o?Oy;obBS~THgkooZ5ij^Bb^sWdp9Wn>BVPJ)+AE$kt#UCyW|I8uf=lW^`m5 zF?ekw&KzsR$1{ykc;1Lx?%W(HXheEhBmRtSBAcfP=2!XiZWC0fO(-WcA!ukbJ21@{ zHmezV>zeUyV>5K`H6tsgncci*+?Q`b@un8+ecOT@G6VjZwV>Li1+9V1@U*ues$VNE zk7~smzSu3Tu-n-R9kW*K^=UT<= z4xAC|fZ?bPgs$ws>f0Sy_OS!QsAcrm~%Ix38t#q;mj&~vSP8W3Ec2Q67!u7~5yh-Z9!3=IZOLRkf3Oi&oyHR+z z8)5wX-Dlg4GgaNF{YM@FHIZ4v|G{D6KYWn?hXGsu;V8KSM%n+czUm)(hxVX(T@RXs z9&EYQ17Euy)JXNBdUP)~kL^X;>Rybw(u*MaTWTMAu|kc%*6f9ZUN5FMkXO*zi_g16 zguVO8+B_p7%uo;!M$!M;^H4xdy5JA)M%z9iwVyc zi3`GUapCb3aUu4-xN!TYxNu2}cZ4N(oukAB-&%2DOTD=8aiD~-feg=*#S%ixMhRiY zehJ~;F$rPoH3`A@wuEr(i-ZuZ!tA)Qgpgm(Z5UZeA$%2Co`)rc*GD9Uejbv-vouLT zDPL0X>y{KYGSeh0A|+@KlM?ohl@cz@mlE>UN(mEpa{u{~l(0clO5oU|VB$$mLaLMy zo+c$^G5h3lL0VY(LRv8PlNJ=4qy@`E17{pT$*Lf|JEpQ`ii)7NE%3lC+5@)B7AtFLBd{j^7dcv3*Qf=(>3>l> zLI-Nkbnw7l2Zx!*N|Vt;tD_#4#OYzqNNxoB=;LLvKCEmFv9O)|g+WH>|Jn%WWQ=iS zk})Q(Fvg*Hva*(#;25>135LvR1)5+E@3^SR-2Pi&hNz`xWS*O0&v!Had&@1ra&z1r zY60~z7C5hJLH@P{H_FNKOt4@+)&hEU7SP>ni5uaTPb~B9B{<-JC4|+1$ zwW$Bsqs*Prcf1Q`&ZYLWl>CL2F0i$7!N@unRBa-QbEhlh%UyAx!4(T;yWxT-d7LTC z-j8!f7C8zNgSe?fy=ileJ5DNika^}o9-JpC&XUdb*AwI8Ju$q*6CbNRv8u%r|0ZxN zXSNp(FY>}->PrPtUKp9jedQ){G-r6@{4#GmzU_@QpS_{1!pyymH+E%vV^kMCNM`Mq zN&DdAMIVHH^T8z*9~=zwK}ebpmNRc}agRGW-+kE$^~F>xX35-rVPEKr%ywU9H2m<5 zdebQtKV0RzXYYskVSZ4{=lx6G*V%vE^czL(=?R%%8vg8{`;!;SEggE1|MHk6E2IX+ zd-!r4yN_bzUm^gDl&M4g450SS8#z3HoUZ`9O%K4Xh1|_q&d%e?Km?MXf8z*uk*^0r zp}04cn|{onwLT9+m?ig;>1kGFgh7s) z)H`~be*MD{F)19mlc_(g3&+qy{Iyv)_Joj$DH(wYvSf+%kAUY`axf1vyMHnQs?4T& z7<2oNoUs`Z5xB{G+B@n|HX|ZYv@{Z4hp9z9_jy$SWpGVJ35F_=Z$@FIK@_{bQTWgsg)d{Gk-sDwBHN;2|127HU!rk1hx^HrF-RbjzjbO1RxF8u z)#@0s9bz!`Rt&EFjlmi5SV+jk!emG+^2TuQ?{F;6UyJ45Vl2Y#WAWD^77NqZyUdM+ z^z1m?+YkpmK70C}JE=__s*i(8R~%N$$3uL7Jnl2EmPVaQ`7>{N&v-b}^XyqmZYC1& zWPbvd-y}OhKLMjGxVbYk5yw_1Vl~;BZ!aWb!lgtUx}6BEM~T?^G!aQJ*vHgJ#4q0b zV{#MWo1X~Nszg||C1TP=X4m$sO@@BI6d24Pdy`)1^&=^exx{=w*_&a>Da^z$*VZQ$FXU6PZDuO22&srU z!~LClejkt_muyHHZmvz^-#KZJB%kcZKXx-^(s7>|ewpdiqPC~w?#FaY|C0`bzv<*T zq@yA|9nT8WaeE;1Y|}DOMjzDcL8mrKWD>p3Z*ypwy)_ zlQYr8*IS*5iWymOF=bBQEDKXdXJhV_Y=l#Dnq8F*V{x)GSLWcVkc0ZI>{VXNf%WYi z)IQBYhdTNEY0Rm~=E8*E9csq#@w-Efb1qz5a6-_Sv^;*N z$wS70d~_enNAoLk_SLCLIpo7RkekWD`TV;gAH8MV&goNtqw)oKa;|`SBlBr?1t_a6 zK!s=_l-Cr(ax*)YPuR88Erfy*^ZTY`=i4)zMqe|UeakIrh2-Qjr&h_%rF0Rll1(;q zO%YTt@F%|~tY|Dk)P!P8T~Lf|r^p_or`b2T7&0Zr*tom|(ML+~$fN{LLMd#|mO}Ge zDN3D6x#?SqxU5nv*+*6{DZTD;b%#Sdd<$gFEonnjM7R2}ZEts`f*4vSva zVSFGvlEu`tMC);w9m!|&>yf^w9-lYV3bm{gM;bBe9B+KnMtq&u3`f;w ze(!5WzJ3dwLRw%KNB%->3l{gbAZ9@;4k@<6@OCSicf85ld9%-F)_zGFzMg7>HT^@Q zuk1HkcjDQQF0AX{jd^ps@#zt_bOO3j7uAij%RPv+=)q_8UZnG$9azi78U#^Fc+^bDvXK{6%><2h5yn-g=LMR!rKA7P1D7M15?;V zd?q2(lAks87rjIF4?z#{w}PZ_h#J<`T{fSQ+o41a=m(ypsUS{M9gDtcz+IUu>jf8)H(S1<|6ov?yR zrWLH^$e|*?JmCnp13PSBCSwboS+;oo-4;`akvf@w-b~?oNy?~2@bhV zI9=j|;xW!h&vZsrn=`i2SGy7If=6xSGW4aF=0J{AjVsw7Zsh&CVX?m(g7e+rL+)td zM|T8klPm7$jVZRv9*`zKT>g|NuAK2iydwQFFHgAjWoK}- z7kk-W&{^$;%x_+ZH1)!oIxqa|^+KtVHzIAl@u8S(sQx}!vdss-ZulVI-v|9VeNaBl z7yr%kMaw5&?27e;BE7Ly%l*)AogZ$V^us78ZVMmtXV=yrf$zD|^Vc5^y8ifS<_}YI z^12)R;nC_3r`-W~^o%Tqj{z7J&;6dcfw-`Wx5vdm;C&zz`6m7igt>U zfjDzi5=|kH+{8O#KX-j>Ls91v%8hMq@*QNJ{Z<%q--ltmT^Jrv!<)LDJna|k|0$8b z%`B7{?}gX{5oo*-0nJYl@Y0Sz7Q25(n|V95N8kZ{t93&op-f%RhPvK4M8bV*B${j^ zvBEnNSN!R1t&hT-t=!|=8HLJSQRuivPs=z8O8)%weD3~~ai^~`3LE-H)lT2* z$lo~L_VM^Jo=m1`WS1_DM`lty7U#vozaM$q!xEUUOTgfr3DhPNkgSpbm0#p9MI>O7 zTp~h7Ct}|rb^=e(zxtHO-U>7DQHd}~=MB*(39nWp;m(>QXr4?$&gUd_|KO&NUJ_=K z<4{6=!vf~tH_zfe@NDk(T~EdoYKNB0PW5^wLtqx(B#CT?^kkqr87rr!pm8&`#62mH zxRt^^oD|%$q|fD(g5cm32ql^ zC$GgjV(3k7`rYQHFnfV#3eqsJZ#wo&Bhz6{I=RN_7j~UqdH3R<{lhG8M0rA!hj2oDVo)zo_ zZXuiLcqaMlnRsWI32$bqY%cqK3JOx5&$jIXH3ke>wTBU&(!N=0A&OhN^Ea zM$E}Y^Wt2{@6W|GCGG+J&BYZ{YMaz9jU02ay_@?$|8fyOg?m4X^MI{nKb&+u?+0VXxFcQ~JZ88yu(^wRb{E5xJcg*fn~5OOYs&?5Wk z9`je{SO1UyB!0XIo#&`)x^Vl4d{kR$`f7uUac@{Ln#L4Emj2p$&tfPC(Pv94#>e_% zAi0G9K9?eJW+|>N;FttnSP+O`7CuFO+~@>XfBfbKNz9Q#%xi&?2V3RQUbn%ytM8b?l44BghiS>=JJKD&7vqpTm#tz|ovZibrq3hTPnaNE! zw5tgx_=-)N5bW6mh0G@G&ToQD0eMu-O}N|H#LihW^y$;RZf!fln+aX`Z)z7x4|gFwrVD*jJ-sK%VWqO zWd`1kEQi!9V!{zcG2!kfF+n4YS@>)*VU`4Sy6xgZ2ea^3?~`L?BO%POmlT#dOA66a zQoa>5 z?+vbudt`V9UI%gLYFA^wcoWlFdcr9_`w8M_^n@jFNv z8|HsOt^OA*TKE;U`@W)W+BaCN`i^s{-|?#WJE9K$K;%>v+?xCoqb+`-w1`Zu^Qstk zMHQ*#)X!>E;oGZ<7{y;0Z1f8|kEx+eK@CaAe&hP}-|%|y8~Gd6G2^s4nqH~n(sy;d zo~3~u`~G0TjX!Am@Q2;>KbZJl6Wg*ivBrwJo39q$6=>ma8TB_wZA>4b4Zq3SNZY9m zix=A5j{6Ij+kc_>>@QlD>R|bP9n}BPVb@p(4aGX>Jg5t^7rHne#h;nFm{G5bDSh?O zepwI7|LNhjfgS><@V1<$kLI=1?Jn!%(M#T$zx3f|tdAahZZr-xK*}To$gD8HNp%CL z|1p4rp8?+OHpIR2hM2zIi2vOhu@7d1=6gmkdtii}+D34GXpD`z#`v9P4DniHEK4=P zr79D4%uUdC+7$gSo3ayXiepF3(C3~RuDvmX{0B3<{bh#1gUz{FXinY09DVPZ_ROC7S>Qz^_j_JgV$54h6n(dZrHLhMq^zL3*b386SpjdXAo|G) zr^#SFu4{$h9QwgUR)`m~#%=n+BQ9`5(%u>m#@OJ-20mLG%}QKLt8CHs!4|!ywm9Wui=A1v@NMFwUYO0!!CV77%qX$L!VWv!7qy2Ub8A}1m`!75 zO`LhPML+DZ#FRaRPJ3=T+hgu(2gDq4z(wkV!S)VV<(2qXOxU^hW=RgOy@hJ*2S5*4QI@c zbjFofXV?}yLrKa7!za6t>B8>mHEM}3T=1sc1rF2@9Xnm%w!#%Ht6U)*bcO#B=GZQ| zVzrhlzF4^;G{+Ua^{!Y!p2vs2ZWu7!4KJ6t(bJ*MXz7NVwq&~6x#6=ovu%>@*+GLwAKi~XNvDOdjH>puR@I#WbAI{18Lvn;aVrKbc)?DuM zob$(l8~%9vnw(b^e|Yikk6ajlG0OrlYYlh4FOmy#gDjB80jQ#9Z_8|)t9k%7I|R_@ z<4?~3_=ob(^Z14Z;w^b0@8;9TKNX1KHv=)wAP@?c{f_P45RK5hJMTgh@WGp;&T{t zdSS5E2*Z~k{;Uea@a{0wP=6fCT-@qC;ka;=yF7}_$$e$l!ZjQl0>ZI1EF3KjWR8rC zK+b{)ZevH_2z5xW`w<9x7=f+NBdEZvS6=8VwY|t zo@7R1B)b?MHOvx_H!@{V6nT$PSiC3-)sLd^AG2~^646+-E*hWKMoF@`ry3|?r(U;%rze$`~b z$;F~=QY@?&#=>)bEV>lQg5&oKQRWM7r^MoCO)U1*#bOn8$;&_D@LnyB|DUj*F({s0 zn|M^*;rECa@yIt|RxT1^NnP>7*Pc3oExB~Lu3fN~Tz$4}gR8JMMYf*@2 z+#;BDz6hId7BSaQgo_^B=wVJED4pAyPl|Dh%no^nVpOdxf$y#o?0sAU*WV@Ng)zS- zQ;G@q$m4ic3O{ZN3|A{9UxnKN`V~l`j<-v`5)G>>*|n@h5VryJBzPN7tHR!3;W0Yh4cemJhRg! zV?Ym{UV4ZgsgHLv^znJVKE62^V8uB@%=IwD*TY6Qn_z-zugoC(gZ|zw3q0(xfa_UH z%pmKk%g*xuNIJ*xy7%*w{jnpTkL;?C?9u4syA6P%f~84zuF6w|HU5{|I??=Deh?(C1ta54 zFiyM+hVAqaeny923z-W}w?d$<83MgOA&9jK!8&eF+g3A+HkNu7bJw@|`QjiOhUe46 zpt&dvQx=E8XKxsD!Q@skhxVtIde+QvXwT;7#@TRe{vHl1-EdsuXUUxGaB_emAW*9+ zz8wLHuMyb7ZeK}g1Po&$;M<=}@ClK)JCoaQx{=JlMIx{`5?@Eq!)>2=Lr#2oh01oA$pWo=A=Hgzn;mkGH1 zGXd462^blefb+~{U!IhR>C~`d<|pFgoH zS-7|)=wv3ryo5ie=QVpW^{PY3ko!cPN}FseuVf6%PsSK-RLc}5V^cRjZw8VJJ}d=G z$PB-Bnw`I&{63WZzd~}usaH+emJ08kshDvu6=NQxB7z!KT~I3jcSyy?id0-_VINR} zJ;32<_`Nd?cGRnSC8r^%EDhm9((&v?Iu^c3$LM$DkVdBCQ%*W8>e4ZycLoAwGa%27 zU)k3ToY%~NvS$X2+sGvCBDbnfCVEX|CVO%w4$jO(J74b=-1E7SiG(}cqWhZ(l|fl3 z9g_vqC0Q8Aoc5$ES@=nfY6w{llH`lW>1JVsBXui!WwleYv2uMj?w!m=%(-kN{me!< zb*h<$n z&YiqcG99Xm*#Yd8hx_C6*wyDo-J(3?EXl*P)%4G(OU>}igZ~8XOg|>4^ld)c-shu_ zVm{pd2D8dAZB7}E&1L=@Ww^AF`V@7l zgEz{UVJ%~>s|@O)We}&9VP8!dw0o2@pIVN+e5yh@-hVE~+VACPw&8OwM`{SYw!(5` zRg>{Bt^%Ud6;MB0fjwFk=-{5ujiL(Xa4YG-lY_Og5}!Vjfu&gqXR}IJSX83Rt`a|) z&zs%JpO2|R{K_gkpdVMDM)reW6(pxuvrAMBA*dR?;;XT!yP7(44Mdx2$TDUwj~r7w z+ZrsBt%byvTK*1R3x#L382_pkZxm}`$G0P?76XUZLD*P_Z`O5K>{5rVvh~>1w;n^L zk&AVy9yr*5(Bs^g{={COTLTXMYrw2l?o6L=B-fc6)9)Lx(WVjFWIEI(G@^cK6KWqf zqr;PYtdwRXr#53o4zqR4(A^*p>ux{``ZTn_V*NkNRsDx^w&XYD{X^WgRxEnjimOV@ z)QxS!o2hLm-P{gG-X*>4+aX`qj#(2s(72)lM?Z9+k3IMPygIO9Qzx$e*NL=-fpPGMNo=-6$R`A@o!r^XjyO@LWSec)UVVSa?KIc*wW4FLf*V9)jBB z9>PGfNW0h(ob`-7LFUlZ$-(mJBPCoMEhT*CQg;46GO(CKyI#rtKxt`V_%LaqaF(=i zB3D|FAKp_qw5q4j@}j43@m)_LFR7=nNWPaae{wHjH*;a(4`l_>6ImhicOT)HjGS=L zUrunEH%REAFhsb2=pz(gr;d=^g~T@Sv67l@)wLcf8+e~KPddCg^#awpmkLjEAHyDH=&2!sd@-Hp^pKb zVVy99RIwrc>t%$C3ym@Ak}+D>{>9t;zsT?Yi?72>G2sfglfRkbM}KN9 z%gnHLyag`3w?xk(ON_l~h0)Kfa97C+TX}!EUbaTrD>B^GtfAp$jUXi(oN=+i%oH0O zjp24pzAf|@*zt4J4hIbEu+-TO^?`Oc6mEy$WIH_RL(OA`JuW)hBlD~SPM>!`EP3t~ zVGi66=3n=4gzrXf6rXU!v{m#5AG3?6!EddKo>4sgYZm8gU)yo}b zyva5$bI0}T?!5Wk(afEhS=1wL&LGo$n+N(fk?%g%6Dsr$o&7vkYkXZ=C(-4dWg@5N#k=^tca> zonywI8K^b9!(8lmi+M8-HPIKRminURx-Yap_~N3WFZ9{5JGRIV1)Kd4M&?5KD?g-D zPcU}zLsy6&Ch{J;Q|t%FN+sMq? zO}*f{7_syiCx4-)pe2UBBR7FvxE-^K`oVE-!|9gL^aWR1=a zf$q5w*qrB`#v3lfCj^JsA8=uIsfoFzeg{IiM-vMBuux2H4&^_q+(O(E1`GD(+OKj4 zk=~=KB7H~YFx2vib;%)h%qC5E6-i%!oQ7|aWN1Vpj6S1*1b1Cz_&dL9 z6ty^RBHBk`q)QY;{@hD!jKYgP{2hNEfA?32rq&(}caLZ!FN{I(!5I8G5`$kKV%V#S zL0&X7Nl7s{Jt3C81~M0Z#-i0AmTccx?!v|4$0PDYzr{gOg*`d*ICwh7aX&Z?j;+im z4T^^=y+nbzqYm=m?H$>TV-GGbJsvUh63}lodvCWB5T=v>H!`%E>=SU!m-iQaM1$l6 zSY;&O-IPRl^9BpskqC{miP(1~5z$Wk>+(c&_DzDxf+Tp6zmQargemk1XE1MMHY^!8 zE+yl|vt(SOS2+HAGE~SB{oI;Nu4@X;j9@QrTMC*KQlLw=R*Y&2TC7tr-HBgIQlL_n zg8EwSB=$?ihVk4)oRkXJC1fxhO+|klb`#9Fn;4x6t@KpH&?_wD4VF7H4Vp{R*o{ns zk5oDa^h`(6$aJWUXLn&PxeJZy^c2a`x{?9^2O02vm4WKQ3|uYBK-~CDC{4-4w|kk~ zoymkva3);BGSMBI$!sLGg7H~UlF!1yo!m=2nT3X{^#88&=3*9U$Db@@hGfCHD+~9@ zPYB@KuqT^))Y&jRoQ>{B*>HbBEx|k+9trF+BxjR!wY(ULyzR)=4ED- z-sjKrm9_%N~r4x>wOZC(lLcX2QAYYArCl%OiM1pEG!BA>otej;zPA!U#nT?XGp zWQ@`~{J4fah+Sof7LgtATZY(xGROp$K_aM(nawg>P3Ds+N6m?HtR!b^l3_UpWR)YY zo;TW@3V4yjV5d+4*9#S}Wlut^T7i163N-tYy_H;nk9_k+RO0rGN=##K;_E8jXluB) zsKCC&sY;xDT8SI4_@8-HB8^)%l>wD7>QDC8*eWQ`t|D`x3JZg(5FK8H=7K6@kFSQ* zvuZq7tj1X8r$l+x!0;NBkEubnT@AWDYTzGIgJZpGxzkpQ9C0m-Giu>lSPP#cbvS>n z4ok1sVb8}p%>Pn{AZj^L)NwX=)yKd8WNuaA#Bt^7pl1 zwqpy7NB@KTjDK*t@DHH_T5-_06_E~P$hWnkw{si&uWcB?&cbM&c6Q|2@nKd6y(T_u z_7m22!qAP~gy>Ebt?Gi>mM)yR-GyZjy5Rn(i<^C2)OfovDYgqCgSw%1rW^6C-5ADB zq3LICBG#}|z-*QT@3i&gI2fC88z)Uta9h?xkY{(Whqjd9_(oa?wvrase3cPaN%Rs< z_2Qe-OHdH{3TKu33E$%S2?N(Wf{M}$Ogj7)6Q8`p;)HKd7^8?YgOuTKse&07HSo3H zZ@kd^jng*t*J^&F`<@moUu!}1T^kN$TK$tTK)b#HM(v`8P=G%&KfFDt)V&G2BH!h zXe_g3cheRj_O|F4WJgbs+sz{8%1_(l-6ea}1lpsa%pRht4rGEm&} zGvi)^nGv7mh#iWKFmmL!vyUT`2Rh-w1}ARmIFsG$j7yiDA$QFgKe){|exD2ad%9pw zj0<%0T~OTNg2>*k_&MDbMP05qxyB6vJKeCXn!7h!+#$V>H_%&m%vtC`mMOWR4?Qqh z&x2ZwC)$2^qGu3qo@h_>jiUzx5h8Yp@c5JnQS@JWtBSB$Lxe;-5oY>|@I@>l+fRh1 zbP?L=$IPN8wU;;1rxD($oaIfQ(;Eq2*>U{AO`D(IxKrefOSRsRpWuUpne03+VE%iX z51w50!9Dsf=4L+Fy2_V#3HelHQ(aK=!*_Sr@ zVrDP;E(`o%zQzwe>-?~Ks~>Eh_#wrb%z`LCC{>U{THudQBk83aqE>ZW3=exT^LS!p zO9h~HVgOFP3&1{W>Q{E;h$aM}H}9T7WL2Ge6o>_64D4)U&T4xQ3@!yB?P?JG6oW8L zDG1{-f{@%CglFUl3|bV7hXONJM}l#uM+h7Sh2X@b5Y$fz!F_rs`{|opF%Ln0cnF-Q zkQ2Hr6c28PLYmxwHAURT8ykl0^h9=_rvGuBdX;q;`H$p<(g%6c$?SHYaOy_k__8(} zC%16V=3F?WUW7w}euy)lG5eHha%6^%i9pVd2zV()AXJlgP-X;Fdz~!9rQ;oM&a1wD5&Xk=gx~6tKukZ>lKau)46FwoeGP&b%SUqoFGTw zYcwX9FsnU3hD^>F?kB}yc}NV@Gh<*fDi+4_v6xQ%Y2K??98qGI(m59Gp|N<@Ld{7w z4pTC`g<`a`Kaz+Y*sW<)kl!DpxI5st;&>!V(BXAG;bSnPbPUTiNIRKwh5&I_#Z=#kANK%7J>`i@%8q|t8nOH_P zfSe6`olcp!5SodB9hnFooQ0jtM+NT6LIQ7~bfYXx7c(ank%fuOXV0vsr_#i)6SMJk zI=NE+WkcacHgtIp-FlXdtzOv(<2|%LFB>Iw**L#42fpueaB4C)?Kb5?WoIr%pJmpG zw~uHL?;P?%e~h5Mw4C4H%tJZv9VM+i2xP0rd*vZE|Noxm-F$5PoR8W1`G_?mE7XFz zQ&B!n_AWp%pUTw&@=)l%3@wD^S@P3W3eiu!5Q{_D*UT!!e&(=mbNj}W&thj0?zk1< zX;$UkGs$03w#eQ=Rf!42=u8yJ8zaaveZYdtU$mf-Yn-TpsHDc9gY=9iR90tDzKAU zQU`C9-AkE;+FJ=%-YTC?Rzf&e2}^hGL`zrUSVk3dDAic;iClG~YIM6)!-LGI_sV2_ z8rI;saSbX4)FOOjEe4FK#RK*^txj_1Mx_=2suhpK zt@sn!3frz$Y#Y#qx}9zKtlWmK$Tr-TX~*0-?a*J@j)~jb(O}e$d1E?w?~)O^wF6VRU;A|8-qJ2q?C65?*Dh>L?!wosE<6~~ zjYWaoco)@;ODWyBFhW8Qrbr0G4@(HYeo6?c<`RNpqy$;%5|s8NgywGke5a&vk{PRU z5t2gB7)jwfHLI{uJp>gp+pF4p2!Dr42`5KN2{Fs0ghPj<;c6&*lXB96{a`Y# zwnz(hs?4aHNDF8Eq=kdqWCY!tGQ!Yz+{tm55yW{if@x|`;bTotVXBzS(LKF|^W1yh zHCk3EwUHH8Imrs93z)&O>nC)!_Y(>d`wP#u$O&iF2MSk`1`6S04eqr(U%o*1J!))<-02u(O=f-C1u_~(@=Y=4;{^^`eA*|F>AZH`xY=2-Z` z0;}uU_Zx1Br3#i9NuTgRh$TGatZ+cELfS4Xc>S@$DC$!k2G-a%!v@=8ZOFy4McpY| zr2b-VhdViqyurMExUoFR4wqKgVb&RHRUho|KHCmG=i6hCo;{MS?6Eu49ydGeVffVn z8=13lJLZgE|B(gNNbgUF{X0_^bf>tWD#HcKkGf*<9aku8xgs!#nVE1`xW95kk&PR+ zy1QXyq8n~^xncHm?il}Y$EYB8*hRX-p}-woBRtT2h`c9n@}5dPFo>D;{Lh{kqUuQ= zId=o2JaKmdZ>eb_NIYQoE{!Zuz8Tpf+-zny?>akp65OvD%sc9YH&(y)MgTJ{s&3v` zm*$PF8Q%C)>Wzz)-neD%1ASX&T*7!;P4(qQGRMr}qg8Y+X~+8vD1y+aU6ZE4ib5bgkk zV2^VMwxx#PQ8sfb73{?=45cm@3XvumO}e4X(1fDKHWZf~Lt*0;iYFDJczP@hpP6aj zWfq2uHeslV4TCiE?N9H8W0@;`!jN!8F;6n-2AN9_BT)4z0`6uJcpSppiZ|7koCtK6 zl2Oqdf#!*k>@Y?mVg)&$%&%uWBLDMeB&Pg|geGsSitY6Nc15A<3Aq$sq9Fbrh3E7I z{ZpdId5*%)8sTM5Ie3!Mj%y9?3GR&fc4N zHF=-RtS=$^;y-$TIxb1jDold*cxq2`==-fqhPpyB+D|6K;ZZU-Qj&4cHyKG}Gc8)3 zg25ZOJIEJ&E(QIrr;taNf`cY0xL}onrC}*}T%JO12DfbXq~i0TRQ$b|3Lk@1*xROJ zst9~H9T&DZntf3ZVW|V=MB4$+*$azoYmaqgF?u(dLQOiWspG@2rlj~lai5lir zR5oQ{E$^<&yR%^RISXMXS=bPeh1&&Lkf_Z90dKg&o9pDFY#d+5uj{kvr)6XM?rfAt zWuqb|8{ez4kuWs}nR9bsxjY9cmvXTDX$~q&xN}pUgKo)Oh)3r_b9F8vH!_<}rpCJS z+#041mG&bSU8>x?vB|}r$Xt9)$c1lKF0`AOUGJi9H8T$yi=8zmfU$BOeKT*}C~SZKv0fp%mSooW_U2p|HZI}?dTZaf%bc45SVZkXThhOKHhR#|pqLQ*%3yScNxO+whY zPeL%@J4t>;#Wx9IE%$KjJehA#k`O+oaksFyq%ftAq!3Jo#lV@8LhU?Bp^2RLyrq)D z6|&yvDoG0Keo6|vizJ0tJ$nej2f3j$M@lF#mJ(9uNDF%ZNehbZ(t>NFwBR>VM#x$& zBb@mwBXo@KEj;_sTX69v6GB2(I5taGh}tVF^f@LgjHWj@WPTr^NB_RUJi~s1l~F&T zaIc&YbZvmJ_NjnzrN_wOz6s^tkMVl;GqQo-LTBkals|nBEz|cfIPwW8`eZgpf5zWA zU+_x$JJRkbL4Vax1a16@@&iAi(UY0~y;&`UpVP+MJKAv8B;!3x2R9pa&<$NQ9?-=>7hNpQ<@X(W zP&=iM0v&za9d3YmQw&gj(*S#J8Nlb80p{Bn;Af!$dTuu4!n78v9GNE6hLA^ZKl z32r?w!2$~tXoZmnpKAg=X22&Hn?l*b6uN1qSUBGdmT$~(wv;Ffer>YE#>rM#bkGXFoUJgg(h7>Q)<_)2J8+CO zq;#!OQDhC(IX39K#|B>KZ1M6Iv)rGk%Z1wFG&Q+m`WJk809l8?jpp-)2k~g7o zA=&Xm?UBC39>b5=<1P8|8_exdVrh@Pyc3%}?C~kf9`DCFVBth^AozyOaA2p-0ikOh zU>WTI$2~0VJZFq0`{9(iGh#xV@j}uCt^Jta9^isQGhC2;zy*GnT=3+J3v%^ckS^_t$K%`$|bfFLCF7|=NR_cIneUL+5#0*Oxe2OHaw8#hdOMP&Dk}oRBM(cCY z7YcWLG2pc?#@hSBm)YzBveI@h@k8xC?lj)ZYMHd6`cmr?UU4iV92Vy?E zn>WcU4R;SjN>Ct5B!akQz?+x3xXWZjOfU+ZhceXQw5m-b|wh#T*IU($lk%LeDF?0vHrqm$o z?}WgE8<3}|L&nC0;8JA>w4_3DUoI4?@}c;?A{2=aLNS21b5Ffc$m^4tZyAazQ7D=N zLh&Y+T#1xW45MbblX*KQ>Xs(^!f@w67}gyL!E^NF*K~ibTi@=JS3;Vm)+o-;g&qZ|y>HH2Dkv%WcOXd1wrb=Eh)@ zQ4BhR$Um))L2g3~q6WvJ5C31;`;lCUud&=qh{eio=I^L&#>|g{{cUErpTuDxb9H_W zahTypkC*Sr)Oak~8xOr3WXRoT&W`?WAbAo^9n?C>k@Mu{_qO#3klviY&RGH`?cuIB zZ|<(M^mT0#_}@JN+o^4)QrAouC!!~JB3n|Jv1_5`*_wy}Lz2+4BnkJnC1JN|6845A z@y{ggM^e{Ro12X1)HV$sBtzjV_r4RErK7GnV(0^m7%t?@ivRU3Cgp zF-!OA5i{IssZjJwMQa1O68-7du1-VN(KMVVN8jo#canakLDnD*9-(RINe{QXZ#tC7 zk%(GFFLy^enm(nY_t$jxGSYF_lB|4(bd*GsHxZkTBkA0VEaH|@aXOAu>vU;j4|E`J z>Oo|jF3P}%O&Qp_H3Q;Z8F>3E1CcuPf|qj(Nih@qA~Nx!CiDL@>(qH)8(pT3NljB^ zodv0+EX=RTf_MS>5}UIzkQ(O4@66I^XJd_1Hga#};LOV$G*{-}dQA?__a{?gQ7%67 zX{@1_yE&IWcrKiHH;1U?qOO9vCcB#lTG`>`R^-)3d6+zhS-7?N*nTV@8{G5pBbpqE zynMVW&BuHl;Z3`_ z7;=L9GKJy%U_KM?@0#4izX;$b8}Iz8Lv0B;9zd*&*j+qIKIyj(Bv6l2%;~_(6PQX3DJ>Y(9_F7}w{ zvLmPqxhMJv*3yUEZ3B457$828`!^eE%v%UjP_&A_5(E<5s4tUe#fbXx^4Ks8^1bbkK zqn+_Y)fqBY&Ul^WjFo*{s7JZroIJOfXS(7hwW-!gZg_ai4Tpcb;gJlpPmkTvKZ+Vu zt~<_*@xZ3H9(bSVf%*auJlo_6^$VUbx$KEUQ^+Z$&)0fg1oWJnFyB- zg~RJ*IO5*WuTu(#yof)ersNmH?{m2MM{Q~I5^f{kBLCnQH)Ql8&?AZ5Q`ty}m`%$1 z5{c8gk<@7-5$73+dCZsZGLC{_coZrNqi~A;+#m9irjLn6(fVjS|HFM4YDM$ONKz>z z|A0Ai^}%EtjEzCl@fdQFV=z`N29nEoNgR%a-qBdtTw(_N3HwK1Vo}7;FeRs0=pdVHhKu=Gc9y** zeufPmkby}S8IbqO!1M^_!$)Oei)kiKn~_yukqN`}OpKDs!nbuly zqAWZq$ihuY>OFn4@o9B7(*3hxN4;lzeKuB0=kPYjf%cpnXe=N{^(>j)cXH79BL`|_ z}n^1+7i>lzdm|xSYs6AK1 zbzL>s7)Ru_YG~i8#>4y7u+pkVzEw3|)>K1rIk%11)u3oAcU%!^^S{#h1g<)zf><8DeJ4THsrVcCnkrOUgkKD=i zP@B#?_kwzSILhyB$tGx}_H%;F@N*5&{@8%6O58nmY{1&s1~lX~V0v)_mew~wkm2^r zxJE?GYs8lwjTrHu5w(hqSXkGHk&~KWypH=XVw*uHwwrY# zTcQhtW_D3i?ZTA6F05JKjf+>hQG2%=@{hZj=jnzawWJ5VC4}>nBm|$8)Q-qS^>`^E z$XZJXPT}OBXG;k8b0ma7YDr^eNeVe+C5+xfR>DI`VLNrB*LCEfa+gM(US2ynsHZRX z5R|X=5Qe^B{=B${Ahn*Hgcnl6lpj*UVk0Txfti%B)Rycik(7{5=IN-B+!93_D-+atGB&#k9$sb?=i1MTbMmhR)|hk*v z;^2P5_9Oj;!yDybT*;{mUbT>_?Aq z@7R0t06#!&=0|AeF@Ncym z_w{i3s~$E_)u#@qk5B^xh%F7VImr+vEr$3hV~o1t#^@YrjJFevv2>O(Ca4+nzBFbh z$CR5prsym;#oKOEC=N2igEeM2dBhBd^~_=Zke$R&7FZX>J1&l2J1wBkm(O;>_jk`91WND>YL8Zuww>@>I1J?NSpAF{w+h9PB4YR|xXn0`Dt~bAW+F~4Uyn;D) z_`2K#BtBsr4}kVuATvoo$O zcfr(0E?C2NkZ(+m3q-sXZDzQl|2|j99(KjvE6mV)yW*6uD@va*D;Dd9zPuZAR=ZPQ zcc&NNj+XV@F5FM%)+G;Y-ROyb)P0`+5>Z1VE7VGaS5EBcM~U#0cVkO$FT6PJg&ybF z!@uVRW%luRDtmEH#0%{OUJ!bEW9CS2?Bq+|>WwG&*;&-}#zI?fY;<6c(a)QC8*;d0 z$i(kO-DebiOnDzX*yMxtM|@Dl+cF~Ahc^nd_4z(nG=%-dz3l4qUD)RfH5F#XB7G5^ z&hEaHA38?+;np@kNWJsJ7)3v9`Rj)!GDAID{cvI#d-~h_v70^pig*6_UGI-cW5u{I zUW^TsxmUA7j4$iyZ!$Ztt;FtQf*6-_#5gl90M{0Ck9to4)Tr@PJY#lVHvkFA+@LNF zz~DoHczTLVE}KBS?hItlAqexf2O&W_h}#E2xam#LGcpK6=y9q`1>+EP9@+81P_+w2 znK&2+qUm{N1>^VB5WJon!cJBQV))wlhHeT$9Q%;(?{ky-2U(ydAvl%7otj$a%Nj$F zHk%sF{7~evi|@W96hoGB+mNsMUMP;d3B?Y2qnXrke*WX%H$DvGdB?h)3`5J;Fih79 zLw|Pg3&{qxwrA$vi+hOlM$O0twb>VrJ&(fi=|eaf_+)&gajRx<1hx&QAIjX>s{;|Z zd?W(?|M8YR9RbxdMK*kvRI28MLqD`D;ePT!$Tf zZrODDL?TGcu4Ov2_~nu49zox9N)*zUMe+Y5cJ#k-)5bLl_3lx4;u!_)T=p#`qVZ*L zG$N?sl$b~3ihVR0^6XRQM`NTcb(_!Ba*WCSH;=(hX4MK4V-U$~{sGxo+@Ovlxi*%Y z8O*1>kHy>{v1qV}g+oIuKJf1SHY5%fm*a5bH}xFqI9l{fV}rPHlN5(j%&Mtfj>q}C z%&2L_W4L!b4)FHPYGfZ@hJE|B+_a{a6Gjc^(A@-#dy;?)Qi<3&A`$25kp`S2C-inA zWXSm1`z{ei*1UiDKKn4cA4}F(W+Gx|C*iR|5-Q*G7XFk3m(TQ1HImRHfjf%H?B(}P zChsX3fn$>KhrP)%dZ(bqle9A#nb)|Vc%Pb&VKS_YxR=O#S>Bbqin09rlai4`4|RzP zbN$}DlZ#Vu2&w3EE*0vJ$qRkT{Qmn?{;tWKz6F_I*1U}~Q?b4-74szMm-b7;JNY!6 zoWxCRg*5K5q+#jlH0*i6yc@k#ZL2g~W2WusjdXmtla7cd^ix&H7PaB#jdMD(;<%xm zmJVBH+6)(GVAt6U47#3y_?sEfeZsuINd|JmGSC*CfoCb)$j;&S%(o5ckqPtundn@Q ziF4O7QF%8L%IcXIrjv%09vOt(SiDL;DLG#bqCAZ9 z&qKtte5gOj$0%m|#q>{&#oRN9%*QW!rV8=}IQO^!USto{=oMgPL;=Ke3ZeGC5K&6p z!nWW(wnHHT50+K%v93ZzA~W`#Rd_mpyo0ONn18bx=bO28J-i0}C)VKf+#2ZZ zslnNUHK@5=gRdSnnAAqj*T!1>q(2&~Sqn+0TKq1qg+^H|>e_2D`AQuIysN_pi3S`P zOg`5z-o(_X{&yeuwP?T{YE$ZZjgZrC#E&6Os2kk`-wn*ocQ@h8+-8hBZa21QcjLzf za!JV{-F8P(n8O?Knp7C=_}Ms zyo;vbC+q~h!iv)OcsSw<`dNHKcLw*JFDv1;suKD;Dj|p2tT7jq(O;wtx5+>8_x4Y$ zyQG5Q{wkOvtI9kknN-f|=oz4n+S3}a)X^ZXP!r1vH1X#svs+HT$O`_AF>`+7%9Y

    >O=!8I9O>|db=1-`A1_3>PdokIW=B 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%0A-|5Xx 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

    @-%P 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 B$}0c> diff --git a/benchmarks/perf-tool/knn-perf-tool.py b/benchmarks/perf-tool/knn-perf-tool.py deleted file mode 100644 index 48eedc427..000000000 --- a/benchmarks/perf-tool/knn-perf-tool.py +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -"""Script for user to run the testing tool.""" - -import okpt.main - -okpt.main.main() diff --git a/benchmarks/perf-tool/okpt/__init__.py b/benchmarks/perf-tool/okpt/__init__.py deleted file mode 100644 index c3bffc54c..000000000 --- a/benchmarks/perf-tool/okpt/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - diff --git a/benchmarks/perf-tool/okpt/diff/diff.py b/benchmarks/perf-tool/okpt/diff/diff.py deleted file mode 100644 index 23f424ab9..000000000 --- a/benchmarks/perf-tool/okpt/diff/diff.py +++ /dev/null @@ -1,142 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Provides the Diff class.""" - -from enum import Enum -from typing import Any, Dict, Tuple - - -class InvalidTestResultsError(Exception): - """Exception raised when the test results are invalid. - - The results can be invalid if they have different fields, non-numeric - values, or if they don't follow the standard result format. - """ - def __init__(self, msg: str): - self.message = msg - super().__init__(self.message) - - -def _is_numeric(a) -> bool: - return isinstance(a, (int, float)) - - -class TestResultFields(str, Enum): - METADATA = 'metadata' - RESULTS = 'results' - TEST_PARAMETERS = 'test_parameters' - - -class TestResultNames(str, Enum): - BASE = 'base_result' - CHANGED = 'changed_result' - - -class Diff: - """Diff class for validating and diffing two test result files. - - Methods: - diff: Returns the diff between two test results. (changed - base) - """ - def __init__( - self, - base_result: Dict[str, - Any], - changed_result: Dict[str, - Any], - metadata: bool - ): - """Initializes test results and validate them.""" - self.base_result = base_result - self.changed_result = changed_result - self.metadata = metadata - - # make sure results have proper test result fields - is_valid, key, result = self._validate_keys() - if not is_valid: - raise InvalidTestResultsError( - f'{result} has a missing or invalid key `{key}`.' - ) - - self.base_results = self.base_result[TestResultFields.RESULTS] - self.changed_results = self.changed_result[TestResultFields.RESULTS] - - # make sure results have the same fields - is_valid, key, result = self._validate_structure() - if not is_valid: - raise InvalidTestResultsError( - f'key `{key}` is not present in {result}.' - ) - - # make sure results have numeric values - is_valid, key, result = self._validate_types() - if not is_valid: - raise InvalidTestResultsError( - f'key `{key}` in {result} points to a non-numeric value.' - ) - - def _validate_keys(self) -> Tuple[bool, str, str]: - """Ensure both test results have `metadata` and `results` keys.""" - check_keydict = lambda key, res: key in res and isinstance( - res[key], dict) - - # check if results have a `metadata` field and if `metadata` is a dict - if self.metadata: - if not check_keydict(TestResultFields.METADATA, self.base_result): - return (False, TestResultFields.METADATA, TestResultNames.BASE) - if not check_keydict(TestResultFields.METADATA, - self.changed_result): - return ( - False, - TestResultFields.METADATA, - TestResultNames.CHANGED - ) - # check if results have a `results` field and `results` is a dict - if not check_keydict(TestResultFields.RESULTS, self.base_result): - return (False, TestResultFields.RESULTS, TestResultNames.BASE) - if not check_keydict(TestResultFields.RESULTS, self.changed_result): - return (False, TestResultFields.RESULTS, TestResultNames.CHANGED) - return (True, '', '') - - def _validate_structure(self) -> Tuple[bool, str, str]: - """Ensure both test results have the same keys.""" - for k in self.base_results: - if not k in self.changed_results: - return (False, k, TestResultNames.CHANGED) - for k in self.changed_results: - if not k in self.base_results: - return (False, k, TestResultNames.BASE) - return (True, '', '') - - def _validate_types(self) -> Tuple[bool, str, str]: - """Ensure both test results have numeric values.""" - for k, v in self.base_results.items(): - if not _is_numeric(v): - return (False, k, TestResultNames.BASE) - for k, v in self.changed_results.items(): - if not _is_numeric(v): - return (False, k, TestResultNames.BASE) - return (True, '', '') - - def diff(self) -> Dict[str, Any]: - """Return the diff between the two test results. (changed - base)""" - results_diff = { - key: self.changed_results[key] - self.base_results[key] - for key in self.base_results - } - - # add metadata if specified - if self.metadata: - return { - f'{TestResultNames.BASE}_{TestResultFields.METADATA}': - self.base_result[TestResultFields.METADATA], - f'{TestResultNames.CHANGED}_{TestResultFields.METADATA}': - self.changed_result[TestResultFields.METADATA], - 'diff': - results_diff - } - return results_diff diff --git a/benchmarks/perf-tool/okpt/io/args.py b/benchmarks/perf-tool/okpt/io/args.py deleted file mode 100644 index f8c5d8809..000000000 --- a/benchmarks/perf-tool/okpt/io/args.py +++ /dev/null @@ -1,178 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Parses and defines command line arguments for the program. - -Defines the subcommands `test` and `diff` and the corresponding -files that are required by each command. - -Functions: - define_args(): Define the command line arguments. - get_args(): Returns a dictionary of the command line args. -""" - -import argparse -import sys -from dataclasses import dataclass -from io import TextIOWrapper -from typing import Union - -_read_type = argparse.FileType('r') -_write_type = argparse.FileType('w') - - -def _add_config(parser, name, **kwargs): - """"Add configuration file path argument.""" - opts = { - 'type': _read_type, - 'help': 'Path of configuration file.', - 'metavar': 'config_path', - **kwargs, - } - parser.add_argument(name, **opts) - - -def _add_result(parser, name, **kwargs): - """"Add results files paths argument.""" - opts = { - 'type': _read_type, - 'help': 'Path of one result file.', - 'metavar': 'result_path', - **kwargs, - } - parser.add_argument(name, **opts) - - -def _add_results(parser, name, **kwargs): - """"Add results files paths argument.""" - opts = { - 'nargs': '+', - 'type': _read_type, - 'help': 'Paths of result files.', - 'metavar': 'result_paths', - **kwargs, - } - parser.add_argument(name, **opts) - - -def _add_output(parser, name, **kwargs): - """"Add output file path argument.""" - opts = { - 'type': _write_type, - 'help': 'Path of output file.', - 'metavar': 'output_path', - **kwargs, - } - parser.add_argument(name, **opts) - - -def _add_metadata(parser, name, **kwargs): - opts = { - 'action': 'store_true', - **kwargs, - } - parser.add_argument(name, **opts) - - -def _add_test_cmd(subparsers): - test_parser = subparsers.add_parser('test') - _add_config(test_parser, 'config') - _add_output(test_parser, 'output') - - -def _add_diff_cmd(subparsers): - diff_parser = subparsers.add_parser('diff') - _add_metadata(diff_parser, '--metadata') - _add_result( - diff_parser, - 'base_result', - help='Base test result.', - metavar='base_result' - ) - _add_result( - diff_parser, - 'changed_result', - help='Changed test result.', - metavar='changed_result' - ) - _add_output(diff_parser, '--output', default=sys.stdout) - - -@dataclass -class TestArgs: - log: str - command: str - config: TextIOWrapper - output: TextIOWrapper - - -@dataclass -class DiffArgs: - log: str - command: str - metadata: bool - base_result: TextIOWrapper - changed_result: TextIOWrapper - output: TextIOWrapper - - -def get_args() -> Union[TestArgs, DiffArgs]: - """Define, parse and return command line args. - - Returns: - A dict containing the command line args. - """ - parser = argparse.ArgumentParser( - description= - 'Run performance tests against the OpenSearch plugin and various ANN ' - 'libaries.' - ) - - def define_args(): - """Define tool commands.""" - - # add log level arg - parser.add_argument( - '--log', - default='info', - type=str, - choices=['debug', - 'info', - 'warning', - 'error', - 'critical'], - help='Log level of the tool.' - ) - - subparsers = parser.add_subparsers( - title='commands', - dest='command', - help='sub-command help' - ) - subparsers.required = True - - # add subcommands - _add_test_cmd(subparsers) - _add_diff_cmd(subparsers) - - define_args() - args = parser.parse_args() - if args.command == 'test': - return TestArgs( - log=args.log, - command=args.command, - config=args.config, - output=args.output - ) - else: - return DiffArgs( - log=args.log, - command=args.command, - metadata=args.metadata, - base_result=args.base_result, - changed_result=args.changed_result, - output=args.output - ) diff --git a/benchmarks/perf-tool/okpt/io/config/parsers/base.py b/benchmarks/perf-tool/okpt/io/config/parsers/base.py deleted file mode 100644 index 795aab1b2..000000000 --- a/benchmarks/perf-tool/okpt/io/config/parsers/base.py +++ /dev/null @@ -1,67 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Base Parser class. - -Classes: - BaseParser: Base class for config parsers. - -Exceptions: - ConfigurationError: An error in the configuration syntax. -""" - -import os -from io import TextIOWrapper - -import cerberus - -from okpt.io.utils import reader - - -class ConfigurationError(Exception): - """Exception raised for errors in the tool configuration. - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message: str): - self.message = f'{message}' - super().__init__(self.message) - - -def _get_validator_from_schema_name(schema_name: str): - """Get the corresponding Cerberus validator from a schema name.""" - curr_file_dir = os.path.dirname(os.path.abspath(__file__)) - schemas_dir = os.path.join(os.path.dirname(curr_file_dir), 'schemas') - schema_file_path = os.path.join(schemas_dir, f'{schema_name}.yml') - schema_obj = reader.parse_yaml_from_path(schema_file_path) - return cerberus.Validator(schema_obj) - - -class BaseParser: - """Base class for config parsers. - - Attributes: - validator: Cerberus validator for a particular schema - errors: Cerberus validation errors (if any are found during validation) - - Methods: - parse: Parse config. - """ - - def __init__(self, schema_name: str): - self.validator = _get_validator_from_schema_name(schema_name) - self.errors = '' - - def parse(self, file_obj: TextIOWrapper): - """Convert file object to dict, while validating against config schema.""" - config_obj = reader.parse_yaml(file_obj) - is_config_valid = self.validator.validate(config_obj) - if not is_config_valid: - raise ConfigurationError(self.validator.errors) - - return self.validator.document diff --git a/benchmarks/perf-tool/okpt/io/config/parsers/test.py b/benchmarks/perf-tool/okpt/io/config/parsers/test.py deleted file mode 100644 index c47e30ecc..000000000 --- a/benchmarks/perf-tool/okpt/io/config/parsers/test.py +++ /dev/null @@ -1,81 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Provides ToolParser. - -Classes: - ToolParser: Tool config parser. -""" -from dataclasses import dataclass -from io import TextIOWrapper -from typing import List - -from okpt.io.config.parsers import base -from okpt.test.steps.base import Step, StepConfig -from okpt.test.steps.factory import create_step - - -@dataclass -class TestConfig: - test_name: str - test_id: str - endpoint: str - port: int - timeout: int - num_runs: int - show_runs: bool - setup: List[Step] - steps: List[Step] - cleanup: List[Step] - - -class TestParser(base.BaseParser): - """Parser for Test config. - - Methods: - parse: Parse and validate the Test config. - """ - - def __init__(self): - super().__init__('test') - - def parse(self, file_obj: TextIOWrapper) -> TestConfig: - """See base class.""" - config_obj = super().parse(file_obj) - - implicit_step_config = dict() - if 'endpoint' in config_obj: - implicit_step_config['endpoint'] = config_obj['endpoint'] - - if 'port' in config_obj: - implicit_step_config['port'] = config_obj['port'] - - # Each step should have its own parse - take the config object and check if its valid - setup = [] - if 'setup' in config_obj: - setup = [create_step(StepConfig(step["name"], step, implicit_step_config)) for step in config_obj['setup']] - - steps = [create_step(StepConfig(step["name"], step, implicit_step_config)) for step in config_obj['steps']] - - cleanup = [] - if 'cleanup' in config_obj: - cleanup = [create_step(StepConfig(step["name"], step, implicit_step_config)) for step - in config_obj['cleanup']] - - test_config = TestConfig( - endpoint=config_obj['endpoint'], - port=config_obj['port'], - timeout=config_obj['timeout'], - test_name=config_obj['test_name'], - test_id=config_obj['test_id'], - num_runs=config_obj['num_runs'], - show_runs=config_obj['show_runs'], - setup=setup, - steps=steps, - cleanup=cleanup - ) - - return test_config diff --git a/benchmarks/perf-tool/okpt/io/config/parsers/util.py b/benchmarks/perf-tool/okpt/io/config/parsers/util.py deleted file mode 100644 index 454fec5a0..000000000 --- a/benchmarks/perf-tool/okpt/io/config/parsers/util.py +++ /dev/null @@ -1,116 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Utility functions for parsing""" - - -from okpt.io.config.parsers.base import ConfigurationError -from okpt.io.dataset import HDF5DataSet, BigANNNeighborDataSet, \ - BigANNVectorDataSet, DataSet, Context - - -def parse_dataset(dataset_format: str, dataset_path: str, - context: Context, custom_context=None) -> DataSet: - if dataset_format == 'hdf5': - return HDF5DataSet(dataset_path, context, custom_context) - - if dataset_format == 'bigann' and context == Context.NEIGHBORS: - return BigANNNeighborDataSet(dataset_path) - - if dataset_format == 'bigann': - return BigANNVectorDataSet(dataset_path) - - raise Exception("Unsupported data-set format") - - -def parse_string_param(key: str, first_map, second_map, default) -> str: - value = first_map.get(key) - if value is not None: - if type(value) is str: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - value = second_map.get(key) - if value is not None: - if type(value) is str: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - if default is None: - raise ConfigurationError("{} must be set".format(key)) - return default - - -def parse_int_param(key: str, first_map, second_map, default) -> int: - value = first_map.get(key) - if value is not None: - if type(value) is int: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - value = second_map.get(key) - if value is not None: - if type(value) is int: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - if default is None: - raise ConfigurationError("{} must be set".format(key)) - return default - - -def parse_bool_param(key: str, first_map, second_map, default) -> bool: - value = first_map.get(key) - if value is not None: - if type(value) is bool: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - value = second_map.get(key) - if value is not None: - if type(value) is bool: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - if default is None: - raise ConfigurationError("{} must be set".format(key)) - return default - - -def parse_dict_param(key: str, first_map, second_map, default) -> dict: - value = first_map.get(key) - if value is not None: - if type(value) is dict: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - value = second_map.get(key) - if value is not None: - if type(value) is dict: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - if default is None: - raise ConfigurationError("{} must be set".format(key)) - return default - - -def parse_list_param(key: str, first_map, second_map, default) -> list: - value = first_map.get(key) - if value is not None: - if type(value) is list: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - value = second_map.get(key) - if value is not None: - if type(value) is list: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - if default is None: - raise ConfigurationError("{} must be set".format(key)) - return default diff --git a/benchmarks/perf-tool/okpt/io/config/schemas/test.yml b/benchmarks/perf-tool/okpt/io/config/schemas/test.yml deleted file mode 100644 index 4d5c21a15..000000000 --- a/benchmarks/perf-tool/okpt/io/config/schemas/test.yml +++ /dev/null @@ -1,35 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -# defined using the cerberus validation API -# https://docs.python-cerberus.org/en/stable/index.html -endpoint: - type: string - default: "localhost" -port: - type: integer - default: 9200 -timeout: - type: integer - default: 60 -test_name: - type: string -test_id: - type: string -num_runs: - type: integer - default: 1 - min: 1 - max: 10000 -show_runs: - type: boolean - default: false -setup: - type: list -steps: - type: list -cleanup: - type: list diff --git a/benchmarks/perf-tool/okpt/io/dataset.py b/benchmarks/perf-tool/okpt/io/dataset.py deleted file mode 100644 index 001563bab..000000000 --- a/benchmarks/perf-tool/okpt/io/dataset.py +++ /dev/null @@ -1,222 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Defines DataSet interface and implements particular formats - -A DataSet is the basic functionality that it can be read in chunks, or -read completely and reset to the start. - -Currently, we support HDF5 formats from ann-benchmarks and big-ann-benchmarks -datasets. - -Classes: - HDF5DataSet: Format used in ann-benchmarks - BigANNNeighborDataSet: Neighbor format for big-ann-benchmarks - BigANNVectorDataSet: Vector format for big-ann-benchmarks -""" -import os -from abc import ABC, ABCMeta, abstractmethod -from enum import Enum -from typing import cast -import h5py -import numpy as np - -import struct - - -class Context(Enum): - """DataSet context enum. Can be used to add additional context for how a - data-set should be interpreted. - """ - INDEX = 1 - QUERY = 2 - NEIGHBORS = 3 - CUSTOM = 4 - - -class DataSet(ABC): - """DataSet interface. Used for reading data-sets from files. - - Methods: - read: Read a chunk of data from the data-set - size: Gets the number of items in the data-set - reset: Resets internal state of data-set to beginning - """ - __metaclass__ = ABCMeta - - @abstractmethod - def read(self, chunk_size: int): - pass - - @abstractmethod - def size(self): - pass - - @abstractmethod - def reset(self): - pass - - -class HDF5DataSet(DataSet): - """ Data-set format corresponding to `ANN Benchmarks - `_ - """ - - def __init__(self, dataset_path: str, context: Context, custom_context=None): - file = h5py.File(dataset_path) - self.data = cast(h5py.Dataset, file[self._parse_context(context, custom_context)]) - self.current = 0 - - def read(self, chunk_size: int): - if self.current >= self.size(): - return None - - end_i = self.current + chunk_size - if end_i > self.size(): - end_i = self.size() - - v = cast(np.ndarray, self.data[self.current:end_i]) - self.current = end_i - return v - - def size(self): - return self.data.len() - - def reset(self): - self.current = 0 - - @staticmethod - def _parse_context(context: Context, custom_context=None) -> str: - if context == Context.NEIGHBORS: - return "neighbors" - - if context == Context.INDEX: - return "train" - - if context == Context.QUERY: - return "test" - - if context == Context.CUSTOM: - return custom_context - - raise Exception("Unsupported context") - - -class BigANNNeighborDataSet(DataSet): - """ Data-set format for neighbor data-sets for `Big ANN Benchmarks - `_""" - - def __init__(self, dataset_path: str): - self.file = open(dataset_path, 'rb') - self.file.seek(0, os.SEEK_END) - num_bytes = self.file.tell() - self.file.seek(0) - - if num_bytes < 8: - raise Exception("File is invalid") - - self.num_queries = int.from_bytes(self.file.read(4), "little") - self.k = int.from_bytes(self.file.read(4), "little") - - # According to the website, the number of bytes that will follow will - # be: num_queries X K x sizeof(uint32_t) bytes + num_queries X K x - # sizeof(float) - if (num_bytes - 8) != 2 * (self.num_queries * self.k * 4): - raise Exception("File is invalid") - - self.current = 0 - - def read(self, chunk_size: int): - if self.current >= self.size(): - return None - - end_i = self.current + chunk_size - if end_i > self.size(): - end_i = self.size() - - v = [[int.from_bytes(self.file.read(4), "little") for _ in - range(self.k)] for _ in range(end_i - self.current)] - - self.current = end_i - return v - - def size(self): - return self.num_queries - - def reset(self): - self.file.seek(8) - self.current = 0 - - -class BigANNVectorDataSet(DataSet): - """ Data-set format for vector data-sets for `Big ANN Benchmarks - `_ - """ - - def __init__(self, dataset_path: str): - self.file = open(dataset_path, 'rb') - self.file.seek(0, os.SEEK_END) - num_bytes = self.file.tell() - self.file.seek(0) - - if num_bytes < 8: - raise Exception("File is invalid") - - self.num_points = int.from_bytes(self.file.read(4), "little") - self.dimension = int.from_bytes(self.file.read(4), "little") - bytes_per_num = self._get_data_size(dataset_path) - - if (num_bytes - 8) != self.num_points * self.dimension * bytes_per_num: - raise Exception("File is invalid") - - self.reader = self._value_reader(dataset_path) - self.current = 0 - - def read(self, chunk_size: int): - if self.current >= self.size(): - return None - - end_i = self.current + chunk_size - if end_i > self.size(): - end_i = self.size() - - v = np.asarray([self._read_vector() for _ in - range(end_i - self.current)]) - self.current = end_i - return v - - def _read_vector(self): - return np.asarray([self.reader(self.file) for _ in - range(self.dimension)]) - - def size(self): - return self.num_points - - def reset(self): - self.file.seek(8) # Seek to 8 bytes to skip re-reading metadata - self.current = 0 - - @staticmethod - def _get_data_size(file_name): - ext = file_name.split('.')[-1] - if ext == "u8bin": - return 1 - - if ext == "fbin": - return 4 - - raise Exception("Unknown extension") - - @staticmethod - def _value_reader(file_name): - ext = file_name.split('.')[-1] - if ext == "u8bin": - return lambda file: float(int.from_bytes(file.read(1), "little")) - - if ext == "fbin": - return lambda file: struct.unpack(' TextIOWrapper: - """Given a file path, get a readable file object. - - Args: - file path - - Returns: - Writeable file object - """ - return open(path, 'r', encoding='UTF-8') - - -def parse_yaml(file: TextIOWrapper) -> Dict[str, Any]: - """Parses YAML file from file object. - - Args: - file: file object to parse - - Returns: - A dict representing the YAML file. - """ - return yaml.load(file, Loader=yaml.SafeLoader) - - -def parse_yaml_from_path(path: str) -> Dict[str, Any]: - """Parses YAML file from file path. - - Args: - path: file path to parse - - Returns: - A dict representing the YAML file. - """ - file = reader.get_file_obj(path) - return parse_yaml(file) - - -def parse_json(file: TextIOWrapper) -> Dict[str, Any]: - """Parses JSON file from file object. - - Args: - file: file object to parse - - Returns: - A dict representing the JSON file. - """ - return json.load(file) - - -def parse_json_from_path(path: str) -> Dict[str, Any]: - """Parses JSON file from file path. - - Args: - path: file path to parse - - Returns: - A dict representing the JSON file. - """ - file = reader.get_file_obj(path) - return json.load(file) diff --git a/benchmarks/perf-tool/okpt/io/utils/writer.py b/benchmarks/perf-tool/okpt/io/utils/writer.py deleted file mode 100644 index 1f14bfd94..000000000 --- a/benchmarks/perf-tool/okpt/io/utils/writer.py +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -"""Provides functions for writing to file. - -Functions: - get_file_obj(): Get a writeable file object. - write_json(): Writes a python dictionary to a JSON file -""" - -import json -from io import TextIOWrapper -from typing import Any, Dict, TextIO, Union - - -def get_file_obj(path: str) -> TextIOWrapper: - """Get a writeable file object from a file path. - - Args: - file path - - Returns: - Writeable file object - """ - return open(path, 'w', encoding='UTF-8') - - -def write_json(data: Dict[str, Any], - file: Union[TextIOWrapper, TextIO], - pretty=False): - """Writes a dictionary to a JSON file. - - Args: - data: A dict to write to JSON. - file: Path of output file. - """ - indent = 2 if pretty else 0 - json.dump(data, file, indent=indent) diff --git a/benchmarks/perf-tool/okpt/main.py b/benchmarks/perf-tool/okpt/main.py deleted file mode 100644 index 3e6e022d4..000000000 --- a/benchmarks/perf-tool/okpt/main.py +++ /dev/null @@ -1,55 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -""" Runner script that serves as the main controller of the testing tool.""" - -import logging -import sys -from typing import cast - -from okpt.diff import diff -from okpt.io import args -from okpt.io.config.parsers import test -from okpt.io.utils import reader, writer -from okpt.test import runner - - -def main(): - """Main function of entry module.""" - cli_args = args.get_args() - output = cli_args.output - if cli_args.log: - log_level = getattr(logging, cli_args.log.upper()) - logging.basicConfig(level=log_level) - - if cli_args.command == 'test': - cli_args = cast(args.TestArgs, cli_args) - - # parse config - parser = test.TestParser() - test_config = parser.parse(cli_args.config) - logging.info('Configs are valid.') - - # run tests - test_runner = runner.TestRunner(test_config=test_config) - test_result = test_runner.execute() - - # write test results - logging.debug( - f'Test Result:\n {writer.write_json(test_result, sys.stdout, pretty=True)}' - ) - writer.write_json(test_result, output, pretty=True) - elif cli_args.command == 'diff': - cli_args = cast(args.DiffArgs, cli_args) - - # parse test results - base_result = reader.parse_json(cli_args.base_result) - changed_result = reader.parse_json(cli_args.changed_result) - - # get diff - diff_result = diff.Diff(base_result, changed_result, - cli_args.metadata).diff() - writer.write_json(data=diff_result, file=output, pretty=True) diff --git a/benchmarks/perf-tool/okpt/test/__init__.py b/benchmarks/perf-tool/okpt/test/__init__.py deleted file mode 100644 index ff4fd04d1..000000000 --- a/benchmarks/perf-tool/okpt/test/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. diff --git a/benchmarks/perf-tool/okpt/test/profile.py b/benchmarks/perf-tool/okpt/test/profile.py deleted file mode 100644 index d96860f9a..000000000 --- a/benchmarks/perf-tool/okpt/test/profile.py +++ /dev/null @@ -1,86 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Provides decorators to profile functions. - -The decorators work by adding a `measureable` (time, memory, etc) field to a -dictionary returned by the wrapped function. So the wrapped functions must -return a dictionary in order to be profiled. -""" -import functools -import time -from typing import Callable - - -class TimerStoppedWithoutStartingError(Exception): - """Error raised when Timer is stopped without having been started.""" - - def __init__(self): - super().__init__() - self.message = 'Timer must call start() before calling end().' - - -class _Timer(): - """Timer class for timing. - - Methods: - start: Starts the timer. - end: Stops the timer and returns the time elapsed since start. - - Raises: - TimerStoppedWithoutStartingError: Timer must start before ending. - """ - - def __init__(self): - self.start_time = None - - def start(self): - """Starts the timer.""" - self.start_time = time.perf_counter() - - def end(self) -> float: - """Stops the timer. - - Returns: - The time elapsed in milliseconds. - """ - # ensure timer has started before ending - if self.start_time is None: - raise TimerStoppedWithoutStartingError() - - elapsed = (time.perf_counter() - self.start_time) * 1000 - self.start_time = None - return elapsed - - -def took(f: Callable): - """Profiles a functions execution time. - - Args: - f: Function to profile. - - Returns: - A function that wraps the passed in function and adds a time took field - to the return value. - """ - - @functools.wraps(f) - def wrapper(*args, **kwargs): - """Wrapper function.""" - timer = _Timer() - timer.start() - result = f(*args, **kwargs) - time_took = timer.end() - - # if result already has a `took` field, don't modify the result - if isinstance(result, dict) and 'took' in result: - return result - # `result` may not be a dictionary, so it may not be unpackable - elif isinstance(result, dict): - return {**result, 'took': time_took} - return {'took': time_took} - - return wrapper diff --git a/benchmarks/perf-tool/okpt/test/runner.py b/benchmarks/perf-tool/okpt/test/runner.py deleted file mode 100644 index 150154691..000000000 --- a/benchmarks/perf-tool/okpt/test/runner.py +++ /dev/null @@ -1,107 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Provides a test runner class.""" -import logging -import platform -import sys -from datetime import datetime -from typing import Any, Dict, List - -import psutil - -from okpt.io.config.parsers import test -from okpt.test.test import Test, get_avg - - -def _aggregate_runs(runs: List[Dict[str, Any]]): - """Aggregates and averages a list of test results. - - Args: - results: A list of test results. - num_runs: Number of times the tests were ran. - - Returns: - A dictionary containing the averages of the test results. - """ - aggregate: Dict[str, Any] = {} - for run in runs: - for key, value in run.items(): - if key in aggregate: - aggregate[key].append(value) - else: - aggregate[key] = [value] - - aggregate = {key: get_avg(value) for key, value in aggregate.items()} - return aggregate - - -class TestRunner: - """Test runner class for running tests and aggregating the results. - - Methods: - execute: Run the tests and aggregate the results. - """ - - def __init__(self, test_config: test.TestConfig): - """"Initializes test state.""" - self.test_config = test_config - self.test = Test(test_config) - - def _get_metadata(self): - """"Retrieves the test metadata.""" - svmem = psutil.virtual_memory() - return { - 'test_name': - self.test_config.test_name, - 'test_id': - self.test_config.test_id, - 'date': - datetime.now().strftime('%m/%d/%Y %H:%M:%S'), - 'python_version': - sys.version, - 'os_version': - platform.platform(), - 'processor': - platform.processor() + ', ' + - str(psutil.cpu_count(logical=True)) + ' cores', - 'memory': - str(svmem.used) + ' (used) / ' + str(svmem.available) + - ' (available) / ' + str(svmem.total) + ' (total)', - } - - def execute(self) -> Dict[str, Any]: - """Runs the tests and aggregates the results. - - Returns: - A dictionary containing the aggregate of test results. - """ - logging.info('Setting up tests.') - self.test.setup() - logging.info('Beginning to run tests.') - runs = [] - for i in range(self.test_config.num_runs): - logging.info( - f'Running test {i + 1} of {self.test_config.num_runs}' - ) - runs.append(self.test.execute()) - - logging.info('Finished running tests.') - aggregate = _aggregate_runs(runs) - - # add metadata to test results - test_result = { - 'metadata': - self._get_metadata(), - 'results': - aggregate - } - - # include info about all test runs if specified in config - if self.test_config.show_runs: - test_result['runs'] = runs - - return test_result diff --git a/benchmarks/perf-tool/okpt/test/steps/base.py b/benchmarks/perf-tool/okpt/test/steps/base.py deleted file mode 100644 index 829980421..000000000 --- a/benchmarks/perf-tool/okpt/test/steps/base.py +++ /dev/null @@ -1,60 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -"""Provides base Step interface.""" - -from dataclasses import dataclass -from typing import Any, Dict, List - -from okpt.test import profile - - -@dataclass -class StepConfig: - step_name: str - config: Dict[str, object] - implicit_config: Dict[str, object] - - -class Step: - """Test step interface. - - Attributes: - label: Name of the step. - - Methods: - execute: Run the step and return a step response with the label and - corresponding measures. - """ - - label = 'base_step' - - def __init__(self, step_config: StepConfig): - self.step_config = step_config - - def _action(self): - """Step logic/behavior to be executed and profiled.""" - pass - - def _get_measures(self) -> List[str]: - """Gets the measures for a particular test""" - pass - - def execute(self) -> List[Dict[str, Any]]: - """Execute step logic while profiling various measures. - - Returns: - Dict containing step label and various step measures. - """ - action = self._action - - # profile the action with measure decorators - add if necessary - action = getattr(profile, 'took')(action) - - result = action() - if isinstance(result, dict): - return [{'label': self.label, **result}] - - raise ValueError('Invalid return by a step') diff --git a/benchmarks/perf-tool/okpt/test/steps/factory.py b/benchmarks/perf-tool/okpt/test/steps/factory.py deleted file mode 100644 index 2033f2672..000000000 --- a/benchmarks/perf-tool/okpt/test/steps/factory.py +++ /dev/null @@ -1,50 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -"""Factory for creating steps.""" - -from okpt.io.config.parsers.base import ConfigurationError -from okpt.test.steps.base import Step, StepConfig - -from okpt.test.steps.steps import CreateIndexStep, DisableRefreshStep, RefreshIndexStep, DeleteIndexStep, \ - TrainModelStep, DeleteModelStep, ForceMergeStep, ClearCacheStep, IngestStep, IngestMultiFieldStep, \ - IngestNestedFieldStep, QueryStep, QueryWithFilterStep, QueryNestedFieldStep, GetStatsStep, WarmupStep - - -def create_step(step_config: StepConfig) -> Step: - if step_config.step_name == CreateIndexStep.label: - return CreateIndexStep(step_config) - elif step_config.step_name == DisableRefreshStep.label: - return DisableRefreshStep(step_config) - elif step_config.step_name == RefreshIndexStep.label: - return RefreshIndexStep(step_config) - elif step_config.step_name == TrainModelStep.label: - return TrainModelStep(step_config) - elif step_config.step_name == DeleteModelStep.label: - return DeleteModelStep(step_config) - elif step_config.step_name == DeleteIndexStep.label: - return DeleteIndexStep(step_config) - elif step_config.step_name == IngestStep.label: - return IngestStep(step_config) - elif step_config.step_name == IngestMultiFieldStep.label: - return IngestMultiFieldStep(step_config) - elif step_config.step_name == IngestNestedFieldStep.label: - return IngestNestedFieldStep(step_config) - elif step_config.step_name == QueryStep.label: - return QueryStep(step_config) - elif step_config.step_name == QueryWithFilterStep.label: - return QueryWithFilterStep(step_config) - elif step_config.step_name == QueryNestedFieldStep.label: - return QueryNestedFieldStep(step_config) - elif step_config.step_name == ForceMergeStep.label: - return ForceMergeStep(step_config) - elif step_config.step_name == ClearCacheStep.label: - return ClearCacheStep(step_config) - elif step_config.step_name == GetStatsStep.label: - return GetStatsStep(step_config) - elif step_config.step_name == WarmupStep.label: - return WarmupStep(step_config) - - raise ConfigurationError(f'Invalid step {step_config.step_name}') diff --git a/benchmarks/perf-tool/okpt/test/steps/steps.py b/benchmarks/perf-tool/okpt/test/steps/steps.py deleted file mode 100644 index 99b2728dc..000000000 --- a/benchmarks/perf-tool/okpt/test/steps/steps.py +++ /dev/null @@ -1,987 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -"""Provides steps for OpenSearch tests. - -Some OpenSearch operations return a `took` field in the response body, -so the profiling decorators aren't needed for some functions. -""" -import json -from abc import abstractmethod -from typing import Any, Dict, List - -import numpy as np -import requests -import time - -from opensearchpy import OpenSearch, RequestsHttpConnection - -from okpt.io.config.parsers.base import ConfigurationError -from okpt.io.config.parsers.util import parse_string_param, parse_int_param, parse_dataset, parse_bool_param, \ - parse_list_param -from okpt.io.dataset import Context -from okpt.io.utils.reader import parse_json_from_path -from okpt.test.steps import base -from okpt.test.steps.base import StepConfig - - -class OpenSearchStep(base.Step): - """See base class.""" - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.endpoint = parse_string_param('endpoint', step_config.config, - step_config.implicit_config, - 'localhost') - default_port = 9200 if self.endpoint == 'localhost' else 80 - self.port = parse_int_param('port', step_config.config, - step_config.implicit_config, default_port) - self.timeout = parse_int_param('timeout', step_config.config, {}, 60) - self.opensearch = get_opensearch_client(str(self.endpoint), - int(self.port), int(self.timeout)) - - -class CreateIndexStep(OpenSearchStep): - """See base class.""" - - label = 'create_index' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - index_spec = parse_string_param('index_spec', step_config.config, {}, - None) - self.body = parse_json_from_path(index_spec) - if self.body is None: - raise ConfigurationError('Index body must be passed in') - - def _action(self): - """Creates an OpenSearch index, applying the index settings/mappings. - - Returns: - An OpenSearch index creation response body. - """ - self.opensearch.indices.create(index=self.index_name, body=self.body) - return {} - - def _get_measures(self) -> List[str]: - return ['took'] - - -class DisableRefreshStep(OpenSearchStep): - """See base class.""" - - label = 'disable_refresh' - - def _action(self): - """Disables the refresh interval for an OpenSearch index. - - Returns: - An OpenSearch index settings update response body. - """ - self.opensearch.indices.put_settings( - body={'index': { - 'refresh_interval': -1 - }}) - - return {} - - def _get_measures(self) -> List[str]: - return ['took'] - - -class RefreshIndexStep(OpenSearchStep): - """See base class.""" - - label = 'refresh_index' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - - def _action(self): - while True: - try: - self.opensearch.indices.refresh(index=self.index_name) - return {'store_kb': get_index_size_in_kb(self.opensearch, - self.index_name)} - except: - pass - - def _get_measures(self) -> List[str]: - return ['took', 'store_kb'] - - -class ForceMergeStep(OpenSearchStep): - """See base class.""" - - label = 'force_merge' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - self.max_num_segments = parse_int_param('max_num_segments', - step_config.config, {}, None) - - def _action(self): - while True: - try: - self.opensearch.indices.forcemerge( - index=self.index_name, - max_num_segments=self.max_num_segments) - return {} - except: - pass - - def _get_measures(self) -> List[str]: - return ['took'] - -class ClearCacheStep(OpenSearchStep): - """See base class.""" - - label = 'clear_cache' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - - def _action(self): - while True: - try: - self.opensearch.indices.clear_cache( - index=self.index_name) - return {} - except: - pass - - def _get_measures(self) -> List[str]: - return ['took'] - - -class WarmupStep(OpenSearchStep): - """See base class.""" - - label = 'warmup_operation' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.index_name = parse_string_param('index_name', step_config.config, {}, - None) - - def _action(self): - """Performs warmup operation on an index.""" - warmup_operation(self.endpoint, self.port, self.index_name) - return {} - - def _get_measures(self) -> List[str]: - return ['took'] - - -class TrainModelStep(OpenSearchStep): - """See base class.""" - - label = 'train_model' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - - self.model_id = parse_string_param('model_id', step_config.config, {}, - 'Test') - self.train_index_name = parse_string_param('train_index', - step_config.config, {}, None) - self.train_index_field = parse_string_param('train_field', - step_config.config, {}, - None) - self.dimension = parse_int_param('dimension', step_config.config, {}, - None) - self.description = parse_string_param('description', step_config.config, - {}, 'Default') - self.max_training_vector_count = parse_int_param( - 'max_training_vector_count', step_config.config, {}, 10000000000000) - - method_spec = parse_string_param('method_spec', step_config.config, {}, - None) - self.method = parse_json_from_path(method_spec) - if self.method is None: - raise ConfigurationError('method must be passed in') - - def _action(self): - """Train a model for an index. - - Returns: - The trained model - """ - - # Build body - body = { - 'training_index': self.train_index_name, - 'training_field': self.train_index_field, - 'description': self.description, - 'dimension': self.dimension, - 'method': self.method, - 'max_training_vector_count': self.max_training_vector_count - } - - # So, we trained the model. Now we need to wait until we have to wait - # until the model is created. Poll every - # 1/10 second - requests.post('http://' + self.endpoint + ':' + str(self.port) + - '/_plugins/_knn/models/' + str(self.model_id) + '/_train', - json.dumps(body), - headers={'content-type': 'application/json'}) - - sleep_time = 0.1 - timeout = 100000 - i = 0 - while i < timeout: - time.sleep(sleep_time) - model_response = get_model(self.endpoint, self.port, self.model_id) - if 'state' in model_response.keys() and model_response['state'] == \ - 'created': - return {} - i += 1 - - raise TimeoutError('Failed to create model') - - def _get_measures(self) -> List[str]: - return ['took'] - - -class DeleteModelStep(OpenSearchStep): - """See base class.""" - - label = 'delete_model' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - - self.model_id = parse_string_param('model_id', step_config.config, {}, - 'Test') - - def _action(self): - """Train a model for an index. - - Returns: - The trained model - """ - delete_model(self.endpoint, self.port, self.model_id) - return {} - - def _get_measures(self) -> List[str]: - return ['took'] - - -class DeleteIndexStep(OpenSearchStep): - """See base class.""" - - label = 'delete_index' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - - def _action(self): - """Delete the index - - Returns: - An empty dict - """ - delete_index(self.opensearch, self.index_name) - return {} - - def _get_measures(self) -> List[str]: - return ['took'] - - -class BaseIngestStep(OpenSearchStep): - """See base class.""" - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - self.field_name = parse_string_param('field_name', step_config.config, - {}, None) - self.bulk_size = parse_int_param('bulk_size', step_config.config, {}, - 300) - self.implicit_config = step_config.implicit_config - dataset_format = parse_string_param('dataset_format', - step_config.config, {}, 'hdf5') - dataset_path = parse_string_param('dataset_path', step_config.config, - {}, None) - self.dataset = parse_dataset(dataset_format, dataset_path, - Context.INDEX) - - self.input_doc_count = parse_int_param('doc_count', step_config.config, {}, - self.dataset.size()) - self.doc_count = min(self.input_doc_count, self.dataset.size()) - - def _action(self): - - def action(doc_id): - return {'index': {'_index': self.index_name, '_id': doc_id}} - - # Maintain minimal state outside of this loop. For large data sets, too - # much state may cause out of memory failure - for i in range(0, self.doc_count, self.bulk_size): - partition = self.dataset.read(self.bulk_size) - self._handle_data_bulk(partition, action, i) - self.dataset.reset() - - return {} - - def _get_measures(self) -> List[str]: - return ['took'] - - @abstractmethod - def _handle_data_bulk(self, partition, action, i): - pass - - -class IngestStep(BaseIngestStep): - """See base class.""" - - label = 'ingest' - - def _handle_data_bulk(self, partition, action, i): - if partition is None: - return - body = bulk_transform(partition, self.field_name, action, i) - bulk_index(self.opensearch, self.index_name, body) - - -class IngestMultiFieldStep(BaseIngestStep): - """See base class.""" - - label = 'ingest_multi_field' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - - dataset_path = parse_string_param('dataset_path', step_config.config, - {}, None) - - self.attributes_dataset_name = parse_string_param('attributes_dataset_name', - step_config.config, {}, None) - - self.attributes_dataset = parse_dataset('hdf5', dataset_path, - Context.CUSTOM, self.attributes_dataset_name) - - self.attribute_spec = parse_list_param('attribute_spec', - step_config.config, {}, []) - - self.partition_attr = self.attributes_dataset.read(self.doc_count) - self.action_buffer = None - - def _handle_data_bulk(self, partition, action, i): - if partition is None: - return - body = self.bulk_transform_with_attributes(partition, self.partition_attr, self.field_name, - action, i, self.attribute_spec) - bulk_index(self.opensearch, self.index_name, body) - - def bulk_transform_with_attributes(self, partition: np.ndarray, partition_attr, field_name: str, - action, offset: int, attributes_def) -> List[Dict[str, Any]]: - """Partitions and transforms a list of vectors into OpenSearch's bulk - injection format. - Args: - partition: An array of vectors to transform. - partition_attr: dictionary of additional data to transform - field_name: field name for action - action: Bulk API action. - offset: to start counting from - attributes_def: definition of additional doc fields - Returns: - An array of transformed vectors in bulk format. - """ - actions = [] - _ = [ - actions.extend([action(i + offset), None]) - for i in range(len(partition)) - ] - idx = 1 - part_list = partition.tolist() - for i in range(len(partition)): - actions[idx] = {field_name: part_list[i]} - attr_idx = i + offset - attr_def_idx = 0 - for attribute in attributes_def: - attr_def_name = attribute['name'] - attr_def_type = attribute['type'] - - if attr_def_type == 'str': - val = partition_attr[attr_idx][attr_def_idx].decode() - if val != 'None': - actions[idx][attr_def_name] = val - elif attr_def_type == 'int': - val = int(partition_attr[attr_idx][attr_def_idx].decode()) - actions[idx][attr_def_name] = val - attr_def_idx += 1 - idx += 2 - - return actions - - -class IngestNestedFieldStep(BaseIngestStep): - """See base class.""" - - label = 'ingest_nested_field' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - - dataset_path = parse_string_param('dataset_path', step_config.config, - {}, None) - - self.attributes_dataset_name = parse_string_param('attributes_dataset_name', - step_config.config, {}, None) - - self.attributes_dataset = parse_dataset('hdf5', dataset_path, - Context.CUSTOM, self.attributes_dataset_name) - - self.attribute_spec = parse_list_param('attribute_spec', - step_config.config, {}, []) - - self.partition_attr = self.attributes_dataset.read(self.doc_count) - - if self.dataset.size() != self.doc_count: - raise ValueError("custom doc_count is not supported for nested field") - self.action_buffer = None - self.action_parent_id = None - self.count = 0 - - def _handle_data_bulk(self, partition, action, i): - if partition is None: - return - body = self.bulk_transform_with_nested(partition, self.partition_attr, self.field_name, - action, i, self.attribute_spec) - if len(body) > 0: - bulk_index(self.opensearch, self.index_name, body) - - def bulk_transform_with_nested(self, partition: np.ndarray, partition_attr, field_name: str, - action, offset: int, attributes_def) -> List[Dict[str, Any]]: - """Partitions and transforms a list of vectors into OpenSearch's bulk - injection format. - Args: - partition: An array of vectors to transform. - partition_attr: dictionary of additional data to transform - field_name: field name for action - action: Bulk API action. - offset: to start counting from - attributes_def: definition of additional doc fields - Returns: - An array of transformed vectors in bulk format. - """ - # offset is index of start row. We need number of parent doc - 1. - # The number of parent document can be calculated by using partition_attr data. - # We need to keep the last parent doc aside so that additional data can be added later. - parent_id_idx = next((index for (index, d) in enumerate(attributes_def) if d.get('name') == 'parent_id'), None) - if parent_id_idx is None: - raise ValueError("parent_id should be provided as attribute spec") - if attributes_def[parent_id_idx]['type'] != 'int': - raise ValueError("parent_id should be int type") - - first_index = offset - last_index = offset + len(partition) - 1 - num_of_actions = int(partition_attr[last_index][parent_id_idx].decode()) - int(partition_attr[first_index][parent_id_idx].decode()) - if self.action_buffer is None: - self.action_buffer = {"nested_field": []} - self.action_parent_id = int(partition_attr[first_index][parent_id_idx].decode()) - - actions = [] - _ = [ - actions.extend([action(i + self.action_parent_id), None]) - for i in range(num_of_actions) - ] - - idx = 1 - part_list = partition.tolist() - for i in range(len(partition)): - self.count += 1 - nested = {field_name: part_list[i]} - attr_idx = i + offset - attr_def_idx = 0 - current_parent_id = None - for attribute in attributes_def: - attr_def_name = attribute['name'] - attr_def_type = attribute['type'] - if attr_def_name == "parent_id": - current_parent_id = int(partition_attr[attr_idx][attr_def_idx].decode()) - attr_def_idx += 1 - continue - - if attr_def_type == 'str': - val = partition_attr[attr_idx][attr_def_idx].decode() - if val != 'None': - nested[attr_def_name] = val - elif attr_def_type == 'int': - val = int(partition_attr[attr_idx][attr_def_idx].decode()) - nested[attr_def_name] = val - attr_def_idx += 1 - - if self.action_parent_id == current_parent_id: - self.action_buffer["nested_field"].append(nested) - else: - actions.extend([action(self.action_parent_id), self.action_buffer]) - self.action_buffer = {"nested_field": []} - self.action_buffer["nested_field"].append(nested) - self.action_parent_id = current_parent_id - idx += 2 - - if self.count == self.doc_count: - actions.extend([action(self.action_parent_id), self.action_buffer]) - - return actions - - -class BaseQueryStep(OpenSearchStep): - """See base class.""" - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.k = parse_int_param('k', step_config.config, {}, 100) - self.r = parse_int_param('r', step_config.config, {}, 1) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - self.field_name = parse_string_param('field_name', step_config.config, - {}, None) - self.calculate_recall = parse_bool_param('calculate_recall', - step_config.config, {}, False) - dataset_format = parse_string_param('dataset_format', - step_config.config, {}, 'hdf5') - dataset_path = parse_string_param('dataset_path', - step_config.config, {}, None) - self.dataset = parse_dataset(dataset_format, dataset_path, - Context.QUERY) - - input_query_count = parse_int_param('query_count', - step_config.config, {}, - self.dataset.size()) - self.query_count = min(input_query_count, self.dataset.size()) - - self.neighbors_format = parse_string_param('neighbors_format', - step_config.config, {}, 'hdf5') - self.neighbors_path = parse_string_param('neighbors_path', - step_config.config, {}, None) - - def _action(self): - - results = {} - query_responses = [] - for _ in range(self.query_count): - query = self.dataset.read(1) - if query is None: - break - query_responses.append( - query_index(self.opensearch, self.index_name, - self.get_body(query[0]) , self.get_exclude_fields())) - - results['took'] = [ - float(query_response['took']) for query_response in query_responses - ] - results['client_time'] = [ - float(query_response['client_time']) for query_response in query_responses - ] - results['memory_kb'] = get_cache_size_in_kb(self.endpoint, self.port) - - if self.calculate_recall: - ids = [[int(hit['_id']) - for hit in query_response['hits']['hits']] - for query_response in query_responses] - results['recall@K'] = recall_at_r(ids, self.neighbors, - self.k, self.k, self.query_count) - self.neighbors.reset() - results[f'recall@{str(self.r)}'] = recall_at_r( - ids, self.neighbors, self.r, self.k, self.query_count) - self.neighbors.reset() - - self.dataset.reset() - - return results - - def _get_measures(self) -> List[str]: - measures = ['took', 'memory_kb', 'client_time'] - - if self.calculate_recall: - measures.extend(['recall@K', f'recall@{str(self.r)}']) - - return measures - - @abstractmethod - def get_body(self, vec): - pass - - def get_exclude_fields(self): - return [self.field_name] - -class QueryStep(BaseQueryStep): - """See base class.""" - - label = 'query' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.neighbors = parse_dataset(self.neighbors_format, self.neighbors_path, - Context.NEIGHBORS) - self.implicit_config = step_config.implicit_config - - def get_body(self, vec): - return { - 'size': self.k, - 'query': { - 'knn': { - self.field_name: { - 'vector': vec, - 'k': self.k - } - } - } - } - - -class QueryWithFilterStep(BaseQueryStep): - """See base class.""" - - label = 'query_with_filter' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - - neighbors_dataset = parse_string_param('neighbors_dataset', - step_config.config, {}, None) - - self.neighbors = parse_dataset(self.neighbors_format, self.neighbors_path, - Context.CUSTOM, neighbors_dataset) - - self.filter_type = parse_string_param('filter_type', step_config.config, {}, 'SCRIPT') - self.filter_spec = parse_string_param('filter_spec', step_config.config, {}, None) - self.score_script_similarity = parse_string_param('score_script_similarity', step_config.config, {}, 'l2') - - self.implicit_config = step_config.implicit_config - - def get_body(self, vec): - filter_json = json.load(open(self.filter_spec)) - if self.filter_type == 'FILTER': - return { - 'size': self.k, - 'query': { - 'knn': { - self.field_name: { - 'vector': vec, - 'k': self.k, - 'filter': filter_json - } - } - } - } - elif self.filter_type == 'SCRIPT': - return { - 'size': self.k, - 'query': { - 'script_score': { - 'query': { - 'bool': { - 'filter': filter_json - } - }, - 'script': { - 'source': 'knn_score', - 'lang': 'knn', - 'params': { - 'field': self.field_name, - 'query_value': vec, - 'space_type': self.score_script_similarity - } - } - } - } - } - elif self.filter_type == 'BOOL_POST_FILTER': - return { - 'size': self.k, - 'query': { - 'bool': { - 'filter': filter_json, - 'must': [ - { - 'knn': { - self.field_name: { - 'vector': vec, - 'k': self.k - } - } - } - ] - } - } - } - else: - raise ConfigurationError('Not supported filter type {}'.format(self.filter_type)) - -class QueryNestedFieldStep(BaseQueryStep): - """See base class.""" - - label = 'query_nested_field' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - - neighbors_dataset = parse_string_param('neighbors_dataset', - step_config.config, {}, None) - - self.neighbors = parse_dataset(self.neighbors_format, self.neighbors_path, - Context.CUSTOM, neighbors_dataset) - - self.implicit_config = step_config.implicit_config - - def get_body(self, vec): - return { - 'size': self.k, - 'query': { - 'nested': { - 'path': 'nested_field', - 'query': { - 'knn': { - 'nested_field.' + self.field_name: { - 'vector': vec, - 'k': self.k - } - } - } - } - } - } - -class GetStatsStep(OpenSearchStep): - """See base class.""" - - label = 'get_stats' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - - def _action(self): - """Get stats for cluster/index etc. - - Returns: - Stats with following info: - - number of committed and search segments in the index - """ - results = {} - segment_stats = get_segment_stats(self.opensearch, self.index_name) - shards = segment_stats["indices"][self.index_name]["shards"] - num_of_committed_segments = 0 - num_of_search_segments = 0; - for shard_key in shards.keys(): - for segment in shards[shard_key]: - num_of_committed_segments += segment["num_committed_segments"] - num_of_search_segments += segment["num_search_segments"] - - results['committed_segments'] = num_of_committed_segments - results['search_segments'] = num_of_search_segments - return results - - def _get_measures(self) -> List[str]: - return ['committed_segments', 'search_segments'] - -# Helper functions - (AKA not steps) -def bulk_transform(partition: np.ndarray, field_name: str, action, - offset: int) -> List[Dict[str, Any]]: - """Partitions and transforms a list of vectors into OpenSearch's bulk - injection format. - Args: - offset: to start counting from - partition: An array of vectors to transform. - field_name: field name for action - action: Bulk API action. - Returns: - An array of transformed vectors in bulk format. - """ - actions = [] - _ = [ - actions.extend([action(i + offset), None]) - for i in range(len(partition)) - ] - actions[1::2] = [{field_name: vec} for vec in partition.tolist()] - return actions - - -def delete_index(opensearch: OpenSearch, index_name: str): - """Deletes an OpenSearch index. - - Args: - opensearch: An OpenSearch client. - index_name: Name of the OpenSearch index to be deleted. - """ - opensearch.indices.delete(index=index_name, ignore=[400, 404]) - - -def get_model(endpoint, port, model_id): - """ - Retrieve a model from an OpenSearch cluster - Args: - endpoint: Endpoint OpenSearch is running on - port: Port OpenSearch is running on - model_id: ID of model to be deleted - Returns: - Get model response - """ - response = requests.get('http://' + endpoint + ':' + str(port) + - '/_plugins/_knn/models/' + model_id, - headers={'content-type': 'application/json'}) - return response.json() - - -def delete_model(endpoint, port, model_id): - """ - Deletes a model from OpenSearch cluster - Args: - endpoint: Endpoint OpenSearch is running on - port: Port OpenSearch is running on - model_id: ID of model to be deleted - Returns: - Deleted model response - """ - response = requests.delete('http://' + endpoint + ':' + str(port) + - '/_plugins/_knn/models/' + model_id, - headers={'content-type': 'application/json'}) - return response.json() - - -def warmup_operation(endpoint, port, index): - """ - Performs warmup operation on index to load native library files - of that index to reduce query latencies. - Args: - endpoint: Endpoint OpenSearch is running on - port: Port OpenSearch is running on - index: index name - Returns: - number of shards the plugin succeeded and failed to warm up. - """ - response = requests.get('http://' + endpoint + ':' + str(port) + - '/_plugins/_knn/warmup/' + index, - headers={'content-type': 'application/json'}) - return response.json() - - -def get_opensearch_client(endpoint: str, port: int, timeout=60): - """ - Get an opensearch client from an endpoint and port - Args: - endpoint: Endpoint OpenSearch is running on - port: Port OpenSearch is running on - timeout: timeout for OpenSearch client, default value 60 - Returns: - OpenSearch client - - """ - # TODO: fix for security in the future - return OpenSearch( - hosts=[{ - 'host': endpoint, - 'port': port - }], - use_ssl=False, - verify_certs=False, - connection_class=RequestsHttpConnection, - timeout=timeout, - ) - - -def recall_at_r(results, neighbor_dataset, r, k, query_count): - """ - Calculates the recall@R for a set of queries against a ground truth nearest - neighbor set - Args: - results: 2D list containing ids of results returned by OpenSearch. - results[i][j] i refers to query, j refers to - result in the query - neighbor_dataset: 2D dataset containing ids of the true nearest - neighbors for a set of queries - r: number of top results to check if they are in the ground truth k-NN - set. - k: k value for the query - query_count: number of queries - Returns: - Recall at R - """ - correct = 0.0 - total_num_of_results = 0 - for query in range(query_count): - true_neighbors = neighbor_dataset.read(1) - if true_neighbors is None: - break - true_neighbors_set = set(true_neighbors[0][:k]) - true_neighbors_set.discard(-1) - min_r = min(r, len(true_neighbors_set)) - total_num_of_results += min_r - for j in range(min_r): - if results[query][j] in true_neighbors_set: - correct += 1.0 - - return correct / total_num_of_results - - -def get_index_size_in_kb(opensearch, index_name): - """ - Gets the size of an index in kilobytes - Args: - opensearch: opensearch client - index_name: name of index to look up - Returns: - size of index in kilobytes - """ - return int( - opensearch.indices.stats(index_name, metric='store')['indices'] - [index_name]['total']['store']['size_in_bytes']) / 1024 - - -def get_cache_size_in_kb(endpoint, port): - """ - Gets the size of the k-NN cache in kilobytes - Args: - endpoint: endpoint of OpenSearch cluster - port: port of endpoint OpenSearch is running on - Returns: - size of cache in kilobytes - """ - response = requests.get('http://' + endpoint + ':' + str(port) + - '/_plugins/_knn/stats', - headers={'content-type': 'application/json'}) - stats = response.json() - - keys = stats['nodes'].keys() - - total_used = 0 - for key in keys: - total_used += int(stats['nodes'][key]['graph_memory_usage']) - return total_used - - -def query_index(opensearch: OpenSearch, index_name: str, body: dict, - excluded_fields: list): - start_time = round(time.time()*1000) - queryResponse = opensearch.search(index=index_name, - body=body, - _source_excludes=excluded_fields) - end_time = round(time.time() * 1000) - queryResponse['client_time'] = end_time - start_time - return queryResponse - - -def bulk_index(opensearch: OpenSearch, index_name: str, body: List): - return opensearch.bulk(index=index_name, body=body) - -def get_segment_stats(opensearch: OpenSearch, index_name: str): - return opensearch.indices.segments(index=index_name) diff --git a/benchmarks/perf-tool/okpt/test/test.py b/benchmarks/perf-tool/okpt/test/test.py deleted file mode 100644 index c947545ad..000000000 --- a/benchmarks/perf-tool/okpt/test/test.py +++ /dev/null @@ -1,188 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Provides a base Test class.""" -from math import floor -from typing import Any, Dict, List - -from okpt.io.config.parsers.test import TestConfig -from okpt.test.steps.base import Step - - -def get_avg(values: List[Any]): - """Get average value of a list. - - Args: - values: A list of values. - - Returns: - The average value in the list. - """ - valid_total = len(values) - running_sum = 0.0 - - for value in values: - if value == -1: - valid_total -= 1 - continue - running_sum += value - - if valid_total == 0: - return -1 - return running_sum / valid_total - - -def _pxx(values: List[Any], p: float): - """Calculates the pXX statistics for a given list. - - Args: - values: List of values. - p: Percentile (between 0 and 1). - - Returns: - The corresponding pXX metric. - """ - lowest_percentile = 1 / len(values) - highest_percentile = (len(values) - 1) / len(values) - - # return -1 if p is out of range or if the list doesn't have enough elements - # to support the specified percentile - if p < 0 or p > 1: - return -1.0 - elif p < lowest_percentile or p > highest_percentile: - if p == 1.0 and len(values) > 1: - return float(values[len(values) - 1]) - return -1.0 - else: - return float(values[floor(len(values) * p)]) - - -def _aggregate_steps(step_results: List[Dict[str, Any]], - measure_labels=None): - """Aggregates the steps for a given Test. - - The aggregation process extracts the measures from each step and calculates - the total time spent performing each step measure, including the - percentile metrics, if possible. - - The aggregation process also extracts the test measures by simply summing - up the respective step measures. - - A step measure is formatted as `{step_name}_{measure_name}`, for example, - {bulk_index}_{took} or {query_index}_{memory}. The braces are not included - in the actual key string. - - Percentile/Total step measures are give as - `{step_name}_{measure_name}_{percentile|total}`. - - Test measures are just step measure sums so they just given as - `test_{measure_name}`. - - Args: - steps: List of test steps to be aggregated. - measures: List of step metrics to account for. - - Returns: - A complete test result. - """ - if measure_labels is None: - measure_labels = ['took'] - test_measures = { - f'test_{measure_label}': 0 - for measure_label in measure_labels - } - step_measures: Dict[str, Any] = {} - - # iterate over all test steps - for step in step_results: - step_label = step['label'] - - step_measure_labels = list(step.keys()) - step_measure_labels.remove('label') - - # iterate over all measures in each test step - for measure_label in step_measure_labels: - - step_measure = step[measure_label] - step_measure_label = f'{measure_label}' if step_label == 'get_stats' else f'{step_label}_{measure_label}' - - # Add cumulative test measures from steps to test measures - if measure_label in measure_labels: - test_measures[f'test_{measure_label}'] += sum(step_measure) if \ - isinstance(step_measure, list) else step_measure - - if step_measure_label in step_measures: - _ = step_measures[step_measure_label].extend(step_measure) \ - if isinstance(step_measure, list) else \ - step_measures[step_measure_label].append(step_measure) - else: - step_measures[step_measure_label] = step_measure if \ - isinstance(step_measure, list) else [step_measure] - - aggregate = {**test_measures} - # calculate the totals and percentile statistics for each step measure - # where relevant - for step_measure_label, step_measure in step_measures.items(): - step_measure.sort() - - aggregate[step_measure_label + '_total'] = float(sum(step_measure)) - - p50 = _pxx(step_measure, 0.50) - if p50 != -1: - aggregate[step_measure_label + '_p50'] = p50 - p90 = _pxx(step_measure, 0.90) - if p90 != -1: - aggregate[step_measure_label + '_p90'] = p90 - p99 = _pxx(step_measure, 0.99) - if p99 != -1: - aggregate[step_measure_label + '_p99'] = p99 - p99_9 = _pxx(step_measure, 0.999) - if p99_9 != -1: - aggregate[step_measure_label + '_p99.9'] = p99_9 - p100 = _pxx(step_measure, 1.00) - if p100 != -1: - aggregate[step_measure_label + '_p100'] = p100 - - return aggregate - - -class Test: - """A base Test class, representing a collection of steps to profiled and - aggregated. - - Methods: - setup: Performs test setup. Usually for steps not intended to be - profiled. - run_steps: Runs the test steps, aggregating the results into the - `step_results` instance field. - cleanup: Perform test cleanup. Useful for clearing the state of a - persistent process like OpenSearch. Cleanup steps are executed after - each run. - execute: Runs steps, cleans up, and aggregates the test result. - """ - def __init__(self, test_config: TestConfig): - """Initializes the test state. - """ - self.test_config = test_config - self.setup_steps: List[Step] = test_config.setup - self.test_steps: List[Step] = test_config.steps - self.cleanup_steps: List[Step] = test_config.cleanup - - def setup(self): - _ = [step.execute() for step in self.setup_steps] - - def _run_steps(self): - step_results = [] - _ = [step_results.extend(step.execute()) for step in self.test_steps] - return step_results - - def _cleanup(self): - _ = [step.execute() for step in self.cleanup_steps] - - def execute(self): - results = self._run_steps() - self._cleanup() - return _aggregate_steps(results) diff --git a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/index.json b/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/index.json deleted file mode 100644 index 7e8ddda8e..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/index.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1, - "knn.algo_param.ef_search": 100 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "faiss", - "parameters": { - "ef_construction": 256, - "m": 16 - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/relaxed-filter-spec.json b/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/relaxed-filter-spec.json deleted file mode 100644 index 3e04d12c4..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/relaxed-filter-spec.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "bool": - { - "should": - [ - { - "range": - { - "age": - { - "gte": 30, - "lte": 70 - } - } - }, - { - "term": - { - "color": "green" - } - }, - { - "term": - { - "color": "blue" - } - }, - { - "term": - { - "color": "yellow" - } - }, - { - "term": - { - "taste": "sweet" - } - } - ] - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/relaxed-filter-test.yml b/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/relaxed-filter-test.yml deleted file mode 100644 index ba8850e1d..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/relaxed-filter/relaxed-filter-test.yml +++ /dev/null @@ -1,40 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Faiss HNSW Relaxed Filter Test" -test_id: "Faiss HNSW Relaxed Filter Test" -num_runs: 3 -show_runs: false -steps: - - name: delete_index - index_name: target_index - - name: create_index - index_name: target_index - index_spec: release-configs/faiss-hnsw/filtering/relaxed-filter/index.json - - name: ingest_multi_field - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - attributes_dataset_name: attributes - attribute_spec: [ { name: 'color', type: 'str' }, { name: 'taste', type: 'str' }, { name: 'age', type: 'int' } ] - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: warmup_operation - index_name: target_index - - name: query_with_filter - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean-with-relaxed-filters.hdf5 - neighbors_dataset: neighbors_filter_5 - filter_spec: release-configs/faiss-hnsw/filtering/relaxed-filter/relaxed-filter-spec.json - filter_type: FILTER diff --git a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/index.json b/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/index.json deleted file mode 100644 index 7e8ddda8e..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/index.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1, - "knn.algo_param.ef_search": 100 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "faiss", - "parameters": { - "ef_construction": 256, - "m": 16 - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/restrictive-filter-spec.json b/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/restrictive-filter-spec.json deleted file mode 100644 index 9e6356f1c..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/restrictive-filter-spec.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "bool": - { - "must": - [ - { - "range": - { - "age": - { - "gte": 30, - "lte": 60 - } - } - }, - { - "term": - { - "taste": "bitter" - } - }, - { - "bool": - { - "should": - [ - { - "term": - { - "color": "blue" - } - }, - { - "term": - { - "color": "green" - } - } - ] - } - } - ] - } -} \ No newline at end of file diff --git a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/restrictive-filter-test.yml b/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/restrictive-filter-test.yml deleted file mode 100644 index 94f4073c7..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnsw/filtering/restrictive-filter/restrictive-filter-test.yml +++ /dev/null @@ -1,40 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Faiss HNSW Restrictive Filter Test" -test_id: "Faiss HNSW Restrictive Filter Test" -num_runs: 3 -show_runs: false -steps: - - name: delete_index - index_name: target_index - - name: create_index - index_name: target_index - index_spec: release-configs/faiss-hnsw/filtering/restrictive-filter/index.json - - name: ingest_multi_field - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - attributes_dataset_name: attributes - attribute_spec: [ { name: 'color', type: 'str' }, { name: 'taste', type: 'str' }, { name: 'age', type: 'int' } ] - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: warmup_operation - index_name: target_index - - name: query_with_filter - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean-with-restrictive-filters.hdf5 - neighbors_dataset: neighbors_filter_4 - filter_spec: release-configs/faiss-hnsw/filtering/restrictive-filter/restrictive-filter-spec.json - filter_type: FILTER diff --git a/benchmarks/perf-tool/release-configs/faiss-hnsw/index.json b/benchmarks/perf-tool/release-configs/faiss-hnsw/index.json deleted file mode 100644 index 7e8ddda8e..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnsw/index.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1, - "knn.algo_param.ef_search": 100 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "faiss", - "parameters": { - "ef_construction": 256, - "m": 16 - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-hnsw/nested/simple/index.json b/benchmarks/perf-tool/release-configs/faiss-hnsw/nested/simple/index.json deleted file mode 100644 index 338ceb1f4..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnsw/nested/simple/index.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1, - "knn.algo_param.ef_search": 100 - } - }, - "mappings": { - "_source": { - "excludes": ["nested_field"] - }, - "properties": { - "nested_field": { - "type": "nested", - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "faiss", - "parameters": { - "ef_construction": 256, - "m": 16 - } - } - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-hnsw/nested/simple/simple-nested-test.yml b/benchmarks/perf-tool/release-configs/faiss-hnsw/nested/simple/simple-nested-test.yml deleted file mode 100644 index 151b2014d..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnsw/nested/simple/simple-nested-test.yml +++ /dev/null @@ -1,37 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Faiss HNSW Nested Field Test" -test_id: "Faiss HNSW Nested Field Test" -num_runs: 3 -show_runs: false -steps: - - name: delete_index - index_name: target_index - - name: create_index - index_name: target_index - index_spec: release-configs/faiss-hnsw/nested/simple/index.json - - name: ingest_nested_field - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-nested.hdf5 - attributes_dataset_name: attributes - attribute_spec: [ { name: 'color', type: 'str' }, { name: 'taste', type: 'str' }, { name: 'age', type: 'int' }, { name: 'parent_id', type: 'int'} ] - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: warmup_operation - index_name: target_index - - name: query_nested_field - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-nested.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean-nested.hdf5 - neighbors_dataset: neighbour_nested \ No newline at end of file diff --git a/benchmarks/perf-tool/release-configs/faiss-hnsw/test.yml b/benchmarks/perf-tool/release-configs/faiss-hnsw/test.yml deleted file mode 100644 index c4740acf5..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnsw/test.yml +++ /dev/null @@ -1,35 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Faiss HNSW Test" -test_id: "Faiss HNSW Test" -num_runs: 3 -show_runs: false -steps: - - name: delete_index - index_name: target_index - - name: create_index - index_name: target_index - index_spec: release-configs/faiss-hnsw/index.json - - name: ingest - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: warmup_operation - index_name: target_index - - name: query - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean.hdf5 diff --git a/benchmarks/perf-tool/release-configs/faiss-hnswpq/index.json b/benchmarks/perf-tool/release-configs/faiss-hnswpq/index.json deleted file mode 100644 index 479703412..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnswpq/index.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "model_id": "test-model" - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-hnswpq/method-spec.json b/benchmarks/perf-tool/release-configs/faiss-hnswpq/method-spec.json deleted file mode 100644 index 2d67bf2df..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnswpq/method-spec.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name":"hnsw", - "engine":"faiss", - "space_type": "l2", - "parameters":{ - "ef_construction": 256, - "m": 16, - "encoder": { - "name": "pq", - "parameters": { - "m": 16 - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-hnswpq/test.yml b/benchmarks/perf-tool/release-configs/faiss-hnswpq/test.yml deleted file mode 100644 index f573ede9c..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnswpq/test.yml +++ /dev/null @@ -1,59 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Faiss HNSW PQ Test" -test_id: "Faiss HNSW PQ Test" -num_runs: 3 -show_runs: false -setup: - - name: delete_index - index_name: train_index - - name: create_index - index_name: train_index - index_spec: release-configs/faiss-hnswpq/train-index-spec.json - - name: ingest - index_name: train_index - field_name: train_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - doc_count: 50000 - - name: refresh_index - index_name: train_index -steps: - - name: delete_model - model_id: test-model - - name: delete_index - index_name: target_index - - name: train_model - model_id: test-model - train_index: train_index - train_field: train_field - dimension: 128 - method_spec: release-configs/faiss-hnswpq/method-spec.json - max_training_vector_count: 50000 - - name: create_index - index_name: target_index - index_spec: release-configs/faiss-hnswpq/index.json - - name: ingest - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: warmup_operation - index_name: target_index - - name: query - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean.hdf5 diff --git a/benchmarks/perf-tool/release-configs/faiss-hnswpq/train-index-spec.json b/benchmarks/perf-tool/release-configs/faiss-hnswpq/train-index-spec.json deleted file mode 100644 index 804a5707e..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-hnswpq/train-index-spec.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "settings": { - "index": { - "number_of_shards": 24, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "train_field": { - "type": "knn_vector", - "dimension": 128 - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/index.json b/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/index.json deleted file mode 100644 index ade7fa377..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/index.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "model_id": "test-model" - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/method-spec.json b/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/method-spec.json deleted file mode 100644 index 51ae89877..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/method-spec.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name":"ivf", - "engine":"faiss", - "space_type": "l2", - "parameters":{ - "nlist": 128, - "nprobes": 8 - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/relaxed-filter-spec.json b/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/relaxed-filter-spec.json deleted file mode 100644 index 3e04d12c4..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/relaxed-filter-spec.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "bool": - { - "should": - [ - { - "range": - { - "age": - { - "gte": 30, - "lte": 70 - } - } - }, - { - "term": - { - "color": "green" - } - }, - { - "term": - { - "color": "blue" - } - }, - { - "term": - { - "color": "yellow" - } - }, - { - "term": - { - "taste": "sweet" - } - } - ] - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/relaxed-filter-test.yml b/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/relaxed-filter-test.yml deleted file mode 100644 index adb25a04d..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/relaxed-filter-test.yml +++ /dev/null @@ -1,64 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Faiss IVF Relaxed Filter Test" -test_id: "Faiss IVF Relaxed Filter Test" -num_runs: 3 -show_runs: false -setup: - - name: delete_index - index_name: train_index - - name: create_index - index_name: train_index - index_spec: release-configs/faiss-ivf/filtering/relaxed-filter/train-index-spec.json - - name: ingest - index_name: train_index - field_name: train_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - doc_count: 50000 - - name: refresh_index - index_name: train_index -steps: - - name: delete_model - model_id: test-model - - name: delete_index - index_name: target_index - - name: train_model - model_id: test-model - train_index: train_index - train_field: train_field - dimension: 128 - method_spec: release-configs/faiss-ivf/filtering/relaxed-filter/method-spec.json - max_training_vector_count: 50000 - - name: create_index - index_name: target_index - index_spec: release-configs/faiss-ivf/filtering/relaxed-filter/index.json - - name: ingest_multi_field - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - attributes_dataset_name: attributes - attribute_spec: [ { name: 'color', type: 'str' }, { name: 'taste', type: 'str' }, { name: 'age', type: 'int' } ] - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: warmup_operation - index_name: target_index - - name: query_with_filter - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean-with-relaxed-filters.hdf5 - neighbors_dataset: neighbors_filter_5 - filter_spec: release-configs/faiss-ivf/filtering/relaxed-filter/relaxed-filter-spec.json - filter_type: FILTER diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/train-index-spec.json b/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/train-index-spec.json deleted file mode 100644 index 137fac9d8..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/relaxed-filter/train-index-spec.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "settings": { - "index": { - "number_of_shards": 24, - "number_of_replicas": 1 - } - }, - "mappings": { - "properties": { - "train_field": { - "type": "knn_vector", - "dimension": 128 - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/index.json b/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/index.json deleted file mode 100644 index ade7fa377..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/index.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "model_id": "test-model" - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/method-spec.json b/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/method-spec.json deleted file mode 100644 index 51ae89877..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/method-spec.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name":"ivf", - "engine":"faiss", - "space_type": "l2", - "parameters":{ - "nlist": 128, - "nprobes": 8 - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/restrictive-filter-spec.json b/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/restrictive-filter-spec.json deleted file mode 100644 index 9e6356f1c..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/restrictive-filter-spec.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "bool": - { - "must": - [ - { - "range": - { - "age": - { - "gte": 30, - "lte": 60 - } - } - }, - { - "term": - { - "taste": "bitter" - } - }, - { - "bool": - { - "should": - [ - { - "term": - { - "color": "blue" - } - }, - { - "term": - { - "color": "green" - } - } - ] - } - } - ] - } -} \ No newline at end of file diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/restrictive-filter-test.yml b/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/restrictive-filter-test.yml deleted file mode 100644 index bad047eab..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/restrictive-filter-test.yml +++ /dev/null @@ -1,64 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Faiss IVF restrictive Filter Test" -test_id: "Faiss IVF restrictive Filter Test" -num_runs: 3 -show_runs: false -setup: - - name: delete_index - index_name: train_index - - name: create_index - index_name: train_index - index_spec: release-configs/faiss-ivf/filtering/restrictive-filter/train-index-spec.json - - name: ingest - index_name: train_index - field_name: train_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - doc_count: 50000 - - name: refresh_index - index_name: train_index -steps: - - name: delete_model - model_id: test-model - - name: delete_index - index_name: target_index - - name: train_model - model_id: test-model - train_index: train_index - train_field: train_field - dimension: 128 - method_spec: release-configs/faiss-ivf/filtering/restrictive-filter/method-spec.json - max_training_vector_count: 50000 - - name: create_index - index_name: target_index - index_spec: release-configs/faiss-ivf/filtering/restrictive-filter/index.json - - name: ingest_multi_field - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - attributes_dataset_name: attributes - attribute_spec: [ { name: 'color', type: 'str' }, { name: 'taste', type: 'str' }, { name: 'age', type: 'int' } ] - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: warmup_operation - index_name: target_index - - name: query_with_filter - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean-with-restrictive-filters.hdf5 - neighbors_dataset: neighbors_filter_4 - filter_spec: release-configs/faiss-ivf/filtering/restrictive-filter/restrictive-filter-spec.json - filter_type: FILTER diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/train-index-spec.json b/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/train-index-spec.json deleted file mode 100644 index 804a5707e..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/filtering/restrictive-filter/train-index-spec.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "settings": { - "index": { - "number_of_shards": 24, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "train_field": { - "type": "knn_vector", - "dimension": 128 - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/index.json b/benchmarks/perf-tool/release-configs/faiss-ivf/index.json deleted file mode 100644 index 479703412..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/index.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "model_id": "test-model" - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/method-spec.json b/benchmarks/perf-tool/release-configs/faiss-ivf/method-spec.json deleted file mode 100644 index 51ae89877..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/method-spec.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name":"ivf", - "engine":"faiss", - "space_type": "l2", - "parameters":{ - "nlist": 128, - "nprobes": 8 - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/test.yml b/benchmarks/perf-tool/release-configs/faiss-ivf/test.yml deleted file mode 100644 index 367c42594..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/test.yml +++ /dev/null @@ -1,59 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Faiss IVF" -test_id: "Faiss IVF" -num_runs: 3 -show_runs: false -setup: - - name: delete_index - index_name: train_index - - name: create_index - index_name: train_index - index_spec: release-configs/faiss-ivf/train-index-spec.json - - name: ingest - index_name: train_index - field_name: train_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - doc_count: 50000 - - name: refresh_index - index_name: train_index -steps: - - name: delete_model - model_id: test-model - - name: delete_index - index_name: target_index - - name: train_model - model_id: test-model - train_index: train_index - train_field: train_field - dimension: 128 - method_spec: release-configs/faiss-ivf/method-spec.json - max_training_vector_count: 50000 - - name: create_index - index_name: target_index - index_spec: release-configs/faiss-ivf/index.json - - name: ingest - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: warmup_operation - index_name: target_index - - name: query - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean.hdf5 diff --git a/benchmarks/perf-tool/release-configs/faiss-ivf/train-index-spec.json b/benchmarks/perf-tool/release-configs/faiss-ivf/train-index-spec.json deleted file mode 100644 index 804a5707e..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivf/train-index-spec.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "settings": { - "index": { - "number_of_shards": 24, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "train_field": { - "type": "knn_vector", - "dimension": 128 - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivfpq/index.json b/benchmarks/perf-tool/release-configs/faiss-ivfpq/index.json deleted file mode 100644 index 479703412..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivfpq/index.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "model_id": "test-model" - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivfpq/method-spec.json b/benchmarks/perf-tool/release-configs/faiss-ivfpq/method-spec.json deleted file mode 100644 index 204b0a653..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivfpq/method-spec.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name":"ivf", - "engine":"faiss", - "space_type": "l2", - "parameters":{ - "nlist": 128, - "nprobes": 8, - "encoder": { - "name": "pq", - "parameters": { - "m": 16, - "code_size": 8 - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/faiss-ivfpq/test.yml b/benchmarks/perf-tool/release-configs/faiss-ivfpq/test.yml deleted file mode 100644 index c3f63348b..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivfpq/test.yml +++ /dev/null @@ -1,59 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Faiss IVF PQ Test" -test_id: "Faiss IVF PQ Test" -num_runs: 3 -show_runs: false -setup: - - name: delete_index - index_name: train_index - - name: create_index - index_name: train_index - index_spec: release-configs/faiss-ivfpq/train-index-spec.json - - name: ingest - index_name: train_index - field_name: train_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - doc_count: 50000 - - name: refresh_index - index_name: train_index -steps: - - name: delete_model - model_id: test-model - - name: delete_index - index_name: target_index - - name: train_model - model_id: test-model - train_index: train_index - train_field: train_field - dimension: 128 - method_spec: release-configs/faiss-ivfpq/method-spec.json - max_training_vector_count: 50000 - - name: create_index - index_name: target_index - index_spec: release-configs/faiss-ivfpq/index.json - - name: ingest - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: warmup_operation - index_name: target_index - - name: query - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean.hdf5 diff --git a/benchmarks/perf-tool/release-configs/faiss-ivfpq/train-index-spec.json b/benchmarks/perf-tool/release-configs/faiss-ivfpq/train-index-spec.json deleted file mode 100644 index 804a5707e..000000000 --- a/benchmarks/perf-tool/release-configs/faiss-ivfpq/train-index-spec.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "settings": { - "index": { - "number_of_shards": 24, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "train_field": { - "type": "knn_vector", - "dimension": 128 - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/index.json b/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/index.json deleted file mode 100644 index 7a9ff2890..000000000 --- a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/index.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "lucene", - "parameters": { - "ef_construction": 256, - "m": 16 - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/relaxed-filter-spec.json b/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/relaxed-filter-spec.json deleted file mode 100644 index 3e04d12c4..000000000 --- a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/relaxed-filter-spec.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "bool": - { - "should": - [ - { - "range": - { - "age": - { - "gte": 30, - "lte": 70 - } - } - }, - { - "term": - { - "color": "green" - } - }, - { - "term": - { - "color": "blue" - } - }, - { - "term": - { - "color": "yellow" - } - }, - { - "term": - { - "taste": "sweet" - } - } - ] - } -} diff --git a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/relaxed-filter-test.yml b/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/relaxed-filter-test.yml deleted file mode 100644 index 3bbb99a0f..000000000 --- a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/relaxed-filter/relaxed-filter-test.yml +++ /dev/null @@ -1,38 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Lucene HNSW Relaxed Filter Test" -test_id: "Lucene HNSW Relaxed Filter Test" -num_runs: 3 -show_runs: false -steps: - - name: delete_index - index_name: target_index - - name: create_index - index_name: target_index - index_spec: release-configs/lucene-hnsw/filtering/relaxed-filter/index.json - - name: ingest_multi_field - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - attributes_dataset_name: attributes - attribute_spec: [ { name: 'color', type: 'str' }, { name: 'taste', type: 'str' }, { name: 'age', type: 'int' } ] - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: query_with_filter - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean-with-relaxed-filters.hdf5 - neighbors_dataset: neighbors_filter_5 - filter_spec: release-configs/lucene-hnsw/filtering/relaxed-filter/relaxed-filter-spec.json - filter_type: FILTER diff --git a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/index.json b/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/index.json deleted file mode 100644 index 7a9ff2890..000000000 --- a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/index.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "lucene", - "parameters": { - "ef_construction": 256, - "m": 16 - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/restrictive-filter-spec.json b/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/restrictive-filter-spec.json deleted file mode 100644 index 9e6356f1c..000000000 --- a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/restrictive-filter-spec.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "bool": - { - "must": - [ - { - "range": - { - "age": - { - "gte": 30, - "lte": 60 - } - } - }, - { - "term": - { - "taste": "bitter" - } - }, - { - "bool": - { - "should": - [ - { - "term": - { - "color": "blue" - } - }, - { - "term": - { - "color": "green" - } - } - ] - } - } - ] - } -} \ No newline at end of file diff --git a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/restrictive-filter-test.yml b/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/restrictive-filter-test.yml deleted file mode 100644 index aa4c5193f..000000000 --- a/benchmarks/perf-tool/release-configs/lucene-hnsw/filtering/restrictive-filter/restrictive-filter-test.yml +++ /dev/null @@ -1,38 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Lucene HNSW Restrictive Filter Test" -test_id: "Lucene HNSW Restrictive Filter Test" -num_runs: 3 -show_runs: false -steps: - - name: delete_index - index_name: target_index - - name: create_index - index_name: target_index - index_spec: release-configs/lucene-hnsw/filtering/restrictive-filter/index.json - - name: ingest_multi_field - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - attributes_dataset_name: attributes - attribute_spec: [ { name: 'color', type: 'str' }, { name: 'taste', type: 'str' }, { name: 'age', type: 'int' } ] - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: query_with_filter - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-with-attr.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean-with-restrictive-filters.hdf5 - neighbors_dataset: neighbors_filter_4 - filter_spec: release-configs/lucene-hnsw/filtering/restrictive-filter/restrictive-filter-spec.json - filter_type: FILTER diff --git a/benchmarks/perf-tool/release-configs/lucene-hnsw/index.json b/benchmarks/perf-tool/release-configs/lucene-hnsw/index.json deleted file mode 100644 index 7a9ff2890..000000000 --- a/benchmarks/perf-tool/release-configs/lucene-hnsw/index.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "lucene", - "parameters": { - "ef_construction": 256, - "m": 16 - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/lucene-hnsw/nested/simple/index.json b/benchmarks/perf-tool/release-configs/lucene-hnsw/nested/simple/index.json deleted file mode 100644 index b41b51c77..000000000 --- a/benchmarks/perf-tool/release-configs/lucene-hnsw/nested/simple/index.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1 - } - }, - "mappings": { - "_source": { - "excludes": ["nested_field"] - }, - "properties": { - "nested_field": { - "type": "nested", - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "lucene", - "parameters": { - "ef_construction": 256, - "m": 16 - } - } - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/lucene-hnsw/nested/simple/simple-nested-test.yml b/benchmarks/perf-tool/release-configs/lucene-hnsw/nested/simple/simple-nested-test.yml deleted file mode 100644 index be825487a..000000000 --- a/benchmarks/perf-tool/release-configs/lucene-hnsw/nested/simple/simple-nested-test.yml +++ /dev/null @@ -1,37 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Lucene HNSW Nested Field Test" -test_id: "Lucene HNSW Nested Field Test" -num_runs: 3 -show_runs: false -steps: - - name: delete_index - index_name: target_index - - name: create_index - index_name: target_index - index_spec: release-configs/lucene-hnsw/nested/simple/index.json - - name: ingest_nested_field - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-nested.hdf5 - attributes_dataset_name: attributes - attribute_spec: [ { name: 'color', type: 'str' }, { name: 'taste', type: 'str' }, { name: 'age', type: 'int' }, { name: 'parent_id', type: 'int'} ] - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: warmup_operation - index_name: target_index - - name: query_nested_field - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean-nested.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean-nested.hdf5 - neighbors_dataset: neighbour_nested \ No newline at end of file diff --git a/benchmarks/perf-tool/release-configs/lucene-hnsw/test.yml b/benchmarks/perf-tool/release-configs/lucene-hnsw/test.yml deleted file mode 100644 index b253ee08e..000000000 --- a/benchmarks/perf-tool/release-configs/lucene-hnsw/test.yml +++ /dev/null @@ -1,33 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Lucene HNSW" -test_id: "Lucene HNSW" -num_runs: 3 -show_runs: false -steps: - - name: delete_index - index_name: target_index - - name: create_index - index_name: target_index - index_spec: release-configs/lucene-hnsw/index.json - - name: ingest - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: query - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean.hdf5 diff --git a/benchmarks/perf-tool/release-configs/nmslib-hnsw/index.json b/benchmarks/perf-tool/release-configs/nmslib-hnsw/index.json deleted file mode 100644 index eb714c5c8..000000000 --- a/benchmarks/perf-tool/release-configs/nmslib-hnsw/index.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 24, - "number_of_replicas": 1, - "knn.algo_param.ef_search": 100 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "nmslib", - "parameters": { - "ef_construction": 256, - "m": 16 - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/release-configs/nmslib-hnsw/test.yml b/benchmarks/perf-tool/release-configs/nmslib-hnsw/test.yml deleted file mode 100644 index 94ad9b131..000000000 --- a/benchmarks/perf-tool/release-configs/nmslib-hnsw/test.yml +++ /dev/null @@ -1,35 +0,0 @@ -endpoint: [ENDPOINT] -port: [PORT] -test_name: "Nmslib HNSW Test" -test_id: "Nmslib HNSW Test" -num_runs: 3 -show_runs: false -steps: - - name: delete_index - index_name: target_index - - name: create_index - index_name: target_index - index_spec: release-configs/nmslib-hnsw/index.json - - name: ingest - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 1 - - name: warmup_operation - index_name: target_index - - name: query - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: dataset/sift-128-euclidean.hdf5 - neighbors_format: hdf5 - neighbors_path: dataset/sift-128-euclidean.hdf5 diff --git a/benchmarks/perf-tool/release-configs/run_all_tests.sh b/benchmarks/perf-tool/release-configs/run_all_tests.sh deleted file mode 100755 index e65d5b5c4..000000000 --- a/benchmarks/perf-tool/release-configs/run_all_tests.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/bash -set -e - -# Description: -# Run a performance test for release -# Dataset should be available in perf-tool/dataset before running this script -# -# Example: -# ./run-test.sh --endpoint localhost -# -# Usage: -# ./run-test.sh \ -# --endpoint -# --port 80 \ -# --num-runs 3 \ -# --outputs ~/outputs - -while [ "$1" != "" ]; do - case $1 in - -url | --endpoint ) shift - ENDPOINT=$1 - ;; - -p | --port ) shift - PORT=$1 - ;; - -n | --num-runs ) shift - NUM_RUNS=$1 - ;; - -o | --outputs ) shift - OUTPUTS=$1 - ;; - * ) echo "Unknown parameter" - echo $1 - exit 1 - ;; - esac - shift -done - -if [ ! -n "$ENDPOINT" ]; then - echo "--endpoint should be specified" - exit -fi - -if [ ! -n "$PORT" ]; then - PORT=80 - echo "--port is not specified. Using default values $PORT" -fi - -if [ ! -n "$NUM_RUNS" ]; then - NUM_RUNS=3 - echo "--num-runs is not specified. Using default values $NUM_RUNS" -fi - -if [ ! -n "$OUTPUTS" ]; then - OUTPUTS="$HOME/outputs" - echo "--outputs is not specified. Using default values $OUTPUTS" -fi - - -curl -X PUT "http://$ENDPOINT:$PORT/_cluster/settings?pretty" -H 'Content-Type: application/json' -d' -{ - "persistent" : { - "knn.algo_param.index_thread_qty" : 4 - } -} -' - -TESTS="./release-configs/faiss-hnsw/filtering/relaxed-filter/relaxed-filter-test.yml -./release-configs/faiss-hnsw/filtering/restrictive-filter/restrictive-filter-test.yml -./release-configs/faiss-hnsw/nested/simple/simple-nested-test.yml -./release-configs/faiss-hnsw/test.yml -./release-configs/faiss-hnswpq/test.yml -./release-configs/faiss-ivf/filtering/relaxed-filter/relaxed-filter-test.yml -./release-configs/faiss-ivf/filtering/restrictive-filter/restrictive-filter-test.yml -./release-configs/faiss-ivf/test.yml -./release-configs/faiss-ivfpq/test.yml -./release-configs/lucene-hnsw/filtering/relaxed-filter/relaxed-filter-test.yml -./release-configs/lucene-hnsw/filtering/restrictive-filter/restrictive-filter-test.yml -./release-configs/lucene-hnsw/nested/simple/simple-nested-test.yml -./release-configs/lucene-hnsw/test.yml -./release-configs/nmslib-hnsw/test.yml" - -if [ ! -d $OUTPUTS ] -then - mkdir $OUTPUTS -fi - -for TEST in $TESTS -do - ORG_FILE=$TEST - NEW_FILE="$ORG_FILE.tmp" - OUT_FILE=$(grep test_id $ORG_FILE | cut -d':' -f2 | sed -r 's/^ "|"$//g' | sed 's/ /_/g') - echo "cp $ORG_FILE $NEW_FILE" - cp $ORG_FILE $NEW_FILE - sed -i "/^endpoint:/c\endpoint: $ENDPOINT" $NEW_FILE - sed -i "/^port:/c\port: $PORT" $NEW_FILE - sed -i "/^num_runs:/c\num_runs: $NUM_RUNS" $NEW_FILE - python3 knn-perf-tool.py test $NEW_FILE $OUTPUTS/$OUT_FILE - #Sleep for 1 min to cool down cpu from the previous run - sleep 60 -done diff --git a/benchmarks/perf-tool/requirements.in b/benchmarks/perf-tool/requirements.in deleted file mode 100644 index fd3555aab..000000000 --- a/benchmarks/perf-tool/requirements.in +++ /dev/null @@ -1,7 +0,0 @@ -Cerberus -opensearch-py -PyYAML -numpy -h5py -requests -psutil diff --git a/benchmarks/perf-tool/requirements.txt b/benchmarks/perf-tool/requirements.txt deleted file mode 100644 index fdfe205f8..000000000 --- a/benchmarks/perf-tool/requirements.txt +++ /dev/null @@ -1,37 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.9 -# To update, run: -# -# pip-compile -# -cerberus==1.3.4 - # via -r requirements.in -certifi==2024.7.4 - # via - # opensearch-py - # requests -charset-normalizer==2.0.4 - # via requests -h5py==3.3.0 - # via -r requirements.in -idna==3.7 - # via requests -numpy==1.24.2 - # via - # -r requirements.in - # h5py -opensearch-py==1.0.0 - # via -r requirements.in -psutil==5.8.0 - # via -r requirements.in -pyyaml==5.4.1 - # via -r requirements.in -requests==2.32.0 - # via -r requirements.in -urllib3==1.26.18 - # via - # opensearch-py - # requests - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/index-spec.json b/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/index-spec.json deleted file mode 100644 index 5542ef387..000000000 --- a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/index-spec.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 3, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "model_id": "test-model" - } - } - } -} diff --git a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/method-spec.json b/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/method-spec.json deleted file mode 100644 index 1aa7f809f..000000000 --- a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/method-spec.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name":"ivf", - "engine":"faiss", - "parameters":{ - "nlist":16, - "nprobes": 4 - } -} diff --git a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/test.yml b/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/test.yml deleted file mode 100644 index 027ba8683..000000000 --- a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/test.yml +++ /dev/null @@ -1,62 +0,0 @@ -endpoint: localhost -test_name: faiss_sift_ivf -test_id: "Test workflow for faiss ivf" -num_runs: 3 -show_runs: true -setup: - - name: delete_model - model_id: test-model - - name: delete_index - index_name: target_index - - name: delete_index - index_name: train_index - - name: create_index - index_name: train_index - index_spec: sample-configs/faiss-sift-ivf/train-index-spec.json - - name: ingest - index_name: train_index - field_name: train_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: train_index -steps: - - name: train_model - model_id: test-model - train_index: train_index - train_field: train_field - dimension: 128 - method_spec: sample-configs/faiss-sift-ivf/method-spec.json - max_training_vector_count: 1000000000 - - name: create_index - index_name: target_index - index_spec: sample-configs/faiss-sift-ivf/index-spec.json - - name: ingest - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 10 - - name: warmup_operation - index_name: target_index - - name: query - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean.hdf5 - neighbors_format: hdf5 - neighbors_path: ../dataset/sift-128-euclidean.hdf5 -cleanup: - - name: delete_model - model_id: test-model - - name: delete_index - index_name: target_index diff --git a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/train-index-spec.json b/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/train-index-spec.json deleted file mode 100644 index 00a418e4f..000000000 --- a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/train-index-spec.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "settings": { - "index": { - "number_of_shards": 3, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "train_field": { - "type": "knn_vector", - "dimension": 128 - } - } - } -} diff --git a/benchmarks/perf-tool/sample-configs/filter-spec/filter-1-spec.json b/benchmarks/perf-tool/sample-configs/filter-spec/filter-1-spec.json deleted file mode 100644 index f529de4fe..000000000 --- a/benchmarks/perf-tool/sample-configs/filter-spec/filter-1-spec.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "bool": - { - "must": - [ - { - "range": - { - "age": - { - "gte": 20, - "lte": 100 - } - } - }, - { - "term": - { - "color": "red" - } - } - ] - } -} \ No newline at end of file diff --git a/benchmarks/perf-tool/sample-configs/filter-spec/filter-2-spec.json b/benchmarks/perf-tool/sample-configs/filter-spec/filter-2-spec.json deleted file mode 100644 index 9d4514e62..000000000 --- a/benchmarks/perf-tool/sample-configs/filter-spec/filter-2-spec.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "bool": - { - "must": - [ - { - "term": - { - "taste": "salty" - } - }, - { - "bool": - { - "should": - [ - { - "bool": - { - "must_not": - { - "exists": - { - "field": "color" - } - } - } - }, - { - "term": - { - "color": "blue" - } - } - ] - } - } - ] - } -} \ No newline at end of file diff --git a/benchmarks/perf-tool/sample-configs/filter-spec/filter-3-spec.json b/benchmarks/perf-tool/sample-configs/filter-spec/filter-3-spec.json deleted file mode 100644 index d69f8768e..000000000 --- a/benchmarks/perf-tool/sample-configs/filter-spec/filter-3-spec.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "bool": - { - "must": - [ - { - "range": - { - "age": - { - "gte": 20, - "lte": 80 - } - } - }, - { - "exists": - { - "field": "color" - } - }, - { - "exists": - { - "field": "taste" - } - } - ] - } -} \ No newline at end of file diff --git a/benchmarks/perf-tool/sample-configs/filter-spec/filter-4-spec.json b/benchmarks/perf-tool/sample-configs/filter-spec/filter-4-spec.json deleted file mode 100644 index 822d63b37..000000000 --- a/benchmarks/perf-tool/sample-configs/filter-spec/filter-4-spec.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "bool": - { - "must": - [ - { - "range": - { - "age": - { - "gte": 30, - "lte": 60 - } - } - }, - { - "term": - { - "taste": "bitter" - } - }, - { - "bool": - { - "should": - [ - { - "term": - { - "color": "blue" - } - }, - { - "term": - { - "color": "green" - } - } - ] - } - } - ] - } -} diff --git a/benchmarks/perf-tool/sample-configs/filter-spec/filter-5-spec.json b/benchmarks/perf-tool/sample-configs/filter-spec/filter-5-spec.json deleted file mode 100644 index 3e04d12c4..000000000 --- a/benchmarks/perf-tool/sample-configs/filter-spec/filter-5-spec.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "bool": - { - "should": - [ - { - "range": - { - "age": - { - "gte": 30, - "lte": 70 - } - } - }, - { - "term": - { - "color": "green" - } - }, - { - "term": - { - "color": "blue" - } - }, - { - "term": - { - "color": "yellow" - } - }, - { - "term": - { - "taste": "sweet" - } - } - ] - } -} diff --git a/benchmarks/perf-tool/sample-configs/lucene-sift-hnsw-filter/index-spec.json b/benchmarks/perf-tool/sample-configs/lucene-sift-hnsw-filter/index-spec.json deleted file mode 100644 index 83ea79b15..000000000 --- a/benchmarks/perf-tool/sample-configs/lucene-sift-hnsw-filter/index-spec.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "refresh_interval": "10s", - "number_of_shards": 30, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "lucene", - "parameters": { - "ef_construction": 100, - "m": 16 - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/sample-configs/lucene-sift-hnsw-filter/test.yml b/benchmarks/perf-tool/sample-configs/lucene-sift-hnsw-filter/test.yml deleted file mode 100644 index aa2ee6389..000000000 --- a/benchmarks/perf-tool/sample-configs/lucene-sift-hnsw-filter/test.yml +++ /dev/null @@ -1,41 +0,0 @@ -endpoint: localhost -test_name: lucene_sift_hnsw -test_id: "Test workflow for lucene hnsw" -num_runs: 1 -show_runs: false -setup: - - name: delete_index - index_name: target_index -steps: - - name: create_index - index_name: target_index - index_spec: sample-configs/lucene-sift-hnsw-filter/index-spec.json - - name: ingest_multi_field - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean-with-attr.hdf5 - attributes_dataset_name: attributes - attribute_spec: [ { name: 'color', type: 'str' }, { name: 'taste', type: 'str' }, { name: 'age', type: 'int' } ] - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 10 - - name: query_with_filter - k: 10 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean-with-attr.hdf5 - neighbors_format: hdf5 - neighbors_path: ../dataset/sift-128-euclidean-with-attr-with-filters.hdf5 - neighbors_dataset: neighbors_filter_1 - filter_spec: sample-configs/filter-spec/filter-1-spec.json - query_count: 100 -cleanup: - - name: delete_index - index_name: target_index \ No newline at end of file diff --git a/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/index-spec.json b/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/index-spec.json deleted file mode 100644 index 75abe7baa..000000000 --- a/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/index-spec.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "knn.algo_param.ef_search": 512, - "refresh_interval": "10s", - "number_of_shards": 1, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "nmslib", - "parameters": { - "ef_construction": 512, - "m": 16 - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/test.yml b/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/test.yml deleted file mode 100644 index 6d96bf80c..000000000 --- a/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/test.yml +++ /dev/null @@ -1,38 +0,0 @@ -endpoint: localhost -test_name: nmslib_sift_hnsw -test_id: "Test workflow for nmslib hnsw" -num_runs: 2 -show_runs: false -setup: - - name: delete_index - index_name: target_index -steps: - - name: create_index - index_name: target_index - index_spec: sample-configs/nmslib-sift-hnsw/index-spec.json - - name: ingest - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 10 - - name: warmup_operation - index_name: target_index - - name: query - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean.hdf5 - neighbors_format: hdf5 - neighbors_path: ../dataset/sift-128-euclidean.hdf5 -cleanup: - - name: delete_index - index_name: target_index From 5423cc15d838ef557b22e88c86a621cd639f04a3 Mon Sep 17 00:00:00 2001 From: akashsha1 <113050768+akashsha1@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:22:40 -0700 Subject: [PATCH 20/59] Add changes for AVX-512 support in k-NN. (#2110) * changes for AVX-512. Signed-off by: Akash Shankaran Signed-off-by: Akash Shankaran * add cpu detection logic to security workflow. Signed-off by: Akash Shankaran Signed-off-by: Akash Shankaran * add cpu detection logic to backward compat test workflow. Signed-off by: Akash Shankaran Signed-off-by: Akash Shankaran * fix bwc workflow. Signed-off by: Akash Shankaran Signed-off-by: Akash Shankaran * address PR feedback. Signed-off by: Akash Shankaran Signed-off-by: Akash Shankaran * fix a bug in KNNSettings. Signed-off by: Akash Shankaran Signed-off-by: Akash Shankaran * fix a bug in KNNSettings. Signed-off by: Akash Shankaran Signed-off-by: Akash Shankaran * update KNNSettings. Signed-off by: Akash Shankaran Signed-off-by: Akash Shankaran --------- Signed-off-by: Akash Shankaran --- .github/workflows/CI.yml | 16 +++-- ...backwards_compatibility_tests_workflow.yml | 29 ++++++++-- .github/workflows/test_security.yml | 13 ++++- CHANGELOG.md | 1 + DEVELOPER_GUIDE.md | 13 +++-- build.gradle | 6 +- jni/cmake/init-faiss.cmake | 14 ++++- scripts/build.sh | 7 ++- .../opensearch/knn/common/KNNConstants.java | 1 + .../org/opensearch/knn/index/KNNSettings.java | 21 +++++++ .../org/opensearch/knn/jni/FaissService.java | 10 +++- .../org/opensearch/knn/jni/PlatformUtils.java | 40 ++++++++++++- .../plugin-metadata/plugin-security.policy | 1 + .../opensearch/knn/jni/PlatformUtilTests.java | 58 +++++++++++++++++++ 14 files changed, 202 insertions(+), 28 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ae9ec2e56..f9dfffdbf 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -73,13 +73,17 @@ jobs: # switching the user, as OpenSearch cluster can only be started as root/Administrator on linux-deb/linux-rpm/windows-zip. run: | chown -R 1000:1000 `pwd` - if lscpu | grep -i avx2 + if lscpu | grep -i avx512f | grep -i avx512cd | grep -i avx512vl | grep -i avx512dq | grep -i avx512bw then - echo "avx2 available on system" + echo "avx512 available on system" su `id -un 1000` -c "whoami && java -version && ./gradlew build -Dnproc.count=`nproc`" + elif lscpu | grep -i avx2 + then + echo "avx2 available on system" + su `id -un 1000` -c "whoami && java -version && ./gradlew build -Dnproc.count=`nproc` -Davx512.enabled=false" else - echo "avx2 not available on system" - su `id -un 1000` -c "whoami && java -version && ./gradlew build -Dsimd.enabled=false -Dnproc.count=`nproc`" + echo "avx512 and avx2 not available on system" + su `id -un 1000` -c "whoami && java -version && ./gradlew build -Davx2.enabled=false -Davx512.enabled=false -Dnproc.count=`nproc`" fi @@ -126,7 +130,7 @@ jobs: ./gradlew build -Dnproc.count=3 else echo "avx2 not available on system" - ./gradlew build -Dsimd.enabled=false -Dnproc.count=3 + ./gradlew build -Davx2.enabled=false -Davx512.enabled=false -Dnproc.count=3 fi Build-k-NN-Windows: @@ -187,4 +191,4 @@ jobs: # TODO: Detect processor count and set the value of nproc.count - name: Run build run: | - ./gradlew.bat build -D'simd.enabled=false' -D'nproc.count=4' + ./gradlew.bat build -D'avx2.enabled=false' -D'avx512.enabled=false' -D'nproc.count=4' diff --git a/.github/workflows/backwards_compatibility_tests_workflow.yml b/.github/workflows/backwards_compatibility_tests_workflow.yml index ea534d284..a0e4530b1 100644 --- a/.github/workflows/backwards_compatibility_tests_workflow.yml +++ b/.github/workflows/backwards_compatibility_tests_workflow.yml @@ -105,9 +105,19 @@ jobs: - if: startsWith(matrix.os,'ubuntu') name: Run k-NN Restart-Upgrade BWC Tests from BWCVersion-${{ matrix.bwc_version }} to OpenSearch Version-${{ matrix.opensearch_version }} on Ubuntu run: | - echo "Running restart-upgrade backwards compatibility tests ..." - ./gradlew :qa:restart-upgrade:testRestartUpgrade -Dtests.bwc.version=$BWC_VERSION_RESTART_UPGRADE - + echo "Running restart-upgrade backwards compatibility tests ..." + if lscpu | grep -i avx512f | grep -i avx512cd | grep -i avx512vl | grep -i avx512dq | grep -i avx512bw + then + echo "avx512 available on system" + ./gradlew :qa:restart-upgrade:testRestartUpgrade -Dtests.bwc.version=$BWC_VERSION_RESTART_UPGRADE -Dnproc.count=`nproc` + elif lscpu | grep -i avx2 + then + echo "avx2 available on system" + ./gradlew :qa:restart-upgrade:testRestartUpgrade -Dtests.bwc.version=$BWC_VERSION_RESTART_UPGRADE -Dnproc.count=`nproc` -Davx512.enabled=false + else + echo "avx512 and avx2 not available on system" + ./gradlew :qa:restart-upgrade:testRestartUpgrade -Dtests.bwc.version=$BWC_VERSION_RESTART_UPGRADE -Davx2.enabled=false -Davx512.enabled=false -Dsimd.enabled=false -Dnproc.count=`nproc` + fi Rolling-Upgrade-BWCTests-k-NN: strategy: @@ -176,4 +186,15 @@ jobs: name: Run k-NN Rolling-Upgrade BWC Tests from BWCVersion-${{ matrix.bwc_version }} to OpenSearch Version-${{ matrix.opensearch_version }} on Ubuntu run: | echo "Running rolling-upgrade backwards compatibility tests ..." - ./gradlew :qa:rolling-upgrade:testRollingUpgrade -Dtests.bwc.version=$BWC_VERSION_ROLLING_UPGRADE + if lscpu | grep -i avx512f | grep -i avx512cd | grep -i avx512vl | grep -i avx512dq | grep -i avx512bw + then + echo "avx512 available on system" + ./gradlew :qa:rolling-upgrade:testRollingUpgrade -Dtests.bwc.version=$BWC_VERSION_ROLLING_UPGRADE -Dnproc.count=`nproc` + elif lscpu | grep -i avx2 + then + echo "avx2 available on system" + ./gradlew :qa:rolling-upgrade:testRollingUpgrade -Dtests.bwc.version=$BWC_VERSION_ROLLING_UPGRADE -Dnproc.count=`nproc` -Davx512.enabled=false + else + echo "avx512 and avx2 not available on system" + ./gradlew :qa:rolling-upgrade:testRollingUpgrade -Dtests.bwc.version=$BWC_VERSION_ROLLING_UPGRADE -Davx2.enabled=false -Davx512.enabled=false -Dsimd.enabled=false -Dnproc.count=`nproc` + fi diff --git a/.github/workflows/test_security.yml b/.github/workflows/test_security.yml index 2f8df8526..f3deb096e 100644 --- a/.github/workflows/test_security.yml +++ b/.github/workflows/test_security.yml @@ -70,4 +70,15 @@ jobs: # switching the user, as OpenSearch cluster can only be started as root/Administrator on linux-deb/linux-rpm/windows-zip. run: | chown -R 1000:1000 `pwd` - su `id -un 1000` -c "whoami && java -version && ./gradlew integTest -Dsecurity.enabled=true -Dsimd.enabled=true -Dnproc.count=`nproc`" + if lscpu | grep -i avx512f | grep -i avx512cd | grep -i avx512vl | grep -i avx512dq | grep -i avx512bw + then + echo "avx512 available on system" + su `id -un 1000` -c "whoami && java -version && ./gradlew build -Dnproc.count=`nproc`" + elif lscpu | grep -i avx2 + then + echo "avx2 available on system" + su `id -un 1000` -c "whoami && java -version && ./gradlew build -Dnproc.count=`nproc` -Davx512.enabled=false" + else + echo "avx512 and avx2 not available on system" + su `id -un 1000` -c "whoami && java -version && ./gradlew build -Davx2.enabled=false -Davx512.enabled=false -Dnproc.count=`nproc`" + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index fd7182e65..e6cb57821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased 2.x](https://github.com/opensearch-project/k-NN/compare/2.17...2.x) ### Features +* Add AVX512 support to k-NN for FAISS library [#2069](https://github.com/opensearch-project/k-NN/pull/2069) ### Enhancements * Add short circuit if no live docs are in segments [#2059](https://github.com/opensearch-project/k-NN/pull/2059) ### Bug Fixes diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index c2545c886..c652209f1 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -283,19 +283,22 @@ make -j 4 ### Enable SIMD Optimization SIMD(Single Instruction/Multiple Data) Optimization is enabled by default on Linux and Mac which boosts the performance -by enabling `AVX2` on `x86 architecture` and `NEON` on `ARM64 architecture` while building the Faiss library. But to enable SIMD, the underlying processor -should support this (AVX2 or NEON). It can be disabled by setting the parameter `simd.enabled` to `false`. As of now, it is not supported on Windows OS. +by enabling `AVX2` and `AVX512` on `x86 architecture` and `NEON` on `ARM64 architecture` where applicable while building the Faiss library. But to enable SIMD, +the underlying processor should support these capabilities (AVX512, AVX2 or NEON). It can be disabled by setting the parameter `avx2.enabled` to `false` and +`avx512.enabled` to `false`. If your processor supports `AVX512` or `AVX2`, they can be set by enabling the setting . By default, these values are enabled on +OpenSearch. Some exceptions: As of now, SIMD support is not supported on Windows OS, and AVX512 is not present on MAC systems due to hardware not supporting the +feature. ``` # While building OpenSearch k-NN -./gradlew build -Dsimd.enabled=true +./gradlew build -Davx2.enabled=true -Davx512.enabled=true # While running OpenSearch k-NN -./gradlew run -Dsimd.enabled=true +./gradlew run -Davx2.enabled=true -Davx512.enabled=true # While building the JNI libraries cd jni -cmake . -DSIMD_ENABLED=true +cmake . -DAVX2_ENABLED=true -DAVX512_ENABLED=true ``` ## Run OpenSearch k-NN diff --git a/build.gradle b/build.gradle index 983573990..f38d0c165 100644 --- a/build.gradle +++ b/build.gradle @@ -17,8 +17,9 @@ buildscript { version_qualifier = System.getProperty("build.version_qualifier", "") opensearch_group = "org.opensearch" isSnapshot = "true" == System.getProperty("build.snapshot", "true") - simd_enabled = System.getProperty("simd.enabled", "true") + avx2_enabled = System.getProperty("avx2.enabled", "true") nproc_count = System.getProperty("nproc.count", "1") + avx512_enabled = System.getProperty("avx512.enabled", "true") // This flag determines whether the CMake build system should apply a custom patch. It prevents build failures // when the cmakeJniLib task is run multiple times. If the build.lib.commit_patches is true, the CMake build // system skips applying the patch if the patches have been applied already. If build.lib.commit_patches is @@ -316,7 +317,8 @@ task cmakeJniLib(type:Exec) { args.add("cmake") args.add(".") args.add("-DKNN_PLUGIN_VERSION=${opensearch_version}") - args.add("-DSIMD_ENABLED=${simd_enabled}") + args.add("-DAVX2_ENABLED=${avx2_enabled}") + args.add("-DAVX512_ENABLED=${avx512_enabled}") args.add("-DCOMMIT_LIB_PATCHES=${commit_lib_patches}") args.add("-DAPPLY_LIB_PATCHES=${apply_lib_patches}") if (Os.isFamily(Os.FAMILY_WINDOWS)) { diff --git a/jni/cmake/init-faiss.cmake b/jni/cmake/init-faiss.cmake index 08f3ccc40..4492d9f45 100644 --- a/jni/cmake/init-faiss.cmake +++ b/jni/cmake/init-faiss.cmake @@ -81,13 +81,21 @@ set(BUILD_TESTING OFF) # Avoid building faiss tests set(FAISS_ENABLE_GPU OFF) set(FAISS_ENABLE_PYTHON OFF) -if(NOT DEFINED SIMD_ENABLED) - set(SIMD_ENABLED true) # set default value as true if the argument is not set +if(NOT DEFINED AVX2_ENABLED) + set(AVX2_ENABLED true) # set default value as true if the argument is not set endif() -if(${CMAKE_SYSTEM_NAME} STREQUAL Windows OR ${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64" OR ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm64" OR NOT ${SIMD_ENABLED}) +if(NOT DEFINED AVX512_ENABLED) + set(AVX512_ENABLED true) # set default value as true if the argument is not set +endif() + +if(${CMAKE_SYSTEM_NAME} STREQUAL Windows OR ${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64" OR ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm64" OR ( NOT AVX2_ENABLED AND NOT AVX512_ENABLED)) set(FAISS_OPT_LEVEL generic) # Keep optimization level as generic on Windows OS as it is not supported due to MINGW64 compiler issue. Also, on aarch64 avx2 is not supported. set(TARGET_LINK_FAISS_LIB faiss) +elseif(${CMAKE_SYSTEM_NAME} STREQUAL Linux AND AVX512_ENABLED) + set(FAISS_OPT_LEVEL avx512) # Keep optimization level as avx512 to improve performance on Linux. This is not present on mac systems, and presently not supported on Windows OS. + set(TARGET_LINK_FAISS_LIB faiss_avx512) + string(PREPEND LIB_EXT "_avx512") # Prepend "_avx512" to lib extension to create the library as "libopensearchknn_faiss_avx512.so" on linux else() set(FAISS_OPT_LEVEL avx2) # Keep optimization level as avx2 to improve performance on Linux and Mac. set(TARGET_LINK_FAISS_LIB faiss_avx2) diff --git a/scripts/build.sh b/scripts/build.sh index 12798633b..be7304ee5 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -118,13 +118,16 @@ fi # Build k-NN lib and plugin through gradle tasks cd $work_dir ./gradlew build --no-daemon --refresh-dependencies -x integTest -x test -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER -Dbuild.lib.commit_patches=false -./gradlew :buildJniLib -Dsimd.enabled=false -Dbuild.lib.commit_patches=false +./gradlew :buildJniLib -Davx512.enabled=false -Davx2.enabled=false -Dbuild.lib.commit_patches=false if [ "$PLATFORM" != "windows" ] && [ "$ARCHITECTURE" = "x64" ]; then echo "Building k-NN library after enabling AVX2" # Skip applying patches as patches were applied already from previous :buildJniLib task # If we apply patches again, it fails with conflict - ./gradlew :buildJniLib -Dsimd.enabled=true -Dbuild.lib.commit_patches=false -Dbuild.lib.apply_patches=false + ./gradlew :buildJniLib -Davx2.enabled=true -Davx512.enabled=false -Dbuild.lib.commit_patches=false -Dbuild.lib.apply_patches=false + + echo "Building k-NN library after enabling AVX512" + ./gradlew :buildJniLib -Davx512.enabled=true -Dbuild.lib.commit_patches=false -Dbuild.lib.apply_patches=false fi ./gradlew publishPluginZipPublicationToZipStagingRepository -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER diff --git a/src/main/java/org/opensearch/knn/common/KNNConstants.java b/src/main/java/org/opensearch/knn/common/KNNConstants.java index 7ba900671..16084499c 100644 --- a/src/main/java/org/opensearch/knn/common/KNNConstants.java +++ b/src/main/java/org/opensearch/knn/common/KNNConstants.java @@ -140,6 +140,7 @@ public class KNNConstants { private static final String JNI_LIBRARY_PREFIX = "opensearchknn_"; public static final String FAISS_JNI_LIBRARY_NAME = JNI_LIBRARY_PREFIX + FAISS_NAME; public static final String FAISS_AVX2_JNI_LIBRARY_NAME = JNI_LIBRARY_PREFIX + FAISS_NAME + "_avx2"; + public static final String FAISS_AVX512_JNI_LIBRARY_NAME = JNI_LIBRARY_PREFIX + FAISS_NAME + "_avx512"; public static final String NMSLIB_JNI_LIBRARY_NAME = JNI_LIBRARY_PREFIX + NMSLIB_NAME; public static final String COMMON_JNI_LIBRARY_NAME = JNI_LIBRARY_PREFIX + COMMONS_NAME; diff --git a/src/main/java/org/opensearch/knn/index/KNNSettings.java b/src/main/java/org/opensearch/knn/index/KNNSettings.java index 4da11a2ad..5fcc51bb5 100644 --- a/src/main/java/org/opensearch/knn/index/KNNSettings.java +++ b/src/main/java/org/opensearch/knn/index/KNNSettings.java @@ -14,6 +14,7 @@ import org.opensearch.client.Client; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.Booleans; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; @@ -86,11 +87,13 @@ public class KNNSettings { public static final String KNN_FAISS_AVX2_DISABLED = "knn.faiss.avx2.disabled"; public static final String QUANTIZATION_STATE_CACHE_SIZE_LIMIT = "knn.quantization.cache.size.limit"; public static final String QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES = "knn.quantization.cache.expiry.minutes"; + public static final String KNN_FAISS_AVX512_DISABLED = "knn.faiss.avx512.disabled"; /** * Default setting values */ public static final boolean KNN_DEFAULT_FAISS_AVX2_DISABLED_VALUE = false; + public static final boolean KNN_DEFAULT_FAISS_AVX512_DISABLED_VALUE = false; public static final String INDEX_KNN_DEFAULT_SPACE_TYPE = "l2"; public static final String INDEX_KNN_DEFAULT_SPACE_TYPE_FOR_BINARY = "hamming"; public static final Integer INDEX_KNN_DEFAULT_ALGO_PARAM_M = 16; @@ -302,6 +305,12 @@ public class KNNSettings { Dynamic ); + public static final Setting KNN_FAISS_AVX512_DISABLED_SETTING = Setting.boolSetting( + KNN_FAISS_AVX512_DISABLED, + KNN_DEFAULT_FAISS_AVX512_DISABLED_VALUE, + NodeScope + ); + /** * Dynamic settings */ @@ -429,6 +438,10 @@ private Setting getSetting(String key) { return KNN_FAISS_AVX2_DISABLED_SETTING; } + if (KNN_FAISS_AVX512_DISABLED.equals(key)) { + return KNN_FAISS_AVX512_DISABLED_SETTING; + } + if (KNN_VECTOR_STREAMING_MEMORY_LIMIT_IN_MB.equals(key)) { return KNN_VECTOR_STREAMING_MEMORY_LIMIT_PCT_SETTING; } @@ -460,6 +473,7 @@ public List> getSettings() { ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_SETTING, KNN_FAISS_AVX2_DISABLED_SETTING, KNN_VECTOR_STREAMING_MEMORY_LIMIT_PCT_SETTING, + KNN_FAISS_AVX512_DISABLED_SETTING, QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING ); @@ -499,6 +513,13 @@ public static boolean isFaissAVX2Disabled() { } } + public static boolean isFaissAVX512Disabled() { + return Booleans.parseBoolean( + KNNSettings.state().getSettingValue(KNNSettings.KNN_FAISS_AVX512_DISABLED).toString(), + KNN_DEFAULT_FAISS_AVX512_DISABLED_VALUE + ); + } + public static Integer getFilteredExactSearchThreshold(final String indexName) { return KNNSettings.state().clusterService.state() .getMetadata() diff --git a/src/main/java/org/opensearch/knn/jni/FaissService.java b/src/main/java/org/opensearch/knn/jni/FaissService.java index 037171b98..4bceed015 100644 --- a/src/main/java/org/opensearch/knn/jni/FaissService.java +++ b/src/main/java/org/opensearch/knn/jni/FaissService.java @@ -20,7 +20,9 @@ import java.util.Map; import static org.opensearch.knn.index.KNNSettings.isFaissAVX2Disabled; +import static org.opensearch.knn.index.KNNSettings.isFaissAVX512Disabled; import static org.opensearch.knn.jni.PlatformUtils.isAVX2SupportedBySystem; +import static org.opensearch.knn.jni.PlatformUtils.isAVX512SupportedBySystem;; /** * Service to interact with faiss jni layer. Class dependencies should be minimal @@ -35,9 +37,11 @@ class FaissService { static { AccessController.doPrivileged((PrivilegedAction) () -> { - // Even if the underlying system supports AVX2, users can override and disable it by using the - // 'knn.faiss.avx2.disabled' setting by setting it to true in the opensearch.yml configuration - if (!isFaissAVX2Disabled() && isAVX2SupportedBySystem()) { + // Even if the underlying system supports AVX512 and AVX2, users can override and disable it by setting + // 'knn.faiss.avx2.disabled' or 'knn.faiss.avx512.disabled' to true in the opensearch.yml configuration + if (!isFaissAVX512Disabled() && isAVX512SupportedBySystem()) { + System.loadLibrary(KNNConstants.FAISS_AVX512_JNI_LIBRARY_NAME); + } else if (!isFaissAVX2Disabled() && isAVX2SupportedBySystem()) { System.loadLibrary(KNNConstants.FAISS_AVX2_JNI_LIBRARY_NAME); } else { System.loadLibrary(KNNConstants.FAISS_JNI_LIBRARY_NAME); diff --git a/src/main/java/org/opensearch/knn/jni/PlatformUtils.java b/src/main/java/org/opensearch/knn/jni/PlatformUtils.java index 8a5549dec..445862f24 100644 --- a/src/main/java/org/opensearch/knn/jni/PlatformUtils.java +++ b/src/main/java/org/opensearch/knn/jni/PlatformUtils.java @@ -20,8 +20,11 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.security.AccessController; +import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.Arrays; import java.util.Locale; +import java.util.stream.Stream; public class PlatformUtils { @@ -37,7 +40,7 @@ public class PlatformUtils { * the flags contains 'avx2' and return true if it exists else false. */ public static boolean isAVX2SupportedBySystem() { - if (!Platform.isIntel()) { + if (!Platform.isIntel() || Platform.isWindows()) { return false; } @@ -58,7 +61,6 @@ public static boolean isAVX2SupportedBySystem() { } } else if (Platform.isLinux()) { - // The "/proc/cpuinfo" is a virtual file which identifies and provides the processor details used // by system. This info contains "flags" for each processor which determines the qualities of that processor // and it's ability to process different instruction sets like mmx, avx, avx2 and so on. @@ -80,4 +82,38 @@ public static boolean isAVX2SupportedBySystem() { } return false; } + + public static boolean isAVX512SupportedBySystem() { + + if (!Platform.isIntel() || Platform.isMac() || Platform.isWindows()) { + return false; + } + + if (Platform.isLinux()) { + // The "/proc/cpuinfo" is a virtual file which identifies and provides the processor details used + // by system. This info contains "flags" for each processor which determines the qualities of that processor + // and it's ability to process different instruction sets like mmx, avx, avx2, avx512 and so on. + // https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/s2-proc-cpuinfo + // Here, we are trying to read the details of all processors used by system and find if any of the processor + // supports AVX512 instructions supported by faiss. + String fileName = "/proc/cpuinfo"; + + // AVX512 has multiple flags, which control various features. k-nn requires the same set of flags as faiss to compile + // using avx512. Please update these if faiss updates their compilation instructions in the future. + // https://github.com/facebookresearch/faiss/blob/main/faiss/CMakeLists.txt + String[] avx512 = { "avx512f", "avx512cd", "avx512vl", "avx512dq", "avx512bw" }; + + try { + return AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + Stream linestream = Files.lines(Paths.get(fileName)); + String flags = linestream.filter(line -> line.startsWith("flags")).findFirst().orElse(""); + return Arrays.stream(avx512).allMatch(flags::contains); + }); + + } catch (PrivilegedActionException e) { + logger.error("[KNN] Error reading file [{}]. [{}]", fileName, e.getMessage(), e); + } + } + return false; + } } diff --git a/src/main/plugin-metadata/plugin-security.policy b/src/main/plugin-metadata/plugin-security.policy index d5ab0be21..ed329740f 100644 --- a/src/main/plugin-metadata/plugin-security.policy +++ b/src/main/plugin-metadata/plugin-security.policy @@ -3,6 +3,7 @@ grant { permission java.lang.RuntimePermission "loadLibrary.opensearchknn_faiss"; permission java.lang.RuntimePermission "loadLibrary.opensearchknn_common"; permission java.lang.RuntimePermission "loadLibrary.opensearchknn_faiss_avx2"; + permission java.lang.RuntimePermission "loadLibrary.opensearchknn_faiss_avx512"; permission java.net.SocketPermission "*", "connect,resolve"; permission java.lang.RuntimePermission "accessDeclaredMembers"; permission java.io.FilePermission "/proc/cpuinfo", "read"; diff --git a/src/test/java/org/opensearch/knn/jni/PlatformUtilTests.java b/src/test/java/org/opensearch/knn/jni/PlatformUtilTests.java index 7816505de..19c0abb07 100644 --- a/src/test/java/org/opensearch/knn/jni/PlatformUtilTests.java +++ b/src/test/java/org/opensearch/knn/jni/PlatformUtilTests.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.mockStatic; import static org.opensearch.knn.jni.PlatformUtils.isAVX2SupportedBySystem; +import static org.opensearch.knn.jni.PlatformUtils.isAVX512SupportedBySystem; public class PlatformUtilTests extends KNNTestCase { public static final String MAC_CPU_FEATURES = "machdep.cpu.leaf7_features"; @@ -124,4 +125,61 @@ public void testIsAVX2SupportedBySystem_platformIsLinux_throwsExceptionReturnsFa } + // AVX512 tests + + public void testIsAVX512SupportedBySystem_platformIsNotIntel_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(false); + assertFalse(isAVX512SupportedBySystem()); + } + } + + public void testIsAVX512SupportedBySystem_platformIsMac_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isMac).thenReturn(false); + assertFalse(isAVX512SupportedBySystem()); + } + } + + public void testIsAVX512SupportedBySystem_platformIsIntelMac_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isMac).thenReturn(true); + assertFalse(isAVX512SupportedBySystem()); + } + } + + public void testIsAVX512SupportedBySystem_platformIsIntelWithOSAsWindows_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isWindows).thenReturn(true); + assertFalse(isAVX512SupportedBySystem()); + } + } + + public void testIsAVX512SupportedBySystem_platformIsLinuxAllAVX512FlagsPresent_returnsTrue() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isLinux).thenReturn(true); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + mockedFiles.when(() -> Files.lines(Paths.get(LINUX_PROC_CPU_INFO))) + .thenReturn(Stream.of("flags: AVX2 avx512f avx512cd avx512vl avx512dq avx512bw", "dummy string")); + assertTrue(isAVX512SupportedBySystem()); + } + } + } + + public void testIsAVX512SupportedBySystem_platformIsLinuxSomeAVX512FlagsPresent_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isLinux).thenReturn(true); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + mockedFiles.when(() -> Files.lines(Paths.get(LINUX_PROC_CPU_INFO))) + .thenReturn(Stream.of("flags: AVX2 avx512vl avx512dq avx512bw avx512vbmi umip pku ospke avx512_vbmi2", "dummy string")); + assertFalse(isAVX512SupportedBySystem()); + } + } + } } From e33afa5de5f8658ad7fbe71125707436e81cc5b8 Mon Sep 17 00:00:00 2001 From: Tejas Shah Date: Mon, 23 Sep 2024 15:33:32 -0700 Subject: [PATCH 21/59] Makes sure KNNVectorValues aren't recreated unnecessarily when quantization isn't needed (#2133) Signed-off-by: Tejas Shah --- CHANGELOG.md | 1 + .../NativeEngines990KnnVectorsWriter.java | 63 +++++++++++-------- ...eEngines990KnnVectorsWriterFlushTests.java | 11 ++++ ...eEngines990KnnVectorsWriterMergeTests.java | 9 +++ 4 files changed, 59 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6cb57821..1adcb2303 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Documentation ### Maintenance ### Refactoring +* Does not create additional KNNVectorValues in NativeEngines990KNNVectorWriter when quantization is not needed [#2133](https://github.com/opensearch-project/k-NN/pull/2133) ## [Unreleased 2.x](https://github.com/opensearch-project/k-NN/compare/2.17...2.x) ### Features diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java index 3f32003ac..23cd2a4de 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import static org.opensearch.knn.common.FieldInfoExtractor.extractVectorDataType; import static org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory.getVectorValues; @@ -82,19 +83,19 @@ public void flush(int maxDoc, final Sorter.DocMap sortMap) throws IOException { for (final NativeEngineFieldVectorsWriter field : fields) { final FieldInfo fieldInfo = field.getFieldInfo(); final VectorDataType vectorDataType = extractVectorDataType(fieldInfo); - int totalLiveDocs = getLiveDocs(getVectorValues(vectorDataType, field.getDocsWithField(), field.getVectors())); + int totalLiveDocs = field.getVectors().size(); if (totalLiveDocs > 0) { - KNNVectorValues knnVectorValues = getVectorValues(vectorDataType, field.getDocsWithField(), field.getVectors()); - - final QuantizationState quantizationState = train(field.getFieldInfo(), knnVectorValues, totalLiveDocs); + final Supplier> knnVectorValuesSupplier = () -> getVectorValues( + vectorDataType, + field.getDocsWithField(), + field.getVectors() + ); + final QuantizationState quantizationState = train(field.getFieldInfo(), knnVectorValuesSupplier, totalLiveDocs); final NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); - - knnVectorValues = getVectorValues(vectorDataType, field.getDocsWithField(), field.getVectors()); + final KNNVectorValues knnVectorValues = knnVectorValuesSupplier.get(); StopWatch stopWatch = new StopWatch().start(); - writer.flushIndex(knnVectorValues, totalLiveDocs); - long time_in_millis = stopWatch.stop().totalTime().millis(); KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.incrementBy(time_in_millis); log.debug("Flush took {} ms for vector field [{}]", time_in_millis, fieldInfo.getName()); @@ -110,17 +111,20 @@ public void mergeOneField(final FieldInfo fieldInfo, final MergeState mergeState flatVectorsWriter.mergeOneField(fieldInfo, mergeState); final VectorDataType vectorDataType = extractVectorDataType(fieldInfo); - int totalLiveDocs = getLiveDocs(getKNNVectorValuesForMerge(vectorDataType, fieldInfo, mergeState)); + final Supplier> knnVectorValuesSupplier = () -> getKNNVectorValuesForMerge( + vectorDataType, + fieldInfo, + mergeState + ); + int totalLiveDocs = getLiveDocs(knnVectorValuesSupplier.get()); if (totalLiveDocs == 0) { log.debug("[Merge] No live docs for field {}", fieldInfo.getName()); return; } - KNNVectorValues knnVectorValues = getKNNVectorValuesForMerge(vectorDataType, fieldInfo, mergeState); - final QuantizationState quantizationState = train(fieldInfo, knnVectorValues, totalLiveDocs); + final QuantizationState quantizationState = train(fieldInfo, knnVectorValuesSupplier, totalLiveDocs); final NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); - - knnVectorValues = getKNNVectorValuesForMerge(vectorDataType, fieldInfo, mergeState); + final KNNVectorValues knnVectorValues = knnVectorValuesSupplier.get(); StopWatch stopWatch = new StopWatch().start(); @@ -191,27 +195,36 @@ private KNNVectorValues getKNNVectorValuesForMerge( final VectorDataType vectorDataType, final FieldInfo fieldInfo, final MergeState mergeState - ) throws IOException { - switch (fieldInfo.getVectorEncoding()) { - case FLOAT32: - FloatVectorValues mergedFloats = MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState); - return getVectorValues(vectorDataType, mergedFloats); - case BYTE: - ByteVectorValues mergedBytes = MergedVectorValues.mergeByteVectorValues(fieldInfo, mergeState); - return getVectorValues(vectorDataType, mergedBytes); - default: - throw new IllegalStateException("Unsupported vector encoding [" + fieldInfo.getVectorEncoding() + "]"); + ) { + try { + switch (fieldInfo.getVectorEncoding()) { + case FLOAT32: + FloatVectorValues mergedFloats = MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState); + return getVectorValues(vectorDataType, mergedFloats); + case BYTE: + ByteVectorValues mergedBytes = MergedVectorValues.mergeByteVectorValues(fieldInfo, mergeState); + return getVectorValues(vectorDataType, mergedBytes); + default: + throw new IllegalStateException("Unsupported vector encoding [" + fieldInfo.getVectorEncoding() + "]"); + } + } catch (final IOException e) { + log.error("Unable to merge vectors for field [{}]", fieldInfo.getName(), e); + throw new IllegalStateException("Unable to merge vectors for field [" + fieldInfo.getName() + "]", e); } } - private QuantizationState train(final FieldInfo fieldInfo, final KNNVectorValues knnVectorValues, final int totalLiveDocs) - throws IOException { + private QuantizationState train( + final FieldInfo fieldInfo, + final Supplier> knnVectorValuesSupplier, + final int totalLiveDocs + ) throws IOException { final QuantizationService quantizationService = QuantizationService.getInstance(); final QuantizationParams quantizationParams = quantizationService.getQuantizationParams(fieldInfo); QuantizationState quantizationState = null; if (quantizationParams != null && totalLiveDocs > 0) { initQuantizationStateWriterIfNecessary(); + KNNVectorValues knnVectorValues = knnVectorValuesSupplier.get(); quantizationState = quantizationService.train(quantizationParams, knnVectorValues, totalLiveDocs); quantizationStateWriter.writeState(fieldInfo.getFieldNumber(), quantizationState); } diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java index ad72f5b24..dbb564908 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java @@ -44,6 +44,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockConstruction; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -176,6 +177,11 @@ public void testFlush() { throw new RuntimeException(e); } }); + + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(any(VectorDataType.class), any(DocsWithFieldSet.class), any()), + times(expectedVectorValues.size()) + ); } } @@ -264,6 +270,11 @@ public void testFlush_WithQuantization() { throw new RuntimeException(e); } }); + + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(any(VectorDataType.class), any(DocsWithFieldSet.class), any()), + times(expectedVectorValues.size() * 2) + ); } } diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java index 440e8bbc5..41940c4d4 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java @@ -45,6 +45,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockConstruction; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @@ -144,6 +145,10 @@ public void testMerge() { if (!mergedVectors.isEmpty()) { verify(nativeIndexWriter).mergeIndex(knnVectorValues, mergedVectors.size()); assertTrue(KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.getValue() > 0L); + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues), + times(2) + ); } else { verifyNoInteractions(nativeIndexWriter); } @@ -211,6 +216,10 @@ public void testMerge_WithQuantization() { verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeState(0, quantizationState); verify(nativeIndexWriter).mergeIndex(knnVectorValues, mergedVectors.size()); assertTrue(KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.getValue() > 0L); + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues), + times(3) + ); } else { assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); verifyNoInteractions(nativeIndexWriter); From 189562e89e5205381a8641d123627bec701cadbf Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Tue, 24 Sep 2024 15:25:50 -0700 Subject: [PATCH 22/59] Add space_type as acceptable field in train API (#2141) Signed-off-by: Ryan Bogan --- .../knn/plugin/rest/RestTrainModelHandler.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java b/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java index 71f7201de..4380310c3 100644 --- a/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java +++ b/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java @@ -126,11 +126,12 @@ private TrainingModelRequest createTransportRequest(RestRequest restRequest) thr mode = parser.text(); } else if (KNNConstants.COMPRESSION_LEVEL_PARAMETER.equals(fieldName) && ensureNotSet(fieldName, compressionLevel)) { compressionLevel = parser.text(); - } else if (KNNConstants.SPACE_TYPE.equals(fieldName) && ensureSpaceTypeNotSet(topLevelSpaceType)) { - topLevelSpaceType = SpaceType.getSpace(parser.text()); - } else { - throw new IllegalArgumentException("Unable to parse token. \"" + fieldName + "\" is not a valid " + "parameter."); - } + } else if ((KNNConstants.SPACE_TYPE.equals(fieldName) || KNNConstants.TOP_LEVEL_PARAMETER_SPACE_TYPE.equals(fieldName)) + && ensureSpaceTypeNotSet(topLevelSpaceType)) { + topLevelSpaceType = SpaceType.getSpace(parser.text()); + } else { + throw new IllegalArgumentException("Unable to parse token. \"" + fieldName + "\" is not a valid " + "parameter."); + } } ensureAtleastOneSet(KNN_METHOD, knnMethodContext, MODE_PARAMETER, mode, COMPRESSION_LEVEL_PARAMETER, compressionLevel); From a1227eafe1f539cfb230468c5213e02a015cb8dc Mon Sep 17 00:00:00 2001 From: Vikasht34 Date: Thu, 26 Sep 2024 12:31:23 -0700 Subject: [PATCH 23/59] Update Default Rescore Context based on Dimesion (#2149) --- CHANGELOG.md | 1 + .../knn/index/mapper/CompressionLevel.java | 29 ++++++++- .../knn/index/mapper/KNNVectorFieldType.java | 6 +- .../index/query/rescore/RescoreContext.java | 2 + .../index/mapper/CompressionLevelTests.java | 61 +++++++++++++++---- 5 files changed, 85 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1adcb2303..32711d473 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Add AVX512 support to k-NN for FAISS library [#2069](https://github.com/opensearch-project/k-NN/pull/2069) ### Enhancements * Add short circuit if no live docs are in segments [#2059](https://github.com/opensearch-project/k-NN/pull/2059) +* Update Default Rescore Context based on Dimension [#2149](https://github.com/opensearch-project/k-NN/pull/2149) ### Bug Fixes ### Infrastructure ### Documentation diff --git a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java index 0709239cf..3e1b47db7 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java +++ b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java @@ -94,9 +94,34 @@ public static boolean isConfigured(CompressionLevel compressionLevel) { return compressionLevel != null && compressionLevel != NOT_CONFIGURED; } - public RescoreContext getDefaultRescoreContext(Mode mode) { + /** + * Returns the appropriate {@link RescoreContext} based on the given {@code mode} and {@code dimension}. + * + *

    If the {@code mode} is present in the valid {@code modesForRescore} set, the method checks the value of + * {@code dimension}: + *

      + *
    • If {@code dimension} is less than or equal to 1000, it returns a {@link RescoreContext} with an + * oversample factor of 5.0f.
    • + *
    • If {@code dimension} is greater than 1000, it returns the default {@link RescoreContext} associated with + * the {@link CompressionLevel}. If no default is set, it falls back to {@link RescoreContext#getDefault()}.
    • + *
    + * If the {@code mode} is not valid, the method returns {@code null}. + * + * @param mode The {@link Mode} for which to retrieve the {@link RescoreContext}. + * @param dimension The dimensional value that determines the {@link RescoreContext} behavior. + * @return A {@link RescoreContext} with an oversample factor of 5.0f if {@code dimension} is less than + * or equal to 1000, the default {@link RescoreContext} if greater, or {@code null} if the mode + * is invalid. + */ + public RescoreContext getDefaultRescoreContext(Mode mode, int dimension) { if (modesForRescore.contains(mode)) { - return defaultRescoreContext; + // Adjust RescoreContext based on dimension + if (dimension <= RescoreContext.DIMENSION_THRESHOLD) { + // For dimensions <= 1000, return a RescoreContext with 5.0f oversample factor + return RescoreContext.builder().oversampleFactor(RescoreContext.OVERSAMPLE_FACTOR_BELOW_DIMENSION_THRESHOLD).build(); + } else { + return defaultRescoreContext; + } } return null; } diff --git a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldType.java b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldType.java index e684ba4f1..a0832c1d0 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldType.java +++ b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldType.java @@ -93,6 +93,10 @@ public RescoreContext resolveRescoreContext(RescoreContext userProvidedContext) if (userProvidedContext != null) { return userProvidedContext; } - return getKnnMappingConfig().getCompressionLevel().getDefaultRescoreContext(getKnnMappingConfig().getMode()); + KNNMappingConfig knnMappingConfig = getKnnMappingConfig(); + int dimension = knnMappingConfig.getDimension(); + CompressionLevel compressionLevel = knnMappingConfig.getCompressionLevel(); + Mode mode = knnMappingConfig.getMode(); + return compressionLevel.getDefaultRescoreContext(mode, dimension); } } diff --git a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java index ea4e92215..51d4e491c 100644 --- a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java +++ b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java @@ -21,6 +21,8 @@ public final class RescoreContext { public static final float MIN_OVERSAMPLE_FACTOR = 1.0f; public static final int MAX_FIRST_PASS_RESULTS = 10000; + public static final int DIMENSION_THRESHOLD = 1000; + public static final float OVERSAMPLE_FACTOR_BELOW_DIMENSION_THRESHOLD = 5.0f; // Todo:- We will improve this in upcoming releases public static final int MIN_FIRST_PASS_RESULTS = 100; diff --git a/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java b/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java index 9eb302b13..cc70d4c2d 100644 --- a/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java +++ b/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java @@ -44,26 +44,65 @@ public void testIsConfigured() { public void testGetDefaultRescoreContext() { // Test rescore context for ON_DISK mode Mode mode = Mode.ON_DISK; + int belowThresholdDimension = 500; // A dimension below the threshold + int aboveThresholdDimension = 1500; // A dimension above the threshold - // x32 should have RescoreContext with an oversample factor of 3.0f - RescoreContext rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode); + // x32 with dimension <= 1000 should have an oversample factor of 5.0f + RescoreContext rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNotNull(rescoreContext); + assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // x32 with dimension > 1000 should have an oversample factor of 3.0f + rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, aboveThresholdDimension); assertNotNull(rescoreContext); assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); - // x16 should have RescoreContext with an oversample factor of 3.0f - rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode); + // x16 with dimension <= 1000 should have an oversample factor of 5.0f + rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNotNull(rescoreContext); + assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // x16 with dimension > 1000 should have an oversample factor of 3.0f + rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, aboveThresholdDimension); assertNotNull(rescoreContext); assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); - // x8 should have RescoreContext with an oversample factor of 2.0f - rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode); + // x8 with dimension <= 1000 should have an oversample factor of 5.0f + rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNotNull(rescoreContext); + assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // x8 with dimension > 1000 should have an oversample factor of 2.0f + rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, aboveThresholdDimension); assertNotNull(rescoreContext); assertEquals(2.0f, rescoreContext.getOversampleFactor(), 0.0f); - // Other compression levels should not have a RescoreContext for ON_DISK mode - assertNull(CompressionLevel.x4.getDefaultRescoreContext(mode)); - assertNull(CompressionLevel.x2.getDefaultRescoreContext(mode)); - assertNull(CompressionLevel.x1.getDefaultRescoreContext(mode)); - assertNull(CompressionLevel.NOT_CONFIGURED.getDefaultRescoreContext(mode)); + // x4 with dimension <= 1000 should have an oversample factor of 5.0f (though it doesn't have its own RescoreContext) + rescoreContext = CompressionLevel.x4.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNull(rescoreContext); + // x4 with dimension > 1000 should return null (no RescoreContext is configured for x4) + rescoreContext = CompressionLevel.x4.getDefaultRescoreContext(mode, aboveThresholdDimension); + assertNull(rescoreContext); + + // Other compression levels should behave similarly with respect to dimension + + rescoreContext = CompressionLevel.x2.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNull(rescoreContext); + + // x2 with dimension > 1000 should return null + rescoreContext = CompressionLevel.x2.getDefaultRescoreContext(mode, aboveThresholdDimension); + assertNull(rescoreContext); + + rescoreContext = CompressionLevel.x1.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNull(rescoreContext); + + // x1 with dimension > 1000 should return null + rescoreContext = CompressionLevel.x1.getDefaultRescoreContext(mode, aboveThresholdDimension); + assertNull(rescoreContext); + + // NOT_CONFIGURED with dimension <= 1000 should return a RescoreContext with an oversample factor of 5.0f + rescoreContext = CompressionLevel.NOT_CONFIGURED.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNull(rescoreContext); + } } From 329fc5754c4cfb2458b065489cfce0c732e9ce4d Mon Sep 17 00:00:00 2001 From: Vijayan Balasubramanian Date: Thu, 26 Sep 2024 13:37:19 -0700 Subject: [PATCH 24/59] KNN80DocValues should only be considered for BinaryDocValues fields (#2147) Consider adding files from fields that has BinaryDocValues and doesn't have filter values in producer. Signed-off-by: Vijayan Balasubramanian --- CHANGELOG.md | 1 + .../KNN80Codec/KNN80DocValuesProducer.java | 9 ++++ .../opensearch/knn/index/OpenSearchIT.java | 4 ++ .../KNN80DocValuesProducerTests.java | 54 +++++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32711d473..7fc1bbd08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Add short circuit if no live docs are in segments [#2059](https://github.com/opensearch-project/k-NN/pull/2059) * Update Default Rescore Context based on Dimension [#2149](https://github.com/opensearch-project/k-NN/pull/2149) ### Bug Fixes +* KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) ### Infrastructure ### Documentation ### Maintenance diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java index 0cfd9c668..b78566f2e 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java @@ -15,6 +15,7 @@ import lombok.extern.log4j.Log4j2; import org.apache.lucene.codecs.DocValuesProducer; import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.SegmentReadState; @@ -69,6 +70,13 @@ public KNN80DocValuesProducer(DocValuesProducer delegate, SegmentReadState state if (!field.attributes().containsKey(KNN_FIELD)) { continue; } + // Only segments that contains BinaryDocValues and doesn't have vector values should be considered. + // By default, we don't create BinaryDocValues for knn field anymore. However, users can set doc_values = true + // to create binary doc values explicitly like any other field. Hence, we only want to include fields + // where approximate search is possible only by BinaryDocValues. + if (field.getDocValuesType() != DocValuesType.BINARY || field.hasVectorValues() == true) { + continue; + } // Only Native Engine put into indexPathMap KNNEngine knnEngine = getNativeKNNEngine(field); if (knnEngine == null) { @@ -77,6 +85,7 @@ public KNN80DocValuesProducer(DocValuesProducer delegate, SegmentReadState state List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), field.name, state.segmentInfo); Path indexPath = PathUtils.get(directoryPath, engineFiles.get(0)); indexPathMap.putIfAbsent(field.getName(), indexPath.toString()); + } } diff --git a/src/test/java/org/opensearch/knn/index/OpenSearchIT.java b/src/test/java/org/opensearch/knn/index/OpenSearchIT.java index ded3a827c..0bb7947ba 100644 --- a/src/test/java/org/opensearch/knn/index/OpenSearchIT.java +++ b/src/test/java/org/opensearch/knn/index/OpenSearchIT.java @@ -16,6 +16,7 @@ import java.util.Locale; import lombok.SneakyThrows; import org.junit.BeforeClass; +import org.junit.Ignore; import org.opensearch.knn.KNNRestTestCase; import org.opensearch.knn.KNNResult; import org.apache.hc.core5.http.io.entity.EntityUtils; @@ -483,6 +484,9 @@ public void testIndexingVectorValidation_updateVectorWithNull() throws Exception assertArrayEquals(vectorForDocumentOne, vectorRestoreInitialValue); } + // This doesn't work since indices that are created post 2.17 don't evict by default when indices are closed or deleted. + // Enable this PR once https://github.com/opensearch-project/k-NN/issues/2148 is resolved. + @Ignore public void testCacheClear_whenCloseIndex() throws Exception { String indexName = "test-index-1"; KNNEngine knnEngine1 = KNNEngine.NMSLIB; diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java index b9a85bbcc..ccceee62b 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java @@ -9,6 +9,7 @@ import org.apache.lucene.codecs.Codec; import org.apache.lucene.codecs.DocValuesFormat; import org.apache.lucene.codecs.DocValuesProducer; +import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.FieldInfos; import org.apache.lucene.index.SegmentInfo; @@ -127,4 +128,57 @@ public void testProduceKNNBinaryField_fromCodec_nmslibCurrent() throws IOExcepti assertTrue(path.contains(segmentFiles.get(0))); } + public void testProduceKNNBinaryField_whenFieldHasNonBinaryDocValues_thenSkipThoseField() throws IOException { + // Set information about the segment and the fields + DocValuesFormat mockDocValuesFormat = mock(DocValuesFormat.class); + Codec mockDelegateCodec = mock(Codec.class); + DocValuesProducer mockDocValuesProducer = mock(DocValuesProducer.class); + when(mockDelegateCodec.docValuesFormat()).thenReturn(mockDocValuesFormat); + when(mockDocValuesFormat.fieldsProducer(any())).thenReturn(mockDocValuesProducer); + when(mockDocValuesFormat.getName()).thenReturn("mockDocValuesFormat"); + Codec codec = new KNN87Codec(mockDelegateCodec); + + String segmentName = "_test"; + int docsInSegment = 100; + String fieldName1 = String.format("test_field1%s", randomAlphaOfLength(4)); + String fieldName2 = String.format("test_field2%s", randomAlphaOfLength(4)); + List segmentFiles = Arrays.asList( + String.format("%s_2011_%s%s", segmentName, fieldName1, KNNEngine.NMSLIB.getExtension()), + String.format("%s_165_%s%s", segmentName, fieldName2, KNNEngine.FAISS.getExtension()) + ); + + KNNEngine knnEngine = KNNEngine.NMSLIB; + SpaceType spaceType = SpaceType.COSINESIMIL; + SegmentInfo segmentInfo = KNNCodecTestUtil.segmentInfoBuilder() + .directory(directory) + .segmentName(segmentName) + .docsInSegment(docsInSegment) + .codec(codec) + .build(); + + for (String name : segmentFiles) { + IndexOutput indexOutput = directory.createOutput(name, IOContext.DEFAULT); + indexOutput.close(); + } + segmentInfo.setFiles(segmentFiles); + + FieldInfo[] fieldInfoArray = new FieldInfo[] { + KNNCodecTestUtil.FieldInfoBuilder.builder(fieldName1) + .addAttribute(KNNVectorFieldMapper.KNN_FIELD, "true") + .addAttribute(KNNConstants.KNN_ENGINE, knnEngine.getName()) + .addAttribute(KNNConstants.SPACE_TYPE, spaceType.getValue()) + .docValuesType(DocValuesType.NONE) + .dvGen(-1) + .build() }; + + FieldInfos fieldInfos = new FieldInfos(fieldInfoArray); + SegmentReadState state = new SegmentReadState(directory, segmentInfo, fieldInfos, IOContext.DEFAULT); + + DocValuesFormat docValuesFormat = codec.docValuesFormat(); + assertTrue(docValuesFormat instanceof KNN80DocValuesFormat); + DocValuesProducer producer = docValuesFormat.fieldsProducer(state); + assertTrue(producer instanceof KNN80DocValuesProducer); + assertEquals(0, ((KNN80DocValuesProducer) producer).getOpenedIndexPath().size()); + } + } From 23b95e7571f28e36725fd8944c11c25a50253f14 Mon Sep 17 00:00:00 2001 From: Naveen Tatikonda Date: Fri, 27 Sep 2024 10:51:10 -0500 Subject: [PATCH 25/59] Add Release Notes for 2.17.1.0 (#2154) --- CHANGELOG.md | 1 - release-notes/opensearch-knn.release-notes-2.17.1.0.md | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 release-notes/opensearch-knn.release-notes-2.17.1.0.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fc1bbd08..a270ec9c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased 3.0](https://github.com/opensearch-project/k-NN/compare/2.x...HEAD) ### Features ### Enhancements -* Adds concurrent segment search support for mode auto [#2111](https://github.com/opensearch-project/k-NN/pull/2111) ### Bug Fixes * Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) ### Infrastructure diff --git a/release-notes/opensearch-knn.release-notes-2.17.1.0.md b/release-notes/opensearch-knn.release-notes-2.17.1.0.md new file mode 100644 index 000000000..0af275a5b --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.17.1.0.md @@ -0,0 +1,8 @@ +## Version 2.17.1.0 Release Notes + +Compatible with OpenSearch 2.17.1 + +### Enhancements +* Adds concurrent segment search support for mode auto [#2111](https://github.com/opensearch-project/k-NN/pull/2111) +### Bug Fixes +* Change min oversample to 1 [#2117](https://github.com/opensearch-project/k-NN/pull/2117) From eba9d98f8fbc7c107b289ea9019f42f12045217d Mon Sep 17 00:00:00 2001 From: Doo Yong Kim <0ctopus13prime@gmail.com> Date: Fri, 27 Sep 2024 12:39:21 -0700 Subject: [PATCH 26/59] Added NMSLIB patched allowing load/write APIs with a stream object. (#2144) Signed-off-by: Dooyong Kim Co-authored-by: Dooyong Kim --- jni/cmake/init-nmslib.cmake | 1 + ...is-using-stream-to-load-save-in-Hnsw.patch | 93 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 jni/patches/nmslib/0003-Adding-two-apis-using-stream-to-load-save-in-Hnsw.patch diff --git a/jni/cmake/init-nmslib.cmake b/jni/cmake/init-nmslib.cmake index b2c16f1fe..64df457c1 100644 --- a/jni/cmake/init-nmslib.cmake +++ b/jni/cmake/init-nmslib.cmake @@ -19,6 +19,7 @@ if(NOT DEFINED APPLY_LIB_PATCHES OR "${APPLY_LIB_PATCHES}" STREQUAL true) set(PATCH_FILE_LIST) list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0001-Initialize-maxlevel-during-add-from-enterpoint-level.patch") list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0002-Adds-ability-to-pass-ef-parameter-in-the-query-for-h.patch") + list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0003-Adding-two-apis-using-stream-to-load-save-in-Hnsw.patch") # Get patch id of the last commit execute_process(COMMAND sh -c "git --no-pager show HEAD | git patch-id --stable" OUTPUT_VARIABLE PATCH_ID_OUTPUT_FROM_COMMIT WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib) diff --git a/jni/patches/nmslib/0003-Adding-two-apis-using-stream-to-load-save-in-Hnsw.patch b/jni/patches/nmslib/0003-Adding-two-apis-using-stream-to-load-save-in-Hnsw.patch new file mode 100644 index 000000000..bbba329b4 --- /dev/null +++ b/jni/patches/nmslib/0003-Adding-two-apis-using-stream-to-load-save-in-Hnsw.patch @@ -0,0 +1,93 @@ +From 7e099ec111e5c9db4b243da249c73f0ecc206281 Mon Sep 17 00:00:00 2001 +From: Dooyong Kim +Date: Thu, 26 Sep 2024 15:20:53 -0700 +Subject: [PATCH] Adding two apis using stream to load/save in Hnsw. + +Signed-off-by: Dooyong Kim +--- + similarity_search/include/method/hnsw.h | 4 +++ + similarity_search/src/method/hnsw.cc | 44 +++++++++++++++++++++++++ + 2 files changed, 48 insertions(+) + +diff --git a/similarity_search/include/method/hnsw.h b/similarity_search/include/method/hnsw.h +index 57d99d0..7ff3f3d 100644 +--- a/similarity_search/include/method/hnsw.h ++++ b/similarity_search/include/method/hnsw.h +@@ -455,8 +455,12 @@ namespace similarity { + public: + virtual void SaveIndex(const string &location) override; + ++ void SaveIndexWithStream(std::ostream& output); ++ + virtual void LoadIndex(const string &location) override; + ++ void LoadIndexWithStream(std::istream& in); ++ + Hnsw(bool PrintProgress, const Space &space, const ObjectVector &data); + void CreateIndex(const AnyParams &IndexParams) override; + +diff --git a/similarity_search/src/method/hnsw.cc b/similarity_search/src/method/hnsw.cc +index 35b372c..e7a2c9e 100644 +--- a/similarity_search/src/method/hnsw.cc ++++ b/similarity_search/src/method/hnsw.cc +@@ -771,6 +771,25 @@ namespace similarity { + output.close(); + } + ++ template ++ void Hnsw::SaveIndexWithStream(std::ostream &output) { ++ output.exceptions(ios::badbit | ios::failbit); ++ ++ unsigned int optimIndexFlag = data_level0_memory_ != nullptr; ++ ++ writeBinaryPOD(output, optimIndexFlag); ++ ++ if (!optimIndexFlag) { ++#if USE_TEXT_REGULAR_INDEX ++ SaveRegularIndexText(output); ++#else ++ SaveRegularIndexBin(output); ++#endif ++ } else { ++ SaveOptimizedIndex(output); ++ } ++ } ++ + template + void + Hnsw::SaveOptimizedIndex(std::ostream& output) { +@@ -1021,6 +1040,31 @@ namespace similarity { + + } + ++ template ++ void Hnsw::LoadIndexWithStream(std::istream& input) { ++ LOG(LIB_INFO) << "Loading index from an input stream."; ++ CHECK_MSG(input, "Cannot open file for reading with an input stream"); ++ ++ input.exceptions(ios::badbit | ios::failbit); ++ ++#if USE_TEXT_REGULAR_INDEX ++ LoadRegularIndexText(input); ++#else ++ unsigned int optimIndexFlag= 0; ++ ++ readBinaryPOD(input, optimIndexFlag); ++ ++ if (!optimIndexFlag) { ++ LoadRegularIndexBin(input); ++ } else { ++ LoadOptimizedIndex(input); ++ } ++#endif ++ ++ LOG(LIB_INFO) << "Finished loading index"; ++ visitedlistpool = new VisitedListPool(1, totalElementsStored_); ++ } ++ + + template + void +-- +2.39.5 (Apple Git-154) + From e0c3afe5fe2f0048b9422364aeeb620e35a44deb Mon Sep 17 00:00:00 2001 From: Junqiu Lei Date: Fri, 27 Sep 2024 14:47:55 -0700 Subject: [PATCH 27/59] Optimize reduceToTopK in ResultUtil by removing pre-filling and reducing peek calls (#2146) Signed-off-by: Junqiu Lei --- CHANGELOG.md | 1 + .../java/org/opensearch/knn/index/query/ResultUtil.java | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a270ec9c0..92f4f0bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Add AVX512 support to k-NN for FAISS library [#2069](https://github.com/opensearch-project/k-NN/pull/2069) ### Enhancements * Add short circuit if no live docs are in segments [#2059](https://github.com/opensearch-project/k-NN/pull/2059) +* Optimize reduceToTopK in ResultUtil by removing pre-filling and reducing peek calls [#2146](https://github.com/opensearch-project/k-NN/pull/2146) * Update Default Rescore Context based on Dimension [#2149](https://github.com/opensearch-project/k-NN/pull/2149) ### Bug Fixes * KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) diff --git a/src/main/java/org/opensearch/knn/index/query/ResultUtil.java b/src/main/java/org/opensearch/knn/index/query/ResultUtil.java index 2ed70b8b3..f62c09cb0 100644 --- a/src/main/java/org/opensearch/knn/index/query/ResultUtil.java +++ b/src/main/java/org/opensearch/knn/index/query/ResultUtil.java @@ -33,15 +33,14 @@ public final class ResultUtil { public static void reduceToTopK(List> perLeafResults, int k) { // Iterate over all scores to get min competitive score PriorityQueue topKMinQueue = new PriorityQueue<>(k); - for (int i = 0; i < k; i++) { - topKMinQueue.add(-Float.MAX_VALUE); - } int count = 0; for (Map perLeafResult : perLeafResults) { count += perLeafResult.size(); for (Float score : perLeafResult.values()) { - if (topKMinQueue.peek() != null && score > topKMinQueue.peek()) { + if (topKMinQueue.size() < k) { + topKMinQueue.add(score); + } else if (topKMinQueue.peek() != null && score > topKMinQueue.peek()) { topKMinQueue.poll(); topKMinQueue.add(score); } From 6f6dd566e4871ddb2d0b5829c08aab9e4af90774 Mon Sep 17 00:00:00 2001 From: Vijayan Balasubramanian Date: Fri, 27 Sep 2024 23:15:43 -0700 Subject: [PATCH 28/59] KNNIterators should support with and without filters (#2155) * Rename class names to represent both and filter and non filter use cases * Iterator should support with filters Update VectorIterator and NesterVector Iterator to iterate even if there is no filters provided to iterator. Currently this is used by exact search to score either topk docs or all docs when filter is provided by users. However, in future we will be allowing exact search even if there are no filters. Hence, decouple filter and make it option to support both cases. --------- Signed-off-by: Vijayan Balasubramanian --- CHANGELOG.md | 1 + .../knn/index/query/ExactSearcher.java | 47 +++++---- .../ByteVectorIdsKNNIterator.java} | 45 ++++++--- .../{filtered => iterators}/KNNIterator.java | 2 +- .../NestedByteVectorIdsKNNIterator.java} | 32 ++++-- .../NestedVectorIdsKNNIterator.java} | 37 ++++--- .../VectorIdsKNNIterator.java} | 51 ++++++---- .../FilteredIdsKNNByteIteratorTests.java | 50 ---------- .../filtered/FilteredIdsKNNIteratorTests.java | 54 ---------- .../ByteVectorIdsKNNIteratorTests.java | 97 ++++++++++++++++++ .../NestedByteVectorIdsKNNIteratorTests.java} | 36 ++++++- .../NestedVectorIdsKNNIteratorTests.java} | 46 +++++++-- .../iterators/VectorIdsKNNIteratorTests.java | 98 +++++++++++++++++++ 13 files changed, 404 insertions(+), 192 deletions(-) rename src/main/java/org/opensearch/knn/index/query/{filtered/FilteredIdsKNNByteIterator.java => iterators/ByteVectorIdsKNNIterator.java} (57%) rename src/main/java/org/opensearch/knn/index/query/{filtered => iterators}/KNNIterator.java (80%) rename src/main/java/org/opensearch/knn/index/query/{filtered/NestedFilteredIdsKNNByteIterator.java => iterators/NestedByteVectorIdsKNNIterator.java} (54%) rename src/main/java/org/opensearch/knn/index/query/{filtered/NestedFilteredIdsKNNIterator.java => iterators/NestedVectorIdsKNNIterator.java} (59%) rename src/main/java/org/opensearch/knn/index/query/{filtered/FilteredIdsKNNIterator.java => iterators/VectorIdsKNNIterator.java} (65%) delete mode 100644 src/test/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNByteIteratorTests.java delete mode 100644 src/test/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNIteratorTests.java create mode 100644 src/test/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIteratorTests.java rename src/test/java/org/opensearch/knn/index/query/{filtered/NestedFilteredIdsKNNByteIteratorTests.java => iterators/NestedByteVectorIdsKNNIteratorTests.java} (54%) rename src/test/java/org/opensearch/knn/index/query/{filtered/NestedFilteredIdsKNNIteratorTests.java => iterators/NestedVectorIdsKNNIteratorTests.java} (55%) create mode 100644 src/test/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIteratorTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 92f4f0bbe..67879bae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Add short circuit if no live docs are in segments [#2059](https://github.com/opensearch-project/k-NN/pull/2059) * Optimize reduceToTopK in ResultUtil by removing pre-filling and reducing peek calls [#2146](https://github.com/opensearch-project/k-NN/pull/2146) * Update Default Rescore Context based on Dimension [#2149](https://github.com/opensearch-project/k-NN/pull/2149) +* KNNIterators should support with and without filters [#2155](https://github.com/opensearch-project/k-NN/pull/2155) ### Bug Fixes * KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) ### Infrastructure diff --git a/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java b/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java index 5b6029766..193cba8c1 100644 --- a/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java +++ b/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java @@ -20,11 +20,11 @@ import org.opensearch.knn.common.FieldInfoExtractor; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; -import org.opensearch.knn.index.query.filtered.FilteredIdsKNNByteIterator; -import org.opensearch.knn.index.query.filtered.FilteredIdsKNNIterator; -import org.opensearch.knn.index.query.filtered.KNNIterator; -import org.opensearch.knn.index.query.filtered.NestedFilteredIdsKNNByteIterator; -import org.opensearch.knn.index.query.filtered.NestedFilteredIdsKNNIterator; +import org.opensearch.knn.index.query.iterators.ByteVectorIdsKNNIterator; +import org.opensearch.knn.index.query.iterators.VectorIdsKNNIterator; +import org.opensearch.knn.index.query.iterators.KNNIterator; +import org.opensearch.knn.index.query.iterators.NestedByteVectorIdsKNNIterator; +import org.opensearch.knn.index.query.iterators.NestedVectorIdsKNNIterator; import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; import org.opensearch.knn.index.vectorvalues.KNNVectorValues; @@ -51,8 +51,9 @@ public class ExactSearcher { */ public Map searchLeaf(final LeafReaderContext leafReaderContext, final ExactSearcherContext exactSearcherContext) throws IOException { - KNNIterator iterator = getMatchedKNNIterator(leafReaderContext, exactSearcherContext); - if (exactSearcherContext.getMatchedDocs().cardinality() <= exactSearcherContext.getK()) { + KNNIterator iterator = getKNNIterator(leafReaderContext, exactSearcherContext); + if (exactSearcherContext.getMatchedDocs() != null + && exactSearcherContext.getMatchedDocs().cardinality() <= exactSearcherContext.getK()) { return scoreAllDocs(iterator); } return searchTopK(iterator, exactSearcherContext.getK()); @@ -98,8 +99,7 @@ private Map searchTopK(KNNIterator iterator, int k) throws IOExc return docToScore; } - private KNNIterator getMatchedKNNIterator(LeafReaderContext leafReaderContext, ExactSearcherContext exactSearcherContext) - throws IOException { + private KNNIterator getKNNIterator(LeafReaderContext leafReaderContext, ExactSearcherContext exactSearcherContext) throws IOException { final KNNQuery knnQuery = exactSearcherContext.getKnnQuery(); final BitSet matchedDocs = exactSearcherContext.getMatchedDocs(); final SegmentReader reader = Lucene.segmentReader(leafReaderContext.reader()); @@ -108,20 +108,18 @@ private KNNIterator getMatchedKNNIterator(LeafReaderContext leafReaderContext, E boolean isNestedRequired = exactSearcherContext.isParentHits() && knnQuery.getParentsFilter() != null; - if (VectorDataType.BINARY == knnQuery.getVectorDataType() && isNestedRequired) { - final KNNVectorValues vectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); - return new NestedFilteredIdsKNNByteIterator( - matchedDocs, - knnQuery.getByteQueryVector(), - (KNNBinaryVectorValues) vectorValues, - spaceType, - knnQuery.getParentsFilter().getBitSet(leafReaderContext) - ); - } - if (VectorDataType.BINARY == knnQuery.getVectorDataType()) { final KNNVectorValues vectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); - return new FilteredIdsKNNByteIterator( + if (isNestedRequired) { + return new NestedByteVectorIdsKNNIterator( + matchedDocs, + knnQuery.getByteQueryVector(), + (KNNBinaryVectorValues) vectorValues, + spaceType, + knnQuery.getParentsFilter().getBitSet(leafReaderContext) + ); + } + return new ByteVectorIdsKNNIterator( matchedDocs, knnQuery.getByteQueryVector(), (KNNBinaryVectorValues) vectorValues, @@ -142,7 +140,7 @@ private KNNIterator getMatchedKNNIterator(LeafReaderContext leafReaderContext, E final KNNVectorValues vectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); if (isNestedRequired) { - return new NestedFilteredIdsKNNIterator( + return new NestedVectorIdsKNNIterator( matchedDocs, knnQuery.getQueryVector(), (KNNFloatVectorValues) vectorValues, @@ -152,8 +150,7 @@ private KNNIterator getMatchedKNNIterator(LeafReaderContext leafReaderContext, E segmentLevelQuantizationInfo ); } - - return new FilteredIdsKNNIterator( + return new VectorIdsKNNIterator( matchedDocs, knnQuery.getQueryVector(), (KNNFloatVectorValues) vectorValues, @@ -180,7 +177,7 @@ public static class ExactSearcherContext { KNNQuery knnQuery; /** * whether the matchedDocs contains parent ids or child ids. This is relevant in the case of - * filtered nested search where the matchedDocs contain the parent ids and {@link NestedFilteredIdsKNNIterator} + * filtered nested search where the matchedDocs contain the parent ids and {@link NestedVectorIdsKNNIterator} * needs to be used. */ boolean isParentHits; diff --git a/src/main/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNByteIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIterator.java similarity index 57% rename from src/main/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNByteIterator.java rename to src/main/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIterator.java index ccfe626a0..b1aea4284 100644 --- a/src/main/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNByteIterator.java +++ b/src/main/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIterator.java @@ -3,11 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.opensearch.knn.index.query.filtered; +package org.opensearch.knn.index.query.iterators; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.util.BitSet; import org.apache.lucene.util.BitSetIterator; +import org.opensearch.common.Nullable; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; @@ -17,11 +18,9 @@ * Inspired by DiversifyingChildrenFloatKnnVectorQuery in lucene * https://github.com/apache/lucene/blob/7b8aece125aabff2823626d5b939abf4747f63a7/lucene/join/src/java/org/apache/lucene/search/join/DiversifyingChildrenFloatKnnVectorQuery.java#L162 * - * The class is used in KNNWeight to score filtered KNN field by iterating filterIdsArray. + * The class is used in KNNWeight to score all docs, but, it iterates over filterIdsArray if filter is provided */ -public class FilteredIdsKNNByteIterator implements KNNIterator { - // Array of doc ids to iterate - protected final BitSet filterIdsBitSet; +public class ByteVectorIdsKNNIterator implements KNNIterator { protected final BitSetIterator bitSetIterator; protected final byte[] queryVector; protected final KNNBinaryVectorValues binaryVectorValues; @@ -29,18 +28,24 @@ public class FilteredIdsKNNByteIterator implements KNNIterator { protected float currentScore = Float.NEGATIVE_INFINITY; protected int docId; - public FilteredIdsKNNByteIterator( - final BitSet filterIdsBitSet, + public ByteVectorIdsKNNIterator( + @Nullable final BitSet filterIdsBitSet, final byte[] queryVector, final KNNBinaryVectorValues binaryVectorValues, final SpaceType spaceType - ) { - this.filterIdsBitSet = filterIdsBitSet; - this.bitSetIterator = new BitSetIterator(filterIdsBitSet, filterIdsBitSet.length()); + ) throws IOException { + this.bitSetIterator = filterIdsBitSet == null ? null : new BitSetIterator(filterIdsBitSet, filterIdsBitSet.length()); this.queryVector = queryVector; this.binaryVectorValues = binaryVectorValues; this.spaceType = spaceType; - this.docId = bitSetIterator.nextDoc(); + // This cannot be moved inside nextDoc() method since it will break when we have nested field, where + // nextDoc should already be referring to next knnVectorValues + this.docId = getNextDocId(); + } + + public ByteVectorIdsKNNIterator(final byte[] queryVector, final KNNBinaryVectorValues binaryVectorValues, final SpaceType spaceType) + throws IOException { + this(null, queryVector, binaryVectorValues, spaceType); } /** @@ -55,10 +60,10 @@ public int nextDoc() throws IOException { if (docId == DocIdSetIterator.NO_MORE_DOCS) { return DocIdSetIterator.NO_MORE_DOCS; } - int doc = binaryVectorValues.advance(docId); currentScore = computeScore(); - docId = bitSetIterator.nextDoc(); - return doc; + int currentDocId = docId; + docId = getNextDocId(); + return currentDocId; } @Override @@ -72,4 +77,16 @@ protected float computeScore() throws IOException { // scores correspond to closer vectors. return spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector); } + + protected int getNextDocId() throws IOException { + if (bitSetIterator == null) { + return binaryVectorValues.nextDoc(); + } + int nextDocID = this.bitSetIterator.nextDoc(); + // For filter case, advance vector values to corresponding doc id from filter bit set + if (nextDocID != DocIdSetIterator.NO_MORE_DOCS) { + binaryVectorValues.advance(nextDocID); + } + return nextDocID; + } } diff --git a/src/main/java/org/opensearch/knn/index/query/filtered/KNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/KNNIterator.java similarity index 80% rename from src/main/java/org/opensearch/knn/index/query/filtered/KNNIterator.java rename to src/main/java/org/opensearch/knn/index/query/iterators/KNNIterator.java index 4a105975a..00cbb3aa2 100644 --- a/src/main/java/org/opensearch/knn/index/query/filtered/KNNIterator.java +++ b/src/main/java/org/opensearch/knn/index/query/iterators/KNNIterator.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.opensearch.knn.index.query.filtered; +package org.opensearch.knn.index.query.iterators; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNByteIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIterator.java similarity index 54% rename from src/main/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNByteIterator.java rename to src/main/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIterator.java index b69a90518..3c93ec888 100644 --- a/src/main/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNByteIterator.java +++ b/src/main/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIterator.java @@ -3,33 +3,45 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.opensearch.knn.index.query.filtered; +package org.opensearch.knn.index.query.iterators; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.util.BitSet; +import org.opensearch.common.Nullable; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; import java.io.IOException; /** - * This iterator iterates filterIdsArray to score. However, it dedupe docs per each parent doc + * This iterator iterates filterIdsArray to score if filter is provided else it iterates over all docs. + * However, it dedupe docs per each parent doc * of which ID is set in parentBitSet and only return best child doc with the highest score. */ -public class NestedFilteredIdsKNNByteIterator extends FilteredIdsKNNByteIterator { +public class NestedByteVectorIdsKNNIterator extends ByteVectorIdsKNNIterator { private final BitSet parentBitSet; - public NestedFilteredIdsKNNByteIterator( - final BitSet filterIdsArray, + public NestedByteVectorIdsKNNIterator( + @Nullable final BitSet filterIdsArray, final byte[] queryVector, final KNNBinaryVectorValues binaryVectorValues, final SpaceType spaceType, final BitSet parentBitSet - ) { + ) throws IOException { super(filterIdsArray, queryVector, binaryVectorValues, spaceType); this.parentBitSet = parentBitSet; } + public NestedByteVectorIdsKNNIterator( + final byte[] queryVector, + final KNNBinaryVectorValues binaryVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet + ) throws IOException { + super(null, queryVector, binaryVectorValues, spaceType); + this.parentBitSet = parentBitSet; + } + /** * Advance to the next best child doc per parent and update score with the best score among child docs from the parent. * DocIdSetIterator.NO_MORE_DOCS is returned when there is no more docs @@ -46,14 +58,18 @@ public int nextDoc() throws IOException { int currentParent = parentBitSet.nextSetBit(docId); int bestChild = -1; + // In order to traverse all children for given parent, we have to use docId < parentId, because, + // kNNVectorValues will not have parent id since DocId is unique per segment. For ex: let's say for doc id 1, there is one child + // and for doc id 5, there are three children. In that case knnVectorValues iterator will have [0, 2, 3, 4] + // and parentBitSet will have [1,5] + // Hence, we have to iterate till docId from knnVectorValues is less than parentId instead of till equal to parentId while (docId != DocIdSetIterator.NO_MORE_DOCS && docId < currentParent) { - binaryVectorValues.advance(docId); float score = computeScore(); if (score > currentScore) { bestChild = docId; currentScore = score; } - docId = bitSetIterator.nextDoc(); + docId = getNextDocId(); } return bestChild; diff --git a/src/main/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIterator.java similarity index 59% rename from src/main/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNIterator.java rename to src/main/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIterator.java index 53ac72882..692793b99 100644 --- a/src/main/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNIterator.java +++ b/src/main/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIterator.java @@ -3,10 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.opensearch.knn.index.query.filtered; +package org.opensearch.knn.index.query.iterators; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.util.BitSet; +import org.opensearch.common.Nullable; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.query.SegmentLevelQuantizationInfo; import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; @@ -14,31 +15,41 @@ import java.io.IOException; /** - * This iterator iterates filterIdsArray to score. However, it dedupe docs per each parent doc + * This iterator iterates filterIdsArray to score if filter is provided else it iterates over all docs. + * However, it dedupe docs per each parent doc * of which ID is set in parentBitSet and only return best child doc with the highest score. */ -public class NestedFilteredIdsKNNIterator extends FilteredIdsKNNIterator { +public class NestedVectorIdsKNNIterator extends VectorIdsKNNIterator { private final BitSet parentBitSet; - NestedFilteredIdsKNNIterator( - final BitSet filterIdsArray, + public NestedVectorIdsKNNIterator( + @Nullable final BitSet filterIdsArray, final float[] queryVector, final KNNFloatVectorValues knnFloatVectorValues, final SpaceType spaceType, final BitSet parentBitSet - ) { + ) throws IOException { this(filterIdsArray, queryVector, knnFloatVectorValues, spaceType, parentBitSet, null, null); } - public NestedFilteredIdsKNNIterator( - final BitSet filterIdsArray, + public NestedVectorIdsKNNIterator( + final float[] queryVector, + final KNNFloatVectorValues knnFloatVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet + ) throws IOException { + this(null, queryVector, knnFloatVectorValues, spaceType, parentBitSet, null, null); + } + + public NestedVectorIdsKNNIterator( + @Nullable final BitSet filterIdsArray, final float[] queryVector, final KNNFloatVectorValues knnFloatVectorValues, final SpaceType spaceType, final BitSet parentBitSet, final byte[] quantizedVector, final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo - ) { + ) throws IOException { super(filterIdsArray, queryVector, knnFloatVectorValues, spaceType, quantizedVector, segmentLevelQuantizationInfo); this.parentBitSet = parentBitSet; } @@ -59,14 +70,18 @@ public int nextDoc() throws IOException { int currentParent = parentBitSet.nextSetBit(docId); int bestChild = -1; + // In order to traverse all children for given parent, we have to use docId < parentId, because, + // kNNVectorValues will not have parent id since DocId is unique per segment. For ex: let's say for doc id 1, there is one child + // and for doc id 5, there are three children. In that case knnVectorValues iterator will have [0, 2, 3, 4] + // and parentBitSet will have [1,5] + // Hence, we have to iterate till docId from knnVectorValues is less than parentId instead of till equal to parentId while (docId != DocIdSetIterator.NO_MORE_DOCS && docId < currentParent) { - knnFloatVectorValues.advance(docId); float score = computeScore(); if (score > currentScore) { bestChild = docId; currentScore = score; } - docId = bitSetIterator.nextDoc(); + docId = getNextDocId(); } return bestChild; diff --git a/src/main/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIterator.java similarity index 65% rename from src/main/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNIterator.java rename to src/main/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIterator.java index 56d291470..9fb354242 100644 --- a/src/main/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNIterator.java +++ b/src/main/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIterator.java @@ -3,11 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.opensearch.knn.index.query.filtered; +package org.opensearch.knn.index.query.iterators; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.util.BitSet; import org.apache.lucene.util.BitSetIterator; +import org.opensearch.common.Nullable; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.query.SegmentLevelQuantizationInfo; import org.opensearch.knn.index.query.SegmentLevelQuantizationUtil; @@ -19,11 +20,9 @@ * Inspired by DiversifyingChildrenFloatKnnVectorQuery in lucene * https://github.com/apache/lucene/blob/7b8aece125aabff2823626d5b939abf4747f63a7/lucene/join/src/java/org/apache/lucene/search/join/DiversifyingChildrenFloatKnnVectorQuery.java#L162 * - * The class is used in KNNWeight to score filtered KNN field by iterating filterIdsArray. + * The class is used in KNNWeight to score all docs, but, it iterates over filterIdsArray if filter is provided */ -public class FilteredIdsKNNIterator implements KNNIterator { - // Array of doc ids to iterate - protected final BitSet filterIdsBitSet; +public class VectorIdsKNNIterator implements KNNIterator { protected final BitSetIterator bitSetIterator; protected final float[] queryVector; private final byte[] quantizedQueryVector; @@ -33,29 +32,35 @@ public class FilteredIdsKNNIterator implements KNNIterator { protected int docId; private final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo; - FilteredIdsKNNIterator( - final BitSet filterIdsBitSet, + public VectorIdsKNNIterator( + @Nullable final BitSet filterIdsBitSet, final float[] queryVector, final KNNFloatVectorValues knnFloatVectorValues, final SpaceType spaceType - ) { + ) throws IOException { this(filterIdsBitSet, queryVector, knnFloatVectorValues, spaceType, null, null); } - public FilteredIdsKNNIterator( - final BitSet filterIdsBitSet, + public VectorIdsKNNIterator(final float[] queryVector, final KNNFloatVectorValues knnFloatVectorValues, final SpaceType spaceType) + throws IOException { + this(null, queryVector, knnFloatVectorValues, spaceType, null, null); + } + + public VectorIdsKNNIterator( + @Nullable final BitSet filterIdsBitSet, final float[] queryVector, final KNNFloatVectorValues knnFloatVectorValues, final SpaceType spaceType, final byte[] quantizedQueryVector, final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo - ) { - this.filterIdsBitSet = filterIdsBitSet; - this.bitSetIterator = new BitSetIterator(filterIdsBitSet, filterIdsBitSet.length()); + ) throws IOException { + this.bitSetIterator = filterIdsBitSet == null ? null : new BitSetIterator(filterIdsBitSet, filterIdsBitSet.length()); this.queryVector = queryVector; this.knnFloatVectorValues = knnFloatVectorValues; this.spaceType = spaceType; - this.docId = bitSetIterator.nextDoc(); + // This cannot be moved inside nextDoc() method since it will break when we have nested field, where + // nextDoc should already be referring to next knnVectorValues + this.docId = getNextDocId(); this.quantizedQueryVector = quantizedQueryVector; this.segmentLevelQuantizationInfo = segmentLevelQuantizationInfo; } @@ -72,10 +77,10 @@ public int nextDoc() throws IOException { if (docId == DocIdSetIterator.NO_MORE_DOCS) { return DocIdSetIterator.NO_MORE_DOCS; } - int doc = knnFloatVectorValues.advance(docId); currentScore = computeScore(); - docId = bitSetIterator.nextDoc(); - return doc; + int currentDocId = docId; + docId = getNextDocId(); + return currentDocId; } @Override @@ -94,4 +99,16 @@ protected float computeScore() throws IOException { return spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector); } } + + protected int getNextDocId() throws IOException { + if (bitSetIterator == null) { + return knnFloatVectorValues.nextDoc(); + } + int nextDocID = this.bitSetIterator.nextDoc(); + // For filter case, advance vector values to corresponding doc id from filter bit set + if (nextDocID != DocIdSetIterator.NO_MORE_DOCS) { + knnFloatVectorValues.advance(nextDocID); + } + return nextDocID; + } } diff --git a/src/test/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNByteIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNByteIteratorTests.java deleted file mode 100644 index c52798c05..000000000 --- a/src/test/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNByteIteratorTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index.query.filtered; - -import junit.framework.TestCase; -import lombok.SneakyThrows; -import org.apache.lucene.search.DocIdSetIterator; -import org.apache.lucene.util.FixedBitSet; -import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class FilteredIdsKNNByteIteratorTests extends TestCase { - @SneakyThrows - public void testNextDoc_whenCalled_IterateAllDocs() { - final SpaceType spaceType = SpaceType.HAMMING; - final byte[] queryVector = { 1, 2, 3 }; - final int[] filterIds = { 1, 2, 3 }; - final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); - final List expectedScores = dataVectors.stream() - .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) - .collect(Collectors.toList()); - - KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); - when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); - - FixedBitSet filterBitSet = new FixedBitSet(4); - for (int id : filterIds) { - when(values.advance(id)).thenReturn(id); - filterBitSet.set(id); - } - - // Execute and verify - FilteredIdsKNNByteIterator iterator = new FilteredIdsKNNByteIterator(filterBitSet, queryVector, values, spaceType); - for (int i = 0; i < filterIds.length; i++) { - assertEquals(filterIds[i], iterator.nextDoc()); - assertEquals(expectedScores.get(i), (Float) iterator.score()); - } - assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); - } -} diff --git a/src/test/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNIteratorTests.java deleted file mode 100644 index 731eed2cc..000000000 --- a/src/test/java/org/opensearch/knn/index/query/filtered/FilteredIdsKNNIteratorTests.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index.query.filtered; - -import lombok.SneakyThrows; -import org.apache.lucene.search.DocIdSetIterator; -import org.apache.lucene.util.FixedBitSet; -import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class FilteredIdsKNNIteratorTests extends KNNTestCase { - @SneakyThrows - public void testNextDoc_whenCalled_IterateAllDocs() { - final SpaceType spaceType = SpaceType.L2; - final float[] queryVector = { 1.0f, 2.0f, 3.0f }; - final int[] filterIds = { 1, 2, 3 }; - final List dataVectors = Arrays.asList( - new float[] { 11.0f, 12.0f, 13.0f }, - new float[] { 14.0f, 15.0f, 16.0f }, - new float[] { 17.0f, 18.0f, 19.0f } - ); - final List expectedScores = dataVectors.stream() - .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) - .collect(Collectors.toList()); - - KNNFloatVectorValues values = mock(KNNFloatVectorValues.class); - when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); - - FixedBitSet filterBitSet = new FixedBitSet(4); - for (int id : filterIds) { - when(values.advance(id)).thenReturn(id); - filterBitSet.set(id); - } - - // Execute and verify - FilteredIdsKNNIterator iterator = new FilteredIdsKNNIterator(filterBitSet, queryVector, values, spaceType); - for (int i = 0; i < filterIds.length; i++) { - assertEquals(filterIds[i], iterator.nextDoc()); - assertEquals(expectedScores.get(i), (Float) iterator.score()); - } - assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); - } -} diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIteratorTests.java new file mode 100644 index 000000000..0b1b71286 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIteratorTests.java @@ -0,0 +1,97 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import junit.framework.TestCase; +import lombok.SneakyThrows; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.FixedBitSet; +import org.mockito.stubbing.OngoingStubbing; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ByteVectorIdsKNNIteratorTests extends TestCase { + @SneakyThrows + public void testNextDoc_whenCalled_thenIterateAllDocs() { + final SpaceType spaceType = SpaceType.HAMMING; + final byte[] queryVector = { 1, 2, 3 }; + final int[] filterIds = { 1, 2, 3 }; + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + + FixedBitSet filterBitSet = new FixedBitSet(4); + for (int id : filterIds) { + when(values.advance(id)).thenReturn(id); + filterBitSet.set(id); + } + + // Execute and verify + ByteVectorIdsKNNIterator iterator = new ByteVectorIdsKNNIterator(filterBitSet, queryVector, values, spaceType); + for (int i = 0; i < filterIds.length; i++) { + assertEquals(filterIds[i], iterator.nextDoc()); + assertEquals(expectedScores.get(i), iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + } + + @SneakyThrows + public void testNextDoc_whenCalled_thenIterateAllDocsWithoutFilter() throws IOException { + final SpaceType spaceType = SpaceType.HAMMING; + final byte[] queryVector = { 1, 2, 3 }; + final List dataVectors = Arrays.asList( + new byte[] { 11, 12, 13 }, + new byte[] { 14, 15, 16 }, + new byte[] { 17, 18, 19 }, + new byte[] { 20, 21, 22 }, + new byte[] { 23, 24, 25 } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + when(values.getVector()).thenReturn( + dataVectors.get(0), + dataVectors.get(1), + dataVectors.get(2), + dataVectors.get(3), + dataVectors.get(4) + ); + + // stub return value when nextDoc is called + OngoingStubbing stubbing = when(values.nextDoc()); + for (int i = 0; i < dataVectors.size(); i++) { + stubbing = stubbing.thenReturn(i); + } + // set last return to be Integer.MAX_VALUE to represent no more docs + stubbing.thenReturn(Integer.MAX_VALUE); + + // Execute and verify + ByteVectorIdsKNNIterator iterator = new ByteVectorIdsKNNIterator(queryVector, values, spaceType); + for (int i = 0; i < dataVectors.size(); i++) { + assertEquals(i, iterator.nextDoc()); + assertEquals(expectedScores.get(i), iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNByteIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIteratorTests.java similarity index 54% rename from src/test/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNByteIteratorTests.java rename to src/test/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIteratorTests.java index 1940ffe12..eff021234 100644 --- a/src/test/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNByteIteratorTests.java +++ b/src/test/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIteratorTests.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.opensearch.knn.index.query.filtered; +package org.opensearch.knn.index.query.iterators; import junit.framework.TestCase; import lombok.SneakyThrows; @@ -17,10 +17,13 @@ import java.util.List; import java.util.stream.Collectors; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class NestedFilteredIdsKNNByteIteratorTests extends TestCase { +public class NestedByteVectorIdsKNNIteratorTests extends TestCase { @SneakyThrows public void testNextDoc_whenIterate_ReturnBestChildDocsPerParent() { final SpaceType spaceType = SpaceType.HAMMING; @@ -45,7 +48,7 @@ public void testNextDoc_whenIterate_ReturnBestChildDocsPerParent() { } // Execute and verify - NestedFilteredIdsKNNByteIterator iterator = new NestedFilteredIdsKNNByteIterator( + NestedByteVectorIdsKNNIterator iterator = new NestedByteVectorIdsKNNIterator( filterBitSet, queryVector, values, @@ -58,4 +61,31 @@ public void testNextDoc_whenIterate_ReturnBestChildDocsPerParent() { assertEquals(expectedScores.get(2), iterator.score()); assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); } + + @SneakyThrows + public void testNextDoc_whenIterateWithoutFilters_thenReturnBestChildDocsPerParent() { + final SpaceType spaceType = SpaceType.HAMMING; + final byte[] queryVector = { 1, 2, 3 }; + // Parent id for 0 -> 1 + // Parent id for 2, 3 -> 4 + // In bit representation, it is 10010. In long, it is 18. + final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + when(values.nextDoc()).thenReturn(0, 2, 3, Integer.MAX_VALUE); + + // Execute and verify + NestedByteVectorIdsKNNIterator iterator = new NestedByteVectorIdsKNNIterator(queryVector, values, spaceType, parentBitSet); + assertEquals(0, iterator.nextDoc()); + assertEquals(expectedScores.get(0), iterator.score()); + assertEquals(3, iterator.nextDoc()); + assertEquals(expectedScores.get(2), iterator.score()); + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } } diff --git a/src/test/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIteratorTests.java similarity index 55% rename from src/test/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNIteratorTests.java rename to src/test/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIteratorTests.java index cca789a4d..f94ddb4e1 100644 --- a/src/test/java/org/opensearch/knn/index/query/filtered/NestedFilteredIdsKNNIteratorTests.java +++ b/src/test/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIteratorTests.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.opensearch.knn.index.query.filtered; +package org.opensearch.knn.index.query.iterators; import junit.framework.TestCase; import lombok.SneakyThrows; @@ -17,10 +17,13 @@ import java.util.List; import java.util.stream.Collectors; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class NestedFilteredIdsKNNIteratorTests extends TestCase { +public class NestedVectorIdsKNNIteratorTests extends TestCase { @SneakyThrows public void testNextDoc_whenIterate_ReturnBestChildDocsPerParent() { final SpaceType spaceType = SpaceType.L2; @@ -53,17 +56,42 @@ public void testNextDoc_whenIterate_ReturnBestChildDocsPerParent() { } // Execute and verify - NestedFilteredIdsKNNIterator iterator = new NestedFilteredIdsKNNIterator( - filterBitSet, - queryVector, - values, - spaceType, - parentBitSet - ); + NestedVectorIdsKNNIterator iterator = new NestedVectorIdsKNNIterator(filterBitSet, queryVector, values, spaceType, parentBitSet); assertEquals(filterIds[0], iterator.nextDoc()); assertEquals(expectedScores.get(0), iterator.score()); assertEquals(filterIds[2], iterator.nextDoc()); assertEquals(expectedScores.get(2), iterator.score()); assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); } + + @SneakyThrows + public void testNextDoc_whenIterateWithoutFilters_thenReturnBestChildDocsPerParent() { + final SpaceType spaceType = SpaceType.L2; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; + // Parent id for 0 -> 1 + // Parent id for 2, 3 -> 4 + // In bit representation, it is 10010. In long, it is 18. + final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); + final List dataVectors = Arrays.asList( + new float[] { 11.0f, 12.0f, 13.0f }, + new float[] { 17.0f, 18.0f, 19.0f }, + new float[] { 14.0f, 15.0f, 16.0f } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNFloatVectorValues values = mock(KNNFloatVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + when(values.nextDoc()).thenReturn(0, 2, 3, Integer.MAX_VALUE); + + // Execute and verify + NestedVectorIdsKNNIterator iterator = new NestedVectorIdsKNNIterator(queryVector, values, spaceType, parentBitSet); + assertEquals(0, iterator.nextDoc()); + assertEquals(expectedScores.get(0), iterator.score()); + assertEquals(3, iterator.nextDoc()); + assertEquals(expectedScores.get(2), iterator.score()); + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } } diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIteratorTests.java new file mode 100644 index 000000000..96932d0f1 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIteratorTests.java @@ -0,0 +1,98 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import lombok.SneakyThrows; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.FixedBitSet; +import org.mockito.stubbing.OngoingStubbing; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class VectorIdsKNNIteratorTests extends KNNTestCase { + @SneakyThrows + public void testNextDoc_whenCalledWithFilters_thenIterateAllDocs() { + final SpaceType spaceType = SpaceType.L2; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; + final int[] filterIds = { 1, 2, 3 }; + final List dataVectors = Arrays.asList( + new float[] { 11.0f, 12.0f, 13.0f }, + new float[] { 14.0f, 15.0f, 16.0f }, + new float[] { 17.0f, 18.0f, 19.0f } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNFloatVectorValues values = mock(KNNFloatVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + + FixedBitSet filterBitSet = new FixedBitSet(4); + for (int id : filterIds) { + when(values.advance(id)).thenReturn(id); + filterBitSet.set(id); + } + + // Execute and verify + VectorIdsKNNIterator iterator = new VectorIdsKNNIterator(filterBitSet, queryVector, values, spaceType); + for (int i = 0; i < filterIds.length; i++) { + assertEquals(filterIds[i], iterator.nextDoc()); + assertEquals(expectedScores.get(i), (Float) iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + } + + @SneakyThrows + public void testNextDoc_whenCalledWithoutFilters_thenIterateAllDocs() { + final SpaceType spaceType = SpaceType.L2; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; + final List dataVectors = Arrays.asList( + new float[] { 11.0f, 12.0f, 13.0f }, + new float[] { 14.0f, 15.0f, 16.0f }, + new float[] { 17.0f, 18.0f, 19.0f }, + new float[] { 20.0f, 21.0f, 22.0f }, + new float[] { 23.0f, 24.0f, 25.0f } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNFloatVectorValues values = mock(KNNFloatVectorValues.class); + when(values.getVector()).thenReturn( + dataVectors.get(0), + dataVectors.get(1), + dataVectors.get(2), + dataVectors.get(3), + dataVectors.get(4) + ); + // stub return value when nextDoc is called + OngoingStubbing stubbing = when(values.nextDoc()); + for (int i = 0; i < dataVectors.size(); i++) { + stubbing = stubbing.thenReturn(i); + } + // set last return to be Integer.MAX_VALUE to represent no more docs + stubbing.thenReturn(Integer.MAX_VALUE); + // Execute and verify + VectorIdsKNNIterator iterator = new VectorIdsKNNIterator(queryVector, values, spaceType); + for (int i = 0; i < dataVectors.size(); i++) { + assertEquals(i, iterator.nextDoc()); + assertEquals(expectedScores.get(i), (Float) iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } +} From f16f2250b082ce8e2c5eda23b38d27a89365fa13 Mon Sep 17 00:00:00 2001 From: Vijayan Balasubramanian Date: Mon, 30 Sep 2024 14:56:18 -0700 Subject: [PATCH 29/59] Refactor and Update unit test to include field with no live docs (#2167) Refactored if/else to reduce nesting. Added unit test when one of the field doesn't have live docs. Signed-off-by: Vijayan Balasubramanian --- CHANGELOG.md | 1 + .../NativeEngines990KnnVectorsWriter.java | 32 ++++++++-------- ...eEngines990KnnVectorsWriterFlushTests.java | 38 ++++++++++++------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67879bae7..5615509de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,3 +31,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Maintenance * Remove benchmarks folder from k-NN repo [#2127](https://github.com/opensearch-project/k-NN/pull/2127) ### Refactoring +* Minor refactoring and refactored some unit test [#2167](https://github.com/opensearch-project/k-NN/pull/2167) diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java index 23cd2a4de..2f22565c9 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java @@ -84,24 +84,24 @@ public void flush(int maxDoc, final Sorter.DocMap sortMap) throws IOException { final FieldInfo fieldInfo = field.getFieldInfo(); final VectorDataType vectorDataType = extractVectorDataType(fieldInfo); int totalLiveDocs = field.getVectors().size(); - if (totalLiveDocs > 0) { - final Supplier> knnVectorValuesSupplier = () -> getVectorValues( - vectorDataType, - field.getDocsWithField(), - field.getVectors() - ); - final QuantizationState quantizationState = train(field.getFieldInfo(), knnVectorValuesSupplier, totalLiveDocs); - final NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); - final KNNVectorValues knnVectorValues = knnVectorValuesSupplier.get(); - - StopWatch stopWatch = new StopWatch().start(); - writer.flushIndex(knnVectorValues, totalLiveDocs); - long time_in_millis = stopWatch.stop().totalTime().millis(); - KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.incrementBy(time_in_millis); - log.debug("Flush took {} ms for vector field [{}]", time_in_millis, fieldInfo.getName()); - } else { + if (totalLiveDocs == 0) { log.debug("[Flush] No live docs for field {}", fieldInfo.getName()); + continue; } + final Supplier> knnVectorValuesSupplier = () -> getVectorValues( + vectorDataType, + field.getDocsWithField(), + field.getVectors() + ); + final QuantizationState quantizationState = train(field.getFieldInfo(), knnVectorValuesSupplier, totalLiveDocs); + final NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); + final KNNVectorValues knnVectorValues = knnVectorValuesSupplier.get(); + + StopWatch stopWatch = new StopWatch().start(); + writer.flushIndex(knnVectorValues, totalLiveDocs); + long time_in_millis = stopWatch.stop().totalTime().millis(); + KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.incrementBy(time_in_millis); + log.debug("Flush took {} ms for vector field [{}]", time_in_millis, fieldInfo.getName()); } } diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java index dbb564908..9f74b2c10 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java @@ -32,8 +32,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.IntStream; import static com.carrotsearch.randomizedtesting.RandomizedTest.$; @@ -44,6 +47,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockConstruction; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -86,6 +90,7 @@ public static Collection data() { "Multi Field", List.of( Map.of(0, new float[] { 1, 2, 3 }, 1, new float[] { 2, 3, 4 }, 2, new float[] { 3, 4, 5 }), + Collections.emptyMap(), Map.of( 0, new float[] { 1, 2, 3, 4 }, @@ -105,18 +110,16 @@ public static Collection data() { @SneakyThrows public void testFlush() { // Given - List> expectedVectorValues = new ArrayList<>(); - IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final List> expectedVectorValues = vectorsPerField.stream().map(vectors -> { final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( - new ArrayList<>(vectorsPerField.get(i).values()) + new ArrayList<>(vectors.values()) ); final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( VectorDataType.FLOAT, randomVectorValues ); - expectedVectorValues.add(knnVectorValues); - - }); + return knnVectorValues; + }).collect(Collectors.toList()); try ( MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); @@ -172,15 +175,19 @@ public void testFlush() { IntStream.range(0, vectorsPerField.size()).forEach(i -> { try { - verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + if (vectorsPerField.get(i).isEmpty()) { + verify(nativeIndexWriter, never()).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } else { + verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } } catch (Exception e) { throw new RuntimeException(e); } }); - + final Long expectedTimesGetVectorValuesIsCalled = vectorsPerField.stream().filter(Predicate.not(Map::isEmpty)).count(); knnVectorValuesFactoryMockedStatic.verify( () -> KNNVectorValuesFactory.getVectorValues(any(VectorDataType.class), any(DocsWithFieldSet.class), any()), - times(expectedVectorValues.size()) + times(Math.toIntExact(expectedTimesGetVectorValuesIsCalled)) ); } } @@ -264,16 +271,21 @@ public void testFlush_WithQuantization() { IntStream.range(0, vectorsPerField.size()).forEach(i -> { try { - verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeState(i, quantizationState); - verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + if (vectorsPerField.get(i).isEmpty()) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0), never()).writeState(i, quantizationState); + verify(nativeIndexWriter, never()).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } else { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeState(i, quantizationState); + verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } } catch (Exception e) { throw new RuntimeException(e); } }); - + final Long expectedTimesGetVectorValuesIsCalled = vectorsPerField.stream().filter(Predicate.not(Map::isEmpty)).count(); knnVectorValuesFactoryMockedStatic.verify( () -> KNNVectorValuesFactory.getVectorValues(any(VectorDataType.class), any(DocsWithFieldSet.class), any()), - times(expectedVectorValues.size() * 2) + times(Math.toIntExact(expectedTimesGetVectorValuesIsCalled) * 2) ); } } From 6d098cf5266160033eccc7ee2e90bd4e4c1c071c Mon Sep 17 00:00:00 2001 From: Naveen Tatikonda Date: Mon, 30 Sep 2024 17:24:28 -0500 Subject: [PATCH 30/59] Fix Faiss efficient filter exact search using byte vector datatype (#2165) * Fix Faiss efficient filter exact search using byte vector datatype Signed-off-by: Naveen Tatikonda * Address Review Comments Signed-off-by: Naveen Tatikonda --------- Signed-off-by: Naveen Tatikonda --- .../knn/index/query/ExactSearcher.java | 21 ++- .../iterators/BinaryVectorIdsKNNIterator.java | 92 +++++++++++ .../iterators/ByteVectorIdsKNNIterator.java | 34 ++-- .../NestedBinaryVectorIdsKNNIterator.java | 77 +++++++++ .../NestedByteVectorIdsKNNIterator.java | 12 +- .../BinaryVectorIdsKNNIteratorTests.java | 97 +++++++++++ .../ByteVectorIdsKNNIteratorTests.java | 24 +-- ...NestedBinaryVectorIdsKNNIteratorTests.java | 91 ++++++++++ .../NestedByteVectorIdsKNNIteratorTests.java | 24 +-- .../knn/integ/FilteredSearchByteIT.java | 104 ++++++++++++ .../knn/integ/NestedSearchByteIT.java | 156 ++++++++++++++++++ 11 files changed, 690 insertions(+), 42 deletions(-) create mode 100644 src/main/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIterator.java create mode 100644 src/main/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIterator.java create mode 100644 src/test/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIteratorTests.java create mode 100644 src/test/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIteratorTests.java create mode 100644 src/test/java/org/opensearch/knn/integ/FilteredSearchByteIT.java create mode 100644 src/test/java/org/opensearch/knn/integ/NestedSearchByteIT.java diff --git a/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java b/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java index 193cba8c1..8e5849abb 100644 --- a/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java +++ b/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java @@ -20,12 +20,15 @@ import org.opensearch.knn.common.FieldInfoExtractor; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.query.iterators.BinaryVectorIdsKNNIterator; import org.opensearch.knn.index.query.iterators.ByteVectorIdsKNNIterator; +import org.opensearch.knn.index.query.iterators.NestedBinaryVectorIdsKNNIterator; import org.opensearch.knn.index.query.iterators.VectorIdsKNNIterator; import org.opensearch.knn.index.query.iterators.KNNIterator; import org.opensearch.knn.index.query.iterators.NestedByteVectorIdsKNNIterator; import org.opensearch.knn.index.query.iterators.NestedVectorIdsKNNIterator; import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNByteVectorValues; import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; import org.opensearch.knn.index.vectorvalues.KNNVectorValues; import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; @@ -111,7 +114,7 @@ private KNNIterator getKNNIterator(LeafReaderContext leafReaderContext, ExactSea if (VectorDataType.BINARY == knnQuery.getVectorDataType()) { final KNNVectorValues vectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); if (isNestedRequired) { - return new NestedByteVectorIdsKNNIterator( + return new NestedBinaryVectorIdsKNNIterator( matchedDocs, knnQuery.getByteQueryVector(), (KNNBinaryVectorValues) vectorValues, @@ -119,13 +122,27 @@ private KNNIterator getKNNIterator(LeafReaderContext leafReaderContext, ExactSea knnQuery.getParentsFilter().getBitSet(leafReaderContext) ); } - return new ByteVectorIdsKNNIterator( + return new BinaryVectorIdsKNNIterator( matchedDocs, knnQuery.getByteQueryVector(), (KNNBinaryVectorValues) vectorValues, spaceType ); } + + if (VectorDataType.BYTE == knnQuery.getVectorDataType()) { + final KNNVectorValues vectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); + if (isNestedRequired) { + return new NestedByteVectorIdsKNNIterator( + matchedDocs, + knnQuery.getQueryVector(), + (KNNByteVectorValues) vectorValues, + spaceType, + knnQuery.getParentsFilter().getBitSet(leafReaderContext) + ); + } + return new ByteVectorIdsKNNIterator(matchedDocs, knnQuery.getQueryVector(), (KNNByteVectorValues) vectorValues, spaceType); + } final byte[] quantizedQueryVector; final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo; if (exactSearcherContext.isUseQuantizedVectorsForSearch()) { diff --git a/src/main/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIterator.java new file mode 100644 index 000000000..5bab5b573 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIterator.java @@ -0,0 +1,92 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.BitSetIterator; +import org.opensearch.common.Nullable; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; + +import java.io.IOException; + +/** + * Inspired by DiversifyingChildrenFloatKnnVectorQuery in lucene + * https://github.com/apache/lucene/blob/7b8aece125aabff2823626d5b939abf4747f63a7/lucene/join/src/java/org/apache/lucene/search/join/DiversifyingChildrenFloatKnnVectorQuery.java#L162 + * + * The class is used in KNNWeight to score all docs, but, it iterates over filterIdsArray if filter is provided + */ +public class BinaryVectorIdsKNNIterator implements KNNIterator { + protected final BitSetIterator bitSetIterator; + protected final byte[] queryVector; + protected final KNNBinaryVectorValues binaryVectorValues; + protected final SpaceType spaceType; + protected float currentScore = Float.NEGATIVE_INFINITY; + protected int docId; + + public BinaryVectorIdsKNNIterator( + @Nullable final BitSet filterIdsBitSet, + final byte[] queryVector, + final KNNBinaryVectorValues binaryVectorValues, + final SpaceType spaceType + ) throws IOException { + this.bitSetIterator = filterIdsBitSet == null ? null : new BitSetIterator(filterIdsBitSet, filterIdsBitSet.length()); + this.queryVector = queryVector; + this.binaryVectorValues = binaryVectorValues; + this.spaceType = spaceType; + // This cannot be moved inside nextDoc() method since it will break when we have nested field, where + // nextDoc should already be referring to next knnVectorValues + this.docId = getNextDocId(); + } + + public BinaryVectorIdsKNNIterator(final byte[] queryVector, final KNNBinaryVectorValues binaryVectorValues, final SpaceType spaceType) + throws IOException { + this(null, queryVector, binaryVectorValues, spaceType); + } + + /** + * Advance to the next doc and update score value with score of the next doc. + * DocIdSetIterator.NO_MORE_DOCS is returned when there is no more docs + * + * @return next doc id + */ + @Override + public int nextDoc() throws IOException { + + if (docId == DocIdSetIterator.NO_MORE_DOCS) { + return DocIdSetIterator.NO_MORE_DOCS; + } + currentScore = computeScore(); + int currentDocId = docId; + docId = getNextDocId(); + return currentDocId; + } + + @Override + public float score() { + return currentScore; + } + + protected float computeScore() throws IOException { + final byte[] vector = binaryVectorValues.getVector(); + // Calculates a similarity score between the two vectors with a specified function. Higher similarity + // scores correspond to closer vectors. + return spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector); + } + + protected int getNextDocId() throws IOException { + if (bitSetIterator == null) { + return binaryVectorValues.nextDoc(); + } + int nextDocID = this.bitSetIterator.nextDoc(); + // For filter case, advance vector values to corresponding doc id from filter bit set + if (nextDocID != DocIdSetIterator.NO_MORE_DOCS) { + binaryVectorValues.advance(nextDocID); + } + return nextDocID; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIterator.java index b1aea4284..0e8005163 100644 --- a/src/main/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIterator.java +++ b/src/main/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIterator.java @@ -10,7 +10,7 @@ import org.apache.lucene.util.BitSetIterator; import org.opensearch.common.Nullable; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNByteVectorValues; import java.io.IOException; @@ -22,30 +22,30 @@ */ public class ByteVectorIdsKNNIterator implements KNNIterator { protected final BitSetIterator bitSetIterator; - protected final byte[] queryVector; - protected final KNNBinaryVectorValues binaryVectorValues; + protected final float[] queryVector; + protected final KNNByteVectorValues byteVectorValues; protected final SpaceType spaceType; protected float currentScore = Float.NEGATIVE_INFINITY; protected int docId; public ByteVectorIdsKNNIterator( @Nullable final BitSet filterIdsBitSet, - final byte[] queryVector, - final KNNBinaryVectorValues binaryVectorValues, + final float[] queryVector, + final KNNByteVectorValues byteVectorValues, final SpaceType spaceType ) throws IOException { this.bitSetIterator = filterIdsBitSet == null ? null : new BitSetIterator(filterIdsBitSet, filterIdsBitSet.length()); this.queryVector = queryVector; - this.binaryVectorValues = binaryVectorValues; + this.byteVectorValues = byteVectorValues; this.spaceType = spaceType; // This cannot be moved inside nextDoc() method since it will break when we have nested field, where // nextDoc should already be referring to next knnVectorValues this.docId = getNextDocId(); } - public ByteVectorIdsKNNIterator(final byte[] queryVector, final KNNBinaryVectorValues binaryVectorValues, final SpaceType spaceType) + public ByteVectorIdsKNNIterator(final float[] queryVector, final KNNByteVectorValues byteVectorValues, final SpaceType spaceType) throws IOException { - this(null, queryVector, binaryVectorValues, spaceType); + this(null, queryVector, byteVectorValues, spaceType); } /** @@ -72,20 +72,30 @@ public float score() { } protected float computeScore() throws IOException { - final byte[] vector = binaryVectorValues.getVector(); + final byte[] vector = byteVectorValues.getVector(); // Calculates a similarity score between the two vectors with a specified function. Higher similarity // scores correspond to closer vectors. - return spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector); + + // The query vector of Faiss byte vector is a Float array because ScalarQuantizer accepts it as float array. + // To compute the score between this query vector and each vector in KNNByteVectorValues we are casting this query vector into byte + // array directly. + // This is safe to do so because float query vector already has validated byte values. Do not reuse this direct cast at any other + // place. + final byte[] byteQueryVector = new byte[queryVector.length]; + for (int i = 0; i < queryVector.length; i++) { + byteQueryVector[i] = (byte) queryVector[i]; + } + return spaceType.getKnnVectorSimilarityFunction().compare(byteQueryVector, vector); } protected int getNextDocId() throws IOException { if (bitSetIterator == null) { - return binaryVectorValues.nextDoc(); + return byteVectorValues.nextDoc(); } int nextDocID = this.bitSetIterator.nextDoc(); // For filter case, advance vector values to corresponding doc id from filter bit set if (nextDocID != DocIdSetIterator.NO_MORE_DOCS) { - binaryVectorValues.advance(nextDocID); + byteVectorValues.advance(nextDocID); } return nextDocID; } diff --git a/src/main/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIterator.java new file mode 100644 index 000000000..97bf3517e --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIterator.java @@ -0,0 +1,77 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.opensearch.common.Nullable; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; + +import java.io.IOException; + +/** + * This iterator iterates filterIdsArray to scoreif filter is provided else it iterates over all docs. + * However, it dedupe docs per each parent doc + * of which ID is set in parentBitSet and only return best child doc with the highest score. + */ +public class NestedBinaryVectorIdsKNNIterator extends BinaryVectorIdsKNNIterator { + private final BitSet parentBitSet; + + public NestedBinaryVectorIdsKNNIterator( + @Nullable final BitSet filterIdsArray, + final byte[] queryVector, + final KNNBinaryVectorValues binaryVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet + ) throws IOException { + super(filterIdsArray, queryVector, binaryVectorValues, spaceType); + this.parentBitSet = parentBitSet; + } + + public NestedBinaryVectorIdsKNNIterator( + final byte[] queryVector, + final KNNBinaryVectorValues binaryVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet + ) throws IOException { + super(null, queryVector, binaryVectorValues, spaceType); + this.parentBitSet = parentBitSet; + } + + /** + * Advance to the next best child doc per parent and update score with the best score among child docs from the parent. + * DocIdSetIterator.NO_MORE_DOCS is returned when there is no more docs + * + * @return next best child doc id + */ + @Override + public int nextDoc() throws IOException { + if (docId == DocIdSetIterator.NO_MORE_DOCS) { + return DocIdSetIterator.NO_MORE_DOCS; + } + + currentScore = Float.NEGATIVE_INFINITY; + int currentParent = parentBitSet.nextSetBit(docId); + int bestChild = -1; + + // In order to traverse all children for given parent, we have to use docId < parentId, because, + // kNNVectorValues will not have parent id since DocId is unique per segment. For ex: let's say for doc id 1, there is one child + // and for doc id 5, there are three children. In that case knnVectorValues iterator will have [0, 2, 3, 4] + // and parentBitSet will have [1,5] + // Hence, we have to iterate till docId from knnVectorValues is less than parentId instead of till equal to parentId + while (docId != DocIdSetIterator.NO_MORE_DOCS && docId < currentParent) { + float score = computeScore(); + if (score > currentScore) { + bestChild = docId; + currentScore = score; + } + docId = getNextDocId(); + } + + return bestChild; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIterator.java index 3c93ec888..9644b620f 100644 --- a/src/main/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIterator.java +++ b/src/main/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIterator.java @@ -9,7 +9,7 @@ import org.apache.lucene.util.BitSet; import org.opensearch.common.Nullable; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNByteVectorValues; import java.io.IOException; @@ -23,18 +23,18 @@ public class NestedByteVectorIdsKNNIterator extends ByteVectorIdsKNNIterator { public NestedByteVectorIdsKNNIterator( @Nullable final BitSet filterIdsArray, - final byte[] queryVector, - final KNNBinaryVectorValues binaryVectorValues, + final float[] queryVector, + final KNNByteVectorValues byteVectorValues, final SpaceType spaceType, final BitSet parentBitSet ) throws IOException { - super(filterIdsArray, queryVector, binaryVectorValues, spaceType); + super(filterIdsArray, queryVector, byteVectorValues, spaceType); this.parentBitSet = parentBitSet; } public NestedByteVectorIdsKNNIterator( - final byte[] queryVector, - final KNNBinaryVectorValues binaryVectorValues, + final float[] queryVector, + final KNNByteVectorValues binaryVectorValues, final SpaceType spaceType, final BitSet parentBitSet ) throws IOException { diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIteratorTests.java new file mode 100644 index 000000000..6d5dffa98 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIteratorTests.java @@ -0,0 +1,97 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import junit.framework.TestCase; +import lombok.SneakyThrows; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.FixedBitSet; +import org.mockito.stubbing.OngoingStubbing; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class BinaryVectorIdsKNNIteratorTests extends TestCase { + @SneakyThrows + public void testNextDoc_whenCalled_IterateAllDocs() { + final SpaceType spaceType = SpaceType.HAMMING; + final byte[] queryVector = { 1, 2, 3 }; + final int[] filterIds = { 1, 2, 3 }; + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + + FixedBitSet filterBitSet = new FixedBitSet(4); + for (int id : filterIds) { + when(values.advance(id)).thenReturn(id); + filterBitSet.set(id); + } + + // Execute and verify + BinaryVectorIdsKNNIterator iterator = new BinaryVectorIdsKNNIterator(filterBitSet, queryVector, values, spaceType); + for (int i = 0; i < filterIds.length; i++) { + assertEquals(filterIds[i], iterator.nextDoc()); + assertEquals(expectedScores.get(i), (Float) iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + } + + @SneakyThrows + public void testNextDoc_whenCalled_thenIterateAllDocsWithoutFilter() throws IOException { + final SpaceType spaceType = SpaceType.HAMMING; + final byte[] queryVector = { 1, 2, 3 }; + final List dataVectors = Arrays.asList( + new byte[] { 11, 12, 13 }, + new byte[] { 14, 15, 16 }, + new byte[] { 17, 18, 19 }, + new byte[] { 20, 21, 22 }, + new byte[] { 23, 24, 25 } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + when(values.getVector()).thenReturn( + dataVectors.get(0), + dataVectors.get(1), + dataVectors.get(2), + dataVectors.get(3), + dataVectors.get(4) + ); + + // stub return value when nextDoc is called + OngoingStubbing stubbing = when(values.nextDoc()); + for (int i = 0; i < dataVectors.size(); i++) { + stubbing = stubbing.thenReturn(i); + } + // set last return to be Integer.MAX_VALUE to represent no more docs + stubbing.thenReturn(Integer.MAX_VALUE); + + // Execute and verify + BinaryVectorIdsKNNIterator iterator = new BinaryVectorIdsKNNIterator(queryVector, values, spaceType); + for (int i = 0; i < dataVectors.size(); i++) { + assertEquals(i, iterator.nextDoc()); + assertEquals(expectedScores.get(i), iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIteratorTests.java index 0b1b71286..60169b95f 100644 --- a/src/test/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIteratorTests.java +++ b/src/test/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIteratorTests.java @@ -11,7 +11,7 @@ import org.apache.lucene.util.FixedBitSet; import org.mockito.stubbing.OngoingStubbing; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNByteVectorValues; import java.io.IOException; import java.util.Arrays; @@ -26,16 +26,17 @@ public class ByteVectorIdsKNNIteratorTests extends TestCase { @SneakyThrows - public void testNextDoc_whenCalled_thenIterateAllDocs() { - final SpaceType spaceType = SpaceType.HAMMING; - final byte[] queryVector = { 1, 2, 3 }; + public void testNextDoc_whenCalled_IterateAllDocs() { + final SpaceType spaceType = SpaceType.L2; + final byte[] byteQueryVector = { 1, 2, 3 }; + final float[] queryVector = { 1f, 2f, 3f }; final int[] filterIds = { 1, 2, 3 }; final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); final List expectedScores = dataVectors.stream() - .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(byteQueryVector, vector)) .collect(Collectors.toList()); - KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + KNNByteVectorValues values = mock(KNNByteVectorValues.class); when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); FixedBitSet filterBitSet = new FixedBitSet(4); @@ -48,15 +49,16 @@ public void testNextDoc_whenCalled_thenIterateAllDocs() { ByteVectorIdsKNNIterator iterator = new ByteVectorIdsKNNIterator(filterBitSet, queryVector, values, spaceType); for (int i = 0; i < filterIds.length; i++) { assertEquals(filterIds[i], iterator.nextDoc()); - assertEquals(expectedScores.get(i), iterator.score()); + assertEquals(expectedScores.get(i), (Float) iterator.score()); } assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); } @SneakyThrows public void testNextDoc_whenCalled_thenIterateAllDocsWithoutFilter() throws IOException { - final SpaceType spaceType = SpaceType.HAMMING; - final byte[] queryVector = { 1, 2, 3 }; + final SpaceType spaceType = SpaceType.L2; + final byte[] byteQueryVector = { 1, 2, 3 }; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; final List dataVectors = Arrays.asList( new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, @@ -65,10 +67,10 @@ public void testNextDoc_whenCalled_thenIterateAllDocsWithoutFilter() throws IOEx new byte[] { 23, 24, 25 } ); final List expectedScores = dataVectors.stream() - .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(byteQueryVector, vector)) .collect(Collectors.toList()); - KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + KNNByteVectorValues values = mock(KNNByteVectorValues.class); when(values.getVector()).thenReturn( dataVectors.get(0), dataVectors.get(1), diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIteratorTests.java new file mode 100644 index 000000000..a39a3b2e9 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIteratorTests.java @@ -0,0 +1,91 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import junit.framework.TestCase; +import lombok.SneakyThrows; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.FixedBitSet; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class NestedBinaryVectorIdsKNNIteratorTests extends TestCase { + @SneakyThrows + public void testNextDoc_whenIterate_ReturnBestChildDocsPerParent() { + final SpaceType spaceType = SpaceType.HAMMING; + final byte[] queryVector = { 1, 2, 3 }; + final int[] filterIds = { 0, 2, 3 }; + // Parent id for 0 -> 1 + // Parent id for 2, 3 -> 4 + // In bit representation, it is 10010. In long, it is 18. + final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + + FixedBitSet filterBitSet = new FixedBitSet(4); + for (int id : filterIds) { + when(values.advance(id)).thenReturn(id); + filterBitSet.set(id); + } + + // Execute and verify + NestedBinaryVectorIdsKNNIterator iterator = new NestedBinaryVectorIdsKNNIterator( + filterBitSet, + queryVector, + values, + spaceType, + parentBitSet + ); + assertEquals(filterIds[0], iterator.nextDoc()); + assertEquals(expectedScores.get(0), iterator.score()); + assertEquals(filterIds[2], iterator.nextDoc()); + assertEquals(expectedScores.get(2), iterator.score()); + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + } + + @SneakyThrows + public void testNextDoc_whenIterateWithoutFilters_thenReturnBestChildDocsPerParent() { + final SpaceType spaceType = SpaceType.HAMMING; + final byte[] queryVector = { 1, 2, 3 }; + // Parent id for 0 -> 1 + // Parent id for 2, 3 -> 4 + // In bit representation, it is 10010. In long, it is 18. + final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + when(values.nextDoc()).thenReturn(0, 2, 3, Integer.MAX_VALUE); + + // Execute and verify + NestedBinaryVectorIdsKNNIterator iterator = new NestedBinaryVectorIdsKNNIterator(queryVector, values, spaceType, parentBitSet); + assertEquals(0, iterator.nextDoc()); + assertEquals(expectedScores.get(0), iterator.score()); + assertEquals(3, iterator.nextDoc()); + assertEquals(expectedScores.get(2), iterator.score()); + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIteratorTests.java index eff021234..08c859779 100644 --- a/src/test/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIteratorTests.java +++ b/src/test/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIteratorTests.java @@ -11,7 +11,7 @@ import org.apache.lucene.util.BitSet; import org.apache.lucene.util.FixedBitSet; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNByteVectorValues; import java.util.Arrays; import java.util.List; @@ -26,19 +26,20 @@ public class NestedByteVectorIdsKNNIteratorTests extends TestCase { @SneakyThrows public void testNextDoc_whenIterate_ReturnBestChildDocsPerParent() { - final SpaceType spaceType = SpaceType.HAMMING; - final byte[] queryVector = { 1, 2, 3 }; + final SpaceType spaceType = SpaceType.L2; + final byte[] byteQueryVector = { 1, 2, 3 }; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; final int[] filterIds = { 0, 2, 3 }; // Parent id for 0 -> 1 // Parent id for 2, 3 -> 4 // In bit representation, it is 10010. In long, it is 18. final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); - final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 17, 18, 19 }, new byte[] { 14, 15, 16 }); final List expectedScores = dataVectors.stream() - .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(byteQueryVector, vector)) .collect(Collectors.toList()); - KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + KNNByteVectorValues values = mock(KNNByteVectorValues.class); when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); FixedBitSet filterBitSet = new FixedBitSet(4); @@ -64,18 +65,19 @@ public void testNextDoc_whenIterate_ReturnBestChildDocsPerParent() { @SneakyThrows public void testNextDoc_whenIterateWithoutFilters_thenReturnBestChildDocsPerParent() { - final SpaceType spaceType = SpaceType.HAMMING; - final byte[] queryVector = { 1, 2, 3 }; + final SpaceType spaceType = SpaceType.L2; + final byte[] byteQueryVector = { 1, 2, 3 }; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; // Parent id for 0 -> 1 // Parent id for 2, 3 -> 4 // In bit representation, it is 10010. In long, it is 18. final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); - final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 17, 18, 19 }, new byte[] { 14, 15, 16 }); final List expectedScores = dataVectors.stream() - .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(byteQueryVector, vector)) .collect(Collectors.toList()); - KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + KNNByteVectorValues values = mock(KNNByteVectorValues.class); when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); when(values.nextDoc()).thenReturn(0, 2, 3, Integer.MAX_VALUE); diff --git a/src/test/java/org/opensearch/knn/integ/FilteredSearchByteIT.java b/src/test/java/org/opensearch/knn/integ/FilteredSearchByteIT.java new file mode 100644 index 000000000..fe4dc7db9 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/FilteredSearchByteIT.java @@ -0,0 +1,104 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.junit.After; +import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNJsonQueryBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.List; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +@Log4j2 +public class FilteredSearchByteIT extends KNNRestTestCase { + @After + public void cleanUp() { + try { + deleteKNNIndex(INDEX_NAME); + } catch (Exception e) { + log.error(e); + } + } + + @SneakyThrows + public void testFilteredSearchWithFaissHnswByte_whenDoingApproximateSearch_thenReturnCorrectResults() { + validateFilteredSearchWithFaissHnswByte(INDEX_NAME, false); + } + + @SneakyThrows + public void testFilteredSearchWithFaissHnswByte_whenDoingExactSearch_thenReturnCorrectResults() { + validateFilteredSearchWithFaissHnswByte(INDEX_NAME, true); + } + + private void validateFilteredSearchWithFaissHnswByte(final String indexName, final boolean doExactSearch) throws Exception { + String filterFieldName = "parking"; + createKnnByteIndex(indexName, FIELD_NAME, 3, KNNEngine.FAISS); + + for (byte i = 1; i < 4; i++) { + addKnnDocWithAttributes( + indexName, + Integer.toString(i), + FIELD_NAME, + new float[] { i, i, i }, + ImmutableMap.of(filterFieldName, i % 2 == 1 ? "true" : "false") + ); + } + refreshIndex(indexName); + forceMergeKnnIndex(indexName); + + // Set it as 0 for approximate search and 100(larger than number of filtered id) for exact search + updateIndexSettings( + indexName, + Settings.builder().put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, doExactSearch ? 100 : 0) + ); + + Float[] queryVector = { 3f, 3f, 3f }; + String query = KNNJsonQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(3) + .filterFieldName(filterFieldName) + .filterValue("true") + .build() + .getQueryString(); + Response response = searchKNNIndex(indexName, query, 3); + String entity = EntityUtils.toString(response.getEntity()); + List docIds = parseIds(entity); + assertEquals(2, docIds.size()); + assertEquals("3", docIds.get(0)); + assertEquals("1", docIds.get(1)); + assertEquals(2, parseTotalSearchHits(entity)); + } + + private void createKnnByteIndex(final String indexName, final String fieldName, final int dimension, final KNNEngine knnEngine) + throws Exception { + KNNJsonIndexMappingsBuilder.Method method = KNNJsonIndexMappingsBuilder.Method.builder() + .methodName(METHOD_HNSW) + .engine(knnEngine.getName()) + .build(); + + String knnIndexMapping = KNNJsonIndexMappingsBuilder.builder() + .fieldName(fieldName) + .dimension(dimension) + .vectorDataType(VectorDataType.BYTE.getValue()) + .method(method) + .build() + .getIndexMapping(); + + createKnnIndex(indexName, knnIndexMapping); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/NestedSearchByteIT.java b/src/test/java/org/opensearch/knn/integ/NestedSearchByteIT.java new file mode 100644 index 000000000..7985d08a7 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/NestedSearchByteIT.java @@ -0,0 +1,156 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.junit.After; +import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNJsonQueryBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.NestedKnnDocBuilder; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.List; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +@Log4j2 +public class NestedSearchByteIT extends KNNRestTestCase { + @After + public void cleanUp() { + try { + deleteKNNIndex(INDEX_NAME); + } catch (Exception e) { + log.error(e); + } + } + + @SneakyThrows + public void testNestedSearchWithFaissHnswByte_whenKIsTwo_thenReturnTwoResults() { + String nestedFieldName = "nested"; + createKnnByteIndexWithNestedField(INDEX_NAME, nestedFieldName, FIELD_NAME, 2, KNNEngine.FAISS); + + int totalDocCount = 15; + for (byte i = 0; i < totalDocCount; i++) { + String doc = NestedKnnDocBuilder.create(nestedFieldName) + .addVectors(FIELD_NAME, new Byte[] { i, i }, new Byte[] { i, i }) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + Byte[] queryVector = { 14, 14 }; + String query = KNNJsonQueryBuilder.builder() + .nestedFieldName(nestedFieldName) + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(2) + .build() + .getQueryString(); + Response response = searchKNNIndex(INDEX_NAME, query, 2); + String entity = EntityUtils.toString(response.getEntity()); + + assertEquals(2, parseHits(entity)); + assertEquals(2, parseTotalSearchHits(entity)); + assertEquals("14", parseIds(entity).get(0)); + assertNotEquals("14", parseIds(entity).get(1)); + } + + /** + * { + * "query": { + * "nested": { + * "path": "test_nested", + * "query": { + * "knn": { + * "test_nested.test_vector": { + * "vector": [ + * 1, 1, 1 + * ], + * "k": 3, + * "filter": { + * "term": { + * "parking": "true" + * } + * } + * } + * } + * } + * } + * } + * } + * + */ + @SneakyThrows + public void testNestedSearchWithFaissHnswByte_whenDoingExactSearch_thenReturnCorrectResults() { + String nestedFieldName = "nested"; + String filterFieldName = "parking"; + createKnnByteIndexWithNestedField(INDEX_NAME, nestedFieldName, FIELD_NAME, 3, KNNEngine.FAISS); + + for (byte i = 1; i < 4; i++) { + String doc = NestedKnnDocBuilder.create(nestedFieldName) + .addVectors(FIELD_NAME, new Byte[] { i, i, i }, new Byte[] { i, i, i }, new Byte[] { i, i, i }) + .addTopLevelField(filterFieldName, i % 2 == 1 ? "true" : "false") + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + // Make it as an exact search by setting the threshold larger than size of filteredIds(6) + updateIndexSettings(INDEX_NAME, Settings.builder().put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, 100)); + + Byte[] queryVector = { 3, 3, 3 }; + String query = KNNJsonQueryBuilder.builder() + .nestedFieldName(nestedFieldName) + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(3) + .filterFieldName(filterFieldName) + .filterValue("true") + .build() + .getQueryString(); + Response response = searchKNNIndex(INDEX_NAME, query, 3); + String entity = EntityUtils.toString(response.getEntity()); + List docIds = parseIds(entity); + assertEquals(2, docIds.size()); + assertEquals("3", docIds.get(0)); + assertEquals("1", docIds.get(1)); + assertEquals(2, parseTotalSearchHits(entity)); + } + + private void createKnnByteIndexWithNestedField( + final String indexName, + final String nestedFieldName, + final String fieldName, + final int dimension, + final KNNEngine knnEngine + ) throws Exception { + KNNJsonIndexMappingsBuilder.Method method = KNNJsonIndexMappingsBuilder.Method.builder() + .methodName(METHOD_HNSW) + .engine(knnEngine.getName()) + .build(); + + String knnIndexMapping = KNNJsonIndexMappingsBuilder.builder() + .nestedFieldName(nestedFieldName) + .fieldName(fieldName) + .dimension(dimension) + .vectorDataType(VectorDataType.BYTE.getValue()) + .method(method) + .build() + .getIndexMapping(); + + createKnnIndex(indexName, knnIndexMapping); + } +} From 07f4df2015a0af5158859c42c98511b865005eac Mon Sep 17 00:00:00 2001 From: Vikasht34 Date: Mon, 30 Sep 2024 17:17:53 -0700 Subject: [PATCH 31/59] Adding Support to Enable/Disble Share level Rescoring and Update Oversampling Factor (#2172) Signed-off-by: VIKASH TIWARI --- CHANGELOG.md | 1 + .../org/opensearch/knn/index/KNNSettings.java | 36 ++++++++- .../knn/index/mapper/CompressionLevel.java | 31 ++++---- .../nativelib/NativeEngineKnnVectorQuery.java | 6 +- .../index/query/rescore/RescoreContext.java | 9 +++ .../knn/index/KNNSettingsTests.java | 35 +++++++++ .../index/mapper/CompressionLevelTests.java | 73 ++++++++++++------- .../NativeEngineKNNVectorQueryTests.java | 71 +++++++++++++++++- 8 files changed, 214 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5615509de..6871074f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Optimize reduceToTopK in ResultUtil by removing pre-filling and reducing peek calls [#2146](https://github.com/opensearch-project/k-NN/pull/2146) * Update Default Rescore Context based on Dimension [#2149](https://github.com/opensearch-project/k-NN/pull/2149) * KNNIterators should support with and without filters [#2155](https://github.com/opensearch-project/k-NN/pull/2155) +* Adding Support to Enable/Disble Share level Rescoring and Update Oversampling Factor[#2172](https://github.com/opensearch-project/k-NN/pull/2172) ### Bug Fixes * KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) ### Infrastructure diff --git a/src/main/java/org/opensearch/knn/index/KNNSettings.java b/src/main/java/org/opensearch/knn/index/KNNSettings.java index 5fcc51bb5..1753140e6 100644 --- a/src/main/java/org/opensearch/knn/index/KNNSettings.java +++ b/src/main/java/org/opensearch/knn/index/KNNSettings.java @@ -88,6 +88,7 @@ public class KNNSettings { public static final String QUANTIZATION_STATE_CACHE_SIZE_LIMIT = "knn.quantization.cache.size.limit"; public static final String QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES = "knn.quantization.cache.expiry.minutes"; public static final String KNN_FAISS_AVX512_DISABLED = "knn.faiss.avx512.disabled"; + public static final String KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED = "index.knn.disk.vector.shard_level_rescoring_disabled"; /** * Default setting values @@ -112,11 +113,31 @@ public class KNNSettings { public static final Integer KNN_MAX_QUANTIZATION_STATE_CACHE_SIZE_LIMIT_PERCENTAGE = 10; // Quantization state cache limit cannot exceed // 10% of the JVM heap public static final Integer KNN_DEFAULT_QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES = 60; + public static final boolean KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_VALUE = true; /** * Settings Definition */ + /** + * This setting controls whether shard-level re-scoring for KNN disk-based vectors is turned off. + * The setting uses: + *
      + *
    • KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED: The name of the setting.
    • + *
    • KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_VALUE: The default value (true or false).
    • + *
    • IndexScope: The setting works at the index level.
    • + *
    • Dynamic: This setting can be changed without restarting the cluster.
    • + *
    + * + * @see Setting#boolSetting(String, boolean, Setting.Property...) + */ + public static final Setting KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_SETTING = Setting.boolSetting( + KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED, + KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_VALUE, + IndexScope, + Dynamic + ); + // This setting controls how much memory should be used to transfer vectors from Java to JNI Layer. The default // 1% of the JVM heap public static final Setting KNN_VECTOR_STREAMING_MEMORY_LIMIT_PCT_SETTING = Setting.memorySizeSetting( @@ -454,6 +475,10 @@ private Setting getSetting(String key) { return QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING; } + if (KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED.equals(key)) { + return KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_SETTING; + } + throw new IllegalArgumentException("Cannot find setting by key [" + key + "]"); } @@ -475,7 +500,8 @@ public List> getSettings() { KNN_VECTOR_STREAMING_MEMORY_LIMIT_PCT_SETTING, KNN_FAISS_AVX512_DISABLED_SETTING, QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, - QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING + QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING, + KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_SETTING ); return Stream.concat(settings.stream(), Stream.concat(getFeatureFlags().stream(), dynamicCacheSettings.values().stream())) .collect(Collectors.toList()); @@ -528,6 +554,14 @@ public static Integer getFilteredExactSearchThreshold(final String indexName) { .getAsInt(ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_DEFAULT_VALUE); } + public static boolean isShardLevelRescoringDisabledForDiskBasedVector(String indexName) { + return KNNSettings.state().clusterService.state() + .getMetadata() + .index(indexName) + .getSettings() + .getAsBoolean(KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED, true); + } + public void initialize(Client client, ClusterService clusterService) { this.client = client; this.clusterService = clusterService; diff --git a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java index 3e1b47db7..c9a169efc 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java +++ b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java @@ -97,32 +97,35 @@ public static boolean isConfigured(CompressionLevel compressionLevel) { /** * Returns the appropriate {@link RescoreContext} based on the given {@code mode} and {@code dimension}. * - *

    If the {@code mode} is present in the valid {@code modesForRescore} set, the method checks the value of - * {@code dimension}: + *

    If the {@code mode} is present in the valid {@code modesForRescore} set, the method adjusts the oversample factor based on the + * {@code dimension} value: *

      - *
    • If {@code dimension} is less than or equal to 1000, it returns a {@link RescoreContext} with an - * oversample factor of 5.0f.
    • - *
    • If {@code dimension} is greater than 1000, it returns the default {@link RescoreContext} associated with - * the {@link CompressionLevel}. If no default is set, it falls back to {@link RescoreContext#getDefault()}.
    • + *
    • If {@code dimension} is greater than or equal to 1000, no oversampling is applied (oversample factor = 1.0).
    • + *
    • If {@code dimension} is greater than or equal to 768 but less than 1000, a 2x oversample factor is applied (oversample factor = 2.0).
    • + *
    • If {@code dimension} is less than 768, a 3x oversample factor is applied (oversample factor = 3.0).
    • *
    - * If the {@code mode} is not valid, the method returns {@code null}. + * If the {@code mode} is not present in the {@code modesForRescore} set, the method returns {@code null}. * * @param mode The {@link Mode} for which to retrieve the {@link RescoreContext}. * @param dimension The dimensional value that determines the {@link RescoreContext} behavior. - * @return A {@link RescoreContext} with an oversample factor of 5.0f if {@code dimension} is less than - * or equal to 1000, the default {@link RescoreContext} if greater, or {@code null} if the mode - * is invalid. + * @return A {@link RescoreContext} with the appropriate oversample factor based on the dimension, or {@code null} if the mode + * is not valid. */ public RescoreContext getDefaultRescoreContext(Mode mode, int dimension) { if (modesForRescore.contains(mode)) { // Adjust RescoreContext based on dimension - if (dimension <= RescoreContext.DIMENSION_THRESHOLD) { - // For dimensions <= 1000, return a RescoreContext with 5.0f oversample factor - return RescoreContext.builder().oversampleFactor(RescoreContext.OVERSAMPLE_FACTOR_BELOW_DIMENSION_THRESHOLD).build(); + if (dimension >= RescoreContext.DIMENSION_THRESHOLD_1000) { + // No oversampling for dimensions >= 1000 + return RescoreContext.builder().oversampleFactor(RescoreContext.OVERSAMPLE_FACTOR_1000).build(); + } else if (dimension >= RescoreContext.DIMENSION_THRESHOLD_768) { + // 2x oversampling for dimensions >= 768 but < 1000 + return RescoreContext.builder().oversampleFactor(RescoreContext.OVERSAMPLE_FACTOR_768).build(); } else { - return defaultRescoreContext; + // 3x oversampling for dimensions < 768 + return RescoreContext.builder().oversampleFactor(RescoreContext.OVERSAMPLE_FACTOR_BELOW_768).build(); } } return null; } + } diff --git a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java index 945da850a..adb2875d5 100644 --- a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java +++ b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java @@ -20,6 +20,7 @@ import org.apache.lucene.util.BitSet; import org.apache.lucene.util.Bits; import org.opensearch.common.StopWatch; +import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.query.ExactSearcher; import org.opensearch.knn.index.query.KNNQuery; import org.opensearch.knn.index.query.KNNWeight; @@ -54,7 +55,6 @@ public Weight createWeight(IndexSearcher indexSearcher, ScoreMode scoreMode, flo final IndexReader reader = indexSearcher.getIndexReader(); final KNNWeight knnWeight = (KNNWeight) knnQuery.createWeight(indexSearcher, ScoreMode.COMPLETE, 1); List leafReaderContexts = reader.leaves(); - List> perLeafResults; RescoreContext rescoreContext = knnQuery.getRescoreContext(); int finalK = knnQuery.getK(); @@ -63,7 +63,9 @@ public Weight createWeight(IndexSearcher indexSearcher, ScoreMode scoreMode, flo } else { int firstPassK = rescoreContext.getFirstPassK(finalK); perLeafResults = doSearch(indexSearcher, leafReaderContexts, knnWeight, firstPassK); - ResultUtil.reduceToTopK(perLeafResults, firstPassK); + if (KNNSettings.isShardLevelRescoringDisabledForDiskBasedVector(knnQuery.getIndexName()) == false) { + ResultUtil.reduceToTopK(perLeafResults, firstPassK); + } StopWatch stopWatch = new StopWatch().start(); perLeafResults = doRescore(indexSearcher, leafReaderContexts, knnWeight, perLeafResults, finalK); diff --git a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java index 51d4e491c..a2563b2a6 100644 --- a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java +++ b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java @@ -24,6 +24,15 @@ public final class RescoreContext { public static final int DIMENSION_THRESHOLD = 1000; public static final float OVERSAMPLE_FACTOR_BELOW_DIMENSION_THRESHOLD = 5.0f; + // Dimension thresholds for adjusting oversample factor + public static final int DIMENSION_THRESHOLD_1000 = 1000; + public static final int DIMENSION_THRESHOLD_768 = 768; + + // Oversample factors based on dimension thresholds + public static final float OVERSAMPLE_FACTOR_1000 = 1.0f; // No oversampling for dimensions >= 1000 + public static final float OVERSAMPLE_FACTOR_768 = 2.0f; // 2x oversampling for dimensions >= 768 and < 1000 + public static final float OVERSAMPLE_FACTOR_BELOW_768 = 3.0f; // 3x oversampling for dimensions < 768 + // Todo:- We will improve this in upcoming releases public static final int MIN_FIRST_PASS_RESULTS = 100; diff --git a/src/test/java/org/opensearch/knn/index/KNNSettingsTests.java b/src/test/java/org/opensearch/knn/index/KNNSettingsTests.java index 75eb14713..fd25699cc 100644 --- a/src/test/java/org/opensearch/knn/index/KNNSettingsTests.java +++ b/src/test/java/org/opensearch/knn/index/KNNSettingsTests.java @@ -158,6 +158,41 @@ public void testGetEfSearch_whenEFSearchValueSetByUser_thenReturnValue() { assertEquals(userProvidedEfSearch, efSearchValue); } + @SneakyThrows + public void testShardLevelRescoringDisabled_whenNoValuesProvidedByUser_thenDefaultSettingsUsed() { + Node mockNode = createMockNode(Collections.emptyMap()); + mockNode.start(); + ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); + mockNode.client().admin().cluster().state(new ClusterStateRequest()).actionGet(); + mockNode.client().admin().indices().create(new CreateIndexRequest(INDEX_NAME)).actionGet(); + KNNSettings.state().setClusterService(clusterService); + + boolean shardLevelRescoringDisabled = KNNSettings.isShardLevelRescoringDisabledForDiskBasedVector(INDEX_NAME); + mockNode.close(); + assertTrue(shardLevelRescoringDisabled); + } + + @SneakyThrows + public void testShardLevelRescoringDisabled_whenValueProvidedByUser_thenSettingApplied() { + boolean userDefinedRescoringDisabled = false; + Node mockNode = createMockNode(Collections.emptyMap()); + mockNode.start(); + ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); + mockNode.client().admin().cluster().state(new ClusterStateRequest()).actionGet(); + mockNode.client().admin().indices().create(new CreateIndexRequest(INDEX_NAME)).actionGet(); + KNNSettings.state().setClusterService(clusterService); + + final Settings rescoringDisabledSetting = Settings.builder() + .put(KNNSettings.KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED, userDefinedRescoringDisabled) + .build(); + + mockNode.client().admin().indices().updateSettings(new UpdateSettingsRequest(rescoringDisabledSetting, INDEX_NAME)).actionGet(); + + boolean shardLevelRescoringDisabled = KNNSettings.isShardLevelRescoringDisabledForDiskBasedVector(INDEX_NAME); + mockNode.close(); + assertEquals(userDefinedRescoringDisabled, shardLevelRescoringDisabled); + } + @SneakyThrows public void testGetFaissAVX2DisabledSettingValueFromConfig_enableSetting_thenValidateAndSucceed() { boolean expectedKNNFaissAVX2Disabled = true; diff --git a/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java b/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java index cc70d4c2d..57372b11e 100644 --- a/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java +++ b/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java @@ -44,65 +44,84 @@ public void testIsConfigured() { public void testGetDefaultRescoreContext() { // Test rescore context for ON_DISK mode Mode mode = Mode.ON_DISK; - int belowThresholdDimension = 500; // A dimension below the threshold - int aboveThresholdDimension = 1500; // A dimension above the threshold - // x32 with dimension <= 1000 should have an oversample factor of 5.0f + // Test various dimensions based on the updated oversampling logic + int belowThresholdDimension = 500; // A dimension below 768 + int between768and1000Dimension = 800; // A dimension between 768 and 1000 + int above1000Dimension = 1500; // A dimension above 1000 + + // Compression level x32 with dimension < 768 should have an oversample factor of 3.0f RescoreContext rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, belowThresholdDimension); assertNotNull(rescoreContext); - assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); + assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); - // x32 with dimension > 1000 should have an oversample factor of 3.0f - rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, aboveThresholdDimension); + // Compression level x32 with dimension between 768 and 1000 should have an oversample factor of 2.0f + rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, between768and1000Dimension); assertNotNull(rescoreContext); - assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); + assertEquals(2.0f, rescoreContext.getOversampleFactor(), 0.0f); - // x16 with dimension <= 1000 should have an oversample factor of 5.0f - rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, belowThresholdDimension); + // Compression level x32 with dimension > 1000 should have no oversampling (1.0f) + rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, above1000Dimension); assertNotNull(rescoreContext); - assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); + assertEquals(1.0f, rescoreContext.getOversampleFactor(), 0.0f); - // x16 with dimension > 1000 should have an oversample factor of 3.0f - rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, aboveThresholdDimension); + // Compression level x16 with dimension < 768 should have an oversample factor of 3.0f + rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, belowThresholdDimension); assertNotNull(rescoreContext); assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); - // x8 with dimension <= 1000 should have an oversample factor of 5.0f + // Compression level x16 with dimension between 768 and 1000 should have an oversample factor of 2.0f + rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, between768and1000Dimension); + assertNotNull(rescoreContext); + assertEquals(2.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // Compression level x16 with dimension > 1000 should have no oversampling (1.0f) + rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, above1000Dimension); + assertNotNull(rescoreContext); + assertEquals(1.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // Compression level x8 with dimension < 768 should have an oversample factor of 3.0f rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, belowThresholdDimension); assertNotNull(rescoreContext); - assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); + assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); - // x8 with dimension > 1000 should have an oversample factor of 2.0f - rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, aboveThresholdDimension); + // Compression level x8 with dimension between 768 and 1000 should have an oversample factor of 2.0f + rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, between768and1000Dimension); assertNotNull(rescoreContext); assertEquals(2.0f, rescoreContext.getOversampleFactor(), 0.0f); - // x4 with dimension <= 1000 should have an oversample factor of 5.0f (though it doesn't have its own RescoreContext) + // Compression level x8 with dimension > 1000 should have no oversampling (1.0f) + rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, above1000Dimension); + assertNotNull(rescoreContext); + assertEquals(1.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // Compression level x4 with dimension < 768 should return null (no RescoreContext) rescoreContext = CompressionLevel.x4.getDefaultRescoreContext(mode, belowThresholdDimension); assertNull(rescoreContext); - // x4 with dimension > 1000 should return null (no RescoreContext is configured for x4) - rescoreContext = CompressionLevel.x4.getDefaultRescoreContext(mode, aboveThresholdDimension); - assertNull(rescoreContext); - // Other compression levels should behave similarly with respect to dimension + // Compression level x4 with dimension > 1000 should return null (no RescoreContext) + rescoreContext = CompressionLevel.x4.getDefaultRescoreContext(mode, above1000Dimension); + assertNull(rescoreContext); + // Compression level x2 with dimension < 768 should return null rescoreContext = CompressionLevel.x2.getDefaultRescoreContext(mode, belowThresholdDimension); assertNull(rescoreContext); - // x2 with dimension > 1000 should return null - rescoreContext = CompressionLevel.x2.getDefaultRescoreContext(mode, aboveThresholdDimension); + // Compression level x2 with dimension > 1000 should return null + rescoreContext = CompressionLevel.x2.getDefaultRescoreContext(mode, above1000Dimension); assertNull(rescoreContext); + // Compression level x1 with dimension < 768 should return null rescoreContext = CompressionLevel.x1.getDefaultRescoreContext(mode, belowThresholdDimension); assertNull(rescoreContext); - // x1 with dimension > 1000 should return null - rescoreContext = CompressionLevel.x1.getDefaultRescoreContext(mode, aboveThresholdDimension); + // Compression level x1 with dimension > 1000 should return null + rescoreContext = CompressionLevel.x1.getDefaultRescoreContext(mode, above1000Dimension); assertNull(rescoreContext); - // NOT_CONFIGURED with dimension <= 1000 should return a RescoreContext with an oversample factor of 5.0f + // NOT_CONFIGURED mode should return null for any dimension rescoreContext = CompressionLevel.NOT_CONFIGURED.getDefaultRescoreContext(mode, belowThresholdDimension); assertNull(rescoreContext); - } + } diff --git a/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java index 06350f39c..7fd96c6df 100644 --- a/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java +++ b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java @@ -17,11 +17,16 @@ import org.apache.lucene.search.TaskExecutor; import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.Weight; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TotalHits; import org.apache.lucene.util.Bits; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.invocation.InvocationOnMock; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.query.KNNQuery; import org.opensearch.knn.index.query.KNNWeight; import org.opensearch.knn.index.query.ResultUtil; @@ -35,12 +40,11 @@ import java.util.Map; import java.util.concurrent.Callable; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.times; import static org.mockito.MockitoAnnotations.openMocks; public class NativeEngineKNNVectorQueryTests extends OpenSearchTestCase { @@ -66,6 +70,9 @@ public class NativeEngineKNNVectorQueryTests extends OpenSearchTestCase { @Mock private LeafReader leafReader2; + @Mock + private ClusterService clusterService; + @InjectMocks private NativeEngineKnnVectorQuery objectUnderTest; @@ -91,6 +98,11 @@ public void setUp() throws Exception { }); when(reader.getContext()).thenReturn(indexReaderContext); + + when(clusterService.state()).thenReturn(mock(ClusterState.class)); // Mock ClusterState + + // Set ClusterService in KNNSettings + KNNSettings.state().setClusterService(clusterService); } @SneakyThrows @@ -127,6 +139,49 @@ public void testMultiLeaf() { assertEquals(expected, actual.getQuery()); } + @SneakyThrows + public void testRescoreWhenShardLevelRescoringEnabled() { + // Given + List leaves = List.of(leaf1, leaf2); + when(reader.leaves()).thenReturn(leaves); + + int k = 2; + int firstPassK = 3; + Map initialLeaf1Results = new HashMap<>(Map.of(0, 21f, 1, 19f, 2, 17f)); + Map initialLeaf2Results = new HashMap<>(Map.of(0, 20f, 1, 18f, 2, 16f)); + Map rescoredLeaf1Results = new HashMap<>(Map.of(0, 18f, 1, 20f)); + Map rescoredLeaf2Results = new HashMap<>(Map.of(0, 21f)); + + when(knnQuery.getRescoreContext()).thenReturn(RescoreContext.builder().oversampleFactor(1.5f).build()); + when(knnQuery.getK()).thenReturn(k); + when(knnWeight.getQuery()).thenReturn(knnQuery); + when(knnWeight.searchLeaf(leaf1, firstPassK)).thenReturn(initialLeaf1Results); + when(knnWeight.searchLeaf(leaf2, firstPassK)).thenReturn(initialLeaf2Results); + when(knnWeight.exactSearch(eq(leaf1), any())).thenReturn(rescoredLeaf1Results); + when(knnWeight.exactSearch(eq(leaf2), any())).thenReturn(rescoredLeaf2Results); + + try ( + MockedStatic mockedKnnSettings = mockStatic(KNNSettings.class); + MockedStatic mockedResultUtil = mockStatic(ResultUtil.class) + ) { + + // When shard-level re-scoring is enabled + mockedKnnSettings.when(() -> KNNSettings.isShardLevelRescoringDisabledForDiskBasedVector(any())).thenReturn(false); + + // Mock ResultUtil to return valid TopDocs + mockedResultUtil.when(() -> ResultUtil.resultMapToTopDocs(any(), anyInt())) + .thenReturn(new TopDocs(new TotalHits(0, TotalHits.Relation.EQUAL_TO), new ScoreDoc[0])); + mockedResultUtil.when(() -> ResultUtil.reduceToTopK(any(), anyInt())).thenCallRealMethod(); + + // When + Weight actual = objectUnderTest.createWeight(searcher, ScoreMode.COMPLETE, 1); + + // Then + mockedResultUtil.verify(() -> ResultUtil.reduceToTopK(any(), anyInt()), times(2)); + assertNotNull(actual); + } + } + @SneakyThrows public void testSingleLeaf() { // Given @@ -188,7 +243,15 @@ public void testRescore() { when(knnWeight.exactSearch(eq(leaf1), any())).thenReturn(rescoredLeaf1Results); when(knnWeight.exactSearch(eq(leaf2), any())).thenReturn(rescoredLeaf2Results); - try (MockedStatic mockedResultUtil = mockStatic(ResultUtil.class)) { + + try ( + MockedStatic mockedKnnSettings = mockStatic(KNNSettings.class); + MockedStatic mockedResultUtil = mockStatic(ResultUtil.class) + ) { + + // When shard-level re-scoring is enabled + mockedKnnSettings.when(() -> KNNSettings.isShardLevelRescoringDisabledForDiskBasedVector(any())).thenReturn(true); + mockedResultUtil.when(() -> ResultUtil.reduceToTopK(any(), anyInt())).thenAnswer(InvocationOnMock::callRealMethod); mockedResultUtil.when(() -> ResultUtil.resultMapToTopDocs(eq(rescoredLeaf1Results), anyInt())).thenAnswer(t -> topDocs1); mockedResultUtil.when(() -> ResultUtil.resultMapToTopDocs(eq(rescoredLeaf2Results), anyInt())).thenAnswer(t -> topDocs2); From f3b2bd08e32dc12a08fb6917c018c6bbbfa4bd3f Mon Sep 17 00:00:00 2001 From: Doo Yong Kim <0ctopus13prime@gmail.com> Date: Thu, 3 Oct 2024 09:14:24 -0700 Subject: [PATCH 32/59] Introducing a loading layer in FAISS native engine. (#2139) * Introducing a loading layer in FAISS native engine. Signed-off-by: Dooyong Kim * Update change log. Signed-off-by: Dooyong Kim * Added unit tests for Faiss stream support. Signed-off-by: Dooyong Kim * Fix a bug to pass a KB size integer value as a byte size integer parameter. Signed-off-by: Dooyong Kim * Fix a casting bugs when it tries to laod more than 4G sized index file. Signed-off-by: Dooyong Kim * Added unit tests for new methods in JNIService. Signed-off-by: Dooyong Kim * Fix formatting and removed nmslib_stream_support. Signed-off-by: Dooyong Kim * Removing redundant exception message in JNIService.loadIndex. Signed-off-by: Dooyong Kim * Fix a flaky testing - testIndexAllocation_closeBlocking Signed-off-by: Dooyong Kim --------- Signed-off-by: Dooyong Kim Signed-off-by: Doo Yong Kim <0ctopus13prime@gmail.com> Co-authored-by: Dooyong Kim --- CHANGELOG.md | 1 + jni/CMakeLists.txt | 1 + jni/include/faiss_stream_support.h | 136 +++++++++++++ jni/include/faiss_wrapper.h | 10 + jni/include/jni_util.h | 102 ++++++---- .../org_opensearch_knn_jni_FaissService.h | 16 ++ jni/src/faiss_wrapper.cpp | 28 +++ jni/src/jni_util.cpp | 28 +++ .../org_opensearch_knn_jni_FaissService.cpp | 43 ++++ jni/tests/commons_test.cpp | 2 +- jni/tests/faiss_stream_support_test.cpp | 132 ++++++++++++ jni/tests/test_util.h | 8 + .../opensearch/knn/index/KNNIndexShard.java | 3 + .../index/memory/NativeMemoryAllocation.java | 34 ++-- .../memory/NativeMemoryEntryContext.java | 48 ++--- .../memory/NativeMemoryLoadStrategy.java | 62 +++++- .../opensearch/knn/index/query/KNNWeight.java | 1 + .../knn/index/store/IndexInputWithBuffer.java | 46 +++++ .../org/opensearch/knn/jni/FaissService.java | 19 ++ .../org/opensearch/knn/jni/JNIService.java | 23 +++ .../memory/NativeMemoryAllocationTests.java | 24 ++- .../memory/NativeMemoryEntryContextTests.java | 5 + .../memory/NativeMemoryLoadStrategyTests.java | 6 + .../opensearch/knn/jni/JNIServiceTests.java | 189 ++++++++++++++++++ 24 files changed, 864 insertions(+), 103 deletions(-) create mode 100644 jni/include/faiss_stream_support.h create mode 100644 jni/tests/faiss_stream_support_test.cpp create mode 100644 src/main/java/org/opensearch/knn/index/store/IndexInputWithBuffer.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 6871074f0..267f6248d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased 3.0](https://github.com/opensearch-project/k-NN/compare/2.x...HEAD) ### Features ### Enhancements +* Introducing a loading layer in FAISS [#2033](https://github.com/opensearch-project/k-NN/issues/2033) ### Bug Fixes * Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) ### Infrastructure diff --git a/jni/CMakeLists.txt b/jni/CMakeLists.txt index 3c071fc1f..4caa907b3 100644 --- a/jni/CMakeLists.txt +++ b/jni/CMakeLists.txt @@ -154,6 +154,7 @@ if ("${WIN32}" STREQUAL "") tests/nmslib_wrapper_unit_test.cpp tests/test_util.cpp tests/commons_test.cpp + tests/faiss_stream_support_test.cpp tests/faiss_index_service_test.cpp ) diff --git a/jni/include/faiss_stream_support.h b/jni/include/faiss_stream_support.h new file mode 100644 index 000000000..65f1631d4 --- /dev/null +++ b/jni/include/faiss_stream_support.h @@ -0,0 +1,136 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H +#define OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H + +#include "faiss/impl/io.h" +#include "jni_util.h" + +#include +#include +#include +#include + +namespace knn_jni { +namespace stream { + +/** + * This class contains Java IndexInputWithBuffer reference and calls its API to copy required bytes into a read buffer. + */ + +class NativeEngineIndexInputMediator { + public: + // Expect IndexInputWithBuffer is given as `_indexInput`. + NativeEngineIndexInputMediator(JNIUtilInterface *_jni_interface, + JNIEnv *_env, + jobject _indexInput) + : jni_interface(_jni_interface), + env(_env), + indexInput(_indexInput), + bufferArray((jbyteArray) (_jni_interface->GetObjectField(_env, + _indexInput, + getBufferFieldId(_jni_interface, _env)))), + copyBytesMethod(getCopyBytesMethod(_jni_interface, _env)) { + } + + void copyBytes(int64_t nbytes, uint8_t *destination) { + while (nbytes > 0) { + // Call `copyBytes` to read bytes as many as possible. + const auto readBytes = + jni_interface->CallIntMethodLong(env, indexInput, copyBytesMethod, nbytes); + + // === Critical Section Start === + + // Get primitive array pointer, no copy is happening in OpenJDK. + auto primitiveArray = + (jbyte *) jni_interface->GetPrimitiveArrayCritical(env, bufferArray, nullptr); + + // Copy Java bytes to C++ destination address. + std::memcpy(destination, primitiveArray, readBytes); + + // Release the acquired primitive array pointer. + // JNI_ABORT tells JVM to directly free memory without copying back to Java byte[]. + // Since we're merely copying data, we don't need to copying back. + jni_interface->ReleasePrimitiveArrayCritical(env, bufferArray, primitiveArray, JNI_ABORT); + + // === Critical Section End === + + destination += readBytes; + nbytes -= readBytes; + } // End while + } + + private: + static jclass getIndexInputWithBufferClass(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jclass INDEX_INPUT_WITH_BUFFER_CLASS = + jni_interface->FindClassFromJNIEnv(env, "org/opensearch/knn/index/store/IndexInputWithBuffer"); + return INDEX_INPUT_WITH_BUFFER_CLASS; + } + + static jmethodID getCopyBytesMethod(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jmethodID COPY_METHOD_ID = + jni_interface->GetMethodID(env, getIndexInputWithBufferClass(jni_interface, env), "copyBytes", "(J)I"); + return COPY_METHOD_ID; + } + + static jfieldID getBufferFieldId(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jfieldID BUFFER_FIELD_ID = + jni_interface->GetFieldID(env, getIndexInputWithBufferClass(jni_interface, env), "buffer", "[B"); + return BUFFER_FIELD_ID; + } + + JNIUtilInterface *jni_interface; + JNIEnv *env; + + // `IndexInputWithBuffer` instance having `IndexInput` instance obtained from `Directory` for reading. + jobject indexInput; + jbyteArray bufferArray; + jmethodID copyBytesMethod; +}; // class NativeEngineIndexInputMediator + + + +/** + * A glue component inheriting IOReader to be passed down to Faiss library. + * This will then indirectly call the mediator component and eventually read required bytes from Lucene's IndexInput. + */ +class FaissOpenSearchIOReader final : public faiss::IOReader { + public: + explicit FaissOpenSearchIOReader(NativeEngineIndexInputMediator *_mediator) + : faiss::IOReader(), + mediator(_mediator) { + name = "FaissOpenSearchIOReader"; + } + + size_t operator()(void *ptr, size_t size, size_t nitems) final { + const auto readBytes = size * nitems; + if (readBytes > 0) { + // Mediator calls IndexInput, then copy read bytes to `ptr`. + mediator->copyBytes(readBytes, (uint8_t *) ptr); + } + return nitems; + } + + int filedescriptor() final { + throw std::runtime_error("filedescriptor() is not supported in FaissOpenSearchIOReader."); + } + + private: + NativeEngineIndexInputMediator *mediator; +}; // class FaissOpenSearchIOReader + + + +} +} + +#endif //OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H diff --git a/jni/include/faiss_wrapper.h b/jni/include/faiss_wrapper.h index d6375653d..8ffce4ad1 100644 --- a/jni/include/faiss_wrapper.h +++ b/jni/include/faiss_wrapper.h @@ -47,11 +47,21 @@ namespace knn_jni { // Return a pointer to the loaded index jlong LoadIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ); + // Loads an index with a reader implemented IOReader + // + // Returns a pointer of the loaded index + jlong LoadIndexWithStream(faiss::IOReader* ioReader); + // Load a binary index from indexPathJ into memory. // // Return a pointer to the loaded index jlong LoadBinaryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ); + // Loads a binary index with a reader implemented IOReader + // + // Returns a pointer of the loaded index + jlong LoadBinaryIndexWithStream(faiss::IOReader* ioReader); + // Check if a loaded index requires shared state bool IsSharedIndexStateRequired(jlong indexPointerJ); diff --git a/jni/include/jni_util.h b/jni/include/jni_util.h index 825471a3c..6b1b926e7 100644 --- a/jni/include/jni_util.h +++ b/jni/include/jni_util.h @@ -22,8 +22,7 @@ namespace knn_jni { // Interface for making calls to JNI - class JNIUtilInterface { - public: + struct JNIUtilInterface { // -------------------------- EXCEPTION HANDLING ---------------------------- // Takes the name of a Java exception type and a message and throws the corresponding exception // to the JVM @@ -127,56 +126,77 @@ namespace knn_jni { virtual void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte * buf) = 0; + virtual jobject GetObjectField(JNIEnv * env, jobject obj, jfieldID fieldID) = 0; + + virtual jclass FindClassFromJNIEnv(JNIEnv * env, const char *name) = 0; + + virtual jmethodID GetMethodID(JNIEnv * env, jclass clazz, const char *name, const char *sig) = 0; + + virtual jfieldID GetFieldID(JNIEnv * env, jclass clazz, const char *name, const char *sig) = 0; + + virtual void * GetPrimitiveArrayCritical(JNIEnv * env, jarray array, jboolean *isCopy) = 0; + + virtual void ReleasePrimitiveArrayCritical(JNIEnv * env, jarray array, void *carray, jint mode) = 0; + + virtual jint CallIntMethodLong(JNIEnv * env, jobject obj, jmethodID methodID, int64_t longArg) = 0; + // -------------------------------------------------------------------------- }; jobject GetJObjectFromMapOrThrow(std::unordered_map map, std::string key); // Class that implements JNIUtilInterface methods - class JNIUtil: public JNIUtilInterface { + class JNIUtil final : public JNIUtilInterface { public: // Initialize and Uninitialize methods are used for caching/cleaning up Java classes and methods void Initialize(JNIEnv* env); void Uninitialize(JNIEnv* env); - void ThrowJavaException(JNIEnv* env, const char* type = "", const char* message = ""); - void HasExceptionInStack(JNIEnv* env); - void HasExceptionInStack(JNIEnv* env, const std::string& message); - void CatchCppExceptionAndThrowJava(JNIEnv* env); - jclass FindClass(JNIEnv * env, const std::string& className); - jmethodID FindMethod(JNIEnv * env, const std::string& className, const std::string& methodName); - std::string ConvertJavaStringToCppString(JNIEnv * env, jstring javaString); - std::unordered_map ConvertJavaMapToCppMap(JNIEnv *env, jobject parametersJ); - std::string ConvertJavaObjectToCppString(JNIEnv *env, jobject objectJ); - int ConvertJavaObjectToCppInteger(JNIEnv *env, jobject objectJ); - std::vector Convert2dJavaObjectArrayToCppFloatVector(JNIEnv *env, jobjectArray array2dJ, int dim); - std::vector ConvertJavaIntArrayToCppIntVector(JNIEnv *env, jintArray arrayJ); - int GetInnerDimensionOf2dJavaFloatArray(JNIEnv *env, jobjectArray array2dJ); - int GetInnerDimensionOf2dJavaByteArray(JNIEnv *env, jobjectArray array2dJ); - int GetJavaObjectArrayLength(JNIEnv *env, jobjectArray arrayJ); - int GetJavaIntArrayLength(JNIEnv *env, jintArray arrayJ); - int GetJavaLongArrayLength(JNIEnv *env, jlongArray arrayJ); - int GetJavaBytesArrayLength(JNIEnv *env, jbyteArray arrayJ); - int GetJavaFloatArrayLength(JNIEnv *env, jfloatArray arrayJ); - - void DeleteLocalRef(JNIEnv *env, jobject obj); - jbyte * GetByteArrayElements(JNIEnv *env, jbyteArray array, jboolean * isCopy); - jfloat * GetFloatArrayElements(JNIEnv *env, jfloatArray array, jboolean * isCopy); - jint * GetIntArrayElements(JNIEnv *env, jintArray array, jboolean * isCopy); - jlong * GetLongArrayElements(JNIEnv *env, jlongArray array, jboolean * isCopy); - jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index); - jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodId, int id, float distance); - jobjectArray NewObjectArray(JNIEnv *env, jsize len, jclass clazz, jobject init); - jbyteArray NewByteArray(JNIEnv *env, jsize len); - void ReleaseByteArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, int mode); - void ReleaseFloatArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, int mode); - void ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode); - void ReleaseLongArrayElements(JNIEnv *env, jlongArray array, jlong *elems, jint mode); - void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject val); - void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte * buf); - void Convert2dJavaObjectArrayAndStoreToFloatVector(JNIEnv *env, jobjectArray array2dJ, int dim, std::vector *vect); - void Convert2dJavaObjectArrayAndStoreToBinaryVector(JNIEnv *env, jobjectArray array2dJ, int dim, std::vector *vect); - void Convert2dJavaObjectArrayAndStoreToByteVector(JNIEnv *env, jobjectArray array2dJ, int dim, std::vector *vect); + void ThrowJavaException(JNIEnv* env, const char* type = "", const char* message = "") final; + void HasExceptionInStack(JNIEnv* env) final; + void HasExceptionInStack(JNIEnv* env, const std::string& message) final; + void CatchCppExceptionAndThrowJava(JNIEnv* env) final; + jclass FindClass(JNIEnv * env, const std::string& className) final; + jmethodID FindMethod(JNIEnv * env, const std::string& className, const std::string& methodName) final; + std::string ConvertJavaStringToCppString(JNIEnv * env, jstring javaString) final; + std::unordered_map ConvertJavaMapToCppMap(JNIEnv *env, jobject parametersJ) final; + std::string ConvertJavaObjectToCppString(JNIEnv *env, jobject objectJ) final; + int ConvertJavaObjectToCppInteger(JNIEnv *env, jobject objectJ) final; + std::vector Convert2dJavaObjectArrayToCppFloatVector(JNIEnv *env, jobjectArray array2dJ, int dim) final; + std::vector ConvertJavaIntArrayToCppIntVector(JNIEnv *env, jintArray arrayJ) final; + int GetInnerDimensionOf2dJavaFloatArray(JNIEnv *env, jobjectArray array2dJ) final; + int GetInnerDimensionOf2dJavaByteArray(JNIEnv *env, jobjectArray array2dJ) final; + int GetJavaObjectArrayLength(JNIEnv *env, jobjectArray arrayJ) final; + int GetJavaIntArrayLength(JNIEnv *env, jintArray arrayJ) final; + int GetJavaLongArrayLength(JNIEnv *env, jlongArray arrayJ) final; + int GetJavaBytesArrayLength(JNIEnv *env, jbyteArray arrayJ) final; + int GetJavaFloatArrayLength(JNIEnv *env, jfloatArray arrayJ) final; + + void DeleteLocalRef(JNIEnv *env, jobject obj) final; + jbyte * GetByteArrayElements(JNIEnv *env, jbyteArray array, jboolean * isCopy) final; + jfloat * GetFloatArrayElements(JNIEnv *env, jfloatArray array, jboolean * isCopy) final; + jint * GetIntArrayElements(JNIEnv *env, jintArray array, jboolean * isCopy) final; + jlong * GetLongArrayElements(JNIEnv *env, jlongArray array, jboolean * isCopy) final; + jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index) final; + jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodId, int id, float distance) final; + jobjectArray NewObjectArray(JNIEnv *env, jsize len, jclass clazz, jobject init) final; + jbyteArray NewByteArray(JNIEnv *env, jsize len) final; + void ReleaseByteArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, int mode) final; + void ReleaseFloatArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, int mode) final; + void ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode) final; + void ReleaseLongArrayElements(JNIEnv *env, jlongArray array, jlong *elems, jint mode) final; + void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject val) final; + void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte * buf) final; + void Convert2dJavaObjectArrayAndStoreToFloatVector(JNIEnv *env, jobjectArray array2dJ, int dim, std::vector *vect) final; + void Convert2dJavaObjectArrayAndStoreToBinaryVector(JNIEnv *env, jobjectArray array2dJ, int dim, std::vector *vect) final; + void Convert2dJavaObjectArrayAndStoreToByteVector(JNIEnv *env, jobjectArray array2dJ, int dim, std::vector *vect) final; + jobject GetObjectField(JNIEnv * env, jobject obj, jfieldID fieldID) final; + jclass FindClassFromJNIEnv(JNIEnv * env, const char *name) final; + jmethodID GetMethodID(JNIEnv * env, jclass clazz, const char *name, const char *sig) final; + jfieldID GetFieldID(JNIEnv * env, jclass clazz, const char *name, const char *sig) final; + jint CallIntMethodLong(JNIEnv * env, jobject obj, jmethodID methodID, int64_t longArg) final; + void * GetPrimitiveArrayCritical(JNIEnv * env, jarray array, jboolean *isCopy) final; + void ReleasePrimitiveArrayCritical(JNIEnv * env, jarray array, void *carray, jint mode) final; private: std::unordered_map cachedClasses; diff --git a/jni/include/org_opensearch_knn_jni_FaissService.h b/jni/include/org_opensearch_knn_jni_FaissService.h index d42ce197c..2969df3ae 100644 --- a/jni/include/org_opensearch_knn_jni_FaissService.h +++ b/jni/include/org_opensearch_knn_jni_FaissService.h @@ -128,6 +128,14 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createIndexFromT JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndex (JNIEnv *, jclass, jstring); +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: loadIndexWithStream + * Signature: (Lorg/opensearch/knn/index/util/IndexInputWithBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndexWithStream + (JNIEnv *, jclass, jobject); + /* * Class: org_opensearch_knn_jni_FaissService * Method: loadBinaryIndex @@ -136,6 +144,14 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndex JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndex (JNIEnv *, jclass, jstring); +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: loadBinaryIndexWithStream + * Signature: (Lorg/opensearch/knn/index/util/IndexInputWithBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndexWithStream + (JNIEnv *, jclass, jobject); + /* * Class: org_opensearch_knn_jni_FaissService * Method: isSharedIndexStateRequired diff --git a/jni/src/faiss_wrapper.cpp b/jni/src/faiss_wrapper.cpp index 45548e0f7..d1c7648dc 100644 --- a/jni/src/faiss_wrapper.cpp +++ b/jni/src/faiss_wrapper.cpp @@ -423,6 +423,20 @@ jlong knn_jni::faiss_wrapper::LoadIndex(knn_jni::JNIUtilInterface * jniUtil, JNI return (jlong) indexReader; } +jlong knn_jni::faiss_wrapper::LoadIndexWithStream(faiss::IOReader* ioReader) { + if (ioReader == nullptr) [[unlikely]] { + throw std::runtime_error("IOReader cannot be null"); + } + + faiss::Index* indexReader = + faiss::read_index(ioReader, + faiss::IO_FLAG_READ_ONLY + | faiss::IO_FLAG_PQ_SKIP_SDC_TABLE + | faiss::IO_FLAG_SKIP_PRECOMPUTE_TABLE); + + return (jlong) indexReader; +} + jlong knn_jni::faiss_wrapper::LoadBinaryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ) { if (indexPathJ == nullptr) { throw std::runtime_error("Index path cannot be null"); @@ -436,6 +450,20 @@ jlong knn_jni::faiss_wrapper::LoadBinaryIndex(knn_jni::JNIUtilInterface * jniUti return (jlong) indexReader; } +jlong knn_jni::faiss_wrapper::LoadBinaryIndexWithStream(faiss::IOReader* ioReader) { + if (ioReader == nullptr) [[unlikely]] { + throw std::runtime_error("IOReader cannot be null"); + } + + faiss::IndexBinary* indexReader = + faiss::read_index_binary(ioReader, + faiss::IO_FLAG_READ_ONLY + | faiss::IO_FLAG_PQ_SKIP_SDC_TABLE + | faiss::IO_FLAG_SKIP_PRECOMPUTE_TABLE); + + return (jlong) indexReader; +} + bool knn_jni::faiss_wrapper::IsSharedIndexStateRequired(jlong indexPointerJ) { auto * index = reinterpret_cast(indexPointerJ); return isIndexIVFPQL2(index); diff --git a/jni/src/jni_util.cpp b/jni/src/jni_util.cpp index 82900b5ce..3eaf3b0a1 100644 --- a/jni/src/jni_util.cpp +++ b/jni/src/jni_util.cpp @@ -547,6 +547,34 @@ void knn_jni::JNIUtil::SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize s this->HasExceptionInStack(env, "Unable to set byte array region"); } +jobject knn_jni::JNIUtil::GetObjectField(JNIEnv * env, jobject obj, jfieldID fieldID) { + return env->GetObjectField(obj, fieldID); +} + +jclass knn_jni::JNIUtil::FindClassFromJNIEnv(JNIEnv * env, const char *name) { + return env->FindClass(name); +} + +jmethodID knn_jni::JNIUtil::GetMethodID(JNIEnv * env, jclass clazz, const char *name, const char *sig) { + return env->GetMethodID(clazz, name, sig); +} + +jfieldID knn_jni::JNIUtil::GetFieldID(JNIEnv * env, jclass clazz, const char *name, const char *sig) { + return env->GetFieldID(clazz, name, sig); +} + +jint knn_jni::JNIUtil::CallIntMethodLong(JNIEnv * env, jobject obj, jmethodID methodID, int64_t longArg) { + return env->CallIntMethod(obj, methodID, longArg); +} + +void * knn_jni::JNIUtil::GetPrimitiveArrayCritical(JNIEnv * env, jarray array, jboolean *isCopy) { + return env->GetPrimitiveArrayCritical(array, isCopy); +} + +void knn_jni::JNIUtil::ReleasePrimitiveArrayCritical(JNIEnv * env, jarray array, void *carray, jint mode) { + return env->ReleasePrimitiveArrayCritical(array, carray, mode); +} + jobject knn_jni::GetJObjectFromMapOrThrow(std::unordered_map map, std::string key) { if(map.find(key) == map.end()) { throw std::runtime_error(key + " not found"); diff --git a/jni/src/org_opensearch_knn_jni_FaissService.cpp b/jni/src/org_opensearch_knn_jni_FaissService.cpp index 70c986b7d..7326c7ba0 100644 --- a/jni/src/org_opensearch_knn_jni_FaissService.cpp +++ b/jni/src/org_opensearch_knn_jni_FaissService.cpp @@ -17,6 +17,7 @@ #include "faiss_wrapper.h" #include "jni_util.h" +#include "faiss_stream_support.h" static knn_jni::JNIUtil jniUtil; static const jint KNN_FAISS_JNI_VERSION = JNI_VERSION_1_1; @@ -217,6 +218,27 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndex(JNIEn return NULL; } +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndexWithStream + (JNIEnv * env, jclass cls, jobject readStream) +{ + try { + // Create a mediator locally. + // Note that `indexInput` is `IndexInputWithBuffer` type. + knn_jni::stream::NativeEngineIndexInputMediator mediator {&jniUtil, env, readStream}; + + // Wrap the mediator with a glue code inheriting IOReader. + knn_jni::stream::FaissOpenSearchIOReader faissOpenSearchIOReader {&mediator}; + + // Pass IOReader to Faiss for loading vector index. + return knn_jni::faiss_wrapper::LoadIndexWithStream( + &faissOpenSearchIOReader); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + + return NULL; +} + JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndex(JNIEnv * env, jclass cls, jstring indexPathJ) { try { @@ -227,6 +249,27 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndex return NULL; } +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndexWithStream + (JNIEnv * env, jclass cls, jobject readStream) +{ + try { + // Create a mediator locally. + // Note that `indexInput` is `IndexInputWithBuffer` type. + knn_jni::stream::NativeEngineIndexInputMediator mediator {&jniUtil, env, readStream}; + + // Wrap the mediator with a glue code inheriting IOReader. + knn_jni::stream::FaissOpenSearchIOReader faissOpenSearchIOReader {&mediator}; + + // Pass IOReader to Faiss for loading vector index. + return knn_jni::faiss_wrapper::LoadBinaryIndexWithStream( + &faissOpenSearchIOReader); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + + return NULL; +} + JNIEXPORT jboolean JNICALL Java_org_opensearch_knn_jni_FaissService_isSharedIndexStateRequired (JNIEnv * env, jclass cls, jlong indexPointerJ) { diff --git a/jni/tests/commons_test.cpp b/jni/tests/commons_test.cpp index d469fe268..39d7f3c99 100644 --- a/jni/tests/commons_test.cpp +++ b/jni/tests/commons_test.cpp @@ -179,7 +179,7 @@ TEST(StoreByteVectorTest, BasicAssertions) { } // Check that freeing vector data works - knn_jni::commons::freeVectorData(memoryAddress); + knn_jni::commons::freeBinaryVectorData(memoryAddress); } TEST(CommonTests, GetIntegerMethodParam) { diff --git a/jni/tests/faiss_stream_support_test.cpp b/jni/tests/faiss_stream_support_test.cpp new file mode 100644 index 000000000..4045985bb --- /dev/null +++ b/jni/tests/faiss_stream_support_test.cpp @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// The OpenSearch Contributors require contributions made to +// this file be licensed under the Apache-2.0 license or a +// compatible open source license. +// +// Modifications Copyright OpenSearch Contributors. See +// GitHub history for details. + +#include "faiss_stream_support.h" +#include +#include "test_util.h" +#include +#include +#include +#include + +using ::testing::Return; +using knn_jni::stream::FaissOpenSearchIOReader; +using knn_jni::stream::NativeEngineIndexInputMediator; +using test_util::MockJNIUtil; + +// Mocking IndexInputWithBuffer. +struct JavaIndexInputMock { + JavaIndexInputMock(std::string _readTargetBytes, int32_t _bufSize) + : readTargetBytes(std::move(_readTargetBytes)), + nextReadIdx(), + buffer(_bufSize) { + } + + // This method is simulating `copyBytes` in IndexInputWithBuffer. + int32_t simulateCopyReads(int64_t readBytes) { + readBytes = std::min(readBytes, (int64_t) buffer.size()); + readBytes = std::min(readBytes, (int64_t) (readTargetBytes.size() - nextReadIdx)); + std::memcpy(buffer.data(), readTargetBytes.data() + nextReadIdx, readBytes); + nextReadIdx += readBytes; + return (int32_t) readBytes; + } + + static std::string makeRandomBytes(int32_t bytesSize) { + // Define the list of possible characters + static const string CHARACTERS + = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv" + "wxyz0123456789"; + + // Create a random number generator + std::random_device rd; + std::mt19937 generator(rd()); + + // Create a distribution to uniformly select from all characters + std::uniform_int_distribution<> distribution( + 0, CHARACTERS.size() - 1); + + // Pre-allocate the string with the desired length + std::string randomString(bytesSize, '\0'); + + // Use generate_n with a back_inserter iterator + std::generate_n(randomString.begin(), bytesSize, [&]() { + return CHARACTERS[distribution(generator)]; + }); + + return randomString; + } + + std::string readTargetBytes; + int64_t nextReadIdx; + std::vector buffer; +}; // struct JavaIndexInputMock + +void setUpMockJNIUtil(JavaIndexInputMock &javaIndexInputMock, MockJNIUtil &mockJni) { + // Set up mocking values + mocking behavior in a method. + ON_CALL(mockJni, FindClassFromJNIEnv).WillByDefault(Return((jclass) 1)); + ON_CALL(mockJni, GetMethodID).WillByDefault(Return((jmethodID) 1)); + ON_CALL(mockJni, GetFieldID).WillByDefault(Return((jfieldID) 1)); + ON_CALL(mockJni, GetObjectField).WillByDefault(Return((jobject) 1)); + ON_CALL(mockJni, CallIntMethodLong).WillByDefault([&javaIndexInputMock](JNIEnv *env, + jobject obj, + jmethodID methodID, + int64_t longArg) { + return javaIndexInputMock.simulateCopyReads(longArg); + }); + ON_CALL(mockJni, GetPrimitiveArrayCritical).WillByDefault([&javaIndexInputMock](JNIEnv *env, + jarray array, + jboolean *isCopy) { + return (jbyte *) javaIndexInputMock.buffer.data(); + }); + ON_CALL(mockJni, ReleasePrimitiveArrayCritical).WillByDefault(Return()); +} + +TEST(FaissStreamSupportTest, NativeEngineIndexInputMediatorCopyWhenEmpty) { + for (auto contentSize : std::vector{0, 2222, 7777, 1024, 77, 1}) { + // Set up mockings + MockJNIUtil mockJni; + JavaIndexInputMock javaIndexInputMock{ + JavaIndexInputMock::makeRandomBytes(contentSize), 1024}; + setUpMockJNIUtil(javaIndexInputMock, mockJni); + + // Prepare copying + NativeEngineIndexInputMediator mediator{&mockJni, nullptr, nullptr}; + std::string readBuffer(javaIndexInputMock.readTargetBytes.size(), '\0'); + + // Call copyBytes + mediator.copyBytes((int32_t) javaIndexInputMock.readTargetBytes.size(), (uint8_t *) readBuffer.data()); + + // Expected that we acquired the same contents as readTargetBytes + ASSERT_EQ(javaIndexInputMock.readTargetBytes, readBuffer); + } // End for +} + +TEST(FaissStreamSupportTest, FaissOpenSearchIOReaderCopy) { + for (auto contentSize : std::vector{0, 2222, 7777, 1024, 77, 1}) { + // Set up mockings + MockJNIUtil mockJni; + JavaIndexInputMock javaIndexInputMock{ + JavaIndexInputMock::makeRandomBytes(contentSize), 1024}; + setUpMockJNIUtil(javaIndexInputMock, mockJni); + + // Prepare copying + NativeEngineIndexInputMediator mediator{&mockJni, nullptr, nullptr}; + std::string readBuffer; + readBuffer.resize(javaIndexInputMock.readTargetBytes.size()); + FaissOpenSearchIOReader ioReader{&mediator}; + + // Read bytes + const auto readBytes = + ioReader((void *) readBuffer.data(), 1, javaIndexInputMock.readTargetBytes.size()); + + // Expected that we acquired the same contents as readTargetBytes + ASSERT_EQ(javaIndexInputMock.readTargetBytes.size(), readBytes); + ASSERT_EQ(javaIndexInputMock.readTargetBytes, readBuffer); + } // End for +} diff --git a/jni/tests/test_util.h b/jni/tests/test_util.h index ea02da6f2..286000c08 100644 --- a/jni/tests/test_util.h +++ b/jni/tests/test_util.h @@ -106,6 +106,14 @@ namespace test_util { (JNIEnv * env, jobjectArray array, jsize index, jobject val)); MOCK_METHOD(void, ThrowJavaException, (JNIEnv * env, const char* type, const char* message)); + MOCK_METHOD(jobject, GetObjectField, + (JNIEnv * env, jobject obj, jfieldID fieldID)); + MOCK_METHOD(jclass, FindClassFromJNIEnv, (JNIEnv * env, const char *name)); + MOCK_METHOD(jmethodID, GetMethodID, (JNIEnv * env, jclass clazz, const char *name, const char *sig)); + MOCK_METHOD(jfieldID, GetFieldID, (JNIEnv * env, jclass clazz, const char *name, const char *sig)); + MOCK_METHOD(jint, CallIntMethodLong, (JNIEnv * env, jobject obj, jmethodID methodID, int64_t longArg)); + MOCK_METHOD(void *, GetPrimitiveArrayCritical, (JNIEnv * env, jarray array, jboolean *isCopy)); + MOCK_METHOD(void, ReleasePrimitiveArrayCritical, (JNIEnv * env, jarray array, void *carray, jint mode)); }; // For our unit tests, we want to ensure that each test tests one function in diff --git a/src/main/java/org/opensearch/knn/index/KNNIndexShard.java b/src/main/java/org/opensearch/knn/index/KNNIndexShard.java index 5c096e4f7..4f339cb4e 100644 --- a/src/main/java/org/opensearch/knn/index/KNNIndexShard.java +++ b/src/main/java/org/opensearch/knn/index/KNNIndexShard.java @@ -13,6 +13,7 @@ import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.SegmentReader; +import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.FilterDirectory; import org.opensearch.common.lucene.Lucene; @@ -89,11 +90,13 @@ public String getIndexName() { */ public void warmup() throws IOException { log.info("[KNN] Warming up index: [{}]", getIndexName()); + final Directory directory = indexShard.store().directory(); try (Engine.Searcher searcher = indexShard.acquireSearcher("knn-warmup")) { getAllEngineFileContexts(searcher.getIndexReader()).forEach((engineFileContext) -> { try { nativeMemoryCacheManager.get( new NativeMemoryEntryContext.IndexEntryContext( + directory, engineFileContext.getIndexPath(), NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), getParametersAtLoading( diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java index 635bc3883..8adf35447 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java @@ -110,7 +110,7 @@ class IndexAllocation implements NativeMemoryAllocation { private final ExecutorService executor; private final long memoryAddress; - private final int size; + private final int sizeKb; private volatile boolean closed; @Getter private final KNNEngine knnEngine; @@ -130,7 +130,7 @@ class IndexAllocation implements NativeMemoryAllocation { * * @param executorService Executor service used to close the allocation * @param memoryAddress Pointer in memory to the index - * @param size Size this index consumes in kilobytes + * @param sizeKb Size this index consumes in kilobytes * @param knnEngine KNNEngine associated with the index allocation * @param indexPath File path to index * @param openSearchIndexName Name of OpenSearch index this index is associated with @@ -139,13 +139,13 @@ class IndexAllocation implements NativeMemoryAllocation { IndexAllocation( ExecutorService executorService, long memoryAddress, - int size, + int sizeKb, KNNEngine knnEngine, String indexPath, String openSearchIndexName, WatcherHandle watcherHandle ) { - this(executorService, memoryAddress, size, knnEngine, indexPath, openSearchIndexName, watcherHandle, null, false); + this(executorService, memoryAddress, sizeKb, knnEngine, indexPath, openSearchIndexName, watcherHandle, null, false); } /** @@ -153,7 +153,7 @@ class IndexAllocation implements NativeMemoryAllocation { * * @param executorService Executor service used to close the allocation * @param memoryAddress Pointer in memory to the index - * @param size Size this index consumes in kilobytes + * @param sizeKb Size this index consumes in kilobytes * @param knnEngine KNNEngine associated with the index allocation * @param indexPath File path to index * @param openSearchIndexName Name of OpenSearch index this index is associated with @@ -163,7 +163,7 @@ class IndexAllocation implements NativeMemoryAllocation { IndexAllocation( ExecutorService executorService, long memoryAddress, - int size, + int sizeKb, KNNEngine knnEngine, String indexPath, String openSearchIndexName, @@ -178,7 +178,7 @@ class IndexAllocation implements NativeMemoryAllocation { this.openSearchIndexName = openSearchIndexName; this.memoryAddress = memoryAddress; this.readWriteLock = new ReentrantReadWriteLock(); - this.size = size; + this.sizeKb = sizeKb; this.watcherHandle = watcherHandle; this.sharedIndexState = sharedIndexState; this.isBinaryIndex = isBinaryIndex; @@ -187,9 +187,12 @@ class IndexAllocation implements NativeMemoryAllocation { protected void closeInternal() { Runnable onClose = () -> { - writeLock(); - cleanup(); - writeUnlock(); + try { + writeLock(); + cleanup(); + } finally { + writeUnlock(); + } }; // The close operation needs to be blocking to prevent overflow @@ -269,7 +272,7 @@ public void writeUnlock() { @Override public int getSizeInKB() { - return size; + return sizeKb; } @Override @@ -325,9 +328,12 @@ public TrainingDataAllocation(ExecutorService executor, long memoryAddress, int @Override public void close() { executor.execute(() -> { - writeLock(); - cleanup(); - writeUnlock(); + try { + writeLock(); + cleanup(); + } finally { + writeUnlock(); + } }); } diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java index dd219593d..00bf023f9 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java @@ -12,6 +12,7 @@ package org.opensearch.knn.index.memory; import lombok.Getter; +import org.apache.lucene.store.Directory; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.Nullable; import org.opensearch.knn.index.engine.qframe.QuantizationConfig; @@ -64,32 +65,40 @@ public String getKey() { public static class IndexEntryContext extends NativeMemoryEntryContext { + @Getter + private final Directory directory; private final NativeMemoryLoadStrategy.IndexLoadStrategy indexLoadStrategy; + @Getter private final String openSearchIndexName; + @Getter private final Map parameters; @Nullable + @Getter private final String modelId; /** * Constructor * - * @param indexPath path to index file. Also used as key in cache. - * @param indexLoadStrategy strategy to load index into memory - * @param parameters load time parameters - * @param openSearchIndexName opensearch index associated with index + * @param directory Lucene directory to create required IndexInput/IndexOutput to access files. + * @param indexPath Path to index file. Also used as key in cache. + * @param indexLoadStrategy Strategy to load index into memory + * @param parameters Load time parameters + * @param openSearchIndexName Opensearch index associated with index */ public IndexEntryContext( + Directory directory, String indexPath, NativeMemoryLoadStrategy.IndexLoadStrategy indexLoadStrategy, Map parameters, String openSearchIndexName ) { - this(indexPath, indexLoadStrategy, parameters, openSearchIndexName, null); + this(directory, indexPath, indexLoadStrategy, parameters, openSearchIndexName, null); } /** * Constructor * + * @param directory Lucene directory to create required IndexInput/IndexOutput to access files. * @param indexPath path to index file. Also used as key in cache. * @param indexLoadStrategy strategy to load index into memory * @param parameters load time parameters @@ -97,6 +106,7 @@ public IndexEntryContext( * @param modelId model to be loaded. If none available, pass null */ public IndexEntryContext( + Directory directory, String indexPath, NativeMemoryLoadStrategy.IndexLoadStrategy indexLoadStrategy, Map parameters, @@ -104,6 +114,7 @@ public IndexEntryContext( String modelId ) { super(indexPath); + this.directory = directory; this.indexLoadStrategy = indexLoadStrategy; this.openSearchIndexName = openSearchIndexName; this.parameters = parameters; @@ -120,33 +131,6 @@ public NativeMemoryAllocation.IndexAllocation load() throws IOException { return indexLoadStrategy.load(this); } - /** - * Getter for OpenSearch index name. - * - * @return OpenSearch index name - */ - public String getOpenSearchIndexName() { - return openSearchIndexName; - } - - /** - * Getter for parameters. - * - * @return parameters - */ - public Map getParameters() { - return parameters; - } - - /** - * Getter - * - * @return return model ID for the index. null if no model is in use - */ - public String getModelId() { - return modelId; - } - private static class IndexSizeCalculator implements Function { static IndexSizeCalculator INSTANCE = new IndexSizeCalculator(); diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java index 960c4f5f0..51158d00c 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java @@ -12,8 +12,12 @@ package org.opensearch.knn.index.memory; import lombok.extern.log4j.Log4j2; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; import org.opensearch.core.action.ActionListener; import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.store.IndexInputWithBuffer; import org.opensearch.knn.index.util.IndexUtil; import org.opensearch.knn.jni.JNIService; import org.opensearch.knn.index.engine.KNNEngine; @@ -87,9 +91,9 @@ public void onFileDeleted(Path indexFilePath) { }; } - @Override - public NativeMemoryAllocation.IndexAllocation load(NativeMemoryEntryContext.IndexEntryContext indexEntryContext) - throws IOException { + private NativeMemoryAllocation.IndexAllocation loadWithAbsoluteIndexPath( + NativeMemoryEntryContext.IndexEntryContext indexEntryContext + ) throws IOException { Path indexPath = Paths.get(indexEntryContext.getKey()); FileWatcher fileWatcher = new FileWatcher(indexPath); fileWatcher.addListener(indexFileOnDeleteListener); @@ -97,6 +101,54 @@ public NativeMemoryAllocation.IndexAllocation load(NativeMemoryEntryContext.Inde KNNEngine knnEngine = KNNEngine.getEngineNameFromPath(indexPath.toString()); long indexAddress = JNIService.loadIndex(indexPath.toString(), indexEntryContext.getParameters(), knnEngine); + return createIndexAllocation( + indexEntryContext, + knnEngine, + indexAddress, + fileWatcher, + indexEntryContext.calculateSizeInKB(), + indexPath + ); + } + + @Override + public NativeMemoryAllocation.IndexAllocation load(NativeMemoryEntryContext.IndexEntryContext indexEntryContext) + throws IOException { + final Path absoluteIndexPath = Paths.get(indexEntryContext.getKey()); + final KNNEngine knnEngine = KNNEngine.getEngineNameFromPath(absoluteIndexPath.toString()); + if (knnEngine != KNNEngine.FAISS) { + // We will support other non-FAISS native engines (ex: NMSLIB) soon. + return loadWithAbsoluteIndexPath(indexEntryContext); + } + + final FileWatcher fileWatcher = new FileWatcher(absoluteIndexPath); + fileWatcher.addListener(indexFileOnDeleteListener); + fileWatcher.init(); + + final Directory directory = indexEntryContext.getDirectory(); + + // Ex: Input -> /a/b/c/_0_NativeEngines990KnnVectorsFormat_0.vec + // Output -> _0_NativeEngines990KnnVectorsFormat_0.vec + final String logicalIndexPath = absoluteIndexPath.getFileName().toString(); + + final int indexSizeKb = Math.toIntExact(directory.fileLength(logicalIndexPath) / 1024); + + try (IndexInput readStream = directory.openInput(logicalIndexPath, IOContext.READONCE)) { + IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(readStream); + long indexAddress = JNIService.loadIndex(indexInputWithBuffer, indexEntryContext.getParameters(), knnEngine); + + return createIndexAllocation(indexEntryContext, knnEngine, indexAddress, fileWatcher, indexSizeKb, absoluteIndexPath); + } + } + + private NativeMemoryAllocation.IndexAllocation createIndexAllocation( + final NativeMemoryEntryContext.IndexEntryContext indexEntryContext, + final KNNEngine knnEngine, + final long indexAddress, + final FileWatcher fileWatcher, + final int indexSizeKb, + final Path absoluteIndexPath + ) throws IOException { SharedIndexState sharedIndexState = null; String modelId = indexEntryContext.getModelId(); if (IndexUtil.isSharedIndexStateRequired(knnEngine, modelId, indexAddress)) { @@ -109,9 +161,9 @@ public NativeMemoryAllocation.IndexAllocation load(NativeMemoryEntryContext.Inde return new NativeMemoryAllocation.IndexAllocation( executor, indexAddress, - indexEntryContext.calculateSizeInKB(), + indexSizeKb, knnEngine, - indexPath.toString(), + absoluteIndexPath.toString(), indexEntryContext.getOpenSearchIndexName(), watcherHandle, sharedIndexState, diff --git a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java index 1c31ed725..0fd2fddf7 100644 --- a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java +++ b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java @@ -286,6 +286,7 @@ private Map doANNSearch( try { indexAllocation = nativeMemoryCacheManager.get( new NativeMemoryEntryContext.IndexEntryContext( + reader.directory(), indexPath.toString(), NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), getParametersAtLoading( diff --git a/src/main/java/org/opensearch/knn/index/store/IndexInputWithBuffer.java b/src/main/java/org/opensearch/knn/index/store/IndexInputWithBuffer.java new file mode 100644 index 000000000..273a4deac --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/store/IndexInputWithBuffer.java @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.store; + +import lombok.NonNull; +import org.apache.lucene.store.IndexInput; + +import java.io.IOException; + +/** + * This class contains a Lucene's IndexInput with a reader buffer. + * A Java reference of this class will be passed to native engines, then 'copyBytes' method will be + * called by native engine via JNI API. + * Therefore, this class servers as a read layer in native engines to read the bytes it wants. + */ +public class IndexInputWithBuffer { + private IndexInput indexInput; + // 4K buffer. + private byte[] buffer = new byte[4 * 1024]; + + public IndexInputWithBuffer(@NonNull IndexInput indexInput) { + this.indexInput = indexInput; + } + + /** + * This method will be invoked in native engines via JNI API. + * Then it will call IndexInput to read required bytes then copy them into a read buffer. + * + * @param nbytes Desired number of bytes to be read. + * @return The number of read bytes in a buffer. + * @throws IOException + */ + private int copyBytes(long nbytes) throws IOException { + final int readBytes = (int) Math.min(nbytes, buffer.length); + indexInput.readBytes(buffer, 0, readBytes); + return readBytes; + } + + @Override + public String toString() { + return "{indexInput=" + indexInput + ", len(buffer)=" + buffer.length + "}"; + } +} diff --git a/src/main/java/org/opensearch/knn/jni/FaissService.java b/src/main/java/org/opensearch/knn/jni/FaissService.java index 4bceed015..c56726c66 100644 --- a/src/main/java/org/opensearch/knn/jni/FaissService.java +++ b/src/main/java/org/opensearch/knn/jni/FaissService.java @@ -14,6 +14,7 @@ import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.query.KNNQueryResult; import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexInputWithBuffer; import java.security.AccessController; import java.security.PrivilegedAction; @@ -217,6 +218,15 @@ public static native void createByteIndexFromTemplate( */ public static native long loadIndex(String indexPath); + /** + * Load an index into memory via a wrapping having Lucene's IndexInput. + * Instead of directly accessing an index path, this will make Faiss delegate IndexInput to load bytes. + * + * @param readStream IndexInput wrapper having a Lucene's IndexInput reference. + * @return pointer to location in memory the index resides in + */ + public static native long loadIndexWithStream(IndexInputWithBuffer readStream); + /** * Load a binary index into memory * @@ -225,6 +235,15 @@ public static native void createByteIndexFromTemplate( */ public static native long loadBinaryIndex(String indexPath); + /** + * Load a binary index into memory with a wrapping having Lucene's IndexInput. + * Instead of directly accessing an index path, this will make Faiss delegate IndexInput to load bytes. + * + * @param readStream IndexInput wrapper having a Lucene's IndexInput reference. + * @return pointer to location in memory the index resides in + */ + public static native long loadBinaryIndexWithStream(IndexInputWithBuffer readStream); + /** * Determine if index contains shared state. * diff --git a/src/main/java/org/opensearch/knn/jni/JNIService.java b/src/main/java/org/opensearch/knn/jni/JNIService.java index 94c1ec48e..448241f9c 100644 --- a/src/main/java/org/opensearch/knn/jni/JNIService.java +++ b/src/main/java/org/opensearch/knn/jni/JNIService.java @@ -16,6 +16,7 @@ import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.query.KNNQueryResult; +import org.opensearch.knn.index.store.IndexInputWithBuffer; import org.opensearch.knn.index.util.IndexUtil; import java.util.Locale; @@ -211,6 +212,28 @@ public static long loadIndex(String indexPath, Map parameters, K ); } + /** + * Load an index via Lucene's IndexInput. + * + * @param readStream A wrapper having Lucene's IndexInput to load bytes from a file. + * @param parameters Parameters to be used when loading index + * @param knnEngine Engine to load index + * @return Pointer to location in memory the index resides in + */ + public static long loadIndex(IndexInputWithBuffer readStream, Map parameters, KNNEngine knnEngine) { + if (KNNEngine.FAISS == knnEngine) { + if (IndexUtil.isBinaryIndex(knnEngine, parameters)) { + return FaissService.loadBinaryIndexWithStream(readStream); + } else { + return FaissService.loadIndexWithStream(readStream); + } + } + + throw new IllegalArgumentException( + String.format(Locale.ROOT, "LoadIndex not supported for provided engine : %s", knnEngine.getName()) + ); + } + /** * Determine if index contains shared state. Currently, we cannot do this in the plugin because we do not store the * model definition anywhere. Only faiss supports indices that have shared state. So for all other engines it will diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java index cb5fbaeba..906ff4cb7 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java @@ -37,6 +37,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import static org.mockito.Mockito.doNothing; @@ -259,11 +260,12 @@ public void testIndexAllocation_closeDefault() { } public void testIndexAllocation_closeBlocking() throws InterruptedException, ExecutionException { + // Prepare mocking and a thread pool. WatcherHandle watcherHandle = (WatcherHandle) mock(WatcherHandle.class); - ExecutorService executorService = Executors.newFixedThreadPool(2); - AtomicReference expectedException = new AtomicReference<>(); + ExecutorService executorService = Executors.newSingleThreadExecutor(); - // Blocking close + // Enable `KNN_FORCE_EVICT_CACHE_ENABLED_SETTING` to force it to block other threads. + // Having it false will make `IndexAllocation` to run close logic in a different thread. when(clusterSettings.get(KNN_FORCE_EVICT_CACHE_ENABLED_SETTING)).thenReturn(true); NativeMemoryAllocation.IndexAllocation blockingIndexAllocation = new NativeMemoryAllocation.IndexAllocation( mock(ExecutorService.class), @@ -275,19 +277,21 @@ public void testIndexAllocation_closeBlocking() throws InterruptedException, Exe watcherHandle ); - executorService.submit(blockingIndexAllocation::readLock); + // Acquire a read lock + blockingIndexAllocation.readLock(); + + // This should be blocked as a read lock is still being held. Future closingThread = executorService.submit(blockingIndexAllocation::close); // Check if thread is currently blocked try { closingThread.get(5, TimeUnit.SECONDS); - } catch (Exception e) { - expectedException.set(e); - } - - assertNotNull(expectedException.get()); + fail("Closing should be blocked. We are still holding a read lock."); + } catch (TimeoutException ignored) {} - executorService.submit(blockingIndexAllocation::readUnlock); + // Now, we unlock a read lock. + blockingIndexAllocation.readUnlock(); + // As we don't hold any locking, the closing thread can now good to acquire a write lock. closingThread.get(); // Waits until close diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java index 1720da1ed..72cab9a1b 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java @@ -12,6 +12,7 @@ package org.opensearch.knn.index.memory; import com.google.common.collect.ImmutableMap; +import org.apache.lucene.store.Directory; import org.opensearch.cluster.service.ClusterService; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.index.engine.qframe.QuantizationConfig; @@ -44,6 +45,7 @@ public void testAbstract_getKey() { public void testIndexEntryContext_load() throws IOException { NativeMemoryLoadStrategy.IndexLoadStrategy indexLoadStrategy = mock(NativeMemoryLoadStrategy.IndexLoadStrategy.class); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( + (Directory) null, "test", indexLoadStrategy, null, @@ -82,6 +84,7 @@ public void testIndexEntryContext_calculateSize() throws IOException { // Check that the indexEntryContext will return the same thing NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( + (Directory) null, tmpFile.toAbsolutePath().toString(), null, null, @@ -94,6 +97,7 @@ public void testIndexEntryContext_calculateSize() throws IOException { public void testIndexEntryContext_getOpenSearchIndexName() { String openSearchIndexName = "test-index"; NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( + (Directory) null, "test", null, null, @@ -106,6 +110,7 @@ public void testIndexEntryContext_getOpenSearchIndexName() { public void testIndexEntryContext_getParameters() { Map parameters = ImmutableMap.of("test-1", 10); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( + (Directory) null, "test", null, parameters, diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java index bdd8d7e45..373afddc7 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java @@ -12,6 +12,8 @@ package org.opensearch.knn.index.memory; import com.google.common.collect.ImmutableMap; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.MMapDirectory; import org.opensearch.core.action.ActionListener; import org.opensearch.action.search.SearchResponse; import org.opensearch.knn.KNNTestCase; @@ -47,6 +49,7 @@ public class NativeMemoryLoadStrategyTests extends KNNTestCase { public void testIndexLoadStrategy_load() throws IOException { // Create basic nmslib HNSW index Path dir = createTempDir(); + Directory luceneDirectory = new MMapDirectory(dir); KNNEngine knnEngine = KNNEngine.NMSLIB; String indexName = "test1" + knnEngine.getExtension(); String path = dir.resolve(indexName).toAbsolutePath().toString(); @@ -68,6 +71,7 @@ public void testIndexLoadStrategy_load() throws IOException { NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( + luceneDirectory, path, NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), parameters, @@ -87,6 +91,7 @@ public void testIndexLoadStrategy_load() throws IOException { public void testLoad_whenFaissBinary_thenSuccess() throws IOException { Path dir = createTempDir(); + Directory luceneDirectory = new MMapDirectory(dir); KNNEngine knnEngine = KNNEngine.FAISS; String indexName = "test1" + knnEngine.getExtension(); String path = dir.resolve(indexName).toAbsolutePath().toString(); @@ -116,6 +121,7 @@ public void testLoad_whenFaissBinary_thenSuccess() throws IOException { NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( + luceneDirectory, path, NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), parameters, diff --git a/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java b/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java index c78478f4d..8566b0223 100644 --- a/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java +++ b/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java @@ -14,6 +14,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import lombok.SneakyThrows; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; +import org.apache.lucene.store.MMapDirectory; import org.junit.BeforeClass; import org.opensearch.Version; import org.opensearch.common.xcontent.XContentFactory; @@ -29,6 +33,7 @@ import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexInputWithBuffer; import java.io.IOException; import java.net.URL; @@ -871,6 +876,29 @@ public void testQueryIndex_faiss_invalid_nullQueryVector() throws IOException { expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, null, KNNEngine.FAISS, null, 0, null)); } + public void testQueryIndex_faiss_streaming_invalid_nullQueryVector() throws IOException { + Path tmpFile = createTempFile(); + + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + tmpFile.toAbsolutePath().toString(), + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(tmpFile.toFile().length() > 0); + + try (final Directory directory = new MMapDirectory(tmpFile.getParent())) { + try (IndexInput indexInput = directory.openInput(tmpFile.getFileName().toString(), IOContext.READONCE)) { + long pointer = JNIService.loadIndex(new IndexInputWithBuffer(indexInput), Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + + expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, null, KNNEngine.FAISS, null, 0, null)); + } + } + } + public void testQueryIndex_faiss_valid() throws IOException { int k = 10; @@ -930,6 +958,68 @@ public void testQueryIndex_faiss_valid() throws IOException { } } + public void testQueryIndex_faiss_streaming_valid() throws IOException { + int k = 10; + int efSearch = 100; + + List methods = ImmutableList.of(faissMethod); + List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + for (String method : methods) { + for (SpaceType spaceType : spaces) { + Path tmpFile = createTempFile(); + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + tmpFile.toAbsolutePath().toString(), + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertTrue(tmpFile.toFile().length() > 0); + + try (final Directory directory = new MMapDirectory(tmpFile.getParent())) { + try (IndexInput indexInput = directory.openInput(tmpFile.getFileName().toString(), IOContext.READONCE)) { + long pointer = JNIService.loadIndex( + new IndexInputWithBuffer(indexInput), + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + null, + 0, + null + ); + assertEquals(k, results.length); + } + + // Filter will result in no ids + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + new long[] { 0 }, + 0, + null + ); + assertEquals(0, results.length); + } // End for + } // End try + } // End try + } // End for + } // End for + } + public void testQueryIndex_faiss_parentIds() throws IOException { int k = 100; @@ -978,6 +1068,58 @@ public void testQueryIndex_faiss_parentIds() throws IOException { } } + public void testQueryIndex_faiss_streaming_parentIds() throws IOException { + + int k = 100; + int efSearch = 100; + + List methods = ImmutableList.of(faissMethod); + List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + int[] parentIds = toParentIdArray(testDataNested.indexData.docs); + Map idToParentIdMap = toIdToParentIdMap(testDataNested.indexData.docs); + for (String method : methods) { + for (SpaceType spaceType : spaces) { + Path tmpFile = createTempFile(); + TestUtils.createIndex( + testDataNested.indexData.docs, + testData.loadDataToMemoryAddress(), + testDataNested.indexData.getDimension(), + tmpFile.toAbsolutePath().toString(), + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertTrue(tmpFile.toFile().length() > 0); + + try (final Directory directory = new MMapDirectory(tmpFile.getParent())) { + try (IndexInput indexInput = directory.openInput(tmpFile.getFileName().toString(), IOContext.READONCE)) { + long pointer = JNIService.loadIndex( + new IndexInputWithBuffer(indexInput), + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + + for (float[] query : testDataNested.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + null, + 0, + parentIds + ); + // Verify there is no more than one result from same parent + Set parentIdSet = toParentIdSet(results, idToParentIdMap); + assertEquals(results.length, parentIdSet.size()); + } // End for + } // End try + } // End try + } // End for + } // End for + } + @SneakyThrows public void testQueryBinaryIndex_faiss_valid() { int k = 10; @@ -1016,6 +1158,53 @@ public void testQueryBinaryIndex_faiss_valid() { } } + @SneakyThrows + public void testQueryBinaryIndex_faiss_streaming_valid() { + int k = 10; + List methods = ImmutableList.of(faissBinaryMethod); + for (String method : methods) { + Path tmpFile = createTempFile(); + long memoryAddr = testData.loadBinaryDataToMemoryAddress(); + TestUtils.createIndex( + testData.indexData.docs, + memoryAddr, + testData.indexData.getDimension(), + tmpFile.toAbsolutePath().toString(), + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + method, + KNNConstants.SPACE_TYPE, + SpaceType.HAMMING.getValue(), + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ), + KNNEngine.FAISS + ); + assertTrue(tmpFile.toFile().length() > 0); + + try (final Directory directory = new MMapDirectory(tmpFile.getParent())) { + try (IndexInput indexInput = directory.openInput(tmpFile.getFileName().toString(), IOContext.READONCE)) { + long pointer = JNIService.loadIndex( + new IndexInputWithBuffer(indexInput), + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + method, + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + + for (byte[] query : testData.binaryQueries) { + KNNQueryResult[] results = JNIService.queryBinaryIndex(pointer, query, k, null, KNNEngine.FAISS, null, 0, null); + assertEquals(k, results.length); + } // End for + } // End try + } // End try + } // End for + } + private Set toParentIdSet(KNNQueryResult[] results, Map idToParentIdMap) { return Arrays.stream(results).map(result -> idToParentIdMap.get(result.getId())).collect(Collectors.toSet()); } From 677184230bccec01ecfaf43fa3ed368dacf5a510 Mon Sep 17 00:00:00 2001 From: Doo Yong Kim <0ctopus13prime@gmail.com> Date: Thu, 3 Oct 2024 15:55:03 -0700 Subject: [PATCH 33/59] Fix sed command in DEVELOPER_GUIDE to append a new line character '\n'. (#2181) Signed-off-by: Dooyong Kim Co-authored-by: Dooyong Kim --- CHANGELOG.md | 1 + DEVELOPER_GUIDE.md | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 267f6248d..fa86cbe3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Infrastructure * Removed JDK 11 and 17 version from CI runs [#1921](https://github.com/opensearch-project/k-NN/pull/1921) ### Documentation +* Fix sed command in DEVELOPER_GUIDE.md to append a new line character '\n'. [#2181](https://github.com/opensearch-project/k-NN/pull/2181) ### Maintenance ### Refactoring * Does not create additional KNNVectorValues in NativeEngines990KNNVectorWriter when quantization is not needed [#2133](https://github.com/opensearch-project/k-NN/pull/2133) diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index c652209f1..5f79d222b 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -119,7 +119,8 @@ export CXX=/opt/homebrew/opt/llvm/bin/clang++ // In case of linking issues with the external libraries and clang, you can try setting the CMAKE compiler to gcc/g++ instead through the following commands: export CC=gcc export CXX=g++ -sed -i '' '/set(CMAKE_CXX_STANDARD_REQUIRED True)/a\'$'\n''set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -fopenmp -L/opt/homebrew/opt/libomp/lib -I/opt/homebrew/opt/libomp/include -lomp -arch arm64 -fexceptions")' CMakeLists.txt +sed -i '' '/set(CMAKE_CXX_STANDARD_REQUIRED True)/a\'$'\n''set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -fopenmp -L/opt/homebrew/opt/libomp/lib -I/opt/homebrew/opt/libomp/include -lomp -arch arm64 -fexceptions")'$'\n''' CMakeLists.txt + // Build cmake . --fresh @@ -478,4 +479,4 @@ The Github workflow in [`backport.yml`](.github/workflows/backport.yml) creates original PR with an appropriate label `backport ` is merged to main with the backport workflow run successfully on the PR. For example, if a PR on main needs to be backported to `1.x` branch, add a label `backport 1.x` to the PR and make sure the backport workflow runs on the PR along with other checks. Once this PR is -merged to main, the workflow will create a backport PR to the `1.x` branch. \ No newline at end of file +merged to main, the workflow will create a backport PR to the `1.x` branch. From 01d7981c8a9ddab24188b08ae9c7ace2ba1e2ac5 Mon Sep 17 00:00:00 2001 From: Vikasht34 Date: Fri, 4 Oct 2024 12:24:18 -0700 Subject: [PATCH 34/59] Score Fix for Binary Quantized Vector and Setting Default value in case of shard level rescoring is disabled for oversampling factor (#2183) Signed-off-by: VIKASH TIWARI --- CHANGELOG.md | 1 + .../org/opensearch/knn/index/KNNSettings.java | 7 +- .../knn/index/mapper/CompressionLevel.java | 39 +++---- .../opensearch/knn/index/query/KNNWeight.java | 4 + .../nativelib/NativeEngineKnnVectorQuery.java | 6 +- .../index/query/rescore/RescoreContext.java | 63 ++++++++++- .../knn/index/KNNSettingsTests.java | 10 +- .../index/mapper/CompressionLevelTests.java | 76 +++++-------- .../knn/index/query/KNNQueryBuilderTests.java | 3 +- .../knn/index/query/KNNWeightTests.java | 106 ++++++++++++++++++ .../NativeEngineKNNVectorQueryTests.java | 6 +- .../query/rescore/RescoreContextTests.java | 79 ++++++++----- 12 files changed, 285 insertions(+), 115 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa86cbe3f..f615a78fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Adding Support to Enable/Disble Share level Rescoring and Update Oversampling Factor[#2172](https://github.com/opensearch-project/k-NN/pull/2172) ### Bug Fixes * KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) +* Score Fix for Binary Quantized Vector and Setting Default value in case of shard level rescoring is disabled for oversampling factor[#2183](https://github.com/opensearch-project/k-NN/pull/2183) ### Infrastructure ### Documentation ### Maintenance diff --git a/src/main/java/org/opensearch/knn/index/KNNSettings.java b/src/main/java/org/opensearch/knn/index/KNNSettings.java index 1753140e6..f5980879a 100644 --- a/src/main/java/org/opensearch/knn/index/KNNSettings.java +++ b/src/main/java/org/opensearch/knn/index/KNNSettings.java @@ -92,6 +92,7 @@ public class KNNSettings { /** * Default setting values + * */ public static final boolean KNN_DEFAULT_FAISS_AVX2_DISABLED_VALUE = false; public static final boolean KNN_DEFAULT_FAISS_AVX512_DISABLED_VALUE = false; @@ -113,7 +114,7 @@ public class KNNSettings { public static final Integer KNN_MAX_QUANTIZATION_STATE_CACHE_SIZE_LIMIT_PERCENTAGE = 10; // Quantization state cache limit cannot exceed // 10% of the JVM heap public static final Integer KNN_DEFAULT_QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES = 60; - public static final boolean KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_VALUE = true; + public static final boolean KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_VALUE = false; /** * Settings Definition @@ -554,12 +555,12 @@ public static Integer getFilteredExactSearchThreshold(final String indexName) { .getAsInt(ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_DEFAULT_VALUE); } - public static boolean isShardLevelRescoringDisabledForDiskBasedVector(String indexName) { + public static boolean isShardLevelRescoringEnabledForDiskBasedVector(String indexName) { return KNNSettings.state().clusterService.state() .getMetadata() .index(indexName) .getSettings() - .getAsBoolean(KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED, true); + .getAsBoolean(KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED, false); } public void initialize(Client client, ClusterService clusterService) { diff --git a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java index c9a169efc..ab583a2e0 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java +++ b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java @@ -25,9 +25,9 @@ public enum CompressionLevel { x1(1, "1x", null, Collections.emptySet()), x2(2, "2x", null, Collections.emptySet()), x4(4, "4x", null, Collections.emptySet()), - x8(8, "8x", new RescoreContext(2.0f), Set.of(Mode.ON_DISK)), - x16(16, "16x", new RescoreContext(3.0f), Set.of(Mode.ON_DISK)), - x32(32, "32x", new RescoreContext(3.0f), Set.of(Mode.ON_DISK)); + x8(8, "8x", new RescoreContext(2.0f, false), Set.of(Mode.ON_DISK)), + x16(16, "16x", new RescoreContext(3.0f, false), Set.of(Mode.ON_DISK)), + x32(32, "32x", new RescoreContext(3.0f, false), Set.of(Mode.ON_DISK)); // Internally, an empty string is easier to deal with them null. However, from the mapping, // we do not want users to pass in the empty string and instead want null. So we make the conversion here @@ -97,32 +97,33 @@ public static boolean isConfigured(CompressionLevel compressionLevel) { /** * Returns the appropriate {@link RescoreContext} based on the given {@code mode} and {@code dimension}. * - *

    If the {@code mode} is present in the valid {@code modesForRescore} set, the method adjusts the oversample factor based on the - * {@code dimension} value: + *

    If the {@code mode} is present in the valid {@code modesForRescore} set, the method checks the value of + * {@code dimension}: *

      - *
    • If {@code dimension} is greater than or equal to 1000, no oversampling is applied (oversample factor = 1.0).
    • - *
    • If {@code dimension} is greater than or equal to 768 but less than 1000, a 2x oversample factor is applied (oversample factor = 2.0).
    • - *
    • If {@code dimension} is less than 768, a 3x oversample factor is applied (oversample factor = 3.0).
    • + *
    • If {@code dimension} is less than or equal to 1000, it returns a {@link RescoreContext} with an + * oversample factor of 5.0f.
    • + *
    • If {@code dimension} is greater than 1000, it returns the default {@link RescoreContext} associated with + * the {@link CompressionLevel}. If no default is set, it falls back to {@link RescoreContext#getDefault()}.
    • *
    - * If the {@code mode} is not present in the {@code modesForRescore} set, the method returns {@code null}. + * If the {@code mode} is not valid, the method returns {@code null}. * * @param mode The {@link Mode} for which to retrieve the {@link RescoreContext}. * @param dimension The dimensional value that determines the {@link RescoreContext} behavior. - * @return A {@link RescoreContext} with the appropriate oversample factor based on the dimension, or {@code null} if the mode - * is not valid. + * @return A {@link RescoreContext} with an oversample factor of 5.0f if {@code dimension} is less than + * or equal to 1000, the default {@link RescoreContext} if greater, or {@code null} if the mode + * is invalid. */ public RescoreContext getDefaultRescoreContext(Mode mode, int dimension) { if (modesForRescore.contains(mode)) { // Adjust RescoreContext based on dimension - if (dimension >= RescoreContext.DIMENSION_THRESHOLD_1000) { - // No oversampling for dimensions >= 1000 - return RescoreContext.builder().oversampleFactor(RescoreContext.OVERSAMPLE_FACTOR_1000).build(); - } else if (dimension >= RescoreContext.DIMENSION_THRESHOLD_768) { - // 2x oversampling for dimensions >= 768 but < 1000 - return RescoreContext.builder().oversampleFactor(RescoreContext.OVERSAMPLE_FACTOR_768).build(); + if (dimension <= RescoreContext.DIMENSION_THRESHOLD) { + // For dimensions <= 1000, return a RescoreContext with 5.0f oversample factor + return RescoreContext.builder() + .oversampleFactor(RescoreContext.OVERSAMPLE_FACTOR_BELOW_DIMENSION_THRESHOLD) + .userProvided(false) + .build(); } else { - // 3x oversampling for dimensions < 768 - return RescoreContext.builder().oversampleFactor(RescoreContext.OVERSAMPLE_FACTOR_BELOW_768).build(); + return defaultRescoreContext; } } return null; diff --git a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java index 0fd2fddf7..37695c208 100644 --- a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java +++ b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java @@ -376,6 +376,10 @@ private Map doANNSearch( return null; } + if (quantizedVector != null) { + return Arrays.stream(results) + .collect(Collectors.toMap(KNNQueryResult::getId, result -> knnEngine.score(result.getScore(), SpaceType.HAMMING))); + } return Arrays.stream(results) .collect(Collectors.toMap(KNNQueryResult::getId, result -> knnEngine.score(result.getScore(), spaceType))); } diff --git a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java index adb2875d5..c97a0d061 100644 --- a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java +++ b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java @@ -61,9 +61,11 @@ public Weight createWeight(IndexSearcher indexSearcher, ScoreMode scoreMode, flo if (rescoreContext == null) { perLeafResults = doSearch(indexSearcher, leafReaderContexts, knnWeight, finalK); } else { - int firstPassK = rescoreContext.getFirstPassK(finalK); + boolean isShardLevelRescoringEnabled = KNNSettings.isShardLevelRescoringEnabledForDiskBasedVector(knnQuery.getIndexName()); + int dimension = knnQuery.getQueryVector().length; + int firstPassK = rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension); perLeafResults = doSearch(indexSearcher, leafReaderContexts, knnWeight, firstPassK); - if (KNNSettings.isShardLevelRescoringDisabledForDiskBasedVector(knnQuery.getIndexName()) == false) { + if (isShardLevelRescoringEnabled == true) { ResultUtil.reduceToTopK(perLeafResults, firstPassK); } diff --git a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java index a2563b2a6..0f8c59499 100644 --- a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java +++ b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java @@ -39,21 +39,74 @@ public final class RescoreContext { @Builder.Default private float oversampleFactor = DEFAULT_OVERSAMPLE_FACTOR; + /** + * Flag to track whether the oversample factor is user-provided or default. The Reason to introduce + * this is to set default when Shard Level rescoring is false, + * else we end up overriding user provided value in NativeEngineKnnVectorQuery + * + * + * This flag is crucial to differentiate between user-defined oversample factors and system-assigned + * default values. The behavior of oversampling logic, especially when shard-level rescoring is disabled, + * depends on whether the user explicitly provided an oversample factor or whether the system is using + * a default value. + * + * When shard-level rescoring is disabled, the system applies dimension-based oversampling logic, + * overriding any default values. However, if the user provides their own oversample factor, the system + * should respect the user’s input and avoid overriding it with the dimension-based logic. + * + * This flag is set to {@code true} when the oversample factor is provided by the user, ensuring + * that their value is not overridden. It is set to {@code false} when the oversample factor is + * determined by system defaults (e.g., through a compression level or automatic logic). The system + * then applies its own oversampling rules if necessary. + * + * Key scenarios: + * - If {@code userProvided} is {@code true} and shard-level rescoring is disabled, the user's + * oversample factor is used as is, without applying the dimension-based logic. + * - If {@code userProvided} is {@code false}, the system applies dimension-based oversampling + * when shard-level rescoring is disabled. + * + * This flag enables flexibility, allowing the system to handle both user-defined and default + * behaviors, ensuring the correct oversampling logic is applied based on the context. + */ + @Builder.Default + private boolean userProvided = true; + /** * * @return default RescoreContext */ public static RescoreContext getDefault() { - return RescoreContext.builder().build(); + return RescoreContext.builder().oversampleFactor(DEFAULT_OVERSAMPLE_FACTOR).userProvided(false).build(); } /** - * Gets the number of results to return for the first pass of rescoring. + * Calculates the number of results to return for the first pass of rescoring (firstPassK). + * This method considers whether shard-level rescoring is enabled and adjusts the oversample factor + * based on the vector dimension if shard-level rescoring is disabled. * - * @param finalK The final number of results to return for the entire shard - * @return The number of results to return for the first pass of rescoring + * @param finalK The final number of results to return for the entire shard. + * @param isShardLevelRescoringEnabled A boolean flag indicating whether shard-level rescoring is enabled. + * If true, the dimension-based oversampling logic is bypassed. + * @param dimension The dimension of the vector. This is used to determine the oversampling factor when + * shard-level rescoring is disabled. + * @return The number of results to return for the first pass of rescoring, adjusted by the oversample factor. */ - public int getFirstPassK(int finalK) { + public int getFirstPassK(int finalK, boolean isShardLevelRescoringEnabled, int dimension) { + // Only apply default dimension-based oversampling logic when: + // 1. Shard-level rescoring is disabled + // 2. The oversample factor was not provided by the user + if (!isShardLevelRescoringEnabled && !userProvided) { + // Apply new dimension-based oversampling logic when shard-level rescoring is disabled + if (dimension >= DIMENSION_THRESHOLD_1000) { + oversampleFactor = OVERSAMPLE_FACTOR_1000; // No oversampling for dimensions >= 1000 + } else if (dimension >= DIMENSION_THRESHOLD_768) { + oversampleFactor = OVERSAMPLE_FACTOR_768; // 2x oversampling for dimensions >= 768 and < 1000 + } else { + oversampleFactor = OVERSAMPLE_FACTOR_BELOW_768; // 3x oversampling for dimensions < 768 + } + } + // The calculation for firstPassK remains the same, applying the oversample factor return Math.min(MAX_FIRST_PASS_RESULTS, Math.max(MIN_FIRST_PASS_RESULTS, (int) Math.ceil(finalK * oversampleFactor))); } + } diff --git a/src/test/java/org/opensearch/knn/index/KNNSettingsTests.java b/src/test/java/org/opensearch/knn/index/KNNSettingsTests.java index fd25699cc..c7a8e7ed8 100644 --- a/src/test/java/org/opensearch/knn/index/KNNSettingsTests.java +++ b/src/test/java/org/opensearch/knn/index/KNNSettingsTests.java @@ -159,7 +159,7 @@ public void testGetEfSearch_whenEFSearchValueSetByUser_thenReturnValue() { } @SneakyThrows - public void testShardLevelRescoringDisabled_whenNoValuesProvidedByUser_thenDefaultSettingsUsed() { + public void testShardLevelRescoringEnabled_whenNoValuesProvidedByUser_thenDefaultSettingsUsed() { Node mockNode = createMockNode(Collections.emptyMap()); mockNode.start(); ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); @@ -167,14 +167,14 @@ public void testShardLevelRescoringDisabled_whenNoValuesProvidedByUser_thenDefau mockNode.client().admin().indices().create(new CreateIndexRequest(INDEX_NAME)).actionGet(); KNNSettings.state().setClusterService(clusterService); - boolean shardLevelRescoringDisabled = KNNSettings.isShardLevelRescoringDisabledForDiskBasedVector(INDEX_NAME); + boolean shardLevelRescoringDisabled = KNNSettings.isShardLevelRescoringEnabledForDiskBasedVector(INDEX_NAME); mockNode.close(); - assertTrue(shardLevelRescoringDisabled); + assertFalse(shardLevelRescoringDisabled); } @SneakyThrows public void testShardLevelRescoringDisabled_whenValueProvidedByUser_thenSettingApplied() { - boolean userDefinedRescoringDisabled = false; + boolean userDefinedRescoringDisabled = true; Node mockNode = createMockNode(Collections.emptyMap()); mockNode.start(); ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); @@ -188,7 +188,7 @@ public void testShardLevelRescoringDisabled_whenValueProvidedByUser_thenSettingA mockNode.client().admin().indices().updateSettings(new UpdateSettingsRequest(rescoringDisabledSetting, INDEX_NAME)).actionGet(); - boolean shardLevelRescoringDisabled = KNNSettings.isShardLevelRescoringDisabledForDiskBasedVector(INDEX_NAME); + boolean shardLevelRescoringDisabled = KNNSettings.isShardLevelRescoringEnabledForDiskBasedVector(INDEX_NAME); mockNode.close(); assertEquals(userDefinedRescoringDisabled, shardLevelRescoringDisabled); } diff --git a/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java b/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java index 57372b11e..e882d6697 100644 --- a/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java +++ b/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java @@ -45,83 +45,57 @@ public void testGetDefaultRescoreContext() { // Test rescore context for ON_DISK mode Mode mode = Mode.ON_DISK; - // Test various dimensions based on the updated oversampling logic - int belowThresholdDimension = 500; // A dimension below 768 - int between768and1000Dimension = 800; // A dimension between 768 and 1000 - int above1000Dimension = 1500; // A dimension above 1000 + int belowThresholdDimension = 500; // A dimension below the threshold + int aboveThresholdDimension = 1500; // A dimension above the threshold - // Compression level x32 with dimension < 768 should have an oversample factor of 3.0f + // x32 with dimension <= 1000 should have an oversample factor of 5.0f RescoreContext rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, belowThresholdDimension); assertNotNull(rescoreContext); - assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); - - // Compression level x32 with dimension between 768 and 1000 should have an oversample factor of 2.0f - rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, between768and1000Dimension); - assertNotNull(rescoreContext); - assertEquals(2.0f, rescoreContext.getOversampleFactor(), 0.0f); + assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); - // Compression level x32 with dimension > 1000 should have no oversampling (1.0f) - rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, above1000Dimension); - assertNotNull(rescoreContext); - assertEquals(1.0f, rescoreContext.getOversampleFactor(), 0.0f); - - // Compression level x16 with dimension < 768 should have an oversample factor of 3.0f - rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, belowThresholdDimension); + // x32 with dimension > 1000 should have an oversample factor of 3.0f + rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, aboveThresholdDimension); assertNotNull(rescoreContext); assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); - // Compression level x16 with dimension between 768 and 1000 should have an oversample factor of 2.0f - rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, between768and1000Dimension); + // x16 with dimension <= 1000 should have an oversample factor of 5.0f + rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, belowThresholdDimension); assertNotNull(rescoreContext); - assertEquals(2.0f, rescoreContext.getOversampleFactor(), 0.0f); + assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); - // Compression level x16 with dimension > 1000 should have no oversampling (1.0f) - rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, above1000Dimension); + // x16 with dimension > 1000 should have an oversample factor of 3.0f + rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, aboveThresholdDimension); assertNotNull(rescoreContext); - assertEquals(1.0f, rescoreContext.getOversampleFactor(), 0.0f); + assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); - // Compression level x8 with dimension < 768 should have an oversample factor of 3.0f + // x8 with dimension <= 1000 should have an oversample factor of 5.0f rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, belowThresholdDimension); assertNotNull(rescoreContext); - assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); - - // Compression level x8 with dimension between 768 and 1000 should have an oversample factor of 2.0f - rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, between768and1000Dimension); + assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); + // x8 with dimension > 1000 should have an oversample factor of 2.0f + rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, aboveThresholdDimension); assertNotNull(rescoreContext); assertEquals(2.0f, rescoreContext.getOversampleFactor(), 0.0f); - // Compression level x8 with dimension > 1000 should have no oversampling (1.0f) - rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, above1000Dimension); - assertNotNull(rescoreContext); - assertEquals(1.0f, rescoreContext.getOversampleFactor(), 0.0f); - - // Compression level x4 with dimension < 768 should return null (no RescoreContext) + // x4 with dimension <= 1000 should have an oversample factor of 5.0f (though it doesn't have its own RescoreContext) rescoreContext = CompressionLevel.x4.getDefaultRescoreContext(mode, belowThresholdDimension); assertNull(rescoreContext); - - // Compression level x4 with dimension > 1000 should return null (no RescoreContext) - rescoreContext = CompressionLevel.x4.getDefaultRescoreContext(mode, above1000Dimension); + // x4 with dimension > 1000 should return null (no RescoreContext is configured for x4) + rescoreContext = CompressionLevel.x4.getDefaultRescoreContext(mode, aboveThresholdDimension); assertNull(rescoreContext); - - // Compression level x2 with dimension < 768 should return null + // Other compression levels should behave similarly with respect to dimension rescoreContext = CompressionLevel.x2.getDefaultRescoreContext(mode, belowThresholdDimension); assertNull(rescoreContext); - - // Compression level x2 with dimension > 1000 should return null - rescoreContext = CompressionLevel.x2.getDefaultRescoreContext(mode, above1000Dimension); + // x2 with dimension > 1000 should return null + rescoreContext = CompressionLevel.x2.getDefaultRescoreContext(mode, aboveThresholdDimension); assertNull(rescoreContext); - - // Compression level x1 with dimension < 768 should return null rescoreContext = CompressionLevel.x1.getDefaultRescoreContext(mode, belowThresholdDimension); assertNull(rescoreContext); - - // Compression level x1 with dimension > 1000 should return null - rescoreContext = CompressionLevel.x1.getDefaultRescoreContext(mode, above1000Dimension); + // x1 with dimension > 1000 should return null + rescoreContext = CompressionLevel.x1.getDefaultRescoreContext(mode, aboveThresholdDimension); assertNull(rescoreContext); - - // NOT_CONFIGURED mode should return null for any dimension + // NOT_CONFIGURED with dimension <= 1000 should return a RescoreContext with an oversample factor of 5.0f rescoreContext = CompressionLevel.NOT_CONFIGURED.getDefaultRescoreContext(mode, belowThresholdDimension); assertNull(rescoreContext); } - } diff --git a/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderTests.java b/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderTests.java index 3db03085b..b28b790d1 100644 --- a/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderTests.java +++ b/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderTests.java @@ -912,7 +912,8 @@ private void assertRescore(Version version, RescoreContext expectedRescoreContex } if (expectedRescoreContext != null) { - assertEquals(expectedRescoreContext, actualRescoreContext); + assertNotNull(actualRescoreContext); + assertEquals(expectedRescoreContext.getOversampleFactor(), actualRescoreContext.getOversampleFactor(), 0.0f); } } diff --git a/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java index f92f32406..2a2c3ed4d 100644 --- a/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java +++ b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java @@ -79,6 +79,7 @@ import static java.util.Collections.emptyMap; import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -516,6 +517,111 @@ public void testANNWithFilterQuery_whenDoingANNBinary_thenSuccess() { validateANNWithFilterQuery_whenDoingANN_thenSuccess(true); } + @SneakyThrows + public void testScorerWithQuantizedVector() { + // Given + int k = 3; + byte[] quantizedVector = new byte[] { 1, 2, 3 }; // Mocked quantized vector + float[] queryVector = new float[] { 0.1f, 0.3f }; + + // Mock the JNI service to return KNNQueryResults + KNNQueryResult[] knnQueryResults = new KNNQueryResult[] { + new KNNQueryResult(1, 10.0f), // Mock result with id 1 and score 10 + new KNNQueryResult(2, 20.0f) // Mock result with id 2 and score 20 + }; + jniServiceMockedStatic.when( + () -> JNIService.queryBinaryIndex(anyLong(), eq(quantizedVector), eq(k), any(), any(), any(), anyInt(), any()) + ).thenReturn(knnQueryResults); + + KNNEngine knnEngine = mock(KNNEngine.class); + when(knnEngine.score(anyFloat(), eq(SpaceType.HAMMING))).thenAnswer(invocation -> { + Float score = invocation.getArgument(0); + return 1 / (1 + score); + }); + + // Build the KNNQuery object + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(queryVector) + .k(k) + .indexName(INDEX_NAME) + .vectorDataType(VectorDataType.BINARY) // Simulate binary vector type for quantization + .build(); + + final float boost = 1.0F; + final KNNWeight knnWeight = new KNNWeight(query, boost); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(FIELD_NAME)).thenReturn(fieldInfo); + + when(fieldInfo.attributes()).thenReturn(Map.of(KNN_ENGINE, KNNEngine.FAISS.getName(), SPACE_TYPE, SpaceType.HAMMING.getValue())); + + FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + when(path.toString()).thenReturn("/fake/directory"); + + SegmentInfo segmentInfo = new SegmentInfo( + directory, // The directory where the segment is stored + Version.LATEST, // Lucene version + Version.LATEST, // Version of the segment info + "0", // Segment name + 100, // Max document count for this segment + false, // Is this a compound file segment + false, // Is this a merged segment + KNNCodecVersion.current().getDefaultCodecDelegate(), // Codec delegate for KNN + Map.of(), // Diagnostics map + new byte[StringHelper.ID_LENGTH], // Segment ID + Map.of(), // Attributes + Sort.RELEVANCE // Default sort order + ); + + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + try (MockedStatic knnCodecUtilMockedStatic = mockStatic(KNNCodecUtil.class)) { + List engineFiles = List.of("_0_1_target_field.faiss"); + knnCodecUtilMockedStatic.when(() -> KNNCodecUtil.getEngineFiles(anyString(), anyString(), eq(segmentInfo))) + .thenReturn(engineFiles); + + try (MockedStatic quantizationUtilMockedStatic = mockStatic(SegmentLevelQuantizationUtil.class)) { + quantizationUtilMockedStatic.when(() -> SegmentLevelQuantizationUtil.quantizeVector(any(), any())) + .thenReturn(quantizedVector); + + // When: Call the scorer method + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + + // Then: Ensure scorer is not null + assertNotNull(knnScorer); + + // Verify that JNIService.queryBinaryIndex is called with the quantized vector + jniServiceMockedStatic.verify( + () -> JNIService.queryBinaryIndex(anyLong(), eq(quantizedVector), eq(k), any(), any(), any(), anyInt(), any()), + times(1) + ); + + // Iterate over the results and ensure they are scored with SpaceType.HAMMING + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertNotNull(docIdSetIterator); + while (docIdSetIterator.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) { + int docId = docIdSetIterator.docID(); + float expectedScore = knnEngine.score(knnQueryResults[docId - 1].getScore(), SpaceType.HAMMING); + float actualScore = knnScorer.score(); + // Check if the score is calculated using HAMMING + assertEquals(expectedScore, actualScore, 0.01f); // Tolerance for floating-point comparison + } + } + } + } + public void validateANNWithFilterQuery_whenDoingANN_thenSuccess(final boolean isBinary) throws IOException { // Given int k = 3; diff --git a/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java index 7fd96c6df..53873e15f 100644 --- a/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java +++ b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java @@ -103,6 +103,8 @@ public void setUp() throws Exception { // Set ClusterService in KNNSettings KNNSettings.state().setClusterService(clusterService); + when(knnQuery.getQueryVector()).thenReturn(new float[] { 1.0f, 2.0f, 3.0f }); // Example vector + } @SneakyThrows @@ -166,7 +168,7 @@ public void testRescoreWhenShardLevelRescoringEnabled() { ) { // When shard-level re-scoring is enabled - mockedKnnSettings.when(() -> KNNSettings.isShardLevelRescoringDisabledForDiskBasedVector(any())).thenReturn(false); + mockedKnnSettings.when(() -> KNNSettings.isShardLevelRescoringEnabledForDiskBasedVector(any())).thenReturn(true); // Mock ResultUtil to return valid TopDocs mockedResultUtil.when(() -> ResultUtil.resultMapToTopDocs(any(), anyInt())) @@ -250,7 +252,7 @@ public void testRescore() { ) { // When shard-level re-scoring is enabled - mockedKnnSettings.when(() -> KNNSettings.isShardLevelRescoringDisabledForDiskBasedVector(any())).thenReturn(true); + mockedKnnSettings.when(() -> KNNSettings.isShardLevelRescoringEnabledForDiskBasedVector(any())).thenReturn(true); mockedResultUtil.when(() -> ResultUtil.reduceToTopK(any(), anyInt())).thenAnswer(InvocationOnMock::callRealMethod); mockedResultUtil.when(() -> ResultUtil.resultMapToTopDocs(eq(rescoredLeaf1Results), anyInt())).thenAnswer(t -> topDocs1); diff --git a/src/test/java/org/opensearch/knn/index/query/rescore/RescoreContextTests.java b/src/test/java/org/opensearch/knn/index/query/rescore/RescoreContextTests.java index fd94667db..2b309e4ab 100644 --- a/src/test/java/org/opensearch/knn/index/query/rescore/RescoreContextTests.java +++ b/src/test/java/org/opensearch/knn/index/query/rescore/RescoreContextTests.java @@ -14,47 +14,72 @@ public class RescoreContextTests extends KNNTestCase { public void testGetFirstPassK() { float oversample = 2.6f; - RescoreContext rescoreContext = RescoreContext.builder().oversampleFactor(oversample).build(); + RescoreContext rescoreContext = RescoreContext.builder().oversampleFactor(oversample).userProvided(true).build(); int finalK = 100; - assertEquals(260, rescoreContext.getFirstPassK(finalK)); - finalK = 1; - assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); - finalK = 0; - assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); - finalK = MAX_FIRST_PASS_RESULTS; - assertEquals(MAX_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); - } - - public void testGetFirstPassKWithMinPassK() { - float oversample = 2.6f; - RescoreContext rescoreContext = RescoreContext.builder().oversampleFactor(oversample).build(); + boolean isShardLevelRescoringEnabled = true; + int dimension = 500; - // Case 1: Test with a finalK that results in a value greater than MIN_FIRST_PASS_RESULTS - int finalK = 100; - assertEquals(260, rescoreContext.getFirstPassK(finalK)); + // Case 1: Test with standard oversample factor when shard-level rescoring is enabled + assertEquals(260, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); // Case 2: Test with a very small finalK that should result in a value less than MIN_FIRST_PASS_RESULTS finalK = 1; - assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); - // Case 3: Test with finalK = 0, should return 0 + // Case 3: Test with finalK = 0, should return MIN_FIRST_PASS_RESULTS finalK = 0; - assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); // Case 4: Test with finalK = MAX_FIRST_PASS_RESULTS, should cap at MAX_FIRST_PASS_RESULTS finalK = MAX_FIRST_PASS_RESULTS; - assertEquals(MAX_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); + assertEquals(MAX_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); + } - // Case 5: Test where finalK * oversample is smaller than MIN_FIRST_PASS_RESULTS + public void testGetFirstPassKWithDimensionBasedOversampling() { + int finalK = 100; + int dimension; + + // Case 1: Test no oversampling for dimensions >= 1000 when shard-level rescoring is disabled + dimension = 1000; + RescoreContext rescoreContext = RescoreContext.builder().userProvided(false).build(); // Ensuring dimension-based logic applies + assertEquals(100, rescoreContext.getFirstPassK(finalK, false, dimension)); // No oversampling + + // Case 2: Test 2x oversampling for dimensions >= 768 but < 1000 when shard-level rescoring is disabled + dimension = 800; + rescoreContext = RescoreContext.builder().userProvided(false).build(); // Ensure previous values don't carry over + assertEquals(200, rescoreContext.getFirstPassK(finalK, false, dimension)); // 2x oversampling + + // Case 3: Test 3x oversampling for dimensions < 768 when shard-level rescoring is disabled + dimension = 700; + rescoreContext = RescoreContext.builder().userProvided(false).build(); // Ensure previous values don't carry over + assertEquals(300, rescoreContext.getFirstPassK(finalK, false, dimension)); // 3x oversampling + + // Case 4: Shard-level rescoring enabled, oversample factor should be used as provided by the user (ignore dimension) + rescoreContext = RescoreContext.builder().oversampleFactor(5.0f).userProvided(true).build(); // Provided by user + dimension = 500; + assertEquals(500, rescoreContext.getFirstPassK(finalK, true, dimension)); // User-defined oversample factor should be used + + // Case 5: Test finalK where oversampling factor results in a value less than MIN_FIRST_PASS_RESULTS finalK = 10; - oversample = 0.5f; // This will result in 5, which is less than MIN_FIRST_PASS_RESULTS - rescoreContext = RescoreContext.builder().oversampleFactor(oversample).build(); - assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); + dimension = 700; + rescoreContext = RescoreContext.builder().userProvided(false).build(); // Ensure dimension-based logic applies + assertEquals(100, rescoreContext.getFirstPassK(finalK, false, dimension)); // 3x oversampling results in 30 + } + + public void testGetFirstPassKWithMinPassK() { + float oversample = 0.5f; + RescoreContext rescoreContext = RescoreContext.builder().oversampleFactor(oversample).userProvided(true).build(); // User provided + boolean isShardLevelRescoringEnabled = false; + + // Case 1: Test where finalK * oversample is smaller than MIN_FIRST_PASS_RESULTS + int finalK = 10; + int dimension = 700; + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); - // Case 6: Test where finalK * oversample results in exactly MIN_FIRST_PASS_RESULTS + // Case 2: Test where finalK * oversample results in exactly MIN_FIRST_PASS_RESULTS finalK = 100; oversample = 1.0f; // This will result in exactly 100 (MIN_FIRST_PASS_RESULTS) - rescoreContext = RescoreContext.builder().oversampleFactor(oversample).build(); - assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK)); + rescoreContext = RescoreContext.builder().oversampleFactor(oversample).userProvided(true).build(); // User provided + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); } } From 2c170fb67fdc73f51f9a5c33e88a34a29a82270d Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:30:22 -0500 Subject: [PATCH 35/59] Update release notes 2.17.1 (#2158) (#2160) Signed-off-by: Naveen Tatikonda (cherry picked from commit fbec0aa6c6b8dd58b42ce87fe82e178913486c3f) Co-authored-by: Naveen Tatikonda --- release-notes/opensearch-knn.release-notes-2.17.1.0.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/release-notes/opensearch-knn.release-notes-2.17.1.0.md b/release-notes/opensearch-knn.release-notes-2.17.1.0.md index 0af275a5b..0d2083959 100644 --- a/release-notes/opensearch-knn.release-notes-2.17.1.0.md +++ b/release-notes/opensearch-knn.release-notes-2.17.1.0.md @@ -2,7 +2,6 @@ Compatible with OpenSearch 2.17.1 -### Enhancements -* Adds concurrent segment search support for mode auto [#2111](https://github.com/opensearch-project/k-NN/pull/2111) ### Bug Fixes +* Adds concurrent segment search support for mode auto [#2111](https://github.com/opensearch-project/k-NN/pull/2111) * Change min oversample to 1 [#2117](https://github.com/opensearch-project/k-NN/pull/2117) From 33229126045b545c9f8e215e11b72b2335e287ae Mon Sep 17 00:00:00 2001 From: Navneet Verma Date: Tue, 8 Oct 2024 15:01:44 -0700 Subject: [PATCH 36/59] Fix lucene codec after lucene version bumped to 9.12 (#2195) Signed-off-by: Navneet Verma --- CHANGELOG.md | 1 + .../codec/KNN9120Codec/KNN9120Codec.java | 61 +++++++++++++++++++ .../NativeEngineFieldVectorsWriter.java | 32 ++++++++-- .../NativeEngines990KnnVectorsWriter.java | 8 ++- .../knn/index/codec/KNNCodecVersion.java | 21 ++++++- ...KNNScalarQuantizedVectorsFormatParams.java | 14 ++++- .../services/org.apache.lucene.codecs.Codec | 1 + .../NativeEngineFieldVectorsWriterTests.java | 51 ++++++++++++---- ...eEngines990KnnVectorsWriterFlushTests.java | 17 ++++-- ...eEngines990KnnVectorsWriterMergeTests.java | 16 +++-- ...alarQuantizedVectorsFormatParamsTests.java | 53 +++++++++++++++- 11 files changed, 241 insertions(+), 34 deletions(-) create mode 100644 src/main/java/org/opensearch/knn/index/codec/KNN9120Codec/KNN9120Codec.java diff --git a/CHANGELOG.md b/CHANGELOG.md index f615a78fb..7bc3019df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Documentation * Fix sed command in DEVELOPER_GUIDE.md to append a new line character '\n'. [#2181](https://github.com/opensearch-project/k-NN/pull/2181) ### Maintenance +* Fix lucene codec after lucene version bumped to 9.12. [#2195](https://github.com/opensearch-project/k-NN/pull/2195) ### Refactoring * Does not create additional KNNVectorValues in NativeEngines990KNNVectorWriter when quantization is not needed [#2133](https://github.com/opensearch-project/k-NN/pull/2133) diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN9120Codec/KNN9120Codec.java b/src/main/java/org/opensearch/knn/index/codec/KNN9120Codec/KNN9120Codec.java new file mode 100644 index 000000000..a370197ec --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN9120Codec/KNN9120Codec.java @@ -0,0 +1,61 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN9120Codec; + +import lombok.Builder; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.CompoundFormat; +import org.apache.lucene.codecs.DocValuesFormat; +import org.apache.lucene.codecs.FilterCodec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.opensearch.knn.index.codec.KNNCodecVersion; +import org.opensearch.knn.index.codec.KNNFormatFacade; + +/** + * KNN Codec that wraps the Lucene Codec which is part of Lucene 9.12 + */ +public class KNN9120Codec extends FilterCodec { + private static final KNNCodecVersion VERSION = KNNCodecVersion.V_9_12_0; + private final KNNFormatFacade knnFormatFacade; + private final PerFieldKnnVectorsFormat perFieldKnnVectorsFormat; + + /** + * No arg constructor that uses Lucene99 as the delegate + */ + public KNN9120Codec() { + this(VERSION.getDefaultCodecDelegate(), VERSION.getPerFieldKnnVectorsFormat()); + } + + /** + * Sole constructor. When subclassing this codec, create a no-arg ctor and pass the delegate codec + * and a unique name to this ctor. + * + * @param delegate codec that will perform all operations this codec does not override + * @param knnVectorsFormat per field format for KnnVector + */ + @Builder + protected KNN9120Codec(Codec delegate, PerFieldKnnVectorsFormat knnVectorsFormat) { + super(VERSION.getCodecName(), delegate); + knnFormatFacade = VERSION.getKnnFormatFacadeSupplier().apply(delegate); + perFieldKnnVectorsFormat = knnVectorsFormat; + } + + @Override + public DocValuesFormat docValuesFormat() { + return knnFormatFacade.docValuesFormat(); + } + + @Override + public CompoundFormat compoundFormat() { + return knnFormatFacade.compoundFormat(); + } + + @Override + public KnnVectorsFormat knnVectorsFormat() { + return perFieldKnnVectorsFormat; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriter.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriter.java index 1abb84944..389c76e49 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriter.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriter.java @@ -13,11 +13,13 @@ import lombok.Getter; import org.apache.lucene.codecs.KnnFieldVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatFieldVectorsWriter; import org.apache.lucene.index.DocsWithFieldSet; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.util.InfoStream; import org.apache.lucene.util.RamUsageEstimator; +import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -44,22 +46,37 @@ class NativeEngineFieldVectorsWriter extends KnnFieldVectorsWriter { @Getter private final DocsWithFieldSet docsWithField; private final InfoStream infoStream; + private final FlatFieldVectorsWriter flatFieldVectorsWriter; - static NativeEngineFieldVectorsWriter create(final FieldInfo fieldInfo, final InfoStream infoStream) { + @SuppressWarnings("unchecked") + static NativeEngineFieldVectorsWriter create( + final FieldInfo fieldInfo, + final FlatFieldVectorsWriter flatFieldVectorsWriter, + final InfoStream infoStream + ) { switch (fieldInfo.getVectorEncoding()) { case FLOAT32: - return new NativeEngineFieldVectorsWriter(fieldInfo, infoStream); + return new NativeEngineFieldVectorsWriter<>( + fieldInfo, + (FlatFieldVectorsWriter) flatFieldVectorsWriter, + infoStream + ); case BYTE: - return new NativeEngineFieldVectorsWriter(fieldInfo, infoStream); + return new NativeEngineFieldVectorsWriter<>(fieldInfo, (FlatFieldVectorsWriter) flatFieldVectorsWriter, infoStream); } throw new IllegalStateException("Unsupported Vector encoding : " + fieldInfo.getVectorEncoding()); } - private NativeEngineFieldVectorsWriter(final FieldInfo fieldInfo, final InfoStream infoStream) { + private NativeEngineFieldVectorsWriter( + final FieldInfo fieldInfo, + final FlatFieldVectorsWriter flatFieldVectorsWriter, + final InfoStream infoStream + ) { this.fieldInfo = fieldInfo; this.infoStream = infoStream; vectors = new HashMap<>(); this.docsWithField = new DocsWithFieldSet(); + this.flatFieldVectorsWriter = flatFieldVectorsWriter; } /** @@ -70,7 +87,7 @@ private NativeEngineFieldVectorsWriter(final FieldInfo fieldInfo, final InfoStre * @param vectorValue T */ @Override - public void addValue(int docID, T vectorValue) { + public void addValue(int docID, T vectorValue) throws IOException { if (docID == lastDocID) { throw new IllegalArgumentException( "[NativeEngineKNNVectorWriter]VectorValuesField \"" @@ -81,6 +98,8 @@ public void addValue(int docID, T vectorValue) { // TODO: we can build the graph here too iteratively. but right now I am skipping that as we need iterative // graph build support on the JNI layer. assert docID > lastDocID; + // ensuring that vector is provided to flatFieldWriter. + flatFieldVectorsWriter.addValue(docID, vectorValue); vectors.put(docID, vectorValue); docsWithField.add(docID); lastDocID = docID; @@ -105,6 +124,7 @@ public long ramBytesUsed() { return SHALLOW_SIZE + docsWithField.ramBytesUsed() + (long) this.vectors.size() * (long) (RamUsageEstimator.NUM_BYTES_OBJECT_REF + RamUsageEstimator.NUM_BYTES_ARRAY_HEADER) + (long) this.vectors.size() * RamUsageEstimator.shallowSizeOfInstance( Integer.class - ) + (long) vectors.size() * fieldInfo.getVectorDimension() * fieldInfo.getVectorEncoding().byteSize; + ) + (long) vectors.size() * fieldInfo.getVectorDimension() * fieldInfo.getVectorEncoding().byteSize + flatFieldVectorsWriter + .ramBytesUsed(); } } diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java index 2f22565c9..eccad41c8 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java @@ -65,9 +65,13 @@ public NativeEngines990KnnVectorsWriter(SegmentWriteState segmentWriteState, Fla */ @Override public KnnFieldVectorsWriter addField(final FieldInfo fieldInfo) throws IOException { - final NativeEngineFieldVectorsWriter newField = NativeEngineFieldVectorsWriter.create(fieldInfo, segmentWriteState.infoStream); + final NativeEngineFieldVectorsWriter newField = NativeEngineFieldVectorsWriter.create( + fieldInfo, + flatVectorsWriter.addField(fieldInfo), + segmentWriteState.infoStream + ); fields.add(newField); - return flatVectorsWriter.addField(fieldInfo, newField); + return newField; } /** diff --git a/src/main/java/org/opensearch/knn/index/codec/KNNCodecVersion.java b/src/main/java/org/opensearch/knn/index/codec/KNNCodecVersion.java index 419505aa2..46171ce9f 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNNCodecVersion.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNNCodecVersion.java @@ -12,12 +12,14 @@ import org.apache.lucene.backward_codecs.lucene94.Lucene94Codec; import org.apache.lucene.codecs.Codec; import org.apache.lucene.backward_codecs.lucene95.Lucene95Codec; -import org.apache.lucene.codecs.lucene99.Lucene99Codec; +import org.apache.lucene.backward_codecs.lucene99.Lucene99Codec; +import org.apache.lucene.codecs.lucene912.Lucene912Codec; import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; import org.opensearch.index.mapper.MapperService; import org.opensearch.knn.index.codec.KNN80Codec.KNN80CompoundFormat; import org.opensearch.knn.index.codec.KNN80Codec.KNN80DocValuesFormat; import org.opensearch.knn.index.codec.KNN910Codec.KNN910Codec; +import org.opensearch.knn.index.codec.KNN9120Codec.KNN9120Codec; import org.opensearch.knn.index.codec.KNN920Codec.KNN920Codec; import org.opensearch.knn.index.codec.KNN920Codec.KNN920PerFieldKnnVectorsFormat; import org.opensearch.knn.index.codec.KNN940Codec.KNN940Codec; @@ -110,9 +112,24 @@ public enum KNNCodecVersion { .knnVectorsFormat(new KNN990PerFieldKnnVectorsFormat(Optional.ofNullable(mapperService))) .build(), KNN990Codec::new + ), + + V_9_12_0( + "KNN9120Codec", + new Lucene912Codec(), + new KNN990PerFieldKnnVectorsFormat(Optional.empty()), + (delegate) -> new KNNFormatFacade( + new KNN80DocValuesFormat(delegate.docValuesFormat()), + new KNN80CompoundFormat(delegate.compoundFormat()) + ), + (userCodec, mapperService) -> KNN9120Codec.builder() + .delegate(userCodec) + .knnVectorsFormat(new KNN990PerFieldKnnVectorsFormat(Optional.ofNullable(mapperService))) + .build(), + KNN9120Codec::new ); - private static final KNNCodecVersion CURRENT = V_9_9_0; + private static final KNNCodecVersion CURRENT = V_9_12_0; private final String codecName; private final Codec defaultCodecDelegate; diff --git a/src/main/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParams.java b/src/main/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParams.java index e2d31183b..3498119c1 100644 --- a/src/main/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParams.java +++ b/src/main/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParams.java @@ -31,7 +31,8 @@ public KNNScalarQuantizedVectorsFormatParams(Map params, int def Map sqEncoderParams = encoderMethodComponentContext.getParameters(); this.initConfidenceInterval(sqEncoderParams); this.initBits(sqEncoderParams); - this.initCompressFlag(); + // compression flag should be set after bits has been initialised as compressionFlag depends on bits. + this.setCompressionFlag(); } @Override @@ -76,7 +77,14 @@ private void initBits(final Map params) { this.bits = LUCENE_SQ_DEFAULT_BITS; } - private void initCompressFlag() { - this.compressFlag = true; + private void setCompressionFlag() { + if (this.bits <= 0) { + throw new IllegalArgumentException( + "Either bits are set to less than 0 or they have not been initialized." + " Bit value: " + this.bits + ); + } + // This check is coming from Lucene. Code ref: + // https://github.com/apache/lucene/blob/branch_9_12/lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99ScalarQuantizedVectorsFormat.java#L113-L116 + this.compressFlag = this.bits <= 4; } } diff --git a/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec b/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec index 401b094b2..7a8916981 100644 --- a/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec +++ b/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec @@ -7,4 +7,5 @@ org.opensearch.knn.index.codec.KNN920Codec.KNN920Codec org.opensearch.knn.index.codec.KNN940Codec.KNN940Codec org.opensearch.knn.index.codec.KNN950Codec.KNN950Codec org.opensearch.knn.index.codec.KNN990Codec.KNN990Codec +org.opensearch.knn.index.codec.KNN9120Codec.KNN9120Codec org.opensearch.knn.index.codec.KNN990Codec.UnitTestCodec diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriterTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriterTests.java index 29e3531cf..6e6a51b88 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriterTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriterTests.java @@ -11,6 +11,8 @@ package org.opensearch.knn.index.codec.KNN990Codec; +import lombok.SneakyThrows; +import org.apache.lucene.codecs.hnsw.FlatFieldVectorsWriter; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.VectorEncoding; import org.apache.lucene.util.InfoStream; @@ -21,82 +23,109 @@ public class NativeEngineFieldVectorsWriterTests extends KNNCodecTestCase { @SuppressWarnings("unchecked") + @SneakyThrows public void testCreate_ForDifferentInputs_thenSuccess() { final FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.FLOAT32); + final FlatFieldVectorsWriter mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); NativeEngineFieldVectorsWriter floatWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter - .create(fieldInfo, InfoStream.getDefault()); - floatWriter.addValue(1, new float[] { 1.0f, 2.0f }); + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); + final float[] floatVector = new float[] { 1.0f, 2.0f }; + floatWriter.addValue(1, floatVector); + Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(1, floatVector); Mockito.verify(fieldInfo).getVectorEncoding(); + Mockito.verify(mockedFlatFieldVectorsWriter).addValue(1, floatVector); + final byte[] byteVector = new byte[] { 1, 2 }; + final FlatFieldVectorsWriter mockedFlatFieldByteVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + Mockito.doNothing().when(mockedFlatFieldByteVectorsWriter).addValue(1, byteVector); Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.BYTE); NativeEngineFieldVectorsWriter byteWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter.create( fieldInfo, + mockedFlatFieldByteVectorsWriter, InfoStream.getDefault() ); Assert.assertNotNull(byteWriter); Mockito.verify(fieldInfo, Mockito.times(2)).getVectorEncoding(); - byteWriter.addValue(1, new byte[] { 1, 2 }); + byteWriter.addValue(1, byteVector); + Mockito.verify(mockedFlatFieldByteVectorsWriter).addValue(1, byteVector); } @SuppressWarnings("unchecked") + @SneakyThrows public void testAddValue_ForDifferentInputs_thenSuccess() { final FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.FLOAT32); - final NativeEngineFieldVectorsWriter floatWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter - .create(fieldInfo, InfoStream.getDefault()); + final FlatFieldVectorsWriter mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); final float[] vec1 = new float[] { 1.0f, 2.0f }; final float[] vec2 = new float[] { 2.0f, 2.0f }; + Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(1, vec1); + Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(2, vec2); + final NativeEngineFieldVectorsWriter floatWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); floatWriter.addValue(1, vec1); floatWriter.addValue(2, vec2); + Mockito.verify(mockedFlatFieldVectorsWriter).addValue(1, vec1); + Mockito.verify(mockedFlatFieldVectorsWriter).addValue(2, vec2); Assert.assertEquals(vec1, floatWriter.getVectors().get(1)); Assert.assertEquals(vec2, floatWriter.getVectors().get(2)); Mockito.verify(fieldInfo).getVectorEncoding(); Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.BYTE); - final NativeEngineFieldVectorsWriter byteWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter - .create(fieldInfo, InfoStream.getDefault()); + final FlatFieldVectorsWriter mockedFlatFieldByteVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); final byte[] bvec1 = new byte[] { 1, 2 }; final byte[] bvec2 = new byte[] { 2, 2 }; + Mockito.doNothing().when(mockedFlatFieldByteVectorsWriter).addValue(1, bvec1); + Mockito.doNothing().when(mockedFlatFieldByteVectorsWriter).addValue(2, bvec2); + final NativeEngineFieldVectorsWriter byteWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter + .create(fieldInfo, mockedFlatFieldByteVectorsWriter, InfoStream.getDefault()); byteWriter.addValue(1, bvec1); byteWriter.addValue(2, bvec2); Assert.assertEquals(bvec1, byteWriter.getVectors().get(1)); Assert.assertEquals(bvec2, byteWriter.getVectors().get(2)); Mockito.verify(fieldInfo, Mockito.times(2)).getVectorEncoding(); + Mockito.verify(mockedFlatFieldByteVectorsWriter).addValue(1, bvec1); + Mockito.verify(mockedFlatFieldByteVectorsWriter).addValue(2, bvec2); } @SuppressWarnings("unchecked") + @SneakyThrows public void testCopyValue_whenValidInput_thenException() { final FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + FlatFieldVectorsWriter mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.FLOAT32); final NativeEngineFieldVectorsWriter floatWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter - .create(fieldInfo, InfoStream.getDefault()); + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); expectThrows(UnsupportedOperationException.class, () -> floatWriter.copyValue(new float[3])); Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.BYTE); final NativeEngineFieldVectorsWriter byteWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter - .create(fieldInfo, InfoStream.getDefault()); + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); expectThrows(UnsupportedOperationException.class, () -> byteWriter.copyValue(new byte[3])); } @SuppressWarnings("unchecked") + @SneakyThrows public void testRamByteUsed_whenValidInput_thenSuccess() { final FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.FLOAT32); Mockito.when(fieldInfo.getVectorDimension()).thenReturn(2); + FlatFieldVectorsWriter mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + Mockito.when(mockedFlatFieldVectorsWriter.ramBytesUsed()).thenReturn(1L); final NativeEngineFieldVectorsWriter floatWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter - .create(fieldInfo, InfoStream.getDefault()); + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); // testing for value > 0 as we don't have a concrete way to find out expected bytes. This can OS dependent too. Assert.assertTrue(floatWriter.ramBytesUsed() > 0); Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.BYTE); final NativeEngineFieldVectorsWriter byteWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter - .create(fieldInfo, InfoStream.getDefault()); + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); // testing for value > 0 as we don't have a concrete way to find out expected bytes. This can OS dependent too. Assert.assertTrue(byteWriter.ramBytesUsed() > 0); + Mockito.verify(mockedFlatFieldVectorsWriter, Mockito.times(2)).ramBytesUsed(); } } diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java index 9f74b2c10..5bb6d1926 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java @@ -8,6 +8,7 @@ import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; +import org.apache.lucene.codecs.hnsw.FlatFieldVectorsWriter; import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; import org.apache.lucene.index.DocsWithFieldSet; import org.apache.lucene.index.FieldInfo; @@ -16,6 +17,7 @@ import org.mockito.Mock; import org.mockito.MockedConstruction; import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.VectorDataType; @@ -68,6 +70,8 @@ public class NativeEngines990KnnVectorsWriterFlushTests extends OpenSearchTestCa @Mock private NativeIndexWriter nativeIndexWriter; + private FlatFieldVectorsWriter mockedFlatFieldVectorsWriter; + private NativeEngines990KnnVectorsWriter objectUnderTest; private final String description; @@ -78,6 +82,9 @@ public void setUp() throws Exception { super.setUp(); MockitoAnnotations.openMocks(this); objectUnderTest = new NativeEngines990KnnVectorsWriter(segmentWriteState, flatVectorsWriter); + mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(Mockito.anyInt(), Mockito.any()); + Mockito.when(flatVectorsWriter.addField(Mockito.any())).thenReturn(mockedFlatFieldVectorsWriter); } @ParametersFactory @@ -139,8 +146,9 @@ public void testFlush() { ); NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); - fieldWriterMockedStatic.when(() -> NativeEngineFieldVectorsWriter.create(fieldInfo, segmentWriteState.infoStream)) - .thenReturn(field); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); try { objectUnderTest.addField(fieldInfo); @@ -227,8 +235,9 @@ public void testFlush_WithQuantization() { ); NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); - fieldWriterMockedStatic.when(() -> NativeEngineFieldVectorsWriter.create(fieldInfo, segmentWriteState.infoStream)) - .thenReturn(field); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); try { objectUnderTest.addField(fieldInfo); diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java index 41940c4d4..af18cd281 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java @@ -9,6 +9,7 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.apache.lucene.codecs.KnnVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatFieldVectorsWriter; import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; import org.apache.lucene.index.DocsWithFieldSet; import org.apache.lucene.index.FieldInfo; @@ -19,6 +20,7 @@ import org.mockito.Mock; import org.mockito.MockedConstruction; import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.VectorDataType; @@ -74,12 +76,16 @@ public class NativeEngines990KnnVectorsWriterMergeTests extends OpenSearchTestCa private final String description; private final Map mergedVectors; + private FlatFieldVectorsWriter mockedFlatFieldVectorsWriter; @Override public void setUp() throws Exception { super.setUp(); MockitoAnnotations.openMocks(this); objectUnderTest = new NativeEngines990KnnVectorsWriter(segmentWriteState, flatVectorsWriter); + mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(Mockito.anyInt(), Mockito.any()); + Mockito.when(flatVectorsWriter.addField(Mockito.any())).thenReturn(mockedFlatFieldVectorsWriter); } @ParametersFactory @@ -120,8 +126,9 @@ public void testMerge() { ); NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, mergedVectors); - fieldWriterMockedStatic.when(() -> NativeEngineFieldVectorsWriter.create(fieldInfo, segmentWriteState.infoStream)) - .thenReturn(field); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); mergedVectorValuesMockedStatic.when(() -> KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState)) .thenReturn(floatVectorValues); @@ -184,8 +191,9 @@ public void testMerge_WithQuantization() { ); NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, mergedVectors); - fieldWriterMockedStatic.when(() -> NativeEngineFieldVectorsWriter.create(fieldInfo, segmentWriteState.infoStream)) - .thenReturn(field); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); mergedVectorValuesMockedStatic.when(() -> KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState)) .thenReturn(floatVectorValues); diff --git a/src/test/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParamsTests.java b/src/test/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParamsTests.java index 573e826e0..b7394b06a 100644 --- a/src/test/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParamsTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParamsTests.java @@ -12,12 +12,14 @@ package org.opensearch.knn.index.codec.params; import junit.framework.TestCase; +import org.junit.Assert; import org.opensearch.knn.index.engine.MethodComponentContext; import java.util.HashMap; import java.util.Map; import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_BITS; import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_CONFIDENCE_INTERVAL; import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_DEFAULT_BITS; import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; @@ -39,7 +41,7 @@ public void testInitParams_whenCalled_thenReturnDefaultParams() { assertEquals(DEFAULT_MAX_CONNECTIONS, knnScalarQuantizedVectorsFormatParams.getMaxConnections()); assertEquals(DEFAULT_BEAM_WIDTH, knnScalarQuantizedVectorsFormatParams.getBeamWidth()); assertNull(knnScalarQuantizedVectorsFormatParams.getConfidenceInterval()); - assertTrue(knnScalarQuantizedVectorsFormatParams.isCompressFlag()); + assertFalse(knnScalarQuantizedVectorsFormatParams.isCompressFlag()); assertEquals(LUCENE_SQ_DEFAULT_BITS, knnScalarQuantizedVectorsFormatParams.getBits()); } @@ -65,10 +67,57 @@ public void testInitParams_whenCalled_thenReturnParams() { assertEquals(m, knnScalarQuantizedVectorsFormatParams.getMaxConnections()); assertEquals(efConstruction, knnScalarQuantizedVectorsFormatParams.getBeamWidth()); assertEquals((float) MINIMUM_CONFIDENCE_INTERVAL, knnScalarQuantizedVectorsFormatParams.getConfidenceInterval()); - assertTrue(knnScalarQuantizedVectorsFormatParams.isCompressFlag()); + assertFalse(knnScalarQuantizedVectorsFormatParams.isCompressFlag()); assertEquals(LUCENE_SQ_DEFAULT_BITS, knnScalarQuantizedVectorsFormatParams.getBits()); } + public void testInitParams_whenBitsIs4_thenReturnParams() { + int m = 64; + int efConstruction = 128; + + Map encoderParams = new HashMap<>(); + encoderParams.put(LUCENE_SQ_CONFIDENCE_INTERVAL, MINIMUM_CONFIDENCE_INTERVAL); + encoderParams.put(LUCENE_SQ_BITS, 4); + MethodComponentContext encoderComponentContext = new MethodComponentContext(ENCODER_SQ, encoderParams); + + Map params = new HashMap<>(); + params.put(METHOD_ENCODER_PARAMETER, encoderComponentContext); + params.put(METHOD_PARAMETER_M, m); + params.put(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction); + + KNNScalarQuantizedVectorsFormatParams knnScalarQuantizedVectorsFormatParams = new KNNScalarQuantizedVectorsFormatParams( + params, + DEFAULT_MAX_CONNECTIONS, + DEFAULT_BEAM_WIDTH + ); + + assertEquals(m, knnScalarQuantizedVectorsFormatParams.getMaxConnections()); + assertEquals(efConstruction, knnScalarQuantizedVectorsFormatParams.getBeamWidth()); + assertEquals((float) MINIMUM_CONFIDENCE_INTERVAL, knnScalarQuantizedVectorsFormatParams.getConfidenceInterval()); + assertTrue(knnScalarQuantizedVectorsFormatParams.isCompressFlag()); + assertEquals(4, knnScalarQuantizedVectorsFormatParams.getBits()); + } + + public void testInitParams_whenBitsIs0_thenThrowException() { + int m = 64; + int efConstruction = 128; + + Map encoderParams = new HashMap<>(); + encoderParams.put(LUCENE_SQ_CONFIDENCE_INTERVAL, MINIMUM_CONFIDENCE_INTERVAL); + encoderParams.put(LUCENE_SQ_BITS, 0); + MethodComponentContext encoderComponentContext = new MethodComponentContext(ENCODER_SQ, encoderParams); + + Map params = new HashMap<>(); + params.put(METHOD_ENCODER_PARAMETER, encoderComponentContext); + params.put(METHOD_PARAMETER_M, m); + params.put(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction); + + Assert.assertThrows( + IllegalArgumentException.class, + () -> new KNNScalarQuantizedVectorsFormatParams(params, DEFAULT_MAX_CONNECTIONS, DEFAULT_BEAM_WIDTH) + ); + } + public void testValidate_whenCalled_thenReturnTrue() { Map params = getDefaultParamsForConstructor(); KNNScalarQuantizedVectorsFormatParams knnScalarQuantizedVectorsFormatParams = new KNNScalarQuantizedVectorsFormatParams( From f8ec9ee2dfa8a14b686573b7f34fe1c567d86bf7 Mon Sep 17 00:00:00 2001 From: John Mazanec Date: Wed, 9 Oct 2024 13:50:49 -0400 Subject: [PATCH 37/59] Fix changelog (#2198) Signed-off-by: John Mazanec --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bc3019df..16772fd00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,33 +7,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased 3.0](https://github.com/opensearch-project/k-NN/compare/2.x...HEAD) ### Features ### Enhancements -* Introducing a loading layer in FAISS [#2033](https://github.com/opensearch-project/k-NN/issues/2033) -### Bug Fixes -* Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) +### Bug Fixes ### Infrastructure * Removed JDK 11 and 17 version from CI runs [#1921](https://github.com/opensearch-project/k-NN/pull/1921) ### Documentation -* Fix sed command in DEVELOPER_GUIDE.md to append a new line character '\n'. [#2181](https://github.com/opensearch-project/k-NN/pull/2181) ### Maintenance -* Fix lucene codec after lucene version bumped to 9.12. [#2195](https://github.com/opensearch-project/k-NN/pull/2195) ### Refactoring -* Does not create additional KNNVectorValues in NativeEngines990KNNVectorWriter when quantization is not needed [#2133](https://github.com/opensearch-project/k-NN/pull/2133) ## [Unreleased 2.x](https://github.com/opensearch-project/k-NN/compare/2.17...2.x) ### Features * Add AVX512 support to k-NN for FAISS library [#2069](https://github.com/opensearch-project/k-NN/pull/2069) ### Enhancements +* Introducing a loading layer in FAISS [#2033](https://github.com/opensearch-project/k-NN/issues/2033) * Add short circuit if no live docs are in segments [#2059](https://github.com/opensearch-project/k-NN/pull/2059) * Optimize reduceToTopK in ResultUtil by removing pre-filling and reducing peek calls [#2146](https://github.com/opensearch-project/k-NN/pull/2146) * Update Default Rescore Context based on Dimension [#2149](https://github.com/opensearch-project/k-NN/pull/2149) * KNNIterators should support with and without filters [#2155](https://github.com/opensearch-project/k-NN/pull/2155) * Adding Support to Enable/Disble Share level Rescoring and Update Oversampling Factor[#2172](https://github.com/opensearch-project/k-NN/pull/2172) ### Bug Fixes +* Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) * KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) * Score Fix for Binary Quantized Vector and Setting Default value in case of shard level rescoring is disabled for oversampling factor[#2183](https://github.com/opensearch-project/k-NN/pull/2183) ### Infrastructure ### Documentation +* Fix sed command in DEVELOPER_GUIDE.md to append a new line character '\n'. [#2181](https://github.com/opensearch-project/k-NN/pull/2181) ### Maintenance * Remove benchmarks folder from k-NN repo [#2127](https://github.com/opensearch-project/k-NN/pull/2127) +* Fix lucene codec after lucene version bumped to 9.12. [#2195](https://github.com/opensearch-project/k-NN/pull/2195) ### Refactoring +* Does not create additional KNNVectorValues in NativeEngines990KNNVectorWriter when quantization is not needed [#2133](https://github.com/opensearch-project/k-NN/pull/2133) * Minor refactoring and refactored some unit test [#2167](https://github.com/opensearch-project/k-NN/pull/2167) From 5a56829385806c9c41e51518b8139744b1e0a0fb Mon Sep 17 00:00:00 2001 From: Vijayan Balasubramanian Date: Wed, 9 Oct 2024 23:50:29 -0700 Subject: [PATCH 38/59] Add support to build vector data structures greedily and perform exact search when there are no engine files (#2188) * Introduce new setting to configure when to build graph during segment creation (#2007) Added new updatable index setting "build_vector_data_structure_threshold", which will be considered when to build braph or not for native engines. This is noop for lucene. This depends on use lucene format as prerequisite. We don't need to add flag since it is only enable if lucene format is already enabled. Signed-off-by: Vijayan Balasubramanian * Add integration test for binary vector values (#2142) Signed-off-by: Vijayan Balasubramanian * Allow build graph greedily for quantization scenarios (#2175) Previosuly we only added support to build greedily for non quantization scenario. In this commit, we can remove that constraint, however, we cannot skip writing quanitization state since it is required irrespective of type of search is executed later. Signed-off-by: Vijayan Balasubramanian * Add exact search if no native engine files are available (#2136) * Add exact search if no engine files are in segments When graph is not available, plugin will return empty results. With this change, exact search will be performed when only no engine file is available in segment. We also don't need version check or feature flag because, option to not build vector data structure will only be available post 2.17. If an index is created using pre 2.17 version, segment will always have engine files and this feature will never be called during search. --------- Signed-off-by: Vijayan Balasubramanian * Add support for radial search in exact search (#2174) * Add support for radial search in exact search When threshold value is set, knn plugin will not be creating graph. Hence, when search request is trigged during that time, exact search will return valid results. However, radial search was never included as part of exact search. This will break radial search when threshold is added and radial search is requested. In this commit, new method is introduced to accept min score and return documents that are greater than min score, similar to how radial search is performed by native engines. This search is independent of engine, but, radial search is supported only for FAISS engine out of all native engines. Signed-off-by: Vijayan Balasubramanian --------- Signed-off-by: Vijayan Balasubramanian --- CHANGELOG.md | 1 + .../org/opensearch/knn/index/KNNSettings.java | 20 + .../codec/BasePerFieldKnnVectorsFormat.java | 20 +- .../NativeEngines990KnnVectorsFormat.java | 24 +- .../NativeEngines990KnnVectorsWriter.java | 37 +- .../knn/index/query/ExactSearcher.java | 55 +- .../opensearch/knn/index/query/KNNWeight.java | 140 +++-- .../knn/index/query/RNNQueryFactory.java | 1 + .../nativelib/NativeEngineKnnVectorQuery.java | 2 +- .../org/opensearch/knn/index/FaissIT.java | 231 +++++++- .../opensearch/knn/index/OpenSearchIT.java | 296 ++++++++++ .../NativeEngineFieldVectorsWriterTests.java | 1 - ...eEngines990KnnVectorsWriterFlushTests.java | 540 +++++++++++++++++- ...eEngines990KnnVectorsWriterMergeTests.java | 125 +++- .../knn/index/query/ExactSearcherTests.java | 139 +++++ .../knn/index/query/KNNWeightTests.java | 86 ++- .../opensearch/knn/integ/BinaryIndexIT.java | 75 ++- .../org/opensearch/knn/KNNRestTestCase.java | 18 +- 18 files changed, 1731 insertions(+), 80 deletions(-) create mode 100644 src/test/java/org/opensearch/knn/index/query/ExactSearcherTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 16772fd00..ea7c67943 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Update Default Rescore Context based on Dimension [#2149](https://github.com/opensearch-project/k-NN/pull/2149) * KNNIterators should support with and without filters [#2155](https://github.com/opensearch-project/k-NN/pull/2155) * Adding Support to Enable/Disble Share level Rescoring and Update Oversampling Factor[#2172](https://github.com/opensearch-project/k-NN/pull/2172) +* Add support to build vector data structures greedily and perform exact search when there are no engine files [#1942](https://github.com/opensearch-project/k-NN/issues/1942) ### Bug Fixes * Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) * KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) diff --git a/src/main/java/org/opensearch/knn/index/KNNSettings.java b/src/main/java/org/opensearch/knn/index/KNNSettings.java index f5980879a..fb9c26fef 100644 --- a/src/main/java/org/opensearch/knn/index/KNNSettings.java +++ b/src/main/java/org/opensearch/knn/index/KNNSettings.java @@ -67,6 +67,7 @@ public class KNNSettings { * Settings name */ public static final String KNN_SPACE_TYPE = "index.knn.space_type"; + public static final String INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD = "index.knn.advanced.approximate_threshold"; public static final String KNN_ALGO_PARAM_M = "index.knn.algo_param.m"; public static final String KNN_ALGO_PARAM_EF_CONSTRUCTION = "index.knn.algo_param.ef_construction"; public static final String KNN_ALGO_PARAM_EF_SEARCH = "index.knn.algo_param.ef_search"; @@ -97,6 +98,9 @@ public class KNNSettings { public static final boolean KNN_DEFAULT_FAISS_AVX2_DISABLED_VALUE = false; public static final boolean KNN_DEFAULT_FAISS_AVX512_DISABLED_VALUE = false; public static final String INDEX_KNN_DEFAULT_SPACE_TYPE = "l2"; + public static final Integer INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE = 0; + public static final Integer INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MIN = -1; + public static final Integer INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MAX = Integer.MAX_VALUE - 2; public static final String INDEX_KNN_DEFAULT_SPACE_TYPE_FOR_BINARY = "hamming"; public static final Integer INDEX_KNN_DEFAULT_ALGO_PARAM_M = 16; public static final Integer INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH = 100; @@ -156,6 +160,21 @@ public class KNNSettings { Setting.Property.Deprecated ); + /** + * build_vector_data_structure_threshold - This parameter determines when to build vector data structure for knn fields during indexing + * and merging. Setting -1 (min) will skip building graph, whereas on any other values, the graph will be built if + * number of live docs in segment is greater than this threshold. Since max number of documents in a segment can + * be Integer.MAX_VALUE - 1, this setting will allow threshold to be up to 1 less than max number of documents in a segment + */ + public static final Setting INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_SETTING = Setting.intSetting( + INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, + INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE, + INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MIN, + INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MAX, + IndexScope, + Dynamic + ); + /** * M - the number of bi-directional links created for every new element during construction. * Reasonable range for M is 2-100. Higher M work better on datasets with high intrinsic @@ -486,6 +505,7 @@ private Setting getSetting(String key) { public List> getSettings() { List> settings = Arrays.asList( INDEX_KNN_SPACE_TYPE, + INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_SETTING, INDEX_KNN_ALGO_PARAM_M_SETTING, INDEX_KNN_ALGO_PARAM_EF_CONSTRUCTION_SETTING, INDEX_KNN_ALGO_PARAM_EF_SEARCH_SETTING, diff --git a/src/main/java/org/opensearch/knn/index/codec/BasePerFieldKnnVectorsFormat.java b/src/main/java/org/opensearch/knn/index/codec/BasePerFieldKnnVectorsFormat.java index b06c2a1d8..72187516f 100644 --- a/src/main/java/org/opensearch/knn/index/codec/BasePerFieldKnnVectorsFormat.java +++ b/src/main/java/org/opensearch/knn/index/codec/BasePerFieldKnnVectorsFormat.java @@ -11,7 +11,9 @@ import org.apache.lucene.codecs.hnsw.FlatVectorScorerUtil; import org.apache.lucene.codecs.lucene99.Lucene99FlatVectorsFormat; import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.opensearch.index.IndexSettings; import org.opensearch.index.mapper.MapperService; +import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.codec.KNN990Codec.NativeEngines990KnnVectorsFormat; import org.opensearch.knn.index.codec.params.KNNScalarQuantizedVectorsFormatParams; import org.opensearch.knn.index.codec.params.KNNVectorsFormatParams; @@ -129,7 +131,23 @@ public KnnVectorsFormat getKnnVectorsFormatForField(final String field) { } private NativeEngines990KnnVectorsFormat nativeEngineVectorsFormat() { - return new NativeEngines990KnnVectorsFormat(new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer())); + // mapperService is already checked for null or valid instance type at caller, hence we don't need + // addition isPresent check here. + int approximateThreshold = getApproximateThresholdValue(); + return new NativeEngines990KnnVectorsFormat( + new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer()), + approximateThreshold + ); + } + + private int getApproximateThresholdValue() { + // This is private method and mapperService is already checked for null or valid instance type before this call + // at caller, hence we don't need additional isPresent check here. + final IndexSettings indexSettings = mapperService.get().getIndexSettings(); + final Integer approximateThresholdValue = indexSettings.getValue(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_SETTING); + return approximateThresholdValue != null + ? approximateThresholdValue + : KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE; } @Override diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormat.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormat.java index 626210f25..520a9838d 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormat.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormat.java @@ -19,6 +19,7 @@ import org.apache.lucene.codecs.lucene99.Lucene99FlatVectorsFormat; import org.apache.lucene.index.SegmentReadState; import org.apache.lucene.index.SegmentWriteState; +import org.opensearch.knn.index.KNNSettings; import java.io.IOException; @@ -30,15 +31,20 @@ public class NativeEngines990KnnVectorsFormat extends KnnVectorsFormat { /** The format for storing, reading, merging vectors on disk */ private static FlatVectorsFormat flatVectorsFormat; private static final String FORMAT_NAME = "NativeEngines990KnnVectorsFormat"; + private static int approximateThreshold; public NativeEngines990KnnVectorsFormat() { - super(FORMAT_NAME); - flatVectorsFormat = new Lucene99FlatVectorsFormat(new DefaultFlatVectorScorer()); + this(new Lucene99FlatVectorsFormat(new DefaultFlatVectorScorer())); + } + + public NativeEngines990KnnVectorsFormat(final FlatVectorsFormat flatVectorsFormat) { + this(flatVectorsFormat, KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE); } - public NativeEngines990KnnVectorsFormat(final FlatVectorsFormat lucene99FlatVectorsFormat) { + public NativeEngines990KnnVectorsFormat(final FlatVectorsFormat flatVectorsFormat, int approximateThreshold) { super(FORMAT_NAME); - flatVectorsFormat = lucene99FlatVectorsFormat; + NativeEngines990KnnVectorsFormat.flatVectorsFormat = flatVectorsFormat; + NativeEngines990KnnVectorsFormat.approximateThreshold = approximateThreshold; } /** @@ -48,7 +54,7 @@ public NativeEngines990KnnVectorsFormat(final FlatVectorsFormat lucene99FlatVect */ @Override public KnnVectorsWriter fieldsWriter(final SegmentWriteState state) throws IOException { - return new NativeEngines990KnnVectorsWriter(state, flatVectorsFormat.fieldsWriter(state)); + return new NativeEngines990KnnVectorsWriter(state, flatVectorsFormat.fieldsWriter(state), approximateThreshold); } /** @@ -63,6 +69,12 @@ public KnnVectorsReader fieldsReader(final SegmentReadState state) throws IOExce @Override public String toString() { - return "NativeEngines99KnnVectorsFormat(name=" + this.getClass().getSimpleName() + ", flatVectorsFormat=" + flatVectorsFormat + ")"; + return "NativeEngines99KnnVectorsFormat(name=" + + this.getClass().getSimpleName() + + ", flatVectorsFormat=" + + flatVectorsFormat + + ", approximateThreshold=" + + approximateThreshold + + ")"; } } diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java index eccad41c8..7c8636577 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java @@ -53,10 +53,16 @@ public class NativeEngines990KnnVectorsWriter extends KnnVectorsWriter { private KNN990QuantizationStateWriter quantizationStateWriter; private final List> fields = new ArrayList<>(); private boolean finished; + private final Integer approximateThreshold; - public NativeEngines990KnnVectorsWriter(SegmentWriteState segmentWriteState, FlatVectorsWriter flatVectorsWriter) { + public NativeEngines990KnnVectorsWriter( + SegmentWriteState segmentWriteState, + FlatVectorsWriter flatVectorsWriter, + Integer approximateThreshold + ) { this.segmentWriteState = segmentWriteState; this.flatVectorsWriter = flatVectorsWriter; + this.approximateThreshold = approximateThreshold; } /** @@ -98,6 +104,17 @@ public void flush(int maxDoc, final Sorter.DocMap sortMap) throws IOException { field.getVectors() ); final QuantizationState quantizationState = train(field.getFieldInfo(), knnVectorValuesSupplier, totalLiveDocs); + // Check only after quantization state writer finish writing its state, since it is required + // even if there are no graph files in segment, which will be later used by exact search + if (shouldSkipBuildingVectorDataStructure(totalLiveDocs)) { + log.info( + "Skip building vector data structure for field: {}, as liveDoc: {} is less than the threshold {} during flush", + fieldInfo.name, + totalLiveDocs, + approximateThreshold + ); + continue; + } final NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); final KNNVectorValues knnVectorValues = knnVectorValuesSupplier.get(); @@ -127,6 +144,17 @@ public void mergeOneField(final FieldInfo fieldInfo, final MergeState mergeState } final QuantizationState quantizationState = train(fieldInfo, knnVectorValuesSupplier, totalLiveDocs); + // Check only after quantization state writer finish writing its state, since it is required + // even if there are no graph files in segment, which will be later used by exact search + if (shouldSkipBuildingVectorDataStructure(totalLiveDocs)) { + log.info( + "Skip building vector data structure for field: {}, as liveDoc: {} is less than the threshold {} during merge", + fieldInfo.name, + totalLiveDocs, + approximateThreshold + ); + return; + } final NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); final KNNVectorValues knnVectorValues = knnVectorValuesSupplier.get(); @@ -257,4 +285,11 @@ private void initQuantizationStateWriterIfNecessary() throws IOException { quantizationStateWriter.writeHeader(segmentWriteState); } } + + private boolean shouldSkipBuildingVectorDataStructure(final long docCount) { + if (approximateThreshold < 0) { + return true; + } + return docCount < approximateThreshold; + } } diff --git a/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java b/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java index 8e5849abb..77e993297 100644 --- a/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java +++ b/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java @@ -5,8 +5,10 @@ package org.opensearch.knn.index.query; +import com.google.common.base.Predicates; import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.NonNull; import lombok.Value; import lombok.extern.log4j.Log4j2; import org.apache.lucene.index.FieldInfo; @@ -21,6 +23,7 @@ import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.query.iterators.BinaryVectorIdsKNNIterator; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.query.iterators.ByteVectorIdsKNNIterator; import org.opensearch.knn.index.query.iterators.NestedBinaryVectorIdsKNNIterator; import org.opensearch.knn.index.query.iterators.VectorIdsKNNIterator; @@ -36,7 +39,9 @@ import java.io.IOException; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import java.util.function.Predicate; @Log4j2 @AllArgsConstructor @@ -55,11 +60,41 @@ public class ExactSearcher { public Map searchLeaf(final LeafReaderContext leafReaderContext, final ExactSearcherContext exactSearcherContext) throws IOException { KNNIterator iterator = getKNNIterator(leafReaderContext, exactSearcherContext); + if (exactSearcherContext.getKnnQuery().getRadius() != null) { + return doRadialSearch(leafReaderContext, exactSearcherContext, iterator); + } if (exactSearcherContext.getMatchedDocs() != null && exactSearcherContext.getMatchedDocs().cardinality() <= exactSearcherContext.getK()) { return scoreAllDocs(iterator); } - return searchTopK(iterator, exactSearcherContext.getK()); + return searchTopCandidates(iterator, exactSearcherContext.getK(), Predicates.alwaysTrue()); + } + + /** + * Perform radial search by comparing scores with min score. Currently, FAISS from native engine supports radial search. + * Hence, we assume that Radius from knnQuery is always distance, and we convert it to score since we do exact search uses scores + * to filter out the documents that does not have given min score. + * @param leafReaderContext + * @param exactSearcherContext + * @param iterator {@link KNNIterator} + * @return Map of docId and score + * @throws IOException exception raised by iterator during traversal + */ + private Map doRadialSearch( + LeafReaderContext leafReaderContext, + ExactSearcherContext exactSearcherContext, + KNNIterator iterator + ) throws IOException { + final SegmentReader reader = Lucene.segmentReader(leafReaderContext.reader()); + final KNNQuery knnQuery = exactSearcherContext.getKnnQuery(); + final FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(knnQuery.getField()); + final KNNEngine engine = FieldInfoExtractor.extractKNNEngine(fieldInfo); + if (KNNEngine.FAISS != engine) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Engine [%s] does not support radial search", engine)); + } + final SpaceType spaceType = FieldInfoExtractor.getSpaceType(modelDao, fieldInfo); + final float minScore = spaceType.scoreTranslation(knnQuery.getRadius()); + return filterDocsByMinScore(exactSearcherContext, iterator, minScore); } private Map scoreAllDocs(KNNIterator iterator) throws IOException { @@ -71,15 +106,17 @@ private Map scoreAllDocs(KNNIterator iterator) throws IOExceptio return docToScore; } - private Map searchTopK(KNNIterator iterator, int k) throws IOException { + private Map searchTopCandidates(KNNIterator iterator, int limit, @NonNull Predicate filterScore) + throws IOException { // Creating min heap and init with MAX DocID and Score as -INF. - final HitQueue queue = new HitQueue(k, true); + final HitQueue queue = new HitQueue(limit, true); ScoreDoc topDoc = queue.top(); final Map docToScore = new HashMap<>(); int docId; while ((docId = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) { - if (iterator.score() > topDoc.score) { - topDoc.score = iterator.score(); + final float currentScore = iterator.score(); + if (filterScore.test(currentScore) && currentScore > topDoc.score) { + topDoc.score = currentScore; topDoc.doc = docId; // As the HitQueue is min heap, updating top will bring the doc with -INF score or worst score we // have seen till now on top. @@ -98,10 +135,16 @@ private Map searchTopK(KNNIterator iterator, int k) throws IOExc final ScoreDoc doc = queue.pop(); docToScore.put(doc.doc, doc.score); } - return docToScore; } + private Map filterDocsByMinScore(ExactSearcherContext context, KNNIterator iterator, float minScore) + throws IOException { + int maxResultWindow = context.getKnnQuery().getContext().getMaxResultWindow(); + Predicate scoreGreaterThanOrEqualToMinScore = score -> score >= minScore; + return searchTopCandidates(iterator, maxResultWindow, scoreGreaterThanOrEqualToMinScore); + } + private KNNIterator getKNNIterator(LeafReaderContext leafReaderContext, ExactSearcherContext exactSearcherContext) throws IOException { final KNNQuery knnQuery = exactSearcherContext.getKnnQuery(); final BitSet matchedDocs = exactSearcherContext.getMatchedDocs(); diff --git a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java index 37695c208..87c99b884 100644 --- a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java +++ b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java @@ -5,6 +5,7 @@ package org.opensearch.knn.index.query; +import com.google.common.annotations.VisibleForTesting; import lombok.extern.log4j.Log4j2; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.LeafReaderContext; @@ -22,6 +23,7 @@ import org.apache.lucene.util.FixedBitSet; import org.opensearch.common.io.PathUtils; import org.opensearch.common.lucene.Lucene; +import org.opensearch.knn.common.FieldInfoExtractor; import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.SpaceType; @@ -33,6 +35,7 @@ import org.opensearch.knn.index.memory.NativeMemoryLoadStrategy; import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.query.ExactSearcher.ExactSearcherContext.ExactSearcherContextBuilder; import org.opensearch.knn.indices.ModelDao; import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.knn.indices.ModelUtil; @@ -93,8 +96,13 @@ public KNNWeight(KNNQuery query, float boost, Weight filterWeight) { } public static void initialize(ModelDao modelDao) { + initialize(modelDao, new ExactSearcher(modelDao)); + } + + @VisibleForTesting + static void initialize(ModelDao modelDao, ExactSearcher exactSearcher) { KNNWeight.modelDao = modelDao; - KNNWeight.DEFAULT_EXACT_SEARCHER = new ExactSearcher(modelDao); + KNNWeight.DEFAULT_EXACT_SEARCHER = exactSearcher; } @Override @@ -129,42 +137,21 @@ public Map searchLeaf(LeafReaderContext context, int k) throws I if (filterWeight != null && cardinality == 0) { return Collections.emptyMap(); } - /* - * The idea for this optimization is to get K results, we need to atleast look at K vectors in the HNSW graph + * The idea for this optimization is to get K results, we need to at least look at K vectors in the HNSW graph * . Hence, if filtered results are less than K and filter query is present we should shift to exact search. * This improves the recall. */ - Map docIdsToScoreMap; - final ExactSearcher.ExactSearcherContext exactSearcherContext = ExactSearcher.ExactSearcherContext.builder() - .k(k) - .isParentHits(true) - .matchedDocs(filterBitSet) - // setting to true, so that if quantization details are present we want to do search on the quantized - // vectors as this flow is used in first pass of search. - .useQuantizedVectorsForSearch(true) - .knnQuery(knnQuery) - .build(); - if (filterWeight != null && canDoExactSearch(cardinality)) { - docIdsToScoreMap = exactSearch(context, exactSearcherContext); - } else { - docIdsToScoreMap = doANNSearch(context, filterBitSet, cardinality, k); - if (docIdsToScoreMap == null) { - return Collections.emptyMap(); - } - if (canDoExactSearchAfterANNSearch(cardinality, docIdsToScoreMap.size())) { - log.debug( - "Doing ExactSearch after doing ANNSearch as the number of documents returned are less than " - + "K, even when we have more than K filtered Ids. K: {}, ANNResults: {}, filteredIdCount: {}", - k, - docIdsToScoreMap.size(), - cardinality - ); - docIdsToScoreMap = exactSearch(context, exactSearcherContext); - } + if (isFilteredExactSearchPreferred(cardinality)) { + return doExactSearch(context, filterBitSet, k); } - if (docIdsToScoreMap.isEmpty()) { - return Collections.emptyMap(); + Map docIdsToScoreMap = doANNSearch(context, filterBitSet, cardinality, k); + // See whether we have to perform exact search based on approx search results + // This is required if there are no native engine files or if approximate search returned + // results less than K, though we have more than k filtered docs + if (isExactSearchRequire(context, cardinality, docIdsToScoreMap.size())) { + final BitSet docs = filterWeight != null ? filterBitSet : null; + return doExactSearch(context, docs, k); } return docIdsToScoreMap; } @@ -221,6 +208,20 @@ private int[] bitSetToIntArray(final BitSet bitSet) { return intArray; } + private Map doExactSearch(final LeafReaderContext context, final BitSet acceptedDocs, int k) throws IOException { + final ExactSearcherContextBuilder exactSearcherContextBuilder = ExactSearcher.ExactSearcherContext.builder() + .isParentHits(true) + .k(k) + // setting to true, so that if quantization details are present we want to do search on the quantized + // vectors as this flow is used in first pass of search. + .useQuantizedVectorsForSearch(true) + .knnQuery(knnQuery); + if (acceptedDocs != null) { + exactSearcherContextBuilder.matchedDocs(acceptedDocs); + } + return exactSearch(context, exactSearcherContextBuilder.build()); + } + private Map doANNSearch( final LeafReaderContext context, final BitSet filterIdsBitSet, @@ -234,7 +235,7 @@ private Map doANNSearch( if (fieldInfo == null) { log.debug("[KNN] Field info not found for {}:{}", knnQuery.getField(), reader.getSegmentName()); - return null; + return Collections.emptyMap(); } KNNEngine knnEngine; @@ -273,8 +274,8 @@ private Map doANNSearch( List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), knnQuery.getField(), reader.getSegmentInfo().info); if (engineFiles.isEmpty()) { - log.debug("[KNN] No engine index found for field {} for segment {}", knnQuery.getField(), reader.getSegmentName()); - return null; + log.debug("[KNN] No native engine files found for field {} for segment {}", knnQuery.getField(), reader.getSegmentName()); + return Collections.emptyMap(); } Path indexPath = PathUtils.get(directory, engineFiles.get(0)); @@ -364,16 +365,9 @@ private Map doANNSearch( indexAllocation.readUnlock(); indexAllocation.decRef(); } - - /* - * Scores represent the distance of the documents with respect to given query vector. - * Lesser the score, the closer the document is to the query vector. - * Since by default results are retrieved in the descending order of scores, to get the nearest - * neighbors we are inverting the scores. - */ if (results.length == 0) { log.debug("[KNN] Query yielded 0 results"); - return null; + return Collections.emptyMap(); } if (quantizedVector != null) { @@ -386,7 +380,6 @@ private Map doANNSearch( /** * Execute exact search for the given matched doc ids and return the results as a map of docId to score. - * * @return Map of docId to score for the exact search results. * @throws IOException If an error occurs during the search. */ @@ -407,18 +400,18 @@ public static float normalizeScore(float score) { return -score + 1; } - private boolean canDoExactSearch(final int filterIdsCount) { + private boolean isFilteredExactSearchPreferred(final int filterIdsCount) { + if (filterWeight == null) { + return false; + } log.debug( "Info for doing exact search filterIdsLength : {}, Threshold value: {}", filterIdsCount, KNNSettings.getFilteredExactSearchThreshold(knnQuery.getIndexName()) ); - if (knnQuery.getRadius() != null) { - return false; - } int filterThresholdValue = KNNSettings.getFilteredExactSearchThreshold(knnQuery.getIndexName()); // Refer this GitHub around more details https://github.com/opensearch-project/k-NN/issues/1049 on the logic - if (filterIdsCount <= knnQuery.getK()) { + if (knnQuery.getRadius() == null && filterIdsCount <= knnQuery.getK()) { return true; } // See user has defined Exact Search filtered threshold. if yes, then use that setting. @@ -446,14 +439,59 @@ private boolean isExactSearchThresholdSettingSet(int filterThresholdValue) { return filterThresholdValue != KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_DEFAULT_VALUE; } + /** + * This condition mainly checks whether exact search should be performed or not + * @param context LeafReaderContext + * @param filterIdsCount count of filtered Doc ids + * @param annResultCount Count of Nearest Neighbours we got after doing filtered ANN Search. + * @return boolean - true if exactSearch needs to be done after ANNSearch. + */ + private boolean isExactSearchRequire(final LeafReaderContext context, final int filterIdsCount, final int annResultCount) { + if (annResultCount == 0 && isMissingNativeEngineFiles(context)) { + log.debug("Perform exact search after approximate search since no native engine files are available"); + return true; + } + if (isFilteredExactSearchRequireAfterANNSearch(filterIdsCount, annResultCount)) { + log.debug( + "Doing ExactSearch after doing ANNSearch as the number of documents returned are less than " + + "K, even when we have more than K filtered Ids. K: {}, ANNResults: {}, filteredIdCount: {}", + this.knnQuery.getK(), + annResultCount, + filterIdsCount + ); + return true; + } + return false; + } + /** * This condition mainly checks during filtered search we have more than K elements in filterIds but the ANN - * doesn't yeild K nearest neighbors. + * doesn't yield K nearest neighbors. * @param filterIdsCount count of filtered Doc ids * @param annResultCount Count of Nearest Neighbours we got after doing filtered ANN Search. * @return boolean - true if exactSearch needs to be done after ANNSearch. */ - private boolean canDoExactSearchAfterANNSearch(final int filterIdsCount, final int annResultCount) { + private boolean isFilteredExactSearchRequireAfterANNSearch(final int filterIdsCount, final int annResultCount) { return filterWeight != null && filterIdsCount >= knnQuery.getK() && knnQuery.getK() > annResultCount; } + + /** + * This condition mainly checks whether segments has native engine files or not + * @return boolean - false if exactSearch needs to be done since no native engine files are in segments. + */ + private boolean isMissingNativeEngineFiles(LeafReaderContext context) { + final SegmentReader reader = Lucene.segmentReader(context.reader()); + final FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(knnQuery.getField()); + // if segment has no documents with at least 1 vector field, field info will be null + if (fieldInfo == null) { + return false; + } + final KNNEngine knnEngine = FieldInfoExtractor.extractKNNEngine(fieldInfo); + final List engineFiles = KNNCodecUtil.getEngineFiles( + knnEngine.getExtension(), + knnQuery.getField(), + reader.getSegmentInfo().info + ); + return engineFiles.isEmpty(); + } } diff --git a/src/main/java/org/opensearch/knn/index/query/RNNQueryFactory.java b/src/main/java/org/opensearch/knn/index/query/RNNQueryFactory.java index 99152ef6b..b5166866c 100644 --- a/src/main/java/org/opensearch/knn/index/query/RNNQueryFactory.java +++ b/src/main/java/org/opensearch/knn/index/query/RNNQueryFactory.java @@ -88,6 +88,7 @@ public static Query create(RNNQueryFactory.CreateQueryRequest createQueryRequest .indexName(indexName) .parentsFilter(parentFilter) .radius(radius) + .vectorDataType(vectorDataType) .methodParameters(methodParameters) .context(knnQueryContext) .filterQuery(filterQuery) diff --git a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java index c97a0d061..8b861b430 100644 --- a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java +++ b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java @@ -57,7 +57,7 @@ public Weight createWeight(IndexSearcher indexSearcher, ScoreMode scoreMode, flo List leafReaderContexts = reader.leaves(); List> perLeafResults; RescoreContext rescoreContext = knnQuery.getRescoreContext(); - int finalK = knnQuery.getK(); + final int finalK = knnQuery.getK(); if (rescoreContext == null) { perLeafResults = doSearch(indexSearcher, leafReaderContexts, knnWeight, finalK); } else { diff --git a/src/test/java/org/opensearch/knn/index/FaissIT.java b/src/test/java/org/opensearch/knn/index/FaissIT.java index 2df1d8a60..c494f7f1f 100644 --- a/src/test/java/org/opensearch/knn/index/FaissIT.java +++ b/src/test/java/org/opensearch/knn/index/FaissIT.java @@ -92,6 +92,7 @@ public class FaissIT extends KNNRestTestCase { private static final String INTEGER_FIELD_NAME = "int_field"; private static final String FILED_TYPE_INTEGER = "integer"; private static final String NON_EXISTENT_INTEGER_FIELD_NAME = "nonexistent_int_field"; + public static final int NEVER_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD = -1; static TestUtils.TestData testData; @@ -575,6 +576,125 @@ public void testHNSWSQFP16_whenIndexedAndQueried_thenSucceed() { validateGraphEviction(); } + @SneakyThrows + public void testHNSWSQFP16_whenGraphThresholdIsNegative_whenIndexed_thenSkipCreatingGraph() { + final String indexName = "test-index-hnsw-sqfp16"; + final String fieldName = "test-field-hnsw-sqfp16"; + final SpaceType[] spaceTypes = { SpaceType.L2, SpaceType.INNER_PRODUCT }; + final Random random = new Random(); + final SpaceType spaceType = spaceTypes[random.nextInt(spaceTypes.length)]; + + final int dimension = 128; + final int numDocs = 100; + + // Create an index + final XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + final Map mappingMap = xContentBuilderToMap(builder); + final String mapping = builder.toString(); + final Settings knnIndexSettings = buildKNNIndexSettings(-1); + createKnnIndex(indexName, knnIndexSettings, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + indexTestData(indexName, fieldName, dimension, numDocs); + + final float[] queryVector = new float[dimension]; + Arrays.fill(queryVector, (float) numDocs); + + // Assert we have the right number of documents in the index + assertEquals(numDocs, getDocCount(indexName)); + + final Response searchResponse = searchKNNIndex(indexName, buildSearchQuery(fieldName, 1, queryVector, null), 1); + final List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), fieldName); + // expect result due to exact search + assertEquals(1, results.size()); + + deleteKNNIndex(indexName); + validateGraphEviction(); + } + + @SneakyThrows + public void testHNSWSQFP16_whenGraphThresholdIsMetDuringMerge_thenCreateGraph() { + final String indexName = "test-index-hnsw-sqfp16"; + final String fieldName = "test-field-hnsw-sqfp16"; + final SpaceType[] spaceTypes = { SpaceType.L2, SpaceType.INNER_PRODUCT }; + final Random random = new Random(); + final SpaceType spaceType = spaceTypes[random.nextInt(spaceTypes.length)]; + final int dimension = 128; + final int numDocs = 100; + + // Create an index + final XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + final Map mappingMap = xContentBuilderToMap(builder); + final String mapping = builder.toString(); + final Settings knnIndexSettings = buildKNNIndexSettings(numDocs); + createKnnIndex(indexName, knnIndexSettings, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + indexTestData(indexName, fieldName, dimension, numDocs); + + final float[] queryVector = new float[dimension]; + Arrays.fill(queryVector, (float) numDocs); + + // Assert we have the right number of documents in the index + assertEquals(numDocs, getDocCount(indexName)); + + // KNN Query should return empty result + final Response searchResponse = searchKNNIndex(indexName, buildSearchQuery(fieldName, 1, queryVector, null), 1); + final List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), fieldName); + assertEquals(1, results.size()); + + // update index setting to build graph and do force merge + // update build vector data structure setting + forceMergeKnnIndex(indexName, 1); + + queryTestData(indexName, fieldName, dimension, numDocs); + + deleteKNNIndex(indexName); + validateGraphEviction(); + } + @SneakyThrows public void testIVFSQFP16_whenIndexedAndQueried_thenSucceed() { @@ -1708,6 +1828,111 @@ public void testIVF_whenBinaryFormat_whenIVF_thenSuccess() { validateGraphEviction(); } + @SneakyThrows + public void testEndToEnd_whenDoRadiusSearch_whenNoGraphFileIsCreated_whenDistanceThreshold_thenSucceed() { + final SpaceType spaceType = SpaceType.L2; + + final List mValues = ImmutableList.of(16, 32, 64, 128); + final List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + final List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + final Integer dimension = testData.indexData.vectors[0].length; + final Settings knnIndexSettings = buildKNNIndexSettings(NEVER_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD); + + // Create an index + final XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + createKnnIndex(INDEX_NAME, knnIndexSettings, builder.toString()); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + INDEX_NAME, + Integer.toString(testData.indexData.docs[i]), + FIELD_NAME, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + + // Assert we have the right number of documents + refreshAllNonSystemIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(INDEX_NAME)); + + final float distance = 300000000000f; + final List> resultsFromDistance = validateRadiusSearchResults( + INDEX_NAME, + FIELD_NAME, + testData.queries, + distance, + null, + spaceType, + null, + null + ); + assertFalse(resultsFromDistance.isEmpty()); + resultsFromDistance.forEach(result -> { assertFalse(result.isEmpty()); }); + final float score = spaceType.scoreTranslation(distance); + final List> resultsFromScore = validateRadiusSearchResults( + INDEX_NAME, + FIELD_NAME, + testData.queries, + null, + score, + spaceType, + null, + null + ); + assertFalse(resultsFromScore.isEmpty()); + resultsFromScore.forEach(result -> { assertFalse(result.isEmpty()); }); + + // Delete index + deleteKNNIndex(INDEX_NAME); + } + + @SneakyThrows + public void testRadialQueryWithFilter_whenNoGraphIsCreated_thenSuccess() { + setupKNNIndexForFilterQuery(buildKNNIndexSettings(NEVER_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD)); + + final float[][] searchVector = new float[][] { { 3.3f, 3.0f, 5.0f } }; + TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("color", "red"); + List expectedDocIds = Arrays.asList(DOC_ID_3); + + float distance = 15f; + List> queryResult = validateRadiusSearchResults( + INDEX_NAME, + FIELD_NAME, + searchVector, + distance, + null, + SpaceType.L2, + termQueryBuilder, + null + ); + + assertEquals(1, queryResult.get(0).size()); + assertEquals(expectedDocIds.get(0), queryResult.get(0).get(0).getDocId()); + + // Delete index + deleteKNNIndex(INDEX_NAME); + } + @SneakyThrows public void testQueryWithFilter_whenNonExistingFieldUsedInFilter_thenSuccessful() { XContentBuilder builder = XContentFactory.jsonBuilder() @@ -1780,6 +2005,10 @@ public void testQueryWithFilter_whenNonExistingFieldUsedInFilter_thenSuccessful( } protected void setupKNNIndexForFilterQuery() throws Exception { + setupKNNIndexForFilterQuery(getKNNDefaultIndexSettings()); + } + + protected void setupKNNIndexForFilterQuery(Settings settings) throws Exception { // Create Mappings XContentBuilder builder = XContentFactory.jsonBuilder() .startObject() @@ -1797,7 +2026,7 @@ protected void setupKNNIndexForFilterQuery() throws Exception { .endObject(); final String mapping = builder.toString(); - createKnnIndex(INDEX_NAME, mapping); + createKnnIndex(INDEX_NAME, settings, mapping); addKnnDocWithAttributes( DOC_ID_1, diff --git a/src/test/java/org/opensearch/knn/index/OpenSearchIT.java b/src/test/java/org/opensearch/knn/index/OpenSearchIT.java index 0bb7947ba..481b307fa 100644 --- a/src/test/java/org/opensearch/knn/index/OpenSearchIT.java +++ b/src/test/java/org/opensearch/knn/index/OpenSearchIT.java @@ -15,6 +15,7 @@ import com.google.common.primitives.Floats; import java.util.Locale; import lombok.SneakyThrows; +import org.apache.hc.core5.http.ParseException; import org.junit.BeforeClass; import org.junit.Ignore; import org.opensearch.knn.KNNRestTestCase; @@ -42,6 +43,8 @@ import java.util.TreeMap; import static org.hamcrest.Matchers.containsString; +import static org.opensearch.knn.index.KNNSettings.INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MAX; +import static org.opensearch.knn.index.KNNSettings.INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MIN; public class OpenSearchIT extends KNNRestTestCase { @@ -611,4 +614,297 @@ public void testCacheClear_whenCloseIndex() throws Exception { fail("Graphs are not getting evicted"); } + + public void testKNNIndex_whenBuildGraphThresholdIsPresent_thenGetThresholdValue() throws Exception { + final Integer buildVectorDataStructureThreshold = randomIntBetween( + INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MIN, + INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MAX + ); + final Settings settings = Settings.builder().put(buildKNNIndexSettings(buildVectorDataStructureThreshold)).build(); + final String knnIndexMapping = createKnnIndexMapping(FIELD_NAME, KNNEngine.getMaxDimensionByEngine(KNNEngine.DEFAULT)); + final String indexName = "test-index-with-build-graph-settings"; + createKnnIndex(indexName, settings, knnIndexMapping); + final String buildVectorDataStructureThresholdSetting = getIndexSettingByName( + indexName, + KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD + ); + assertNotNull("build_vector_data_structure_threshold index setting is not found", buildVectorDataStructureThresholdSetting); + assertEquals( + "incorrect setting for build_vector_data_structure_threshold", + buildVectorDataStructureThreshold, + Integer.valueOf(buildVectorDataStructureThresholdSetting) + ); + deleteKNNIndex(indexName); + } + + public void testKNNIndex_whenBuildThresholdIsNotProvided_thenShouldNotReturnSetting() throws Exception { + final String knnIndexMapping = createKnnIndexMapping(FIELD_NAME, KNNEngine.getMaxDimensionByEngine(KNNEngine.DEFAULT)); + final String indexName = "test-index-with-build-graph-settings"; + createKnnIndex(indexName, knnIndexMapping); + final String buildVectorDataStructureThresholdSetting = getIndexSettingByName( + indexName, + KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD + ); + assertNull( + "build_vector_data_structure_threshold index setting should not be added in index setting", + buildVectorDataStructureThresholdSetting + ); + deleteKNNIndex(indexName); + } + + public void testKNNIndex_whenGetIndexSettingWithDefaultIsCalled_thenReturnDefaultBuildGraphThresholdValue() throws Exception { + final String knnIndexMapping = createKnnIndexMapping(FIELD_NAME, KNNEngine.getMaxDimensionByEngine(KNNEngine.DEFAULT)); + final String indexName = "test-index-with-build-vector-graph-settings"; + createKnnIndex(indexName, knnIndexMapping); + final String buildVectorDataStructureThresholdSetting = getIndexSettingByName( + indexName, + KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, + true + ); + assertNotNull("build_vector_data_structure index setting is not found", buildVectorDataStructureThresholdSetting); + assertEquals( + "incorrect default setting for build_vector_data_structure_threshold", + KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE, + Integer.valueOf(buildVectorDataStructureThresholdSetting) + ); + deleteKNNIndex(indexName); + } + + /* + For this testcase, we will create index with setting build_vector_data_structure_threshold as -1, then index few documents, perform knn search, + then, confirm hits because of exact search though there are no graph. In next step, update setting to 0, force merge segment to 1, perform knn search and confirm expected + hits are returned. + */ + public void testKNNIndex_whenBuildVectorGraphThresholdIsProvidedEndToEnd_thenBuildGraphBasedOnSetting() throws Exception { + final String indexName = "test-index-1"; + final String fieldName1 = "test-field-1"; + final String fieldName2 = "test-field-2"; + + final Integer dimension = testData.indexData.vectors[0].length; + final Settings knnIndexSettings = buildKNNIndexSettings(-1); + + // Create an index + final XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName1) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .startObject(KNNConstants.PARAMETERS) + .endObject() + .endObject() + .endObject() + .startObject(fieldName2) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(KNNConstants.PARAMETERS) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + createKnnIndex(indexName, knnIndexSettings, builder.toString()); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + ImmutableList.of(fieldName1, fieldName2), + ImmutableList.of( + Floats.asList(testData.indexData.vectors[i]).toArray(), + Floats.asList(testData.indexData.vectors[i]).toArray() + ) + ); + } + + refreshAllIndices(); + // Assert we have the right number of documents in the index + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + + final List nmslibNeighbors = getResults(indexName, fieldName1, testData.queries[0], 1); + assertEquals("unexpected neighbors are returned", nmslibNeighbors.size(), nmslibNeighbors.size()); + + final List faissNeighbors = getResults(indexName, fieldName2, testData.queries[0], 1); + assertEquals("unexpected neighbors are returned", faissNeighbors.size(), faissNeighbors.size()); + + // update build vector data structure setting + updateIndexSettings(indexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); + forceMergeKnnIndex(indexName, 1); + + final int k = 10; + for (int i = 0; i < testData.queries.length; i++) { + // Search nmslib field + final Response response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName1, testData.queries[i], k), k); + final String responseBody = EntityUtils.toString(response.getEntity()); + final List nmslibValidNeighbors = parseSearchResponse(responseBody, fieldName1); + assertEquals(k, nmslibValidNeighbors.size()); + // Search faiss field + final List faissValidNeighbors = getResults(indexName, fieldName2, testData.queries[i], k); + assertEquals(k, faissValidNeighbors.size()); + } + + // Delete index + deleteKNNIndex(indexName); + } + + /* + For this testcase, we will create index with setting build_vector_data_structure_threshold number of documents to ingest, then index x documents, perform knn search, + then, confirm expected hits are returned. Here, we don't need force merge to build graph, since, threshold is less than + actual number of documents in segments + */ + public void testKNNIndex_whenBuildVectorDataStructureIsLessThanDocCount_thenBuildGraphBasedSuccessfully() throws Exception { + final String indexName = "test-index-1"; + final String fieldName = "test-field-1"; + + final Integer dimension = testData.indexData.vectors[0].length; + final Settings knnIndexSettings = buildKNNIndexSettings(testData.indexData.docs.length); + + // Create an index + final XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .startObject(KNNConstants.PARAMETERS) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + createKnnIndex(indexName, knnIndexSettings, builder.toString()); + // Disable refresh + updateIndexSettings(indexName, Settings.builder().put("index.refresh_interval", -1)); + + // Index the test data without refresh on every document + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + ImmutableList.of(fieldName), + ImmutableList.of(Floats.asList(testData.indexData.vectors[i]).toArray()), + false + ); + } + + refreshAllIndices(); + // Assert we have the right number of documents in the index + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + + final int k = 10; + for (int i = 0; i < testData.queries.length; i++) { + final Response response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName, testData.queries[i], k), k); + final String responseBody = EntityUtils.toString(response.getEntity()); + final List nmslibValidNeighbors = parseSearchResponse(responseBody, fieldName); + assertEquals(k, nmslibValidNeighbors.size()); + } + // Delete index + deleteKNNIndex(indexName); + } + + /* + For this testcase, we will create index with setting build_vector_data_structure_threshold as -1, then index few documents, perform knn search, + then, confirm hits because of exact search though there are no graph. In next step, update setting to 0, force merge segment to 1, perform knn search and confirm expected + hits are returned. + */ + public void testKNNIndex_whenBuildVectorGraphThresholdIsProvidedEndToEnd_thenBuildGraphBasedOnSettingUsingRadialSearch() + throws Exception { + final String indexName = "test-index-1"; + final String fieldName1 = "test-field-1"; + final String fieldName2 = "test-field-2"; + + final Integer dimension = testData.indexData.vectors[0].length; + final Settings knnIndexSettings = buildKNNIndexSettings(-1); + + // Create an index + final XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName1) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .startObject(KNNConstants.PARAMETERS) + .endObject() + .endObject() + .endObject() + .startObject(fieldName2) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(KNNConstants.PARAMETERS) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + createKnnIndex(indexName, knnIndexSettings, builder.toString()); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + ImmutableList.of(fieldName1, fieldName2), + ImmutableList.of( + Floats.asList(testData.indexData.vectors[i]).toArray(), + Floats.asList(testData.indexData.vectors[i]).toArray() + ) + ); + } + + refreshAllIndices(); + // Assert we have the right number of documents in the index + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + + final List nmslibNeighbors = getResults(indexName, fieldName1, testData.queries[0], 1); + assertEquals("unexpected neighbors are returned", nmslibNeighbors.size(), nmslibNeighbors.size()); + + final List faissNeighbors = getResults(indexName, fieldName2, testData.queries[0], 1); + assertEquals("unexpected neighbors are returned", faissNeighbors.size(), faissNeighbors.size()); + + // update build vector data structure setting + updateIndexSettings(indexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); + forceMergeKnnIndex(indexName, 1); + + final int k = 10; + for (int i = 0; i < testData.queries.length; i++) { + // Search nmslib field + final Response response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName1, testData.queries[i], k), k); + final String responseBody = EntityUtils.toString(response.getEntity()); + final List nmslibValidNeighbors = parseSearchResponse(responseBody, fieldName1); + assertEquals(k, nmslibValidNeighbors.size()); + // Search faiss field + final List faissValidNeighbors = getResults(indexName, fieldName2, testData.queries[i], k); + assertEquals(k, faissValidNeighbors.size()); + } + + // Delete index + deleteKNNIndex(indexName); + } + + private List getResults(final String indexName, final String fieldName, final float[] vector, final int k) + throws IOException, ParseException { + final Response searchResponseField = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName, vector, k), k); + final String searchResponseBody = EntityUtils.toString(searchResponseField.getEntity()); + return parseSearchResponse(searchResponseBody, fieldName); + } + } diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriterTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriterTests.java index 6e6a51b88..4f68a360e 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriterTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriterTests.java @@ -126,6 +126,5 @@ public void testRamByteUsed_whenValidInput_thenSuccess() { // testing for value > 0 as we don't have a concrete way to find out expected bytes. This can OS dependent too. Assert.assertTrue(byteWriter.ramBytesUsed() > 0); Mockito.verify(mockedFlatFieldVectorsWriter, Mockito.times(2)).ramBytesUsed(); - } } diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java index 5bb6d1926..03d0f6160 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java @@ -31,10 +31,12 @@ import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; import org.opensearch.test.OpenSearchTestCase; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Predicate; @@ -52,6 +54,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @RequiredArgsConstructor @@ -76,12 +79,14 @@ public class NativeEngines990KnnVectorsWriterFlushTests extends OpenSearchTestCa private final String description; private final List> vectorsPerField; + private static final Integer BUILD_GRAPH_ALWAYS_THRESHOLD = 0; + private static final Integer BUILD_GRAPH_NEVER_THRESHOLD = -1; @Override public void setUp() throws Exception { super.setUp(); MockitoAnnotations.openMocks(this); - objectUnderTest = new NativeEngines990KnnVectorsWriter(segmentWriteState, flatVectorsWriter); + objectUnderTest = new NativeEngines990KnnVectorsWriter(segmentWriteState, flatVectorsWriter, BUILD_GRAPH_ALWAYS_THRESHOLD); mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(Mockito.anyInt(), Mockito.any()); Mockito.when(flatVectorsWriter.addField(Mockito.any())).thenReturn(mockedFlatFieldVectorsWriter); @@ -299,6 +304,539 @@ public void testFlush_WithQuantization() { } } + public void testFlush_whenThresholdIsNegative_thenNativeIndexWriterIsNeverCalled() throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + expectedVectorValues.add(knnVectorValues); + + }); + + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + BUILD_GRAPH_NEVER_THRESHOLD + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + }); + + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + verifyNoInteractions(nativeIndexWriter); + } + } + + public void testFlush_whenThresholdIsGreaterThanVectorSize_thenNativeIndexWriterIsNeverCalled() throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + final Map sizeMap = new HashMap<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + sizeMap.put(i, randomVectorValues.size()); + expectedVectorValues.add(knnVectorValues); + + }); + final int maxThreshold = sizeMap.values().stream().filter(count -> count != 0).max(Integer::compareTo).orElse(0); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + maxThreshold + 1 + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + }); + + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + verifyNoInteractions(nativeIndexWriter); + } + } + + public void testFlush_whenThresholdIsEqualToMinNumberOfVectors_thenNativeIndexWriterIsCalled() throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + final Map sizeMap = new HashMap<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + sizeMap.put(i, randomVectorValues.size()); + expectedVectorValues.add(knnVectorValues); + + }); + + final int minThreshold = sizeMap.values().stream().filter(count -> count != 0).min(Integer::compareTo).orElse(0); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + minThreshold + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + }); + + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + assertTrue((long) KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.getValue() > 0); + } + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + if (vectorsPerField.get(i).size() > 0) { + verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } else { + verify(nativeIndexWriter, never()).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + } + + public void testFlush_whenThresholdIsEqualToFixedValue_thenRelevantNativeIndexWriterIsCalled() throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + expectedVectorValues.add(knnVectorValues); + + }); + final int threshold = 4; + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + threshold + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + }); + + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + if (vectorsPerField.get(i).size() >= threshold) { + verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } else { + verify(nativeIndexWriter, never()).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + } + + public void testFlush_whenQuantizationIsProvided_whenBuildGraphDatStructureThresholdIsNotMet_thenSkipBuildingGraph() + throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + final Map sizeMap = new HashMap<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + sizeMap.put(i, randomVectorValues.size()); + expectedVectorValues.add(knnVectorValues); + + }); + final int maxThreshold = sizeMap.values().stream().filter(count -> count != 0).max(Integer::compareTo).orElse(0); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + maxThreshold + 1 // to avoid building graph using max doc threshold, the same can be achieved by -1 too + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(quantizationParams); + try { + when(quantizationService.train(quantizationParams, expectedVectorValues.get(i), vectorsPerField.get(i).size())) + .thenReturn(quantizationState); + } catch (Exception e) { + throw new RuntimeException(e); + } + + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState)) + .thenReturn(nativeIndexWriter); + }); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeHeader(segmentWriteState); + } else { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + verifyNoInteractions(nativeIndexWriter); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + if (vectorsPerField.get(i).isEmpty()) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0), never()).writeState(i, quantizationState); + } else { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeState(i, quantizationState); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + final Long expectedTimesGetVectorValuesIsCalled = vectorsPerField.stream().filter(Predicate.not(Map::isEmpty)).count(); + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(any(VectorDataType.class), any(DocsWithFieldSet.class), any()), + times(Math.toIntExact(expectedTimesGetVectorValuesIsCalled)) + ); + } + } + + public void testFlush_whenQuantizationIsProvided_whenBuildGraphDatStructureThresholdIsNegative_thenSkipBuildingGraph() + throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + final Map sizeMap = new HashMap<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + sizeMap.put(i, randomVectorValues.size()); + expectedVectorValues.add(knnVectorValues); + + }); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + BUILD_GRAPH_NEVER_THRESHOLD + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(quantizationParams); + try { + when(quantizationService.train(quantizationParams, expectedVectorValues.get(i), vectorsPerField.get(i).size())) + .thenReturn(quantizationState); + } catch (Exception e) { + throw new RuntimeException(e); + } + + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState)) + .thenReturn(nativeIndexWriter); + }); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeHeader(segmentWriteState); + } else { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + verifyNoInteractions(nativeIndexWriter); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + if (vectorsPerField.get(i).isEmpty()) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0), never()).writeState(i, quantizationState); + } else { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeState(i, quantizationState); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + final Long expectedTimesGetVectorValuesIsCalled = vectorsPerField.stream().filter(Predicate.not(Map::isEmpty)).count(); + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(any(VectorDataType.class), any(DocsWithFieldSet.class), any()), + times(Math.toIntExact(expectedTimesGetVectorValuesIsCalled)) + ); + } + } + private FieldInfo fieldInfo(int fieldNumber, VectorEncoding vectorEncoding, Map attributes) { FieldInfo fieldInfo = mock(FieldInfo.class); when(fieldInfo.getFieldNumber()).thenReturn(fieldNumber); diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java index af18cd281..77f3fd8ed 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java @@ -34,6 +34,7 @@ import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; import org.opensearch.test.OpenSearchTestCase; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -77,12 +78,14 @@ public class NativeEngines990KnnVectorsWriterMergeTests extends OpenSearchTestCa private final String description; private final Map mergedVectors; private FlatFieldVectorsWriter mockedFlatFieldVectorsWriter; + private static final Integer BUILD_GRAPH_ALWAYS_THRESHOLD = 0; + private static final Integer BUILD_GRAPH_NEVER_THRESHOLD = -1; @Override public void setUp() throws Exception { super.setUp(); MockitoAnnotations.openMocks(this); - objectUnderTest = new NativeEngines990KnnVectorsWriter(segmentWriteState, flatVectorsWriter); + objectUnderTest = new NativeEngines990KnnVectorsWriter(segmentWriteState, flatVectorsWriter, BUILD_GRAPH_ALWAYS_THRESHOLD); mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(Mockito.anyInt(), Mockito.any()); Mockito.when(flatVectorsWriter.addField(Mockito.any())).thenReturn(mockedFlatFieldVectorsWriter); @@ -162,6 +165,126 @@ public void testMerge() { } } + public void testMerge_whenThresholdIsNegative_thenNativeIndexWriterIsNeverCalled() throws IOException { + // Given + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(mergedVectors.values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + BUILD_GRAPH_NEVER_THRESHOLD + ); + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedStatic mergedVectorValuesMockedStatic = mockStatic( + KnnVectorsWriter.MergedVectorValues.class + ); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + final FieldInfo fieldInfo = fieldInfo( + 0, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, mergedVectors); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + mergedVectorValuesMockedStatic.when(() -> KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState)) + .thenReturn(floatVectorValues); + knnVectorValuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues)) + .thenReturn(knnVectorValues); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).mergeIndex(any(), anyInt()); + + // When + nativeEngineWriter.mergeOneField(fieldInfo, mergeState); + + // Then + verify(flatVectorsWriter).mergeOneField(fieldInfo, mergeState); + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + verifyNoInteractions(nativeIndexWriter); + } + } + + public void testMerge_whenThresholdIsEqualToNumberOfVectors_thenNativeIndexWriterIsCalled() throws IOException { + // Given + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(mergedVectors.values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + mergedVectors.size() + ); + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedStatic mergedVectorValuesMockedStatic = mockStatic( + KnnVectorsWriter.MergedVectorValues.class + ); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + final FieldInfo fieldInfo = fieldInfo( + 0, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, mergedVectors); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + mergedVectorValuesMockedStatic.when(() -> KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState)) + .thenReturn(floatVectorValues); + knnVectorValuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues)) + .thenReturn(knnVectorValues); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).mergeIndex(any(), anyInt()); + + // When + nativeEngineWriter.mergeOneField(fieldInfo, mergeState); + + // Then + verify(flatVectorsWriter).mergeOneField(fieldInfo, mergeState); + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + if (!mergedVectors.isEmpty()) { + verify(nativeIndexWriter).mergeIndex(knnVectorValues, mergedVectors.size()); + } else { + verifyNoInteractions(nativeIndexWriter); + } + } + } + @SneakyThrows public void testMerge_WithQuantization() { // Given diff --git a/src/test/java/org/opensearch/knn/index/query/ExactSearcherTests.java b/src/test/java/org/opensearch/knn/index/query/ExactSearcherTests.java new file mode 100644 index 000000000..8492ca1f0 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/ExactSearcherTests.java @@ -0,0 +1,139 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import lombok.SneakyThrows; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FieldInfos; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.SegmentCommitInfo; +import org.apache.lucene.index.SegmentInfo; +import org.apache.lucene.index.SegmentReader; +import org.apache.lucene.search.Sort; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.util.StringHelper; +import org.apache.lucene.util.Version; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.codec.KNNCodecVersion; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.KNNRestTestCase.FIELD_NAME; +import static org.opensearch.knn.KNNRestTestCase.INDEX_NAME; +import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; + +public class ExactSearcherTests extends KNNTestCase { + + private static final String SEGMENT_NAME = "0"; + + @SneakyThrows + public void testRadialSearch_whenNoEngineFiles_thenSuccess() { + try (MockedStatic valuesFactoryMockedStatic = Mockito.mockStatic(KNNVectorValuesFactory.class)) { + final float[] queryVector = new float[] { 0.1f, 2.0f, 3.0f }; + final SpaceType spaceType = randomFrom(SpaceType.L2, SpaceType.INNER_PRODUCT); + final List dataVectors = Arrays.asList( + new float[] { 11.0f, 12.0f, 13.0f }, + new float[] { 14.0f, 15.0f, 16.0f }, + new float[] { 17.0f, 18.0f, 19.0f } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + final Float score = Collections.min(expectedScores); + final float radius = KNNEngine.FAISS.scoreToRadialThreshold(score, spaceType); + final int maxResults = 1000; + final KNNQuery.Context context = mock(KNNQuery.Context.class); + when(context.getMaxResultWindow()).thenReturn(maxResults); + KNNWeight.initialize(null); + + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(queryVector) + .radius(radius) + .indexName(INDEX_NAME) + .context(context) + .build(); + + final ExactSearcher.ExactSearcherContext.ExactSearcherContextBuilder exactSearcherContextBuilder = + ExactSearcher.ExactSearcherContext.builder() + // setting to true, so that if quantization details are present we want to do search on the quantized + // vectors as this flow is used in first pass of search. + .useQuantizedVectorsForSearch(false) + .knnQuery(query); + + ExactSearcher exactSearcher = new ExactSearcher(null); + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + final SegmentInfo segmentInfo = new SegmentInfo( + directory, + Version.LATEST, + Version.LATEST, + SEGMENT_NAME, + 100, + false, + false, + KNNCodecVersion.current().getDefaultCodecDelegate(), + Map.of(), + new byte[StringHelper.ID_LENGTH], + Map.of(), + Sort.RELEVANCE + ); + segmentInfo.setFiles(Set.of()); + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + final Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn( + Map.of( + SPACE_TYPE, + spaceType.getValue(), + KNN_ENGINE, + KNNEngine.FAISS.getName(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ) + ); + when(fieldInfo.getAttribute(SPACE_TYPE)).thenReturn(spaceType.getValue()); + KNNFloatVectorValues floatVectorValues = mock(KNNFloatVectorValues.class); + valuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(fieldInfo, reader)).thenReturn(floatVectorValues); + when(floatVectorValues.nextDoc()).thenReturn(0, 1, 2, NO_MORE_DOCS); + when(floatVectorValues.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + final Map integerFloatMap = exactSearcher.searchLeaf(leafReaderContext, exactSearcherContextBuilder.build()); + assertEquals(integerFloatMap.size(), dataVectors.size()); + assertEquals(expectedScores, new ArrayList<>(integerFloatMap.values())); + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java index 2a2c3ed4d..482e7e0cb 100644 --- a/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java +++ b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java @@ -90,6 +90,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.opensearch.knn.KNNRestTestCase.INDEX_NAME; import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; @@ -328,8 +329,9 @@ public void testQueryScoreForFaissWithNonExistingModel() throws IOException { } @SneakyThrows - public void testShardWithoutFiles() { + public void testScorer_whenNoVectorFieldsInDocument_thenEmptyScorerIsReturned() { final KNNQuery query = new KNNQuery(FIELD_NAME, QUERY_VECTOR, K, INDEX_NAME, (BitSetProducer) null); + KNNWeight.initialize(null); final KNNWeight knnWeight = new KNNWeight(query, 0.0f); final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); @@ -360,10 +362,9 @@ public void testShardWithoutFiles() { final Path path = mock(Path.class); when(directory.getDirectory()).thenReturn(path); final FieldInfos fieldInfos = mock(FieldInfos.class); - final FieldInfo fieldInfo = mock(FieldInfo.class); when(reader.getFieldInfos()).thenReturn(fieldInfos); - when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); - + // When no knn fields are available , field info for vector field will be null + when(fieldInfos.fieldInfo(FIELD_NAME)).thenReturn(null); final Scorer knnScorer = knnWeight.scorer(leafReaderContext); assertEquals(KNNScorer.emptyScorer(knnWeight), knnScorer); } @@ -868,6 +869,83 @@ public void validateANNWithFilterQuery_whenExactSearch_thenSuccess(final boolean } } + @SneakyThrows + public void testRadialSearch_whenNoEngineFiles_thenPerformExactSearch() { + ExactSearcher mockedExactSearcher = mock(ExactSearcher.class); + final float[] queryVector = new float[] { 0.1f, 2.0f, 3.0f }; + final SpaceType spaceType = randomFrom(SpaceType.L2, SpaceType.INNER_PRODUCT); + KNNWeight.initialize(null, mockedExactSearcher); + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(queryVector) + .indexName(INDEX_NAME) + .methodParameters(HNSW_METHOD_PARAMETERS) + .build(); + final KNNWeight knnWeight = new KNNWeight(query, 1.0f); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + final SegmentInfo segmentInfo = new SegmentInfo( + directory, + Version.LATEST, + Version.LATEST, + SEGMENT_NAME, + 100, + false, + false, + KNNCodecVersion.current().getDefaultCodecDelegate(), + Map.of(), + new byte[StringHelper.ID_LENGTH], + Map.of(), + Sort.RELEVANCE + ); + segmentInfo.setFiles(Set.of()); + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + final Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(FIELD_NAME)).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn( + Map.of( + SPACE_TYPE, + spaceType.getValue(), + KNN_ENGINE, + KNNEngine.FAISS.getName(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ) + ); + final ExactSearcher.ExactSearcherContext exactSearchContext = ExactSearcher.ExactSearcherContext.builder() + .isParentHits(true) + // setting to true, so that if quantization details are present we want to do search on the quantized + // vectors as this flow is used in first pass of search. + .useQuantizedVectorsForSearch(true) + .knnQuery(query) + .build(); + when(mockedExactSearcher.searchLeaf(leafReaderContext, exactSearchContext)).thenReturn(DOC_ID_TO_SCORES); + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + assertNotNull(knnScorer); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + final List actualDocIds = new ArrayList<>(); + for (int docId = docIdSetIterator.nextDoc(); docId != NO_MORE_DOCS; docId = docIdSetIterator.nextDoc()) { + actualDocIds.add(docId); + assertEquals(DOC_ID_TO_SCORES.get(docId), knnScorer.score(), 0.00000001f); + } + assertEquals(docIdSetIterator.cost(), actualDocIds.size()); + assertTrue(Comparators.isInOrder(actualDocIds, Comparator.naturalOrder())); + // verify JNI Service is not called + jniServiceMockedStatic.verifyNoInteractions(); + verify(mockedExactSearcher).searchLeaf(leafReaderContext, exactSearchContext); + } + @SneakyThrows public void testANNWithFilterQuery_whenExactSearchAndThresholdComputations_thenSuccess() { ModelDao modelDao = mock(ModelDao.class); diff --git a/src/test/java/org/opensearch/knn/integ/BinaryIndexIT.java b/src/test/java/org/opensearch/knn/integ/BinaryIndexIT.java index d5b79768d..4fb267eb5 100644 --- a/src/test/java/org/opensearch/knn/integ/BinaryIndexIT.java +++ b/src/test/java/org/opensearch/knn/integ/BinaryIndexIT.java @@ -5,6 +5,7 @@ package org.opensearch.knn.integ; +import com.google.common.collect.ImmutableList; import com.google.common.primitives.Floats; import lombok.SneakyThrows; import lombok.extern.log4j.Log4j2; @@ -13,11 +14,13 @@ import org.junit.After; import org.junit.BeforeClass; import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; import org.opensearch.knn.KNNJsonIndexMappingsBuilder; import org.opensearch.knn.KNNJsonQueryBuilder; import org.opensearch.knn.KNNRestTestCase; import org.opensearch.knn.KNNResult; import org.opensearch.knn.TestUtils; +import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.KNNEngine; @@ -36,6 +39,8 @@ @Log4j2 public class BinaryIndexIT extends KNNRestTestCase { private static TestUtils.TestData testData; + private static final int NEVER_BUILD_GRAPH = -1; + private static final int ALWAYS_BUILD_GRAPH = 0; @BeforeClass public static void setUpClass() throws IOException { @@ -104,6 +109,52 @@ public void testFaissHnswBinary_when1000Data_thenRecallIsAboveNinePointZero() { } } + @SneakyThrows + public void testFaissHnswBinary_whenBuildVectorGraphThresholdIsNegativeEndToEnd_thenBuildGraphBasedOnSetting() { + // Create Index + createKnnHnswBinaryIndex(KNNEngine.FAISS, INDEX_NAME, FIELD_NAME, 128, NEVER_BUILD_GRAPH); + ingestTestData(INDEX_NAME, FIELD_NAME); + + assertEquals(1, runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[0], 1).size()); + + // update build vector data structure setting + updateIndexSettings(INDEX_NAME, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, ALWAYS_BUILD_GRAPH)); + forceMergeKnnIndex(INDEX_NAME, 1); + + int k = 100; + for (int i = 0; i < testData.queries.length; i++) { + List knnResults = runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[i], k); + float recall = getRecall( + Set.of(Arrays.copyOf(testData.groundTruthValues[i], k)), + knnResults.stream().map(KNNResult::getDocId).collect(Collectors.toSet()) + ); + assertTrue("Recall: " + recall, recall > 0.1); + } + } + + @SneakyThrows + public void testFaissHnswBinary_whenBuildVectorGraphThresholdIsProvidedEndToEnd_thenBuildGraphBasedOnSetting() { + // Create Index + createKnnHnswBinaryIndex(KNNEngine.FAISS, INDEX_NAME, FIELD_NAME, 128, testData.indexData.docs.length); + ingestTestData(INDEX_NAME, FIELD_NAME, false); + + assertEquals(1, runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[0], 1).size()); + + // update build vector data structure setting + updateIndexSettings(INDEX_NAME, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, ALWAYS_BUILD_GRAPH)); + forceMergeKnnIndex(INDEX_NAME, 1); + + int k = 100; + for (int i = 0; i < testData.queries.length; i++) { + List knnResults = runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[i], k); + float recall = getRecall( + Set.of(Arrays.copyOf(testData.groundTruthValues[i], k)), + knnResults.stream().map(KNNResult::getDocId).collect(Collectors.toSet()) + ); + assertTrue("Recall: " + recall, recall > 0.1); + } + } + @SneakyThrows public void testFaissHnswBinary_whenRadialSearch_thenThrowException() { // Create Index @@ -157,13 +208,18 @@ private List runKnnQuery(final String indexName, final String fieldNa } private void ingestTestData(final String indexName, final String fieldName) throws Exception { + ingestTestData(indexName, fieldName, true); + } + + private void ingestTestData(final String indexName, final String fieldName, boolean refresh) throws Exception { // Index the test data for (int i = 0; i < testData.indexData.docs.length; i++) { addKnnDoc( indexName, Integer.toString(testData.indexData.docs[i]), - fieldName, - Floats.asList(testData.indexData.vectors[i]).toArray() + ImmutableList.of(fieldName), + ImmutableList.of(Floats.asList(testData.indexData.vectors[i]).toArray()), + refresh ); } @@ -172,8 +228,13 @@ private void ingestTestData(final String indexName, final String fieldName) thro assertEquals(testData.indexData.docs.length, getDocCount(indexName)); } - private void createKnnHnswBinaryIndex(final KNNEngine knnEngine, final String indexName, final String fieldName, final int dimension) - throws IOException { + private void createKnnHnswBinaryIndex( + final KNNEngine knnEngine, + final String indexName, + final String fieldName, + final int dimension, + final int threshold + ) throws IOException { KNNJsonIndexMappingsBuilder.Method method = KNNJsonIndexMappingsBuilder.Method.builder() .methodName(METHOD_HNSW) .engine(knnEngine.getName()) @@ -186,7 +247,11 @@ private void createKnnHnswBinaryIndex(final KNNEngine knnEngine, final String in .method(method) .build() .getIndexMapping(); + createKnnIndex(indexName, buildKNNIndexSettings(threshold), knnIndexMapping); + } - createKnnIndex(indexName, knnIndexMapping); + private void createKnnHnswBinaryIndex(final KNNEngine knnEngine, final String indexName, final String fieldName, final int dimension) + throws IOException { + createKnnHnswBinaryIndex(knnEngine, indexName, fieldName, dimension, ALWAYS_BUILD_GRAPH); } } diff --git a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java index 8c033ca03..2c2efed3d 100644 --- a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java +++ b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java @@ -100,6 +100,8 @@ import static org.opensearch.knn.TestUtils.computeGroundTruthValues; import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD; +import static org.opensearch.knn.index.KNNSettings.KNN_INDEX; import static org.opensearch.knn.index.SpaceType.L2; import static org.opensearch.knn.index.memory.NativeMemoryCacheManager.GRAPH_COUNT; import static org.opensearch.knn.index.engine.KNNEngine.FAISS; @@ -638,7 +640,12 @@ protected void addKnnDocWithNestedField(String index, String docId, String neste * Add a single KNN Doc to an index with multiple fields */ protected void addKnnDoc(String index, String docId, List fieldNames, List vectors) throws IOException { - Request request = new Request("POST", "/" + index + "/_doc/" + docId + "?refresh=true"); + addKnnDoc(index, docId, fieldNames, vectors, true); + } + + protected void addKnnDoc(String index, String docId, List fieldNames, List vectors, boolean refresh) + throws IOException { + Request request = new Request("POST", "/" + index + "/_doc/" + docId + "?refresh=" + refresh); XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); for (int i = 0; i < fieldNames.size(); i++) { @@ -768,6 +775,15 @@ protected Settings getKNNSegmentReplicatedIndexSettings() { .build(); } + protected Settings buildKNNIndexSettings(int approximateThreshold) { + return Settings.builder() + .put("number_of_shards", 1) + .put("number_of_replicas", 0) + .put(KNN_INDEX, true) + .put(INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, approximateThreshold) + .build(); + } + @SneakyThrows protected int getDataNodeCount() { Request request = new Request("GET", "_nodes/stats?filter_path=nodes.*.roles"); From d9c7ba5e1b25856cd52e33aa54b37f1c3eb6a882 Mon Sep 17 00:00:00 2001 From: Naveen Tatikonda Date: Fri, 11 Oct 2024 12:11:32 -0500 Subject: [PATCH 39/59] Bump Faiss commit from 33c0ba5 to 4eecd91 (#2194) * Bump Faiss commit from 33c0ba5 to 4eecd91 Signed-off-by: Naveen Tatikonda * Update Faiss patches after commit bump Signed-off-by: Naveen Tatikonda --------- Signed-off-by: Naveen Tatikonda --- jni/external/faiss | 2 +- ...Custom-patch-to-support-multi-vector.patch | 46 +++++++++---------- ...ble-precomp-table-to-be-shared-ivfpq.patch | 22 ++++----- 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/jni/external/faiss b/jni/external/faiss index 33c0ba5d0..4eecd9165 160000 --- a/jni/external/faiss +++ b/jni/external/faiss @@ -1 +1 @@ -Subproject commit 33c0ba5d002a7cd9761513f06ecc9822079d4a2f +Subproject commit 4eecd9165ae56bc8ff4afbf14cc8b359fdfe4004 diff --git a/jni/patches/faiss/0001-Custom-patch-to-support-multi-vector.patch b/jni/patches/faiss/0001-Custom-patch-to-support-multi-vector.patch index 7afdabc1f..26b143346 100644 --- a/jni/patches/faiss/0001-Custom-patch-to-support-multi-vector.patch +++ b/jni/patches/faiss/0001-Custom-patch-to-support-multi-vector.patch @@ -1,4 +1,4 @@ -From 9e5affabe2caacf38f5585a0b906620dd35deef5 Mon Sep 17 00:00:00 2001 +From e775a8e65da96232822d5aed77f538592fccffda Mon Sep 17 00:00:00 2001 From: Heemin Kim Date: Tue, 30 Jan 2024 14:43:56 -0800 Subject: [PATCH] Add IDGrouper for HNSW @@ -6,7 +6,7 @@ Subject: [PATCH] Add IDGrouper for HNSW Signed-off-by: Heemin Kim --- faiss/CMakeLists.txt | 3 + - faiss/Index.h | 8 +- + faiss/Index.h | 6 +- faiss/IndexHNSW.cpp | 13 +- faiss/IndexIDMap.cpp | 29 +++++ faiss/IndexIDMap.h | 22 ++++ @@ -18,7 +18,7 @@ Signed-off-by: Heemin Kim tests/CMakeLists.txt | 2 + tests/test_group_heap.cpp | 98 +++++++++++++++ tests/test_id_grouper.cpp | 241 +++++++++++++++++++++++++++++++++++++ - 13 files changed, 890 insertions(+), 5 deletions(-) + 13 files changed, 889 insertions(+), 4 deletions(-) create mode 100644 faiss/impl/IDGrouper.cpp create mode 100644 faiss/impl/IDGrouper.h create mode 100644 faiss/utils/GroupHeap.h @@ -26,10 +26,10 @@ Signed-off-by: Heemin Kim create mode 100644 tests/test_id_grouper.cpp diff --git a/faiss/CMakeLists.txt b/faiss/CMakeLists.txt -index 1b0860f3..f3d72df3 100644 +index 2871d974..d0bcec6a 100644 --- a/faiss/CMakeLists.txt +++ b/faiss/CMakeLists.txt -@@ -54,6 +54,7 @@ set(FAISS_SRC +@@ -55,6 +55,7 @@ set(FAISS_SRC impl/AuxIndexStructures.cpp impl/CodePacker.cpp impl/IDSelector.cpp @@ -37,7 +37,7 @@ index 1b0860f3..f3d72df3 100644 impl/FaissException.cpp impl/HNSW.cpp impl/NSG.cpp -@@ -149,6 +150,7 @@ set(FAISS_HEADERS +@@ -151,6 +152,7 @@ set(FAISS_HEADERS impl/AuxIndexStructures.h impl/CodePacker.h impl/IDSelector.h @@ -45,7 +45,7 @@ index 1b0860f3..f3d72df3 100644 impl/DistanceComputer.h impl/FaissAssert.h impl/FaissException.h -@@ -184,6 +186,7 @@ set(FAISS_HEADERS +@@ -186,6 +188,7 @@ set(FAISS_HEADERS invlists/InvertedListsIOHook.h utils/AlignedTable.h utils/bf16.h @@ -54,23 +54,21 @@ index 1b0860f3..f3d72df3 100644 utils/WorkerThread.h utils/distances.h diff --git a/faiss/Index.h b/faiss/Index.h -index 3d1bdb99..a8622858 100644 +index f57140ec..8f511e5d 100644 --- a/faiss/Index.h +++ b/faiss/Index.h -@@ -38,9 +38,10 @@ - +@@ -51,8 +51,9 @@ namespace faiss { --/// Forward declarations see impl/AuxIndexStructures.h, impl/IDSelector.h and --/// impl/DistanceComputer.h -+/// Forward declarations see impl/AuxIndexStructures.h, impl/IDSelector.h + /// Forward declarations see impl/AuxIndexStructures.h, impl/IDSelector.h +-/// and impl/DistanceComputer.h +/// ,impl/IDGrouper.h and impl/DistanceComputer.h struct IDSelector; +struct IDGrouper; struct RangeSearchResult; struct DistanceComputer; -@@ -52,6 +53,9 @@ struct DistanceComputer; +@@ -64,6 +65,9 @@ struct DistanceComputer; struct SearchParameters { /// if non-null, only these IDs will be considered during search. IDSelector* sel = nullptr; @@ -81,10 +79,10 @@ index 3d1bdb99..a8622858 100644 virtual ~SearchParameters() {} }; diff --git a/faiss/IndexHNSW.cpp b/faiss/IndexHNSW.cpp -index 8e5c654f..d473b6ad 100644 +index 6a1186ca..9c8a8255 100644 --- a/faiss/IndexHNSW.cpp +++ b/faiss/IndexHNSW.cpp -@@ -320,10 +320,17 @@ void IndexHNSW::search( +@@ -301,10 +301,17 @@ void IndexHNSW::search( const SearchParameters* params_in) const { FAISS_THROW_IF_NOT(k > 0); @@ -198,10 +196,10 @@ index 2d164123..a68887bd 100644 + } // namespace faiss diff --git a/faiss/impl/HNSW.cpp b/faiss/impl/HNSW.cpp -index 3ba5f72f..c574ce39 100644 +index c3693fd9..7ae28062 100644 --- a/faiss/impl/HNSW.cpp +++ b/faiss/impl/HNSW.cpp -@@ -831,6 +831,12 @@ int extract_k_from_ResultHandler(ResultHandler& res) { +@@ -906,6 +906,12 @@ int extract_k_from_ResultHandler(ResultHandler& res) { if (auto hres = dynamic_cast(&res)) { return hres->k; } @@ -329,19 +327,19 @@ index 00000000..d56113d9 + +} // namespace faiss diff --git a/faiss/impl/ResultHandler.h b/faiss/impl/ResultHandler.h -index 713fe8e4..d307fd70 100644 +index 3116eb24..126ed015 100644 --- a/faiss/impl/ResultHandler.h +++ b/faiss/impl/ResultHandler.h -@@ -13,6 +13,8 @@ - +@@ -14,6 +14,8 @@ #include #include + #include +#include +#include #include #include - #include -@@ -267,6 +269,193 @@ struct HeapBlockResultHandler : BlockResultHandler { + +@@ -286,6 +288,193 @@ struct HeapBlockResultHandler : BlockResultHandler { } }; @@ -725,7 +723,7 @@ index 00000000..3b7078da +} // namespace faiss \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt -index 3980d7dd..c888a5a6 100644 +index c41edf0c..87ab2020 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,6 +27,8 @@ set(FAISS_TEST_SRC diff --git a/jni/patches/faiss/0002-Enable-precomp-table-to-be-shared-ivfpq.patch b/jni/patches/faiss/0002-Enable-precomp-table-to-be-shared-ivfpq.patch index 619832f62..88e6a8106 100644 --- a/jni/patches/faiss/0002-Enable-precomp-table-to-be-shared-ivfpq.patch +++ b/jni/patches/faiss/0002-Enable-precomp-table-to-be-shared-ivfpq.patch @@ -1,4 +1,4 @@ -From a33e6ef35385009f24200586294f96235cb95d61 Mon Sep 17 00:00:00 2001 +From 9b33874562c9e62abf4a863657c54f0d349b0f67 Mon Sep 17 00:00:00 2001 From: John Mazanec Date: Wed, 21 Feb 2024 15:34:15 -0800 Subject: [PATCH] Enable precomp table to be shared ivfpq @@ -22,7 +22,7 @@ Signed-off-by: John Mazanec create mode 100644 tests/test_ivfpq_share_table.cpp diff --git a/faiss/IndexIVFPQ.cpp b/faiss/IndexIVFPQ.cpp -index 0b7f4d05..07bc7e83 100644 +index 100f499c..09508890 100644 --- a/faiss/IndexIVFPQ.cpp +++ b/faiss/IndexIVFPQ.cpp @@ -59,6 +59,29 @@ IndexIVFPQ::IndexIVFPQ( @@ -55,7 +55,7 @@ index 0b7f4d05..07bc7e83 100644 } /**************************************************************** -@@ -466,11 +489,23 @@ void IndexIVFPQ::precompute_table() { +@@ -464,11 +487,23 @@ void IndexIVFPQ::precompute_table() { use_precomputed_table, quantizer, pq, @@ -80,7 +80,7 @@ index 0b7f4d05..07bc7e83 100644 namespace { #define TIC t0 = get_cycles() -@@ -650,7 +685,7 @@ struct QueryTables { +@@ -648,7 +683,7 @@ struct QueryTables { fvec_madd( pq.M * pq.ksub, @@ -89,7 +89,7 @@ index 0b7f4d05..07bc7e83 100644 -2.0, sim_table_2, sim_table); -@@ -679,7 +714,7 @@ struct QueryTables { +@@ -677,7 +712,7 @@ struct QueryTables { k >>= cpq.nbits; // get corresponding table @@ -98,7 +98,7 @@ index 0b7f4d05..07bc7e83 100644 (ki * pq.M + cm * Mf) * pq.ksub; if (polysemous_ht == 0) { -@@ -709,7 +744,7 @@ struct QueryTables { +@@ -707,7 +742,7 @@ struct QueryTables { dis0 = coarse_dis; const float* s = @@ -107,7 +107,7 @@ index 0b7f4d05..07bc7e83 100644 for (int m = 0; m < pq.M; m++) { sim_table_ptrs[m] = s; s += pq.ksub; -@@ -729,7 +764,7 @@ struct QueryTables { +@@ -727,7 +762,7 @@ struct QueryTables { int ki = k & ((uint64_t(1) << cpq.nbits) - 1); k >>= cpq.nbits; @@ -116,7 +116,7 @@ index 0b7f4d05..07bc7e83 100644 (ki * pq.M + cm * Mf) * pq.ksub; for (int m = m0; m < m0 + Mf; m++) { -@@ -1346,6 +1381,8 @@ IndexIVFPQ::IndexIVFPQ() { +@@ -1344,6 +1379,8 @@ IndexIVFPQ::IndexIVFPQ() { do_polysemous_training = false; polysemous_ht = 0; polysemous_training = nullptr; @@ -302,13 +302,13 @@ index 00dd2f11..91f35a6e 100644 /// same as the regular IVFPQ encoder. The codes are not reorganized by /// blocks a that point diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt -index c888a5a6..83ecedfd 100644 +index 87ab2020..a859516c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt -@@ -37,6 +37,7 @@ set(FAISS_TEST_SRC - test_disable_pq_sdc_tables.cpp +@@ -38,6 +38,7 @@ set(FAISS_TEST_SRC test_common_ivf_empty_index.cpp test_callback.cpp + test_utils.cpp + test_ivfpq_share_table.cpp ) From 19162c24525a0b761e0e38ea8572b88cf812795a Mon Sep 17 00:00:00 2001 From: Naveen Tatikonda Date: Fri, 11 Oct 2024 13:47:22 -0500 Subject: [PATCH 40/59] Bump byte-buddy to 1.15.4 (#2204) Signed-off-by: Naveen Tatikonda --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f38d0c165..132fd7f43 100644 --- a/build.gradle +++ b/build.gradle @@ -295,9 +295,9 @@ dependencies { api group: 'com.google.guava', name: 'guava', version:'32.1.3-jre' api group: 'commons-lang', name: 'commons-lang', version: '2.6' testFixturesImplementation "org.opensearch.test:framework:${opensearch_version}" - testImplementation group: 'net.bytebuddy', name: 'byte-buddy', version: '1.14.9' + testImplementation group: 'net.bytebuddy', name: 'byte-buddy', version: '1.15.4' testImplementation group: 'org.objenesis', name: 'objenesis', version: '3.2' - testImplementation group: 'net.bytebuddy', name: 'byte-buddy-agent', version: '1.14.9' + testImplementation group: 'net.bytebuddy', name: 'byte-buddy-agent', version: '1.15.4' testFixturesImplementation "org.opensearch:common-utils:${version}" implementation 'com.github.oshi:oshi-core:6.4.13' api "net.java.dev.jna:jna:5.13.0" From 07491755e684803aee96c96756a19bb2d438dc79 Mon Sep 17 00:00:00 2001 From: Navneet Verma Date: Fri, 11 Oct 2024 14:32:30 -0700 Subject: [PATCH 41/59] Adding shatejas as the maintainer (#2202) Signed-off-by: Navneet Verma --- .github/CODEOWNERS | 2 +- MAINTAINERS.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b72c1da95..3a99ad3ac 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # This should match the owning team set up in https://github.com/orgs/opensearch-project/teams -* @heemin32 @navneet1v @VijayanB @vamshin @jmazanec15 @naveentatikonda @junqiu-lei @martin-gaievski @ryanbogan @luyuncheng +* @heemin32 @navneet1v @VijayanB @vamshin @jmazanec15 @naveentatikonda @junqiu-lei @martin-gaievski @ryanbogan @luyuncheng @shatejas diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 0093c26e4..36c99b96a 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -13,6 +13,8 @@ This document contains a list of maintainers in this repo. See [opensearch-proje | Naveen Tatikonda | [naveentatikonda](https://github.com/naveentatikonda) | Amazon | | Navneet Verma | [navneet1v](https://github.com/navneet1v) | Amazon | | Ryan Bogan | [ryanbogan](https://github.com/ryanbogan) | Amazon | +| Tejas Shah | [shatejas](https://github.com/shatejas) | Amazon | | Vamshi Vijay Nakkirtha | [vamshin](https://github.com/vamshin) | Amazon | | Vijayan Balasubramanian | [VijayanB](https://github.com/VijayanB) | Amazon | | Yuncheng Lu | [luyuncheng](https://github.com/luyuncheng) | Bytedance | + From f810493cf9d255da7532e0bb1d59d4bcee927d87 Mon Sep 17 00:00:00 2001 From: Navneet Verma Date: Mon, 14 Oct 2024 09:15:20 -0700 Subject: [PATCH 42/59] Minor code fixes which includes: (#2206) * Passing correct score mode in NativeEngineKNNVectorQuery * Ensuring visitor is called in KnnQuery Signed-off-by: Navneet Verma --- .../org/opensearch/knn/index/query/KNNQuery.java | 2 +- .../nativelib/NativeEngineKnnVectorQuery.java | 2 +- .../nativelib/NativeEngineKNNVectorQueryTests.java | 14 ++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/opensearch/knn/index/query/KNNQuery.java b/src/main/java/org/opensearch/knn/index/query/KNNQuery.java index f5c4d3131..f0974f7e9 100644 --- a/src/main/java/org/opensearch/knn/index/query/KNNQuery.java +++ b/src/main/java/org/opensearch/knn/index/query/KNNQuery.java @@ -188,7 +188,7 @@ private Weight getFilterWeight(IndexSearcher searcher) throws IOException { @Override public void visit(QueryVisitor visitor) { - + visitor.visitLeaf(this); } @Override diff --git a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java index 8b861b430..a34a0f1ee 100644 --- a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java +++ b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java @@ -53,7 +53,7 @@ public class NativeEngineKnnVectorQuery extends Query { @Override public Weight createWeight(IndexSearcher indexSearcher, ScoreMode scoreMode, float boost) throws IOException { final IndexReader reader = indexSearcher.getIndexReader(); - final KNNWeight knnWeight = (KNNWeight) knnQuery.createWeight(indexSearcher, ScoreMode.COMPLETE, 1); + final KNNWeight knnWeight = (KNNWeight) knnQuery.createWeight(indexSearcher, scoreMode, 1); List leafReaderContexts = reader.leaves(); List> perLeafResults; RescoreContext rescoreContext = knnQuery.getRescoreContext(); diff --git a/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java index 53873e15f..4577a34d4 100644 --- a/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java +++ b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java @@ -76,6 +76,8 @@ public class NativeEngineKNNVectorQueryTests extends OpenSearchTestCase { @InjectMocks private NativeEngineKnnVectorQuery objectUnderTest; + private static ScoreMode scoreMode = ScoreMode.TOP_SCORES; + @Override public void setUp() throws Exception { super.setUp(); @@ -85,7 +87,7 @@ public void setUp() throws Exception { when(leaf2.reader()).thenReturn(leafReader2); when(searcher.getIndexReader()).thenReturn(reader); - when(knnQuery.createWeight(searcher, ScoreMode.COMPLETE, 1)).thenReturn(knnWeight); + when(knnQuery.createWeight(searcher, scoreMode, 1)).thenReturn(knnWeight); when(searcher.getTaskExecutor()).thenReturn(taskExecutor); when(taskExecutor.invokeAll(any())).thenAnswer(invocationOnMock -> { @@ -135,7 +137,7 @@ public void testMultiLeaf() { Query expected = new DocAndScoreQuery(4, expectedDocs, expectedScores, findSegments, 1); // When - Weight actual = objectUnderTest.createWeight(searcher, ScoreMode.COMPLETE, 1); + Weight actual = objectUnderTest.createWeight(searcher, scoreMode, 1); // Then assertEquals(expected, actual.getQuery()); @@ -176,7 +178,7 @@ public void testRescoreWhenShardLevelRescoringEnabled() { mockedResultUtil.when(() -> ResultUtil.reduceToTopK(any(), anyInt())).thenCallRealMethod(); // When - Weight actual = objectUnderTest.createWeight(searcher, ScoreMode.COMPLETE, 1); + Weight actual = objectUnderTest.createWeight(searcher, scoreMode, 1); // Then mockedResultUtil.verify(() -> ResultUtil.reduceToTopK(any(), anyInt()), times(2)); @@ -199,7 +201,7 @@ public void testSingleLeaf() { Query expected = new DocAndScoreQuery(4, expectedDocs, expectedScores, findSegments, 1); // When - Weight actual = objectUnderTest.createWeight(searcher, ScoreMode.COMPLETE, 1); + Weight actual = objectUnderTest.createWeight(searcher, scoreMode, 1); // Then assertEquals(expected, actual.getQuery()); @@ -214,7 +216,7 @@ public void testNoMatch() { when(knnQuery.getK()).thenReturn(4); // When - Weight actual = objectUnderTest.createWeight(searcher, ScoreMode.COMPLETE, 1); + Weight actual = objectUnderTest.createWeight(searcher, scoreMode, 1); // Then assertEquals(new MatchNoDocsQuery(), actual.getQuery()); @@ -260,7 +262,7 @@ public void testRescore() { try (MockedStatic mockedStaticNativeKnnVectorQuery = mockStatic(NativeEngineKnnVectorQuery.class)) { mockedStaticNativeKnnVectorQuery.when(() -> NativeEngineKnnVectorQuery.findSegmentStarts(any(), any())) .thenReturn(new int[] { 0, 4, 2 }); - Weight actual = objectUnderTest.createWeight(searcher, ScoreMode.COMPLETE, 1); + Weight actual = objectUnderTest.createWeight(searcher, scoreMode, 1); assertEquals(expected, actual.getQuery()); } } From 7cf45c8cfd4219912d67becb731f8a7af61410c0 Mon Sep 17 00:00:00 2001 From: Doo Yong Kim <0ctopus13prime@gmail.com> Date: Tue, 15 Oct 2024 09:41:14 -0700 Subject: [PATCH 43/59] Introduce a loading layer in NMSLIB. (#2185) * Introduce a loading layer in NMSLIB. Signed-off-by: Dooyong Kim * Added NMSLIB istream implementation. Signed-off-by: Dooyong Kim * Fix integer overflow issue when passing read size for loading NMSLIB vector index. Signed-off-by: Dooyong Kim * Added unit test for NMSLIB loading layer. Signed-off-by: Dooyong Kim * Made a patch in NMSLIB to avoid frequently calling JNI for better loading index performance. Signed-off-by: Dooyong Kim * Compliance constexpr function in C++11 having nullstatement. Signed-off-by: Dooyong Kim --------- Signed-off-by: Dooyong Kim Co-authored-by: Dooyong Kim --- jni/CMakeLists.txt | 3 +- jni/cmake/init-nmslib.cmake | 3 +- jni/include/faiss_stream_support.h | 81 +-- jni/include/jni_util.h | 9 +- jni/include/native_engines_stream_support.h | 125 ++++ jni/include/nmslib_stream_support.h | 51 ++ jni/include/nmslib_wrapper.h | 8 + .../org_opensearch_knn_jni_NmslibService.h | 8 + ...pis-for-vector-index-loading-in-Hnsw.patch | 221 ++++++++ ...is-using-stream-to-load-save-in-Hnsw.patch | 93 --- jni/src/jni_util.cpp | 10 +- jni/src/nmslib_wrapper.cpp | 533 ++++++++++-------- .../org_opensearch_knn_jni_NmslibService.cpp | 117 ++-- jni/tests/faiss_stream_support_test.cpp | 98 ++-- jni/tests/native_stream_support_util.h | 102 ++++ jni/tests/nmslib_stream_support_test.cpp | 120 ++++ jni/tests/test_util.h | 3 +- .../index/memory/NativeMemoryAllocation.java | 4 +- .../memory/NativeMemoryLoadStrategy.java | 27 +- .../knn/index/store/IndexInputWithBuffer.java | 10 +- .../org/opensearch/knn/jni/JNIService.java | 2 + .../org/opensearch/knn/jni/NmslibService.java | 10 + .../knn/index/KNNCircuitBreakerIT.java | 2 +- .../opensearch/knn/jni/JNIServiceTests.java | 25 + 24 files changed, 1103 insertions(+), 562 deletions(-) create mode 100644 jni/include/native_engines_stream_support.h create mode 100644 jni/include/nmslib_stream_support.h create mode 100644 jni/patches/nmslib/0003-Added-streaming-apis-for-vector-index-loading-in-Hnsw.patch delete mode 100644 jni/patches/nmslib/0003-Adding-two-apis-using-stream-to-load-save-in-Hnsw.patch create mode 100644 jni/tests/native_stream_support_util.h create mode 100644 jni/tests/nmslib_stream_support_test.cpp diff --git a/jni/CMakeLists.txt b/jni/CMakeLists.txt index 4caa907b3..1920453c7 100644 --- a/jni/CMakeLists.txt +++ b/jni/CMakeLists.txt @@ -156,7 +156,8 @@ if ("${WIN32}" STREQUAL "") tests/commons_test.cpp tests/faiss_stream_support_test.cpp tests/faiss_index_service_test.cpp - ) + tests/nmslib_stream_support_test.cpp + ) target_link_libraries( jni_test diff --git a/jni/cmake/init-nmslib.cmake b/jni/cmake/init-nmslib.cmake index 64df457c1..2554b2bd7 100644 --- a/jni/cmake/init-nmslib.cmake +++ b/jni/cmake/init-nmslib.cmake @@ -12,14 +12,13 @@ if (NOT EXISTS ${NMS_REPO_DIR}) execute_process(COMMAND git submodule update --init -- external/nmslib WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endif () - # Apply patches if(NOT DEFINED APPLY_LIB_PATCHES OR "${APPLY_LIB_PATCHES}" STREQUAL true) # Define list of patch files set(PATCH_FILE_LIST) list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0001-Initialize-maxlevel-during-add-from-enterpoint-level.patch") list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0002-Adds-ability-to-pass-ef-parameter-in-the-query-for-h.patch") - list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0003-Adding-two-apis-using-stream-to-load-save-in-Hnsw.patch") + list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0003-Added-streaming-apis-for-vector-index-loading-in-Hnsw.patch") # Get patch id of the last commit execute_process(COMMAND sh -c "git --no-pager show HEAD | git patch-id --stable" OUTPUT_VARIABLE PATCH_ID_OUTPUT_FROM_COMMIT WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib) diff --git a/jni/include/faiss_stream_support.h b/jni/include/faiss_stream_support.h index 65f1631d4..a12d66ae9 100644 --- a/jni/include/faiss_stream_support.h +++ b/jni/include/faiss_stream_support.h @@ -9,11 +9,12 @@ * GitHub history for details. */ -#ifndef OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H -#define OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H +#ifndef OPENSEARCH_KNN_JNI_FAISS_STREAM_SUPPORT_H +#define OPENSEARCH_KNN_JNI_FAISS_STREAM_SUPPORT_H #include "faiss/impl/io.h" #include "jni_util.h" +#include "native_engines_stream_support.h" #include #include @@ -23,80 +24,6 @@ namespace knn_jni { namespace stream { -/** - * This class contains Java IndexInputWithBuffer reference and calls its API to copy required bytes into a read buffer. - */ - -class NativeEngineIndexInputMediator { - public: - // Expect IndexInputWithBuffer is given as `_indexInput`. - NativeEngineIndexInputMediator(JNIUtilInterface *_jni_interface, - JNIEnv *_env, - jobject _indexInput) - : jni_interface(_jni_interface), - env(_env), - indexInput(_indexInput), - bufferArray((jbyteArray) (_jni_interface->GetObjectField(_env, - _indexInput, - getBufferFieldId(_jni_interface, _env)))), - copyBytesMethod(getCopyBytesMethod(_jni_interface, _env)) { - } - - void copyBytes(int64_t nbytes, uint8_t *destination) { - while (nbytes > 0) { - // Call `copyBytes` to read bytes as many as possible. - const auto readBytes = - jni_interface->CallIntMethodLong(env, indexInput, copyBytesMethod, nbytes); - - // === Critical Section Start === - - // Get primitive array pointer, no copy is happening in OpenJDK. - auto primitiveArray = - (jbyte *) jni_interface->GetPrimitiveArrayCritical(env, bufferArray, nullptr); - - // Copy Java bytes to C++ destination address. - std::memcpy(destination, primitiveArray, readBytes); - - // Release the acquired primitive array pointer. - // JNI_ABORT tells JVM to directly free memory without copying back to Java byte[]. - // Since we're merely copying data, we don't need to copying back. - jni_interface->ReleasePrimitiveArrayCritical(env, bufferArray, primitiveArray, JNI_ABORT); - - // === Critical Section End === - - destination += readBytes; - nbytes -= readBytes; - } // End while - } - - private: - static jclass getIndexInputWithBufferClass(JNIUtilInterface *jni_interface, JNIEnv *env) { - static jclass INDEX_INPUT_WITH_BUFFER_CLASS = - jni_interface->FindClassFromJNIEnv(env, "org/opensearch/knn/index/store/IndexInputWithBuffer"); - return INDEX_INPUT_WITH_BUFFER_CLASS; - } - - static jmethodID getCopyBytesMethod(JNIUtilInterface *jni_interface, JNIEnv *env) { - static jmethodID COPY_METHOD_ID = - jni_interface->GetMethodID(env, getIndexInputWithBufferClass(jni_interface, env), "copyBytes", "(J)I"); - return COPY_METHOD_ID; - } - - static jfieldID getBufferFieldId(JNIUtilInterface *jni_interface, JNIEnv *env) { - static jfieldID BUFFER_FIELD_ID = - jni_interface->GetFieldID(env, getIndexInputWithBufferClass(jni_interface, env), "buffer", "[B"); - return BUFFER_FIELD_ID; - } - - JNIUtilInterface *jni_interface; - JNIEnv *env; - - // `IndexInputWithBuffer` instance having `IndexInput` instance obtained from `Directory` for reading. - jobject indexInput; - jbyteArray bufferArray; - jmethodID copyBytesMethod; -}; // class NativeEngineIndexInputMediator - /** @@ -133,4 +60,4 @@ class FaissOpenSearchIOReader final : public faiss::IOReader { } } -#endif //OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H +#endif //OPENSEARCH_KNN_JNI_FAISS_STREAM_SUPPORT_H diff --git a/jni/include/jni_util.h b/jni/include/jni_util.h index 6b1b926e7..9f4daef7c 100644 --- a/jni/include/jni_util.h +++ b/jni/include/jni_util.h @@ -138,7 +138,11 @@ namespace knn_jni { virtual void ReleasePrimitiveArrayCritical(JNIEnv * env, jarray array, void *carray, jint mode) = 0; - virtual jint CallIntMethodLong(JNIEnv * env, jobject obj, jmethodID methodID, int64_t longArg) = 0; + virtual jint CallNonvirtualIntMethodA(JNIEnv *env, jobject obj, jclass clazz, + jmethodID methodID, jvalue *args) = 0; + + virtual jlong CallNonvirtualLongMethodA(JNIEnv * env, jobject obj, jclass clazz, + jmethodID methodID, jvalue* args) = 0; // -------------------------------------------------------------------------- }; @@ -194,7 +198,8 @@ namespace knn_jni { jclass FindClassFromJNIEnv(JNIEnv * env, const char *name) final; jmethodID GetMethodID(JNIEnv * env, jclass clazz, const char *name, const char *sig) final; jfieldID GetFieldID(JNIEnv * env, jclass clazz, const char *name, const char *sig) final; - jint CallIntMethodLong(JNIEnv * env, jobject obj, jmethodID methodID, int64_t longArg) final; + jint CallNonvirtualIntMethodA(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, jvalue *args) final; + jlong CallNonvirtualLongMethodA(JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args) final; void * GetPrimitiveArrayCritical(JNIEnv * env, jarray array, jboolean *isCopy) final; void ReleasePrimitiveArrayCritical(JNIEnv * env, jarray array, void *carray, jint mode) final; diff --git a/jni/include/native_engines_stream_support.h b/jni/include/native_engines_stream_support.h new file mode 100644 index 000000000..5d4b32d3d --- /dev/null +++ b/jni/include/native_engines_stream_support.h @@ -0,0 +1,125 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H +#define OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H + +#include "jni_util.h" + +#include +#include +#include +#include + +namespace knn_jni { +namespace stream { + + + +/** + * This class contains Java IndexInputWithBuffer reference and calls its API to copy required bytes into a read buffer. + */ +class NativeEngineIndexInputMediator { + public: + // Expect IndexInputWithBuffer is given as `_indexInput`. + NativeEngineIndexInputMediator(JNIUtilInterface *_jni_interface, + JNIEnv *_env, + jobject _indexInput) + : jni_interface(_jni_interface), + env(_env), + indexInput(_indexInput), + bufferArray((jbyteArray) (_jni_interface->GetObjectField(_env, + _indexInput, + getBufferFieldId(_jni_interface, _env)))), + copyBytesMethod(getCopyBytesMethod(_jni_interface, _env)), + remainingBytesMethod(getRemainingBytesMethod(_jni_interface, _env)) { + } + + void copyBytes(int64_t nbytes, uint8_t *destination) { + auto jclazz = getIndexInputWithBufferClass(jni_interface, env); + + while (nbytes > 0) { + // Call `copyBytes` to read bytes as many as possible. + jvalue args; + args.j = nbytes; + const auto readBytes = + jni_interface->CallNonvirtualIntMethodA(env, indexInput, jclazz, copyBytesMethod, &args); + + // === Critical Section Start === + + // Get primitive array pointer, no copy is happening in OpenJDK. + auto primitiveArray = + (jbyte *) jni_interface->GetPrimitiveArrayCritical(env, bufferArray, nullptr); + + // Copy Java bytes to C++ destination address. + std::memcpy(destination, primitiveArray, readBytes); + + // Release the acquired primitive array pointer. + // JNI_ABORT tells JVM to directly free memory without copying back to Java byte[]. + // Since we're merely copying data, we don't need to copying back. + jni_interface->ReleasePrimitiveArrayCritical(env, bufferArray, primitiveArray, JNI_ABORT); + + // === Critical Section End === + + destination += readBytes; + nbytes -= readBytes; + } // End while + } + + int64_t remainingBytes() { + return jni_interface->CallNonvirtualLongMethodA(env, + indexInput, + getIndexInputWithBufferClass(jni_interface, env), + remainingBytesMethod, + nullptr); + } + + private: + static jclass getIndexInputWithBufferClass(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jclass INDEX_INPUT_WITH_BUFFER_CLASS = + jni_interface->FindClassFromJNIEnv(env, "org/opensearch/knn/index/store/IndexInputWithBuffer"); + return INDEX_INPUT_WITH_BUFFER_CLASS; + } + + static jmethodID getCopyBytesMethod(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jmethodID COPY_METHOD_ID = + jni_interface->GetMethodID(env, getIndexInputWithBufferClass(jni_interface, env), "copyBytes", "(J)I"); + return COPY_METHOD_ID; + } + + static jmethodID getRemainingBytesMethod(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jmethodID COPY_METHOD_ID = + jni_interface->GetMethodID(env, getIndexInputWithBufferClass(jni_interface, env), "remainingBytes", "()J"); + return COPY_METHOD_ID; + } + + static jfieldID getBufferFieldId(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jfieldID BUFFER_FIELD_ID = + jni_interface->GetFieldID(env, getIndexInputWithBufferClass(jni_interface, env), "buffer", "[B"); + return BUFFER_FIELD_ID; + } + + JNIUtilInterface *jni_interface; + JNIEnv *env; + + // `IndexInputWithBuffer` instance having `IndexInput` instance obtained from `Directory` for reading. + jobject indexInput; + jbyteArray bufferArray; + jmethodID copyBytesMethod; + jmethodID remainingBytesMethod; +}; // class NativeEngineIndexInputMediator + + + +} +} + +#endif //OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H diff --git a/jni/include/nmslib_stream_support.h b/jni/include/nmslib_stream_support.h new file mode 100644 index 000000000..38c06cb95 --- /dev/null +++ b/jni/include/nmslib_stream_support.h @@ -0,0 +1,51 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef OPENSEARCH_KNN_JNI_NMSLIB_STREAM_SUPPORT_H +#define OPENSEARCH_KNN_JNI_NMSLIB_STREAM_SUPPORT_H + +#include "native_engines_stream_support.h" + +namespace knn_jni { +namespace stream { + + + +/** + * NmslibIOReader implementation delegating NativeEngineIndexInputMediator to read bytes. + */ +class NmslibOpenSearchIOReader final : public similarity::NmslibIOReader { + public: + explicit NmslibOpenSearchIOReader(NativeEngineIndexInputMediator *_mediator) + : mediator(_mediator) { + } + + void read(char *bytes, size_t len) final { + if (len > 0) { + // Mediator calls IndexInput, then copy read bytes to `ptr`. + mediator->copyBytes(len, (uint8_t *) bytes); + } + } + + size_t remainingBytes() final { + return mediator->remainingBytes(); + } + + private: + NativeEngineIndexInputMediator *mediator; +}; // class NmslibOpenSearchIOReader + + + +} +} + +#endif //OPENSEARCH_KNN_JNI_NMSLIB_STREAM_SUPPORT_H diff --git a/jni/include/nmslib_wrapper.h b/jni/include/nmslib_wrapper.h index 27a013c10..2853cd71f 100644 --- a/jni/include/nmslib_wrapper.h +++ b/jni/include/nmslib_wrapper.h @@ -33,6 +33,14 @@ namespace knn_jni { // Return a pointer to the loaded index jlong LoadIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ, jobject parametersJ); + // Load an index via an input stream into memory. Use parametersJ to set any query time parameters + // + // Return a pointer to the loaded index + jlong LoadIndexWithStream(knn_jni::JNIUtilInterface * jniUtil, + JNIEnv * env, + jobject readStream, + jobject parametersJ); + // Execute a query against the index located in memory at indexPointerJ. // // Return an array of KNNQueryResults diff --git a/jni/include/org_opensearch_knn_jni_NmslibService.h b/jni/include/org_opensearch_knn_jni_NmslibService.h index a9d5238b7..8d6633aff 100644 --- a/jni/include/org_opensearch_knn_jni_NmslibService.h +++ b/jni/include/org_opensearch_knn_jni_NmslibService.h @@ -34,6 +34,14 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_createIndex JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_NmslibService_loadIndex (JNIEnv *, jclass, jstring, jobject); +/* + * Class: org_opensearch_knn_jni_NmslibService + * Method: loadIndexWithStream + * Signature: (Lorg/opensearch/knn/index/store/IndexInputWithBuffer;Ljava/util/Map;)J + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_NmslibService_loadIndexWithStream + (JNIEnv *, jclass, jobject, jobject); + /* * Class: org_opensearch_knn_jni_NmslibService * Method: queryIndex diff --git a/jni/patches/nmslib/0003-Added-streaming-apis-for-vector-index-loading-in-Hnsw.patch b/jni/patches/nmslib/0003-Added-streaming-apis-for-vector-index-loading-in-Hnsw.patch new file mode 100644 index 000000000..55e7a8c81 --- /dev/null +++ b/jni/patches/nmslib/0003-Added-streaming-apis-for-vector-index-loading-in-Hnsw.patch @@ -0,0 +1,221 @@ +From 2e9b7f7117842009e081dd79e8ab8b019122a3de Mon Sep 17 00:00:00 2001 +From: Dooyong Kim +Date: Fri, 11 Oct 2024 16:19:45 -0700 +Subject: [PATCH] Added streaming apis for vector index loading in Hnsw. + +Signed-off-by: Dooyong Kim +--- + similarity_search/include/method/hnsw.h | 3 + + similarity_search/include/utils.h | 12 +++ + similarity_search/src/method/hnsw.cc | 138 +++++++++++++++++++++++- + 3 files changed, 152 insertions(+), 1 deletion(-) + +diff --git a/similarity_search/include/method/hnsw.h b/similarity_search/include/method/hnsw.h +index e6dcea7..433f98f 100644 +--- a/similarity_search/include/method/hnsw.h ++++ b/similarity_search/include/method/hnsw.h +@@ -457,6 +457,8 @@ namespace similarity { + + virtual void LoadIndex(const string &location) override; + ++ void LoadIndexWithStream(similarity::NmslibIOReader& in); ++ + Hnsw(bool PrintProgress, const Space &space, const ObjectVector &data); + void CreateIndex(const AnyParams &IndexParams) override; + +@@ -500,6 +502,7 @@ namespace similarity { + + void SaveOptimizedIndex(std::ostream& output); + void LoadOptimizedIndex(std::istream& input); ++ void LoadOptimizedIndex(NmslibIOReader& input); + + void SaveRegularIndexBin(std::ostream& output); + void LoadRegularIndexBin(std::istream& input); +diff --git a/similarity_search/include/utils.h b/similarity_search/include/utils.h +index b521c26..a3931b7 100644 +--- a/similarity_search/include/utils.h ++++ b/similarity_search/include/utils.h +@@ -299,12 +299,24 @@ inline void WriteField(ostream& out, const string& fieldName, const FieldType& f + } + } + ++struct NmslibIOReader { ++ virtual ~NmslibIOReader() = default; ++ ++ virtual void read(char* bytes, size_t len) = 0; ++ ++ virtual size_t remainingBytes() = 0; ++}; + + template + void writeBinaryPOD(ostream& out, const T& podRef) { + out.write((char*)&podRef, sizeof(T)); + } + ++template ++static void readBinaryPOD(NmslibIOReader& in, T& podRef) { ++ in.read((char*)&podRef, sizeof(T)); ++} ++ + template + static void readBinaryPOD(istream& in, T& podRef) { + in.read((char*)&podRef, sizeof(T)); +diff --git a/similarity_search/src/method/hnsw.cc b/similarity_search/src/method/hnsw.cc +index 4080b3b..662f06c 100644 +--- a/similarity_search/src/method/hnsw.cc ++++ b/similarity_search/src/method/hnsw.cc +@@ -950,7 +950,6 @@ namespace similarity { + " read so far doesn't match the number of read lines: " + ConvertToString(lineNum)); + } + +- + template + void + Hnsw::LoadRegularIndexBin(std::istream& input) { +@@ -1034,6 +1033,143 @@ namespace similarity { + + } + ++ constexpr bool _isLittleEndian() { ++ return (((uint32_t) 1) & 0xFFU) == 1; ++ } ++ ++ SIZEMASS_TYPE _readIntBigEndian(uint8_t byte0, uint8_t byte1, uint8_t byte2, uint8_t byte3) noexcept { ++ return (static_cast(byte0) << 24) | ++ (static_cast(byte1) << 16) | ++ (static_cast(byte2) << 8) | ++ static_cast(byte3); ++ } ++ ++ SIZEMASS_TYPE _readIntLittleEndian(uint8_t byte0, uint8_t byte1, uint8_t byte2, uint8_t byte3) noexcept { ++ return (static_cast(byte3) << 24) | ++ (static_cast(byte2) << 16) | ++ (static_cast(byte1) << 8) | ++ static_cast(byte0); ++ } ++ ++ template ++ void Hnsw::LoadIndexWithStream(NmslibIOReader& input) { ++ LOG(LIB_INFO) << "Loading index from an input stream(NmslibIOReader)."; ++ ++ unsigned int optimIndexFlag= 0; ++ readBinaryPOD(input, optimIndexFlag); ++ ++ if (!optimIndexFlag) { ++ throw std::runtime_error("With stream, we only support optimized index type."); ++ } else { ++ LoadOptimizedIndex(input); ++ } ++ ++ LOG(LIB_INFO) << "Finished loading index"; ++ visitedlistpool = new VisitedListPool(1, totalElementsStored_); ++ } ++ ++ template ++ void Hnsw::LoadOptimizedIndex(NmslibIOReader& input) { ++ static_assert(sizeof(SIZEMASS_TYPE) == 4, "Expected sizeof(SIZEMASS_TYPE) == 4."); ++ ++ LOG(LIB_INFO) << "Loading optimized index(NmslibIOReader)."; ++ ++ readBinaryPOD(input, totalElementsStored_); ++ readBinaryPOD(input, memoryPerObject_); ++ readBinaryPOD(input, offsetLevel0_); ++ readBinaryPOD(input, offsetData_); ++ readBinaryPOD(input, maxlevel_); ++ readBinaryPOD(input, enterpointId_); ++ readBinaryPOD(input, maxM_); ++ readBinaryPOD(input, maxM0_); ++ readBinaryPOD(input, dist_func_type_); ++ readBinaryPOD(input, searchMethod_); ++ ++ LOG(LIB_INFO) << "searchMethod: " << searchMethod_; ++ ++ fstdistfunc_ = getDistFunc(dist_func_type_); ++ iscosine_ = (dist_func_type_ == kNormCosine); ++ CHECK_MSG(fstdistfunc_ != nullptr, "Unknown distance function code: " + ConvertToString(dist_func_type_)); ++ ++ LOG(LIB_INFO) << "Total: " << totalElementsStored_ << ", Memory per object: " << memoryPerObject_; ++ size_t data_plus_links0_size = memoryPerObject_ * totalElementsStored_; ++ ++ // we allocate a few extra bytes to prevent prefetch from accessing out of range memory ++ data_level0_memory_ = (char *)malloc(data_plus_links0_size + EXTRA_MEM_PAD_SIZE); ++ CHECK(data_level0_memory_); ++ input.read(data_level0_memory_, data_plus_links0_size); ++ // we allocate a few extra bytes to prevent prefetch from accessing out of range memory ++ linkLists_ = (char **)malloc( (sizeof(void *) * totalElementsStored_) + EXTRA_MEM_PAD_SIZE); ++ CHECK(linkLists_); ++ ++ data_rearranged_.resize(totalElementsStored_); ++ ++ const size_t bufferSize = 64 * 1024; // 64KB ++ std::unique_ptr buffer (new char[bufferSize]); ++ uint32_t end = 0; ++ uint32_t pos = 0; ++ constexpr bool isLittleEndian = _isLittleEndian(); ++ ++ for (size_t i = 0, remainingBytes = input.remainingBytes(); i < totalElementsStored_; i++) { ++ if ((pos + sizeof(SIZEMASS_TYPE)) >= end) { ++ // Underflow during reading an integer size field. ++ // So the idea is to move the first partial bytes (which is < 4 bytes) to the beginning section of ++ // buffer. ++ // Ex: buffer -> [..., b0, b1] where we only have two bytes and still need to read two bytes more ++ // buffer -> [b0, b1, ...] after move the first part. firstPartLen = 2. ++ const auto firstPartLen = end - pos; ++ if (firstPartLen > 0) { ++ std::memcpy(buffer.get(), buffer.get() + pos, firstPartLen); ++ } ++ // Then, bulk load bytes from input stream. Note that the first few bytes are already occupied by ++ // earlier moving logic, hence required bytes are bufferSize - firstPartLen. ++ const auto copyBytes = std::min(remainingBytes, bufferSize - firstPartLen); ++ input.read(buffer.get() + firstPartLen, copyBytes); ++ remainingBytes -= copyBytes; ++ end = copyBytes + firstPartLen; ++ pos = 0; ++ } ++ ++ // Read data size field. ++ // Since NMSLIB directly write 4 bytes integer casting to char*, bytes outline may differ among systems. ++ SIZEMASS_TYPE linkListSize = 0; ++ if (isLittleEndian) { ++ linkListSize = _readIntLittleEndian(buffer[pos], buffer[pos + 1], buffer[pos + 2], buffer[pos + 3]); ++ } else { ++ linkListSize = _readIntBigEndian(buffer[pos], buffer[pos + 1], buffer[pos + 2], buffer[pos + 3]); ++ } ++ pos += sizeof(SIZEMASS_TYPE); ++ ++ if (linkListSize == 0) { ++ linkLists_[i] = nullptr; ++ } else { ++ linkLists_[i] = (char *)malloc(linkListSize); ++ CHECK(linkLists_[i]); ++ ++ SIZEMASS_TYPE leftLinkListData = linkListSize; ++ auto dataPtr = linkLists_[i]; ++ while (leftLinkListData > 0) { ++ if (pos >= end) { ++ // Underflow during read linked list bytes. ++ const auto copyBytes = std::min(remainingBytes, bufferSize); ++ input.read(buffer.get(), copyBytes); ++ remainingBytes -= copyBytes; ++ end = copyBytes; ++ pos = 0; ++ } ++ ++ // Read linked list bytes. ++ const auto copyBytes = std::min(leftLinkListData, end - pos); ++ std::memcpy(dataPtr, buffer.get() + pos, copyBytes); ++ dataPtr += copyBytes; ++ leftLinkListData -= copyBytes; ++ pos += copyBytes; ++ } // End while ++ } // End if ++ ++ data_rearranged_[i] = new Object(data_level0_memory_ + (i)*memoryPerObject_ + offsetData_); ++ } // End for ++ } + + template + void +-- +2.39.5 (Apple Git-154) + diff --git a/jni/patches/nmslib/0003-Adding-two-apis-using-stream-to-load-save-in-Hnsw.patch b/jni/patches/nmslib/0003-Adding-two-apis-using-stream-to-load-save-in-Hnsw.patch deleted file mode 100644 index bbba329b4..000000000 --- a/jni/patches/nmslib/0003-Adding-two-apis-using-stream-to-load-save-in-Hnsw.patch +++ /dev/null @@ -1,93 +0,0 @@ -From 7e099ec111e5c9db4b243da249c73f0ecc206281 Mon Sep 17 00:00:00 2001 -From: Dooyong Kim -Date: Thu, 26 Sep 2024 15:20:53 -0700 -Subject: [PATCH] Adding two apis using stream to load/save in Hnsw. - -Signed-off-by: Dooyong Kim ---- - similarity_search/include/method/hnsw.h | 4 +++ - similarity_search/src/method/hnsw.cc | 44 +++++++++++++++++++++++++ - 2 files changed, 48 insertions(+) - -diff --git a/similarity_search/include/method/hnsw.h b/similarity_search/include/method/hnsw.h -index 57d99d0..7ff3f3d 100644 ---- a/similarity_search/include/method/hnsw.h -+++ b/similarity_search/include/method/hnsw.h -@@ -455,8 +455,12 @@ namespace similarity { - public: - virtual void SaveIndex(const string &location) override; - -+ void SaveIndexWithStream(std::ostream& output); -+ - virtual void LoadIndex(const string &location) override; - -+ void LoadIndexWithStream(std::istream& in); -+ - Hnsw(bool PrintProgress, const Space &space, const ObjectVector &data); - void CreateIndex(const AnyParams &IndexParams) override; - -diff --git a/similarity_search/src/method/hnsw.cc b/similarity_search/src/method/hnsw.cc -index 35b372c..e7a2c9e 100644 ---- a/similarity_search/src/method/hnsw.cc -+++ b/similarity_search/src/method/hnsw.cc -@@ -771,6 +771,25 @@ namespace similarity { - output.close(); - } - -+ template -+ void Hnsw::SaveIndexWithStream(std::ostream &output) { -+ output.exceptions(ios::badbit | ios::failbit); -+ -+ unsigned int optimIndexFlag = data_level0_memory_ != nullptr; -+ -+ writeBinaryPOD(output, optimIndexFlag); -+ -+ if (!optimIndexFlag) { -+#if USE_TEXT_REGULAR_INDEX -+ SaveRegularIndexText(output); -+#else -+ SaveRegularIndexBin(output); -+#endif -+ } else { -+ SaveOptimizedIndex(output); -+ } -+ } -+ - template - void - Hnsw::SaveOptimizedIndex(std::ostream& output) { -@@ -1021,6 +1040,31 @@ namespace similarity { - - } - -+ template -+ void Hnsw::LoadIndexWithStream(std::istream& input) { -+ LOG(LIB_INFO) << "Loading index from an input stream."; -+ CHECK_MSG(input, "Cannot open file for reading with an input stream"); -+ -+ input.exceptions(ios::badbit | ios::failbit); -+ -+#if USE_TEXT_REGULAR_INDEX -+ LoadRegularIndexText(input); -+#else -+ unsigned int optimIndexFlag= 0; -+ -+ readBinaryPOD(input, optimIndexFlag); -+ -+ if (!optimIndexFlag) { -+ LoadRegularIndexBin(input); -+ } else { -+ LoadOptimizedIndex(input); -+ } -+#endif -+ -+ LOG(LIB_INFO) << "Finished loading index"; -+ visitedlistpool = new VisitedListPool(1, totalElementsStored_); -+ } -+ - - template - void --- -2.39.5 (Apple Git-154) - diff --git a/jni/src/jni_util.cpp b/jni/src/jni_util.cpp index 3eaf3b0a1..8dc818c94 100644 --- a/jni/src/jni_util.cpp +++ b/jni/src/jni_util.cpp @@ -563,8 +563,14 @@ jfieldID knn_jni::JNIUtil::GetFieldID(JNIEnv * env, jclass clazz, const char *na return env->GetFieldID(clazz, name, sig); } -jint knn_jni::JNIUtil::CallIntMethodLong(JNIEnv * env, jobject obj, jmethodID methodID, int64_t longArg) { - return env->CallIntMethod(obj, methodID, longArg); +jint knn_jni::JNIUtil::CallNonvirtualIntMethodA(JNIEnv * env, jobject obj, jclass clazz, + jmethodID methodID, jvalue* args) { + return env->CallNonvirtualIntMethodA(obj, clazz, methodID, args); +} + +jlong knn_jni::JNIUtil::CallNonvirtualLongMethodA(JNIEnv * env, jobject obj, jclass clazz, + jmethodID methodID, jvalue* args) { + return env->CallNonvirtualLongMethodA(obj, clazz, methodID, args); } void * knn_jni::JNIUtil::GetPrimitiveArrayCritical(JNIEnv * env, jarray array, jboolean *isCopy) { diff --git a/jni/src/nmslib_wrapper.cpp b/jni/src/nmslib_wrapper.cpp index 21b34eb83..536558caa 100644 --- a/jni/src/nmslib_wrapper.cpp +++ b/jni/src/nmslib_wrapper.cpp @@ -11,6 +11,7 @@ #include "jni_util.h" #include "nmslib_wrapper.h" +#include "nmslib_stream_support.h" #include "commons.h" @@ -25,303 +26,365 @@ #include #include +#include #include "hnswquery.h" +#include "method/hnsw.h" - -std::string TranslateSpaceType(const std::string& spaceType); +std::string TranslateSpaceType(const std::string &spaceType); // We do not use label functionality of nmslib so we pass default label. Setting as a const allows us to avoid a few // allocations const similarity::LabelType DEFAULT_LABEL = -1; -void knn_jni::nmslib_wrapper::CreateIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, +void knn_jni::nmslib_wrapper::CreateIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jintArray idsJ, jlong vectorsAddressJ, jint dimJ, jstring indexPathJ, jobject parametersJ) { - if (idsJ == nullptr) { - throw std::runtime_error("IDs cannot be null"); - } + if (idsJ == nullptr) { + throw std::runtime_error("IDs cannot be null"); + } - if (vectorsAddressJ <= 0) { - throw std::runtime_error("VectorsAddress cannot be less than 0"); - } + if (vectorsAddressJ <= 0) { + throw std::runtime_error("VectorsAddress cannot be less than 0"); + } - if(dimJ <= 0) { - throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); - } + if (dimJ <= 0) { + throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); + } - if (indexPathJ == nullptr) { - throw std::runtime_error("Index path cannot be null"); - } + if (indexPathJ == nullptr) { + throw std::runtime_error("Index path cannot be null"); + } - if (parametersJ == nullptr) { - throw std::runtime_error("Parameters cannot be null"); - } - - // Handle parameters - auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); - std::vector indexParameters; + if (parametersJ == nullptr) { + throw std::runtime_error("Parameters cannot be null"); + } - // Algorithm parameters will be in a sub map - if(parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { - jobject subParametersJ = parametersCpp[knn_jni::PARAMETERS]; - auto subParametersCpp = jniUtil->ConvertJavaMapToCppMap(env, subParametersJ); + // Handle parameters + auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); + std::vector indexParameters; - if(subParametersCpp.find(knn_jni::EF_CONSTRUCTION) != subParametersCpp.end()) { - auto efConstruction = jniUtil->ConvertJavaObjectToCppInteger(env, subParametersCpp[knn_jni::EF_CONSTRUCTION]); - indexParameters.push_back(knn_jni::EF_CONSTRUCTION_NMSLIB + "=" + std::to_string(efConstruction)); - } + // Algorithm parameters will be in a sub map + if (parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { + jobject subParametersJ = parametersCpp[knn_jni::PARAMETERS]; + auto subParametersCpp = jniUtil->ConvertJavaMapToCppMap(env, subParametersJ); - if(subParametersCpp.find(knn_jni::M) != subParametersCpp.end()) { - auto m = jniUtil->ConvertJavaObjectToCppInteger(env, subParametersCpp[knn_jni::M]); - indexParameters.push_back(knn_jni::M_NMSLIB + "=" + std::to_string(m)); - } - - jniUtil->DeleteLocalRef(env, subParametersJ); + if (subParametersCpp.find(knn_jni::EF_CONSTRUCTION) != subParametersCpp.end()) { + auto efConstruction = jniUtil->ConvertJavaObjectToCppInteger(env, subParametersCpp[knn_jni::EF_CONSTRUCTION]); + indexParameters.push_back(knn_jni::EF_CONSTRUCTION_NMSLIB + "=" + std::to_string(efConstruction)); } - if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { - auto indexThreadQty = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); - indexParameters.push_back(knn_jni::INDEX_THREAD_QUANTITY + "=" + std::to_string(indexThreadQty)); + if (subParametersCpp.find(knn_jni::M) != subParametersCpp.end()) { + auto m = jniUtil->ConvertJavaObjectToCppInteger(env, subParametersCpp[knn_jni::M]); + indexParameters.push_back(knn_jni::M_NMSLIB + "=" + std::to_string(m)); } - jniUtil->DeleteLocalRef(env, parametersJ); - - // Get the path to save the index - std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); - - // Get space type for this index - jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); - std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); - spaceTypeCpp = TranslateSpaceType(spaceTypeCpp); - - std::unique_ptr> space; - space.reset(similarity::SpaceFactoryRegistry::Instance().CreateSpace(spaceTypeCpp,similarity::AnyParams())); - - // Get number of ids and vectors and dimension - auto *inputVectors = reinterpret_cast*>(vectorsAddressJ); - int dim = (int)dimJ; - // The number of vectors can be int here because a lucene segment number of total docs never crosses INT_MAX value - int numVectors = (int) ( inputVectors->size() / (uint64_t) dim); - if(numVectors == 0) { - throw std::runtime_error("Number of vectors cannot be 0"); + jniUtil->DeleteLocalRef(env, subParametersJ); + } + + if (parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { + auto indexThreadQty = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); + indexParameters.push_back(knn_jni::INDEX_THREAD_QUANTITY + "=" + std::to_string(indexThreadQty)); + } + + jniUtil->DeleteLocalRef(env, parametersJ); + + // Get the path to save the index + std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); + + // Get space type for this index + jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); + std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); + spaceTypeCpp = TranslateSpaceType(spaceTypeCpp); + + std::unique_ptr> space; + space.reset(similarity::SpaceFactoryRegistry::Instance().CreateSpace(spaceTypeCpp, similarity::AnyParams())); + + // Get number of ids and vectors and dimension + auto *inputVectors = reinterpret_cast *>(vectorsAddressJ); + int dim = (int) dimJ; + // The number of vectors can be int here because a lucene segment number of total docs never crosses INT_MAX value + int numVectors = (int) (inputVectors->size() / (uint64_t) dim); + if (numVectors == 0) { + throw std::runtime_error("Number of vectors cannot be 0"); + } + + int numIds = jniUtil->GetJavaIntArrayLength(env, idsJ); + if (numIds != numVectors) { + throw std::runtime_error("Number of IDs does not match number of vectors"); + } + + // Read dataset + similarity::ObjectVector dataset; + dataset.reserve(numVectors); + int *idsCpp; + try { + // Read in data set + idsCpp = jniUtil->GetIntArrayElements(env, idsJ, nullptr); + size_t vectorSizeInBytes = dim * sizeof(float); + // vectorPointer needs to be unsigned long long, this will ensure that out of range doesn't happen for this pointer + // when the values of numVectors * dim becomes very large. + // Example: for 10M vectors of 1536 dim vectorPointer max value will be ~15.3B which is already > range of ints. + // keeping it unsigned long long we will never go above the range. + unsigned long long vectorPointer = 0; + + // Allocate a large buffer that will contain all the vectors. Allocating the objects in one large buffer as + // opposed to individually will prevent heap fragmentation. We have observed that allocating individual + // objects causes RSS to rise throughout the lifetime of a process + // (see https://github.com/opensearch-project/k-NN/issues/772 and + // https://github.com/opensearch-project/k-NN/issues/72). This is because, in typical systems, small + // allocations will reside on some kind of heap managed by an allocator. Once freed, the allocator does not + // always return the memory to the OS. If the heap gets fragmented, this will cause the allocator + // to ask for more memory, causing RSS to grow. On large allocations (> 128 kb), most allocators will + // internally use mmap. Once freed, unmap will be called, which will immediately return memory to the OS + // which in turn prevents RSS from growing out of control. Wrap with a smart pointer so that buffer will be + // freed once variable goes out of scope. For reference, the code that specifies the layout of the buffer can be + // found: https://github.com/nmslib/nmslib/blob/v2.1.1/similarity_search/include/object.h#L61-L75 + std::unique_ptr objectBuffer + (new char[(similarity::ID_SIZE + similarity::LABEL_SIZE + similarity::DATALENGTH_SIZE + vectorSizeInBytes) + * numVectors]); + char *ptr = objectBuffer.get(); + for (int i = 0; i < numVectors; i++) { + dataset.push_back(new similarity::Object(ptr)); + + memcpy(ptr, &idsCpp[i], similarity::ID_SIZE); + ptr += similarity::ID_SIZE; + memcpy(ptr, &DEFAULT_LABEL, similarity::LABEL_SIZE); + ptr += similarity::LABEL_SIZE; + memcpy(ptr, &vectorSizeInBytes, similarity::DATALENGTH_SIZE); + ptr += similarity::DATALENGTH_SIZE; + + memcpy(ptr, &(inputVectors->at(vectorPointer)), vectorSizeInBytes); + ptr += vectorSizeInBytes; + vectorPointer += dim; } - - int numIds = jniUtil->GetJavaIntArrayLength(env, idsJ); - if (numIds != numVectors) { - throw std::runtime_error("Number of IDs does not match number of vectors"); + jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); + + // Releasing the vectorsAddressJ memory as that is not required once we have created the index. + // This is not the ideal approach, please refer this gh issue for long term solution: + // https://github.com/opensearch-project/k-NN/issues/1600 + //commons::freeVectorData(vectorsAddressJ); + delete inputVectors; + + std::unique_ptr> index; + index.reset(similarity::MethodFactoryRegistry::Instance().CreateMethod(false, + "hnsw", + spaceTypeCpp, + *(space), + dataset)); + index->CreateIndex(similarity::AnyParams(indexParameters)); + index->SaveIndex(indexPathCpp); + + for (auto &it : dataset) { + delete it; } - - // Read dataset - similarity::ObjectVector dataset; - dataset.reserve(numVectors); - int* idsCpp; - try { - // Read in data set - idsCpp = jniUtil->GetIntArrayElements(env, idsJ, nullptr); - size_t vectorSizeInBytes = dim*sizeof(float); - // vectorPointer needs to be unsigned long long, this will ensure that out of range doesn't happen for this pointer - // when the values of numVectors * dim becomes very large. - // Example: for 10M vectors of 1536 dim vectorPointer max value will be ~15.3B which is already > range of ints. - // keeping it unsigned long long we will never go above the range. - unsigned long long vectorPointer = 0; - - // Allocate a large buffer that will contain all the vectors. Allocating the objects in one large buffer as - // opposed to individually will prevent heap fragmentation. We have observed that allocating individual - // objects causes RSS to rise throughout the lifetime of a process - // (see https://github.com/opensearch-project/k-NN/issues/772 and - // https://github.com/opensearch-project/k-NN/issues/72). This is because, in typical systems, small - // allocations will reside on some kind of heap managed by an allocator. Once freed, the allocator does not - // always return the memory to the OS. If the heap gets fragmented, this will cause the allocator - // to ask for more memory, causing RSS to grow. On large allocations (> 128 kb), most allocators will - // internally use mmap. Once freed, unmap will be called, which will immediately return memory to the OS - // which in turn prevents RSS from growing out of control. Wrap with a smart pointer so that buffer will be - // freed once variable goes out of scope. For reference, the code that specifies the layout of the buffer can be - // found: https://github.com/nmslib/nmslib/blob/v2.1.1/similarity_search/include/object.h#L61-L75 - std::unique_ptr objectBuffer(new char[(similarity::ID_SIZE + similarity::LABEL_SIZE + similarity::DATALENGTH_SIZE + vectorSizeInBytes) * numVectors]); - char* ptr = objectBuffer.get(); - for (int i = 0; i < numVectors; i++) { - dataset.push_back(new similarity::Object(ptr)); - - memcpy(ptr, &idsCpp[i], similarity::ID_SIZE); - ptr += similarity::ID_SIZE; - memcpy(ptr, &DEFAULT_LABEL, similarity::LABEL_SIZE); - ptr += similarity::LABEL_SIZE; - memcpy(ptr, &vectorSizeInBytes, similarity::DATALENGTH_SIZE); - ptr += similarity::DATALENGTH_SIZE; - - memcpy(ptr, &(inputVectors->at(vectorPointer)), vectorSizeInBytes); - ptr += vectorSizeInBytes; - vectorPointer += dim; - } - jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); - - // Releasing the vectorsAddressJ memory as that is not required once we have created the index. - // This is not the ideal approach, please refer this gh issue for long term solution: - // https://github.com/opensearch-project/k-NN/issues/1600 - //commons::freeVectorData(vectorsAddressJ); - delete inputVectors; - - std::unique_ptr> index; - index.reset(similarity::MethodFactoryRegistry::Instance().CreateMethod(false, "hnsw", spaceTypeCpp, *(space), dataset)); - index->CreateIndex(similarity::AnyParams(indexParameters)); - index->SaveIndex(indexPathCpp); - - for (auto & it : dataset) { - delete it; - } - } catch (...) { - for (auto & it : dataset) { - delete it; - } - - jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); - throw; + } catch (...) { + for (auto &it : dataset) { + delete it; } + + jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); + throw; + } } -jlong knn_jni::nmslib_wrapper::LoadIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ, +jlong knn_jni::nmslib_wrapper::LoadIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jstring indexPathJ, jobject parametersJ) { - if (indexPathJ == nullptr) { - throw std::runtime_error("Index path cannot be null"); - } + if (indexPathJ == nullptr) { + throw std::runtime_error("Index path cannot be null"); + } - if (parametersJ == nullptr) { - throw std::runtime_error("Parameters cannot be null"); - } + if (parametersJ == nullptr) { + throw std::runtime_error("Parameters cannot be null"); + } - std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); + std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); - auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); + auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); - // Get space type for this index - jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); - std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); - spaceTypeCpp = TranslateSpaceType(spaceTypeCpp); + // Get space type for this index + jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); + std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); + spaceTypeCpp = TranslateSpaceType(spaceTypeCpp); - // Parse query params - std::vector queryParams; + // Parse query params + std::vector queryParams; - if(parametersCpp.find("efSearch") != parametersCpp.end()) { - auto efSearch = std::to_string(jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp["efSearch"])); - queryParams.push_back("efSearch=" + efSearch); - } + auto it = parametersCpp.find("efSearch"); + if (it != parametersCpp.end()) { + auto efSearch = std::to_string(jniUtil->ConvertJavaObjectToCppInteger(env, it->second)); + queryParams.push_back("efSearch=" + efSearch); + } - // Load index - knn_jni::nmslib_wrapper::IndexWrapper * indexWrapper; - try { - indexWrapper = new knn_jni::nmslib_wrapper::IndexWrapper(spaceTypeCpp); - indexWrapper->index->LoadIndex(indexPathCpp); - indexWrapper->index->SetQueryTimeParams(similarity::AnyParams(queryParams)); - } catch (...) { - delete indexWrapper; - throw; + // Load index + knn_jni::nmslib_wrapper::IndexWrapper *indexWrapper = nullptr; + try { + indexWrapper = new knn_jni::nmslib_wrapper::IndexWrapper(spaceTypeCpp); + indexWrapper->index->LoadIndex(indexPathCpp); + indexWrapper->index->SetQueryTimeParams(similarity::AnyParams(queryParams)); + } catch (...) { + delete indexWrapper; + throw; + } + + return (jlong) indexWrapper; +} + +jlong knn_jni::nmslib_wrapper::LoadIndexWithStream(knn_jni::JNIUtilInterface *jniUtil, + JNIEnv *env, + jobject readStream, + jobject parametersJ) { + if (readStream == nullptr) { + throw std::runtime_error("Read stream cannot be null"); + } + + if (parametersJ == nullptr) { + throw std::runtime_error("Parameters cannot be null"); + } + + auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); + + // Get space type for this index + jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); + std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); + spaceTypeCpp = TranslateSpaceType(spaceTypeCpp); + + // Parse query params + std::vector queryParams; + + auto it = parametersCpp.find("efSearch"); + if (it != parametersCpp.end()) { + auto efSearch = std::to_string(jniUtil->ConvertJavaObjectToCppInteger(env, it->second)); + queryParams.push_back("efSearch=" + efSearch); + } + + // Create a mediator locally. + // Note that `indexInput` is `IndexInputWithBuffer` type. + knn_jni::stream::NativeEngineIndexInputMediator mediator{jniUtil, env, readStream}; + + knn_jni::stream::NmslibOpenSearchIOReader ioReader {&mediator}; + + // Load index + knn_jni::nmslib_wrapper::IndexWrapper *indexWrapper = nullptr; + try { + indexWrapper = new knn_jni::nmslib_wrapper::IndexWrapper(spaceTypeCpp); + indexWrapper->index->SetQueryTimeParams(similarity::AnyParams(queryParams)); + + if (auto hnswFloatIndex = dynamic_cast *>(indexWrapper->index.get())) { + hnswFloatIndex->LoadIndexWithStream(ioReader); + } else { + throw std::runtime_error("We only support similarity::Hnsw in NMSLIB."); } + } catch (...) { + delete indexWrapper; + throw; + } - return (jlong) indexWrapper; + return (jlong) indexWrapper; } -jobjectArray knn_jni::nmslib_wrapper::QueryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong indexPointerJ, +jobjectArray knn_jni::nmslib_wrapper::QueryIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong indexPointerJ, jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ) { - if (queryVectorJ == nullptr) { - throw std::runtime_error("Query Vector cannot be null"); - } - - if (indexPointerJ == 0) { - throw std::runtime_error("Invalid pointer to index"); - } + if (queryVectorJ == nullptr) { + throw std::runtime_error("Query Vector cannot be null"); + } - auto *indexWrapper = reinterpret_cast(indexPointerJ); + if (indexPointerJ == 0) { + throw std::runtime_error("Invalid pointer to index"); + } - int dim = jniUtil->GetJavaFloatArrayLength(env, queryVectorJ); + auto *indexWrapper = reinterpret_cast(indexPointerJ); - float* rawQueryvector = jniUtil->GetFloatArrayElements(env, queryVectorJ, nullptr); // Have to call release on this + int dim = jniUtil->GetJavaFloatArrayLength(env, queryVectorJ); - std::unique_ptr queryObject; - try { - queryObject.reset(new similarity::Object(-1, -1, dim*sizeof(float), rawQueryvector)); - } catch (...) { - jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); - throw; - } + float *rawQueryvector = jniUtil->GetFloatArrayElements(env, queryVectorJ, nullptr); // Have to call release on this + std::unique_ptr queryObject; + try { + queryObject.reset(new similarity::Object(-1, -1, dim * sizeof(float), rawQueryvector)); + } catch (...) { jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); - std::unordered_map methodParams; - if (methodParamsJ != nullptr) { - methodParams = jniUtil->ConvertJavaMapToCppMap(env, methodParamsJ); + throw; + } + + jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); + std::unordered_map methodParams; + if (methodParamsJ != nullptr) { + methodParams = jniUtil->ConvertJavaMapToCppMap(env, methodParamsJ); + } + + int queryEfSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, -1); + similarity::KNNQuery + *query; // TODO: Replace with smart pointers https://github.com/opensearch-project/k-NN/issues/1785 + std::unique_ptr> neighbors; + try { + if (queryEfSearch == -1) { + query = new similarity::KNNQuery(*(indexWrapper->space), queryObject.get(), kJ); + } else { + query = new similarity::HNSWQuery(*(indexWrapper->space), queryObject.get(), kJ, queryEfSearch); } - int queryEfSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, -1); - similarity::KNNQuery* query; // TODO: Replace with smart pointers https://github.com/opensearch-project/k-NN/issues/1785 - std::unique_ptr> neighbors; - try { - if (queryEfSearch == -1) { - query = new similarity::KNNQuery(*(indexWrapper->space), queryObject.get(), kJ); - } else { - query = new similarity::HNSWQuery(*(indexWrapper->space), queryObject.get(), kJ, queryEfSearch); - } - - indexWrapper->index->Search(query); - neighbors.reset(query->Result()->Clone()); - } catch (...) { - if (query != nullptr) { - delete query; - } - throw; + indexWrapper->index->Search(query); + neighbors.reset(query->Result()->Clone()); + } catch (...) { + if (query != nullptr) { + delete query; } - delete query; - - int resultSize = neighbors->Size(); - jclass resultClass = jniUtil->FindClass(env,"org/opensearch/knn/index/query/KNNQueryResult"); - jmethodID allArgs = jniUtil->FindMethod(env, "org/opensearch/knn/index/query/KNNQueryResult", ""); - - jobjectArray results = jniUtil->NewObjectArray(env, resultSize, resultClass, nullptr); - - jobject result; - float distance; - long id; - for(int i = 0; i < resultSize; ++i) { - distance = neighbors->TopDistance(); - id = neighbors->Pop()->id(); - result = jniUtil->NewObject(env, resultClass, allArgs, id, distance); - jniUtil->SetObjectArrayElement(env, results, i, result); - } - - return results; + throw; + } + delete query; + + int resultSize = neighbors->Size(); + jclass resultClass = jniUtil->FindClass(env, "org/opensearch/knn/index/query/KNNQueryResult"); + jmethodID allArgs = jniUtil->FindMethod(env, "org/opensearch/knn/index/query/KNNQueryResult", ""); + + jobjectArray results = jniUtil->NewObjectArray(env, resultSize, resultClass, nullptr); + + jobject result; + float distance; + long id; + for (int i = 0; i < resultSize; ++i) { + distance = neighbors->TopDistance(); + id = neighbors->Pop()->id(); + result = jniUtil->NewObject(env, resultClass, allArgs, id, distance); + jniUtil->SetObjectArrayElement(env, results, i, result); + } + + return results; } void knn_jni::nmslib_wrapper::Free(jlong indexPointerJ) { - auto *indexWrapper = reinterpret_cast(indexPointerJ); - delete indexWrapper; + auto *indexWrapper = reinterpret_cast(indexPointerJ); + delete indexWrapper; } void knn_jni::nmslib_wrapper::InitLibrary() { - similarity::initLibrary(); + similarity::initLibrary(); } -std::string TranslateSpaceType(const std::string& spaceType) { - if (spaceType == knn_jni::L2) { - return spaceType; - } +std::string TranslateSpaceType(const std::string &spaceType) { + if (spaceType == knn_jni::L2) { + return spaceType; + } - if (spaceType == knn_jni::L1) { - return spaceType; - } + if (spaceType == knn_jni::L1) { + return spaceType; + } - if (spaceType == knn_jni::LINF) { - return spaceType; - } + if (spaceType == knn_jni::LINF) { + return spaceType; + } - if (spaceType == knn_jni::COSINESIMIL) { - return spaceType; - } + if (spaceType == knn_jni::COSINESIMIL) { + return spaceType; + } - if (spaceType == knn_jni::INNER_PRODUCT) { - return knn_jni::NEG_DOT_PRODUCT; - } + if (spaceType == knn_jni::INNER_PRODUCT) { + return knn_jni::NEG_DOT_PRODUCT; + } - throw std::runtime_error("Invalid spaceType"); + throw std::runtime_error("Invalid spaceType"); } diff --git a/jni/src/org_opensearch_knn_jni_NmslibService.cpp b/jni/src/org_opensearch_knn_jni_NmslibService.cpp index e265827cd..8e4df2e9c 100644 --- a/jni/src/org_opensearch_knn_jni_NmslibService.cpp +++ b/jni/src/org_opensearch_knn_jni_NmslibService.cpp @@ -12,7 +12,6 @@ #include "org_opensearch_knn_jni_NmslibService.h" #include -#include #include "jni_util.h" #include "nmslib_wrapper.h" @@ -20,71 +19,85 @@ static knn_jni::JNIUtil jniUtil; static const jint KNN_NMSLIB_JNI_VERSION = JNI_VERSION_1_1; -jint JNI_OnLoad(JavaVM* vm, void* reserved) { - JNIEnv* env; - if (vm->GetEnv((void**)&env, KNN_NMSLIB_JNI_VERSION) != JNI_OK) { - return JNI_ERR; - } +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv *env; + if (vm->GetEnv((void **) &env, KNN_NMSLIB_JNI_VERSION) != JNI_OK) { + return JNI_ERR; + } - jniUtil.Initialize(env); + jniUtil.Initialize(env); - return KNN_NMSLIB_JNI_VERSION; + return KNN_NMSLIB_JNI_VERSION; } void JNI_OnUnload(JavaVM *vm, void *reserved) { - JNIEnv* env; - vm->GetEnv((void**)&env, KNN_NMSLIB_JNI_VERSION); - jniUtil.Uninitialize(env); + JNIEnv *env; + vm->GetEnv((void **) &env, KNN_NMSLIB_JNI_VERSION); + jniUtil.Uninitialize(env); } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_createIndex(JNIEnv * env, jclass cls, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, jstring indexPathJ, - jobject parametersJ) -{ - try { - knn_jni::nmslib_wrapper::CreateIndex(&jniUtil, env, idsJ, vectorsAddressJ, dimJ, indexPathJ, parametersJ); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_createIndex(JNIEnv *env, + jclass cls, + jintArray idsJ, + jlong vectorsAddressJ, + jint dimJ, + jstring indexPathJ, + jobject parametersJ) { + try { + knn_jni::nmslib_wrapper::CreateIndex(&jniUtil, env, idsJ, vectorsAddressJ, dimJ, indexPathJ, parametersJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } } -JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_NmslibService_loadIndex(JNIEnv * env, jclass cls, - jstring indexPathJ, jobject parametersJ) -{ - try { - return knn_jni::nmslib_wrapper::LoadIndex(&jniUtil, env, indexPathJ, parametersJ); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } - return NULL; +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_NmslibService_loadIndex(JNIEnv *env, jclass cls, + jstring indexPathJ, jobject parametersJ) { + try { + return knn_jni::nmslib_wrapper::LoadIndex(&jniUtil, env, indexPathJ, parametersJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return NULL; } -JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_NmslibService_queryIndex(JNIEnv * env, jclass cls, +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_NmslibService_loadIndexWithStream(JNIEnv *env, + jclass cls, + jobject readStream, + jobject parametersJ) { + try { + return knn_jni::nmslib_wrapper::LoadIndexWithStream(&jniUtil, env, readStream, parametersJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return NULL; +} + +JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_NmslibService_queryIndex(JNIEnv *env, + jclass cls, jlong indexPointerJ, - jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ) -{ - try { - return knn_jni::nmslib_wrapper::QueryIndex(&jniUtil, env, indexPointerJ, queryVectorJ, kJ, methodParamsJ); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } - return nullptr; + jfloatArray queryVectorJ, + jint kJ, + jobject methodParamsJ) { + try { + return knn_jni::nmslib_wrapper::QueryIndex(&jniUtil, env, indexPointerJ, queryVectorJ, kJ, methodParamsJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return nullptr; } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_free(JNIEnv * env, jclass cls, jlong indexPointerJ) -{ - try { - return knn_jni::nmslib_wrapper::Free(indexPointerJ); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_free(JNIEnv *env, jclass cls, jlong indexPointerJ) { + try { + return knn_jni::nmslib_wrapper::Free(indexPointerJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_initLibrary(JNIEnv * env, jclass cls) -{ - try { - knn_jni::nmslib_wrapper::InitLibrary(); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_initLibrary(JNIEnv *env, jclass cls) { + try { + knn_jni::nmslib_wrapper::InitLibrary(); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } } diff --git a/jni/tests/faiss_stream_support_test.cpp b/jni/tests/faiss_stream_support_test.cpp index 4045985bb..94a9b3991 100644 --- a/jni/tests/faiss_stream_support_test.cpp +++ b/jni/tests/faiss_stream_support_test.cpp @@ -8,83 +8,49 @@ // GitHub history for details. #include "faiss_stream_support.h" -#include +#include "native_stream_support_util.h" #include "test_util.h" + +#include #include -#include #include #include +using ::testing::_; using ::testing::Return; using knn_jni::stream::FaissOpenSearchIOReader; using knn_jni::stream::NativeEngineIndexInputMediator; using test_util::MockJNIUtil; - -// Mocking IndexInputWithBuffer. -struct JavaIndexInputMock { - JavaIndexInputMock(std::string _readTargetBytes, int32_t _bufSize) - : readTargetBytes(std::move(_readTargetBytes)), - nextReadIdx(), - buffer(_bufSize) { - } - - // This method is simulating `copyBytes` in IndexInputWithBuffer. - int32_t simulateCopyReads(int64_t readBytes) { - readBytes = std::min(readBytes, (int64_t) buffer.size()); - readBytes = std::min(readBytes, (int64_t) (readTargetBytes.size() - nextReadIdx)); - std::memcpy(buffer.data(), readTargetBytes.data() + nextReadIdx, readBytes); - nextReadIdx += readBytes; - return (int32_t) readBytes; - } - - static std::string makeRandomBytes(int32_t bytesSize) { - // Define the list of possible characters - static const string CHARACTERS - = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv" - "wxyz0123456789"; - - // Create a random number generator - std::random_device rd; - std::mt19937 generator(rd()); - - // Create a distribution to uniformly select from all characters - std::uniform_int_distribution<> distribution( - 0, CHARACTERS.size() - 1); - - // Pre-allocate the string with the desired length - std::string randomString(bytesSize, '\0'); - - // Use generate_n with a back_inserter iterator - std::generate_n(randomString.begin(), bytesSize, [&]() { - return CHARACTERS[distribution(generator)]; - }); - - return randomString; - } - - std::string readTargetBytes; - int64_t nextReadIdx; - std::vector buffer; -}; // struct JavaIndexInputMock +using test_util::JavaIndexInputMock; +using ::testing::NiceMock; +using ::testing::Return; void setUpMockJNIUtil(JavaIndexInputMock &javaIndexInputMock, MockJNIUtil &mockJni) { // Set up mocking values + mocking behavior in a method. - ON_CALL(mockJni, FindClassFromJNIEnv).WillByDefault(Return((jclass) 1)); - ON_CALL(mockJni, GetMethodID).WillByDefault(Return((jmethodID) 1)); - ON_CALL(mockJni, GetFieldID).WillByDefault(Return((jfieldID) 1)); - ON_CALL(mockJni, GetObjectField).WillByDefault(Return((jobject) 1)); - ON_CALL(mockJni, CallIntMethodLong).WillByDefault([&javaIndexInputMock](JNIEnv *env, - jobject obj, - jmethodID methodID, - int64_t longArg) { - return javaIndexInputMock.simulateCopyReads(longArg); - }); - ON_CALL(mockJni, GetPrimitiveArrayCritical).WillByDefault([&javaIndexInputMock](JNIEnv *env, - jarray array, - jboolean *isCopy) { - return (jbyte *) javaIndexInputMock.buffer.data(); - }); - ON_CALL(mockJni, ReleasePrimitiveArrayCritical).WillByDefault(Return()); + EXPECT_CALL(mockJni, CallNonvirtualIntMethodA(_, _, _, _, _)) + .WillRepeatedly([&javaIndexInputMock](JNIEnv *env, + jobject obj, + jclass clazz, + jmethodID methodID, + jvalue* args) { + return javaIndexInputMock.simulateCopyReads(args[0].j); + }); + EXPECT_CALL(mockJni, CallNonvirtualLongMethodA(_, _, _, _, _)) + .WillRepeatedly([&javaIndexInputMock](JNIEnv *env, + jobject obj, + jclass clazz, + jmethodID methodID, + jvalue* args) { + return javaIndexInputMock.remainingBytes(); + }); + EXPECT_CALL(mockJni, GetPrimitiveArrayCritical(_, _, _)) + .WillRepeatedly([&javaIndexInputMock](JNIEnv *env, + jarray array, + jboolean *isCopy) { + return (jbyte *) javaIndexInputMock.buffer.data(); + }); + EXPECT_CALL(mockJni, ReleasePrimitiveArrayCritical(_, _, _, _)) + .WillRepeatedly(Return()); } TEST(FaissStreamSupportTest, NativeEngineIndexInputMediatorCopyWhenEmpty) { @@ -110,7 +76,7 @@ TEST(FaissStreamSupportTest, NativeEngineIndexInputMediatorCopyWhenEmpty) { TEST(FaissStreamSupportTest, FaissOpenSearchIOReaderCopy) { for (auto contentSize : std::vector{0, 2222, 7777, 1024, 77, 1}) { // Set up mockings - MockJNIUtil mockJni; + NiceMock mockJni; JavaIndexInputMock javaIndexInputMock{ JavaIndexInputMock::makeRandomBytes(contentSize), 1024}; setUpMockJNIUtil(javaIndexInputMock, mockJni); diff --git a/jni/tests/native_stream_support_util.h b/jni/tests/native_stream_support_util.h new file mode 100644 index 000000000..e33f3beb4 --- /dev/null +++ b/jni/tests/native_stream_support_util.h @@ -0,0 +1,102 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef KNNPLUGIN_JNI_TESTS_NATIVE_STREAM_SUPPORT_UTIL_H_ +#define KNNPLUGIN_JNI_TESTS_NATIVE_STREAM_SUPPORT_UTIL_H_ + +#include "test_util.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace test_util { + + +// Mocking IndexInputWithBuffer. +struct JavaIndexInputMock { + JavaIndexInputMock(std::string _readTargetBytes, int32_t _bufSize) + : readTargetBytes(std::move(_readTargetBytes)), + nextReadIdx(), + buffer(_bufSize) { + } + + // This method is simulating `copyBytes` in IndexInputWithBuffer. + int32_t simulateCopyReads(int64_t readBytes) { + readBytes = std::min(readBytes, (int64_t) buffer.size()); + readBytes = std::min(readBytes, (int64_t) (readTargetBytes.size() - nextReadIdx)); + std::memcpy(buffer.data(), readTargetBytes.data() + nextReadIdx, readBytes); + nextReadIdx += readBytes; + return (int32_t) readBytes; + } + + int64_t remainingBytes() { + return readTargetBytes.size() - nextReadIdx; + } + + static std::string makeRandomBytes(int32_t bytesSize) { + // Define the list of possible characters + static const string CHARACTERS + = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv" + "wxyz0123456789"; + + // Create a random number generator + std::random_device rd; + std::mt19937 generator(rd()); + + // Create a distribution to uniformly select from all characters + std::uniform_int_distribution<> distribution( + 0, CHARACTERS.size() - 1); + + // Pre-allocate the string with the desired length + std::string randomString(bytesSize, '\0'); + + // Use generate_n with a back_inserter iterator + std::generate_n(randomString.begin(), bytesSize, [&]() { + return CHARACTERS[distribution(generator)]; + }); + + return randomString; + } + + std::string readTargetBytes; + int64_t nextReadIdx; + std::vector buffer; +}; // struct JavaIndexInputMock + + + +struct JavaFileIndexInputMock { + JavaFileIndexInputMock(std::ifstream &_file_input, int32_t _buf_size) + : file_input(_file_input), + buffer(_buf_size) { + } + + int64_t remainingBytes() { + std::streampos currentPos = file_input.tellg(); + file_input.seekg(0, std::ios::end); + std::streamsize fileSize = file_input.tellg(); + file_input.seekg(currentPos); + return fileSize - currentPos; + } + + int32_t copyBytes(int64_t read_size) { + const auto copy_size = std::min((int64_t) buffer.size(), read_size); + file_input.read(buffer.data(), copy_size); + return (int32_t) copy_size; + } + + std::ifstream &file_input; + std::vector buffer; +}; // class JavaFileIndexInputMock + + +} // namespace test_util + +#endif //KNNPLUGIN_JNI_TESTS_NATIVE_STREAM_SUPPORT_UTIL_H_ diff --git a/jni/tests/nmslib_stream_support_test.cpp b/jni/tests/nmslib_stream_support_test.cpp new file mode 100644 index 000000000..e0e7a2d08 --- /dev/null +++ b/jni/tests/nmslib_stream_support_test.cpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// The OpenSearch Contributors require contributions made to +// this file be licensed under the Apache-2.0 license or a +// compatible open source license. +// +// Modifications Copyright OpenSearch Contributors. See +// GitHub history for details. + +#include "nmslib_wrapper.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "jni_util.h" +#include "test_util.h" +#include "native_stream_support_util.h" + +using ::testing::_; +using ::testing::NiceMock; +using ::testing::Return; +using ::test_util::MockJNIUtil; +using ::test_util::JavaFileIndexInputMock; + +void setUpJavaFileInputMocking(JavaFileIndexInputMock &java_index_input, MockJNIUtil &mockJni) { + // Set up mocking values + mocking behavior in a method. + EXPECT_CALL(mockJni, CallNonvirtualIntMethodA(_, _, _, _, _)) + .WillRepeatedly([&java_index_input](JNIEnv *env, + jobject obj, + jclass clazz, + jmethodID methodID, + jvalue *args) { + return java_index_input.copyBytes(args[0].j); + }); + EXPECT_CALL(mockJni, CallNonvirtualLongMethodA(_, _, _, _, _)) + .WillRepeatedly([&java_index_input](JNIEnv *env, + jobject obj, + jclass clazz, + jmethodID methodID, + jvalue *args) { + return java_index_input.remainingBytes(); + }); + EXPECT_CALL(mockJni, GetPrimitiveArrayCritical(_, _, _)).WillRepeatedly([&java_index_input](JNIEnv *env, + jarray array, + jboolean *isCopy) { + return (jbyte *) java_index_input.buffer.data(); + }); + EXPECT_CALL(mockJni, ReleasePrimitiveArrayCritical(_, _, _, _)).WillRepeatedly(Return()); +} + +TEST(NmslibStreamLoadingTest, BasicAssertions) { + // Initialize nmslib + similarity::initLibrary(); + + // Define index data + int numIds = 100; + std::vector ids; + auto vectors = new std::vector(); + int dim = 2; + vectors->reserve(dim * numIds); + for (int i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { + vectors->push_back(test_util::RandomFloat(-500.0, 500.0)); + } + } + + std::string spaceType = knn_jni::L2; + std::string indexPath = test_util::RandomString( + 10, "/tmp/", ".nmslib"); + + std::unordered_map parametersMap; + int efConstruction = 512; + int m = 96; + + parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + parametersMap[knn_jni::EF_CONSTRUCTION] = (jobject) &efConstruction; + parametersMap[knn_jni::M] = (jobject) &m; + + // Set up jni + JNIEnv *jniEnv = nullptr; + NiceMock mockJNIUtil; + + EXPECT_CALL(mockJNIUtil, + GetJavaObjectArrayLength( + jniEnv, reinterpret_cast(vectors))) + .WillRepeatedly(Return(vectors->size())); + + EXPECT_CALL(mockJNIUtil, + GetJavaIntArrayLength(jniEnv, reinterpret_cast(&ids))) + .WillRepeatedly(Return(ids.size())); + + EXPECT_CALL(mockJNIUtil, + ConvertJavaMapToCppMap(jniEnv, reinterpret_cast(¶metersMap))) + .WillRepeatedly(Return(parametersMap)); + + // Create the index + knn_jni::nmslib_wrapper::CreateIndex( + &mockJNIUtil, jniEnv, reinterpret_cast(&ids), + (jlong) vectors, dim, (jstring) &indexPath, + (jobject) ¶metersMap); + + // Create Java index input mock. + std::ifstream file_input{indexPath, std::ios::binary}; + const int32_t buffer_size = 128; + JavaFileIndexInputMock java_file_index_input_mock{file_input, buffer_size}; + setUpJavaFileInputMocking(java_file_index_input_mock, mockJNIUtil); + + // Make sure index can be loaded + jlong index = knn_jni::nmslib_wrapper::LoadIndexWithStream( + &mockJNIUtil, jniEnv, + (jobject) (&java_file_index_input_mock), + (jobject) (¶metersMap)); + + knn_jni::nmslib_wrapper::Free(index); + + // Clean up + std::remove(indexPath.c_str()); +} diff --git a/jni/tests/test_util.h b/jni/tests/test_util.h index 286000c08..a6b39aa41 100644 --- a/jni/tests/test_util.h +++ b/jni/tests/test_util.h @@ -111,7 +111,8 @@ namespace test_util { MOCK_METHOD(jclass, FindClassFromJNIEnv, (JNIEnv * env, const char *name)); MOCK_METHOD(jmethodID, GetMethodID, (JNIEnv * env, jclass clazz, const char *name, const char *sig)); MOCK_METHOD(jfieldID, GetFieldID, (JNIEnv * env, jclass clazz, const char *name, const char *sig)); - MOCK_METHOD(jint, CallIntMethodLong, (JNIEnv * env, jobject obj, jmethodID methodID, int64_t longArg)); + MOCK_METHOD(jint, CallNonvirtualIntMethodA, (JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args)); + MOCK_METHOD(jlong, CallNonvirtualLongMethodA, (JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args)); MOCK_METHOD(void *, GetPrimitiveArrayCritical, (JNIEnv * env, jarray array, jboolean *isCopy)); MOCK_METHOD(void, ReleasePrimitiveArrayCritical, (JNIEnv * env, jarray array, void *carray, jint mode)); }; diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java index 8adf35447..360c827f9 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java @@ -187,8 +187,8 @@ class IndexAllocation implements NativeMemoryAllocation { protected void closeInternal() { Runnable onClose = () -> { + writeLock(); try { - writeLock(); cleanup(); } finally { writeUnlock(); @@ -328,8 +328,8 @@ public TrainingDataAllocation(ExecutorService executor, long memoryAddress, int @Override public void close() { executor.execute(() -> { + writeLock(); try { - writeLock(); cleanup(); } finally { writeUnlock(); diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java index 51158d00c..5daa1e047 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java @@ -91,36 +91,11 @@ public void onFileDeleted(Path indexFilePath) { }; } - private NativeMemoryAllocation.IndexAllocation loadWithAbsoluteIndexPath( - NativeMemoryEntryContext.IndexEntryContext indexEntryContext - ) throws IOException { - Path indexPath = Paths.get(indexEntryContext.getKey()); - FileWatcher fileWatcher = new FileWatcher(indexPath); - fileWatcher.addListener(indexFileOnDeleteListener); - fileWatcher.init(); - - KNNEngine knnEngine = KNNEngine.getEngineNameFromPath(indexPath.toString()); - long indexAddress = JNIService.loadIndex(indexPath.toString(), indexEntryContext.getParameters(), knnEngine); - return createIndexAllocation( - indexEntryContext, - knnEngine, - indexAddress, - fileWatcher, - indexEntryContext.calculateSizeInKB(), - indexPath - ); - } - @Override public NativeMemoryAllocation.IndexAllocation load(NativeMemoryEntryContext.IndexEntryContext indexEntryContext) throws IOException { final Path absoluteIndexPath = Paths.get(indexEntryContext.getKey()); final KNNEngine knnEngine = KNNEngine.getEngineNameFromPath(absoluteIndexPath.toString()); - if (knnEngine != KNNEngine.FAISS) { - // We will support other non-FAISS native engines (ex: NMSLIB) soon. - return loadWithAbsoluteIndexPath(indexEntryContext); - } - final FileWatcher fileWatcher = new FileWatcher(absoluteIndexPath); fileWatcher.addListener(indexFileOnDeleteListener); fileWatcher.init(); @@ -182,7 +157,7 @@ class TrainingLoadStrategy NativeMemoryLoadStrategy, Closeable { - private static TrainingLoadStrategy INSTANCE; + private static volatile TrainingLoadStrategy INSTANCE; private final ExecutorService executor; private VectorReader vectorReader; diff --git a/src/main/java/org/opensearch/knn/index/store/IndexInputWithBuffer.java b/src/main/java/org/opensearch/knn/index/store/IndexInputWithBuffer.java index 273a4deac..0b1c934e5 100644 --- a/src/main/java/org/opensearch/knn/index/store/IndexInputWithBuffer.java +++ b/src/main/java/org/opensearch/knn/index/store/IndexInputWithBuffer.java @@ -18,11 +18,13 @@ */ public class IndexInputWithBuffer { private IndexInput indexInput; - // 4K buffer. - private byte[] buffer = new byte[4 * 1024]; + private long contentLength; + // 64K buffer. + private byte[] buffer = new byte[64 * 1024]; public IndexInputWithBuffer(@NonNull IndexInput indexInput) { this.indexInput = indexInput; + this.contentLength = indexInput.length(); } /** @@ -39,6 +41,10 @@ private int copyBytes(long nbytes) throws IOException { return readBytes; } + private long remainingBytes() { + return contentLength - indexInput.getFilePointer(); + } + @Override public String toString() { return "{indexInput=" + indexInput + ", len(buffer)=" + buffer.length + "}"; diff --git a/src/main/java/org/opensearch/knn/jni/JNIService.java b/src/main/java/org/opensearch/knn/jni/JNIService.java index 448241f9c..a0daf65a7 100644 --- a/src/main/java/org/opensearch/knn/jni/JNIService.java +++ b/src/main/java/org/opensearch/knn/jni/JNIService.java @@ -227,6 +227,8 @@ public static long loadIndex(IndexInputWithBuffer readStream, Map parameters); + /** + * Load an index into memory through the provided read stream wrapping Lucene's IndexInput. + * + * @param readStream Read stream wrapping Lucene's IndexInput. + * @param parameters Parameters to be used when loading index + * @return Pointer to location in memory the index resides in + */ + public static native long loadIndexWithStream(IndexInputWithBuffer readStream, Map parameters); + /** * Query an index * diff --git a/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java b/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java index f7f68eda1..935a2e22c 100644 --- a/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java +++ b/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java @@ -48,7 +48,7 @@ private void tripCb() throws Exception { createKnnIndex(indexName2, settings, createKnnIndexMapping(FIELD_NAME, 2)); Float[] vector = { 1.3f, 2.2f }; - int docsInIndex = 5; // through testing, 7 is minimum number of docs to trip circuit breaker at 1kb + int docsInIndex = 7; // through testing, 7 is minimum number of docs to trip circuit breaker at 1kb for (int i = 0; i < docsInIndex; i++) { addKnnDoc(indexName1, Integer.toString(i), FIELD_NAME, vector); diff --git a/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java b/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java index 8566b0223..f6d118092 100644 --- a/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java +++ b/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java @@ -749,6 +749,31 @@ public void testLoadIndex_nmslib_valid() throws IOException { assertNotEquals(0, pointer); } + public void testLoadIndex_nmslib_valid_with_stream() throws IOException { + Path tmpFile = createTempFile(); + + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + tmpFile.toAbsolutePath().toString(), + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(tmpFile.toFile().length() > 0); + + try (final Directory directory = new MMapDirectory(tmpFile.getParent())) { + try (IndexInput indexInput = directory.openInput(tmpFile.getFileName().toString(), IOContext.READONCE)) { + long pointer = JNIService.loadIndex( + new IndexInputWithBuffer(indexInput), + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertNotEquals(0, pointer); + } + } + } + public void testLoadIndex_faiss_invalid_fileDoesNotExist() { expectThrows(Exception.class, () -> JNIService.loadIndex("invalid", Collections.emptyMap(), KNNEngine.FAISS)); } From 228aead4b016f355be6f91934777bda8ccd895a0 Mon Sep 17 00:00:00 2001 From: John Mazanec Date: Wed, 16 Oct 2024 14:00:15 -0400 Subject: [PATCH 44/59] Add CompressionLevel Calculation for PQ (#2200) Currently, for product quantization, we set the calculated compression level to NOT_CONFIGURED. The main issue with this is that if a user sets up a disk-based index with PQ, no re-scoring will happen by default. This change adds the calculation so that the proper re-scoring will happen. The formula is fairly straightforward => actual compression = (d * 32) / (m * code_size). Then, we round to the neareste compression level (because we only support discrete compression levels). One small issue with this is that if PQ is configured to have compression > 64x, the value will be 64x. Functionally, the only issue will be that we may not be as aggressive on oversampling for on disk mode. Signed-off-by: John Mazanec --- CHANGELOG.md | 1 + .../engine/faiss/AbstractFaissPQEncoder.java | 92 +++++++++++++++++++ .../engine/faiss/FaissHNSWPQEncoder.java | 15 +-- .../index/engine/faiss/FaissIVFPQEncoder.java | 19 +--- .../knn/index/mapper/CompressionLevel.java | 14 +-- .../index/mapper/KNNVectorFieldMapper.java | 14 ++- .../org/opensearch/knn/index/FaissIT.java | 3 +- .../faiss/AbstractFaissPQEncoderTests.java | 79 ++++++++++++++++ .../engine/faiss/FaissHNSWPQEncoderTests.java | 16 ---- .../engine/faiss/FaissIVFPQEncoderTests.java | 16 ---- .../knn/integ/ModeAndCompressionIT.java | 3 +- 11 files changed, 197 insertions(+), 75 deletions(-) create mode 100644 src/main/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoder.java create mode 100644 src/test/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoderTests.java delete mode 100644 src/test/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoderTests.java delete mode 100644 src/test/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoderTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ea7c67943..270c73c3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * KNNIterators should support with and without filters [#2155](https://github.com/opensearch-project/k-NN/pull/2155) * Adding Support to Enable/Disble Share level Rescoring and Update Oversampling Factor[#2172](https://github.com/opensearch-project/k-NN/pull/2172) * Add support to build vector data structures greedily and perform exact search when there are no engine files [#1942](https://github.com/opensearch-project/k-NN/issues/1942) +* Add CompressionLevel Calculation for PQ [#2200](https://github.com/opensearch-project/k-NN/pull/2200) ### Bug Fixes * Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) * KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoder.java new file mode 100644 index 000000000..a894d8ed6 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoder.java @@ -0,0 +1,92 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.CompressionLevel; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; + +/** + * Abstract class for Faiss PQ encoders. This class provides the common logic for product quantization based encoders + */ +public abstract class AbstractFaissPQEncoder implements Encoder { + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + // Roughly speaking, PQ can be configured to produce a lot of different compression levels. The "m" parameter + // specifies how many sub-vectors to break the vector up into, and then the "code_size" represents the number + // of bits to encode each subvector. Thus, a d-dimensional vector of float32s goes from + // d*32 -> (m)*code_size bits. So if we want (d*32)/(m*code_size) will be the compression level. + // + // Example: + // d=768, m=384, code_size=8 + // (768*32)/(384*8) = 8x (i.e. 24,576 vs. 3,072). + // + // Because of this variability, we will need to properly round to one of the supported values. + if (methodComponentContext.getParameters().containsKey(ENCODER_PARAMETER_PQ_M) == false + || methodComponentContext.getParameters().containsKey(ENCODER_PARAMETER_PQ_CODE_SIZE) == false) { + return CompressionLevel.NOT_CONFIGURED; + } + + // Map the number of bits passed in, back to the compression level + Object value = methodComponentContext.getParameters().get(ENCODER_PARAMETER_PQ_M); + ValidationException validationException = getMethodComponent().getParameters() + .get(ENCODER_PARAMETER_PQ_M) + .validate(value, knnMethodConfigContext); + if (validationException != null) { + throw validationException; + } + Integer m = (Integer) value; + value = methodComponentContext.getParameters().get(ENCODER_PARAMETER_PQ_CODE_SIZE); + validationException = getMethodComponent().getParameters() + .get(ENCODER_PARAMETER_PQ_CODE_SIZE) + .validate(value, knnMethodConfigContext); + if (validationException != null) { + throw validationException; + } + Integer codeSize = (Integer) value; + int dimension = knnMethodConfigContext.getDimension(); + + float actualCompression = ((float) dimension * 32) / (m * codeSize); + + if (actualCompression < 2.0f) { + return CompressionLevel.x1; + } + + if (actualCompression < 4.0f) { + return CompressionLevel.x2; + } + + if (actualCompression < 8.0f) { + return CompressionLevel.x4; + } + + if (actualCompression < 16.0f) { + return CompressionLevel.x8; + } + + if (actualCompression < 32.0f) { + return CompressionLevel.x16; + } + + if (actualCompression < 64.0f) { + return CompressionLevel.x32; + } + + // TODO: The problem is that the theoretical compression level of PQ can be in the thousands. Thus, Im not sure + // it makes sense to have an enum all the way up to that value. So, for now, we will just return the max + // compression + return CompressionLevel.MAX_COMPRESSION_LEVEL; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoder.java index 6750d84ed..c22a9dec7 100644 --- a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoder.java +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoder.java @@ -8,12 +8,8 @@ import com.google.common.collect.ImmutableSet; import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.VectorDataType; -import org.opensearch.knn.index.engine.Encoder; -import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.MethodComponent; -import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.engine.Parameter; -import org.opensearch.knn.index.mapper.CompressionLevel; import java.util.Objects; import java.util.Set; @@ -30,7 +26,7 @@ * Faiss HNSW PQ encoder. Right now, the implementations are slightly different during validation between this an * {@link FaissIVFPQEncoder}. Hence, they are separate classes. */ -public class FaissHNSWPQEncoder implements Encoder { +public class FaissHNSWPQEncoder extends AbstractFaissPQEncoder { private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of(VectorDataType.FLOAT); @@ -72,13 +68,4 @@ public class FaissHNSWPQEncoder implements Encoder { public MethodComponent getMethodComponent() { return METHOD_COMPONENT; } - - @Override - public CompressionLevel calculateCompressionLevel( - MethodComponentContext methodComponentContext, - KNNMethodConfigContext knnMethodConfigContext - ) { - // TODO: For now, not supported out of the box - return CompressionLevel.NOT_CONFIGURED; - } } diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoder.java index 8d54548bd..8c10aebdf 100644 --- a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoder.java +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoder.java @@ -5,15 +5,12 @@ package org.opensearch.knn.index.engine.faiss; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.VectorDataType; -import org.opensearch.knn.index.engine.Encoder; -import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.MethodComponent; -import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.engine.Parameter; -import org.opensearch.knn.index.mapper.CompressionLevel; import java.util.Set; @@ -30,11 +27,12 @@ * Faiss IVF PQ encoder. Right now, the implementations are slightly different during validation between this an * {@link FaissHNSWPQEncoder}. Hence, they are separate classes. */ -public class FaissIVFPQEncoder implements Encoder { +public class FaissIVFPQEncoder extends AbstractFaissPQEncoder { private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of(VectorDataType.FLOAT); - private final static MethodComponent METHOD_COMPONENT = MethodComponent.Builder.builder(KNNConstants.ENCODER_PQ) + @VisibleForTesting + final static MethodComponent METHOD_COMPONENT = MethodComponent.Builder.builder(KNNConstants.ENCODER_PQ) .addSupportedDataTypes(SUPPORTED_DATA_TYPES) .addParameter( ENCODER_PARAMETER_PQ_M, @@ -93,13 +91,4 @@ public class FaissIVFPQEncoder implements Encoder { public MethodComponent getMethodComponent() { return METHOD_COMPONENT; } - - @Override - public CompressionLevel calculateCompressionLevel( - MethodComponentContext methodComponentContext, - KNNMethodConfigContext knnMethodConfigContext - ) { - // TODO: For now, not supported out of the box - return CompressionLevel.NOT_CONFIGURED; - } } diff --git a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java index ab583a2e0..99f74c246 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java +++ b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java @@ -27,18 +27,10 @@ public enum CompressionLevel { x4(4, "4x", null, Collections.emptySet()), x8(8, "8x", new RescoreContext(2.0f, false), Set.of(Mode.ON_DISK)), x16(16, "16x", new RescoreContext(3.0f, false), Set.of(Mode.ON_DISK)), - x32(32, "32x", new RescoreContext(3.0f, false), Set.of(Mode.ON_DISK)); + x32(32, "32x", new RescoreContext(3.0f, false), Set.of(Mode.ON_DISK)), + x64(64, "64x", new RescoreContext(5.0f, false), Set.of(Mode.ON_DISK)); - // Internally, an empty string is easier to deal with them null. However, from the mapping, - // we do not want users to pass in the empty string and instead want null. So we make the conversion here - public static final String[] NAMES_ARRAY = new String[] { - NOT_CONFIGURED.getName(), - x1.getName(), - x2.getName(), - x4.getName(), - x8.getName(), - x16.getName(), - x32.getName() }; + public static final CompressionLevel MAX_COMPRESSION_LEVEL = CompressionLevel.x64; /** * Default is set to 1x and is a noop diff --git a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java index 6e5138a56..18d4f7b64 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java +++ b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java @@ -15,6 +15,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import com.google.common.annotations.VisibleForTesting; import lombok.Getter; import lombok.Setter; import lombok.extern.log4j.Log4j2; @@ -75,6 +76,17 @@ private static KNNVectorFieldMapper toType(FieldMapper in) { return (KNNVectorFieldMapper) in; } + // Supported compression levels for knn_vector field type + @VisibleForTesting + public static final String[] MAPPING_COMPRESSION_NAMES_ARRAY = new String[] { + CompressionLevel.NOT_CONFIGURED.getName(), + CompressionLevel.x1.getName(), + CompressionLevel.x2.getName(), + CompressionLevel.x4.getName(), + CompressionLevel.x8.getName(), + CompressionLevel.x16.getName(), + CompressionLevel.x32.getName() }; + /** * Builder for KNNVectorFieldMapper. This class defines the set of parameters that can be applied to the knn_vector * field type @@ -161,7 +173,7 @@ public static class Builder extends ParametrizedFieldMapper.Builder { KNNConstants.COMPRESSION_LEVEL_PARAMETER, false, m -> toType(m).originalMappingParameters.getCompressionLevel(), - CompressionLevel.NAMES_ARRAY + MAPPING_COMPRESSION_NAMES_ARRAY ).acceptsNull(); // A top level space Type field. diff --git a/src/test/java/org/opensearch/knn/index/FaissIT.java b/src/test/java/org/opensearch/knn/index/FaissIT.java index c494f7f1f..f6aef8cb1 100644 --- a/src/test/java/org/opensearch/knn/index/FaissIT.java +++ b/src/test/java/org/opensearch/knn/index/FaissIT.java @@ -1714,7 +1714,8 @@ public void testIVF_InvalidPQM_thenFail() { () -> ingestDataAndTrainModel(modelId, trainingIndexName, trainingFieldName, dimension, modelDescription, in, trainingDataCount) ); assertTrue( - re.getMessage().contains("Validation Failed: 1: parameter validation failed for MethodComponentContext parameter [encoder].;") + re.getMessage(), + re.getMessage().contains("Validation Failed: 1: parameter validation failed for Integer parameter [m].;") ); } diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoderTests.java new file mode 100644 index 000000000..704657c11 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoderTests.java @@ -0,0 +1,79 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import lombok.SneakyThrows; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.CompressionLevel; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PQ; + +public class AbstractFaissPQEncoderTests extends KNNTestCase { + + @SneakyThrows + public void testCalculateCompressionLevel() { + AbstractFaissPQEncoder encoder = new AbstractFaissPQEncoder() { + @Override + public MethodComponent getMethodComponent() { + return FaissIVFPQEncoder.METHOD_COMPONENT; + } + }; + + // Compression formula is: + // actual_compression = (d*32)/(m*code_size) and then round down to nearest: 1x, 2x, 4x, 8x, 16x, 32x + + // d=768 + // m=2 + // code_size=8 + // actual_compression = (768*32)/(2*8) = 1,536x + // expected_compression = Max compression level + assertCompressionLevel(2, 8, 768, CompressionLevel.MAX_COMPRESSION_LEVEL, encoder); + + // d=32 + // m=4 + // code_size=16 + // actual_compression = (32*32)/(4*16) = 16x + // expected_compression = Max compression level + assertCompressionLevel(4, 16, 32, CompressionLevel.x16, encoder); + + // d=1536 + // m=768 + // code_size=8 + // actual_compression = (1536*32)/(768*8) = 8x + // expected_compression = Max compression level + assertCompressionLevel(768, 8, 1536, CompressionLevel.x8, encoder); + + // d=128 + // m=128 + // code_size=8 + // actual_compression = (128*32)/(128*8) = 4x + // expected_compression = Max compression level + assertCompressionLevel(128, 8, 128, CompressionLevel.x4, encoder); + } + + private void assertCompressionLevel(int m, int codeSize, int d, CompressionLevel expectedCompression, Encoder encoder) { + assertEquals( + expectedCompression, + encoder.calculateCompressionLevel(generateMethodComponentContext(m, codeSize), generateKNNMethodConfigContext(d)) + ); + } + + private MethodComponentContext generateMethodComponentContext(int m, int codeSize) { + return new MethodComponentContext(ENCODER_PQ, Map.of(ENCODER_PARAMETER_PQ_M, m, ENCODER_PARAMETER_PQ_CODE_SIZE, codeSize)); + } + + private KNNMethodConfigContext generateKNNMethodConfigContext(int dimension) { + return KNNMethodConfigContext.builder().dimension(dimension).build(); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoderTests.java deleted file mode 100644 index 3f7dd9dcd..000000000 --- a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoderTests.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index.engine.faiss; - -import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.mapper.CompressionLevel; - -public class FaissHNSWPQEncoderTests extends KNNTestCase { - public void testCalculateCompressionLevel() { - FaissHNSWPQEncoder encoder = new FaissHNSWPQEncoder(); - assertEquals(CompressionLevel.NOT_CONFIGURED, encoder.calculateCompressionLevel(null, null)); - } -} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoderTests.java deleted file mode 100644 index 35b7a64ab..000000000 --- a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoderTests.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index.engine.faiss; - -import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.mapper.CompressionLevel; - -public class FaissIVFPQEncoderTests extends KNNTestCase { - public void testCalculateCompressionLevel() { - FaissIVFPQEncoder encoder = new FaissIVFPQEncoder(); - assertEquals(CompressionLevel.NOT_CONFIGURED, encoder.calculateCompressionLevel(null, null)); - } -} diff --git a/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java index 8f0f78753..0913d9b36 100644 --- a/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java +++ b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java @@ -39,6 +39,7 @@ import static org.opensearch.knn.common.KNNConstants.TRAIN_FIELD_PARAMETER; import static org.opensearch.knn.common.KNNConstants.TRAIN_INDEX_PARAMETER; import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapper.MAPPING_COMPRESSION_NAMES_ARRAY; public class ModeAndCompressionIT extends KNNRestTestCase { @@ -253,7 +254,7 @@ public void testTraining_whenInvalid_thenFail() { public void testTraining_whenValid_thenSucceed() { setupTrainingIndex(); XContentBuilder builder; - for (String compressionLevel : CompressionLevel.NAMES_ARRAY) { + for (String compressionLevel : MAPPING_COMPRESSION_NAMES_ARRAY) { if (compressionLevel.equals("4x")) { continue; } From f3f8e25c51f9c5363ac89cc484529b12eddcddc8 Mon Sep 17 00:00:00 2001 From: Vikasht34 Date: Thu, 17 Oct 2024 11:23:52 -0700 Subject: [PATCH 45/59] Java Docs Fix for 2.x in Linux Machine (#2190) Signed-off-by: VIKASH TIWARI --- CHANGELOG.md | 1 + .../index/query/rescore/RescoreContext.java | 24 ------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 270c73c3c..08a190919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) * KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) * Score Fix for Binary Quantized Vector and Setting Default value in case of shard level rescoring is disabled for oversampling factor[#2183](https://github.com/opensearch-project/k-NN/pull/2183) +* Java Docs Fix For 2.x[#2190](https://github.com/opensearch-project/k-NN/pull/2190) ### Infrastructure ### Documentation * Fix sed command in DEVELOPER_GUIDE.md to append a new line character '\n'. [#2181](https://github.com/opensearch-project/k-NN/pull/2181) diff --git a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java index 0f8c59499..09aeb7591 100644 --- a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java +++ b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java @@ -43,30 +43,6 @@ public final class RescoreContext { * Flag to track whether the oversample factor is user-provided or default. The Reason to introduce * this is to set default when Shard Level rescoring is false, * else we end up overriding user provided value in NativeEngineKnnVectorQuery - * - * - * This flag is crucial to differentiate between user-defined oversample factors and system-assigned - * default values. The behavior of oversampling logic, especially when shard-level rescoring is disabled, - * depends on whether the user explicitly provided an oversample factor or whether the system is using - * a default value. - * - * When shard-level rescoring is disabled, the system applies dimension-based oversampling logic, - * overriding any default values. However, if the user provides their own oversample factor, the system - * should respect the user’s input and avoid overriding it with the dimension-based logic. - * - * This flag is set to {@code true} when the oversample factor is provided by the user, ensuring - * that their value is not overridden. It is set to {@code false} when the oversample factor is - * determined by system defaults (e.g., through a compression level or automatic logic). The system - * then applies its own oversampling rules if necessary. - * - * Key scenarios: - * - If {@code userProvided} is {@code true} and shard-level rescoring is disabled, the user's - * oversample factor is used as is, without applying the dimension-based logic. - * - If {@code userProvided} is {@code false}, the system applies dimension-based oversampling - * when shard-level rescoring is disabled. - * - * This flag enables flexibility, allowing the system to handle both user-defined and default - * behaviors, ensuring the correct oversampling logic is applied based on the context. */ @Builder.Default private boolean userProvided = true; From e5599aa151cf43700d0ea327ad536cc106e5bb61 Mon Sep 17 00:00:00 2001 From: Doo Yong Kim <0ctopus13prime@gmail.com> Date: Mon, 21 Oct 2024 15:53:37 -0700 Subject: [PATCH 46/59] Remove FileWatcher from KNN (#2182) Signed-off-by: Dooyong Kim --- CHANGELOG.md | 1 + .../opensearch/knn/index/KNNIndexShard.java | 51 +++++---- .../org/opensearch/knn/index/KNNSettings.java | 6 +- .../KNN80Codec/KNN80DocValuesProducer.java | 105 +++++------------- .../NativeEngines990KnnVectorsReader.java | 40 ++++++- .../knn/index/codec/util/KNNCodecUtil.java | 45 ++++++++ .../util/NativeMemoryCacheKeyHelper.java | 45 ++++++++ .../index/memory/NativeMemoryAllocation.java | 56 ++++------ .../memory/NativeMemoryCacheManager.java | 4 +- .../memory/NativeMemoryEntryContext.java | 35 +++--- .../memory/NativeMemoryLoadStrategy.java | 65 ++++------- .../opensearch/knn/index/query/KNNWeight.java | 12 +- .../org/opensearch/knn/jni/JNIService.java | 1 - .../org/opensearch/knn/plugin/KNNPlugin.java | 1 - .../knn/index/KNNIndexShardTests.java | 46 ++++++-- .../KNN80DocValuesProducerTests.java | 10 +- .../knn/index/codec/KNNCodecTestCase.java | 9 -- .../memory/NativeMemoryAllocationTests.java | 46 ++------ .../memory/NativeMemoryCacheManagerTests.java | 27 ++--- .../memory/NativeMemoryEntryContextTests.java | 38 +++---- .../memory/NativeMemoryLoadStrategyTests.java | 16 +-- .../java/org/opensearch/knn/TestUtils.java | 5 + 22 files changed, 337 insertions(+), 327 deletions(-) create mode 100644 src/main/java/org/opensearch/knn/index/codec/util/NativeMemoryCacheKeyHelper.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 08a190919..bf34ebcfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Adding Support to Enable/Disble Share level Rescoring and Update Oversampling Factor[#2172](https://github.com/opensearch-project/k-NN/pull/2172) * Add support to build vector data structures greedily and perform exact search when there are no engine files [#1942](https://github.com/opensearch-project/k-NN/issues/1942) * Add CompressionLevel Calculation for PQ [#2200](https://github.com/opensearch-project/k-NN/pull/2200) +* Remove FSDirectory dependency from native engine constructing side and deprecated FileWatcher [#2182](https://github.com/opensearch-project/k-NN/pull/2182) ### Bug Fixes * Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) * KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) diff --git a/src/main/java/org/opensearch/knn/index/KNNIndexShard.java b/src/main/java/org/opensearch/knn/index/KNNIndexShard.java index 4f339cb4e..0188f488f 100644 --- a/src/main/java/org/opensearch/knn/index/KNNIndexShard.java +++ b/src/main/java/org/opensearch/knn/index/KNNIndexShard.java @@ -12,14 +12,15 @@ import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.SegmentCommitInfo; +import org.apache.lucene.index.SegmentInfo; import org.apache.lucene.index.SegmentReader; import org.apache.lucene.store.Directory; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.store.FilterDirectory; import org.opensearch.common.lucene.Lucene; import org.opensearch.index.engine.Engine; import org.opensearch.index.shard.IndexShard; import org.opensearch.knn.common.FieldInfoExtractor; +import org.opensearch.knn.index.codec.util.NativeMemoryCacheKeyHelper; import org.opensearch.knn.index.engine.qframe.QuantizationConfig; import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; import org.opensearch.knn.index.memory.NativeMemoryAllocation; @@ -29,9 +30,7 @@ import org.opensearch.knn.index.engine.KNNEngine; import java.io.IOException; -import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; @@ -94,14 +93,18 @@ public void warmup() throws IOException { try (Engine.Searcher searcher = indexShard.acquireSearcher("knn-warmup")) { getAllEngineFileContexts(searcher.getIndexReader()).forEach((engineFileContext) -> { try { + final String cacheKey = NativeMemoryCacheKeyHelper.constructCacheKey( + engineFileContext.vectorFileName, + engineFileContext.segmentInfo + ); nativeMemoryCacheManager.get( new NativeMemoryEntryContext.IndexEntryContext( directory, - engineFileContext.getIndexPath(), + cacheKey, NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), getParametersAtLoading( engineFileContext.getSpaceType(), - KNNEngine.getEngineNameFromPath(engineFileContext.getIndexPath()), + KNNEngine.getEngineNameFromPath(engineFileContext.getVectorFileName()), getIndexName(), engineFileContext.getVectorDataType() ), @@ -133,9 +136,13 @@ public void clearCache() { indexAllocation.writeLock(); log.info("[KNN] Evicting index from cache: [{}]", indexName); try (Engine.Searcher searcher = indexShard.acquireSearcher(INDEX_SHARD_CLEAR_CACHE_SEARCHER)) { - getAllEngineFileContexts(searcher.getIndexReader()).forEach( - (engineFileContext) -> nativeMemoryCacheManager.invalidate(engineFileContext.getIndexPath()) - ); + getAllEngineFileContexts(searcher.getIndexReader()).forEach((engineFileContext) -> { + final String cacheKey = NativeMemoryCacheKeyHelper.constructCacheKey( + engineFileContext.vectorFileName, + engineFileContext.segmentInfo + ); + nativeMemoryCacheManager.invalidate(cacheKey); + }); } catch (IOException ex) { log.error("[KNN] Failed to evict index from cache: [{}]", indexName, ex); throw new RuntimeException(ex); @@ -166,7 +173,6 @@ List getEngineFileContexts(IndexReader indexReader, KNNEngine for (LeafReaderContext leafReaderContext : indexReader.leaves()) { SegmentReader reader = Lucene.segmentReader(leafReaderContext.reader()); - Path shardPath = ((FSDirectory) FilterDirectory.unwrap(reader.directory())).getDirectory(); String fileExtension = reader.getSegmentInfo().info.getUseCompoundFile() ? knnEngine.getCompoundExtension() : knnEngine.getExtension(); @@ -180,11 +186,9 @@ List getEngineFileContexts(IndexReader indexReader, KNNEngine String modelId = fieldInfo.attributes().getOrDefault(MODEL_ID, null); engineFiles.addAll( getEngineFileContexts( - reader.getSegmentInfo().files(), - reader.getSegmentInfo().info.name, + reader.getSegmentInfo(), fieldInfo.name, fileExtension, - shardPath, spaceType, modelId, FieldInfoExtractor.extractQuantizationConfig(fieldInfo) == QuantizationConfig.EMPTY @@ -202,22 +206,22 @@ List getEngineFileContexts(IndexReader indexReader, KNNEngine @VisibleForTesting List getEngineFileContexts( - Collection files, - String segmentName, + SegmentCommitInfo segmentCommitInfo, String fieldName, String fileExtension, - Path shardPath, SpaceType spaceType, String modelId, VectorDataType vectorDataType - ) { - String prefix = buildEngineFilePrefix(segmentName); - String suffix = buildEngineFileSuffix(fieldName, fileExtension); - return files.stream() + ) throws IOException { + // Ex: 0_ + final String prefix = buildEngineFilePrefix(segmentCommitInfo.info.name); + // Ex: _my_field.faiss + final String suffix = buildEngineFileSuffix(fieldName, fileExtension); + return segmentCommitInfo.files() + .stream() .filter(fileName -> fileName.startsWith(prefix)) .filter(fileName -> fileName.endsWith(suffix)) - .map(fileName -> shardPath.resolve(fileName).toString()) - .map(fileName -> new EngineFileContext(spaceType, modelId, fileName, vectorDataType)) + .map(vectorFileName -> new EngineFileContext(spaceType, modelId, vectorFileName, vectorDataType, segmentCommitInfo.info)) .collect(Collectors.toList()); } @@ -227,7 +231,8 @@ List getEngineFileContexts( static class EngineFileContext { private final SpaceType spaceType; private final String modelId; - private final String indexPath; + private final String vectorFileName; private final VectorDataType vectorDataType; + private final SegmentInfo segmentInfo; } } diff --git a/src/main/java/org/opensearch/knn/index/KNNSettings.java b/src/main/java/org/opensearch/knn/index/KNNSettings.java index fb9c26fef..6eff61d4b 100644 --- a/src/main/java/org/opensearch/knn/index/KNNSettings.java +++ b/src/main/java/org/opensearch/knn/index/KNNSettings.java @@ -562,8 +562,10 @@ public static boolean isFaissAVX2Disabled() { public static boolean isFaissAVX512Disabled() { return Booleans.parseBoolean( - KNNSettings.state().getSettingValue(KNNSettings.KNN_FAISS_AVX512_DISABLED).toString(), - KNN_DEFAULT_FAISS_AVX512_DISABLED_VALUE + Objects.requireNonNullElse( + KNNSettings.state().getSettingValue(KNNSettings.KNN_FAISS_AVX512_DISABLED), + KNN_DEFAULT_FAISS_AVX512_DISABLED_VALUE + ).toString() ); } diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java index b78566f2e..23c9f3105 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java @@ -11,82 +11,34 @@ package org.opensearch.knn.index.codec.KNN80Codec; -import lombok.NonNull; import lombok.extern.log4j.Log4j2; import org.apache.lucene.codecs.DocValuesProducer; import org.apache.lucene.index.BinaryDocValues; import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.FieldInfo; + +import java.io.IOException; + import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.SegmentReadState; import org.apache.lucene.index.SortedDocValues; import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.index.SortedSetDocValues; -import org.apache.lucene.store.Directory; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.store.FilterDirectory; -import org.opensearch.common.io.PathUtils; -import org.opensearch.knn.common.FieldInfoExtractor; import org.opensearch.knn.index.codec.util.KNNCodecUtil; -import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.codec.util.NativeMemoryCacheKeyHelper; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; -import java.io.IOException; -import java.nio.file.Path; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import static org.opensearch.knn.common.KNNConstants.MODEL_ID; -import static org.opensearch.knn.index.mapper.KNNVectorFieldMapper.KNN_FIELD; @Log4j2 public class KNN80DocValuesProducer extends DocValuesProducer { - - private final SegmentReadState state; private final DocValuesProducer delegate; - private final NativeMemoryCacheManager nativeMemoryCacheManager; - private final Map indexPathMap = new HashMap(); + private List cacheKeys; public KNN80DocValuesProducer(DocValuesProducer delegate, SegmentReadState state) { this.delegate = delegate; - this.state = state; - this.nativeMemoryCacheManager = NativeMemoryCacheManager.getInstance(); - - Directory directory = state.directory; - // directory would be CompoundDirectory, we need get directory firstly and then unwrap - if (state.directory instanceof KNN80CompoundDirectory) { - directory = ((KNN80CompoundDirectory) state.directory).getDir(); - } - - Directory dir = FilterDirectory.unwrap(directory); - if (!(dir instanceof FSDirectory)) { - log.warn("{} can not casting to FSDirectory", directory); - return; - } - String directoryPath = ((FSDirectory) dir).getDirectory().toString(); - for (FieldInfo field : state.fieldInfos) { - if (!field.attributes().containsKey(KNN_FIELD)) { - continue; - } - // Only segments that contains BinaryDocValues and doesn't have vector values should be considered. - // By default, we don't create BinaryDocValues for knn field anymore. However, users can set doc_values = true - // to create binary doc values explicitly like any other field. Hence, we only want to include fields - // where approximate search is possible only by BinaryDocValues. - if (field.getDocValuesType() != DocValuesType.BINARY || field.hasVectorValues() == true) { - continue; - } - // Only Native Engine put into indexPathMap - KNNEngine knnEngine = getNativeKNNEngine(field); - if (knnEngine == null) { - continue; - } - List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), field.name, state.segmentInfo); - Path indexPath = PathUtils.get(directoryPath, engineFiles.get(0)); - indexPathMap.putIfAbsent(field.getName(), indexPath.toString()); - - } + this.cacheKeys = getVectorCacheKeysFromSegmentReaderState(state); } @Override @@ -121,32 +73,35 @@ public void checkIntegrity() throws IOException { @Override public void close() throws IOException { - for (String path : indexPathMap.values()) { - nativeMemoryCacheManager.invalidate(path); - } + final NativeMemoryCacheManager nativeMemoryCacheManager = NativeMemoryCacheManager.getInstance(); + cacheKeys.forEach(nativeMemoryCacheManager::invalidate); delegate.close(); } - public final List getOpenedIndexPath() { - return new ArrayList<>(indexPathMap.values()); + public final List getCacheKeys() { + return new ArrayList<>(cacheKeys); } - /** - * Get KNNEngine From FieldInfo - * - * @param field which field we need produce from engine - * @return if and only if Native Engine we return specific engine, else return null - */ - private KNNEngine getNativeKNNEngine(@NonNull FieldInfo field) { - - final String modelId = field.attributes().get(MODEL_ID); - if (modelId != null) { - return null; - } - KNNEngine engine = FieldInfoExtractor.extractKNNEngine(field); - if (KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(engine)) { - return engine; + private static List getVectorCacheKeysFromSegmentReaderState(SegmentReadState segmentReadState) { + final List cacheKeys = new ArrayList<>(); + + for (FieldInfo field : segmentReadState.fieldInfos) { + // Only segments that contains BinaryDocValues and doesn't have vector values should be considered. + // By default, we don't create BinaryDocValues for knn field anymore. However, users can set doc_values = true + // to create binary doc values explicitly like any other field. Hence, we only want to include fields + // where approximate search is possible only by BinaryDocValues. + if (field.getDocValuesType() != DocValuesType.BINARY || field.hasVectorValues()) { + continue; + } + + final String vectorIndexFileName = KNNCodecUtil.getNativeEngineFileFromFieldInfo(field, segmentReadState.segmentInfo); + if (vectorIndexFileName == null) { + continue; + } + final String cacheKey = NativeMemoryCacheKeyHelper.constructCacheKey(vectorIndexFileName, segmentReadState.segmentInfo); + cacheKeys.add(cacheKey); } - return null; + + return cacheKeys; } } diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsReader.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsReader.java index 16631fd97..efabc3a70 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsReader.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsReader.java @@ -24,13 +24,18 @@ import org.apache.lucene.util.Bits; import org.apache.lucene.util.IOUtils; import org.opensearch.common.UUIDs; +import org.opensearch.knn.index.codec.util.KNNCodecUtil; +import org.opensearch.knn.index.codec.util.NativeMemoryCacheKeyHelper; +import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.quantizationservice.QuantizationService; import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; import org.opensearch.knn.quantization.models.quantizationState.QuantizationStateCacheManager; import org.opensearch.knn.quantization.models.quantizationState.QuantizationStateReadConfig; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -40,12 +45,14 @@ public class NativeEngines990KnnVectorsReader extends KnnVectorsReader { private final FlatVectorsReader flatVectorsReader; - private final SegmentReadState segmentReadState; private Map quantizationStateCacheKeyPerField; + private SegmentReadState segmentReadState; + private final List cacheKeys; - public NativeEngines990KnnVectorsReader(final SegmentReadState state, final FlatVectorsReader flatVectorsReader) throws IOException { - this.segmentReadState = state; + public NativeEngines990KnnVectorsReader(final SegmentReadState state, final FlatVectorsReader flatVectorsReader) { this.flatVectorsReader = flatVectorsReader; + this.segmentReadState = state; + this.cacheKeys = getVectorCacheKeysFromSegmentReaderState(state); loadCacheKeyMap(); } @@ -176,10 +183,18 @@ public void search(String field, byte[] target, KnnCollector knnCollector, Bits */ @Override public void close() throws IOException { + // Clean up allocated vector indices resources from cache. + final NativeMemoryCacheManager nativeMemoryCacheManager = NativeMemoryCacheManager.getInstance(); + cacheKeys.forEach(nativeMemoryCacheManager::invalidate); + + // Close a reader. IOUtils.close(flatVectorsReader); + + // Clean up quantized state cache. if (quantizationStateCacheKeyPerField != null) { + final QuantizationStateCacheManager quantizationStateCacheManager = QuantizationStateCacheManager.getInstance(); for (String cacheKey : quantizationStateCacheKeyPerField.values()) { - QuantizationStateCacheManager.getInstance().evict(cacheKey); + quantizationStateCacheManager.evict(cacheKey); } } } @@ -192,11 +207,26 @@ public long ramBytesUsed() { return flatVectorsReader.ramBytesUsed(); } - private void loadCacheKeyMap() throws IOException { + private void loadCacheKeyMap() { quantizationStateCacheKeyPerField = new HashMap<>(); for (FieldInfo fieldInfo : segmentReadState.fieldInfos) { String cacheKey = UUIDs.base64UUID(); quantizationStateCacheKeyPerField.put(fieldInfo.getName(), cacheKey); } } + + private static List getVectorCacheKeysFromSegmentReaderState(SegmentReadState segmentReadState) { + final List cacheKeys = new ArrayList<>(); + + for (FieldInfo field : segmentReadState.fieldInfos) { + final String vectorIndexFileName = KNNCodecUtil.getNativeEngineFileFromFieldInfo(field, segmentReadState.segmentInfo); + if (vectorIndexFileName == null) { + continue; + } + final String cacheKey = NativeMemoryCacheKeyHelper.constructCacheKey(vectorIndexFileName, segmentReadState.segmentInfo); + cacheKeys.add(cacheKey); + } + + return cacheKeys; + } } diff --git a/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java b/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java index 84c7c4675..3ccfc3c2b 100644 --- a/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java +++ b/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java @@ -5,8 +5,11 @@ package org.opensearch.knn.index.codec.util; +import lombok.NonNull; import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.SegmentInfo; +import org.opensearch.knn.common.FieldInfoExtractor; import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.codec.KNN80Codec.KNN80BinaryDocValues; @@ -16,6 +19,8 @@ import java.util.List; import java.util.stream.Collectors; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapper.KNN_FIELD; + public class KNNCodecUtil { // Floats are 4 bytes in size public static final int FLOAT_BYTE_SIZE = 4; @@ -84,4 +89,44 @@ public static List getEngineFiles(String extension, String fieldName, Se .collect(Collectors.toList()); return engineFiles; } + + /** + * Get engine file name from given field and segment info. + * Ex: _0_165_my_field.faiss + * + * @param field : Field info that might have a vector index file. Not always it has it. + * @param segmentInfo : Segment where we are collecting an engine file list. + * @return : Found vector engine names, if not found, returns null. + */ + public static String getNativeEngineFileFromFieldInfo(FieldInfo field, SegmentInfo segmentInfo) { + if (!field.attributes().containsKey(KNN_FIELD)) { + return null; + } + // Only Native Engine put into indexPathMap + final KNNEngine knnEngine = getNativeKNNEngine(field); + if (knnEngine == null) { + return null; + } + final List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), field.name, segmentInfo); + if (engineFiles.isEmpty()) { + return null; + } else { + final String vectorIndexFileName = engineFiles.get(0); + return vectorIndexFileName; + } + } + + /** + * Get KNNEngine From FieldInfo + * + * @param field which field we need produce from engine + * @return if and only if Native Engine we return specific engine, else return null + */ + private static KNNEngine getNativeKNNEngine(@NonNull FieldInfo field) { + final KNNEngine engine = FieldInfoExtractor.extractKNNEngine(field); + if (KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(engine)) { + return engine; + } + return null; + } } diff --git a/src/main/java/org/opensearch/knn/index/codec/util/NativeMemoryCacheKeyHelper.java b/src/main/java/org/opensearch/knn/index/codec/util/NativeMemoryCacheKeyHelper.java new file mode 100644 index 000000000..8d50bf029 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/util/NativeMemoryCacheKeyHelper.java @@ -0,0 +1,45 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.util; + +import org.apache.lucene.index.SegmentInfo; + +import java.util.Base64; + +public final class NativeMemoryCacheKeyHelper { + private NativeMemoryCacheKeyHelper() {} + + /** + * Construct a unique cache key for look-up operation in {@link org.opensearch.knn.index.memory.NativeMemoryCacheManager} + * + * @param vectorIndexFileName Vector index file name. Ex: _0_165_test_field.faiss. + * @param segmentInfo Segment info object representing a logical segment unit containing a vector index. + * @return Unique cache key that can be used for look-up and invalidating in + * {@link org.opensearch.knn.index.memory.NativeMemoryCacheManager} + */ + public static String constructCacheKey(final String vectorIndexFileName, final SegmentInfo segmentInfo) { + final String segmentId = Base64.getEncoder().encodeToString(segmentInfo.getId()); + final String cacheKey = vectorIndexFileName + "@" + segmentId; + return cacheKey; + } + + /** + * From cacheKey, we extract a vector file name. + * Note that expected format of cacheKey consists of two part with '@' as a delimiter. + * First part would be the vector file name, the second one is the segment id. + * + * @param cacheKey : Cache key for {@link org.opensearch.knn.index.memory.NativeMemoryCacheManager} + * @return : Vector file name, if the given cacheKey was invalid format, returns null. + */ + public static String extractVectorIndexFileName(final String cacheKey) { + final int indexOfDelimiter = cacheKey.indexOf('@'); + if (indexOfDelimiter != -1) { + final String vectorFileName = cacheKey.substring(0, indexOfDelimiter); + return vectorFileName; + } + return null; + } +} diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java index 360c827f9..9ac3caa23 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java @@ -21,8 +21,6 @@ import org.opensearch.knn.index.query.KNNWeight; import org.opensearch.knn.jni.JNIService; import org.opensearch.knn.index.engine.KNNEngine; -import org.opensearch.watcher.FileWatcher; -import org.opensearch.watcher.WatcherHandle; import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; @@ -115,11 +113,10 @@ class IndexAllocation implements NativeMemoryAllocation { @Getter private final KNNEngine knnEngine; @Getter - private final String indexPath; + private final String vectorFileName; @Getter private final String openSearchIndexName; private final ReadWriteLock readWriteLock; - private final WatcherHandle watcherHandle; private final SharedIndexState sharedIndexState; @Getter private final boolean isBinaryIndex; @@ -132,20 +129,18 @@ class IndexAllocation implements NativeMemoryAllocation { * @param memoryAddress Pointer in memory to the index * @param sizeKb Size this index consumes in kilobytes * @param knnEngine KNNEngine associated with the index allocation - * @param indexPath File path to index + * @param vectorFileName Vector file name. Ex: _0_165_my_field.faiss * @param openSearchIndexName Name of OpenSearch index this index is associated with - * @param watcherHandle Handle for watching index file */ IndexAllocation( ExecutorService executorService, long memoryAddress, int sizeKb, KNNEngine knnEngine, - String indexPath, - String openSearchIndexName, - WatcherHandle watcherHandle + String vectorFileName, + String openSearchIndexName ) { - this(executorService, memoryAddress, sizeKb, knnEngine, indexPath, openSearchIndexName, watcherHandle, null, false); + this(executorService, memoryAddress, sizeKb, knnEngine, vectorFileName, openSearchIndexName, null, false); } /** @@ -155,9 +150,8 @@ class IndexAllocation implements NativeMemoryAllocation { * @param memoryAddress Pointer in memory to the index * @param sizeKb Size this index consumes in kilobytes * @param knnEngine KNNEngine associated with the index allocation - * @param indexPath File path to index + * @param vectorFileName Vector file name. Ex: _0_165_my_field.faiss * @param openSearchIndexName Name of OpenSearch index this index is associated with - * @param watcherHandle Handle for watching index file * @param sharedIndexState Shared index state. If not shared state present, pass null. */ IndexAllocation( @@ -165,21 +159,19 @@ class IndexAllocation implements NativeMemoryAllocation { long memoryAddress, int sizeKb, KNNEngine knnEngine, - String indexPath, + String vectorFileName, String openSearchIndexName, - WatcherHandle watcherHandle, SharedIndexState sharedIndexState, boolean isBinaryIndex ) { this.executor = executorService; this.closed = false; this.knnEngine = knnEngine; - this.indexPath = indexPath; + this.vectorFileName = vectorFileName; this.openSearchIndexName = openSearchIndexName; this.memoryAddress = memoryAddress; this.readWriteLock = new ReentrantReadWriteLock(); this.sizeKb = sizeKb; - this.watcherHandle = watcherHandle; this.sharedIndexState = sharedIndexState; this.isBinaryIndex = isBinaryIndex; this.refCounted = new RefCountedReleasable<>("IndexAllocation-Reference", this, this::closeInternal); @@ -218,8 +210,6 @@ private void cleanup() { this.closed = true; - watcherHandle.stop(); - // memoryAddress is sometimes initialized to 0. If this is ever the case, freeing will surely fail. if (memoryAddress != 0) { JNIService.free(memoryAddress, knnEngine, isBinaryIndex); @@ -294,30 +284,31 @@ class TrainingDataAllocation implements NativeMemoryAllocation { private final ExecutorService executor; private volatile boolean closed; + @Setter private long memoryAddress; - private final int size; + private final int sizeKb; @Getter @Setter private QuantizationConfig quantizationConfig = QuantizationConfig.EMPTY; // Implement reader/writer with semaphores to deal with passing lock conditions between threads private int readCount; - private Semaphore readSemaphore; - private Semaphore writeSemaphore; - private VectorDataType vectorDataType; + private final Semaphore readSemaphore; + private final Semaphore writeSemaphore; + private final VectorDataType vectorDataType; /** * Constructor * * @param executor Executor used for allocation close * @param memoryAddress pointer in memory to the training data allocation - * @param size amount memory needed for allocation in kilobytes + * @param sizeKb amount memory needed for allocation in kilobytes */ - public TrainingDataAllocation(ExecutorService executor, long memoryAddress, int size, VectorDataType vectorDataType) { + public TrainingDataAllocation(ExecutorService executor, long memoryAddress, int sizeKb, VectorDataType vectorDataType) { this.executor = executor; this.closed = false; this.memoryAddress = memoryAddress; - this.size = size; + this.sizeKb = sizeKb; this.readCount = 0; this.readSemaphore = new Semaphore(1); @@ -401,7 +392,7 @@ public void readLock() { /** * A write lock will be obtained either on eviction from {@link NativeMemoryCacheManager NativeMemoryManager's} * or when training data is actually being loaded. A semaphore is used because collecting training data - * happens asynchrously, so the thread that obtains the lock will not be the same thread that releases the + * happens asynchronously, so the thread that obtains the lock will not be the same thread that releases the * lock. */ @Override @@ -438,23 +429,14 @@ public void writeUnlock() { @Override public int getSizeInKB() { - return size; - } - - /** - * Setter for memory address to training data - * - * @param memoryAddress Pointer to training data - */ - public void setMemoryAddress(long memoryAddress) { - this.memoryAddress = memoryAddress; + return sizeKb; } } /** * An anonymous allocation is used to reserve space in the native memory cache. It does not have a * memory address. This allocation type should be used when a function allocates a large portion of memory in the - * function, runs for awhile, and then frees it. + * function, runs for a while, and then frees it. */ class AnonymousAllocation implements NativeMemoryAllocation { diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryCacheManager.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryCacheManager.java index 649fb9774..b8aecc5a5 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryCacheManager.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryCacheManager.java @@ -291,8 +291,8 @@ public CacheStats getCacheStats() { public NativeMemoryAllocation get(NativeMemoryEntryContext nativeMemoryEntryContext, boolean isAbleToTriggerEviction) throws ExecutionException { if (!isAbleToTriggerEviction - && !cache.asMap().containsKey(nativeMemoryEntryContext.getKey()) - && maxWeight - getCacheSizeInKilobytes() - nativeMemoryEntryContext.calculateSizeInKB() <= 0) { + && (maxWeight - getCacheSizeInKilobytes() - nativeMemoryEntryContext.calculateSizeInKB()) <= 0 + && !cache.asMap().containsKey(nativeMemoryEntryContext.getKey())) { throw new OutOfNativeMemoryException( "Entry cannot be loaded into cache because it would not fit. " + "Entry size: " diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java index 00bf023f9..0af13fb46 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java @@ -15,14 +15,13 @@ import org.apache.lucene.store.Directory; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.Nullable; +import org.opensearch.knn.index.codec.util.NativeMemoryCacheKeyHelper; import org.opensearch.knn.index.engine.qframe.QuantizationConfig; -import org.opensearch.knn.index.util.IndexUtil; import org.opensearch.knn.index.VectorDataType; import java.io.IOException; import java.util.Map; import java.util.UUID; -import java.util.function.Function; /** * Encapsulates all information needed to load a component into native memory. @@ -80,26 +79,26 @@ public static class IndexEntryContext extends NativeMemoryEntryContext parameters, String openSearchIndexName ) { - this(directory, indexPath, indexLoadStrategy, parameters, openSearchIndexName, null); + this(directory, vectorIndexCacheKey, indexLoadStrategy, parameters, openSearchIndexName, null); } /** * Constructor * * @param directory Lucene directory to create required IndexInput/IndexOutput to access files. - * @param indexPath path to index file. Also used as key in cache. + * @param vectorIndexCacheKey Cache key for {@link NativeMemoryCacheManager}. It must contain a vector file name. * @param indexLoadStrategy strategy to load index into memory * @param parameters load time parameters * @param openSearchIndexName opensearch index associated with index @@ -107,13 +106,13 @@ public IndexEntryContext( */ public IndexEntryContext( Directory directory, - String indexPath, + String vectorIndexCacheKey, NativeMemoryLoadStrategy.IndexLoadStrategy indexLoadStrategy, Map parameters, String openSearchIndexName, String modelId ) { - super(indexPath); + super(vectorIndexCacheKey); this.directory = directory; this.indexLoadStrategy = indexLoadStrategy; this.openSearchIndexName = openSearchIndexName; @@ -123,25 +122,19 @@ public IndexEntryContext( @Override public Integer calculateSizeInKB() { - return IndexSizeCalculator.INSTANCE.apply(this); + final String indexFileName = NativeMemoryCacheKeyHelper.extractVectorIndexFileName(key); + try { + final long fileLength = directory.fileLength(indexFileName); + return (int) (fileLength / 1024L); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override public NativeMemoryAllocation.IndexAllocation load() throws IOException { return indexLoadStrategy.load(this); } - - private static class IndexSizeCalculator implements Function { - - static IndexSizeCalculator INSTANCE = new IndexSizeCalculator(); - - IndexSizeCalculator() {} - - @Override - public Integer apply(IndexEntryContext indexEntryContext) { - return IndexUtil.getFileSizeInKB(indexEntryContext.getKey()); - } - } } public static class TrainingDataEntryContext extends NativeMemoryEntryContext { diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java index 5daa1e047..8cbdb4fd7 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategy.java @@ -16,6 +16,7 @@ import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexInput; import org.opensearch.core.action.ActionListener; +import org.opensearch.knn.index.codec.util.NativeMemoryCacheKeyHelper; import org.opensearch.knn.index.engine.qframe.QuantizationConfig; import org.opensearch.knn.index.store.IndexInputWithBuffer; import org.opensearch.knn.index.util.IndexUtil; @@ -23,15 +24,9 @@ import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.training.TrainingDataConsumer; import org.opensearch.knn.training.VectorReader; -import org.opensearch.watcher.FileChangesListener; -import org.opensearch.watcher.FileWatcher; -import org.opensearch.watcher.ResourceWatcherService; -import org.opensearch.watcher.WatcherHandle; import java.io.Closeable; import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -57,8 +52,6 @@ class IndexLoadStrategy private static IndexLoadStrategy INSTANCE; private final ExecutorService executor; - private final FileChangesListener indexFileOnDeleteListener; - private ResourceWatcherService resourceWatcherService; /** * Get Singleton of this load strategy. @@ -72,47 +65,34 @@ public static synchronized IndexLoadStrategy getInstance() { return INSTANCE; } - /** - * Initialize singleton. - * - * @param resourceWatcherService service used to monitor index files for deletion - */ - public static void initialize(final ResourceWatcherService resourceWatcherService) { - getInstance().resourceWatcherService = resourceWatcherService; - } - private IndexLoadStrategy() { executor = Executors.newSingleThreadExecutor(); - indexFileOnDeleteListener = new FileChangesListener() { - @Override - public void onFileDeleted(Path indexFilePath) { - NativeMemoryCacheManager.getInstance().invalidate(indexFilePath.toString()); - } - }; } @Override public NativeMemoryAllocation.IndexAllocation load(NativeMemoryEntryContext.IndexEntryContext indexEntryContext) throws IOException { - final Path absoluteIndexPath = Paths.get(indexEntryContext.getKey()); - final KNNEngine knnEngine = KNNEngine.getEngineNameFromPath(absoluteIndexPath.toString()); - final FileWatcher fileWatcher = new FileWatcher(absoluteIndexPath); - fileWatcher.addListener(indexFileOnDeleteListener); - fileWatcher.init(); + // Extract vector file name from the given cache key. + // Ex: _0_165_my_field.faiss@1vaqiupVUwvkXAG4Qc/RPg== + final String cacheKey = indexEntryContext.getKey(); + final String vectorFileName = NativeMemoryCacheKeyHelper.extractVectorIndexFileName(cacheKey); + if (vectorFileName == null) { + throw new IllegalStateException( + "Invalid cache key was given. The key [" + cacheKey + "] does not contain the corresponding vector file name." + ); + } + // Prepare for opening index input from directory. + final KNNEngine knnEngine = KNNEngine.getEngineNameFromPath(vectorFileName); final Directory directory = indexEntryContext.getDirectory(); + final int indexSizeKb = Math.toIntExact(directory.fileLength(vectorFileName) / 1024); - // Ex: Input -> /a/b/c/_0_NativeEngines990KnnVectorsFormat_0.vec - // Output -> _0_NativeEngines990KnnVectorsFormat_0.vec - final String logicalIndexPath = absoluteIndexPath.getFileName().toString(); - - final int indexSizeKb = Math.toIntExact(directory.fileLength(logicalIndexPath) / 1024); + // Try to open an index input then pass it down to native engine for loading an index. + try (IndexInput readStream = directory.openInput(vectorFileName, IOContext.READONCE)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(readStream); + final long indexAddress = JNIService.loadIndex(indexInputWithBuffer, indexEntryContext.getParameters(), knnEngine); - try (IndexInput readStream = directory.openInput(logicalIndexPath, IOContext.READONCE)) { - IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(readStream); - long indexAddress = JNIService.loadIndex(indexInputWithBuffer, indexEntryContext.getParameters(), knnEngine); - - return createIndexAllocation(indexEntryContext, knnEngine, indexAddress, fileWatcher, indexSizeKb, absoluteIndexPath); + return createIndexAllocation(indexEntryContext, knnEngine, indexAddress, indexSizeKb, vectorFileName); } } @@ -120,10 +100,9 @@ private NativeMemoryAllocation.IndexAllocation createIndexAllocation( final NativeMemoryEntryContext.IndexEntryContext indexEntryContext, final KNNEngine knnEngine, final long indexAddress, - final FileWatcher fileWatcher, final int indexSizeKb, - final Path absoluteIndexPath - ) throws IOException { + final String vectorFileName + ) { SharedIndexState sharedIndexState = null; String modelId = indexEntryContext.getModelId(); if (IndexUtil.isSharedIndexStateRequired(knnEngine, modelId, indexAddress)) { @@ -132,15 +111,13 @@ private NativeMemoryAllocation.IndexAllocation createIndexAllocation( JNIService.setSharedIndexState(indexAddress, sharedIndexState.getSharedIndexStateAddress(), knnEngine); } - final WatcherHandle watcherHandle = resourceWatcherService.add(fileWatcher); return new NativeMemoryAllocation.IndexAllocation( executor, indexAddress, indexSizeKb, knnEngine, - absoluteIndexPath.toString(), + vectorFileName, indexEntryContext.getOpenSearchIndexName(), - watcherHandle, sharedIndexState, IndexUtil.isBinaryIndex(knnEngine, indexEntryContext.getParameters()) ); diff --git a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java index 87c99b884..b5b2a5d22 100644 --- a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java +++ b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java @@ -15,13 +15,10 @@ import org.apache.lucene.search.FilteredDocIdSetIterator; import org.apache.lucene.search.Scorer; import org.apache.lucene.search.Weight; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.store.FilterDirectory; import org.apache.lucene.util.BitSet; import org.apache.lucene.util.BitSetIterator; import org.apache.lucene.util.Bits; import org.apache.lucene.util.FixedBitSet; -import org.opensearch.common.io.PathUtils; import org.opensearch.common.lucene.Lucene; import org.opensearch.knn.common.FieldInfoExtractor; import org.opensearch.knn.common.KNNConstants; @@ -29,6 +26,7 @@ import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.codec.util.KNNCodecUtil; +import org.opensearch.knn.index.codec.util.NativeMemoryCacheKeyHelper; import org.opensearch.knn.index.memory.NativeMemoryAllocation; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.memory.NativeMemoryEntryContext; @@ -43,7 +41,6 @@ import org.opensearch.knn.plugin.stats.KNNCounter; import java.io.IOException; -import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -229,7 +226,6 @@ private Map doANNSearch( final int k ) throws IOException { final SegmentReader reader = Lucene.segmentReader(context.reader()); - String directory = ((FSDirectory) FilterDirectory.unwrap(reader.directory())).getDirectory().toString(); FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(knnQuery.getField()); @@ -278,7 +274,9 @@ private Map doANNSearch( return Collections.emptyMap(); } - Path indexPath = PathUtils.get(directory, engineFiles.get(0)); + final String vectorIndexFileName = engineFiles.get(0); + final String cacheKey = NativeMemoryCacheKeyHelper.constructCacheKey(vectorIndexFileName, reader.getSegmentInfo().info); + final KNNQueryResult[] results; KNNCounter.GRAPH_QUERY_REQUESTS.increment(); @@ -288,7 +286,7 @@ private Map doANNSearch( indexAllocation = nativeMemoryCacheManager.get( new NativeMemoryEntryContext.IndexEntryContext( reader.directory(), - indexPath.toString(), + cacheKey, NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), getParametersAtLoading( spaceType, diff --git a/src/main/java/org/opensearch/knn/jni/JNIService.java b/src/main/java/org/opensearch/knn/jni/JNIService.java index a0daf65a7..dd4dcef17 100644 --- a/src/main/java/org/opensearch/knn/jni/JNIService.java +++ b/src/main/java/org/opensearch/knn/jni/JNIService.java @@ -135,7 +135,6 @@ public static void createIndex( Map parameters, KNNEngine knnEngine ) { - if (KNNEngine.NMSLIB == knnEngine) { NmslibService.createIndex(ids, vectorsAddress, dim, indexPath, parameters); return; diff --git a/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java b/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java index ff079031f..d27f502e1 100644 --- a/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java +++ b/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java @@ -192,7 +192,6 @@ public Collection createComponents( this.clusterService = clusterService; // Initialize Native Memory loading strategies - NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); VectorReader vectorReader = new VectorReader(client); NativeMemoryLoadStrategy.TrainingLoadStrategy.initialize(vectorReader); diff --git a/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java b/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java index aaeee31e1..18b9656e5 100644 --- a/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java +++ b/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java @@ -8,6 +8,12 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import lombok.SneakyThrows; +import org.apache.lucene.index.SegmentCommitInfo; +import org.apache.lucene.index.SegmentInfo; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.StringHelper; +import org.apache.lucene.util.Version; +import org.mockito.Mockito; import org.opensearch.knn.KNNSingleNodeTestCase; import org.opensearch.index.IndexService; import org.opensearch.index.engine.Engine; @@ -15,8 +21,9 @@ import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -113,11 +120,14 @@ public void testGetAllEngineFileContexts() throws IOException, ExecutionExceptio searcher = indexShard.acquireSearcher("test-hnsw-paths-2"); engineFileContexts = knnIndexShard.getAllEngineFileContexts(searcher.getIndexReader()); assertEquals(1, engineFileContexts.size()); - List paths = engineFileContexts.stream().map(KNNIndexShard.EngineFileContext::getIndexPath).collect(Collectors.toList()); + List paths = engineFileContexts.stream() + .map(KNNIndexShard.EngineFileContext::getVectorFileName) + .collect(Collectors.toList()); assertTrue(paths.get(0).contains("hnsw") || paths.get(0).contains("hnswc")); searcher.close(); } + @SneakyThrows public void testGetEngineFileContexts() { // Check that the correct engine paths are being returned by the KNNIndexShard String segmentName = "_0"; @@ -143,20 +153,40 @@ public void testGetEngineFileContexts() { KNNIndexShard knnIndexShard = new KNNIndexShard(null); - Path path = Paths.get(""); - List included = knnIndexShard.getEngineFileContexts( - files, + final Directory dummyDirectory = Mockito.mock(Directory.class); + final SegmentInfo segmentInfo = new SegmentInfo( + dummyDirectory, + Version.LATEST, + null, segmentName, + 0, + false, + false, + null, + Collections.emptyMap(), + new byte[StringHelper.ID_LENGTH], + Collections.emptyMap(), + null + ); + // Inject 'files' into the segment info instance. + // Since SegmentInfo class does trim out its given file list, for example removing segment name from a file name etc, + // we can't just use 'setFiles' api to assign the file list. Which will lead this unit test to be fail. + final Field setFilesPrivateField = SegmentInfo.class.getDeclaredField("setFiles"); + setFilesPrivateField.setAccessible(true); + setFilesPrivateField.set(segmentInfo, new HashSet<>(files)); + + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, -1, 0, 0, null); + List included = knnIndexShard.getEngineFileContexts( + segmentCommitInfo, fieldName, fileExt, - path, spaceType, modelId, vectorDataType ); assertEquals(includedFileNames.size(), included.size()); - included.stream().map(KNNIndexShard.EngineFileContext::getIndexPath).forEach(o -> assertTrue(includedFileNames.contains(o))); + included.stream().map(KNNIndexShard.EngineFileContext::getVectorFileName).forEach(o -> assertTrue(includedFileNames.contains(o))); } @SneakyThrows diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java index ccceee62b..6b18c51c3 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java @@ -121,11 +121,11 @@ public void testProduceKNNBinaryField_fromCodec_nmslibCurrent() throws IOExcepti assertTrue(docValuesFormat instanceof KNN80DocValuesFormat); DocValuesProducer producer = docValuesFormat.fieldsProducer(state); assertTrue(producer instanceof KNN80DocValuesProducer); - int pathSize = ((KNN80DocValuesProducer) producer).getOpenedIndexPath().size(); - assertEquals(pathSize, 1); + int cacheKeySize = ((KNN80DocValuesProducer) producer).getCacheKeys().size(); + assertEquals(cacheKeySize, 1); - String path = ((KNN80DocValuesProducer) producer).getOpenedIndexPath().get(0); - assertTrue(path.contains(segmentFiles.get(0))); + String cacheKey = ((KNN80DocValuesProducer) producer).getCacheKeys().get(0); + assertTrue(cacheKey.contains(segmentFiles.get(0))); } public void testProduceKNNBinaryField_whenFieldHasNonBinaryDocValues_thenSkipThoseField() throws IOException { @@ -178,7 +178,7 @@ public void testProduceKNNBinaryField_whenFieldHasNonBinaryDocValues_thenSkipTho assertTrue(docValuesFormat instanceof KNN80DocValuesFormat); DocValuesProducer producer = docValuesFormat.fieldsProducer(state); assertTrue(producer instanceof KNN80DocValuesProducer); - assertEquals(0, ((KNN80DocValuesProducer) producer).getOpenedIndexPath().size()); + assertEquals(0, ((KNN80DocValuesProducer) producer).getCacheKeys().size()); } } diff --git a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java index 3d9969a1e..e6fcb643d 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java @@ -185,8 +185,6 @@ public void testMultiFieldsKnnIndex(Codec codec) throws Exception { writer.flush(); IndexReader reader = writer.getReader(); writer.close(); - ResourceWatcherService resourceWatcherService = createDisabledResourceWatcherService(); - NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); List hnswfiles = Arrays.stream(dir.listAll()).filter(x -> x.contains("hnsw")).collect(Collectors.toList()); // there should be 2 hnsw index files created. one for test_vector and one for my_vector @@ -213,7 +211,6 @@ public void testMultiFieldsKnnIndex(Codec codec) throws Exception { reader.close(); dir.close(); - resourceWatcherService.close(); NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance().close(); } @@ -298,8 +295,6 @@ public void testBuildFromModelTemplate(Codec codec) throws IOException, Executio // Make sure that search returns the correct results KNNWeight.initialize(modelDao); - ResourceWatcherService resourceWatcherService = createDisabledResourceWatcherService(); - NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); float[] query = { 10.0f, 10.0f, 10.0f }; IndexSearcher searcher = new IndexSearcher(reader); TopDocs topDocs = searcher.search(new KNNQuery(fieldName, query, 4, "dummy", (BitSetProducer) null), 10); @@ -311,7 +306,6 @@ public void testBuildFromModelTemplate(Codec codec) throws IOException, Executio reader.close(); dir.close(); - resourceWatcherService.close(); NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance().close(); } } @@ -422,8 +416,6 @@ public void testKnnVectorIndex( writer.addDocument(doc1); IndexReader reader1 = writer.getReader(); writer.close(); - ResourceWatcherService resourceWatcherService = createDisabledResourceWatcherService(); - NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); verify(perFieldKnnVectorsFormatSpy, atLeastOnce()).getKnnVectorsFormatForField(eq(FIELD_NAME_TWO)); verify(perFieldKnnVectorsFormatSpy, atLeastOnce()).getMaxDimensions(eq(FIELD_NAME_TWO)); @@ -444,7 +436,6 @@ public void testKnnVectorIndex( reader1.close(); dir.close(); - resourceWatcherService.close(); NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance().close(); } } diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java index 906ff4cb7..db6231adf 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java @@ -26,8 +26,6 @@ import org.opensearch.knn.index.util.IndexUtil; import org.opensearch.knn.jni.JNICommons; import org.opensearch.knn.jni.JNIService; -import org.opensearch.watcher.FileWatcher; -import org.opensearch.watcher.WatcherHandle; import java.nio.file.Path; import java.util.Arrays; @@ -40,7 +38,6 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.opensearch.knn.common.featureflags.KNNFeatureFlags.KNN_FORCE_EVICT_CACHE_ENABLED_SETTING; @@ -86,10 +83,6 @@ public void testIndexAllocation_close() throws InterruptedException { // Load index into memory long memoryAddress = JNIService.loadIndex(path, parameters, knnEngine); - @SuppressWarnings("unchecked") - WatcherHandle watcherHandle = (WatcherHandle) mock(WatcherHandle.class); - doNothing().when(watcherHandle).stop(); - ExecutorService executorService = Executors.newSingleThreadExecutor(); NativeMemoryAllocation.IndexAllocation indexAllocation = new NativeMemoryAllocation.IndexAllocation( executorService, @@ -97,8 +90,7 @@ public void testIndexAllocation_close() throws InterruptedException { IndexUtil.getFileSizeInKB(path), knnEngine, path, - "test", - watcherHandle + "test" ); indexAllocation.close(); @@ -147,10 +139,6 @@ public void testClose_whenBinaryFiass_thenSuccess() { // Load index into memory long memoryAddress = JNIService.loadIndex(path, parameters, knnEngine); - @SuppressWarnings("unchecked") - WatcherHandle watcherHandle = (WatcherHandle) mock(WatcherHandle.class); - doNothing().when(watcherHandle).stop(); - ExecutorService executorService = Executors.newSingleThreadExecutor(); NativeMemoryAllocation.IndexAllocation indexAllocation = new NativeMemoryAllocation.IndexAllocation( executorService, @@ -159,7 +147,6 @@ public void testClose_whenBinaryFiass_thenSuccess() { knnEngine, path, "test", - watcherHandle, null, true ); @@ -189,8 +176,7 @@ public void testIndexAllocation_getMemoryAddress() { 0, null, "test", - "test", - null + "test" ); assertEquals(memoryAddress, indexAllocation.getMemoryAddress()); @@ -205,8 +191,7 @@ public void testIndexAllocation_readLock() throws InterruptedException { 0, null, "test", - "test", - null + "test" ); int initialValue = 10; @@ -232,7 +217,6 @@ public void testIndexAllocation_readLock() throws InterruptedException { } public void testIndexAllocation_closeDefault() { - WatcherHandle watcherHandle = (WatcherHandle) mock(WatcherHandle.class); ExecutorService executorService = Executors.newFixedThreadPool(2); AtomicReference expectedException = new AtomicReference<>(); @@ -243,8 +227,7 @@ public void testIndexAllocation_closeDefault() { 0, null, "test", - "test", - watcherHandle + "test" ); executorService.submit(nonBlockingIndexAllocation::readLock); @@ -261,7 +244,6 @@ public void testIndexAllocation_closeDefault() { public void testIndexAllocation_closeBlocking() throws InterruptedException, ExecutionException { // Prepare mocking and a thread pool. - WatcherHandle watcherHandle = (WatcherHandle) mock(WatcherHandle.class); ExecutorService executorService = Executors.newSingleThreadExecutor(); // Enable `KNN_FORCE_EVICT_CACHE_ENABLED_SETTING` to force it to block other threads. @@ -273,8 +255,7 @@ public void testIndexAllocation_closeBlocking() throws InterruptedException, Exe 0, null, "test", - "test", - watcherHandle + "test" ); // Acquire a read lock @@ -309,8 +290,7 @@ public void testIndexAllocation_writeLock() throws InterruptedException { 0, null, "test", - "test", - null + "test" ); int initialValue = 10; @@ -342,8 +322,7 @@ public void testIndexAllocation_getSize() { size, null, "test", - "test", - null + "test" ); assertEquals(size, indexAllocation.getSizeInKB()); @@ -357,8 +336,7 @@ public void testIndexAllocation_getKnnEngine() { 0, knnEngine, "test", - "test", - null + "test" ); assertEquals(knnEngine, indexAllocation.getKnnEngine()); @@ -372,11 +350,10 @@ public void testIndexAllocation_getIndexPath() { 0, null, indexPath, - "test", - null + "test" ); - assertEquals(indexPath, indexAllocation.getIndexPath()); + assertEquals(indexPath, indexAllocation.getVectorFileName()); } public void testIndexAllocation_getOsIndexName() { @@ -387,8 +364,7 @@ public void testIndexAllocation_getOsIndexName() { 0, null, "test", - osIndexName, - null + osIndexName ); assertEquals(osIndexName, indexAllocation.getOpenSearchIndexName()); diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java index 85eaf3322..4baf66cb4 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java @@ -118,8 +118,7 @@ public void testGetIndexSizeInKilobytes() throws ExecutionException, IOException indexEntryWeight, null, key, - indexName, - null + indexName ); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = mock(NativeMemoryEntryContext.IndexEntryContext.class); @@ -152,8 +151,7 @@ public void testGetIndexSizeAsPercentage() throws ExecutionException, IOExceptio indexEntryWeight, null, key, - indexName, - null + indexName ); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = mock(NativeMemoryEntryContext.IndexEntryContext.class); @@ -231,8 +229,7 @@ public void testGetIndexGraphCount() throws ExecutionException, IOException { indexEntryWeight, null, key1, - indexName1, - null + indexName1 ); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = mock(NativeMemoryEntryContext.IndexEntryContext.class); @@ -247,8 +244,7 @@ public void testGetIndexGraphCount() throws ExecutionException, IOException { indexEntryWeight, null, key2, - indexName1, - null + indexName1 ); indexEntryContext = mock(NativeMemoryEntryContext.IndexEntryContext.class); @@ -263,8 +259,7 @@ public void testGetIndexGraphCount() throws ExecutionException, IOException { indexEntryWeight, null, key3, - indexName2, - null + indexName2 ); indexEntryContext = mock(NativeMemoryEntryContext.IndexEntryContext.class); @@ -408,8 +403,7 @@ public void testGetIndicesCacheStats() throws IOException, ExecutionException { size1, null, testKey1, - indexName1, - null + indexName1 ); NativeMemoryAllocation.IndexAllocation indexAllocation2 = new NativeMemoryAllocation.IndexAllocation( @@ -418,8 +412,7 @@ public void testGetIndicesCacheStats() throws IOException, ExecutionException { size2, null, testKey2, - indexName1, - null + indexName1 ); NativeMemoryAllocation.IndexAllocation indexAllocation3 = new NativeMemoryAllocation.IndexAllocation( @@ -428,8 +421,7 @@ public void testGetIndicesCacheStats() throws IOException, ExecutionException { size1, null, testKey3, - indexName2, - null + indexName2 ); NativeMemoryAllocation.IndexAllocation indexAllocation4 = new NativeMemoryAllocation.IndexAllocation( @@ -438,8 +430,7 @@ public void testGetIndicesCacheStats() throws IOException, ExecutionException { size2, null, testKey4, - indexName2, - null + indexName2 ); NativeMemoryEntryContext.IndexEntryContext indexEntryContext1 = mock(NativeMemoryEntryContext.IndexEntryContext.class); diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java index 72cab9a1b..5379abc74 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java @@ -13,23 +13,21 @@ import com.google.common.collect.ImmutableMap; import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexOutput; +import org.apache.lucene.store.MMapDirectory; import org.opensearch.cluster.service.ClusterService; import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.TestUtils; import org.opensearch.knn.index.engine.qframe.QuantizationConfig; -import org.opensearch.knn.index.util.IndexUtil; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.KNNEngine; -import java.io.BufferedOutputStream; import java.io.IOException; -import java.io.OutputStream; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Map; -import static java.nio.file.StandardOpenOption.APPEND; -import static java.nio.file.StandardOpenOption.CREATE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -46,7 +44,7 @@ public void testIndexEntryContext_load() throws IOException { NativeMemoryLoadStrategy.IndexLoadStrategy indexLoadStrategy = mock(NativeMemoryLoadStrategy.IndexLoadStrategy.class); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( (Directory) null, - "test", + TestUtils.createFakeNativeMamoryCacheKey("test"), indexLoadStrategy, null, "test" @@ -58,8 +56,7 @@ public void testIndexEntryContext_load() throws IOException { 10, KNNEngine.DEFAULT, "test-path", - "test-name", - null + "test-name" ); when(indexLoadStrategy.load(indexEntryContext)).thenReturn(indexAllocation); @@ -69,36 +66,37 @@ public void testIndexEntryContext_load() throws IOException { public void testIndexEntryContext_calculateSize() throws IOException { // Create a file and write random bytes to it - Path tmpFile = createTempFile(); + final Path tmpDirectory = createTempDir(); + final Directory directory = new MMapDirectory(tmpDirectory); + final String indexFileName = "test.faiss"; byte[] data = new byte[1024 * 3]; Arrays.fill(data, (byte) 'c'); - try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(tmpFile, CREATE, APPEND))) { - out.write(data, 0, data.length); - } catch (IOException x) { - fail("Failed to write to file"); + try (IndexOutput output = directory.createOutput(indexFileName, IOContext.DEFAULT)) { + output.writeBytes(data, data.length); } // Get the expected size of this function - int expectedSize = IndexUtil.getFileSizeInKB(tmpFile.toAbsolutePath().toString()); + final long expectedSizeBytes = directory.fileLength(indexFileName); + final long expectedSizeKb = expectedSizeBytes / 1024L; // Check that the indexEntryContext will return the same thing NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( - (Directory) null, - tmpFile.toAbsolutePath().toString(), + directory, + TestUtils.createFakeNativeMamoryCacheKey(indexFileName), null, null, "test" ); - assertEquals(expectedSize, indexEntryContext.calculateSizeInKB().longValue()); + assertEquals(expectedSizeKb, indexEntryContext.calculateSizeInKB().longValue()); } public void testIndexEntryContext_getOpenSearchIndexName() { String openSearchIndexName = "test-index"; NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( (Directory) null, - "test", + TestUtils.createFakeNativeMamoryCacheKey("test"), null, null, openSearchIndexName @@ -111,7 +109,7 @@ public void testIndexEntryContext_getParameters() { Map parameters = ImmutableMap.of("test-1", 10); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( (Directory) null, - "test", + TestUtils.createFakeNativeMamoryCacheKey("test"), null, parameters, "test" diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java index 373afddc7..8236d0518 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java @@ -28,7 +28,6 @@ import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.training.FloatTrainingDataConsumer; import org.opensearch.knn.training.VectorReader; -import org.opensearch.watcher.ResourceWatcherService; import java.io.IOException; import java.nio.file.Path; @@ -38,10 +37,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; public class NativeMemoryLoadStrategyTests extends KNNTestCase { @@ -66,13 +62,9 @@ public void testIndexLoadStrategy_load() throws IOException { TestUtils.createIndex(ids, memoryAddress, dimension, path, parameters, knnEngine); // Setup mock resource manager - ResourceWatcherService resourceWatcherService = mock(ResourceWatcherService.class); - doReturn(null).when(resourceWatcherService).add(any()); - NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); - NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( luceneDirectory, - path, + TestUtils.createFakeNativeMamoryCacheKey(indexName), NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), parameters, "test" @@ -116,13 +108,9 @@ public void testLoad_whenFaissBinary_thenSuccess() throws IOException { TestUtils.createIndex(ids, memoryAddress, dimension, path, parameters, knnEngine); // Setup mock resource manager - ResourceWatcherService resourceWatcherService = mock(ResourceWatcherService.class); - doReturn(null).when(resourceWatcherService).add(any()); - NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); - NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( luceneDirectory, - path, + TestUtils.createFakeNativeMamoryCacheKey(indexName), NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), parameters, "test" diff --git a/src/testFixtures/java/org/opensearch/knn/TestUtils.java b/src/testFixtures/java/org/opensearch/knn/TestUtils.java index b644c37f8..ba3aaca7a 100644 --- a/src/testFixtures/java/org/opensearch/knn/TestUtils.java +++ b/src/testFixtures/java/org/opensearch/knn/TestUtils.java @@ -24,6 +24,7 @@ import org.opensearch.knn.jni.JNIService; import org.opensearch.knn.plugin.script.KNNScoringUtil; +import java.util.Base64; import java.util.Collections; import java.util.Comparator; import java.util.Random; @@ -186,6 +187,10 @@ public static float[][] getIndexVectors(int docCount, int dimensions, boolean is } } + public static String createFakeNativeMamoryCacheKey(final String fileName) { + return fileName + "@" + Base64.getEncoder().encodeToString(fileName.getBytes()); + } + /* * Recall is the number of relevant documents retrieved by a search divided by the total number of existing relevant documents. * We are similarly calculating recall by verifying number of relevant documents obtained in the search results by comparing with From d52ee1448742f1480c4794313cd163ac74488e0d Mon Sep 17 00:00:00 2001 From: Naveen Tatikonda Date: Mon, 21 Oct 2024 18:03:01 -0500 Subject: [PATCH 47/59] Bump Faiss commit from 4eecd916 to 1f42e815 (#2224) Signed-off-by: Naveen Tatikonda --- jni/external/faiss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/external/faiss b/jni/external/faiss index 4eecd9165..1f42e815d 160000 --- a/jni/external/faiss +++ b/jni/external/faiss @@ -1 +1 @@ -Subproject commit 4eecd9165ae56bc8ff4afbf14cc8b359fdfe4004 +Subproject commit 1f42e815db7754297e3b4467763352b829b6cde0 From 8f2d9114df33d232a9b749b8863922219cf56afd Mon Sep 17 00:00:00 2001 From: Vikasht34 Date: Mon, 21 Oct 2024 18:01:17 -0700 Subject: [PATCH 48/59] Add Release Notes for 2.18.0.0 (#2227) * Add Release Notes for 2.18.0.0 Signed-off-by: Vikasht34 * Add Release Notes for 2.18.0.0 Signed-off-by: Vikasht34 --------- Signed-off-by: Vikasht34 --- CHANGELOG.md | 21 +------------- .../opensearch-knn.release-notes-2.18.0.0.md | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 release-notes/opensearch-knn.release-notes-2.18.0.0.md diff --git a/CHANGELOG.md b/CHANGELOG.md index bf34ebcfc..a5fcadee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,30 +14,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Maintenance ### Refactoring -## [Unreleased 2.x](https://github.com/opensearch-project/k-NN/compare/2.17...2.x) +## [Unreleased 2.x](https://github.com/opensearch-project/k-NN/compare/2.18...2.x) ### Features -* Add AVX512 support to k-NN for FAISS library [#2069](https://github.com/opensearch-project/k-NN/pull/2069) ### Enhancements -* Introducing a loading layer in FAISS [#2033](https://github.com/opensearch-project/k-NN/issues/2033) -* Add short circuit if no live docs are in segments [#2059](https://github.com/opensearch-project/k-NN/pull/2059) -* Optimize reduceToTopK in ResultUtil by removing pre-filling and reducing peek calls [#2146](https://github.com/opensearch-project/k-NN/pull/2146) -* Update Default Rescore Context based on Dimension [#2149](https://github.com/opensearch-project/k-NN/pull/2149) -* KNNIterators should support with and without filters [#2155](https://github.com/opensearch-project/k-NN/pull/2155) -* Adding Support to Enable/Disble Share level Rescoring and Update Oversampling Factor[#2172](https://github.com/opensearch-project/k-NN/pull/2172) -* Add support to build vector data structures greedily and perform exact search when there are no engine files [#1942](https://github.com/opensearch-project/k-NN/issues/1942) -* Add CompressionLevel Calculation for PQ [#2200](https://github.com/opensearch-project/k-NN/pull/2200) -* Remove FSDirectory dependency from native engine constructing side and deprecated FileWatcher [#2182](https://github.com/opensearch-project/k-NN/pull/2182) ### Bug Fixes -* Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) -* KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) -* Score Fix for Binary Quantized Vector and Setting Default value in case of shard level rescoring is disabled for oversampling factor[#2183](https://github.com/opensearch-project/k-NN/pull/2183) -* Java Docs Fix For 2.x[#2190](https://github.com/opensearch-project/k-NN/pull/2190) ### Infrastructure ### Documentation -* Fix sed command in DEVELOPER_GUIDE.md to append a new line character '\n'. [#2181](https://github.com/opensearch-project/k-NN/pull/2181) ### Maintenance -* Remove benchmarks folder from k-NN repo [#2127](https://github.com/opensearch-project/k-NN/pull/2127) -* Fix lucene codec after lucene version bumped to 9.12. [#2195](https://github.com/opensearch-project/k-NN/pull/2195) ### Refactoring -* Does not create additional KNNVectorValues in NativeEngines990KNNVectorWriter when quantization is not needed [#2133](https://github.com/opensearch-project/k-NN/pull/2133) -* Minor refactoring and refactored some unit test [#2167](https://github.com/opensearch-project/k-NN/pull/2167) diff --git a/release-notes/opensearch-knn.release-notes-2.18.0.0.md b/release-notes/opensearch-knn.release-notes-2.18.0.0.md new file mode 100644 index 000000000..ec9114fde --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.18.0.0.md @@ -0,0 +1,29 @@ +## Version 2.18.0.0 Release Notes + +Compatible with OpenSearch 2.18.0 + +### Features +* Add AVX512 support to k-NN for FAISS library [#2069](https://github.com/opensearch-project/k-NN/pull/2069) +### Enhancements +* Introducing a loading layer in FAISS [#2033](https://github.com/opensearch-project/k-NN/issues/2033) +* Add short circuit if no live docs are in segments [#2059](https://github.com/opensearch-project/k-NN/pull/2059) +* Optimize reduceToTopK in ResultUtil by removing pre-filling and reducing peek calls [#2146](https://github.com/opensearch-project/k-NN/pull/2146) +* Update Default Rescore Context based on Dimension [#2149](https://github.com/opensearch-project/k-NN/pull/2149) +* KNNIterators should support with and without filters [#2155](https://github.com/opensearch-project/k-NN/pull/2155) +* Adding Support to Enable/Disble Share level Rescoring and Update Oversampling Factor[#2172](https://github.com/opensearch-project/k-NN/pull/2172) +* Add support to build vector data structures greedily and perform exact search when there are no engine files [#1942](https://github.com/opensearch-project/k-NN/issues/1942) +* Add CompressionLevel Calculation for PQ [#2200](https://github.com/opensearch-project/k-NN/pull/2200) +* Remove FSDirectory dependency from native engine constructing side and deprecated FileWatcher [#2182](https://github.com/opensearch-project/k-NN/pull/2182) +### Bug Fixes +* Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) +* KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) +* Score Fix for Binary Quantized Vector and Setting Default value in case of shard level rescoring is disabled for oversampling factor[#2183](https://github.com/opensearch-project/k-NN/pull/2183) +* Java Docs Fix For 2.x[#2190](https://github.com/opensearch-project/k-NN/pull/2190) +### Documentation +* Fix sed command in DEVELOPER_GUIDE.md to append a new line character '\n'. [#2181](https://github.com/opensearch-project/k-NN/pull/2181) +### Maintenance +* Remove benchmarks folder from k-NN repo [#2127](https://github.com/opensearch-project/k-NN/pull/2127) +* Fix lucene codec after lucene version bumped to 9.12. [#2195](https://github.com/opensearch-project/k-NN/pull/2195) +### Refactoring +* Does not create additional KNNVectorValues in NativeEngines990KNNVectorWriter when quantization is not needed [#2133](https://github.com/opensearch-project/k-NN/pull/2133) +* Minor refactoring and refactored some unit test [#2167](https://github.com/opensearch-project/k-NN/pull/2167) From eb0a3c7454cb33346f135161beb21f46f43b8457 Mon Sep 17 00:00:00 2001 From: Vijayan Balasubramanian Date: Wed, 23 Oct 2024 15:40:00 -0700 Subject: [PATCH 49/59] Update approximate_threshold to 15K documents (#2229) * Update threshold to 15K documents After comparing indexing and search performance, we are updating default value to be 15000. Signed-off-by: Vijayan Balasubramanian * Fix bwc test Signed-off-by: Vijayan Balasubramanian * Update test method Signed-off-by: Vijayan Balasubramanian * Flush data after index Signed-off-by: Vijayan Balasubramanian --------- Signed-off-by: Vijayan Balasubramanian --- .../org/opensearch/knn/bwc/ClearCacheIT.java | 17 ++++---------- .../org/opensearch/knn/bwc/IndexingIT.java | 10 +++++++-- .../java/org/opensearch/knn/bwc/WarmupIT.java | 12 +++++++++- .../org/opensearch/knn/bwc/ClearCacheIT.java | 9 +++----- .../java/org/opensearch/knn/bwc/WarmupIT.java | 4 ++++ .../opensearch-knn.release-notes-2.18.0.0.md | 1 + .../org/opensearch/knn/index/KNNSettings.java | 2 +- .../NativeEngines990KnnVectorsFormat.java | 4 ++++ .../codec/KNN990Codec/UnitTestCodec.java | 4 +++- .../opensearch/knn/KNNSingleNodeTestCase.java | 22 +++++++++++++++++++ .../knn/index/KNNCircuitBreakerIT.java | 3 +++ .../knn/index/KNNESSettingsTestIT.java | 7 ++++-- .../knn/index/KNNIndexShardTests.java | 5 +++++ .../org/opensearch/knn/index/NmslibIT.java | 2 +- .../opensearch/knn/index/OpenSearchIT.java | 2 +- .../action/RestClearCacheHandlerIT.java | 13 ++++++----- .../plugin/action/RestKNNStatsHandlerIT.java | 4 ++-- .../plugin/action/RestKNNWarmupHandlerIT.java | 6 ++--- .../action/RestLegacyKNNStatsHandlerIT.java | 2 +- .../action/RestLegacyKNNWarmupHandlerIT.java | 9 ++++---- .../ClearCacheTransportActionTests.java | 2 +- .../KNNWarmupTransportActionTests.java | 2 +- .../org/opensearch/knn/KNNRestTestCase.java | 9 ++++++++ 23 files changed, 105 insertions(+), 46 deletions(-) diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java index 045821e09..f93f2f883 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java @@ -22,14 +22,11 @@ public void testClearCache() throws Exception { if (isRunningAgainstOldCluster()) { createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docId, NUM_DOCS); - } else { queryCnt = NUM_DOCS; - validateClearCacheOnUpgrade(queryCnt); - - docId = NUM_DOCS; - addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docId, NUM_DOCS); - - queryCnt = queryCnt + NUM_DOCS; + int graphCount = getTotalGraphsInCache(); + knnWarmup(Collections.singletonList(testIndex)); + assertTrue(getTotalGraphsInCache() > graphCount); + } else { validateClearCacheOnUpgrade(queryCnt); deleteKNNIndex(testIndex); } @@ -37,13 +34,7 @@ public void testClearCache() throws Exception { // validation steps for Clear Cache API after upgrading node to new version private void validateClearCacheOnUpgrade(int queryCount) throws Exception { - int graphCount = getTotalGraphsInCache(); - knnWarmup(Collections.singletonList(testIndex)); - assertTrue(getTotalGraphsInCache() > graphCount); - validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, queryCount, K); - clearCache(Collections.singletonList(testIndex)); assertEquals(0, getTotalGraphsInCache()); - validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, queryCount, K); } } diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java index 6b0bab9ee..bf4c8f7ec 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java @@ -6,11 +6,14 @@ package org.opensearch.knn.bwc; import org.junit.Assert; +import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.KNNEngine; +import java.util.List; import java.util.Map; import static org.opensearch.knn.TestUtils.KNN_ALGO_PARAM_EF_CONSTRUCTION_MIN_VALUE; @@ -52,6 +55,8 @@ public void testKNNIndexDefaultLegacyFieldMapping() throws Exception { createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { + // update index setting to allow build graph always since we test graph count that are loaded into memory + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); validateKNNIndexingOnUpgrade(NUM_DOCS); } } @@ -275,13 +280,14 @@ public void testNoParametersOnUpgrade() throws Exception { // KNN indexing tests when the cluster is upgraded to latest version public void validateKNNIndexingOnUpgrade(int numOfDocs) throws Exception { + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); + forceMergeKnnIndex(testIndex); QUERY_COUNT = numOfDocs; validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); - cleanUpCache(); + clearCache(List.of(testIndex)); DOC_ID = numOfDocs; addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); QUERY_COUNT = QUERY_COUNT + NUM_DOCS; - validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); forceMergeKnnIndex(testIndex); validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); deleteKNNIndex(testIndex); diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java index 8a965d2fa..db3555a8d 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java @@ -6,6 +6,7 @@ package org.opensearch.knn.bwc; import org.opensearch.common.settings.Settings; +import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.SpaceType; import java.util.Collections; @@ -34,6 +35,8 @@ public void testKNNWarmupDefaultLegacyFieldMapping() throws Exception { createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { + // update index setting to allow build graph always since we test graph count that are loaded into memory + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); validateKNNWarmupOnUpgrade(); } } @@ -65,6 +68,8 @@ public void testKNNWarmupDefaultMethodFieldMapping() throws Exception { createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKNNIndexMethodFieldMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { + // update index setting to allow build graph always since we test graph count that are loaded into memory + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); validateKNNWarmupOnUpgrade(); } } @@ -85,21 +90,26 @@ public void testKNNWarmupCustomMethodFieldMapping() throws Exception { } public void validateKNNWarmupOnUpgrade() throws Exception { + // update index setting to allow build graph always since we test graph count that are loaded into memory + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); int graphCount = getTotalGraphsInCache(); knnWarmup(Collections.singletonList(testIndex)); - assertTrue(getTotalGraphsInCache() > graphCount); + int totalGraph = getTotalGraphsInCache(); + assertTrue(totalGraph > graphCount); QUERY_COUNT = NUM_DOCS; validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); DOC_ID = NUM_DOCS; addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); + forceMergeKnnIndex(testIndex); int updatedGraphCount = getTotalGraphsInCache(); knnWarmup(Collections.singletonList(testIndex)); assertTrue(getTotalGraphsInCache() > updatedGraphCount); QUERY_COUNT = QUERY_COUNT + NUM_DOCS; + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); deleteKNNIndex(testIndex); } diff --git a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java index 24c674d0d..5ef3b1d97 100644 --- a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java +++ b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java @@ -25,6 +25,9 @@ public void testClearCache() throws Exception { createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); int docIdOld = 0; addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + int graphCount = getTotalGraphsInCache(); + knnWarmup(Collections.singletonList(testIndex)); + assertTrue(getTotalGraphsInCache() > graphCount); break; case UPGRADED: queryCnt = NUM_DOCS; @@ -42,14 +45,8 @@ public void testClearCache() throws Exception { // validation steps for Clear Cache API after upgrading all nodes from old version to new version public void validateClearCacheOnUpgrade(int queryCount) throws Exception { - int graphCount = getTotalGraphsInCache(); - knnWarmup(Collections.singletonList(testIndex)); - assertTrue(getTotalGraphsInCache() > graphCount); - validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, queryCount, K); - clearCache(Collections.singletonList(testIndex)); assertEquals(0, getTotalGraphsInCache()); - validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, queryCount, K); } } diff --git a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java index 9cbb99d87..c760fde2a 100644 --- a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java +++ b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java @@ -5,6 +5,9 @@ package org.opensearch.knn.bwc; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.index.KNNSettings; + import java.util.Collections; import static org.opensearch.knn.TestUtils.NODES_BWC_CLUSTER; @@ -33,6 +36,7 @@ public void testKNNWarmup() throws Exception { docIdMixed = 2 * NUM_DOCS; totalDocsCountMixed = 3 * NUM_DOCS; } + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); validateKNNWarmupOnUpgrade(totalDocsCountMixed, docIdMixed); break; case UPGRADED: diff --git a/release-notes/opensearch-knn.release-notes-2.18.0.0.md b/release-notes/opensearch-knn.release-notes-2.18.0.0.md index ec9114fde..9e85fd9ca 100644 --- a/release-notes/opensearch-knn.release-notes-2.18.0.0.md +++ b/release-notes/opensearch-knn.release-notes-2.18.0.0.md @@ -14,6 +14,7 @@ Compatible with OpenSearch 2.18.0 * Add support to build vector data structures greedily and perform exact search when there are no engine files [#1942](https://github.com/opensearch-project/k-NN/issues/1942) * Add CompressionLevel Calculation for PQ [#2200](https://github.com/opensearch-project/k-NN/pull/2200) * Remove FSDirectory dependency from native engine constructing side and deprecated FileWatcher [#2182](https://github.com/opensearch-project/k-NN/pull/2182) +* Update approximate_threshold to 15K documents [#2229](https://github.com/opensearch-project/k-NN/pull/2229) ### Bug Fixes * Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) * KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) diff --git a/src/main/java/org/opensearch/knn/index/KNNSettings.java b/src/main/java/org/opensearch/knn/index/KNNSettings.java index 6eff61d4b..b81a54124 100644 --- a/src/main/java/org/opensearch/knn/index/KNNSettings.java +++ b/src/main/java/org/opensearch/knn/index/KNNSettings.java @@ -98,7 +98,7 @@ public class KNNSettings { public static final boolean KNN_DEFAULT_FAISS_AVX2_DISABLED_VALUE = false; public static final boolean KNN_DEFAULT_FAISS_AVX512_DISABLED_VALUE = false; public static final String INDEX_KNN_DEFAULT_SPACE_TYPE = "l2"; - public static final Integer INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE = 0; + public static final Integer INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE = 15_000; public static final Integer INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MIN = -1; public static final Integer INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MAX = Integer.MAX_VALUE - 2; public static final String INDEX_KNN_DEFAULT_SPACE_TYPE_FOR_BINARY = "hamming"; diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormat.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormat.java index 520a9838d..dd326123e 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormat.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormat.java @@ -37,6 +37,10 @@ public NativeEngines990KnnVectorsFormat() { this(new Lucene99FlatVectorsFormat(new DefaultFlatVectorScorer())); } + public NativeEngines990KnnVectorsFormat(int approximateThreshold) { + this(new Lucene99FlatVectorsFormat(new DefaultFlatVectorScorer()), approximateThreshold); + } + public NativeEngines990KnnVectorsFormat(final FlatVectorsFormat flatVectorsFormat) { this(flatVectorsFormat, KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE); } diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/UnitTestCodec.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/UnitTestCodec.java index 6998efbc3..d651f410a 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/UnitTestCodec.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/UnitTestCodec.java @@ -21,6 +21,8 @@ * able to pick this class if its in test folder. Don't use this codec outside of testing */ public class UnitTestCodec extends FilterCodec { + private static final Integer BUILD_GRAPH_ALWAYS = 0; + public UnitTestCodec() { super("UnitTestCodec", KNNCodecVersion.current().getDefaultKnnCodecSupplier().get()); } @@ -30,7 +32,7 @@ public KnnVectorsFormat knnVectorsFormat() { return new PerFieldKnnVectorsFormat() { @Override public KnnVectorsFormat getKnnVectorsFormatForField(String field) { - return new NativeEngines990KnnVectorsFormat(); + return new NativeEngines990KnnVectorsFormat(BUILD_GRAPH_ALWAYS); } }; } diff --git a/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java b/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java index 06431bf07..587e80e5d 100644 --- a/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java +++ b/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java @@ -5,6 +5,7 @@ package org.opensearch.knn; +import org.opensearch.action.admin.indices.settings.put.UpdateSettingsRequest; import org.opensearch.core.action.ActionListener; import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.cluster.ClusterName; @@ -14,6 +15,7 @@ import org.opensearch.cluster.block.ClusterBlocks; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.xcontent.XContentHelper; +import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.query.KNNQueryBuilder; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.memory.NativeMemoryLoadStrategy; @@ -104,6 +106,14 @@ protected void createKnnIndexMapping(String indexName, String fieldName, Integer OpenSearchAssertions.assertAcked(client().admin().indices().putMapping(request).actionGet()); } + /** + * Create simple k-NN mapping with engine + */ + protected void updateIndexSetting(String indexName, Settings setting) { + UpdateSettingsRequest request = new UpdateSettingsRequest(setting, indexName); + OpenSearchAssertions.assertAcked(client().admin().indices().updateSettings(request).actionGet()); + } + /** * Create simple k-NN mapping which can be nested. * e.g. fieldPath = "a.b.c" will create mapping for "c" as knn_vector @@ -140,6 +150,18 @@ protected Settings getKNNDefaultIndexSettings() { return Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0).put("index.knn", true).build(); } + /** + * Get default k-NN settings for test cases with build graph always + */ + protected Settings getKNNDefaultIndexSettingsBuildsGraphAlways() { + return Settings.builder() + .put("number_of_shards", 1) + .put("number_of_replicas", 0) + .put("index.knn", true) + .put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0) + .build(); + } + /** * Add a k-NN doc to an index */ diff --git a/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java b/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java index 935a2e22c..385491cd9 100644 --- a/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java +++ b/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java @@ -20,6 +20,8 @@ * Integration tests to test Circuit Breaker functionality */ public class KNNCircuitBreakerIT extends KNNRestTestCase { + private static final Integer ALWAYS_BUILD_GRAPH = 0; + /** * To trip the circuit breaker, we will create two indices and index documents. Each index will be small enough so * that individually they fit into the cache, but together they do not. To prevent Lucene conditions where @@ -39,6 +41,7 @@ private void tripCb() throws Exception { .put("number_of_shards", 1) .put("number_of_replicas", numNodes - 1) .put("index.knn", true) + .put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, ALWAYS_BUILD_GRAPH) .build(); String indexName1 = INDEX_NAME + "1"; diff --git a/src/test/java/org/opensearch/knn/index/KNNESSettingsTestIT.java b/src/test/java/org/opensearch/knn/index/KNNESSettingsTestIT.java index 23a3196fb..20940f151 100644 --- a/src/test/java/org/opensearch/knn/index/KNNESSettingsTestIT.java +++ b/src/test/java/org/opensearch/knn/index/KNNESSettingsTestIT.java @@ -21,6 +21,9 @@ import static org.hamcrest.Matchers.containsString; public class KNNESSettingsTestIT extends KNNRestTestCase { + + public static final int ALWAYS_BUILD_GRAPH = 0; + /** * KNN Index writes should be blocked when the plugin disabled * @throws Exception Exception from test @@ -72,7 +75,7 @@ public void testQueriesPluginDisabled() throws Exception { } public void testItemRemovedFromCache_expiration() throws Exception { - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + createKnnIndex(INDEX_NAME, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(FIELD_NAME, 2)); updateClusterSettings(KNNSettings.KNN_CACHE_ITEM_EXPIRY_ENABLED, true); updateClusterSettings(KNNSettings.KNN_CACHE_ITEM_EXPIRY_TIME_MINUTES, "1m"); @@ -119,7 +122,7 @@ public void testUpdateIndexSetting() throws IOException { @SuppressWarnings("unchecked") public void testCacheRebuiltAfterUpdateIndexSettings() throws Exception { - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + createKnnIndex(INDEX_NAME, buildKNNIndexSettings(0), createKnnIndexMapping(FIELD_NAME, 2)); Float[] vector = { 6.0f, 6.0f }; addKnnDoc(INDEX_NAME, "1", FIELD_NAME, vector); diff --git a/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java b/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java index 18b9656e5..c25d2a390 100644 --- a/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java +++ b/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java @@ -14,6 +14,7 @@ import org.apache.lucene.util.StringHelper; import org.apache.lucene.util.Version; import org.mockito.Mockito; +import org.opensearch.common.settings.Settings; import org.opensearch.knn.KNNSingleNodeTestCase; import org.opensearch.index.IndexService; import org.opensearch.index.engine.Engine; @@ -71,6 +72,7 @@ public void testWarmup_emptyIndex() throws IOException { public void testWarmup_shardPresentInCache() throws InterruptedException, ExecutionException, IOException { IndexService indexService = createKNNIndex(testIndexName); createKnnIndexMapping(testIndexName, testFieldName, dimensions); + updateIndexSetting(testIndexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0).build()); addKnnDoc(testIndexName, "1", testFieldName, new Float[] { 2.5F, 3.5F }); searchKNNIndex(testIndexName, testFieldName, new float[] { 1.0f, 2.0f }, 1); @@ -85,6 +87,7 @@ public void testWarmup_shardPresentInCache() throws InterruptedException, Execut public void testWarmup_shardNotPresentInCache() throws InterruptedException, ExecutionException, IOException { IndexService indexService = createKNNIndex(testIndexName); createKnnIndexMapping(testIndexName, testFieldName, dimensions); + updateIndexSetting(testIndexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0).build()); IndexShard indexShard; KNNIndexShard knnIndexShard; @@ -106,6 +109,7 @@ public void testWarmup_shardNotPresentInCache() throws InterruptedException, Exe public void testGetAllEngineFileContexts() throws IOException, ExecutionException, InterruptedException { IndexService indexService = createKNNIndex(testIndexName); createKnnIndexMapping(testIndexName, testFieldName, dimensions); + updateIndexSetting(testIndexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0).build()); IndexShard indexShard = indexService.iterator().next(); KNNIndexShard knnIndexShard = new KNNIndexShard(indexShard); @@ -204,6 +208,7 @@ public void testClearCache_emptyIndex() { public void testClearCache_shardPresentInCache() { IndexService indexService = createKNNIndex(testIndexName); createKnnIndexMapping(testIndexName, testFieldName, dimensions); + updateIndexSetting(testIndexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0).build()); addKnnDoc(testIndexName, String.valueOf(randomInt()), testFieldName, new Float[] { randomFloat(), randomFloat() }); IndexShard indexShard = indexService.iterator().next(); diff --git a/src/test/java/org/opensearch/knn/index/NmslibIT.java b/src/test/java/org/opensearch/knn/index/NmslibIT.java index 0d8dd9b12..b342fdf3f 100644 --- a/src/test/java/org/opensearch/knn/index/NmslibIT.java +++ b/src/test/java/org/opensearch/knn/index/NmslibIT.java @@ -155,7 +155,7 @@ public void testEndToEnd() throws Exception { Map mappingMap = xContentBuilderToMap(builder); String mapping = builder.toString(); - createKnnIndex(indexName, mapping); + createKnnIndex(indexName, buildKNNIndexSettings(0), mapping); assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); // Index the test data diff --git a/src/test/java/org/opensearch/knn/index/OpenSearchIT.java b/src/test/java/org/opensearch/knn/index/OpenSearchIT.java index 481b307fa..7e375f884 100644 --- a/src/test/java/org/opensearch/knn/index/OpenSearchIT.java +++ b/src/test/java/org/opensearch/knn/index/OpenSearchIT.java @@ -113,7 +113,7 @@ public void testEndToEnd() throws Exception { Map mappingMap = xContentBuilderToMap(builder); String mapping = builder.toString(); - createKnnIndex(indexName, mapping); + createKnnIndex(indexName, buildKNNIndexSettings(0), mapping); assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); // Index the test data diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestClearCacheHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestClearCacheHandlerIT.java index 2618519f2..2b9f8a82f 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestClearCacheHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestClearCacheHandlerIT.java @@ -25,6 +25,7 @@ public class RestClearCacheHandlerIT extends KNNRestTestCase { private static final String TEST_FIELD = "test-field"; private static final int DIMENSIONS = 2; + public static final int ALWAYS_BUILD_GRAPH = 0; @SneakyThrows public void testNonExistentIndex() { @@ -53,7 +54,7 @@ public void testNotKnnIndex() { public void testClearCacheSingleIndex() { String testIndex = getTestName().toLowerCase(); int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + createKnnIndex(testIndex, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKnnDoc(testIndex, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); knnWarmup(Collections.singletonList(testIndex)); @@ -70,10 +71,10 @@ public void testClearCacheMultipleIndices() { String testIndex2 = getTestName().toLowerCase() + 1; int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndex1, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + createKnnIndex(testIndex1, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKnnDoc(testIndex1, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); - createKnnIndex(testIndex2, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + createKnnIndex(testIndex2, buildKNNIndexSettings(0), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKnnDoc(testIndex2, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); knnWarmup(Arrays.asList(testIndex1, testIndex2)); @@ -91,13 +92,13 @@ public void testClearCacheMultipleIndicesWithPatterns() { String testIndex3 = "abc" + getTestName().toLowerCase(); int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndex1, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + createKnnIndex(testIndex1, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKnnDoc(testIndex1, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); - createKnnIndex(testIndex2, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + createKnnIndex(testIndex2, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKnnDoc(testIndex2, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); - createKnnIndex(testIndex3, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + createKnnIndex(testIndex3, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKnnDoc(testIndex3, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); knnWarmup(Arrays.asList(testIndex1, testIndex2, testIndex3)); diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestKNNStatsHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestKNNStatsHandlerIT.java index 5d16fe59d..671185abf 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestKNNStatsHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestKNNStatsHandlerIT.java @@ -109,7 +109,7 @@ public void testStatsValueCheck() throws Exception { Integer knnQueryWithFilterCount0 = (Integer) nodeStats0.get(StatNames.KNN_QUERY_WITH_FILTER_REQUESTS.getName()); // Setup index - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + createKnnIndex(INDEX_NAME, buildKNNIndexSettings(0), createKnnIndexMapping(FIELD_NAME, 2)); // Index test document Float[] vector = { 6.0f, 6.0f }; @@ -392,7 +392,7 @@ public void testModelIndexingDegradedMetricsStats() throws Exception { * @throws IOException throws IOException */ public void testFieldByEngineStats() throws Exception { - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2, METHOD_HNSW, NMSLIB_NAME)); + createKnnIndex(INDEX_NAME, buildKNNIndexSettings(0), createKnnIndexMapping(FIELD_NAME, 2, METHOD_HNSW, NMSLIB_NAME)); putMappingRequest(INDEX_NAME, createKnnIndexMapping(FIELD_NAME_2, 3, METHOD_HNSW, LUCENE_NAME)); putMappingRequest(INDEX_NAME, createKnnIndexMapping(FIELD_NAME_3, 3, METHOD_HNSW, FAISS_NAME)); diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestKNNWarmupHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestKNNWarmupHandlerIT.java index d433cd285..bc1865522 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestKNNWarmupHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestKNNWarmupHandlerIT.java @@ -47,7 +47,7 @@ public void testEmptyIndex() throws Exception { public void testSingleIndex() throws Exception { int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndexName, getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName, buildKNNIndexSettings(0), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName, "1", testFieldName, new Float[] { 6.0f, 6.0f }); knnWarmup(Collections.singletonList(testIndexName)); @@ -58,10 +58,10 @@ public void testSingleIndex() throws Exception { public void testMultipleIndices() throws Exception { int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndexName + "1", getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName + "1", buildKNNIndexSettings(0), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName + "1", "1", testFieldName, new Float[] { 6.0f, 6.0f }); - createKnnIndex(testIndexName + "2", getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName + "2", buildKNNIndexSettings(0), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName + "2", "1", testFieldName, new Float[] { 6.0f, 6.0f }); knnWarmup(Arrays.asList(testIndexName + "1", testIndexName + "2")); diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNStatsHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNStatsHandlerIT.java index a78806b27..bea93e13c 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNStatsHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNStatsHandlerIT.java @@ -82,7 +82,7 @@ public void testStatsValueCheck() throws Exception { Integer missCount0 = (Integer) nodeStats0.get(StatNames.MISS_COUNT.getName()); // Setup index - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + createKnnIndex(INDEX_NAME, buildKNNIndexSettings(0), createKnnIndexMapping(FIELD_NAME, 2)); // Index test document Float[] vector = { 6.0f, 6.0f }; diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNWarmupHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNWarmupHandlerIT.java index e6345faba..e0fe9ec5c 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNWarmupHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNWarmupHandlerIT.java @@ -27,6 +27,7 @@ public class RestLegacyKNNWarmupHandlerIT extends KNNRestTestCase { + public static final int ALWAYS_BUILD_GRAPH = 0; private final String testIndexName = "test-index"; private final String testFieldName = "test-field"; private final int dimensions = 2; @@ -45,7 +46,7 @@ public void testNonKnnIndex() throws IOException { public void testEmptyIndex() throws Exception { int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndexName, getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(testFieldName, dimensions)); executeWarmupRequest(Collections.singletonList(testIndexName), KNNPlugin.LEGACY_KNN_BASE_URI); @@ -54,7 +55,7 @@ public void testEmptyIndex() throws Exception { public void testSingleIndex() throws Exception { int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndexName, getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName, "1", testFieldName, new Float[] { 6.0f, 6.0f }); executeWarmupRequest(Collections.singletonList(testIndexName), KNNPlugin.LEGACY_KNN_BASE_URI); @@ -65,10 +66,10 @@ public void testSingleIndex() throws Exception { public void testMultipleIndices() throws Exception { int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndexName + "1", getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName + "1", buildKNNIndexSettings(0), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName + "1", "1", testFieldName, new Float[] { 6.0f, 6.0f }); - createKnnIndex(testIndexName + "2", getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName + "2", buildKNNIndexSettings(0), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName + "2", "1", testFieldName, new Float[] { 6.0f, 6.0f }); executeWarmupRequest(Arrays.asList(testIndexName + "1", testIndexName + "2"), KNNPlugin.LEGACY_KNN_BASE_URI); diff --git a/src/test/java/org/opensearch/knn/plugin/transport/ClearCacheTransportActionTests.java b/src/test/java/org/opensearch/knn/plugin/transport/ClearCacheTransportActionTests.java index 3222a3eb7..d79f2f738 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/ClearCacheTransportActionTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/ClearCacheTransportActionTests.java @@ -33,7 +33,7 @@ public void testShardOperation() { KNNWarmupTransportAction knnWarmupTransportAction = node().injector().getInstance(KNNWarmupTransportAction.class); assertEquals(0, NativeMemoryCacheManager.getInstance().getIndicesCacheStats().size()); - IndexService indexService = createKNNIndex(testIndex); + IndexService indexService = createIndex(testIndex, getKNNDefaultIndexSettingsBuildsGraphAlways()); createKnnIndexMapping(testIndex, TEST_FIELD, DIMENSIONS); addKnnDoc(testIndex, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); ShardRouting shardRouting = indexService.iterator().next().routingEntry(); diff --git a/src/test/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportActionTests.java b/src/test/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportActionTests.java index 1f72f78a7..f4b52246d 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportActionTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportActionTests.java @@ -37,7 +37,7 @@ public void testShardOperation() throws IOException, ExecutionException, Interru KNNWarmupTransportAction knnWarmupTransportAction = node().injector().getInstance(KNNWarmupTransportAction.class); assertEquals(0, NativeMemoryCacheManager.getInstance().getIndicesCacheStats().size()); - indexService = createKNNIndex(testIndexName); + indexService = createIndex(testIndexName, getKNNDefaultIndexSettingsBuildsGraphAlways()); createKnnIndexMapping(testIndexName, testFieldName, dimensions); shardRouting = indexService.iterator().next().routingEntry(); diff --git a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java index 2c2efed3d..93f6b0792 100644 --- a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java +++ b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java @@ -1309,6 +1309,7 @@ public void addKNNDocs(String testIndex, String testField, int dimension, int fi Arrays.fill(indexVector, (float) i); addKnnDoc(testIndex, Integer.toString(i), testField, indexVector); } + flushIndex(testIndex); } public void addKNNByteDocs(String testIndex, String testField, int dimension, int firstDocID, int numDocs) throws IOException { @@ -1317,6 +1318,7 @@ public void addKNNByteDocs(String testIndex, String testField, int dimension, in Arrays.fill(indexVector, (byte) i); addKnnDoc(testIndex, Integer.toString(i), testField, indexVector); } + flushIndex(testIndex); } public void validateKNNSearch(String testIndex, String testField, int dimension, int numDocs, int k) throws Exception { @@ -1792,6 +1794,13 @@ protected void refreshIndex(final String index) throws IOException { assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); } + protected void flushIndex(final String index) throws IOException { + Request request = new Request("POST", "/" + index + "/_flush"); + + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + protected void addKnnDocWithAttributes(String docId, float[] vector, Map fieldValues) throws IOException { Request request = new Request("POST", "/" + INDEX_NAME + "/_doc/" + docId + "?refresh=true"); From 7d87c52b4827770f5b4d25e7e6fc8231b83f809f Mon Sep 17 00:00:00 2001 From: Vijayan Balasubramanian Date: Thu, 24 Oct 2024 08:59:13 -0700 Subject: [PATCH 50/59] update warmup rolling upgrade scenario (#2232) Signed-off-by: Vijayan Balasubramanian --- .../java/org/opensearch/knn/bwc/WarmupIT.java | 45 +++++-------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java index c760fde2a..c8f9ad6e3 100644 --- a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java +++ b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java @@ -9,13 +9,13 @@ import org.opensearch.knn.index.KNNSettings; import java.util.Collections; +import java.util.List; import static org.opensearch.knn.TestUtils.NODES_BWC_CLUSTER; public class WarmupIT extends AbstractRollingUpgradeTestCase { private static final String TEST_FIELD = "test-field"; private static final int DIMENSIONS = 5; - private static final int K = 5; private static final int NUM_DOCS = 10; public void testKNNWarmup() throws Exception { @@ -23,45 +23,22 @@ public void testKNNWarmup() throws Exception { switch (getClusterType()) { case OLD: createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); - int docIdOld = 0; - addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 0, NUM_DOCS); break; case MIXED: - int totalDocsCountMixed; - int docIdMixed; - if (isFirstMixedRound()) { - docIdMixed = NUM_DOCS; - totalDocsCountMixed = 2 * NUM_DOCS; - } else { - docIdMixed = 2 * NUM_DOCS; - totalDocsCountMixed = 3 * NUM_DOCS; - } - updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); - validateKNNWarmupOnUpgrade(totalDocsCountMixed, docIdMixed); + int graphCount = getTotalGraphsInCache(); + knnWarmup(Collections.singletonList(testIndex)); + assertTrue(getTotalGraphsInCache() > graphCount); + clearCache(List.of(testIndex)); break; case UPGRADED: - int docIdUpgraded = 3 * NUM_DOCS; - int totalDocsCountUpgraded = 4 * NUM_DOCS; - validateKNNWarmupOnUpgrade(totalDocsCountUpgraded, docIdUpgraded); - + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, NUM_DOCS); + int updatedGraphCount = getTotalGraphsInCache(); + knnWarmup(Collections.singletonList(testIndex)); + assertTrue(getTotalGraphsInCache() > updatedGraphCount); deleteKNNIndex(testIndex); } - - } - - // validation steps for KNN Warmup after upgrading each node from old version to new version - public void validateKNNWarmupOnUpgrade(int totalDocsCount, int docId) throws Exception { - int graphCount = getTotalGraphsInCache(); - knnWarmup(Collections.singletonList(testIndex)); - assertTrue(getTotalGraphsInCache() > graphCount); - - addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docId, NUM_DOCS); - - int updatedGraphCount = getTotalGraphsInCache(); - knnWarmup(Collections.singletonList(testIndex)); - assertTrue(getTotalGraphsInCache() > updatedGraphCount); - - validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, totalDocsCount, K); } } From cbf90f5a456108fa49ffb85020f3edf7e307420e Mon Sep 17 00:00:00 2001 From: Vijayan Balasubramanian Date: Fri, 25 Oct 2024 11:12:21 -0700 Subject: [PATCH 51/59] Update bwc test to update index setting (#2236) Signed-off-by: Vijayan Balasubramanian --- CHANGELOG.md | 1 + .../org/opensearch/knn/bwc/ClearCacheIT.java | 9 +++++- .../org/opensearch/knn/bwc/IndexingIT.java | 16 +++++------ .../java/org/opensearch/knn/bwc/WarmupIT.java | 22 +++++++++++---- .../org/opensearch/knn/bwc/ClearCacheIT.java | 7 ++++- .../java/org/opensearch/knn/bwc/WarmupIT.java | 5 +++- .../org/opensearch/knn/KNNRestTestCase.java | 28 +++++++++++++++++-- 7 files changed, 69 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5fcadee3..811dc9856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,4 +21,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Infrastructure ### Documentation ### Maintenance +* Select index settings based on cluster version[2236](https://github.com/opensearch-project/k-NN/pull/2236) ### Refactoring diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java index f93f2f883..e4ac25000 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java @@ -5,7 +5,10 @@ package org.opensearch.knn.bwc; +import org.opensearch.common.settings.Settings; + import java.util.Collections; + import static org.opensearch.knn.TestUtils.NODES_BWC_CLUSTER; public class ClearCacheIT extends AbstractRestartUpgradeTestCase { @@ -20,7 +23,11 @@ public class ClearCacheIT extends AbstractRestartUpgradeTestCase { public void testClearCache() throws Exception { waitForClusterHealthGreen(NODES_BWC_CLUSTER); if (isRunningAgainstOldCluster()) { - createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + // if approximate threshold is supported, set value to 0, to build graph always + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); + createKnnIndex(testIndex, indexSettings, createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docId, NUM_DOCS); queryCnt = NUM_DOCS; int graphCount = getTotalGraphsInCache(); diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java index bf4c8f7ec..7d2b3807a 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java @@ -143,15 +143,15 @@ public void testKNNIndexCustomLegacyFieldMapping() throws Exception { // When the cluster is in old version, create a KNN index with custom legacy field mapping settings // and add documents into that index if (isRunningAgainstOldCluster()) { - createKnnIndex( - testIndex, - createKNNIndexCustomLegacyFieldMappingSettings( - SpaceType.LINF, - KNN_ALGO_PARAM_M_MIN_VALUE, - KNN_ALGO_PARAM_EF_CONSTRUCTION_MIN_VALUE - ), - createKnnIndexMapping(TEST_FIELD, DIMENSIONS) + Settings.Builder indexMappingSettings = createKNNIndexCustomLegacyFieldMappingIndexSettingsBuilder( + SpaceType.LINF, + KNN_ALGO_PARAM_M_MIN_VALUE, + KNN_ALGO_PARAM_EF_CONSTRUCTION_MIN_VALUE ); + if (isApproximateThresholdSupported(getBWCVersion())) { + indexMappingSettings.put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0); + } + createKnnIndex(testIndex, indexMappingSettings.build(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { validateKNNIndexingOnUpgrade(NUM_DOCS); diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java index db3555a8d..64c55f6fd 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java @@ -32,7 +32,10 @@ public void testKNNWarmupDefaultLegacyFieldMapping() throws Exception { waitForClusterHealthGreen(NODES_BWC_CLUSTER); if (isRunningAgainstOldCluster()) { - createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); + createKnnIndex(testIndex, indexSettings, createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { // update index setting to allow build graph always since we test graph count that are loaded into memory @@ -48,13 +51,16 @@ public void testKNNWarmupCustomLegacyFieldMapping() throws Exception { // When the cluster is in old version, create a KNN index with custom legacy field mapping settings // and add documents into that index if (isRunningAgainstOldCluster()) { - Settings indexMappingSettings = createKNNIndexCustomLegacyFieldMappingSettings( + Settings.Builder indexMappingSettings = createKNNIndexCustomLegacyFieldMappingIndexSettingsBuilder( SpaceType.LINF, KNN_ALGO_PARAM_M_MIN_VALUE, KNN_ALGO_PARAM_EF_CONSTRUCTION_MIN_VALUE ); + if (isApproximateThresholdSupported(getBWCVersion())) { + indexMappingSettings.put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0); + } String indexMapping = createKnnIndexMapping(TEST_FIELD, DIMENSIONS); - createKnnIndex(testIndex, indexMappingSettings, indexMapping); + createKnnIndex(testIndex, indexMappingSettings.build(), indexMapping); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { validateKNNWarmupOnUpgrade(); @@ -65,7 +71,10 @@ public void testKNNWarmupCustomLegacyFieldMapping() throws Exception { // space_type : "l2", engine : "nmslib", m : 16, ef_construction : 512 public void testKNNWarmupDefaultMethodFieldMapping() throws Exception { if (isRunningAgainstOldCluster()) { - createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKNNIndexMethodFieldMapping(TEST_FIELD, DIMENSIONS)); + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); + createKnnIndex(testIndex, indexSettings, createKNNIndexMethodFieldMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { // update index setting to allow build graph always since we test graph count that are loaded into memory @@ -78,9 +87,12 @@ public void testKNNWarmupDefaultMethodFieldMapping() throws Exception { // space_type : "innerproduct", engine : "faiss", m : 50, ef_construction : 1024 public void testKNNWarmupCustomMethodFieldMapping() throws Exception { if (isRunningAgainstOldCluster()) { + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); createKnnIndex( testIndex, - getKNNDefaultIndexSettings(), + indexSettings, createKNNIndexCustomMethodFieldMapping(TEST_FIELD, DIMENSIONS, SpaceType.INNER_PRODUCT, FAISS_NAME, M, EF_CONSTRUCTION) ); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); diff --git a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java index 5ef3b1d97..0e8748eb6 100644 --- a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java +++ b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java @@ -5,6 +5,8 @@ package org.opensearch.knn.bwc; +import org.opensearch.common.settings.Settings; + import java.util.Collections; import static org.opensearch.knn.TestUtils.NODES_BWC_CLUSTER; @@ -22,7 +24,10 @@ public void testClearCache() throws Exception { waitForClusterHealthGreen(NODES_BWC_CLUSTER); switch (getClusterType()) { case OLD: - createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); + createKnnIndex(testIndex, indexSettings, createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); int docIdOld = 0; addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); int graphCount = getTotalGraphsInCache(); diff --git a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java index c8f9ad6e3..c665c3e86 100644 --- a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java +++ b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java @@ -22,7 +22,10 @@ public void testKNNWarmup() throws Exception { waitForClusterHealthGreen(NODES_BWC_CLUSTER); switch (getClusterType()) { case OLD: - createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); + createKnnIndex(testIndex, indexSettings, createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 0, NUM_DOCS); break; case MIXED: diff --git a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java index 93f6b0792..8a4885cfe 100644 --- a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java +++ b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java @@ -13,6 +13,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.net.URIBuilder; +import org.opensearch.Version; import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.core.xcontent.DeprecationHandler; @@ -64,6 +65,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.PriorityQueue; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -1345,15 +1347,22 @@ public void validateKNNSearch(String testIndex, String testField, int dimension, } } - protected Settings createKNNIndexCustomLegacyFieldMappingSettings(SpaceType spaceType, Integer m, Integer ef_construction) { + protected Settings.Builder createKNNIndexCustomLegacyFieldMappingIndexSettingsBuilder( + SpaceType spaceType, + Integer m, + Integer ef_construction + ) { return Settings.builder() .put(NUMBER_OF_SHARDS, 1) .put(NUMBER_OF_REPLICAS, 0) .put(INDEX_KNN, true) .put(KNNSettings.KNN_SPACE_TYPE, spaceType.getValue()) .put(KNNSettings.KNN_ALGO_PARAM_M, m) - .put(KNNSettings.KNN_ALGO_PARAM_EF_CONSTRUCTION, ef_construction) - .build(); + .put(KNNSettings.KNN_ALGO_PARAM_EF_CONSTRUCTION, ef_construction); + } + + protected Settings createKNNIndexCustomLegacyFieldMappingIndexSettings(SpaceType spaceType, Integer m, Integer ef_construction) { + return createKNNIndexCustomLegacyFieldMappingIndexSettingsBuilder(spaceType, m, ef_construction).build(); } public String createKNNIndexMethodFieldMapping(String fieldName, Integer dimensions) throws IOException { @@ -1859,4 +1868,17 @@ protected XContentBuilder buildSearchQuery(String fieldName, int k, float[] vect builder.endObject().endObject().endObject().endObject(); return builder; } + + // approximate threshold parameter is only supported on or after V_2_18_0 + protected boolean isApproximateThresholdSupported(final Optional bwcVersion) { + if (bwcVersion.isEmpty()) { + return false; + } + String versionString = bwcVersion.get(); + if (versionString.endsWith("-SNAPSHOT")) { + versionString = versionString.substring(0, versionString.length() - 9); + } + final Version version = Version.fromString(versionString); + return version.onOrAfter(Version.V_2_18_0); + } } From a029fa8c9664d6c37b2c78dbb22e0d16b7b8878a Mon Sep 17 00:00:00 2001 From: Doo Yong Kim <0ctopus13prime@gmail.com> Date: Sun, 3 Nov 2024 15:33:01 -0800 Subject: [PATCH 52/59] Added 'j' option to use multiple cpus to build JNI library. (#2244) Signed-off-by: Dooyong Kim Co-authored-by: Dooyong Kim --- scripts/build.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) mode change 100644 => 100755 scripts/build.sh diff --git a/scripts/build.sh b/scripts/build.sh old mode 100644 new mode 100755 index be7304ee5..203b76c99 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -18,10 +18,11 @@ function usage() { echo -e "-p PLATFORM\t[Optional] Platform, ignored." echo -e "-a ARCHITECTURE\t[Optional] Build architecture, ignored." echo -e "-o OUTPUT\t[Optional] Output path, default is 'artifacts'." + echo -e "-j NPROC_COUNT\t[Optional] Number of CPUs to use when building JNI library. Default is 1." echo -e "-h help" } -while getopts ":h:v:q:s:o:p:a:" arg; do +while getopts ":h:v:q:s:o:p:a:j:" arg; do case $arg in h) usage @@ -45,6 +46,9 @@ while getopts ":h:v:q:s:o:p:a:" arg; do a) ARCHITECTURE=$OPTARG ;; + j) + NPROC_COUNT=$OPTARG + ;; :) echo "Error: -${OPTARG} requires an argument" usage @@ -118,7 +122,7 @@ fi # Build k-NN lib and plugin through gradle tasks cd $work_dir ./gradlew build --no-daemon --refresh-dependencies -x integTest -x test -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER -Dbuild.lib.commit_patches=false -./gradlew :buildJniLib -Davx512.enabled=false -Davx2.enabled=false -Dbuild.lib.commit_patches=false +./gradlew :buildJniLib -Davx512.enabled=false -Davx2.enabled=false -Dbuild.lib.commit_patches=false -Dnproc.count=${NPROC_COUNT:-1} if [ "$PLATFORM" != "windows" ] && [ "$ARCHITECTURE" = "x64" ]; then echo "Building k-NN library after enabling AVX2" From 64bae9243209a629b19a5253223d7c5b1a5ce5c5 Mon Sep 17 00:00:00 2001 From: Doo Yong Kim <0ctopus13prime@gmail.com> Date: Tue, 5 Nov 2024 09:59:02 -0800 Subject: [PATCH 53/59] Introduced writing layer, getting rid of writing logic that uses an absolute path in the filesystem. (#2241) Signed-off-by: Dooyong Kim Co-authored-by: Dooyong Kim --- CHANGELOG.md | 1 + jni/cmake/init-nmslib.cmake | 1 + jni/include/commons.h | 6 + jni/include/faiss_index_service.h | 63 +- jni/include/faiss_methods.h | 14 +- jni/include/faiss_stream_support.h | 37 +- jni/include/faiss_wrapper.h | 35 +- jni/include/jni_util.h | 35 +- jni/include/memory_util.h | 23 + jni/include/native_engines_stream_support.h | 133 +- jni/include/nmslib_stream_support.h | 28 +- jni/include/nmslib_wrapper.h | 2 +- .../org_opensearch_knn_jni_FaissService.h | 60 +- .../org_opensearch_knn_jni_NmslibService.h | 4 +- jni/include/parameter_utils.h | 39 + ...-apis-in-Hnsw-with-streaming-interfa.patch | 165 ++ jni/src/commons.cpp | 3 - jni/src/faiss_index_service.cpp | 89 +- jni/src/faiss_methods.cpp | 9 +- jni/src/faiss_wrapper.cpp | 131 +- jni/src/jni_util.cpp | 20 +- jni/src/nmslib_wrapper.cpp | 54 +- .../org_opensearch_knn_jni_FaissService.cpp | 202 +- .../org_opensearch_knn_jni_NmslibService.cpp | 4 +- jni/tests/faiss_index_service_test.cpp | 17 +- jni/tests/faiss_stream_support_test.cpp | 10 +- jni/tests/faiss_wrapper_test.cpp | 398 ++-- jni/tests/mocks/faiss_index_service_mock.h | 11 +- jni/tests/mocks/faiss_methods_mock.h | 4 +- jni/tests/native_stream_support_util.h | 70 +- jni/tests/nmslib_stream_support_test.cpp | 174 +- jni/tests/nmslib_wrapper_test.cpp | 194 +- jni/tests/test_util.cpp | 10 +- jni/tests/test_util.h | 6 +- .../DefaultIndexBuildStrategy.java | 4 +- .../MemOptimizedNativeIndexBuildStrategy.java | 3 +- .../codec/nativeindex/NativeIndexWriter.java | 76 +- .../nativeindex/model/BuildIndexParams.java | 3 +- .../index/store/IndexOutputWithBuffer.java | 40 + .../org/opensearch/knn/jni/FaissService.java | 31 +- .../org/opensearch/knn/jni/JNIService.java | 54 +- .../org/opensearch/knn/jni/NmslibService.java | 24 +- .../common/RaisingIOExceptionIndexInput.java | 51 + .../common/RasingIOExceptionIndexOutput.java | 41 + .../knn/index/codec/KNNCodecTestCase.java | 113 +- .../knn/index/codec/KNNCodecTestUtil.java | 73 +- .../DefaultIndexBuildStrategyTests.java | 17 +- ...ptimizedNativeIndexBuildStrategyTests.java | 14 +- .../memory/NativeMemoryAllocationTests.java | 200 +- .../memory/NativeMemoryLoadStrategyTests.java | 175 +- .../opensearch/knn/jni/JNIServiceTests.java | 2039 ++++++++++------- .../knn/training/TrainingJobTests.java | 34 +- .../java/org/opensearch/knn/TestUtils.java | 28 +- 53 files changed, 3142 insertions(+), 1930 deletions(-) create mode 100644 jni/include/memory_util.h create mode 100644 jni/include/parameter_utils.h create mode 100644 jni/patches/nmslib/0004-Added-a-new-save-apis-in-Hnsw-with-streaming-interfa.patch create mode 100644 src/main/java/org/opensearch/knn/index/store/IndexOutputWithBuffer.java create mode 100644 src/test/java/org/opensearch/knn/common/RaisingIOExceptionIndexInput.java create mode 100644 src/test/java/org/opensearch/knn/common/RasingIOExceptionIndexOutput.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 811dc9856..c66eb2184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased 2.x](https://github.com/opensearch-project/k-NN/compare/2.18...2.x) ### Features ### Enhancements +- Introduced a writing layer in native engines where relies on the writing interface to process IO. (#2241)[https://github.com/opensearch-project/k-NN/pull/2241] ### Bug Fixes ### Infrastructure ### Documentation diff --git a/jni/cmake/init-nmslib.cmake b/jni/cmake/init-nmslib.cmake index 2554b2bd7..a7c3f7d93 100644 --- a/jni/cmake/init-nmslib.cmake +++ b/jni/cmake/init-nmslib.cmake @@ -19,6 +19,7 @@ if(NOT DEFINED APPLY_LIB_PATCHES OR "${APPLY_LIB_PATCHES}" STREQUAL true) list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0001-Initialize-maxlevel-during-add-from-enterpoint-level.patch") list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0002-Adds-ability-to-pass-ef-parameter-in-the-query-for-h.patch") list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0003-Added-streaming-apis-for-vector-index-loading-in-Hnsw.patch") + list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0004-Added-a-new-save-apis-in-Hnsw-with-streaming-interfa.patch") # Get patch id of the last commit execute_process(COMMAND sh -c "git --no-pager show HEAD | git patch-id --stable" OUTPUT_VARIABLE PATCH_ID_OUTPUT_FROM_COMMIT WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib) diff --git a/jni/include/commons.h b/jni/include/commons.h index 3f1ee19a3..38b00cc5d 100644 --- a/jni/include/commons.h +++ b/jni/include/commons.h @@ -8,6 +8,10 @@ * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ + +#ifndef OPENSEARCH_KNN_COMMONS_H +#define OPENSEARCH_KNN_COMMONS_H + #include "jni_util.h" #include namespace knn_jni { @@ -99,3 +103,5 @@ namespace knn_jni { int getIntegerMethodParameter(JNIEnv *, knn_jni::JNIUtilInterface *, std::unordered_map, std::string, int); } } + +#endif \ No newline at end of file diff --git a/jni/include/faiss_index_service.h b/jni/include/faiss_index_service.h index 29ec90e80..d96c3e755 100644 --- a/jni/include/faiss_index_service.h +++ b/jni/include/faiss_index_service.h @@ -16,8 +16,10 @@ #include #include "faiss/MetricType.h" +#include "faiss/impl/io.h" #include "jni_util.h" #include "faiss_methods.h" +#include "faiss_stream_support.h" #include namespace knn_jni { @@ -30,7 +32,8 @@ namespace faiss_wrapper { */ class IndexService { public: - IndexService(std::unique_ptr faissMethods); + explicit IndexService(std::unique_ptr faissMethods); + /** * Initialize index * @@ -45,6 +48,7 @@ class IndexService { * @return memory address of the native index object */ virtual jlong initIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, faiss::MetricType metric, std::string indexDescription, int dim, int numVectors, int threadCount, std::unordered_map parameters); + /** * Add vectors to index * @@ -55,29 +59,34 @@ class IndexService { * @param idMapAddress memory address of the native index object */ virtual void insertToIndex(int dim, int numIds, int threadCount, int64_t vectorsAddress, std::vector &ids, jlong idMapAddress); + /** * Write index to disk * - * @param threadCount number of thread count to be used while adding data - * @param indexPath path to write index - * @param idMap memory address of the native index object + * @param writer IOWriter implementation doing IO processing. + * In most cases, it is expected to have underlying Lucene's IndexOuptut. + * @param idMapAddress memory address of the native index object */ - virtual void writeIndex(std::string indexPath, jlong idMapAddress); + virtual void writeIndex(faiss::IOWriter* writer, jlong idMapAddress); + virtual ~IndexService() = default; + protected: virtual void allocIndex(faiss::Index * index, size_t dim, size_t numVectors); + std::unique_ptr faissMethods; -}; +}; // class IndexService /** * A class to provide operations on index * This class should evolve to have only cpp object but not jni object */ -class BinaryIndexService : public IndexService { +class BinaryIndexService final : public IndexService { public: //TODO Remove dependency on JNIUtilInterface and JNIEnv //TODO Reduce the number of parameters - BinaryIndexService(std::unique_ptr faissMethods); + explicit BinaryIndexService(std::unique_ptr faissMethods); + /** * Initialize index * @@ -91,7 +100,8 @@ class BinaryIndexService : public IndexService { * @param parameters parameters to be applied to faiss index * @return memory address of the native index object */ - virtual jlong initIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, faiss::MetricType metric, std::string indexDescription, int dim, int numVectors, int threadCount, std::unordered_map parameters) override; + jlong initIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, faiss::MetricType metric, std::string indexDescription, int dim, int numVectors, int threadCount, std::unordered_map parameters) final; + /** * Add vectors to index * @@ -106,7 +116,8 @@ class BinaryIndexService : public IndexService { * @param idMap a map of document id and vector id * @param parameters parameters to be applied to faiss index */ - virtual void insertToIndex(int dim, int numIds, int threadCount, int64_t vectorsAddress, std::vector &ids, jlong idMapAddress) override; + void insertToIndex(int dim, int numIds, int threadCount, int64_t vectorsAddress, std::vector &ids, jlong idMapAddress) final; + /** * Write index to disk * @@ -119,23 +130,23 @@ class BinaryIndexService : public IndexService { * @param idMap a map of document id and vector id * @param parameters parameters to be applied to faiss index */ - virtual void writeIndex(std::string indexPath, jlong idMapAddress) override; - virtual ~BinaryIndexService() = default; + void writeIndex(faiss::IOWriter* writer, jlong idMapAddress) final; + protected: - virtual void allocIndex(faiss::Index * index, size_t dim, size_t numVectors) override; -}; + void allocIndex(faiss::Index * index, size_t dim, size_t numVectors) final; +}; // class BinaryIndexService /** * A class to provide operations on index * This class should evolve to have only cpp object but not jni object */ -class ByteIndexService : public IndexService { +class ByteIndexService final : public IndexService { public: //TODO Remove dependency on JNIUtilInterface and JNIEnv //TODO Reduce the number of parameters - ByteIndexService(std::unique_ptr faissMethods); + explicit ByteIndexService(std::unique_ptr faissMethods); -/** + /** * Initialize index * * @param jniUtil jni util @@ -148,7 +159,8 @@ class ByteIndexService : public IndexService { * @param parameters parameters to be applied to faiss index * @return memory address of the native index object */ - virtual jlong initIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, faiss::MetricType metric, std::string indexDescription, int dim, int numVectors, int threadCount, std::unordered_map parameters) override; + jlong initIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, faiss::MetricType metric, std::string indexDescription, int dim, int numVectors, int threadCount, std::unordered_map parameters) final; + /** * Add vectors to index * @@ -163,7 +175,8 @@ class ByteIndexService : public IndexService { * @param idMap a map of document id and vector id * @param parameters parameters to be applied to faiss index */ - virtual void insertToIndex(int dim, int numIds, int threadCount, int64_t vectorsAddress, std::vector &ids, jlong idMapAddress) override; + void insertToIndex(int dim, int numIds, int threadCount, int64_t vectorsAddress, std::vector &ids, jlong idMapAddress) final; + /** * Write index to disk * @@ -176,14 +189,14 @@ class ByteIndexService : public IndexService { * @param idMap a map of document id and vector id * @param parameters parameters to be applied to faiss index */ - virtual void writeIndex(std::string indexPath, jlong idMapAddress) override; - virtual ~ByteIndexService() = default; -protected: - virtual void allocIndex(faiss::Index * index, size_t dim, size_t numVectors) override; -}; + void writeIndex(faiss::IOWriter* writer, jlong idMapAddress) final; + + protected: + void allocIndex(faiss::Index * index, size_t dim, size_t numVectors) final; +}; // class ByteIndexService } } -#endif //OPENSEARCH_KNN_FAISS_INDEX_SERVICE_H \ No newline at end of file +#endif //OPENSEARCH_KNN_FAISS_INDEX_SERVICE_H diff --git a/jni/include/faiss_methods.h b/jni/include/faiss_methods.h index 38d8d756a..d8f14d03f 100644 --- a/jni/include/faiss_methods.h +++ b/jni/include/faiss_methods.h @@ -10,6 +10,7 @@ #ifndef OPENSEARCH_KNN_FAISS_METHODS_H #define OPENSEARCH_KNN_FAISS_METHODS_H +#include "faiss/impl/io.h" #include "faiss/Index.h" #include "faiss/IndexBinary.h" #include "faiss/IndexIDMap.h" @@ -26,14 +27,21 @@ namespace faiss_wrapper { class FaissMethods { public: FaissMethods() = default; + virtual faiss::Index* indexFactory(int d, const char* description, faiss::MetricType metric); + virtual faiss::IndexBinary* indexBinaryFactory(int d, const char* description); + virtual faiss::IndexIDMapTemplate* indexIdMap(faiss::Index* index); + virtual faiss::IndexIDMapTemplate* indexBinaryIdMap(faiss::IndexBinary* index); - virtual void writeIndex(const faiss::Index* idx, const char* fname); - virtual void writeIndexBinary(const faiss::IndexBinary* idx, const char* fname); + + virtual void writeIndex(const faiss::Index* idx, faiss::IOWriter* writer); + + virtual void writeIndexBinary(const faiss::IndexBinary* idx, faiss::IOWriter* writer); + virtual ~FaissMethods() = default; -}; +}; // class FaissMethods } //namespace faiss_wrapper } //namespace knn_jni diff --git a/jni/include/faiss_stream_support.h b/jni/include/faiss_stream_support.h index a12d66ae9..eb1b2a404 100644 --- a/jni/include/faiss_stream_support.h +++ b/jni/include/faiss_stream_support.h @@ -15,6 +15,7 @@ #include "faiss/impl/io.h" #include "jni_util.h" #include "native_engines_stream_support.h" +#include "parameter_utils.h" #include #include @@ -34,7 +35,7 @@ class FaissOpenSearchIOReader final : public faiss::IOReader { public: explicit FaissOpenSearchIOReader(NativeEngineIndexInputMediator *_mediator) : faiss::IOReader(), - mediator(_mediator) { + mediator(knn_jni::util::ParameterCheck::require_non_null(_mediator, "mediator")) { name = "FaissOpenSearchIOReader"; } @@ -56,6 +57,40 @@ class FaissOpenSearchIOReader final : public faiss::IOReader { }; // class FaissOpenSearchIOReader +/** + * A glue component inheriting IOWriter to delegate IO processing down to the given + * mediator. The mediator is expected to do write bytes via the provided Lucene's IndexOutput. + */ +class FaissOpenSearchIOWriter final : public faiss::IOWriter { + public: + explicit FaissOpenSearchIOWriter(NativeEngineIndexOutputMediator *_mediator) + : faiss::IOWriter(), + mediator(knn_jni::util::ParameterCheck::require_non_null(_mediator, "mediator")) { + name = "FaissOpenSearchIOWriter"; + } + + size_t operator()(const void *ptr, size_t size, size_t nitems) final { + const auto writeBytes = size * nitems; + if (writeBytes > 0) { + mediator->writeBytes(reinterpret_cast(ptr), writeBytes); + } + return nitems; + } + + // return a file number that can be memory-mapped + int filedescriptor() final { + throw std::runtime_error("filedescriptor() is not supported in FaissOpenSearchIOWriter."); + } + + void flush() { + mediator->flush(); + } + + private: + NativeEngineIndexOutputMediator *mediator; +}; // class FaissOpenSearchIOWriter + + } } diff --git a/jni/include/faiss_wrapper.h b/jni/include/faiss_wrapper.h index 8ffce4ad1..e48e6faa9 100644 --- a/jni/include/faiss_wrapper.h +++ b/jni/include/faiss_wrapper.h @@ -14,6 +14,7 @@ #include "jni_util.h" #include "faiss_index_service.h" +#include "faiss_stream_support.h" #include namespace knn_jni { @@ -22,25 +23,25 @@ namespace knn_jni { void InsertToIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jintArray idsJ, jlong vectorsAddressJ, jint dimJ, jlong indexAddr, jint threadCount, IndexService *indexService); - void WriteIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jstring indexPathJ, jlong indexAddr, IndexService *indexService); + void WriteIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jobject output, jlong indexAddr, IndexService *indexService); // Create an index with ids and vectors. Instead of creating a new index, this function creates the index // based off of the template index passed in. The index is serialized to indexPathJ. void CreateIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, jstring indexPathJ, jbyteArray templateIndexJ, + jlong vectorsAddressJ, jint dimJ, jobject output, jbyteArray templateIndexJ, jobject parametersJ); // Create an index with ids and vectors. Instead of creating a new index, this function creates the index // based off of the template index passed in. The index is serialized to indexPathJ. void CreateBinaryIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, jstring indexPathJ, jbyteArray templateIndexJ, - jobject parametersJ); + jlong vectorsAddressJ, jint dimJ, jobject output, jbyteArray templateIndexJ, + jobject parametersJ); // Create a index with ids and byte vectors. Instead of creating a new index, this function creates the index // based off of the template index passed in. The index is serialized to indexPathJ. void CreateByteIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, jstring indexPathJ, jbyteArray templateIndexJ, - jobject parametersJ); + jlong vectorsAddressJ, jint dimJ, jobject output, jbyteArray templateIndexJ, + jobject parametersJ); // Load an index from indexPathJ into memory. // @@ -74,28 +75,28 @@ namespace knn_jni { // Sets the sharedIndexState for an index void SetSharedIndexState(jlong indexPointerJ, jlong shareIndexStatePointerJ); - /** + /** * Execute a query against the index located in memory at indexPointerJ - * + * * Parameters: * methodParamsJ: introduces a map to have additional method parameters - * + * * Return an array of KNNQueryResults - */ + */ jobjectArray QueryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong indexPointerJ, jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ, jintArray parentIdsJ); /** * Execute a query against the index located in memory at indexPointerJ along with Filters - * + * * Parameters: * methodParamsJ: introduces a map to have additional method parameters - * + * * Return an array of KNNQueryResults */ jobjectArray QueryIndex_WithFilter(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong indexPointerJ, - jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ, jlongArray filterIdsJ, - jint filterIdsTypeJ, jintArray parentIdsJ); + jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ, jlongArray filterIdsJ, + jint filterIdsTypeJ, jintArray parentIdsJ); // Execute a query against the binary index located in memory at indexPointerJ along with Filters // @@ -124,14 +125,14 @@ namespace knn_jni { // // Return the serialized representation jbyteArray TrainBinaryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jobject parametersJ, jint dimension, - jlong trainVectorsPointerJ); + jlong trainVectorsPointerJ); // Create an empty byte index defined by the values in the Java map, parametersJ. Train the index with // the byte vectors located at trainVectorsPointerJ. // // Return the serialized representation jbyteArray TrainByteIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jobject parametersJ, jint dimension, - jlong trainVectorsPointerJ); + jlong trainVectorsPointerJ); /* * Perform a range search with filter against the index located in memory at indexPointerJ. @@ -163,7 +164,7 @@ namespace knn_jni { * @return an array of RangeQueryResults */ jobjectArray RangeSearch(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong indexPointerJ, jfloatArray queryVectorJ, - jfloat radiusJ, jobject methodParamsJ, jint maxResultWindowJ, jintArray parentIdsJ); + jfloat radiusJ, jobject methodParamsJ, jint maxResultWindowJ, jintArray parentIdsJ); } } diff --git a/jni/include/jni_util.h b/jni/include/jni_util.h index 9f4daef7c..45068ae1b 100644 --- a/jni/include/jni_util.h +++ b/jni/include/jni_util.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace knn_jni { @@ -33,7 +34,7 @@ namespace knn_jni { virtual void HasExceptionInStack(JNIEnv* env) = 0; // HasExceptionInStack with ability to specify message - virtual void HasExceptionInStack(JNIEnv* env, const std::string& message) = 0; + virtual void HasExceptionInStack(JNIEnv* env, const char *message) = 0; // Catches a C++ exception and throws the corresponding exception to the JVM virtual void CatchCppExceptionAndThrowJava(JNIEnv* env) = 0; @@ -144,6 +145,9 @@ namespace knn_jni { virtual jlong CallNonvirtualLongMethodA(JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args) = 0; + virtual void CallNonvirtualVoidMethodA(JNIEnv * env, jobject obj, jclass clazz, + jmethodID methodID, jvalue* args) = 0; + // -------------------------------------------------------------------------- }; @@ -158,7 +162,7 @@ namespace knn_jni { void ThrowJavaException(JNIEnv* env, const char* type = "", const char* message = "") final; void HasExceptionInStack(JNIEnv* env) final; - void HasExceptionInStack(JNIEnv* env, const std::string& message) final; + void HasExceptionInStack(JNIEnv* env, const char* message) final; void CatchCppExceptionAndThrowJava(JNIEnv* env) final; jclass FindClass(JNIEnv * env, const std::string& className) final; jmethodID FindMethod(JNIEnv * env, const std::string& className, const std::string& methodName) final; @@ -200,13 +204,38 @@ namespace knn_jni { jfieldID GetFieldID(JNIEnv * env, jclass clazz, const char *name, const char *sig) final; jint CallNonvirtualIntMethodA(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, jvalue *args) final; jlong CallNonvirtualLongMethodA(JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args) final; + void CallNonvirtualVoidMethodA(JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args) final; void * GetPrimitiveArrayCritical(JNIEnv * env, jarray array, jboolean *isCopy) final; void ReleasePrimitiveArrayCritical(JNIEnv * env, jarray array, void *carray, jint mode) final; private: std::unordered_map cachedClasses; std::unordered_map cachedMethods; - }; + }; // class JNIUtil + + /** + * It's common cleaner to release a primitive array within its destructor. + * Ex: JNIReleaseElements release_int_array_elements {[=](){ + * jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); + * }}; + */ + struct JNIReleaseElements { + explicit JNIReleaseElements(std::function _release_func) + : release_func(std::move(_release_func)) { + } + + ~JNIReleaseElements() { + try { + if (release_func) { + release_func(); + } + } catch (...) { + // Ignore + } + } + + std::function release_func; + }; // struct ReleaseIntArrayElements // ------------------------------- CONSTANTS -------------------------------- extern const std::string FAISS_NAME; diff --git a/jni/include/memory_util.h b/jni/include/memory_util.h new file mode 100644 index 000000000..5e1fc13ae --- /dev/null +++ b/jni/include/memory_util.h @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef KNNPLUGIN_JNI_INCLUDE_MEMORY_UTIL_H_ +#define KNNPLUGIN_JNI_INCLUDE_MEMORY_UTIL_H_ + +#if defined(__GNUC__) || defined(__clang__) +#define RESTRICT __restrict__ +#elif defined(_MSC_VER) +#define RESTRICT __declspec(restrict) +#else +#define RESTRICT +#endif + +#endif //KNNPLUGIN_JNI_INCLUDE_MEMORY_UTIL_H_ diff --git a/jni/include/native_engines_stream_support.h b/jni/include/native_engines_stream_support.h index 5d4b32d3d..07f97f3ac 100644 --- a/jni/include/native_engines_stream_support.h +++ b/jni/include/native_engines_stream_support.h @@ -13,6 +13,8 @@ #define OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H #include "jni_util.h" +#include "parameter_utils.h" +#include "memory_util.h" #include #include @@ -22,8 +24,6 @@ namespace knn_jni { namespace stream { - - /** * This class contains Java IndexInputWithBuffer reference and calls its API to copy required bytes into a read buffer. */ @@ -33,9 +33,10 @@ class NativeEngineIndexInputMediator { NativeEngineIndexInputMediator(JNIUtilInterface *_jni_interface, JNIEnv *_env, jobject _indexInput) - : jni_interface(_jni_interface), - env(_env), - indexInput(_indexInput), + : jni_interface(knn_jni::util::ParameterCheck::require_non_null( + _jni_interface, "jni_interface")), + env(knn_jni::util::ParameterCheck::require_non_null(_env, "env")), + indexInput(knn_jni::util::ParameterCheck::require_non_null(_indexInput, "indexInput")), bufferArray((jbyteArray) (_jni_interface->GetObjectField(_env, _indexInput, getBufferFieldId(_jni_interface, _env)))), @@ -43,7 +44,7 @@ class NativeEngineIndexInputMediator { remainingBytesMethod(getRemainingBytesMethod(_jni_interface, _env)) { } - void copyBytes(int64_t nbytes, uint8_t *destination) { + void copyBytes(int64_t nbytes, uint8_t * RESTRICT destination) { auto jclazz = getIndexInputWithBufferClass(jni_interface, env); while (nbytes > 0) { @@ -52,11 +53,12 @@ class NativeEngineIndexInputMediator { args.j = nbytes; const auto readBytes = jni_interface->CallNonvirtualIntMethodA(env, indexInput, jclazz, copyBytesMethod, &args); + jni_interface->HasExceptionInStack(env, "Reading bytes via IndexInput has failed."); // === Critical Section Start === // Get primitive array pointer, no copy is happening in OpenJDK. - auto primitiveArray = + jbyte * RESTRICT primitiveArray = (jbyte *) jni_interface->GetPrimitiveArrayCritical(env, bufferArray, nullptr); // Copy Java bytes to C++ destination address. @@ -65,6 +67,7 @@ class NativeEngineIndexInputMediator { // Release the acquired primitive array pointer. // JNI_ABORT tells JVM to directly free memory without copying back to Java byte[]. // Since we're merely copying data, we don't need to copying back. + // Note than when we received an internal primitive array pointer, then the mode will be ignored. jni_interface->ReleasePrimitiveArrayCritical(env, bufferArray, primitiveArray, JNI_ABORT); // === Critical Section End === @@ -75,11 +78,13 @@ class NativeEngineIndexInputMediator { } int64_t remainingBytes() { - return jni_interface->CallNonvirtualLongMethodA(env, - indexInput, - getIndexInputWithBufferClass(jni_interface, env), - remainingBytesMethod, - nullptr); + auto bytes = jni_interface->CallNonvirtualLongMethodA(env, + indexInput, + getIndexInputWithBufferClass(jni_interface, env), + remainingBytesMethod, + nullptr); + jni_interface->HasExceptionInStack(env, "Checking remaining bytes has failed."); + return bytes; } private: @@ -119,6 +124,110 @@ class NativeEngineIndexInputMediator { +/** + * This class delegates the provided index output to do IO processing. + * In most cases, it is expected that IndexOutputWithBuffer was passed down to this, + * which eventually have Lucene's IndexOutput to write bytes. + */ +class NativeEngineIndexOutputMediator { + public: + NativeEngineIndexOutputMediator(JNIUtilInterface *_jni_interface, + JNIEnv *_env, + jobject _indexOutput) + : jni_interface(knn_jni::util::ParameterCheck::require_non_null(_jni_interface, "jni_interface")), + env(knn_jni::util::ParameterCheck::require_non_null(_env, "env")), + indexOutput(knn_jni::util::ParameterCheck::require_non_null(_indexOutput, "indexOutput")), + bufferArray((jbyteArray) (_jni_interface->GetObjectField(_env, + _indexOutput, + getBufferFieldId(_jni_interface, _env)))), + writeBytesMethod(getWriteBytesMethod(_jni_interface, _env)), + bufferLength(jni_interface->GetJavaBytesArrayLength(env, bufferArray)), + nextWriteIndex() { + } + + void writeBytes(const uint8_t * RESTRICT source, size_t nbytes) { + auto left = nbytes; + while (left > 0) { + const auto writeBytes = std::min(bufferLength - nextWriteIndex, left); + + // === Critical Section Start === + + // Get primitive array pointer, no copy is happening in OpenJDK. + jbyte * RESTRICT primitiveArray = + (jbyte *) jni_interface->GetPrimitiveArrayCritical(env, bufferArray, nullptr); + + // Copy the given bytes to Java byte[] address. + std::memcpy(primitiveArray + nextWriteIndex, source, writeBytes); + + // Release the acquired primitive array pointer. + // 0 tells JVM to copy back the content, and to free the pointer. It will be ignored if we acquired an internal + // primitive array pointer instead of a copied version. + // From JNI docs: + // Mode 0 : copy back the content and free the elems buffer + // The mode argument provides information on how the array buffer should be released. mode has no effect if elems + // is not a copy of the elements in array. + jni_interface->ReleasePrimitiveArrayCritical(env, bufferArray, primitiveArray, 0); + + // === Critical Section End === + + nextWriteIndex += writeBytes; + if (nextWriteIndex >= bufferLength) { + callWriteBytesInIndexOutput(); + } + + source += writeBytes; + left -= writeBytes; + } // End while + } + + void flush() { + if (nextWriteIndex > 0) { + callWriteBytesInIndexOutput(); + } + } + + private: + static jclass getIndexOutputWithBufferClass(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jclass INDEX_OUTPUT_WITH_BUFFER_CLASS = + jni_interface->FindClassFromJNIEnv(env, "org/opensearch/knn/index/store/IndexOutputWithBuffer"); + return INDEX_OUTPUT_WITH_BUFFER_CLASS; + } + + static jmethodID getWriteBytesMethod(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jmethodID WRITE_METHOD_ID = + jni_interface->GetMethodID(env, getIndexOutputWithBufferClass(jni_interface, env), "writeBytes", "(I)V"); + return WRITE_METHOD_ID; + } + + static jfieldID getBufferFieldId(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jfieldID BUFFER_FIELD_ID = + jni_interface->GetFieldID(env, getIndexOutputWithBufferClass(jni_interface, env), "buffer", "[B"); + return BUFFER_FIELD_ID; + } + + void callWriteBytesInIndexOutput() { + auto jclazz = getIndexOutputWithBufferClass(jni_interface, env); + // Initializing the first integer parameter of `writeBytes`. + // `i` represents an integer parameter. + jvalue args {.i = nextWriteIndex}; + jni_interface->CallNonvirtualVoidMethodA(env, indexOutput, jclazz, writeBytesMethod, &args); + jni_interface->HasExceptionInStack(env, "Writing bytes via IndexOutput has failed."); + nextWriteIndex = 0; + } + + JNIUtilInterface *jni_interface; + JNIEnv *env; + + // `IndexOutputWithBuffer` instance having `IndexOutput` instance obtained from `Directory` for reading. + jobject indexOutput; + jbyteArray bufferArray; + jmethodID writeBytesMethod; + size_t bufferLength; + int32_t nextWriteIndex; +}; // NativeEngineIndexOutputMediator + + + } } diff --git a/jni/include/nmslib_stream_support.h b/jni/include/nmslib_stream_support.h index 38c06cb95..2c410dde6 100644 --- a/jni/include/nmslib_stream_support.h +++ b/jni/include/nmslib_stream_support.h @@ -13,19 +13,20 @@ #define OPENSEARCH_KNN_JNI_NMSLIB_STREAM_SUPPORT_H #include "native_engines_stream_support.h" +#include "utils.h" // This is from NMSLIB +#include "parameter_utils.h" namespace knn_jni { namespace stream { - - /** * NmslibIOReader implementation delegating NativeEngineIndexInputMediator to read bytes. */ class NmslibOpenSearchIOReader final : public similarity::NmslibIOReader { public: explicit NmslibOpenSearchIOReader(NativeEngineIndexInputMediator *_mediator) - : mediator(_mediator) { + : similarity::NmslibIOReader(), + mediator(knn_jni::util::ParameterCheck::require_non_null(_mediator, "mediator")) { } void read(char *bytes, size_t len) final { @@ -44,6 +45,27 @@ class NmslibOpenSearchIOReader final : public similarity::NmslibIOReader { }; // class NmslibOpenSearchIOReader +class NmslibOpenSearchIOWriter final : public similarity::NmslibIOWriter { + public: + explicit NmslibOpenSearchIOWriter(NativeEngineIndexOutputMediator *_mediator) + : similarity::NmslibIOWriter(), + mediator(knn_jni::util::ParameterCheck::require_non_null(_mediator, "mediator")) { + } + + void write(char *bytes, size_t len) final { + if (len > 0) { + mediator->writeBytes((uint8_t *) bytes, len); + } + } + + void flush() final { + mediator->flush(); + } + + private: + NativeEngineIndexOutputMediator *mediator; +}; // class NmslibOpenSearchIOWriter + } } diff --git a/jni/include/nmslib_wrapper.h b/jni/include/nmslib_wrapper.h index 2853cd71f..687a96d59 100644 --- a/jni/include/nmslib_wrapper.h +++ b/jni/include/nmslib_wrapper.h @@ -26,7 +26,7 @@ namespace knn_jni { // Create an index with ids and vectors. The configuration is defined by values in the Java map, parametersJ. // The index is serialized to indexPathJ. void CreateIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, jlong vectorsAddress, jint dim, - jstring indexPathJ, jobject parametersJ); + jobject output, jobject parametersJ); // Load an index from indexPathJ into memory. Use parametersJ to set any query time parameters // diff --git a/jni/include/org_opensearch_knn_jni_FaissService.h b/jni/include/org_opensearch_knn_jni_FaissService.h index 2969df3ae..dce580138 100644 --- a/jni/include/org_opensearch_knn_jni_FaissService.h +++ b/jni/include/org_opensearch_knn_jni_FaissService.h @@ -24,16 +24,16 @@ extern "C" { * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V */ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initIndex(JNIEnv * env, jclass cls, - jlong numDocs, jint dimJ, - jobject parametersJ); + jlong numDocs, jint dimJ, + jobject parametersJ); /* * Class: org_opensearch_knn_jni_FaissService * Method: initBinaryIndex * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V */ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initBinaryIndex(JNIEnv * env, jclass cls, - jlong numDocs, jint dimJ, - jobject parametersJ); + jlong numDocs, jint dimJ, + jobject parametersJ); /* * Class: org_opensearch_knn_jni_FaissService @@ -41,8 +41,8 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initBinaryIndex * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V */ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initByteIndex(JNIEnv * env, jclass cls, - jlong numDocs, jint dimJ, - jobject parametersJ); + jlong numDocs, jint dimJ, + jobject parametersJ); /* * Class: org_opensearch_knn_jni_FaissService @@ -50,16 +50,16 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initByteIndex(J * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V */ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToIndex(JNIEnv * env, jclass cls, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, - jlong indexAddress, jint threadCount); + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount); /* * Class: org_opensearch_knn_jni_FaissService * Method: insertToBinaryIndex * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V */ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToBinaryIndex(JNIEnv * env, jclass cls, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, - jlong indexAddress, jint threadCount); + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount); /* * Class: org_opensearch_knn_jni_FaissService @@ -67,58 +67,54 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToBinaryIn * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V */ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToByteIndex(JNIEnv * env, jclass cls, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, - jlong indexAddress, jint threadCount); + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount); /* * Class: org_opensearch_knn_jni_FaissService * Method: writeIndex - * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + * Signature: (JLorg/opensearch/knn/index/store/IndexOutputWithBuffer;)V */ -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeIndex(JNIEnv * env, jclass cls, - jlong indexAddress, - jstring indexPathJ); +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeIndex(JNIEnv *, jclass, jlong, jobject); + + /* * Class: org_opensearch_knn_jni_FaissService * Method: writeBinaryIndex - * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + * Signature: (JLorg/opensearch/knn/index/store/IndexOutputWithBuffer;)V */ -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeBinaryIndex(JNIEnv * env, jclass cls, - jlong indexAddress, - jstring indexPathJ); +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeBinaryIndex(JNIEnv *, jclass, jlong, jobject); /* * Class: org_opensearch_knn_jni_FaissService * Method: writeByteIndex - * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + * Signature: (JLorg/opensearch/knn/index/store/IndexOutputWithBuffer;)V */ -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeByteIndex(JNIEnv * env, jclass cls, - jlong indexAddress, - jstring indexPathJ); +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeByteIndex(JNIEnv *, jclass, jlong, jobject); /* * Class: org_opensearch_knn_jni_FaissService * Method: createIndexFromTemplate - * Signature: ([IJILjava/lang/String;[BLjava/util/Map;)V + * Signature: ([IJILorg/opensearch/knn/index/store/IndexOutputWithBuffer;[BLjava/util/Map;)V */ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createIndexFromTemplate - (JNIEnv *, jclass, jintArray, jlong, jint, jstring, jbyteArray, jobject); + (JNIEnv *, jclass, jintArray, jlong, jint, jobject, jbyteArray, jobject); /* * Class: org_opensearch_knn_jni_FaissService * Method: createBinaryIndexFromTemplate - * Signature: ([IJILjava/lang/String;[BLjava/util/Map;)V + * Signature: ([IJILorg/opensearch/knn/index/store/IndexOutputWithBuffer;[BLjava/util/Map;)V */ - JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createBinaryIndexFromTemplate - (JNIEnv *, jclass, jintArray, jlong, jint, jstring, jbyteArray, jobject); +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createBinaryIndexFromTemplate + (JNIEnv *, jclass, jintArray, jlong, jint, jobject, jbyteArray, jobject); /* * Class: org_opensearch_knn_jni_FaissService * Method: createByteIndexFromTemplate - * Signature: ([IJILjava/lang/String;[BLjava/util/Map;)V + * Signature: ([IJILorg/opensearch/knn/index/store/IndexOutputWithBuffer;[BLjava/util/Map;)V */ - JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createByteIndexFromTemplate - (JNIEnv *, jclass, jintArray, jlong, jint, jstring, jbyteArray, jobject); +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createByteIndexFromTemplate + (JNIEnv *, jclass, jintArray, jlong, jint, jobject, jbyteArray, jobject); /* * Class: org_opensearch_knn_jni_FaissService diff --git a/jni/include/org_opensearch_knn_jni_NmslibService.h b/jni/include/org_opensearch_knn_jni_NmslibService.h index 8d6633aff..0e035c3dd 100644 --- a/jni/include/org_opensearch_knn_jni_NmslibService.h +++ b/jni/include/org_opensearch_knn_jni_NmslibService.h @@ -21,10 +21,10 @@ extern "C" { /* * Class: org_opensearch_knn_jni_NmslibService * Method: createIndex - * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + * Signature: ([IJILorg/opensearch/knn/index/store/IndexOutputWithBuffer;Ljava/util/Map;)V */ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_createIndex - (JNIEnv *, jclass, jintArray, jlong, jint, jstring, jobject); + (JNIEnv *, jclass, jintArray, jlong, jint, jobject, jobject); /* * Class: org_opensearch_knn_jni_NmslibService diff --git a/jni/include/parameter_utils.h b/jni/include/parameter_utils.h new file mode 100644 index 000000000..aff922324 --- /dev/null +++ b/jni/include/parameter_utils.h @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef KNNPLUGIN_JNI_INCLUDE_PARAMETER_UTILS_H_ +#define KNNPLUGIN_JNI_INCLUDE_PARAMETER_UTILS_H_ + +#include +#include + +namespace knn_jni { +namespace util { + +struct ParameterCheck { + template + static PtrType *require_non_null(PtrType *ptr, const char *parameter_name) { + if (ptr == nullptr) { + throw std::invalid_argument(std::string("Parameter [") + parameter_name + "] should not be null."); + } + return ptr; + } + + private: + ParameterCheck() = default; +}; // class ParameterCheck + + + +} +} // namespace knn_jni + +#endif //KNNPLUGIN_JNI_INCLUDE_PARAMETER_UTILS_H_ diff --git a/jni/patches/nmslib/0004-Added-a-new-save-apis-in-Hnsw-with-streaming-interfa.patch b/jni/patches/nmslib/0004-Added-a-new-save-apis-in-Hnsw-with-streaming-interfa.patch new file mode 100644 index 000000000..0d324bc29 --- /dev/null +++ b/jni/patches/nmslib/0004-Added-a-new-save-apis-in-Hnsw-with-streaming-interfa.patch @@ -0,0 +1,165 @@ +From eb9ceb60c695f795895b80ea66b251aea1fbf781 Mon Sep 17 00:00:00 2001 +From: Dooyong Kim +Date: Wed, 30 Oct 2024 15:44:34 -0700 +Subject: [PATCH] Added a new IOWriter interface in Hnsw node to support + streaming writing. + +Signed-off-by: Dooyong Kim +--- + similarity_search/include/method/hnsw.h | 3 ++ + similarity_search/include/utils.h | 20 ++++++++-- + similarity_search/src/method/hnsw.cc | 52 +++++++++++++++++++++++-- + 3 files changed, 68 insertions(+), 7 deletions(-) + +diff --git a/similarity_search/include/method/hnsw.h b/similarity_search/include/method/hnsw.h +index 433f98f..d235c15 100644 +--- a/similarity_search/include/method/hnsw.h ++++ b/similarity_search/include/method/hnsw.h +@@ -459,6 +459,8 @@ namespace similarity { + + void LoadIndexWithStream(similarity::NmslibIOReader& in); + ++ void SaveIndexWithStream(similarity::NmslibIOWriter& out); ++ + Hnsw(bool PrintProgress, const Space &space, const ObjectVector &data); + void CreateIndex(const AnyParams &IndexParams) override; + +@@ -501,6 +503,7 @@ namespace similarity { + + + void SaveOptimizedIndex(std::ostream& output); ++ void SaveOptimizedIndex(NmslibIOWriter& output); + void LoadOptimizedIndex(std::istream& input); + void LoadOptimizedIndex(NmslibIOReader& input); + +diff --git a/similarity_search/include/utils.h b/similarity_search/include/utils.h +index a3931b7..1cc55b2 100644 +--- a/similarity_search/include/utils.h ++++ b/similarity_search/include/utils.h +@@ -305,19 +305,33 @@ struct NmslibIOReader { + virtual void read(char* bytes, size_t len) = 0; + + virtual size_t remainingBytes() = 0; +-}; ++}; // class NmslibIOReader ++ ++struct NmslibIOWriter { ++ virtual ~NmslibIOWriter() = default; ++ ++ virtual void write(char* bytes, size_t len) = 0; ++ ++ virtual void flush() { ++ } ++}; // class NmslibIOWriter + + template + void writeBinaryPOD(ostream& out, const T& podRef) { + out.write((char*)&podRef, sizeof(T)); + } + +-template ++template ++void writeBinaryPOD(NmslibIOWriter& out, const T& podRef) { ++ out.write((char*)&podRef, sizeof(T)); ++} ++ ++template + static void readBinaryPOD(NmslibIOReader& in, T& podRef) { + in.read((char*)&podRef, sizeof(T)); + } + +-template ++template + static void readBinaryPOD(istream& in, T& podRef) { + in.read((char*)&podRef, sizeof(T)); + } +diff --git a/similarity_search/src/method/hnsw.cc b/similarity_search/src/method/hnsw.cc +index 662f06c..bfa3a23 100644 +--- a/similarity_search/src/method/hnsw.cc ++++ b/similarity_search/src/method/hnsw.cc +@@ -784,6 +784,19 @@ namespace similarity { + output.close(); + } + ++ template ++ void Hnsw::SaveIndexWithStream(NmslibIOWriter& output) { ++ unsigned int optimIndexFlag = data_level0_memory_ != nullptr; ++ ++ writeBinaryPOD(output, optimIndexFlag); ++ ++ if (!optimIndexFlag) { ++ throw std::runtime_error("With stream, we only support optimized index type."); ++ } else { ++ SaveOptimizedIndex(output); ++ } ++ } ++ + template + void + Hnsw::SaveOptimizedIndex(std::ostream& output) { +@@ -818,6 +831,37 @@ namespace similarity { + + } + ++ template ++ void ++ Hnsw::SaveOptimizedIndex(NmslibIOWriter& output) { ++ totalElementsStored_ = ElList_.size(); ++ ++ writeBinaryPOD(output, totalElementsStored_); ++ writeBinaryPOD(output, memoryPerObject_); ++ writeBinaryPOD(output, offsetLevel0_); ++ writeBinaryPOD(output, offsetData_); ++ writeBinaryPOD(output, maxlevel_); ++ writeBinaryPOD(output, enterpointId_); ++ writeBinaryPOD(output, maxM_); ++ writeBinaryPOD(output, maxM0_); ++ writeBinaryPOD(output, dist_func_type_); ++ writeBinaryPOD(output, searchMethod_); ++ ++ const size_t data_plus_links0_size = memoryPerObject_ * totalElementsStored_; ++ LOG(LIB_INFO) << "writing " << data_plus_links0_size << " bytes"; ++ output.write(data_level0_memory_, data_plus_links0_size); ++ ++ for (size_t i = 0; i < totalElementsStored_; i++) { ++ // TODO Can this one overflow? I really doubt ++ const SIZEMASS_TYPE sizemass = ((ElList_[i]->level) * (maxM_ + 1)) * sizeof(int); ++ writeBinaryPOD(output, sizemass); ++ if (sizemass) { ++ output.write(linkLists_[i], sizemass); ++ } ++ } ++ output.flush(); ++ } ++ + template + void + Hnsw::SaveRegularIndexBin(std::ostream& output) { +@@ -1036,20 +1080,20 @@ namespace similarity { + constexpr bool _isLittleEndian() { + return (((uint32_t) 1) & 0xFFU) == 1; + } +- ++ + SIZEMASS_TYPE _readIntBigEndian(uint8_t byte0, uint8_t byte1, uint8_t byte2, uint8_t byte3) noexcept { + return (static_cast(byte0) << 24) | + (static_cast(byte1) << 16) | + (static_cast(byte2) << 8) | + static_cast(byte3); +- } +- ++ } ++ + SIZEMASS_TYPE _readIntLittleEndian(uint8_t byte0, uint8_t byte1, uint8_t byte2, uint8_t byte3) noexcept { + return (static_cast(byte3) << 24) | + (static_cast(byte2) << 16) | + (static_cast(byte1) << 8) | + static_cast(byte0); +- } ++ } + + template + void Hnsw::LoadIndexWithStream(NmslibIOReader& input) { +-- +2.39.5 (Apple Git-154) + diff --git a/jni/src/commons.cpp b/jni/src/commons.cpp index 38e3ac8a4..444dc18b0 100644 --- a/jni/src/commons.cpp +++ b/jni/src/commons.cpp @@ -8,8 +8,6 @@ * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ -#ifndef OPENSEARCH_KNN_COMMONS_H -#define OPENSEARCH_KNN_COMMONS_H #include #include @@ -109,4 +107,3 @@ int knn_jni::commons::getIntegerMethodParameter(JNIEnv * env, knn_jni::JNIUtilIn return defaultValue; } -#endif //OPENSEARCH_KNN_COMMONS_H diff --git a/jni/src/faiss_index_service.cpp b/jni/src/faiss_index_service.cpp index 16ded4bcb..4999e3172 100644 --- a/jni/src/faiss_index_service.cpp +++ b/jni/src/faiss_index_service.cpp @@ -9,7 +9,6 @@ #include "faiss_index_service.h" #include "faiss_methods.h" -#include "faiss/index_factory.h" #include "faiss/Index.h" #include "faiss/IndexBinary.h" #include "faiss/IndexHNSW.h" @@ -17,8 +16,7 @@ #include "faiss/IndexIVFFlat.h" #include "faiss/IndexBinaryIVF.h" #include "faiss/IndexIDMap.h" -#include "faiss/index_io.h" -#include + #include #include #include @@ -55,20 +53,18 @@ void SetExtraParameters(knn_jni::JNIUtilInterface * jniUtil, JNIEnv *env, } } -IndexService::IndexService(std::unique_ptr faissMethods) : faissMethods(std::move(faissMethods)) {} +IndexService::IndexService(std::unique_ptr _faissMethods) : faissMethods(std::move(_faissMethods)) {} void IndexService::allocIndex(faiss::Index * index, size_t dim, size_t numVectors) { - if(auto * indexHNSWSQ = dynamic_cast(index)) { - if(auto * indexScalarQuantizer = dynamic_cast(indexHNSWSQ->storage)) { + if (auto * indexHNSWSQ = dynamic_cast(index)) { + if (auto * indexScalarQuantizer = dynamic_cast(indexHNSWSQ->storage)) { indexScalarQuantizer->codes.reserve(indexScalarQuantizer->code_size * numVectors); } - return; } - if(auto * indexHNSW = dynamic_cast(index)) { + if (auto * indexHNSW = dynamic_cast(index)) { if(auto * indexFlat = dynamic_cast(indexHNSW->storage)) { indexFlat->codes.reserve(indexFlat->code_size * numVectors); } - return; } } @@ -86,7 +82,7 @@ jlong IndexService::initIndex( std::unique_ptr index(faissMethods->indexFactory(dim, indexDescription.c_str(), metric)); // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread - if(threadCount != 0) { + if (threadCount != 0) { omp_set_num_threads(threadCount); } @@ -94,7 +90,7 @@ jlong IndexService::initIndex( SetExtraParameters(jniUtil, env, parameters, index.get()); // Check that the index does not need to be trained - if(!index->is_trained) { + if (!index->is_trained) { throw std::runtime_error("Index is not trained"); } @@ -123,7 +119,7 @@ void IndexService::insertToIndex( // The number of vectors can be int here because a lucene segment number of total docs never crosses INT_MAX value int numVectors = (int) (inputVectors->size() / (uint64_t) dim); - if(numVectors == 0) { + if (numVectors == 0) { throw std::runtime_error("Number of vectors cannot be 0"); } @@ -132,7 +128,7 @@ void IndexService::insertToIndex( } // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread - if(threadCount != 0) { + if (threadCount != 0) { omp_set_num_threads(threadCount); } @@ -143,26 +139,30 @@ void IndexService::insertToIndex( } void IndexService::writeIndex( - std::string indexPath, - jlong idMapAddress - ) { + faiss::IOWriter* writer, + jlong idMapAddress +) { std::unique_ptr idMap (reinterpret_cast (idMapAddress)); try { // Write the index to disk - faissMethods->writeIndex(idMap.get(), indexPath.c_str()); + faissMethods->writeIndex(idMap.get(), writer); + if (auto openSearchIOWriter = dynamic_cast(writer)) { + openSearchIOWriter->flush(); + } } catch(std::exception &e) { throw std::runtime_error("Failed to write index to disk"); } } -BinaryIndexService::BinaryIndexService(std::unique_ptr faissMethods) : IndexService(std::move(faissMethods)) {} +BinaryIndexService::BinaryIndexService(std::unique_ptr _faissMethods) + : IndexService(std::move(_faissMethods)) { +} void BinaryIndexService::allocIndex(faiss::Index * index, size_t dim, size_t numVectors) { - if(auto * indexBinaryHNSW = dynamic_cast(index)) { + if (auto * indexBinaryHNSW = dynamic_cast(index)) { auto * indexBinaryFlat = dynamic_cast(indexBinaryHNSW->storage); indexBinaryFlat->xb.reserve(dim * numVectors / 8); - return; } } @@ -179,15 +179,15 @@ jlong BinaryIndexService::initIndex( // Create index using Faiss factory method std::unique_ptr index(faissMethods->indexBinaryFactory(dim, indexDescription.c_str())); // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread - if(threadCount != 0) { - omp_set_num_threads(threadCount); + if (threadCount != 0) { + omp_set_num_threads(threadCount); } // Add extra parameters that cant be configured with the index factory SetExtraParameters(jniUtil, env, parameters, index.get()); // Check that the index does not need to be trained - if(!index->is_trained) { + if (!index->is_trained) { throw std::runtime_error("Index is not trained"); } @@ -216,7 +216,7 @@ void BinaryIndexService::insertToIndex( // The number of vectors can be int here because a lucene segment number of total docs never crosses INT_MAX value int numVectors = (int) (inputVectors->size() / (uint64_t) (dim / 8)); - if(numVectors == 0) { + if (numVectors == 0) { throw std::runtime_error("Number of vectors cannot be 0"); } @@ -225,7 +225,7 @@ void BinaryIndexService::insertToIndex( } // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread - if(threadCount != 0) { + if (threadCount != 0) { omp_set_num_threads(threadCount); } @@ -236,28 +236,31 @@ void BinaryIndexService::insertToIndex( } void BinaryIndexService::writeIndex( - std::string indexPath, - jlong idMapAddress - ) { - + faiss::IOWriter* writer, + jlong idMapAddress +) { std::unique_ptr idMap (reinterpret_cast (idMapAddress)); try { // Write the index to disk - faissMethods->writeIndexBinary(idMap.get(), indexPath.c_str()); + faissMethods->writeIndexBinary(idMap.get(), writer); + if (auto openSearchIOWriter = dynamic_cast(writer)) { + openSearchIOWriter->flush(); + } } catch(std::exception &e) { throw std::runtime_error("Failed to write index to disk"); } } -ByteIndexService::ByteIndexService(std::unique_ptr faissMethods) : IndexService(std::move(faissMethods)) {} +ByteIndexService::ByteIndexService(std::unique_ptr _faissMethods) + : IndexService(std::move(_faissMethods)) { +} void ByteIndexService::allocIndex(faiss::Index * index, size_t dim, size_t numVectors) { - if(auto * indexHNSWSQ = dynamic_cast(index)) { + if (auto * indexHNSWSQ = dynamic_cast(index)) { if(auto * indexScalarQuantizer = dynamic_cast(indexHNSWSQ->storage)) { indexScalarQuantizer->codes.reserve(indexScalarQuantizer->code_size * numVectors); } - return; } } @@ -275,7 +278,7 @@ jlong ByteIndexService::initIndex( std::unique_ptr index(faissMethods->indexFactory(dim, indexDescription.c_str(), metric)); // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread - if(threadCount != 0) { + if (threadCount != 0) { omp_set_num_threads(threadCount); } @@ -312,7 +315,7 @@ void ByteIndexService::insertToIndex( // The number of vectors can be int here because a lucene segment number of total docs never crosses INT_MAX value int numVectors = inputVectors->size() / dim; - if(numVectors == 0) { + if (numVectors == 0) { throw std::runtime_error("Number of vectors cannot be 0"); } @@ -321,7 +324,7 @@ void ByteIndexService::insertToIndex( } // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread - if(threadCount != 0) { + if (threadCount != 0) { omp_set_num_threads(threadCount); } @@ -332,7 +335,6 @@ void ByteIndexService::insertToIndex( int batchSize = 1000; std::vector inputFloatVectors(batchSize * dim); std::vector floatVectorsIds(batchSize); - int id = 0; auto iter = inputVectors->begin(); for (int id = 0; id < numVectors; id += batchSize) { @@ -351,17 +353,20 @@ void ByteIndexService::insertToIndex( } void ByteIndexService::writeIndex( - std::string indexPath, - jlong idMapAddress - ) { + faiss::IOWriter* writer, + jlong idMapAddress +) { std::unique_ptr idMap (reinterpret_cast (idMapAddress)); try { // Write the index to disk - faissMethods->writeIndex(idMap.get(), indexPath.c_str()); + faissMethods->writeIndex(idMap.get(), writer); + if (auto openSearchIOWriter = dynamic_cast(writer)) { + openSearchIOWriter->flush(); + } } catch(std::exception &e) { throw std::runtime_error("Failed to write index to disk"); } } } // namespace faiss_wrapper -} // namesapce knn_jni \ No newline at end of file +} // namesapce knn_jni diff --git a/jni/src/faiss_methods.cpp b/jni/src/faiss_methods.cpp index 05c8f459a..dc44c0df9 100644 --- a/jni/src/faiss_methods.cpp +++ b/jni/src/faiss_methods.cpp @@ -29,11 +29,12 @@ faiss::IndexIDMapTemplate* FaissMethods::indexBinaryIdMap(fa return new faiss::IndexBinaryIDMap(index); } -void FaissMethods::writeIndex(const faiss::Index* idx, const char* fname) { - faiss::write_index(idx, fname); +void FaissMethods::writeIndex(const faiss::Index* idx, faiss::IOWriter* writer) { + faiss::write_index(idx, writer); } -void FaissMethods::writeIndexBinary(const faiss::IndexBinary* idx, const char* fname) { - faiss::write_index_binary(idx, fname); + +void FaissMethods::writeIndexBinary(const faiss::IndexBinary* idx, faiss::IOWriter* writer) { + faiss::write_index_binary(idx, writer); } } // namespace faiss_wrapper diff --git a/jni/src/faiss_wrapper.cpp b/jni/src/faiss_wrapper.cpp index d1c7648dc..98c40cf6b 100644 --- a/jni/src/faiss_wrapper.cpp +++ b/jni/src/faiss_wrapper.cpp @@ -13,13 +13,13 @@ #include "faiss_wrapper.h" #include "faiss_util.h" #include "faiss_index_service.h" +#include "faiss_stream_support.h" #include "faiss/impl/io.h" #include "faiss/index_factory.h" #include "faiss/index_io.h" #include "faiss/IndexHNSW.h" #include "faiss/IndexIVFFlat.h" -#include "faiss/MetaIndexes.h" #include "faiss/Index.h" #include "faiss/impl/IDSelector.h" #include "faiss/IndexIVFPQ.h" @@ -48,18 +48,25 @@ struct IDSelectorJlongBitmap : IDSelector { * @param n size of the bitmap array * @param bitmap id like Lucene FixedBitSet bits */ - IDSelectorJlongBitmap(size_t n, const jlong* bitmap) : n(n), bitmap(bitmap) {}; + IDSelectorJlongBitmap(size_t _n, const jlong* _bitmap) + : IDSelector(), + n(_n), + bitmap(_bitmap) { + } + bool is_member(idx_t id) const final { - uint64_t index = id; - uint64_t i = index >> 6; // div 64 - if (i >= n ) { + const uint64_t index = id; + const uint64_t i = index >> 6ULL; // div 64 + if (i >= n) { return false; } - return (bitmap[i] >> ( index & 63)) & 1L; + return (bitmap[i] >> (index & 63ULL)) & 1ULL; } - ~IDSelectorJlongBitmap() override {} -}; -} +}; // class IDSelectorJlongBitmap + +} // namespace faiss + + // Translate space type to faiss metric faiss::MetricType TranslateSpaceToMetric(const std::string& spaceType); @@ -136,7 +143,14 @@ jlong knn_jni::faiss_wrapper::InitIndex(knn_jni::JNIUtilInterface * jniUtil, JNI // end parameters to pass // Create index - return indexService->initIndex(jniUtil, env, metric, indexDescriptionCpp, dim, numDocs, threadCount, subParametersCpp); + return indexService->initIndex(jniUtil, + env, + metric, + std::move(indexDescriptionCpp), + dim, + numDocs, + threadCount, + std::move(subParametersCpp)); } void knn_jni::faiss_wrapper::InsertToIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, jlong vectorsAddressJ, jint dimJ, @@ -170,21 +184,22 @@ void knn_jni::faiss_wrapper::InsertToIndex(knn_jni::JNIUtilInterface * jniUtil, } void knn_jni::faiss_wrapper::WriteIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, - jstring indexPathJ, jlong index_ptr, IndexService* indexService) { + jobject output, jlong index_ptr, IndexService* indexService) { - if (indexPathJ == nullptr) { - throw std::runtime_error("Index path cannot be null"); + if (output == nullptr) { + throw std::runtime_error("Index output stream cannot be null"); } - // Index path - std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); + // IndexOutput wrapper. + knn_jni::stream::NativeEngineIndexOutputMediator mediator {jniUtil, env, output}; + knn_jni::stream::FaissOpenSearchIOWriter writer {&mediator}; - // Create index - indexService->writeIndex(indexPathCpp, index_ptr); + // Create index. + indexService->writeIndex(&writer, index_ptr); } void knn_jni::faiss_wrapper::CreateIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, jstring indexPathJ, + jlong vectorsAddressJ, jint dimJ, jobject output, jbyteArray templateIndexJ, jobject parametersJ) { if (idsJ == nullptr) { throw std::runtime_error("IDs cannot be null"); @@ -198,8 +213,8 @@ void knn_jni::faiss_wrapper::CreateIndexFromTemplate(knn_jni::JNIUtilInterface * throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); } - if (indexPathJ == nullptr) { - throw std::runtime_error("Index path cannot be null"); + if (output == nullptr) { + throw std::runtime_error("Index output stream cannot be null"); } if (templateIndexJ == nullptr) { @@ -245,14 +260,17 @@ void knn_jni::faiss_wrapper::CreateIndexFromTemplate(knn_jni::JNIUtilInterface * // This is not the ideal approach, please refer this gh issue for long term solution: // https://github.com/opensearch-project/k-NN/issues/1600 delete inputVectors; + // Write the index to disk - std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); - faiss::write_index(&idMap, indexPathCpp.c_str()); + knn_jni::stream::NativeEngineIndexOutputMediator mediator {jniUtil, env, output}; + knn_jni::stream::FaissOpenSearchIOWriter writer {&mediator}; + faiss::write_index(&idMap, &writer); + mediator.flush(); } void knn_jni::faiss_wrapper::CreateBinaryIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, jstring indexPathJ, - jbyteArray templateIndexJ, jobject parametersJ) { + jlong vectorsAddressJ, jint dimJ, jobject output, + jbyteArray templateIndexJ, jobject parametersJ) { if (idsJ == nullptr) { throw std::runtime_error("IDs cannot be null"); } @@ -261,12 +279,12 @@ void knn_jni::faiss_wrapper::CreateBinaryIndexFromTemplate(knn_jni::JNIUtilInter throw std::runtime_error("VectorsAddress cannot be less than 0"); } - if(dimJ <= 0) { + if (dimJ <= 0) { throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); } - if (indexPathJ == nullptr) { - throw std::runtime_error("Index path cannot be null"); + if (output == nullptr) { + throw std::runtime_error("Index output stream cannot be null"); } if (templateIndexJ == nullptr) { @@ -315,14 +333,17 @@ void knn_jni::faiss_wrapper::CreateBinaryIndexFromTemplate(knn_jni::JNIUtilInter // This is not the ideal approach, please refer this gh issue for long term solution: // https://github.com/opensearch-project/k-NN/issues/1600 delete inputVectors; + // Write the index to disk - std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); - faiss::write_index_binary(&idMap, indexPathCpp.c_str()); + knn_jni::stream::NativeEngineIndexOutputMediator mediator {jniUtil, env, output}; + knn_jni::stream::FaissOpenSearchIOWriter writer {&mediator}; + faiss::write_index_binary(&idMap, &writer); + mediator.flush(); } void knn_jni::faiss_wrapper::CreateByteIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, jstring indexPathJ, - jbyteArray templateIndexJ, jobject parametersJ) { + jlong vectorsAddressJ, jint dimJ, jobject output, + jbyteArray templateIndexJ, jobject parametersJ) { if (idsJ == nullptr) { throw std::runtime_error("IDs cannot be null"); } @@ -331,12 +352,12 @@ void knn_jni::faiss_wrapper::CreateByteIndexFromTemplate(knn_jni::JNIUtilInterfa throw std::runtime_error("VectorsAddress cannot be less than 0"); } - if(dimJ <= 0) { + if (dimJ <= 0) { throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); } - if (indexPathJ == nullptr) { - throw std::runtime_error("Index path cannot be null"); + if (output == nullptr) { + throw std::runtime_error("Index output stream cannot be null"); } if (templateIndexJ == nullptr) { @@ -345,8 +366,9 @@ void knn_jni::faiss_wrapper::CreateByteIndexFromTemplate(knn_jni::JNIUtilInterfa // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); - if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { - auto threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); + auto it = parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY); + if (it != parametersCpp.end()) { + auto threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, it->second); omp_set_num_threads(threadCount); } jniUtil->DeleteLocalRef(env, parametersJ); @@ -354,8 +376,8 @@ void knn_jni::faiss_wrapper::CreateByteIndexFromTemplate(knn_jni::JNIUtilInterfa // Read data set // Read vectors from memory address auto *inputVectors = reinterpret_cast*>(vectorsAddressJ); - int dim = (int)dimJ; - int numVectors = (int) (inputVectors->size() / (uint64_t) dim); + auto dim = (int) dimJ; + auto numVectors = (int) (inputVectors->size() / (uint64_t) dim); int numIds = jniUtil->GetJavaIntArrayLength(env, idsJ); if (numIds != numVectors) { @@ -367,14 +389,14 @@ void knn_jni::faiss_wrapper::CreateByteIndexFromTemplate(knn_jni::JNIUtilInterfa jbyte * indexBytesJ = jniUtil->GetByteArrayElements(env, templateIndexJ, nullptr); faiss::VectorIOReader vectorIoReader; + vectorIoReader.data.reserve(indexBytesCount); for (int i = 0; i < indexBytesCount; i++) { vectorIoReader.data.push_back((uint8_t) indexBytesJ[i]); } jniUtil->ReleaseByteArrayElements(env, templateIndexJ, indexBytesJ, JNI_ABORT); // Create faiss index - std::unique_ptr indexWriter; - indexWriter.reset(faiss::read_index(&vectorIoReader, 0)); + std::unique_ptr indexWriter (faiss::read_index(&vectorIoReader, 0)); auto ids = jniUtil->ConvertJavaIntArrayToCppIntVector(env, idsJ); faiss::IndexIDMap idMap = faiss::IndexIDMap(indexWriter.get()); @@ -405,9 +427,12 @@ void knn_jni::faiss_wrapper::CreateByteIndexFromTemplate(knn_jni::JNIUtilInterfa // This is not the ideal approach, please refer this gh issue for long term solution: // https://github.com/opensearch-project/k-NN/issues/1600 delete inputVectors; + // Write the index to disk - std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); - faiss::write_index(&idMap, indexPathCpp.c_str()); + knn_jni::stream::NativeEngineIndexOutputMediator mediator {jniUtil, env, output}; + knn_jni::stream::FaissOpenSearchIOWriter writer {&mediator}; + faiss::write_index(&idMap, &writer); + mediator.flush(); } jlong knn_jni::faiss_wrapper::LoadIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ) { @@ -424,7 +449,7 @@ jlong knn_jni::faiss_wrapper::LoadIndex(knn_jni::JNIUtilInterface * jniUtil, JNI } jlong knn_jni::faiss_wrapper::LoadIndexWithStream(faiss::IOReader* ioReader) { - if (ioReader == nullptr) [[unlikely]] { + if (ioReader == nullptr) { throw std::runtime_error("IOReader cannot be null"); } @@ -451,7 +476,7 @@ jlong knn_jni::faiss_wrapper::LoadBinaryIndex(knn_jni::JNIUtilInterface * jniUti } jlong knn_jni::faiss_wrapper::LoadBinaryIndexWithStream(faiss::IOReader* ioReader) { - if (ioReader == nullptr) [[unlikely]] { + if (ioReader == nullptr) { throw std::runtime_error("IOReader cannot be null"); } @@ -820,13 +845,13 @@ jbyteArray knn_jni::faiss_wrapper::TrainIndex(knn_jni::JNIUtilInterface * jniUti } // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread - if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { + if (parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { auto threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); omp_set_num_threads(threadCount); } // Add extra parameters that cant be configured with the index factory - if(parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { + if (parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { jobject subParametersJ = parametersCpp[knn_jni::PARAMETERS]; auto subParametersCpp = jniUtil->ConvertJavaMapToCppMap(env, subParametersJ); SetExtraParameters(jniUtil, env, subParametersCpp, indexWriter.get()); @@ -934,13 +959,13 @@ jbyteArray knn_jni::faiss_wrapper::TrainByteIndex(knn_jni::JNIUtilInterface * jn indexWriter.reset(faiss::index_factory((int) dimensionJ, indexDescriptionCpp.c_str(), metric)); // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread - if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { + if (parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { auto threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); omp_set_num_threads(threadCount); } // Add extra parameters that cant be configured with the index factory - if(parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { + if (parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { jobject subParametersJ = parametersCpp[knn_jni::PARAMETERS]; auto subParametersCpp = jniUtil->ConvertJavaMapToCppMap(env, subParametersJ); SetExtraParameters(jniUtil, env, subParametersCpp, indexWriter.get()); @@ -957,7 +982,7 @@ jbyteArray knn_jni::faiss_wrapper::TrainByteIndex(knn_jni::JNIUtilInterface * jn trainingFloatVectors[i] = static_cast(*iter); } - if(!indexWriter->is_trained) { + if (!indexWriter->is_trained) { InternalTrainIndex(indexWriter.get(), numVectors, trainingFloatVectors.data()); } jniUtil->DeleteLocalRef(env, parametersJ); @@ -1115,11 +1140,11 @@ jobjectArray knn_jni::faiss_wrapper::RangeSearchWithFilter(knn_jni::JNIUtilInter // The second parameter is always true, as lims is allocated by FAISS faiss::RangeSearchResult res(1, true); - if(filterIdsJ != nullptr) { + if (filterIdsJ != nullptr) { jlong *filteredIdsArray = jniUtil->GetLongArrayElements(env, filterIdsJ, nullptr); int filterIdsLength = jniUtil->GetJavaLongArrayLength(env, filterIdsJ); std::unique_ptr idSelector; - if(filterIdsTypeJ == BITMAP) { + if (filterIdsTypeJ == BITMAP) { idSelector.reset(new faiss::IDSelectorJlongBitmap(filterIdsLength, filteredIdsArray)); } else { faiss::idx_t* batchIndices = reinterpret_cast(filteredIdsArray); @@ -1131,7 +1156,7 @@ jobjectArray knn_jni::faiss_wrapper::RangeSearchWithFilter(knn_jni::JNIUtilInter std::unique_ptr idGrouper; std::vector idGrouperBitmap; auto hnswReader = dynamic_cast(indexReader->index); - if(hnswReader) { + if (hnswReader) { // Query param ef_search supersedes ef_search provided during index setting. hnswParams.efSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, hnswReader->hnsw.efSearch); hnswParams.sel = idSelector.get(); @@ -1195,7 +1220,7 @@ jobjectArray knn_jni::faiss_wrapper::RangeSearchWithFilter(knn_jni::JNIUtilInter jobjectArray results = jniUtil->NewObjectArray(env, resultSize, resultClass, nullptr); jobject result; - for(int i = 0; i < resultSize; ++i) { + for (int i = 0; i < resultSize; ++i) { result = jniUtil->NewObject(env, resultClass, allArgs, res.labels[i], res.distances[i]); jniUtil->SetObjectArrayElement(env, results, i, result); } diff --git a/jni/src/jni_util.cpp b/jni/src/jni_util.cpp index 8dc818c94..3ff79752a 100644 --- a/jni/src/jni_util.cpp +++ b/jni/src/jni_util.cpp @@ -88,7 +88,7 @@ void knn_jni::JNIUtil::HasExceptionInStack(JNIEnv* env) { this->HasExceptionInStack(env, "Exception in jni occurred"); } -void knn_jni::JNIUtil::HasExceptionInStack(JNIEnv* env, const std::string& message) { +void knn_jni::JNIUtil::HasExceptionInStack(JNIEnv* env, const char* message) { if (env->ExceptionCheck() == JNI_TRUE) { throw std::runtime_error(message); } @@ -252,11 +252,11 @@ void knn_jni::JNIUtil::Convert2dJavaObjectArrayAndStoreToFloatVector(JNIEnv *env throw std::runtime_error("Unable to get float array elements"); } - for(int j = 0; j < dim; ++j) { + for (int j = 0; j < dim; ++j) { vect->push_back(vector[j]); } env->ReleaseFloatArrayElements(vectorArray, vector, JNI_ABORT); - } + } // End for this->HasExceptionInStack(env); env->DeleteLocalRef(array2dJ); } @@ -285,7 +285,7 @@ void knn_jni::JNIUtil::Convert2dJavaObjectArrayAndStoreToBinaryVector(JNIEnv *en throw std::runtime_error("Unable to get byte array elements"); } - for(int j = 0; j < dim; ++j) { + for (int j = 0; j < dim; ++j) { vect->push_back(vector[j]); } env->ReleaseByteArrayElements(vectorArray, reinterpret_cast(vector), JNI_ABORT); @@ -573,6 +573,11 @@ jlong knn_jni::JNIUtil::CallNonvirtualLongMethodA(JNIEnv * env, jobject obj, jcl return env->CallNonvirtualLongMethodA(obj, clazz, methodID, args); } +void knn_jni::JNIUtil::CallNonvirtualVoidMethodA(JNIEnv * env, jobject obj, jclass clazz, + jmethodID methodID, jvalue* args) { + return env->CallNonvirtualVoidMethodA(obj, clazz, methodID, args); +} + void * knn_jni::JNIUtil::GetPrimitiveArrayCritical(JNIEnv * env, jarray array, jboolean *isCopy) { return env->GetPrimitiveArrayCritical(array, isCopy); } @@ -582,10 +587,11 @@ void knn_jni::JNIUtil::ReleasePrimitiveArrayCritical(JNIEnv * env, jarray array, } jobject knn_jni::GetJObjectFromMapOrThrow(std::unordered_map map, std::string key) { - if(map.find(key) == map.end()) { - throw std::runtime_error(key + " not found"); + auto it = map.find(key); + if (it != map.end()) { + return it->second; } - return map[key]; + throw std::runtime_error(key + " not found"); } //TODO: This potentially should use const char * diff --git a/jni/src/nmslib_wrapper.cpp b/jni/src/nmslib_wrapper.cpp index 536558caa..e8fc77fdb 100644 --- a/jni/src/nmslib_wrapper.cpp +++ b/jni/src/nmslib_wrapper.cpp @@ -26,7 +26,6 @@ #include #include -#include #include "hnswquery.h" #include "method/hnsw.h" @@ -39,7 +38,7 @@ const similarity::LabelType DEFAULT_LABEL = -1; void knn_jni::nmslib_wrapper::CreateIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jintArray idsJ, jlong vectorsAddressJ, jint dimJ, - jstring indexPathJ, jobject parametersJ) { + jobject output, jobject parametersJ) { if (idsJ == nullptr) { throw std::runtime_error("IDs cannot be null"); @@ -53,8 +52,8 @@ void knn_jni::nmslib_wrapper::CreateIndex(knn_jni::JNIUtilInterface *jniUtil, JN throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); } - if (indexPathJ == nullptr) { - throw std::runtime_error("Index path cannot be null"); + if (output == nullptr) { + throw std::runtime_error("Index output stream cannot be null"); } if (parametersJ == nullptr) { @@ -90,9 +89,6 @@ void knn_jni::nmslib_wrapper::CreateIndex(knn_jni::JNIUtilInterface *jniUtil, JN jniUtil->DeleteLocalRef(env, parametersJ); - // Get the path to save the index - std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); - // Get space type for this index jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); @@ -159,7 +155,9 @@ void knn_jni::nmslib_wrapper::CreateIndex(knn_jni::JNIUtilInterface *jniUtil, JN ptr += vectorSizeInBytes; vectorPointer += dim; } - jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); + JNIReleaseElements release_int_array_elements {[=](){ + jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); + }}; // Releasing the vectorsAddressJ memory as that is not required once we have created the index. // This is not the ideal approach, please refer this gh issue for long term solution: @@ -174,17 +172,24 @@ void knn_jni::nmslib_wrapper::CreateIndex(knn_jni::JNIUtilInterface *jniUtil, JN *(space), dataset)); index->CreateIndex(similarity::AnyParams(indexParameters)); - index->SaveIndex(indexPathCpp); - for (auto &it : dataset) { + knn_jni::stream::NativeEngineIndexOutputMediator mediator {jniUtil, env, output}; + knn_jni::stream::NmslibOpenSearchIOWriter writer {&mediator}; + + if (auto hnswFloatIndex = dynamic_cast *>(index.get())) { + hnswFloatIndex->SaveIndexWithStream(writer); + } else { + throw std::runtime_error("We only support similarity::Hnsw in NMSLIB."); + } + + for (auto it : dataset) { delete it; } } catch (...) { - for (auto &it : dataset) { + for (auto it : dataset) { delete it; } - jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); throw; } } @@ -317,25 +322,16 @@ jobjectArray knn_jni::nmslib_wrapper::QueryIndex(knn_jni::JNIUtilInterface *jniU } int queryEfSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, -1); - similarity::KNNQuery - *query; // TODO: Replace with smart pointers https://github.com/opensearch-project/k-NN/issues/1785 + std::unique_ptr> query; std::unique_ptr> neighbors; - try { - if (queryEfSearch == -1) { - query = new similarity::KNNQuery(*(indexWrapper->space), queryObject.get(), kJ); - } else { - query = new similarity::HNSWQuery(*(indexWrapper->space), queryObject.get(), kJ, queryEfSearch); - } - - indexWrapper->index->Search(query); - neighbors.reset(query->Result()->Clone()); - } catch (...) { - if (query != nullptr) { - delete query; - } - throw; + if (queryEfSearch == -1) { + query.reset(new similarity::KNNQuery(*(indexWrapper->space), queryObject.get(), kJ)); + } else { + query.reset(new similarity::HNSWQuery(*(indexWrapper->space), queryObject.get(), kJ, queryEfSearch)); } - delete query; + + indexWrapper->index->Search(query.get()); + neighbors.reset(query->Result()->Clone()); int resultSize = neighbors->Size(); jclass resultClass = jniUtil->FindClass(env, "org/opensearch/knn/index/query/KNNQueryResult"); diff --git a/jni/src/org_opensearch_knn_jni_FaissService.cpp b/jni/src/org_opensearch_knn_jni_FaissService.cpp index 7326c7ba0..836774402 100644 --- a/jni/src/org_opensearch_knn_jni_FaissService.cpp +++ b/jni/src/org_opensearch_knn_jni_FaissService.cpp @@ -41,8 +41,8 @@ void JNI_OnUnload(JavaVM *vm, void *reserved) { } JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initIndex(JNIEnv * env, jclass cls, - jlong numDocs, jint dimJ, - jobject parametersJ) + jlong numDocs, jint dimJ, + jobject parametersJ) { try { std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); @@ -55,8 +55,8 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initIndex(JNIEn } JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initBinaryIndex(JNIEnv * env, jclass cls, - jlong numDocs, jint dimJ, - jobject parametersJ) + jlong numDocs, jint dimJ, + jobject parametersJ) { try { std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); @@ -69,8 +69,8 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initBinaryIndex } JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initByteIndex(JNIEnv * env, jclass cls, - jlong numDocs, jint dimJ, - jobject parametersJ) + jlong numDocs, jint dimJ, + jobject parametersJ) { try { std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); @@ -83,8 +83,8 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initByteIndex(J } JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToIndex(JNIEnv * env, jclass cls, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, - jlong indexAddress, jint threadCount) + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount) { try { std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); @@ -97,8 +97,8 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToIndex(JN } JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToBinaryIndex(JNIEnv * env, jclass cls, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, - jlong indexAddress, jint threadCount) + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount) { try { std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); @@ -111,8 +111,8 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToBinaryIn } JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToByteIndex(JNIEnv * env, jclass cls, jintArray idsJ, - jlong vectorsAddressJ, jint dimJ, - jlong indexAddress, jint threadCount) + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount) { try { std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); @@ -124,85 +124,112 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToByteInde } } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeIndex(JNIEnv * env, jclass cls, - jlong indexAddress, - jstring indexPathJ) +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeIndex(JNIEnv * env, + jclass cls, + jlong indexAddress, + jobject output) { - try { - std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); - knn_jni::faiss_wrapper::IndexService indexService(std::move(faissMethods)); - knn_jni::faiss_wrapper::WriteIndex(&jniUtil, env, indexPathJ, indexAddress, &indexService); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } -} - -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeBinaryIndex(JNIEnv * env, jclass cls, - jlong indexAddress, - jstring indexPathJ) + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::IndexService indexService(std::move(faissMethods)); + knn_jni::faiss_wrapper::WriteIndex(&jniUtil, env, output, indexAddress, &indexService); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeBinaryIndex(JNIEnv * env, + jclass cls, + jlong indexAddress, + jobject output) { - try { - std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); - knn_jni::faiss_wrapper::BinaryIndexService binaryIndexService(std::move(faissMethods)); - knn_jni::faiss_wrapper::WriteIndex(&jniUtil, env, indexPathJ, indexAddress, &binaryIndexService); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } -} - -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeByteIndex(JNIEnv * env, jclass cls, - jlong indexAddress, - jstring indexPathJ) + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::BinaryIndexService binaryIndexService(std::move(faissMethods)); + knn_jni::faiss_wrapper::WriteIndex(&jniUtil, env, output, indexAddress, &binaryIndexService); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeByteIndex(JNIEnv * env, + jclass cls, + jlong indexAddress, + jobject output) { - try { - std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); - knn_jni::faiss_wrapper::ByteIndexService byteIndexService(std::move(faissMethods)); - knn_jni::faiss_wrapper::WriteIndex(&jniUtil, env, indexPathJ, indexAddress, &byteIndexService); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::ByteIndexService byteIndexService(std::move(faissMethods)); + knn_jni::faiss_wrapper::WriteIndex(&jniUtil, env, output, indexAddress, &byteIndexService); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createIndexFromTemplate(JNIEnv * env, jclass cls, +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createIndexFromTemplate(JNIEnv * env, + jclass cls, jintArray idsJ, jlong vectorsAddressJ, jint dimJ, - jstring indexPathJ, + jobject output, jbyteArray templateIndexJ, jobject parametersJ) { try { - knn_jni::faiss_wrapper::CreateIndexFromTemplate(&jniUtil, env, idsJ, vectorsAddressJ, dimJ, indexPathJ, templateIndexJ, parametersJ); + knn_jni::faiss_wrapper::CreateIndexFromTemplate(&jniUtil, + env, + idsJ, + vectorsAddressJ, + dimJ, + output, + templateIndexJ, + parametersJ); } catch (...) { jniUtil.CatchCppExceptionAndThrowJava(env); } } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createBinaryIndexFromTemplate(JNIEnv * env, jclass cls, - jintArray idsJ, - jlong vectorsAddressJ, - jint dimJ, - jstring indexPathJ, - jbyteArray templateIndexJ, - jobject parametersJ) +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createBinaryIndexFromTemplate(JNIEnv * env, + jclass cls, + jintArray idsJ, + jlong vectorsAddressJ, + jint dimJ, + jobject output, + jbyteArray templateIndexJ, + jobject parametersJ) { try { - knn_jni::faiss_wrapper::CreateBinaryIndexFromTemplate(&jniUtil, env, idsJ, vectorsAddressJ, dimJ, indexPathJ, templateIndexJ, parametersJ); + knn_jni::faiss_wrapper::CreateBinaryIndexFromTemplate(&jniUtil, + env, + idsJ, + vectorsAddressJ, + dimJ, + output, + templateIndexJ, + parametersJ); } catch (...) { jniUtil.CatchCppExceptionAndThrowJava(env); } } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createByteIndexFromTemplate(JNIEnv * env, jclass cls, - jintArray idsJ, - jlong vectorsAddressJ, - jint dimJ, - jstring indexPathJ, - jbyteArray templateIndexJ, - jobject parametersJ) +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createByteIndexFromTemplate(JNIEnv * env, + jclass cls, + jintArray idsJ, + jlong vectorsAddressJ, + jint dimJ, + jobject output, + jbyteArray templateIndexJ, + jobject parametersJ) { try { - knn_jni::faiss_wrapper::CreateByteIndexFromTemplate(&jniUtil, env, idsJ, vectorsAddressJ, dimJ, indexPathJ, templateIndexJ, parametersJ); + knn_jni::faiss_wrapper::CreateByteIndexFromTemplate(&jniUtil, + env, + idsJ, + vectorsAddressJ, + dimJ, + output, + templateIndexJ, + parametersJ); } catch (...) { jniUtil.CatchCppExceptionAndThrowJava(env); } @@ -210,16 +237,17 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createByteIndexF JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndex(JNIEnv * env, jclass cls, jstring indexPathJ) { - try { - return knn_jni::faiss_wrapper::LoadIndex(&jniUtil, env, indexPathJ); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } - return NULL; + try { + return knn_jni::faiss_wrapper::LoadIndex(&jniUtil, env, indexPathJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return NULL; } -JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndexWithStream - (JNIEnv * env, jclass cls, jobject readStream) +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndexWithStream(JNIEnv * env, + jclass cls, + jobject readStream) { try { // Create a mediator locally. @@ -249,8 +277,9 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndex return NULL; } -JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndexWithStream - (JNIEnv * env, jclass cls, jobject readStream) +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndexWithStream(JNIEnv * env, + jclass cls, + jobject readStream) { try { // Create a mediator locally. @@ -262,7 +291,7 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndex // Pass IOReader to Faiss for loading vector index. return knn_jni::faiss_wrapper::LoadBinaryIndexWithStream( - &faissOpenSearchIOReader); + &faissOpenSearchIOReader); } catch (...) { jniUtil.CatchCppExceptionAndThrowJava(env); } @@ -270,8 +299,9 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndex return NULL; } -JNIEXPORT jboolean JNICALL Java_org_opensearch_knn_jni_FaissService_isSharedIndexStateRequired - (JNIEnv * env, jclass cls, jlong indexPointerJ) +JNIEXPORT jboolean JNICALL Java_org_opensearch_knn_jni_FaissService_isSharedIndexStateRequired(JNIEnv * env, + jclass cls, + jlong indexPointerJ) { try { return knn_jni::faiss_wrapper::IsSharedIndexStateRequired(indexPointerJ); @@ -425,10 +455,10 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_transferVectors } JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_rangeSearchIndex(JNIEnv * env, jclass cls, - jlong indexPointerJ, - jfloatArray queryVectorJ, - jfloat radiusJ, jobject methodParamsJ, - jint maxResultWindowJ, jintArray parentIdsJ) + jlong indexPointerJ, + jfloatArray queryVectorJ, + jfloat radiusJ, jobject methodParamsJ, + jint maxResultWindowJ, jintArray parentIdsJ) { try { return knn_jni::faiss_wrapper::RangeSearch(&jniUtil, env, indexPointerJ, queryVectorJ, radiusJ, methodParamsJ, maxResultWindowJ, parentIdsJ); @@ -439,10 +469,10 @@ JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_rangeSea } JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_rangeSearchIndexWithFilter(JNIEnv * env, jclass cls, - jlong indexPointerJ, - jfloatArray queryVectorJ, - jfloat radiusJ, jobject methodParamsJ, jint maxResultWindowJ, - jlongArray filterIdsJ, jint filterIdsTypeJ, jintArray parentIdsJ) + jlong indexPointerJ, + jfloatArray queryVectorJ, + jfloat radiusJ, jobject methodParamsJ, jint maxResultWindowJ, + jlongArray filterIdsJ, jint filterIdsTypeJ, jintArray parentIdsJ) { try { return knn_jni::faiss_wrapper::RangeSearchWithFilter(&jniUtil, env, indexPointerJ, queryVectorJ, radiusJ, methodParamsJ, maxResultWindowJ, filterIdsJ, filterIdsTypeJ, parentIdsJ); diff --git a/jni/src/org_opensearch_knn_jni_NmslibService.cpp b/jni/src/org_opensearch_knn_jni_NmslibService.cpp index 8e4df2e9c..15bc8420e 100644 --- a/jni/src/org_opensearch_knn_jni_NmslibService.cpp +++ b/jni/src/org_opensearch_knn_jni_NmslibService.cpp @@ -41,10 +41,10 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_createIndex(JNI jintArray idsJ, jlong vectorsAddressJ, jint dimJ, - jstring indexPathJ, + jobject output, jobject parametersJ) { try { - knn_jni::nmslib_wrapper::CreateIndex(&jniUtil, env, idsJ, vectorsAddressJ, dimJ, indexPathJ, parametersJ); + knn_jni::nmslib_wrapper::CreateIndex(&jniUtil, env, idsJ, vectorsAddressJ, dimJ, output, parametersJ); } catch (...) { jniUtil.CatchCppExceptionAndThrowJava(env); } diff --git a/jni/tests/faiss_index_service_test.cpp b/jni/tests/faiss_index_service_test.cpp index 8d9e4bb43..127ca07b8 100644 --- a/jni/tests/faiss_index_service_test.cpp +++ b/jni/tests/faiss_index_service_test.cpp @@ -19,9 +19,9 @@ #include "gtest/gtest.h" #include "commons.h" -using ::testing::_; using ::testing::NiceMock; using ::testing::Return; +using ::testing::_; TEST(CreateIndexTest, BasicAssertions) { // Define the data @@ -38,6 +38,7 @@ TEST(CreateIndexTest, BasicAssertions) { } std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::FileIOWriter fileIOWriter {indexPath.c_str()}; faiss::MetricType metricType = faiss::METRIC_L2; std::string indexDescription = "HNSW32,Flat"; int threadCount = 1; @@ -59,14 +60,14 @@ TEST(CreateIndexTest, BasicAssertions) { .WillOnce(Return(index)); EXPECT_CALL(*mockFaissMethods, indexIdMap(index)) .WillOnce(Return(indexIdMap)); - EXPECT_CALL(*mockFaissMethods, writeIndex(indexIdMap, ::testing::StrEq(indexPath.c_str()))) + EXPECT_CALL(*mockFaissMethods, writeIndex(indexIdMap, ::testing::Eq(&fileIOWriter))) .Times(1); // Create the index knn_jni::faiss_wrapper::IndexService indexService(std::move(mockFaissMethods)); long indexAddress = indexService.initIndex(&mockJNIUtil, jniEnv, metricType, indexDescription, dim, numIds, threadCount, parametersMap); indexService.insertToIndex(dim, numIds, threadCount, (int64_t) &vectors, ids, indexAddress); - indexService.writeIndex(indexPath, indexAddress); + indexService.writeIndex(&fileIOWriter, indexAddress); } TEST(CreateBinaryIndexTest, BasicAssertions) { @@ -84,6 +85,7 @@ TEST(CreateBinaryIndexTest, BasicAssertions) { } std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::FileIOWriter fileIOWriter {indexPath.c_str()}; faiss::MetricType metricType = faiss::METRIC_L2; std::string indexDescription = "BHNSW32"; int threadCount = 1; @@ -105,14 +107,14 @@ TEST(CreateBinaryIndexTest, BasicAssertions) { .WillOnce(Return(index)); EXPECT_CALL(*mockFaissMethods, indexBinaryIdMap(index)) .WillOnce(Return(indexIdMap)); - EXPECT_CALL(*mockFaissMethods, writeIndexBinary(indexIdMap, ::testing::StrEq(indexPath.c_str()))) + EXPECT_CALL(*mockFaissMethods, writeIndexBinary(indexIdMap, ::testing::Eq(&fileIOWriter))) .Times(1); // Create the index knn_jni::faiss_wrapper::BinaryIndexService indexService(std::move(mockFaissMethods)); long indexAddress = indexService.initIndex(&mockJNIUtil, jniEnv, metricType, indexDescription, dim, numIds, threadCount, parametersMap); indexService.insertToIndex(dim, numIds, threadCount, (int64_t) &vectors, ids, indexAddress); - indexService.writeIndex(indexPath, indexAddress); + indexService.writeIndex(&fileIOWriter, indexAddress); } TEST(CreateByteIndexTest, BasicAssertions) { @@ -130,6 +132,7 @@ TEST(CreateByteIndexTest, BasicAssertions) { } std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::FileIOWriter fileIOWriter {indexPath.c_str()}; faiss::MetricType metricType = faiss::METRIC_L2; std::string indexDescription = "HNSW16,SQ8_direct_signed"; int threadCount = 1; @@ -149,12 +152,12 @@ TEST(CreateByteIndexTest, BasicAssertions) { .WillOnce(Return(index)); EXPECT_CALL(*mockFaissMethods, indexIdMap(index)) .WillOnce(Return(indexIdMap)); - EXPECT_CALL(*mockFaissMethods, writeIndex(indexIdMap, ::testing::StrEq(indexPath.c_str()))) + EXPECT_CALL(*mockFaissMethods, writeIndex(indexIdMap, ::testing::Eq(&fileIOWriter))) .Times(1); // Create the index knn_jni::faiss_wrapper::ByteIndexService indexService(std::move(mockFaissMethods)); long indexAddress = indexService.initIndex(&mockJNIUtil, jniEnv, metricType, indexDescription, dim, numIds, threadCount, parametersMap); indexService.insertToIndex(dim, numIds, threadCount, (int64_t) &vectors, ids, indexAddress); - indexService.writeIndex(indexPath, indexAddress); + indexService.writeIndex(&fileIOWriter, indexAddress); } \ No newline at end of file diff --git a/jni/tests/faiss_stream_support_test.cpp b/jni/tests/faiss_stream_support_test.cpp index 94a9b3991..79beea7cb 100644 --- a/jni/tests/faiss_stream_support_test.cpp +++ b/jni/tests/faiss_stream_support_test.cpp @@ -62,7 +62,10 @@ TEST(FaissStreamSupportTest, NativeEngineIndexInputMediatorCopyWhenEmpty) { setUpMockJNIUtil(javaIndexInputMock, mockJni); // Prepare copying - NativeEngineIndexInputMediator mediator{&mockJni, nullptr, nullptr}; + NiceMock jniEnv; + // It's a dummy value, which will not be used. If we pass a null, then NPE will be raised. + jobject jobjectDummy = reinterpret_cast(1); + NativeEngineIndexInputMediator mediator{&mockJni, &jniEnv, jobjectDummy}; std::string readBuffer(javaIndexInputMock.readTargetBytes.size(), '\0'); // Call copyBytes @@ -82,7 +85,10 @@ TEST(FaissStreamSupportTest, FaissOpenSearchIOReaderCopy) { setUpMockJNIUtil(javaIndexInputMock, mockJni); // Prepare copying - NativeEngineIndexInputMediator mediator{&mockJni, nullptr, nullptr}; + NiceMock jniEnv; + // It's a dummy value, which will not be used. If we pass a null, then NPE will be raised. + jobject jobjectDummy = reinterpret_cast(1); + NativeEngineIndexInputMediator mediator{&mockJni, &jniEnv, jobjectDummy}; std::string readBuffer; readBuffer.resize(javaIndexInputMock.readTargetBytes.size()); FaissOpenSearchIOReader ioReader{&mediator}; diff --git a/jni/tests/faiss_wrapper_test.cpp b/jni/tests/faiss_wrapper_test.cpp index 5f6f83c46..651201964 100644 --- a/jni/tests/faiss_wrapper_test.cpp +++ b/jni/tests/faiss_wrapper_test.cpp @@ -20,17 +20,22 @@ #include "faiss/IndexHNSW.h" #include "faiss/IndexIVFPQ.h" #include "mocks/faiss_index_service_mock.h" +#include "native_stream_support_util.h" -using ::testing::_; +using ::test_util::JavaFileIndexOutputMock; +using ::test_util::MockJNIUtil; +using ::test_util::StreamIOError; +using ::test_util::setUpJavaFileOutputMocking; +using ::testing::Mock; using ::testing::NiceMock; using ::testing::Return; -using ::testing::Mock; +using ::testing::_; -float randomDataMin = -500.0; -float randomDataMax = 500.0; -float rangeSearchRandomDataMin = -50; -float rangeSearchRandomDataMax = 50; -float rangeSearchRadius = 20000; +const float randomDataMin = -500.0; +const float randomDataMax = 500.0; +const float rangeSearchRandomDataMin = -50; +const float rangeSearchRandomDataMax = 50; +const float rangeSearchRadius = 20000; void createIndexIteratively( knn_jni::JNIUtilInterface * JNIUtil, @@ -38,23 +43,25 @@ void createIndexIteratively( std::vector & ids, std::vector & vectors, int dim, - std::string & indexPath, - std::unordered_map parametersMap, + jobject javaFileOutputMock, + std::unordered_map parametersMap, IndexService * indexService, int insertions = 10 ) { long numDocs = ids.size(); - if(numDocs % insertions != 0) { + if (numDocs % insertions != 0) { throw std::invalid_argument("Number of documents should be divisible by number of insertions"); } long docsPerInsertion = numDocs / insertions; long index_ptr = knn_jni::faiss_wrapper::InitIndex(JNIUtil, jniEnv, numDocs, dim, (jobject)¶metersMap, indexService); - for(int i = 0; i < insertions; i++) { + std::vector insertIds; + std::vector insertVecs; + for (int i = 0; i < insertions; i++) { + insertIds.clear(); + insertVecs.clear(); int start_idx = i * docsPerInsertion; int end_idx = start_idx + docsPerInsertion; - std::vector insertIds; - std::vector insertVecs; - for(int j = start_idx; j < end_idx; j++) { + for (int j = start_idx; j < end_idx; j++) { insertIds.push_back(j); for(int k = 0; k < dim; k++) { insertVecs.push_back(vectors[j * dim + k]); @@ -62,7 +69,7 @@ void createIndexIteratively( } knn_jni::faiss_wrapper::InsertToIndex(JNIUtil, jniEnv, reinterpret_cast(&insertIds), (jlong)&insertVecs, dim, index_ptr, 0, indexService); } - knn_jni::faiss_wrapper::WriteIndex(JNIUtil, jniEnv, (jstring)&indexPath, index_ptr, indexService); + knn_jni::faiss_wrapper::WriteIndex(JNIUtil, jniEnv, javaFileOutputMock, index_ptr, indexService); } void createBinaryIndexIteratively( @@ -71,21 +78,25 @@ void createBinaryIndexIteratively( std::vector & ids, std::vector & vectors, int dim, - std::string & indexPath, + jobject javaFileOutputMock, std::unordered_map parametersMap, IndexService * indexService, int insertions = 10 ) { - long numDocs = ids.size();; + long numDocs = ids.size(); long index_ptr = knn_jni::faiss_wrapper::InitIndex(JNIUtil, jniEnv, numDocs, dim, (jobject)¶metersMap, indexService); - for(int i = 0; i < insertions; i++) { + std::vector insertIds; + std::vector insertVecs; + for (int i = 0; i < insertions; i++) { int start_idx = numDocs * i / insertions; int end_idx = numDocs * (i + 1) / insertions; int docs_to_insert = end_idx - start_idx; - if(docs_to_insert == 0) continue; - std::vector insertIds; - std::vector insertVecs; - for(int j = start_idx; j < end_idx; j++) { + if (docs_to_insert == 0) { + continue; + } + insertIds.clear(); + insertVecs.clear(); + for (int j = start_idx; j < end_idx; j++) { insertIds.push_back(j); for(int k = 0; k < dim / 8; k++) { insertVecs.push_back(vectors[j * (dim / 8) + k]); @@ -93,7 +104,8 @@ void createBinaryIndexIteratively( } knn_jni::faiss_wrapper::InsertToIndex(JNIUtil, jniEnv, reinterpret_cast(&insertIds), (jlong)&insertVecs, dim, index_ptr, 0, indexService); } - knn_jni::faiss_wrapper::WriteIndex(JNIUtil, jniEnv, (jstring)&indexPath, index_ptr, indexService); + + knn_jni::faiss_wrapper::WriteIndex(JNIUtil, jniEnv, javaFileOutputMock, index_ptr, indexService); } TEST(FaissCreateIndexTest, BasicAssertions) { @@ -104,10 +116,10 @@ TEST(FaissCreateIndexTest, BasicAssertions) { int dim = 2; vectors.reserve(dim * numIds); for (int64_t i = 0; i < numIds; ++i) { - ids.push_back(i); - for (int j = 0; j < dim; ++j) { - vectors.push_back(test_util::RandomFloat(-500.0, 500.0)); - } + ids.push_back(i); + for (int j = 0; j < dim; ++j) { + vectors.push_back(test_util::RandomFloat(-500.0, 500.0)); + } } std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); @@ -121,8 +133,10 @@ TEST(FaissCreateIndexTest, BasicAssertions) { parametersMap[knn_jni::PARAMETERS] = (jobject)&subParametersMap; // Set up jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, false); // Create the index std::unique_ptr faissMethods(new FaissMethods()); @@ -132,10 +146,18 @@ TEST(FaissCreateIndexTest, BasicAssertions) { .Times(1); EXPECT_CALL(mockIndexService, insertToIndex(dim, numIds / insertions, 0, _, _, _)) .Times(insertions); - EXPECT_CALL(mockIndexService, writeIndex(indexPath, _)) + EXPECT_CALL(mockIndexService, writeIndex(_, _)) .Times(1); - createIndexIteratively(&mockJNIUtil, jniEnv, ids, vectors, dim, indexPath, parametersMap, &mockIndexService, insertions); + createIndexIteratively(&mockJNIUtil, + &jniEnv, + ids, + vectors, + dim, + (jobject) (&javaFileIndexOutputMock), + parametersMap, + &mockIndexService, + insertions); } TEST(FaissCreateBinaryIndexTest, BasicAssertions) { @@ -146,10 +168,10 @@ TEST(FaissCreateBinaryIndexTest, BasicAssertions) { int dim = 128; vectors.reserve(numIds); for (int64_t i = 0; i < numIds; ++i) { - ids.push_back(i); - for (int j = 0; j < dim / 8; ++j) { - vectors.push_back(test_util::RandomInt(0, 255)); - } + ids.push_back(i); + for (int j = 0; j < dim / 8; ++j) { + vectors.push_back(test_util::RandomInt(0, 255)); + } } std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); @@ -163,8 +185,10 @@ TEST(FaissCreateBinaryIndexTest, BasicAssertions) { parametersMap[knn_jni::PARAMETERS] = (jobject)&subParametersMap; // Set up jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, false); // Create the index std::unique_ptr faissMethods(new FaissMethods()); @@ -174,109 +198,132 @@ TEST(FaissCreateBinaryIndexTest, BasicAssertions) { .Times(1); EXPECT_CALL(mockIndexService, insertToIndex(dim, numIds / insertions, 0, _, _, _)) .Times(insertions); - EXPECT_CALL(mockIndexService, writeIndex(indexPath, _)) + EXPECT_CALL(mockIndexService, writeIndex(_, _)) .Times(1); // This method calls delete vectors at the end - createBinaryIndexIteratively(&mockJNIUtil, jniEnv, ids, vectors, dim, indexPath, parametersMap, &mockIndexService, insertions); + createBinaryIndexIteratively(&mockJNIUtil, + &jniEnv, + ids, + vectors, + dim, + (jobject) (&javaFileIndexOutputMock), + parametersMap, + &mockIndexService, + insertions); } TEST(FaissCreateIndexFromTemplateTest, BasicAssertions) { - // Define the data - faiss::idx_t numIds = 100; - std::vector ids; - auto *vectors = new std::vector(); - int dim = 2; - vectors->reserve(dim * numIds); - for (int64_t i = 0; i < numIds; ++i) { - ids.push_back(i); - for (int j = 0; j < dim; ++j) { + for (auto throwIOException : std::array {false, true}) { + // Define the data + faiss::idx_t numIds = 100; + std::vector ids; + auto *vectors = new std::vector(); + int dim = 2; + vectors->reserve(dim * numIds); + for (int64_t i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { vectors->push_back(test_util::RandomFloat(-500.0, 500.0)); + } } - } - std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); - faiss::MetricType metricType = faiss::METRIC_L2; - std::string method = "HNSW32,Flat"; + std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::MetricType metricType = faiss::METRIC_L2; + std::string method = "HNSW32,Flat"; - std::unique_ptr createdIndex( + std::unique_ptr createdIndex( test_util::FaissCreateIndex(dim, method, metricType)); - auto vectorIoWriter = test_util::FaissGetSerializedIndex(createdIndex.get()); - - // Setup jni - JNIEnv *jniEnv = nullptr; - NiceMock mockJNIUtil; - - EXPECT_CALL(mockJNIUtil, - GetJavaObjectArrayLength( - jniEnv, reinterpret_cast(&vectors))) - .WillRepeatedly(Return(vectors->size())); - - std::string spaceType = knn_jni::L2; - std::unordered_map parametersMap; - parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + auto vectorIoWriter = test_util::FaissGetSerializedIndex(createdIndex.get()); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); + + std::string spaceType = knn_jni::L2; + std::unordered_map parametersMap; + parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + + try { + knn_jni::faiss_wrapper::CreateIndexFromTemplate( + &mockJNIUtil, &jniEnv, reinterpret_cast(&ids), + (jlong)vectors, dim, (jobject)(&javaFileIndexOutputMock), + reinterpret_cast(&(vectorIoWriter.data)), + (jobject) ¶metersMap); + javaFileIndexOutputMock.file_writer.close(); + } catch (const StreamIOError& e) { + ASSERT_TRUE(throwIOException); + continue; + } - knn_jni::faiss_wrapper::CreateIndexFromTemplate( - &mockJNIUtil, jniEnv, reinterpret_cast(&ids), - (jlong)vectors, dim, (jstring)&indexPath, - reinterpret_cast(&(vectorIoWriter.data)), - (jobject) ¶metersMap - ); + ASSERT_FALSE(throwIOException); - // Make sure index can be loaded - std::unique_ptr index(test_util::FaissLoadIndex(indexPath)); + // Make sure index can be loaded + std::unique_ptr index(test_util::FaissLoadIndex(indexPath)); - // Clean up - std::remove(indexPath.c_str()); + // Clean up + std::remove(indexPath.c_str()); + } // End for } TEST(FaissCreateByteIndexFromTemplateTest, BasicAssertions) { - // Define the data - faiss::idx_t numIds = 100; - std::vector ids; - auto *vectors = new std::vector(); - int dim = 8; - vectors->reserve(dim * numIds); - for (int64_t i = 0; i < numIds; ++i) { - ids.push_back(i); - for (int j = 0; j < dim; ++j) { + for (auto throwIOException : std::array {false, true}) { + // Define the data + faiss::idx_t numIds = 100; + std::vector ids; + auto *vectors = new std::vector(); + int dim = 8; + vectors->reserve(dim * numIds); + for (int64_t i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { vectors->push_back(test_util::RandomInt(-128, 127)); + } } - } - std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); - faiss::MetricType metricType = faiss::METRIC_L2; - std::string method = "HNSW32,SQ8_direct_signed"; + std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::MetricType metricType = faiss::METRIC_L2; + std::string method = "HNSW32,SQ8_direct_signed"; - std::unique_ptr createdIndex( + std::unique_ptr createdIndex( test_util::FaissCreateIndex(dim, method, metricType)); - auto vectorIoWriter = test_util::FaissGetSerializedIndex(createdIndex.get()); - - // Setup jni - JNIEnv *jniEnv = nullptr; - NiceMock mockJNIUtil; - - EXPECT_CALL(mockJNIUtil, - GetJavaObjectArrayLength( - jniEnv, reinterpret_cast(&vectors))) - .WillRepeatedly(Return(vectors->size())); + auto vectorIoWriter = test_util::FaissGetSerializedIndex(createdIndex.get()); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); + + std::string spaceType = knn_jni::L2; + std::unordered_map parametersMap; + parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + + try { + knn_jni::faiss_wrapper::CreateByteIndexFromTemplate( + &mockJNIUtil, &jniEnv, reinterpret_cast(&ids), + (jlong) vectors, dim, (jstring) (&javaFileIndexOutputMock), + reinterpret_cast(&(vectorIoWriter.data)), + (jobject) ¶metersMap + ); - std::string spaceType = knn_jni::L2; - std::unordered_map parametersMap; - parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + // Make sure we close a file stream before reopening the created file. + javaFileIndexOutputMock.file_writer.close(); + } catch (const StreamIOError& e) { + ASSERT_TRUE(throwIOException); + continue; + } - knn_jni::faiss_wrapper::CreateByteIndexFromTemplate( - &mockJNIUtil, jniEnv, reinterpret_cast(&ids), - (jlong)vectors, dim, (jstring)&indexPath, - reinterpret_cast(&(vectorIoWriter.data)), - (jobject) ¶metersMap - ); + ASSERT_FALSE(throwIOException); - // Make sure index can be loaded - std::unique_ptr index(test_util::FaissLoadIndex(indexPath)); + // Make sure index can be loaded + std::unique_ptr index(test_util::FaissLoadIndex(indexPath)); - // Clean up - std::remove(indexPath.c_str()); + // Clean up + std::remove(indexPath.c_str()); + } // End for } TEST(FaissLoadIndexTest, BasicAssertions) { @@ -299,12 +346,12 @@ TEST(FaissLoadIndexTest, BasicAssertions) { test_util::FaissWriteIndex(&createdIndexWithData, indexPath); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; std::unique_ptr loadedIndexPointer( reinterpret_cast(knn_jni::faiss_wrapper::LoadIndex( - &mockJNIUtil, jniEnv, (jstring)&indexPath))); + &mockJNIUtil, &jniEnv, (jstring)&indexPath))); // Compare serialized versions auto createIndexSerialization = @@ -339,7 +386,6 @@ TEST(FaissLoadBinaryIndexTest, BasicAssertions) { } std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); - std::string spaceType = knn_jni::HAMMING; std::string method = "BHNSW32"; // Create the index @@ -351,12 +397,12 @@ TEST(FaissLoadBinaryIndexTest, BasicAssertions) { test_util::FaissWriteBinaryIndex(&createdIndexWithData, indexPath); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; std::unique_ptr loadedIndexPointer( reinterpret_cast(knn_jni::faiss_wrapper::LoadBinaryIndex( - &mockJNIUtil, jniEnv, (jstring)&indexPath))); + &mockJNIUtil, &jniEnv, (jstring)&indexPath))); // Compare serialized versions auto createIndexSerialization = @@ -394,12 +440,12 @@ TEST(FaissLoadIndexTest, HNSWPQDisableSdcTable) { test_util::FaissWriteIndex(&faissIndexWithIDMap, indexPath); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; std::unique_ptr loadedIndexPointer( reinterpret_cast(knn_jni::faiss_wrapper::LoadIndex( - &mockJNIUtil, jniEnv, (jstring)&indexPath))); + &mockJNIUtil, &jniEnv, (jstring)&indexPath))); // Cast down until we get to the pq backed storage index and checke the size of the table auto idMapIndex = dynamic_cast(loadedIndexPointer.get()); @@ -427,12 +473,12 @@ TEST(FaissLoadIndexTest, IVFPQDisablePrecomputeTable) { test_util::FaissWriteIndex(&faissIndexWithIDMap, indexPath); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; std::unique_ptr loadedIndexPointer( reinterpret_cast(knn_jni::faiss_wrapper::LoadIndex( - &mockJNIUtil, jniEnv, (jstring)&indexPath))); + &mockJNIUtil, &jniEnv, (jstring)&indexPath))); // Cast down until we get to the ivfpq-l2 state auto idMapIndex = dynamic_cast(loadedIndexPointer.get()); @@ -477,7 +523,7 @@ TEST(FaissQueryIndexTest, BasicAssertions) { test_util::FaissAddData(createdIndex.get(), ids, vectors); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; auto methodParamsJ = reinterpret_cast(&methodParams); @@ -485,7 +531,7 @@ TEST(FaissQueryIndexTest, BasicAssertions) { std::unique_ptr *>> results( reinterpret_cast *> *>( knn_jni::faiss_wrapper::QueryIndex( - &mockJNIUtil, jniEnv, + &mockJNIUtil, &jniEnv, reinterpret_cast(&createdIndexWithData), reinterpret_cast(&query), k, methodParamsJ, nullptr))); @@ -533,14 +579,14 @@ TEST(FaissQueryBinaryIndexTest, BasicAssertions) { test_util::FaissAddBinaryData(createdIndex.get(), ids, vectors); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; for (auto query : queries) { std::unique_ptr *>> results( reinterpret_cast *> *>( knn_jni::faiss_wrapper::QueryBinaryIndex_WithFilter( - &mockJNIUtil, jniEnv, + &mockJNIUtil, &jniEnv, reinterpret_cast(&createdIndexWithData), reinterpret_cast(&query), k, nullptr, nullptr, 0, nullptr))); @@ -594,11 +640,11 @@ TEST(FaissQueryIndexWithFilterTest1435, BasicAssertions) { test_util::FaissAddData(createdIndex.get(), ids, vectors); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; EXPECT_CALL(mockJNIUtil, GetJavaLongArrayLength( - jniEnv, reinterpret_cast(&bitmap))) + &jniEnv, reinterpret_cast(&bitmap))) .WillRepeatedly(Return(bitmap.size())); int k = 20; @@ -606,7 +652,7 @@ TEST(FaissQueryIndexWithFilterTest1435, BasicAssertions) { std::unique_ptr *>> results( reinterpret_cast *> *>( knn_jni::faiss_wrapper::QueryIndex_WithFilter( - &mockJNIUtil, jniEnv, + &mockJNIUtil, &jniEnv, reinterpret_cast(&createdIndexWithData), reinterpret_cast(&query), k, nullptr, reinterpret_cast(&bitmap), 0, nullptr))); @@ -671,17 +717,17 @@ TEST(FaissQueryIndexWithParentFilterTest, BasicAssertions) { methodParams[knn_jni::EF_SEARCH] = reinterpret_cast(&efSearch); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; EXPECT_CALL(mockJNIUtil, GetJavaIntArrayLength( - jniEnv, reinterpret_cast(&parentIds))) + &jniEnv, reinterpret_cast(&parentIds))) .WillRepeatedly(Return(parentIds.size())); for (auto query : queries) { std::unique_ptr *>> results( reinterpret_cast *> *>( knn_jni::faiss_wrapper::QueryIndex( - &mockJNIUtil, jniEnv, + &mockJNIUtil, &jniEnv, reinterpret_cast(&createdIndexWithData), reinterpret_cast(&query), k, reinterpret_cast(&methodParams), reinterpret_cast(&parentIds)))); @@ -749,14 +795,14 @@ TEST(FaissTrainIndexTest, BasicAssertions) { std::vector trainingVectors = test_util::RandomVectors(dim, numTrainingVectors, randomDataMin, randomDataMax); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; // Perform training std::unique_ptr> trainedIndexSerialization( reinterpret_cast *>( knn_jni::faiss_wrapper::TrainIndex( - &mockJNIUtil, jniEnv, (jobject) ¶metersMap, dim, + &mockJNIUtil, &jniEnv, (jobject) ¶metersMap, dim, reinterpret_cast(&trainingVectors)))); std::unique_ptr trainedIndex( @@ -781,14 +827,14 @@ TEST(FaissTrainByteIndexTest, BasicAssertions) { std::vector trainingVectors = test_util::RandomByteVectors(dim, numTrainingVectors, -128, 127); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; // Perform training std::unique_ptr> trainedIndexSerialization( reinterpret_cast *>( knn_jni::faiss_wrapper::TrainByteIndex( - &mockJNIUtil, jniEnv, (jobject) ¶metersMap, dim, + &mockJNIUtil, &jniEnv, (jobject) ¶metersMap, dim, reinterpret_cast(&trainingVectors)))); std::unique_ptr trainedIndex( @@ -812,7 +858,6 @@ TEST(FaissCreateHnswSQfp16IndexTest, BasicAssertions) { } } - std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); std::string spaceType = knn_jni::L2; std::string index_description = "HNSW32,SQfp16"; @@ -820,31 +865,46 @@ TEST(FaissCreateHnswSQfp16IndexTest, BasicAssertions) { parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; parametersMap[knn_jni::INDEX_DESCRIPTION] = (jobject)&index_description; - // Set up jni - JNIEnv *jniEnv = nullptr; - NiceMock mockJNIUtil; + for (auto throwIOException : std::array {false, true}) { + const std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); - EXPECT_CALL(mockJNIUtil, - GetJavaObjectArrayLength( - jniEnv, reinterpret_cast(&vectors))) + // Set up jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); + + EXPECT_CALL(mockJNIUtil, + GetJavaObjectArrayLength( + &jniEnv, reinterpret_cast(&vectors))) .WillRepeatedly(Return(vectors.size())); - // Create the index - std::unique_ptr faissMethods(new FaissMethods()); - knn_jni::faiss_wrapper::IndexService IndexService(std::move(faissMethods)); - - createIndexIteratively(&mockJNIUtil, jniEnv, ids, vectors, dim, indexPath, parametersMap, &IndexService); - - // Make sure index can be loaded - std::unique_ptr index(test_util::FaissLoadIndex(indexPath)); - auto indexIDMap = dynamic_cast(index.get()); - - // Assert that Index is of type IndexHNSWSQ - ASSERT_NE(indexIDMap, nullptr); - ASSERT_NE(dynamic_cast(indexIDMap->index), nullptr); - - // Clean up - std::remove(indexPath.c_str()); + // Create the index + std::unique_ptr faissMethods(new FaissMethods()); + knn_jni::faiss_wrapper::IndexService IndexService(std::move(faissMethods)); + + try { + createIndexIteratively(&mockJNIUtil, &jniEnv, ids, vectors, dim, (jobject) (&javaFileIndexOutputMock), parametersMap, &IndexService); + // Make sure we close a file stream before reopening the created file. + javaFileIndexOutputMock.file_writer.close(); + } catch (const std::exception& e) { + ASSERT_STREQ("Failed to write index to disk", e.what()); + ASSERT_TRUE(throwIOException); + continue; + } + ASSERT_FALSE(throwIOException); + + // Make sure index can be loaded + std::unique_ptr index(test_util::FaissLoadIndex(indexPath)); + auto indexIDMap = dynamic_cast(index.get()); + + // Assert that Index is of type IndexHNSWSQ + ASSERT_NE(indexIDMap, nullptr); + ASSERT_NE(dynamic_cast(indexIDMap->index), nullptr); + + // Clean up + std::remove(indexPath.c_str()); + } // End for } TEST(FaissIsSharedIndexStateRequired, BasicAssertions) { @@ -917,12 +977,12 @@ TEST(FaissInitAndSetSharedIndexState, BasicAssertions) { test_util::FaissWriteIndex(&faissIndexWithIDMap, indexPath); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; std::unique_ptr loadedIndexPointer( reinterpret_cast(knn_jni::faiss_wrapper::LoadIndex( - &mockJNIUtil, jniEnv, (jstring)&indexPath))); + &mockJNIUtil, &jniEnv, (jstring)&indexPath))); auto idMapIndex = dynamic_cast(loadedIndexPointer.get()); ASSERT_NE(idMapIndex, nullptr); @@ -973,7 +1033,7 @@ TEST(FaissRangeSearchQueryIndexTest, BasicAssertions) { test_util::FaissAddData(createdIndex.get(), ids, vectors); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; int maxResultWindow = 20000; @@ -983,7 +1043,7 @@ TEST(FaissRangeSearchQueryIndexTest, BasicAssertions) { reinterpret_cast *> *>( knn_jni::faiss_wrapper::RangeSearch( - &mockJNIUtil, jniEnv, + &mockJNIUtil, &jniEnv, reinterpret_cast(&createdIndexWithData), reinterpret_cast(&query), rangeSearchRadius, methodParamsJ, maxResultWindow, nullptr))); @@ -1028,7 +1088,7 @@ TEST(FaissRangeSearchQueryIndexTest_WhenHitMaxWindowResult, BasicAssertions){ test_util::FaissAddData(createdIndex.get(), ids, vectors); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; int maxResultWindow = 10; @@ -1038,7 +1098,7 @@ TEST(FaissRangeSearchQueryIndexTest_WhenHitMaxWindowResult, BasicAssertions){ reinterpret_cast *> *>( knn_jni::faiss_wrapper::RangeSearch( - &mockJNIUtil, jniEnv, + &mockJNIUtil, &jniEnv, reinterpret_cast(&createdIndexWithData), reinterpret_cast(&query), rangeSearchRadius, nullptr, maxResultWindow, nullptr))); @@ -1084,7 +1144,7 @@ TEST(FaissRangeSearchQueryIndexTestWithFilterTest, BasicAssertions) { test_util::FaissAddData(createdIndex.get(), ids, vectors); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; int num_bits = test_util::bits2words(164); @@ -1104,7 +1164,7 @@ TEST(FaissRangeSearchQueryIndexTestWithFilterTest, BasicAssertions) { reinterpret_cast *> *>( knn_jni::faiss_wrapper::RangeSearchWithFilter( - &mockJNIUtil, jniEnv, + &mockJNIUtil, &jniEnv, reinterpret_cast(&createdIndexWithData), reinterpret_cast(&query), rangeSearchRadius, nullptr, maxResultWindow, reinterpret_cast(&bitmap), 0, nullptr))); @@ -1165,11 +1225,11 @@ TEST(FaissRangeSearchQueryIndexTestWithParentFilterTest, BasicAssertions) { test_util::FaissAddData(createdIndex.get(), ids, vectors); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; EXPECT_CALL(mockJNIUtil, GetJavaIntArrayLength( - jniEnv, reinterpret_cast(&parentIds))) + &jniEnv, reinterpret_cast(&parentIds))) .WillRepeatedly(Return(parentIds.size())); int maxResultWindow = 10000; @@ -1179,7 +1239,7 @@ TEST(FaissRangeSearchQueryIndexTestWithParentFilterTest, BasicAssertions) { reinterpret_cast *> *>( knn_jni::faiss_wrapper::RangeSearchWithFilter( - &mockJNIUtil, jniEnv, + &mockJNIUtil, &jniEnv, reinterpret_cast(&createdIndexWithData), reinterpret_cast(&query), rangeSearchRadius, nullptr, maxResultWindow, nullptr, 0, reinterpret_cast(&parentIds)))); diff --git a/jni/tests/mocks/faiss_index_service_mock.h b/jni/tests/mocks/faiss_index_service_mock.h index 285e34053..6065b5bbe 100644 --- a/jni/tests/mocks/faiss_index_service_mock.h +++ b/jni/tests/mocks/faiss_index_service_mock.h @@ -21,7 +21,10 @@ typedef std::unordered_map StringToJObjectMap; class MockIndexService : public IndexService { public: - MockIndexService(std::unique_ptr faissMethods) : IndexService(std::move(faissMethods)) {}; + explicit MockIndexService(std::unique_ptr _faissMethods) + : IndexService(std::move(_faissMethods)) { + } + MOCK_METHOD( long, initIndex, @@ -36,6 +39,7 @@ class MockIndexService : public IndexService { StringToJObjectMap parameters ), (override)); + MOCK_METHOD( void, insertToIndex, @@ -48,14 +52,15 @@ class MockIndexService : public IndexService { long indexPtr ), (override)); + MOCK_METHOD( void, writeIndex, ( - std::string indexPath, + faiss::IOWriter* writer, long indexPtr ), (override)); }; -#endif // OPENSEARCH_KNN_FAISS_INDEX_SERVICE_MOCK_H \ No newline at end of file +#endif // OPENSEARCH_KNN_FAISS_INDEX_SERVICE_MOCK_H diff --git a/jni/tests/mocks/faiss_methods_mock.h b/jni/tests/mocks/faiss_methods_mock.h index 64a23b895..304269501 100644 --- a/jni/tests/mocks/faiss_methods_mock.h +++ b/jni/tests/mocks/faiss_methods_mock.h @@ -21,8 +21,8 @@ class MockFaissMethods : public knn_jni::faiss_wrapper::FaissMethods { MOCK_METHOD(faiss::IndexBinary*, indexBinaryFactory, (int d, const char* description), (override)); MOCK_METHOD(faiss::IndexIDMapTemplate*, indexIdMap, (faiss::Index* index), (override)); MOCK_METHOD(faiss::IndexIDMapTemplate*, indexBinaryIdMap, (faiss::IndexBinary* index), (override)); - MOCK_METHOD(void, writeIndex, (const faiss::Index* idx, const char* fname), (override)); - MOCK_METHOD(void, writeIndexBinary, (const faiss::IndexBinary* idx, const char* fname), (override)); + MOCK_METHOD(void, writeIndex, (const faiss::Index* idx, faiss::IOWriter* writer), (override)); + MOCK_METHOD(void, writeIndexBinary, (const faiss::IndexBinary* idx, faiss::IOWriter* writer), (override)); }; #endif // OPENSEARCH_KNN_FAISS_METHODS_MOCK_H \ No newline at end of file diff --git a/jni/tests/native_stream_support_util.h b/jni/tests/native_stream_support_util.h index e33f3beb4..ba926d690 100644 --- a/jni/tests/native_stream_support_util.h +++ b/jni/tests/native_stream_support_util.h @@ -12,13 +12,15 @@ #ifndef KNNPLUGIN_JNI_TESTS_NATIVE_STREAM_SUPPORT_UTIL_H_ #define KNNPLUGIN_JNI_TESTS_NATIVE_STREAM_SUPPORT_UTIL_H_ +#include +#include + #include "test_util.h" #include "gmock/gmock.h" #include "gtest/gtest.h" namespace test_util { - // Mocking IndexInputWithBuffer. struct JavaIndexInputMock { JavaIndexInputMock(std::string _readTargetBytes, int32_t _bufSize) @@ -37,7 +39,7 @@ struct JavaIndexInputMock { } int64_t remainingBytes() { - return readTargetBytes.size() - nextReadIdx; + return readTargetBytes.size() - nextReadIdx; } static std::string makeRandomBytes(int32_t bytesSize) { @@ -94,8 +96,70 @@ struct JavaFileIndexInputMock { std::ifstream &file_input; std::vector buffer; -}; // class JavaFileIndexInputMock +}; // struct JavaFileIndexInputMock + + + +struct JavaFileIndexOutputMock { + explicit JavaFileIndexOutputMock(const std::string &_file_path) + : file_writer(_file_path, std::ios::out | std::ios::binary), + buffer(64 * 1024) { + file_writer.exceptions(std::ios::failbit | std::ios::badbit); + } + + void writeBytes(int length) { + file_writer.write(buffer.data(), length); + } + + std::ofstream file_writer; + std::vector buffer; +}; // struct JavaFileIndexOutputMock +struct StreamIOError : public std::runtime_error { + StreamIOError() + : std::runtime_error(what()) { + } + + const char* what() const noexcept final { + return "Mocking IOError in Java side."; + } +}; // struct StreamIOError + +inline void setUpJavaFileOutputMocking(JavaFileIndexOutputMock &java_index_output, + MockJNIUtil &mockJni, + bool throwIOException) { + EXPECT_CALL(mockJni, GetPrimitiveArrayCritical(::testing::_, ::testing::_, ::testing::_)) + .WillRepeatedly([&java_index_output](JNIEnv *env, + jarray array, + jboolean *isCopy) { + return (jbyte *) java_index_output.buffer.data(); + }); + + EXPECT_CALL(mockJni, CallNonvirtualVoidMethodA(::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillRepeatedly([&java_index_output](JNIEnv *env, + jobject obj, + jclass clazz, + jmethodID methodID, + jvalue *args) { + const auto bytes_to_write = args[0].i; + java_index_output.writeBytes(bytes_to_write); + }); + + EXPECT_CALL(mockJni, GetJavaBytesArrayLength(::testing::_, ::testing::_)) + .WillRepeatedly([&java_index_output](JNIEnv *env, jbyteArray arrayJ) { + return java_index_output.buffer.size(); + }); + + if (throwIOException) { + EXPECT_CALL(mockJni, HasExceptionInStack(::testing::_, ::testing::_)) + .WillRepeatedly([](JNIEnv *env, const char* errorMsg){ + throw StreamIOError{}; + }); + } else { + EXPECT_CALL(mockJni, HasExceptionInStack(::testing::_, ::testing::_)) + .WillRepeatedly(::testing::Return()); + } +} } // namespace test_util diff --git a/jni/tests/nmslib_stream_support_test.cpp b/jni/tests/nmslib_stream_support_test.cpp index e0e7a2d08..7db94d5e3 100644 --- a/jni/tests/nmslib_stream_support_test.cpp +++ b/jni/tests/nmslib_stream_support_test.cpp @@ -17,11 +17,13 @@ #include "test_util.h" #include "native_stream_support_util.h" -using ::testing::_; +using ::test_util::JavaFileIndexInputMock; +using ::test_util::JavaFileIndexOutputMock; +using ::test_util::MockJNIUtil; +using ::test_util::StreamIOError; using ::testing::NiceMock; using ::testing::Return; -using ::test_util::MockJNIUtil; -using ::test_util::JavaFileIndexInputMock; +using ::testing::_; void setUpJavaFileInputMocking(JavaFileIndexInputMock &java_index_input, MockJNIUtil &mockJni) { // Set up mocking values + mocking behavior in a method. @@ -35,86 +37,100 @@ void setUpJavaFileInputMocking(JavaFileIndexInputMock &java_index_input, MockJNI }); EXPECT_CALL(mockJni, CallNonvirtualLongMethodA(_, _, _, _, _)) .WillRepeatedly([&java_index_input](JNIEnv *env, - jobject obj, - jclass clazz, - jmethodID methodID, - jvalue *args) { + jobject obj, + jclass clazz, + jmethodID methodID, + jvalue *args) { return java_index_input.remainingBytes(); }); - EXPECT_CALL(mockJni, GetPrimitiveArrayCritical(_, _, _)).WillRepeatedly([&java_index_input](JNIEnv *env, - jarray array, - jboolean *isCopy) { - return (jbyte *) java_index_input.buffer.data(); - }); + EXPECT_CALL(mockJni, GetPrimitiveArrayCritical(_, _, _)) + .WillRepeatedly([&java_index_input](JNIEnv *env, + jarray array, + jboolean *isCopy) { + return (jbyte *) java_index_input.buffer.data(); + }); EXPECT_CALL(mockJni, ReleasePrimitiveArrayCritical(_, _, _, _)).WillRepeatedly(Return()); } TEST(NmslibStreamLoadingTest, BasicAssertions) { - // Initialize nmslib - similarity::initLibrary(); - - // Define index data - int numIds = 100; - std::vector ids; - auto vectors = new std::vector(); - int dim = 2; - vectors->reserve(dim * numIds); - for (int i = 0; i < numIds; ++i) { - ids.push_back(i); - for (int j = 0; j < dim; ++j) { - vectors->push_back(test_util::RandomFloat(-500.0, 500.0)); - } - } - - std::string spaceType = knn_jni::L2; - std::string indexPath = test_util::RandomString( - 10, "/tmp/", ".nmslib"); - - std::unordered_map parametersMap; - int efConstruction = 512; - int m = 96; - - parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; - parametersMap[knn_jni::EF_CONSTRUCTION] = (jobject) &efConstruction; - parametersMap[knn_jni::M] = (jobject) &m; - - // Set up jni - JNIEnv *jniEnv = nullptr; - NiceMock mockJNIUtil; - - EXPECT_CALL(mockJNIUtil, - GetJavaObjectArrayLength( - jniEnv, reinterpret_cast(vectors))) - .WillRepeatedly(Return(vectors->size())); - - EXPECT_CALL(mockJNIUtil, - GetJavaIntArrayLength(jniEnv, reinterpret_cast(&ids))) - .WillRepeatedly(Return(ids.size())); - - EXPECT_CALL(mockJNIUtil, - ConvertJavaMapToCppMap(jniEnv, reinterpret_cast(¶metersMap))) - .WillRepeatedly(Return(parametersMap)); - - // Create the index - knn_jni::nmslib_wrapper::CreateIndex( - &mockJNIUtil, jniEnv, reinterpret_cast(&ids), - (jlong) vectors, dim, (jstring) &indexPath, - (jobject) ¶metersMap); - - // Create Java index input mock. - std::ifstream file_input{indexPath, std::ios::binary}; - const int32_t buffer_size = 128; - JavaFileIndexInputMock java_file_index_input_mock{file_input, buffer_size}; - setUpJavaFileInputMocking(java_file_index_input_mock, mockJNIUtil); - - // Make sure index can be loaded - jlong index = knn_jni::nmslib_wrapper::LoadIndexWithStream( - &mockJNIUtil, jniEnv, - (jobject) (&java_file_index_input_mock), - (jobject) (¶metersMap)); - - knn_jni::nmslib_wrapper::Free(index); - - // Clean up - std::remove(indexPath.c_str()); + for (auto throwIOException : std::array {false, true}) { + // Initialize nmslib + similarity::initLibrary(); + + // Define index data + int numIds = 100; + std::vector ids; + auto vectors = new std::vector(); + int dim = 2; + vectors->reserve(dim * numIds); + for (int i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { + vectors->push_back(test_util::RandomFloat(-500.0, 500.0)); + } + } + + std::string spaceType = knn_jni::L2; + std::string indexPath = test_util::RandomString( + 10, "/tmp/", ".nmslib"); + + std::unordered_map parametersMap; + int efConstruction = 512; + int m = 96; + + parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + parametersMap[knn_jni::EF_CONSTRUCTION] = (jobject) &efConstruction; + parametersMap[knn_jni::M] = (jobject) &m; + + // Set up jni + NiceMock jniEnv; + + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); + knn_jni::stream::NativeEngineIndexOutputMediator mediator {&mockJNIUtil, &jniEnv, (jobject) (&javaFileIndexOutputMock)}; + knn_jni::stream::NmslibOpenSearchIOWriter writer {&mediator}; + + EXPECT_CALL(mockJNIUtil, + GetJavaObjectArrayLength( + &jniEnv, reinterpret_cast(vectors))) + .WillRepeatedly(Return(vectors->size())); + + EXPECT_CALL(mockJNIUtil, + GetJavaIntArrayLength(&jniEnv, reinterpret_cast(&ids))) + .WillRepeatedly(Return(ids.size())); + + EXPECT_CALL(mockJNIUtil, + ConvertJavaMapToCppMap(&jniEnv, reinterpret_cast(¶metersMap))) + .WillRepeatedly(Return(parametersMap)); + + // Create the index + try { + knn_jni::nmslib_wrapper::CreateIndex( + &mockJNIUtil, &jniEnv, reinterpret_cast(&ids), + (jlong) vectors, dim, (jobject) (&javaFileIndexOutputMock), + (jobject) ¶metersMap); + javaFileIndexOutputMock.file_writer.close(); + } catch (const StreamIOError& e) { + continue; + } + + // Create Java index input mock. + std::ifstream file_input{indexPath, std::ios::binary}; + const int32_t buffer_size = 128; + JavaFileIndexInputMock java_file_index_input_mock{file_input, buffer_size}; + setUpJavaFileInputMocking(java_file_index_input_mock, mockJNIUtil); + + // Make sure index can be loaded + jlong index = knn_jni::nmslib_wrapper::LoadIndexWithStream( + &mockJNIUtil, &jniEnv, + (jobject) (&java_file_index_input_mock), + (jobject) (¶metersMap)); + + knn_jni::nmslib_wrapper::Free(index); + + // Clean up + file_input.close(); + std::remove(indexPath.c_str()); + } // End for } diff --git a/jni/tests/nmslib_wrapper_test.cpp b/jni/tests/nmslib_wrapper_test.cpp index 4e0c57044..b7690ee94 100644 --- a/jni/tests/nmslib_wrapper_test.cpp +++ b/jni/tests/nmslib_wrapper_test.cpp @@ -10,6 +10,7 @@ */ #include "nmslib_wrapper.h" +#include "nmslib_stream_support.h" #include @@ -17,7 +18,11 @@ #include "gtest/gtest.h" #include "jni_util.h" #include "test_util.h" +#include "native_stream_support_util.h" +using ::test_util::JavaFileIndexOutputMock; +using ::test_util::StreamIOError; +using ::test_util::setUpJavaFileOutputMocking; using ::testing::NiceMock; using ::testing::Return; @@ -33,117 +38,146 @@ TEST(NmslibIndexWrapperSearchTest, BasicAssertions) { } TEST(NmslibCreateIndexTest, BasicAssertions) { - // Initialize nmslib - similarity::initLibrary(); - - // Define index data - int numIds = 100; - std::vector ids; - auto *vectors = new std::vector(); - int dim = 2; - vectors->reserve(dim * numIds); - for (int64_t i = 0; i < numIds; ++i) { - ids.push_back(i); - for (int j = 0; j < dim; ++j) { + for (auto throwIOException : std::array {false, true}) { + // Initialize nmslib + similarity::initLibrary(); + + // Define index data + int numIds = 100; + std::vector ids; + auto *vectors = new std::vector(); + int dim = 2; + vectors->reserve(dim * numIds); + for (int64_t i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { vectors->push_back(test_util::RandomFloat(-500.0, 500.0)); + } } - } - std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); - std::string spaceType = knn_jni::L2; + std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); + std::string spaceType = knn_jni::L2; - std::unordered_map parametersMap; - int efConstruction = 512; - int m = 96; + std::unordered_map parametersMap; + int efConstruction = 512; + int m = 96; - parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; - parametersMap[knn_jni::EF_CONSTRUCTION] = (jobject)&efConstruction; - parametersMap[knn_jni::M] = (jobject)&m; + parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; + parametersMap[knn_jni::EF_CONSTRUCTION] = (jobject)&efConstruction; + parametersMap[knn_jni::M] = (jobject)&m; - // Set up jni - JNIEnv *jniEnv = nullptr; - NiceMock mockJNIUtil; + // Set up jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); - EXPECT_CALL(mockJNIUtil, - GetJavaObjectArrayLength( - jniEnv, reinterpret_cast(&vectors))) + EXPECT_CALL(mockJNIUtil, + GetJavaObjectArrayLength( + &jniEnv, reinterpret_cast(&vectors))) .WillRepeatedly(Return(vectors->size())); - EXPECT_CALL(mockJNIUtil, - GetJavaIntArrayLength(jniEnv, reinterpret_cast(&ids))) + EXPECT_CALL(mockJNIUtil, + GetJavaIntArrayLength(&jniEnv, reinterpret_cast(&ids))) .WillRepeatedly(Return(ids.size())); - // Create the index - knn_jni::nmslib_wrapper::CreateIndex( - &mockJNIUtil, jniEnv, reinterpret_cast(&ids), - (jlong) vectors, dim, (jstring)&indexPath, - (jobject)¶metersMap); + // Create the index + try { + knn_jni::nmslib_wrapper::CreateIndex( + &mockJNIUtil, &jniEnv, reinterpret_cast(&ids), + (jlong) vectors, dim, (jobject)(&javaFileIndexOutputMock), + (jobject)¶metersMap); + } catch (const StreamIOError& e) { + ASSERT_TRUE(throwIOException); + continue; + } + ASSERT_FALSE(throwIOException); - // Make sure index can be loaded - std::unique_ptr> space( + // Make sure we close a file stream before reopening the created file. + javaFileIndexOutputMock.file_writer.close(); + + // Make sure index can be loaded + std::unique_ptr> space( similarity::SpaceFactoryRegistry::Instance().CreateSpace( - spaceType, similarity::AnyParams())); - std::vector params; - std::unique_ptr> loadedIndex( + spaceType, similarity::AnyParams())); + std::vector params; + std::unique_ptr> loadedIndex( test_util::NmslibLoadIndex(indexPath, space.get(), spaceType, params)); - // Clean up - std::remove(indexPath.c_str()); + // Clean up + std::remove(indexPath.c_str()); + } } TEST(NmslibLoadIndexTest, BasicAssertions) { - // Initialize nmslib - similarity::initLibrary(); - - // Define index data - int numIds = 100; - std::vector ids; - std::vector> vectors; - int dim = 2; - for (int i = 0; i < numIds; ++i) { - ids.push_back(i); - - std::vector vect; - vect.reserve(dim); - for (int j = 0; j < dim; ++j) { + for (auto throwIOException : std::array {false, true}) { + // Initialize nmslib + similarity::initLibrary(); + + // Define index data + int numIds = 100; + std::vector ids; + std::vector> vectors; + int dim = 2; + for (int i = 0; i < numIds; ++i) { + ids.push_back(i); + + std::vector vect; + vect.reserve(dim); + for (int j = 0; j < dim; ++j) { vect.push_back(test_util::RandomFloat(-500.0, 500.0)); + } + vectors.push_back(vect); } - vectors.push_back(vect); - } - std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); - std::string spaceType = knn_jni::L2; - std::unique_ptr> space( + std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); + std::string spaceType = knn_jni::L2; + std::unique_ptr> space( similarity::SpaceFactoryRegistry::Instance().CreateSpace( - spaceType, similarity::AnyParams())); + spaceType, similarity::AnyParams())); - std::vector indexParameters; + std::vector indexParameters; + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); + knn_jni::stream::NativeEngineIndexOutputMediator mediator {&mockJNIUtil, &jniEnv, (jobject) (&javaFileIndexOutputMock)}; + knn_jni::stream::NmslibOpenSearchIOWriter writer {&mediator}; - // Create index and write to disk - std::unique_ptr> createdIndex( + // Create index and write to disk + std::unique_ptr> createdIndex( test_util::NmslibCreateIndex(ids.data(), vectors, space.get(), spaceType, indexParameters)); - test_util::NmslibWriteIndex(createdIndex.get(), indexPath); - // Setup jni - JNIEnv *jniEnv = nullptr; - NiceMock mockJNIUtil; + try { + test_util::NmslibWriteIndex(createdIndex.get(), writer); - // Load index - std::unordered_map parametersMap; - parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; + // Make sure we close a file stream before reopening the created file. + javaFileIndexOutputMock.file_writer.close(); + } catch (const StreamIOError& e) { + ASSERT_TRUE(throwIOException); + continue; + } + ASSERT_FALSE(throwIOException); - std::unique_ptr loadedIndex( + // Load index + std::unordered_map parametersMap; + parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; + + std::unique_ptr loadedIndex( reinterpret_cast( - knn_jni::nmslib_wrapper::LoadIndex(&mockJNIUtil, jniEnv, - (jstring)&indexPath, - (jobject)¶metersMap))); + knn_jni::nmslib_wrapper::LoadIndex(&mockJNIUtil, &jniEnv, + (jstring)&indexPath, + (jobject)¶metersMap))); - // Check that load succeeds - ASSERT_EQ(createdIndex->StrDesc(), loadedIndex->index->StrDesc()); + // Check that load succeeds + ASSERT_EQ(createdIndex->StrDesc(), loadedIndex->index->StrDesc()); - // Clean up - std::remove(indexPath.c_str()); + // Clean up + std::remove(indexPath.c_str()); + } } TEST(NmslibQueryIndexTest, BasicAssertions) { @@ -166,7 +200,6 @@ TEST(NmslibQueryIndexTest, BasicAssertions) { vectors.push_back(vect); } - std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); std::string spaceType = knn_jni::L2; std::unique_ptr> space( similarity::SpaceFactoryRegistry::Instance().CreateSpace( @@ -239,7 +272,6 @@ TEST(NmslibFreeTest, BasicAssertions) { vectors.push_back(vect); } - std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); std::string spaceType = knn_jni::L2; std::unique_ptr> space( similarity::SpaceFactoryRegistry::Instance().CreateSpace( diff --git a/jni/tests/test_util.cpp b/jni/tests/test_util.cpp index 47d1a7c8e..e337eaaf3 100644 --- a/jni/tests/test_util.cpp +++ b/jni/tests/test_util.cpp @@ -29,6 +29,7 @@ #include "methodfactory.h" #include "params.h" #include "space.h" +#include "method/hnsw.h" test_util::MockJNIUtil::MockJNIUtil() { // Set default for calls. If necessary, these can be overriden with @@ -374,8 +375,13 @@ similarity::Index *test_util::NmslibCreateIndex( } void test_util::NmslibWriteIndex(similarity::Index *index, - const std::string &indexPath) { - index->SaveIndex(indexPath); + knn_jni::stream::NmslibOpenSearchIOWriter& writer) { + if (auto hnswFloatIndex = dynamic_cast *>(index)) { + hnswFloatIndex->SaveIndexWithStream(writer); + writer.flush(); + } else { + throw std::runtime_error("We only support similarity::Hnsw in NMSLIB."); + } } similarity::Index *test_util::NmslibLoadIndex( diff --git a/jni/tests/test_util.h b/jni/tests/test_util.h index a6b39aa41..0262c8467 100644 --- a/jni/tests/test_util.h +++ b/jni/tests/test_util.h @@ -24,6 +24,7 @@ #include "faiss/MetaIndexes.h" #include "faiss/MetricType.h" #include "faiss/impl/io.h" +#include "nmslib_stream_support.h" #include "index.h" #include "init.h" #include "jni_util.h" @@ -84,7 +85,7 @@ namespace test_util { (JNIEnv * env, jobjectArray arrayJ, jsize index)); MOCK_METHOD(void, HasExceptionInStack, (JNIEnv * env)); MOCK_METHOD(void, HasExceptionInStack, - (JNIEnv * env, const std::string& message)); + (JNIEnv * env, const char* message)); MOCK_METHOD(jbyteArray, NewByteArray, (JNIEnv * env, jsize len)); MOCK_METHOD(jobject, NewObject, (JNIEnv * env, jclass clazz, jmethodID methodId, int id, @@ -115,6 +116,7 @@ namespace test_util { MOCK_METHOD(jlong, CallNonvirtualLongMethodA, (JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args)); MOCK_METHOD(void *, GetPrimitiveArrayCritical, (JNIEnv * env, jarray array, jboolean *isCopy)); MOCK_METHOD(void, ReleasePrimitiveArrayCritical, (JNIEnv * env, jarray array, void *carray, jint mode)); + MOCK_METHOD(void, CallNonvirtualVoidMethodA, (JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args)); }; // For our unit tests, we want to ensure that each test tests one function in @@ -160,7 +162,7 @@ namespace test_util { const std::vector& indexParameters); void NmslibWriteIndex(similarity::Index* index, - const std::string& indexPath); + knn_jni::stream::NmslibOpenSearchIOWriter& writer); similarity::Index* NmslibLoadIndex( const std::string& indexPath, similarity::Space* space, diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategy.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategy.java index 476c95b8d..23c3ba116 100644 --- a/src/main/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategy.java +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategy.java @@ -83,7 +83,7 @@ public void buildAndWriteIndex(final BuildIndexParams indexInfo) throws IOExcept intListToArray(transferredDocIds), vectorAddress, indexBuildSetup.getDimensions(), - indexInfo.getIndexPath(), + indexInfo.getIndexOutputWithBuffer(), (byte[]) params.get(KNNConstants.MODEL_BLOB_PARAMETER), params, indexInfo.getKnnEngine() @@ -96,7 +96,7 @@ public void buildAndWriteIndex(final BuildIndexParams indexInfo) throws IOExcept intListToArray(transferredDocIds), vectorAddress, indexBuildSetup.getDimensions(), - indexInfo.getIndexPath(), + indexInfo.getIndexOutputWithBuffer(), params, indexInfo.getKnnEngine() ); diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategy.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategy.java index b7e337081..81f5915a7 100644 --- a/src/main/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategy.java +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategy.java @@ -48,7 +48,6 @@ public static MemOptimizedNativeIndexBuildStrategy getInstance() { * flushed and used to build the index. The index is then written to the specified path using JNI calls.

    * * @param indexInfo The {@link BuildIndexParams} containing the parameters and configuration for building the index. - * @param knnVectorValues The {@link KNNVectorValues} representing the vectors to be indexed. * @throws IOException If an I/O error occurs during the process of building and writing the index. */ public void buildAndWriteIndex(final BuildIndexParams indexInfo) throws IOException { @@ -123,7 +122,7 @@ public void buildAndWriteIndex(final BuildIndexParams indexInfo) throws IOExcept // Write vector AccessController.doPrivileged((PrivilegedAction) () -> { - JNIService.writeIndex(indexInfo.getIndexPath(), indexMemoryAddress, engine, indexParameters); + JNIService.writeIndex(indexInfo.getIndexOutputWithBuffer(), indexMemoryAddress, engine, indexParameters); return null; }); diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexWriter.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexWriter.java index edc96c9e1..27a1ecfb6 100644 --- a/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexWriter.java +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexWriter.java @@ -7,11 +7,10 @@ import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.apache.lucene.codecs.CodecUtil; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.SegmentWriteState; -import org.apache.lucene.store.ChecksumIndexInput; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.store.FilterDirectory; +import org.apache.lucene.store.IndexOutput; import org.opensearch.common.Nullable; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.core.common.bytes.BytesArray; @@ -27,6 +26,7 @@ import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.engine.qframe.QuantizationConfig; import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import org.opensearch.knn.index.util.IndexUtil; import org.opensearch.knn.index.vectorvalues.KNNVectorValues; import org.opensearch.knn.indices.Model; @@ -35,16 +35,9 @@ import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.util.HashMap; import java.util.Map; -import static org.apache.lucene.codecs.CodecUtil.FOOTER_MAGIC; import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; import static org.opensearch.knn.common.FieldInfoExtractor.extractKNNEngine; import static org.opensearch.knn.common.FieldInfoExtractor.extractVectorDataType; @@ -133,7 +126,7 @@ public void mergeIndex(final KNNVectorValues knnVectorValues, int totalLiveDo private void buildAndWriteIndex(final KNNVectorValues knnVectorValues, int totalLiveDocs) throws IOException { if (totalLiveDocs == 0) { - log.debug("No live docs for field " + fieldInfo.name); + log.debug("No live docs for field {}", fieldInfo.name); return; } @@ -144,15 +137,18 @@ private void buildAndWriteIndex(final KNNVectorValues knnVectorValues, int to fieldInfo.name, knnEngine.getExtension() ); - final String indexPath = Paths.get( - ((FSDirectory) (FilterDirectory.unwrap(state.directory))).getDirectory().toString(), - engineFileName - ).toString(); - state.directory.createOutput(engineFileName, state.context).close(); - - final BuildIndexParams nativeIndexParams = indexParams(fieldInfo, indexPath, knnEngine, knnVectorValues, totalLiveDocs); - indexBuilder.buildAndWriteIndex(nativeIndexParams); - writeFooter(indexPath, engineFileName, state); + try (IndexOutput output = state.directory.createOutput(engineFileName, state.context)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(output); + final BuildIndexParams nativeIndexParams = indexParams( + fieldInfo, + indexOutputWithBuffer, + knnEngine, + knnVectorValues, + totalLiveDocs + ); + indexBuilder.buildAndWriteIndex(nativeIndexParams); + CodecUtil.writeFooter(output); + } } // The logic for building parameters need to be cleaned up. There are various cases handled here @@ -160,7 +156,7 @@ private void buildAndWriteIndex(final KNNVectorValues knnVectorValues, int to // TODO: Refactor this so its scalable. Possibly move it out of this class private BuildIndexParams indexParams( FieldInfo fieldInfo, - String indexPath, + IndexOutputWithBuffer indexOutputWithBuffer, KNNEngine knnEngine, KNNVectorValues vectorValues, int totalLiveDocs @@ -184,7 +180,7 @@ private BuildIndexParams indexParams( .parameters(parameters) .vectorDataType(vectorDataType) .knnEngine(knnEngine) - .indexPath(indexPath) + .indexOutputWithBuffer(indexOutputWithBuffer) .quantizationState(quantizationState) .vectorValues(vectorValues) .totalLiveDocs(totalLiveDocs) @@ -302,42 +298,6 @@ private void recordRefreshStats() { KNNGraphValue.REFRESH_TOTAL_OPERATIONS.increment(); } - private boolean isChecksumValid(long value) { - // Check pulled from - // https://github.com/apache/lucene/blob/branch_9_0/lucene/core/src/java/org/apache/lucene/codecs/CodecUtil.java#L644-L647 - return (value & CRC32_CHECKSUM_SANITY) != 0; - } - - private void writeFooter(String indexPath, String engineFileName, SegmentWriteState state) throws IOException { - // Opens the engine file that was created and appends a footer to it. The footer consists of - // 1. A Footer magic number (int - 4 bytes) - // 2. A checksum algorithm id (int - 4 bytes) - // 3. A checksum (long - bytes) - // The checksum is computed on all the bytes written to the file up to that point. - // Logic where footer is written in Lucene can be found here: - // https://github.com/apache/lucene/blob/branch_9_0/lucene/core/src/java/org/apache/lucene/codecs/CodecUtil.java#L390-L412 - OutputStream os = Files.newOutputStream(Paths.get(indexPath), StandardOpenOption.APPEND); - ByteBuffer byteBuffer = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN); - byteBuffer.putInt(FOOTER_MAGIC); - byteBuffer.putInt(0); - os.write(byteBuffer.array()); - os.flush(); - - ChecksumIndexInput checksumIndexInput = state.directory.openChecksumInput(engineFileName, state.context); - checksumIndexInput.seek(checksumIndexInput.length()); - long value = checksumIndexInput.getChecksum(); - checksumIndexInput.close(); - - if (isChecksumValid(value)) { - throw new IllegalStateException("Illegal CRC-32 checksum: " + value + " (resource=" + os + ")"); - } - - // Write the CRC checksum to the end of the OutputStream and close the stream - byteBuffer.putLong(0, value); - os.write(byteBuffer.array()); - os.close(); - } - /** * Helper method to create the appropriate NativeIndexWriter based on the field info and quantization state. * diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/model/BuildIndexParams.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/model/BuildIndexParams.java index 88507b1fc..36e874c43 100644 --- a/src/main/java/org/opensearch/knn/index/codec/nativeindex/model/BuildIndexParams.java +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/model/BuildIndexParams.java @@ -11,6 +11,7 @@ import org.opensearch.common.Nullable; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import org.opensearch.knn.index.vectorvalues.KNNVectorValues; import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; @@ -22,7 +23,7 @@ public class BuildIndexParams { String fieldName; KNNEngine knnEngine; - String indexPath; + IndexOutputWithBuffer indexOutputWithBuffer; VectorDataType vectorDataType; Map parameters; /** diff --git a/src/main/java/org/opensearch/knn/index/store/IndexOutputWithBuffer.java b/src/main/java/org/opensearch/knn/index/store/IndexOutputWithBuffer.java new file mode 100644 index 000000000..c1420238a --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/store/IndexOutputWithBuffer.java @@ -0,0 +1,40 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.store; + +import org.apache.lucene.store.IndexOutput; + +import java.io.IOException; + +public class IndexOutputWithBuffer { + // Underlying `IndexOutput` obtained from Lucene's Directory. + private IndexOutput indexOutput; + // Write buffer. Native engine will copy bytes into this buffer. + // Allocating 64KB here since it show better performance in NMSLIB with the size. (We had slightly improvement in FAISS than having 4KB) + // NMSLIB writes an adjacent list size first, then followed by serializing the list. Since we usually have more adjacent lists, having + // 64KB to accumulate bytes as possible to reduce the times of calling `writeBytes`. + private byte[] buffer = new byte[64 * 1024]; + + public IndexOutputWithBuffer(IndexOutput indexOutput) { + this.indexOutput = indexOutput; + } + + // This method will be called in JNI layer which precisely knows + // the amount of bytes need to be written. + public void writeBytes(int length) { + try { + // Delegate Lucene `indexOuptut` to write bytes. + indexOutput.writeBytes(buffer, 0, length); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return "{indexOutput=" + indexOutput + ", len(buffer)=" + buffer.length + "}"; + } +} diff --git a/src/main/java/org/opensearch/knn/jni/FaissService.java b/src/main/java/org/opensearch/knn/jni/FaissService.java index c56726c66..dcc7b180d 100644 --- a/src/main/java/org/opensearch/knn/jni/FaissService.java +++ b/src/main/java/org/opensearch/knn/jni/FaissService.java @@ -12,9 +12,10 @@ package org.opensearch.knn.jni; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.query.KNNQueryResult; import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryResult; import org.opensearch.knn.index.store.IndexInputWithBuffer; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import java.security.AccessController; import java.security.PrivilegedAction; @@ -23,11 +24,11 @@ import static org.opensearch.knn.index.KNNSettings.isFaissAVX2Disabled; import static org.opensearch.knn.index.KNNSettings.isFaissAVX512Disabled; import static org.opensearch.knn.jni.PlatformUtils.isAVX2SupportedBySystem; -import static org.opensearch.knn.jni.PlatformUtils.isAVX512SupportedBySystem;; +import static org.opensearch.knn.jni.PlatformUtils.isAVX512SupportedBySystem; /** * Service to interact with faiss jni layer. Class dependencies should be minimal - * + *

    * In order to compile C++ header file, run: * javac -h jni/include src/main/java/org/opensearch/knn/jni/FaissService.java * src/main/java/org/opensearch/knn/index/query/KNNQueryResult.java @@ -129,9 +130,9 @@ class FaissService { * NOTE: This will always free the index. Do not call free after this. * * @param indexAddress address of native memory where index is stored - * @param indexPath path to save index file to + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. */ - public static native void writeIndex(long indexAddress, String indexPath); + public static native void writeIndex(long indexAddress, IndexOutputWithBuffer output); /** * Writes a faiss index. @@ -139,9 +140,9 @@ class FaissService { * NOTE: This will always free the index. Do not call free after this. * * @param indexAddress address of native memory where index is stored - * @param indexPath path to save index file to + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. */ - public static native void writeBinaryIndex(long indexAddress, String indexPath); + public static native void writeBinaryIndex(long indexAddress, IndexOutputWithBuffer output); /** * Writes a faiss index. @@ -149,9 +150,9 @@ class FaissService { * NOTE: This will always free the index. Do not call free after this. * * @param indexAddress address of native memory where index is stored - * @param indexPath path to save index file to + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. */ - public static native void writeByteIndex(long indexAddress, String indexPath); + public static native void writeByteIndex(long indexAddress, IndexOutputWithBuffer output); /** * Create an index for the native library with a provided template index @@ -159,7 +160,7 @@ class FaissService { * @param ids array of ids mapping to the data passed in * @param vectorsAddress address of native memory where vectors are stored * @param dim dimension of the vector to be indexed - * @param indexPath path to save index file to + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. * @param templateIndex empty template index * @param parameters additional build time parameters */ @@ -167,7 +168,7 @@ public static native void createIndexFromTemplate( int[] ids, long vectorsAddress, int dim, - String indexPath, + IndexOutputWithBuffer output, byte[] templateIndex, Map parameters ); @@ -178,7 +179,7 @@ public static native void createIndexFromTemplate( * @param ids array of ids mapping to the data passed in * @param vectorsAddress address of native memory where vectors are stored * @param dim dimension of the vector to be indexed - * @param indexPath path to save index file to + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. * @param templateIndex empty template index * @param parameters additional build time parameters */ @@ -186,7 +187,7 @@ public static native void createBinaryIndexFromTemplate( int[] ids, long vectorsAddress, int dim, - String indexPath, + IndexOutputWithBuffer output, byte[] templateIndex, Map parameters ); @@ -197,7 +198,7 @@ public static native void createBinaryIndexFromTemplate( * @param ids array of ids mapping to the data passed in * @param vectorsAddress address of native memory where vectors are stored * @param dim dimension of the vector to be indexed - * @param indexPath path to save index file to + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. * @param templateIndex empty template index * @param parameters additional build time parameters */ @@ -205,7 +206,7 @@ public static native void createByteIndexFromTemplate( int[] ids, long vectorsAddress, int dim, - String indexPath, + IndexOutputWithBuffer output, byte[] templateIndex, Map parameters ); diff --git a/src/main/java/org/opensearch/knn/jni/JNIService.java b/src/main/java/org/opensearch/knn/jni/JNIService.java index dd4dcef17..b490476eb 100644 --- a/src/main/java/org/opensearch/knn/jni/JNIService.java +++ b/src/main/java/org/opensearch/knn/jni/JNIService.java @@ -17,6 +17,7 @@ import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.query.KNNQueryResult; import org.opensearch.knn.index.store.IndexInputWithBuffer; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import org.opensearch.knn.index.util.IndexUtil; import java.util.Locale; @@ -92,19 +93,19 @@ public static void insertToIndex( /** * Writes a faiss index to disk. * - * @param indexPath path to save index to + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. * @param indexAddress address of native memory where index is stored * @param knnEngine knn engine * @param parameters parameters to build index */ - public static void writeIndex(String indexPath, long indexAddress, KNNEngine knnEngine, Map parameters) { + public static void writeIndex(IndexOutputWithBuffer output, long indexAddress, KNNEngine knnEngine, Map parameters) { if (KNNEngine.FAISS == knnEngine) { if (IndexUtil.isBinaryIndex(knnEngine, parameters)) { - FaissService.writeBinaryIndex(indexAddress, indexPath); + FaissService.writeBinaryIndex(indexAddress, output); } else if (IndexUtil.isByteIndex(parameters)) { - FaissService.writeByteIndex(indexAddress, indexPath); + FaissService.writeByteIndex(indexAddress, output); } else { - FaissService.writeIndex(indexAddress, indexPath); + FaissService.writeIndex(indexAddress, output); } return; } @@ -123,7 +124,7 @@ public static void writeIndex(String indexPath, long indexAddress, KNNEngine knn * @param ids array of ids mapping to the data passed in * @param vectorsAddress address of native memory where vectors are stored * @param dim dimension of the vector to be indexed - * @param indexPath path to save index file to + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. * @param parameters parameters to build index * @param knnEngine engine to build index for */ @@ -131,12 +132,12 @@ public static void createIndex( int[] ids, long vectorsAddress, int dim, - String indexPath, + IndexOutputWithBuffer output, Map parameters, KNNEngine knnEngine ) { if (KNNEngine.NMSLIB == knnEngine) { - NmslibService.createIndex(ids, vectorsAddress, dim, indexPath, parameters); + NmslibService.createIndex(ids, vectorsAddress, dim, output, parameters); return; } @@ -151,7 +152,7 @@ public static void createIndex( * @param ids array of ids mapping to the data passed in * @param vectorsAddress address of native memory where vectors are stored * @param dim dimension of vectors to be indexed - * @param indexPath path to save index file to + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. * @param templateIndex empty template index * @param parameters parameters to build index * @param knnEngine engine to build index for @@ -160,24 +161,23 @@ public static void createIndexFromTemplate( int[] ids, long vectorsAddress, int dim, - String indexPath, + IndexOutputWithBuffer output, byte[] templateIndex, Map parameters, KNNEngine knnEngine ) { if (KNNEngine.FAISS == knnEngine) { if (IndexUtil.isBinaryIndex(knnEngine, parameters)) { - FaissService.createBinaryIndexFromTemplate(ids, vectorsAddress, dim, indexPath, templateIndex, parameters); + FaissService.createBinaryIndexFromTemplate(ids, vectorsAddress, dim, output, templateIndex, parameters); return; } if (IndexUtil.isByteIndex(parameters)) { - FaissService.createByteIndexFromTemplate(ids, vectorsAddress, dim, indexPath, templateIndex, parameters); + FaissService.createByteIndexFromTemplate(ids, vectorsAddress, dim, output, templateIndex, parameters); return; } - FaissService.createIndexFromTemplate(ids, vectorsAddress, dim, indexPath, templateIndex, parameters); + FaissService.createIndexFromTemplate(ids, vectorsAddress, dim, output, templateIndex, parameters); return; - } throw new IllegalArgumentException( @@ -185,32 +185,6 @@ public static void createIndexFromTemplate( ); } - /** - * Load an index into memory - * - * @param indexPath path to index file - * @param parameters parameters to be used when loading index - * @param knnEngine engine to load index - * @return pointer to location in memory the index resides in - */ - public static long loadIndex(String indexPath, Map parameters, KNNEngine knnEngine) { - if (KNNEngine.NMSLIB == knnEngine) { - return NmslibService.loadIndex(indexPath, parameters); - } - - if (KNNEngine.FAISS == knnEngine) { - if (IndexUtil.isBinaryIndex(knnEngine, parameters)) { - return FaissService.loadBinaryIndex(indexPath); - } else { - return FaissService.loadIndex(indexPath); - } - } - - throw new IllegalArgumentException( - String.format(Locale.ROOT, "LoadIndex not supported for provided engine : %s", knnEngine.getName()) - ); - } - /** * Load an index via Lucene's IndexInput. * diff --git a/src/main/java/org/opensearch/knn/jni/NmslibService.java b/src/main/java/org/opensearch/knn/jni/NmslibService.java index feb850d30..16cc6bf52 100644 --- a/src/main/java/org/opensearch/knn/jni/NmslibService.java +++ b/src/main/java/org/opensearch/knn/jni/NmslibService.java @@ -12,9 +12,10 @@ package org.opensearch.knn.jni; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.query.KNNQueryResult; import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryResult; import org.opensearch.knn.index.store.IndexInputWithBuffer; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import java.security.AccessController; import java.security.PrivilegedAction; @@ -22,7 +23,7 @@ /** * Service to interact with nmslib jni layer. Class dependencies should be minimal - * + *

    * In order to compile C++ header file, run: * javac -h jni/include src/main/java/org/opensearch/knn/jni/NmslibService.java * src/main/java/org/opensearch/knn/index/KNNQueryResult.java @@ -48,19 +49,16 @@ class NmslibService { * @param ids array of ids mapping to the data passed in * @param vectorsAddress address of native memory where vectors are stored * @param dim dimension of the vector to be indexed - * @param indexPath path to save index file to + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. * @param parameters parameters to build index */ - public static native void createIndex(int[] ids, long vectorsAddress, int dim, String indexPath, Map parameters); - - /** - * Load an index into memory - * - * @param indexPath path to index file - * @param parameters parameters to be used when loading index - * @return pointer to location in memory the index resides in - */ - public static native long loadIndex(String indexPath, Map parameters); + public static native void createIndex( + int[] ids, + long vectorsAddress, + int dim, + IndexOutputWithBuffer output, + Map parameters + ); /** * Load an index into memory through the provided read stream wrapping Lucene's IndexInput. diff --git a/src/test/java/org/opensearch/knn/common/RaisingIOExceptionIndexInput.java b/src/test/java/org/opensearch/knn/common/RaisingIOExceptionIndexInput.java new file mode 100644 index 000000000..8882f7d2f --- /dev/null +++ b/src/test/java/org/opensearch/knn/common/RaisingIOExceptionIndexInput.java @@ -0,0 +1,51 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.common; + +import org.apache.lucene.store.IndexInput; + +import java.io.IOException; + +public class RaisingIOExceptionIndexInput extends IndexInput { + public RaisingIOExceptionIndexInput() { + super(RaisingIOExceptionIndexInput.class.getSimpleName()); + } + + @Override + public void close() throws IOException { + throw new IOException("RaisingIOExceptionIndexInput::readBytes failed."); + } + + @Override + public long getFilePointer() { + throw new RuntimeException("RaisingIOExceptionIndexInput::readBytes failed."); + } + + @Override + public void seek(long l) throws IOException { + throw new IOException("RaisingIOExceptionIndexInput::readBytes failed."); + } + + @Override + public long length() { + return 0; + } + + @Override + public IndexInput slice(String s, long l, long l1) throws IOException { + throw new IOException("RaisingIOExceptionIndexInput::readBytes failed."); + } + + @Override + public byte readByte() throws IOException { + throw new IOException("RaisingIOExceptionIndexInput::readBytes failed."); + } + + @Override + public void readBytes(byte[] bytes, int i, int i1) throws IOException { + throw new IOException("RaisingIOExceptionIndexInput::readBytes failed."); + } +} diff --git a/src/test/java/org/opensearch/knn/common/RasingIOExceptionIndexOutput.java b/src/test/java/org/opensearch/knn/common/RasingIOExceptionIndexOutput.java new file mode 100644 index 000000000..7334bf201 --- /dev/null +++ b/src/test/java/org/opensearch/knn/common/RasingIOExceptionIndexOutput.java @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.common; + +import org.apache.lucene.store.IndexOutput; + +import java.io.IOException; + +public class RasingIOExceptionIndexOutput extends IndexOutput { + public RasingIOExceptionIndexOutput() { + super("Always throws IOException", RasingIOExceptionIndexOutput.class.getSimpleName()); + } + + @Override + public void close() throws IOException { + throw new IOException("RaiseIOExceptionIndexInput::close failed."); + } + + @Override + public long getFilePointer() { + throw new RuntimeException("RaiseIOExceptionIndexInput::getFilePointer failed."); + } + + @Override + public long getChecksum() throws IOException { + throw new IOException("RaiseIOExceptionIndexInput::getChecksum failed."); + } + + @Override + public void writeByte(byte b) throws IOException { + throw new IOException("RaiseIOExceptionIndexInput::writeByte failed."); + } + + @Override + public void writeBytes(byte[] bytes, int i, int i1) throws IOException { + throw new IOException("RaiseIOExceptionIndexInput::writeBytes failed."); + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java index e6fcb643d..2036e14aa 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java @@ -154,64 +154,67 @@ protected ResourceWatcherService createDisabledResourceWatcherService() { public void testMultiFieldsKnnIndex(Codec codec) throws Exception { setUpMockClusterService(); - Directory dir = newFSDirectory(createTempDir()); - IndexWriterConfig iwc = newIndexWriterConfig(); - iwc.setMergeScheduler(new SerialMergeScheduler()); - iwc.setCodec(codec); - // Set merge policy to no merges so that we create a predictable number of segments. - iwc.setMergePolicy(NoMergePolicy.INSTANCE); - - /** - * Add doc with field "test_vector" - */ - float[] array = { 1.0f, 3.0f, 4.0f }; - VectorField vectorField = new VectorField("test_vector", array, sampleFieldType); - RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc); - Document doc = new Document(); - doc.add(vectorField); - writer.addDocument(doc); - // ensuring the refresh happens, to create the segment and hnsw file - writer.flush(); - - /** - * Add doc with field "my_vector" - */ - float[] array1 = { 6.0f, 14.0f }; - VectorField vectorField1 = new VectorField("my_vector", array1, sampleFieldType); - Document doc1 = new Document(); - doc1.add(vectorField1); - writer.addDocument(doc1); - // ensuring the refresh happens, to create the segment and hnsw file - writer.flush(); - IndexReader reader = writer.getReader(); - writer.close(); - List hnswfiles = Arrays.stream(dir.listAll()).filter(x -> x.contains("hnsw")).collect(Collectors.toList()); + try (Directory dir = newFSDirectory(createTempDir())) { + IndexWriterConfig iwc = newIndexWriterConfig(); + iwc.setMergeScheduler(new SerialMergeScheduler()); + iwc.setCodec(codec); + // Set merge policy to no merges so that we create a predictable number of segments. + iwc.setMergePolicy(NoMergePolicy.INSTANCE); + + /** + * Add doc with field "test_vector" + */ + float[] array = { 1.0f, 3.0f, 4.0f }; + VectorField vectorField = new VectorField("test_vector", array, sampleFieldType); + RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc); + Document doc = new Document(); + doc.add(vectorField); + writer.addDocument(doc); + // ensuring the refresh happens, to create the segment and hnsw file + writer.flush(); + + /** + * Add doc with field "my_vector" + */ + float[] array1 = { 6.0f, 14.0f }; + VectorField vectorField1 = new VectorField("my_vector", array1, sampleFieldType); + Document doc1 = new Document(); + doc1.add(vectorField1); + writer.addDocument(doc1); + // ensuring the refresh happens, to create the segment and hnsw file + writer.flush(); + IndexReader reader = writer.getReader(); + writer.close(); + List hnswfiles = Arrays.stream(dir.listAll()).filter(x -> x.contains("hnsw")).collect(Collectors.toList()); - // there should be 2 hnsw index files created. one for test_vector and one for my_vector - assertEquals(2, hnswfiles.size()); - assertEquals(hnswfiles.stream().filter(x -> x.contains("test_vector")).collect(Collectors.toList()).size(), 1); - assertEquals(hnswfiles.stream().filter(x -> x.contains("my_vector")).collect(Collectors.toList()).size(), 1); + // there should be 2 hnsw index files created. one for test_vector and one for my_vector + assertEquals(2, hnswfiles.size()); + assertEquals(hnswfiles.stream().filter(x -> x.contains("test_vector")).collect(Collectors.toList()).size(), 1); + assertEquals(hnswfiles.stream().filter(x -> x.contains("my_vector")).collect(Collectors.toList()).size(), 1); - // query to verify distance for each of the field - IndexSearcher searcher = new IndexSearcher(reader); - float score = searcher.search( - new KNNQuery("test_vector", new float[] { 1.0f, 0.0f, 0.0f }, 1, "dummy", (BitSetProducer) null), - 10 - ).scoreDocs[0].score; - float score1 = searcher.search( - new KNNQuery("my_vector", new float[] { 1.0f, 2.0f }, 1, "dummy", (BitSetProducer) null), - 10 - ).scoreDocs[0].score; - assertEquals(1.0f / (1 + 25), score, 0.01f); - assertEquals(1.0f / (1 + 169), score1, 0.01f); - - // query to determine the hits - assertEquals(1, searcher.count(new KNNQuery("test_vector", new float[] { 1.0f, 0.0f, 0.0f }, 1, "dummy", (BitSetProducer) null))); - assertEquals(1, searcher.count(new KNNQuery("my_vector", new float[] { 1.0f, 1.0f }, 1, "dummy", (BitSetProducer) null))); + // query to verify distance for each of the field + IndexSearcher searcher = new IndexSearcher(reader); + float score = searcher.search( + new KNNQuery("test_vector", new float[] { 1.0f, 0.0f, 0.0f }, 1, "dummy", (BitSetProducer) null), + 10 + ).scoreDocs[0].score; + float score1 = searcher.search( + new KNNQuery("my_vector", new float[] { 1.0f, 2.0f }, 1, "dummy", (BitSetProducer) null), + 10 + ).scoreDocs[0].score; + assertEquals(1.0f / (1 + 25), score, 0.01f); + assertEquals(1.0f / (1 + 169), score1, 0.01f); + + // query to determine the hits + assertEquals( + 1, + searcher.count(new KNNQuery("test_vector", new float[] { 1.0f, 0.0f, 0.0f }, 1, "dummy", (BitSetProducer) null)) + ); + assertEquals(1, searcher.count(new KNNQuery("my_vector", new float[] { 1.0f, 1.0f }, 1, "dummy", (BitSetProducer) null))); - reader.close(); - dir.close(); - NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance().close(); + reader.close(); + NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance().close(); + } } public void testBuildFromModelTemplate(Codec codec) throws IOException, ExecutionException, InterruptedException { diff --git a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestUtil.java b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestUtil.java index 2afd86a04..d6f22ca7f 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestUtil.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestUtil.java @@ -20,9 +20,8 @@ import org.apache.lucene.search.Sort; import org.apache.lucene.store.ChecksumIndexInput; import org.apache.lucene.store.Directory; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.store.FilterDirectory; import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; import org.apache.lucene.util.StringHelper; import org.apache.lucene.util.Version; import java.util.Set; @@ -31,10 +30,10 @@ import org.opensearch.knn.index.query.KNNQueryResult; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexInputWithBuffer; import org.opensearch.knn.jni.JNIService; import java.io.IOException; -import java.nio.file.Paths; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -206,14 +205,21 @@ public static void assertLoadableByEngine( SpaceType spaceType, int dimension ) { - String filePath = Paths.get(((FSDirectory) (FilterDirectory.unwrap(state.directory))).getDirectory().toString(), fileName) - .toString(); - long indexPtr = JNIService.loadIndex(filePath, Maps.newHashMap(ImmutableMap.of(SPACE_TYPE, spaceType.getValue())), knnEngine); - int k = 2; - float[] queryVector = new float[dimension]; - KNNQueryResult[] results = JNIService.queryIndex(indexPtr, queryVector, k, methodParameters, knnEngine, null, 0, null); - assertTrue(results.length > 0); - JNIService.free(indexPtr, knnEngine); + try (final IndexInput indexInput = state.directory.openInput(fileName, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long indexPtr = JNIService.loadIndex( + indexInputWithBuffer, + Maps.newHashMap(ImmutableMap.of(SPACE_TYPE, spaceType.getValue())), + knnEngine + ); + int k = 2; + float[] queryVector = new float[dimension]; + KNNQueryResult[] results = JNIService.queryIndex(indexPtr, queryVector, k, methodParameters, knnEngine, null, 0, null); + assertTrue(results.length > 0); + JNIService.free(indexPtr, knnEngine); + } catch (IOException e) { + throw new RuntimeException(e); + } } public static void assertBinaryIndexLoadableByEngine( @@ -224,27 +230,30 @@ public static void assertBinaryIndexLoadableByEngine( int dimension, VectorDataType vectorDataType ) { - String filePath = Paths.get(((FSDirectory) (FilterDirectory.unwrap(state.directory))).getDirectory().toString(), fileName) - .toString(); - long indexPtr = JNIService.loadIndex( - filePath, - Maps.newHashMap( - ImmutableMap.of( - SPACE_TYPE, - spaceType.getValue(), - INDEX_DESCRIPTION_PARAMETER, - "BHNSW32", - VECTOR_DATA_TYPE_FIELD, - vectorDataType.getValue() - ) - ), - knnEngine - ); - int k = 2; - byte[] queryVector = new byte[dimension]; - KNNQueryResult[] results = JNIService.queryBinaryIndex(indexPtr, queryVector, k, null, knnEngine, null, 0, null); - assertTrue(results.length > 0); - JNIService.free(indexPtr, knnEngine); + try (final IndexInput indexInput = state.directory.openInput(fileName, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long indexPtr = JNIService.loadIndex( + indexInputWithBuffer, + Maps.newHashMap( + ImmutableMap.of( + SPACE_TYPE, + spaceType.getValue(), + INDEX_DESCRIPTION_PARAMETER, + "BHNSW32", + VECTOR_DATA_TYPE_FIELD, + vectorDataType.getValue() + ) + ), + knnEngine + ); + int k = 2; + byte[] queryVector = new byte[dimension]; + KNNQueryResult[] results = JNIService.queryBinaryIndex(indexPtr, queryVector, k, null, knnEngine, null, 0, null); + assertTrue(results.length > 0); + JNIService.free(indexPtr, knnEngine); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Builder(builderMethodName = "segmentInfoBuilder") diff --git a/src/test/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategyTests.java b/src/test/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategyTests.java index abb61ccd9..35c54f3b3 100644 --- a/src/test/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategyTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategyTests.java @@ -10,6 +10,7 @@ import org.junit.Before; import org.mockito.ArgumentCaptor; import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.opensearch.core.common.unit.ByteSizeValue; import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.VectorDataType; @@ -18,6 +19,7 @@ import org.opensearch.knn.index.codec.transfer.OffHeapVectorTransferFactory; import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; import org.opensearch.knn.index.vectorvalues.KNNVectorValues; import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; @@ -66,8 +68,9 @@ public void testBuildAndWrite() { when(offHeapVectorTransfer.getVectorAddress()).thenReturn(200L); + IndexOutputWithBuffer indexOutputWithBuffer = Mockito.mock(IndexOutputWithBuffer.class); BuildIndexParams buildIndexParams = BuildIndexParams.builder() - .indexPath("indexPath") + .indexOutputWithBuffer(indexOutputWithBuffer) .knnEngine(KNNEngine.NMSLIB) .vectorDataType(VectorDataType.FLOAT) .parameters(Map.of("index", "param")) @@ -84,7 +87,7 @@ public void testBuildAndWrite() { eq(new int[] { 0, 1, 2 }), eq(200L), eq(knnVectorValues.dimension()), - eq("indexPath"), + eq(indexOutputWithBuffer), eq(Map.of("index", "param")), eq(KNNEngine.NMSLIB) ) @@ -159,8 +162,9 @@ public void testBuildAndWrite_withQuantization() { when(offHeapVectorTransfer.flush(false)).thenReturn(true); when(offHeapVectorTransfer.getVectorAddress()).thenReturn(200L); + IndexOutputWithBuffer indexOutputWithBuffer = Mockito.mock(IndexOutputWithBuffer.class); BuildIndexParams buildIndexParams = BuildIndexParams.builder() - .indexPath("indexPath") + .indexOutputWithBuffer(indexOutputWithBuffer) .knnEngine(KNNEngine.FAISS) .vectorDataType(VectorDataType.FLOAT) .parameters(Map.of("index", "param")) @@ -206,7 +210,7 @@ public void testBuildAndWrite_withQuantization() { ); mockedJNIService.verify( - () -> JNIService.writeIndex(eq("indexPath"), eq(100L), eq(KNNEngine.FAISS), eq(Map.of("index", "param"))) + () -> JNIService.writeIndex(eq(indexOutputWithBuffer), eq(100L), eq(KNNEngine.FAISS), eq(Map.of("index", "param"))) ); assertEquals(200L, vectorAddressCaptor.getValue().longValue()); assertEquals(vectorAddressCaptor.getValue().longValue(), vectorAddressCaptor.getAllValues().get(0).longValue()); @@ -244,8 +248,9 @@ public void testBuildAndWriteWithModel() { when(offHeapVectorTransfer.getVectorAddress()).thenReturn(200L); + IndexOutputWithBuffer indexOutputWithBuffer = Mockito.mock(IndexOutputWithBuffer.class); BuildIndexParams buildIndexParams = BuildIndexParams.builder() - .indexPath("indexPath") + .indexOutputWithBuffer(indexOutputWithBuffer) .knnEngine(KNNEngine.NMSLIB) .vectorDataType(VectorDataType.FLOAT) .parameters(Map.of("model_id", "id", "model_blob", modelBlob)) @@ -262,7 +267,7 @@ public void testBuildAndWriteWithModel() { eq(new int[] { 0, 1, 2 }), eq(200L), eq(2), - eq("indexPath"), + eq(indexOutputWithBuffer), eq(modelBlob), eq(Map.of("model_id", "id", "model_blob", modelBlob)), eq(KNNEngine.NMSLIB) diff --git a/src/test/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategyTests.java b/src/test/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategyTests.java index 77abe1cd2..08942fe7f 100644 --- a/src/test/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategyTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategyTests.java @@ -15,6 +15,7 @@ import org.opensearch.knn.index.codec.transfer.OffHeapVectorTransferFactory; import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import org.opensearch.knn.index.vectorvalues.KNNVectorValues; import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; import org.opensearch.knn.index.vectorvalues.TestVectorValues; @@ -50,15 +51,15 @@ public void testBuildAndWrite() { MockedStatic mockedJNIService = Mockito.mockStatic(JNIService.class); MockedStatic mockedOffHeapVectorTransferFactory = Mockito.mockStatic( OffHeapVectorTransferFactory.class - ); + ) ) { - // Limits transfer to 2 vectors mockedJNIService.when(() -> JNIService.initIndex(3, 2, Map.of("index", "param"), KNNEngine.FAISS)).thenReturn(100L); OffHeapVectorTransfer offHeapVectorTransfer = mock(OffHeapVectorTransfer.class); mockedOffHeapVectorTransferFactory.when(() -> OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.FLOAT, 8, 3)) .thenReturn(offHeapVectorTransfer); + IndexOutputWithBuffer indexOutputWithBuffer = Mockito.mock(IndexOutputWithBuffer.class); when(offHeapVectorTransfer.getTransferLimit()).thenReturn(2); when(offHeapVectorTransfer.transfer(vectorTransferCapture.capture(), eq(false))).thenReturn(false) @@ -68,7 +69,7 @@ public void testBuildAndWrite() { when(offHeapVectorTransfer.getVectorAddress()).thenReturn(200L); BuildIndexParams buildIndexParams = BuildIndexParams.builder() - .indexPath("indexPath") + .indexOutputWithBuffer(indexOutputWithBuffer) .knnEngine(KNNEngine.FAISS) .vectorDataType(VectorDataType.FLOAT) .parameters(Map.of("index", "param")) @@ -113,7 +114,7 @@ public void testBuildAndWrite() { ); mockedJNIService.verify( - () -> JNIService.writeIndex(eq("indexPath"), eq(100L), eq(KNNEngine.FAISS), eq(Map.of("index", "param"))) + () -> JNIService.writeIndex(eq(indexOutputWithBuffer), eq(100L), eq(KNNEngine.FAISS), eq(Map.of("index", "param"))) ); assertEquals(200L, vectorAddressCaptor.getValue().longValue()); assertEquals(vectorAddressCaptor.getValue().longValue(), vectorAddressCaptor.getAllValues().get(0).longValue()); @@ -185,8 +186,9 @@ public void testBuildAndWrite_withQuantization() { when(offHeapVectorTransfer.flush(false)).thenReturn(true); when(offHeapVectorTransfer.getVectorAddress()).thenReturn(200L); + IndexOutputWithBuffer indexOutputWithBuffer = Mockito.mock(IndexOutputWithBuffer.class); BuildIndexParams buildIndexParams = BuildIndexParams.builder() - .indexPath("indexPath") + .indexOutputWithBuffer(indexOutputWithBuffer) .knnEngine(KNNEngine.FAISS) .vectorDataType(VectorDataType.FLOAT) .parameters(Map.of("index", "param")) @@ -232,7 +234,7 @@ public void testBuildAndWrite_withQuantization() { ); mockedJNIService.verify( - () -> JNIService.writeIndex(eq("indexPath"), eq(100L), eq(KNNEngine.FAISS), eq(Map.of("index", "param"))) + () -> JNIService.writeIndex(eq(indexOutputWithBuffer), eq(100L), eq(KNNEngine.FAISS), eq(Map.of("index", "param"))) ); assertEquals(200L, vectorAddressCaptor.getValue().longValue()); assertEquals(vectorAddressCaptor.getValue().longValue(), vectorAddressCaptor.getAllValues().get(0).longValue()); diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java index db6231adf..be20150bc 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java @@ -13,6 +13,9 @@ import com.google.common.collect.ImmutableMap; import lombok.SneakyThrows; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; import org.junit.Before; import org.mockito.Mock; import org.opensearch.common.settings.ClusterSettings; @@ -23,7 +26,7 @@ import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.KNNEngine; -import org.opensearch.knn.index.util.IndexUtil; +import org.opensearch.knn.index.store.IndexInputWithBuffer; import org.opensearch.knn.jni.JNICommons; import org.opensearch.knn.jni.JNIService; @@ -62,110 +65,121 @@ public void setUp() throws Exception { KNNSettings.state().setClusterService(clusterService); } - public void testIndexAllocation_close() throws InterruptedException { + @SneakyThrows + public void testIndexAllocation_close() { // Create basic nmslib HNSW index - Path dir = createTempDir(); - KNNEngine knnEngine = KNNEngine.NMSLIB; - String indexName = "test1" + knnEngine.getExtension(); - String path = dir.resolve(indexName).toAbsolutePath().toString(); - int numVectors = 10; - int dimension = 10; - int[] ids = new int[numVectors]; - float[][] vectors = new float[numVectors][dimension]; - for (int i = 0; i < numVectors; i++) { - ids[i] = i; - Arrays.fill(vectors[i], 1f); - } - Map parameters = ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.DEFAULT.getValue()); - long vectorMemoryAddress = JNICommons.storeVectorData(0, vectors, numVectors * dimension); - TestUtils.createIndex(ids, vectorMemoryAddress, dimension, path, parameters, knnEngine); - - // Load index into memory - long memoryAddress = JNIService.loadIndex(path, parameters, knnEngine); - - ExecutorService executorService = Executors.newSingleThreadExecutor(); - NativeMemoryAllocation.IndexAllocation indexAllocation = new NativeMemoryAllocation.IndexAllocation( - executorService, - memoryAddress, - IndexUtil.getFileSizeInKB(path), - knnEngine, - path, - "test" - ); - - indexAllocation.close(); - - Thread.sleep(1000 * 2); - indexAllocation.writeLock(); - assertTrue(indexAllocation.isClosed()); - indexAllocation.writeUnlock(); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + KNNEngine knnEngine = KNNEngine.NMSLIB; + String indexFileName = "test1" + knnEngine.getExtension(); + int numVectors = 10; + int dimension = 10; + int[] ids = new int[numVectors]; + float[][] vectors = new float[numVectors][dimension]; + for (int i = 0; i < numVectors; i++) { + ids[i] = i; + Arrays.fill(vectors[i], 1f); + } + Map parameters = ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.DEFAULT.getValue()); + long vectorMemoryAddress = JNICommons.storeVectorData(0, vectors, numVectors * dimension); + TestUtils.createIndex(ids, vectorMemoryAddress, dimension, directory, indexFileName, parameters, knnEngine); + + // Load index into memory + final long memoryAddress; + try (IndexInput indexInput = directory.openInput(indexFileName, IOContext.DEFAULT)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + memoryAddress = JNIService.loadIndex(indexInputWithBuffer, parameters, knnEngine); + } + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + NativeMemoryAllocation.IndexAllocation indexAllocation = new NativeMemoryAllocation.IndexAllocation( + executorService, + memoryAddress, + (int) directory.fileLength(indexFileName) / 1024, + knnEngine, + indexFileName, + "test" + ); + + indexAllocation.close(); + + Thread.sleep(1000 * 2); + indexAllocation.writeLock(); + assertTrue(indexAllocation.isClosed()); + indexAllocation.writeUnlock(); - indexAllocation.close(); + indexAllocation.close(); - Thread.sleep(1000 * 2); - indexAllocation.writeLock(); - assertTrue(indexAllocation.isClosed()); - indexAllocation.writeUnlock(); + Thread.sleep(1000 * 2); + indexAllocation.writeLock(); + assertTrue(indexAllocation.isClosed()); + indexAllocation.writeUnlock(); - executorService.shutdown(); + executorService.shutdown(); + } } @SneakyThrows public void testClose_whenBinaryFiass_thenSuccess() { - Path dir = createTempDir(); + Path tempDirPath = createTempDir(); KNNEngine knnEngine = KNNEngine.FAISS; - String indexName = "test1" + knnEngine.getExtension(); - String path = dir.resolve(indexName).toAbsolutePath().toString(); - int numVectors = 10; - int dimension = 8; - int dataLength = dimension / 8; - int[] ids = new int[numVectors]; - byte[][] vectors = new byte[numVectors][dataLength]; - for (int i = 0; i < numVectors; i++) { - ids[i] = i; - vectors[i][0] = 1; - } - Map parameters = ImmutableMap.of( - KNNConstants.SPACE_TYPE, - SpaceType.HAMMING.getValue(), - KNNConstants.INDEX_DESCRIPTION_PARAMETER, - "BHNSW32", - KNNConstants.VECTOR_DATA_TYPE_FIELD, - VectorDataType.BINARY.getValue() - ); - long vectorMemoryAddress = JNICommons.storeBinaryVectorData(0, vectors, numVectors * dataLength); - TestUtils.createIndex(ids, vectorMemoryAddress, dimension, path, parameters, knnEngine); - - // Load index into memory - long memoryAddress = JNIService.loadIndex(path, parameters, knnEngine); - - ExecutorService executorService = Executors.newSingleThreadExecutor(); - NativeMemoryAllocation.IndexAllocation indexAllocation = new NativeMemoryAllocation.IndexAllocation( - executorService, - memoryAddress, - IndexUtil.getFileSizeInKB(path), - knnEngine, - path, - "test", - null, - true - ); - - indexAllocation.close(); - - Thread.sleep(1000 * 2); - indexAllocation.writeLock(); - assertTrue(indexAllocation.isClosed()); - indexAllocation.writeUnlock(); + String indexFileName = "test1" + knnEngine.getExtension(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int numVectors = 10; + int dimension = 8; + int dataLength = dimension / 8; + int[] ids = new int[numVectors]; + byte[][] vectors = new byte[numVectors][dataLength]; + for (int i = 0; i < numVectors; i++) { + ids[i] = i; + vectors[i][0] = 1; + } + Map parameters = ImmutableMap.of( + KNNConstants.SPACE_TYPE, + SpaceType.HAMMING.getValue(), + KNNConstants.INDEX_DESCRIPTION_PARAMETER, + "BHNSW32", + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ); + long vectorMemoryAddress = JNICommons.storeBinaryVectorData(0, vectors, numVectors * dataLength); + TestUtils.createIndex(ids, vectorMemoryAddress, dimension, directory, indexFileName, parameters, knnEngine); + + // Load index into memory + final long memoryAddress; + try (IndexInput indexInput = directory.openInput(indexFileName, IOContext.DEFAULT)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + memoryAddress = JNIService.loadIndex(indexInputWithBuffer, parameters, knnEngine); + } + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + NativeMemoryAllocation.IndexAllocation indexAllocation = new NativeMemoryAllocation.IndexAllocation( + executorService, + memoryAddress, + (int) directory.fileLength(indexFileName) / 1024, + knnEngine, + indexFileName, + "test", + null, + true + ); + + indexAllocation.close(); + + Thread.sleep(1000 * 2); + indexAllocation.writeLock(); + assertTrue(indexAllocation.isClosed()); + indexAllocation.writeUnlock(); - indexAllocation.close(); + indexAllocation.close(); - Thread.sleep(1000 * 2); - indexAllocation.writeLock(); - assertTrue(indexAllocation.isClosed()); - indexAllocation.writeUnlock(); + Thread.sleep(1000 * 2); + indexAllocation.writeLock(); + assertTrue(indexAllocation.isClosed()); + indexAllocation.writeUnlock(); - executorService.shutdown(); + executorService.shutdown(); + } } public void testIndexAllocation_getMemoryAddress() { diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java index 8236d0518..735974bd1 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java @@ -13,7 +13,6 @@ import com.google.common.collect.ImmutableMap; import org.apache.lucene.store.Directory; -import org.apache.lucene.store.MMapDirectory; import org.opensearch.core.action.ActionListener; import org.opensearch.action.search.SearchResponse; import org.opensearch.knn.KNNTestCase; @@ -44,98 +43,98 @@ public class NativeMemoryLoadStrategyTests extends KNNTestCase { public void testIndexLoadStrategy_load() throws IOException { // Create basic nmslib HNSW index - Path dir = createTempDir(); - Directory luceneDirectory = new MMapDirectory(dir); - KNNEngine knnEngine = KNNEngine.NMSLIB; - String indexName = "test1" + knnEngine.getExtension(); - String path = dir.resolve(indexName).toAbsolutePath().toString(); - int numVectors = 10; - int dimension = 10; - int[] ids = new int[numVectors]; - float[][] vectors = new float[numVectors][dimension]; - for (int i = 0; i < numVectors; i++) { - ids[i] = i; - Arrays.fill(vectors[i], 1f); + Path tempDirPath = createTempDir(); + try (Directory luceneDirectory = newFSDirectory(tempDirPath)) { + KNNEngine knnEngine = KNNEngine.NMSLIB; + String indexFileName = "test1" + knnEngine.getExtension(); + int numVectors = 10; + int dimension = 10; + int[] ids = new int[numVectors]; + float[][] vectors = new float[numVectors][dimension]; + for (int i = 0; i < numVectors; i++) { + ids[i] = i; + Arrays.fill(vectors[i], 1f); + } + Map parameters = ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.DEFAULT.getValue()); + long memoryAddress = JNICommons.storeVectorData(0, vectors, numVectors * dimension); + TestUtils.createIndex(ids, memoryAddress, dimension, luceneDirectory, indexFileName, parameters, knnEngine); + + // Setup mock resource manager + NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( + luceneDirectory, + TestUtils.createFakeNativeMamoryCacheKey(indexFileName), + NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), + parameters, + "test" + ); + + // Load + NativeMemoryAllocation.IndexAllocation indexAllocation = NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance() + .load(indexEntryContext); + + // Confirm that the file was loaded by querying + float[] query = new float[dimension]; + Arrays.fill(query, numVectors + 1); + KNNQueryResult[] results = JNIService.queryIndex(indexAllocation.getMemoryAddress(), query, 2, null, knnEngine, null, 0, null); + assertTrue(results.length > 0); } - Map parameters = ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.DEFAULT.getValue()); - long memoryAddress = JNICommons.storeVectorData(0, vectors, numVectors * dimension); - TestUtils.createIndex(ids, memoryAddress, dimension, path, parameters, knnEngine); - - // Setup mock resource manager - NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( - luceneDirectory, - TestUtils.createFakeNativeMamoryCacheKey(indexName), - NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), - parameters, - "test" - ); - - // Load - NativeMemoryAllocation.IndexAllocation indexAllocation = NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance() - .load(indexEntryContext); - - // Confirm that the file was loaded by querying - float[] query = new float[dimension]; - Arrays.fill(query, numVectors + 1); - KNNQueryResult[] results = JNIService.queryIndex(indexAllocation.getMemoryAddress(), query, 2, null, knnEngine, null, 0, null); - assertTrue(results.length > 0); } public void testLoad_whenFaissBinary_thenSuccess() throws IOException { - Path dir = createTempDir(); - Directory luceneDirectory = new MMapDirectory(dir); - KNNEngine knnEngine = KNNEngine.FAISS; - String indexName = "test1" + knnEngine.getExtension(); - String path = dir.resolve(indexName).toAbsolutePath().toString(); - int numVectors = 10; - int dimension = 8; - int dataLength = dimension / 8; - int[] ids = new int[numVectors]; - byte[][] vectors = new byte[numVectors][dataLength]; - for (int i = 0; i < numVectors; i++) { - ids[i] = i; - vectors[i][0] = 1; + Path tempDirPath = createTempDir(); + try (Directory luceneDirectory = newFSDirectory(tempDirPath)) { + KNNEngine knnEngine = KNNEngine.FAISS; + String indexFileName = "test1" + knnEngine.getExtension(); + int numVectors = 10; + int dimension = 8; + int dataLength = dimension / 8; + int[] ids = new int[numVectors]; + byte[][] vectors = new byte[numVectors][dataLength]; + for (int i = 0; i < numVectors; i++) { + ids[i] = i; + vectors[i][0] = 1; + } + Map parameters = ImmutableMap.of( + KNNConstants.SPACE_TYPE, + SpaceType.HAMMING.getValue(), + KNNConstants.INDEX_DESCRIPTION_PARAMETER, + "BHNSW32", + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ); + long memoryAddress = JNICommons.storeBinaryVectorData(0, vectors, numVectors); + TestUtils.createIndex(ids, memoryAddress, dimension, luceneDirectory, indexFileName, parameters, knnEngine); + + // Setup mock resource manager + NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( + luceneDirectory, + TestUtils.createFakeNativeMamoryCacheKey(indexFileName), + NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), + parameters, + "test" + ); + + // Load + NativeMemoryAllocation.IndexAllocation indexAllocation = NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance() + .load(indexEntryContext); + + // Verify + assertTrue(indexAllocation.isBinaryIndex()); + + // Confirm that the file was loaded by querying + byte[] query = { 1 }; + KNNQueryResult[] results = JNIService.queryBinaryIndex( + indexAllocation.getMemoryAddress(), + query, + 2, + null, + knnEngine, + null, + 0, + null + ); + assertTrue(results.length > 0); } - Map parameters = ImmutableMap.of( - KNNConstants.SPACE_TYPE, - SpaceType.HAMMING.getValue(), - KNNConstants.INDEX_DESCRIPTION_PARAMETER, - "BHNSW32", - KNNConstants.VECTOR_DATA_TYPE_FIELD, - VectorDataType.BINARY.getValue() - ); - long memoryAddress = JNICommons.storeBinaryVectorData(0, vectors, numVectors); - TestUtils.createIndex(ids, memoryAddress, dimension, path, parameters, knnEngine); - - // Setup mock resource manager - NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( - luceneDirectory, - TestUtils.createFakeNativeMamoryCacheKey(indexName), - NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), - parameters, - "test" - ); - - // Load - NativeMemoryAllocation.IndexAllocation indexAllocation = NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance() - .load(indexEntryContext); - - // Verify - assertTrue(indexAllocation.isBinaryIndex()); - - // Confirm that the file was loaded by querying - byte[] query = { 1 }; - KNNQueryResult[] results = JNIService.queryBinaryIndex( - indexAllocation.getMemoryAddress(), - query, - 2, - null, - knnEngine, - null, - 0, - null - ); - assertTrue(results.length > 0); } @SuppressWarnings("unchecked") diff --git a/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java b/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java index f6d118092..53a78b381 100644 --- a/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java +++ b/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java @@ -17,7 +17,7 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexInput; -import org.apache.lucene.store.MMapDirectory; +import org.apache.lucene.store.IndexOutput; import org.junit.BeforeClass; import org.opensearch.Version; import org.opensearch.common.xcontent.XContentFactory; @@ -25,6 +25,8 @@ import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.TestUtils; import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.common.RaisingIOExceptionIndexInput; +import org.opensearch.knn.common.RasingIOExceptionIndexOutput; import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.KNNMethodContext; import org.opensearch.knn.index.VectorDataType; @@ -34,6 +36,7 @@ import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.store.IndexInputWithBuffer; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import java.io.IOException; import java.net.URL; @@ -45,6 +48,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; @@ -88,27 +92,40 @@ public static void setUpClass() throws IOException { testDataNested = new TestUtils.TestData(testIndexVectorsNested.getPath(), testQueries.getPath()); } + @SneakyThrows public void testCreateIndex_invalid_engineNotSupported() { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + IllegalArgumentException.class, + () -> TestUtils.createIndex( + new int[] {}, + 0, + 0, + directory, + "DONT_CARE", + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.LUCENE + ) + ); + } + } + + public void testCreateIndex_invalid_engineNull() { expectThrows( - IllegalArgumentException.class, + Exception.class, () -> TestUtils.createIndex( new int[] {}, 0, 0, - "test", + null, + "DONT_CARE", ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.LUCENE + null ) ); } - public void testCreateIndex_invalid_engineNull() { - expectThrows( - Exception.class, - () -> TestUtils.createIndex(new int[] {}, 0, 0, "test", ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), null) - ); - } - public void testCreateIndex_nmslib_invalid_noSpaceType() { expectThrows( Exception.class, @@ -116,7 +133,8 @@ public void testCreateIndex_nmslib_invalid_noSpaceType() { testData.indexData.docs, testData.loadDataToMemoryAddress(), testData.indexData.getDimension(), - "something", + null, + "DONT_CARE", Collections.emptyMap(), KNNEngine.NMSLIB ) @@ -124,98 +142,109 @@ public void testCreateIndex_nmslib_invalid_noSpaceType() { } public void testCreateIndex_nmslib_invalid_vectorDocIDMismatch() throws IOException { - int[] docIds = new int[] { 1, 2, 3 }; float[][] vectors1 = new float[][] { { 1, 2 }, { 3, 4 } }; long memoryAddress = JNICommons.storeVectorData(0, vectors1, vectors1.length * vectors1[0].length); - Path tmpFile1 = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - memoryAddress, - vectors1[0].length, - tmpFile1.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ) - ); - - float[][] vectors2 = new float[][] { { 1, 2 }, { 3, 4 }, { 4, 5 }, { 6, 7 }, { 8, 9 } }; - long memoryAddress2 = JNICommons.storeVectorData(0, vectors2, vectors2.length * vectors2[0].length); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1.tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors1[0].length, + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ) + ); - Path tmpFile2 = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - memoryAddress2, - vectors2[0].length, - tmpFile2.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ) - ); + float[][] vectors2 = new float[][] { { 1, 2 }, { 3, 4 }, { 4, 5 }, { 6, 7 }, { 8, 9 } }; + long memoryAddress2 = JNICommons.storeVectorData(0, vectors2, vectors2.length * vectors2[0].length); + + String indexFileName2 = "test2.tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress2, + vectors2[0].length, + directory, + indexFileName2, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ) + ); + } } public void testCreateIndex_nmslib_invalid_nullArgument() throws IOException { + Path tempDirPath = createTempDir(); + String indexFileName = "test.tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] {}; + float[][] vectors = new float[][] {}; + long memoryAddress = JNICommons.storeVectorData(0, vectors, vectors.length); + + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + null, + memoryAddress, + 0, + directory, + indexFileName, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ) + ); - int[] docIds = new int[] {}; - float[][] vectors = new float[][] {}; - long memoryAddress = JNICommons.storeVectorData(0, vectors, vectors.length); - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - null, - memoryAddress, - 0, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ) - ); - - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - 0, - 0, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + 0, + 0, + directory, + indexFileName, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ) + ); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - memoryAddress, - 0, - null, - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + 0, + directory, + null, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ) + ); - expectThrows( - Exception.class, - () -> TestUtils.createIndex(docIds, memoryAddress, 0, tmpFile.toAbsolutePath().toString(), null, KNNEngine.NMSLIB) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex(docIds, memoryAddress, 0, directory, indexFileName, null, KNNEngine.NMSLIB) + ); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - memoryAddress, - 0, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - null - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + 0, + directory, + indexFileName, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + null + ) + ); + } } public void testCreateIndex_nmslib_invalid_badSpace() throws IOException { @@ -223,18 +252,22 @@ public void testCreateIndex_nmslib_invalid_badSpace() throws IOException { int[] docIds = new int[] { 1 }; float[][] vectors = new float[][] { { 2, 3 } }; long memoryAddress = JNICommons.storeVectorData(0, vectors, vectors.length * vectors[0].length); - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - memoryAddress, - vectors[0].length, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, "invalid"), - KNNEngine.NMSLIB - ) - ); + Path tempDirPath = createTempDir(); + String indexFileName = "test.tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName, + ImmutableMap.of(KNNConstants.SPACE_TYPE, "invalid"), + KNNEngine.NMSLIB + ) + ); + } } public void testCreateIndex_nmslib_invalid_badParameterType() throws IOException { @@ -249,74 +282,89 @@ public void testCreateIndex_nmslib_invalid_badParameterType() throws IOException KNNConstants.METHOD_PARAMETER_M, "12" ); - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - memoryAddress, - vectors[0].length, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue(), KNNConstants.PARAMETERS, parametersMap), - KNNEngine.NMSLIB - ) - ); + Path tempDirPath = createTempDir(); + String indexFileName = "test.tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue(), KNNConstants.PARAMETERS, parametersMap), + KNNEngine.NMSLIB + ) + ); + } } public void testCreateIndex_nmslib_valid() throws IOException { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + for (SpaceType spaceType : NmslibHNSWMethod.SUPPORTED_SPACES) { + if (SpaceType.UNDEFINED == spaceType) { + continue; + } - for (SpaceType spaceType : NmslibHNSWMethod.SUPPORTED_SPACES) { - if (SpaceType.UNDEFINED == spaceType) { - continue; - } - - Path tmpFile = createTempFile(); + final String indexFileName1 = "test" + UUID.randomUUID() + ".tmp"; - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.NMSLIB - ); - assertTrue(tmpFile.toFile().length() > 0); + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - tmpFile = createTempFile(); + final String indexFileName2 = "test" + UUID.randomUUID() + ".tmp"; - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of( - KNNConstants.SPACE_TYPE, - spaceType.getValue(), - KNNConstants.HNSW_ALGO_EF_CONSTRUCTION, - 14, - KNNConstants.METHOD_PARAMETER_M, - 12 - ), - KNNEngine.NMSLIB - ); - assertTrue(tmpFile.toFile().length() > 0); + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName2, + ImmutableMap.of( + KNNConstants.SPACE_TYPE, + spaceType.getValue(), + KNNConstants.HNSW_ALGO_EF_CONSTRUCTION, + 14, + KNNConstants.METHOD_PARAMETER_M, + 12 + ), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName2) > 0); + } } } + @SneakyThrows public void testCreateIndex_faiss_invalid_noSpaceType() { int[] docIds = new int[] {}; - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - "something", - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod), - KNNEngine.FAISS - ) - ); + Path tempDirPath = createTempDir(); + String indexFileName = "test.tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod), + KNNEngine.FAISS + ) + ); + + } } public void testCreateIndex_faiss_invalid_vectorDocIDMismatch() throws IOException { @@ -324,251 +372,331 @@ public void testCreateIndex_faiss_invalid_vectorDocIDMismatch() throws IOExcepti int[] docIds = new int[] { 1, 2, 3 }; float[][] vectors1 = new float[][] { { 1, 2 }, { 3, 4 } }; long memoryAddress = JNICommons.storeVectorData(0, vectors1, vectors1.length * vectors1[0].length); - Path tmpFile1 = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - memoryAddress, - vectors1[0].length, - tmpFile1.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ) - ); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors1[0].length, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); - float[][] vectors2 = new float[][] { { 1, 2 }, { 3, 4 }, { 4, 5 }, { 6, 7 }, { 8, 9 } }; - long memoryAddress2 = JNICommons.storeVectorData(0, vectors2, vectors2.length * vectors2[0].length); - Path tmpFile2 = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - memoryAddress, - vectors2[0].length, - tmpFile2.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ) - ); + float[][] vectors2 = new float[][] { { 1, 2 }, { 3, 4 }, { 4, 5 }, { 6, 7 }, { 8, 9 } }; + long memoryAddress2 = JNICommons.storeVectorData(0, vectors2, vectors2.length * vectors2[0].length); + String indexFileName2 = "test2" + UUID.randomUUID() + ".tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress2, + vectors2[0].length, + directory, + indexFileName2, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); + } } public void testCreateIndex_faiss_invalid_null() throws IOException { + Path tempDirPath = createTempDir(); int[] docIds = new int[] {}; float[][] vectors = new float[][] {}; long memoryAddress = JNICommons.storeVectorData(0, vectors, 0); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + null, + memoryAddress, + 0, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - null, - memoryAddress, - 0, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ) - ); - - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - 0, - 0, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + 0, + 0, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - null, - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + null, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - null, - KNNEngine.FAISS - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + null, + KNNEngine.FAISS + ) + ); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - null - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + null + ) + ); + } } public void testCreateIndex_faiss_invalid_invalidSpace() throws IOException { - - int[] docIds = new int[] { 1 }; - float[][] vectors = new float[][] { { 2, 3 } }; - long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); - - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - memoryAddress, - vectors[0].length, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, "invalid"), - KNNEngine.FAISS - ) - ); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1 }; + float[][] vectors = new float[][] { { 2, 3 } }; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, "invalid"), + KNNEngine.FAISS + ) + ); + } } public void testCreateIndex_faiss_invalid_noIndexDescription() throws IOException { - - int[] docIds = new int[] { 1, 2 }; - float[][] vectors = new float[][] { { 2, 3 }, { 2, 3 } }; - long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); - - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - memoryAddress, - vectors[0].length, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ) - ); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1, 2 }; + float[][] vectors = new float[][] { { 2, 3 }, { 2, 3 } }; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); + } } public void testCreateIndex_faiss_invalid_invalidIndexDescription() throws IOException { - int[] docIds = new int[] { 1, 2 }; - float[][] vectors = new float[][] { { 2, 3 }, { 2, 3 } }; - long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - memoryAddress, - vectors[0].length, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, "invalid", KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ) - ); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1, 2 }; + float[][] vectors = new float[][] { { 2, 3 }, { 2, 3 } }; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, "invalid", KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); + } } @SneakyThrows public void testCreateIndex_faiss_sqfp16_invalidIndexDescription() { - - int[] docIds = new int[] { 1, 2 }; - float[][] vectors = new float[][] { { 2, 3 }, { 3, 4 } }; - long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); - - String sqfp16InvalidIndexDescription = "HNSW16,SQfp1655"; - - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1, 2 }; + float[][] vectors = new float[][] { { 2, 3 }, { 3, 4 } }; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + + String sqfp16InvalidIndexDescription = "HNSW16,SQfp1655"; + + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName1, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + sqfp16InvalidIndexDescription, + KNNConstants.SPACE_TYPE, + SpaceType.L2.getValue() + ), + KNNEngine.FAISS + ) + ); + } + } + + @SneakyThrows + public void testLoadIndex_faiss_sqfp16_valid() { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1, 2 }; + float[][] vectors = new float[][] { { 2, 3 }, { 3, 4 } }; + String sqfp16IndexDescription = "HNSW16,SQfp16"; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( docIds, memoryAddress, vectors[0].length, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of( - INDEX_DESCRIPTION_PARAMETER, - sqfp16InvalidIndexDescription, - KNNConstants.SPACE_TYPE, - SpaceType.L2.getValue() - ), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, sqfp16IndexDescription, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), KNNEngine.FAISS - ) - ); + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + } + } } @SneakyThrows - public void testLoadIndex_faiss_sqfp16_valid() { + public void testLoadIndex_when_io_exception_was_raised() { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1, 2 }; + float[][] vectors = new float[][] { { 2, 3 }, { 3, 4 } }; + String sqfp16IndexDescription = "HNSW16,SQfp16"; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, sqfp16IndexDescription, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - int[] docIds = new int[] { 1, 2 }; - float[][] vectors = new float[][] { { 2, 3 }, { 3, 4 } }; - String sqfp16IndexDescription = "HNSW16,SQfp16"; - long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); - Path tmpFile = createTempFile(); - TestUtils.createIndex( - docIds, - memoryAddress, - vectors[0].length, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, sqfp16IndexDescription, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); + final IndexInput raiseIOExceptionIndexInput = new RaisingIOExceptionIndexInput(); + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(raiseIOExceptionIndexInput); - long pointer = JNIService.loadIndex(tmpFile.toAbsolutePath().toString(), Collections.emptyMap(), KNNEngine.FAISS); - assertNotEquals(0, pointer); + try { + JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + fail("Exception thrown is expected."); + } catch (Throwable e) { + assertTrue(e.getMessage().contains("Reading bytes via IndexInput has failed.")); + } + } } @SneakyThrows public void testQueryIndex_faiss_sqfp16_valid() { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + String sqfp16IndexDescription = "HNSW16,SQfp16"; + int k = 10; + Map methodParameters = Map.of("ef_search", 12); + float[][] truncatedVectors = truncateToFp16Range(testData.indexData.vectors); + long memoryAddress = JNICommons.storeVectorData( + 0, + truncatedVectors, + (long) truncatedVectors.length * truncatedVectors[0].length + ); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testData.indexData.docs, + memoryAddress, + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, sqfp16IndexDescription, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - String sqfp16IndexDescription = "HNSW16,SQfp16"; - int k = 10; - Map methodParameters = Map.of("ef_search", 12); - float[][] truncatedVectors = truncateToFp16Range(testData.indexData.vectors); - long memoryAddress = JNICommons.storeVectorData(0, truncatedVectors, (long) truncatedVectors.length * truncatedVectors[0].length); - Path tmpFile = createTempFile(); - TestUtils.createIndex( - testData.indexData.docs, - memoryAddress, - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, sqfp16IndexDescription, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); - - long pointer = JNIService.loadIndex(tmpFile.toAbsolutePath().toString(), Collections.emptyMap(), KNNEngine.FAISS); - assertNotEquals(0, pointer); + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - for (float[] query : testData.queries) { - KNNQueryResult[] results = JNIService.queryIndex(pointer, query, k, methodParameters, KNNEngine.FAISS, null, 0, null); - assertEquals(k, results.length); - } + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex(pointer, query, k, methodParameters, KNNEngine.FAISS, null, 0, null); + assertEquals(k, results.length); + } - // Filter will result in no ids - for (float[] query : testData.queries) { - KNNQueryResult[] results = JNIService.queryIndex( - pointer, - query, - k, - methodParameters, - KNNEngine.FAISS, - new long[] { 0 }, - 0, - null - ); - assertEquals(0, results.length); + // Filter will result in no ids + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + methodParameters, + KNNEngine.FAISS, + new long[] { 0 }, + 0, + null + ); + assertEquals(0, results.length); + } } } @@ -625,93 +753,103 @@ public void testTrain_whenConfigurationIsIVFSQFP16_thenSucceed() { } public void testCreateIndex_faiss_invalid_invalidParameterType() throws IOException { - - int[] docIds = new int[] {}; - float[][] vectors = new float[][] {}; - - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> TestUtils.createIndex( - docIds, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of( - INDEX_DESCRIPTION_PARAMETER, - "IVF13", - KNNConstants.SPACE_TYPE, - SpaceType.L2.getValue(), - KNNConstants.PARAMETERS, - ImmutableMap.of(KNNConstants.METHOD_PARAMETER_NPROBES, "14") - ), - KNNEngine.FAISS - ) - ); - + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] {}; + float[][] vectors = new float[][] {}; + + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + "IVF13", + KNNConstants.SPACE_TYPE, + SpaceType.L2.getValue(), + KNNConstants.PARAMETERS, + ImmutableMap.of(KNNConstants.METHOD_PARAMETER_NPROBES, "14") + ), + KNNEngine.FAISS + ) + ); + } } public void testCreateIndex_faiss_valid() throws IOException { List methods = ImmutableList.of(faissMethod); List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); - for (String method : methods) { - for (SpaceType spaceType : spaces) { - Path tmpFile1 = createTempFile(); - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile1.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.FAISS - ); - assertTrue(tmpFile1.toFile().length() > 0); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + for (String method : methods) { + for (SpaceType spaceType : spaces) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + } } } } @SneakyThrows public void testCreateIndex_binary_faiss_valid() { - Path tmpFile1 = createTempFile(); - long memoryAddr = testData.loadBinaryDataToMemoryAddress(); - TestUtils.createIndex( - testData.indexData.docs, - memoryAddr, - testData.indexData.getDimension(), - tmpFile1.toAbsolutePath().toString(), - ImmutableMap.of( - INDEX_DESCRIPTION_PARAMETER, - faissBinaryMethod, - KNNConstants.SPACE_TYPE, - SpaceType.HAMMING.getValue(), - KNNConstants.VECTOR_DATA_TYPE_FIELD, - VectorDataType.BINARY.getValue() - ), - KNNEngine.FAISS - ); - assertTrue(tmpFile1.toFile().length() > 0); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + long memoryAddr = testData.loadBinaryDataToMemoryAddress(); + TestUtils.createIndex( + testData.indexData.docs, + memoryAddr, + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + faissBinaryMethod, + KNNConstants.SPACE_TYPE, + SpaceType.HAMMING.getValue(), + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + } } public void testLoadIndex_invalidEngine() { - expectThrows(IllegalArgumentException.class, () -> JNIService.loadIndex("test", Collections.emptyMap(), KNNEngine.LUCENE)); + expectThrows(IllegalArgumentException.class, () -> JNIService.loadIndex(null, Collections.emptyMap(), KNNEngine.LUCENE)); } public void testLoadIndex_nmslib_invalid_badSpaceType() { expectThrows( Exception.class, - () -> JNIService.loadIndex("test", ImmutableMap.of(KNNConstants.SPACE_TYPE, "invalid"), KNNEngine.NMSLIB) + () -> JNIService.loadIndex(null, ImmutableMap.of(KNNConstants.SPACE_TYPE, "invalid"), KNNEngine.NMSLIB) ); } public void testLoadIndex_nmslib_invalid_noSpaceType() { - expectThrows(Exception.class, () -> JNIService.loadIndex("test", Collections.emptyMap(), KNNEngine.NMSLIB)); + expectThrows(Exception.class, () -> JNIService.loadIndex(null, Collections.emptyMap(), KNNEngine.NMSLIB)); } public void testLoadIndex_nmslib_invalid_fileDoesNotExist() { expectThrows( Exception.class, - () -> JNIService.loadIndex("invalid", ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), KNNEngine.NMSLIB) + () -> JNIService.loadIndex(null, ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), KNNEngine.NMSLIB) ); } @@ -719,91 +857,142 @@ public void testLoadIndex_nmslib_invalid_badFile() throws IOException { Path tmpFile = createTempFile(); expectThrows( Exception.class, - () -> JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ) + () -> JNIService.loadIndex(null, ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), KNNEngine.NMSLIB) ); } public void testLoadIndex_nmslib_valid() throws IOException { - Path tmpFile = createTempFile(); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ); - assertTrue(tmpFile.toFile().length() > 0); + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + } + } + } - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ); - assertNotEquals(0, pointer); + public void testLoadIndex_nmslib_raise_io_exception() throws IOException { + + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + final IndexInput raiseIOExceptionIndexInput = new RaisingIOExceptionIndexInput(); + + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(raiseIOExceptionIndexInput); + try { + JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + fail("Exception expected"); + } catch (Throwable e) { + assertTrue(e.getMessage().contains("Reading bytes via IndexInput has failed.")); + } + } } public void testLoadIndex_nmslib_valid_with_stream() throws IOException { - Path tmpFile = createTempFile(); - - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ); - assertTrue(tmpFile.toFile().length() > 0); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - try (final Directory directory = new MMapDirectory(tmpFile.getParent())) { - try (IndexInput indexInput = directory.openInput(tmpFile.getFileName().toString(), IOContext.READONCE)) { + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); long pointer = JNIService.loadIndex( - new IndexInputWithBuffer(indexInput), + indexInputWithBuffer, ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), KNNEngine.NMSLIB ); assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); } } } - public void testLoadIndex_faiss_invalid_fileDoesNotExist() { - expectThrows(Exception.class, () -> JNIService.loadIndex("invalid", Collections.emptyMap(), KNNEngine.FAISS)); - } - - public void testLoadIndex_faiss_invalid_badFile() throws IOException { - - Path tmpFile = createTempFile(); - - expectThrows( - Exception.class, - () -> JNIService.loadIndex(tmpFile.toAbsolutePath().toString(), Collections.emptyMap(), KNNEngine.FAISS) - ); + public void testWriteIndex_nmslib_when_io_exception_occured() { + try { + final IndexOutput indexOutput = new RasingIOExceptionIndexOutput(); + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + indexOutputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + fail("Exception thrown is expected."); + } catch (Throwable e) { + assertTrue(e.getMessage().contains("Writing bytes via IndexOutput has failed.")); + } } public void testLoadIndex_faiss_valid() throws IOException { + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - Path tmpFile = createTempFile(); - - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); - - long pointer = JNIService.loadIndex(tmpFile.toAbsolutePath().toString(), Collections.emptyMap(), KNNEngine.FAISS); - assertNotEquals(0, pointer); + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + } + } } public void testQueryIndex_invalidEngine() { @@ -820,107 +1009,144 @@ public void testQueryIndex_nmslib_invalid_badPointer() { public void testQueryIndex_nmslib_invalid_nullQueryVector() throws IOException { - Path tmpFile = createTempFile(); - - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ); - assertTrue(tmpFile.toFile().length() > 0); - - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ); - assertNotEquals(0, pointer); - - expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, null, KNNEngine.NMSLIB, null, 0, null)); - } - - public void testQueryIndex_nmslib_valid() throws IOException { - - int k = 50; - for (SpaceType spaceType : NmslibHNSWMethod.SUPPORTED_SPACES) { - if (SpaceType.UNDEFINED == spaceType) { - continue; - } - - Path tmpFile = createTempFile(); - + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { TestUtils.createIndex( testData.indexData.docs, testData.loadDataToMemoryAddress(), testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.NMSLIB - ); - assertTrue(tmpFile.toFile().length() > 0); - - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), KNNEngine.NMSLIB ); - assertNotEquals(0, pointer); + assertTrue(directory.fileLength(indexFileName1) > 0); - for (float[] query : testData.queries) { - KNNQueryResult[] results = JNIService.queryIndex(pointer, query, k, null, KNNEngine.NMSLIB, null, 0, null); - assertEquals(k, results.length); + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; } + + expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, null, KNNEngine.NMSLIB, null, 0, null)); } } - public void testQueryIndex_faiss_invalid_badPointer() { + public void testQueryIndex_nmslib_valid() throws IOException { - expectThrows(Exception.class, () -> JNIService.queryIndex(0L, new float[] {}, 0, null, KNNEngine.FAISS, null, 0, null)); - } + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int k = 50; + for (SpaceType spaceType : NmslibHNSWMethod.SUPPORTED_SPACES) { + if (SpaceType.UNDEFINED == spaceType) { + continue; + } - public void testQueryIndex_faiss_invalid_nullQueryVector() throws IOException { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; - Path tmpFile = createTempFile(); + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.NMSLIB + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex(pointer, query, k, null, KNNEngine.NMSLIB, null, 0, null); + assertEquals(k, results.length); + } + } + } + } - long pointer = JNIService.loadIndex(tmpFile.toAbsolutePath().toString(), Collections.emptyMap(), KNNEngine.FAISS); - assertNotEquals(0, pointer); + public void testQueryIndex_faiss_invalid_badPointer() { - expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, null, KNNEngine.FAISS, null, 0, null)); + expectThrows(Exception.class, () -> JNIService.queryIndex(0L, new float[] {}, 0, null, KNNEngine.FAISS, null, 0, null)); } - public void testQueryIndex_faiss_streaming_invalid_nullQueryVector() throws IOException { - Path tmpFile = createTempFile(); + public void testQueryIndex_faiss_invalid_nullQueryVector() throws IOException { - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - try (final Directory directory = new MMapDirectory(tmpFile.getParent())) { - try (IndexInput indexInput = directory.openInput(tmpFile.getFileName().toString(), IOContext.READONCE)) { - long pointer = JNIService.loadIndex(new IndexInputWithBuffer(indexInput), Collections.emptyMap(), KNNEngine.FAISS); + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + + expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, null, KNNEngine.FAISS, null, 0, null)); + } + } + + public void testQueryIndex_faiss_streaming_invalid_nullQueryVector() throws IOException { + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, null, KNNEngine.FAISS, null, 0, null)); + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; } + + expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, null, KNNEngine.FAISS, null, 0, null)); } } @@ -929,55 +1155,66 @@ public void testQueryIndex_faiss_valid() throws IOException { int k = 10; int efSearch = 100; - List methods = ImmutableList.of(faissMethod); - List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); - for (String method : methods) { - for (SpaceType spaceType : spaces) { - Path tmpFile = createTempFile(); - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); - - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.FAISS - ); - assertNotEquals(0, pointer); - - for (float[] query : testData.queries) { - KNNQueryResult[] results = JNIService.queryIndex( - pointer, - query, - k, - Map.of("ef_search", efSearch), - KNNEngine.FAISS, - null, - 0, - null + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + List methods = ImmutableList.of(faissMethod); + List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + for (String method : methods) { + for (SpaceType spaceType : spaces) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS ); - assertEquals(k, results.length); - } + assertTrue(directory.fileLength(indexFileName1) > 0); - // Filter will result in no ids - for (float[] query : testData.queries) { - KNNQueryResult[] results = JNIService.queryIndex( - pointer, - query, - k, - Map.of("ef_search", efSearch), - KNNEngine.FAISS, - new long[] { 0 }, - 0, - null - ); - assertEquals(0, results.length); + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + null, + 0, + null + ); + assertEquals(k, results.length); + } + + // Filter will result in no ids + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + new long[] { 0 }, + 0, + null + ); + assertEquals(0, results.length); + } } } } @@ -987,23 +1224,25 @@ public void testQueryIndex_faiss_streaming_valid() throws IOException { int k = 10; int efSearch = 100; - List methods = ImmutableList.of(faissMethod); - List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); - for (String method : methods) { - for (SpaceType spaceType : spaces) { - Path tmpFile = createTempFile(); - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + List methods = ImmutableList.of(faissMethod); + List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + for (String method : methods) { + for (SpaceType spaceType : spaces) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - try (final Directory directory = new MMapDirectory(tmpFile.getParent())) { - try (IndexInput indexInput = directory.openInput(tmpFile.getFileName().toString(), IOContext.READONCE)) { + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.READONCE)) { long pointer = JNIService.loadIndex( new IndexInputWithBuffer(indexInput), ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), @@ -1040,9 +1279,9 @@ public void testQueryIndex_faiss_streaming_valid() throws IOException { assertEquals(0, results.length); } // End for } // End try - } // End try + } // End for } // End for - } // End for + } } public void testQueryIndex_faiss_parentIds() throws IOException { @@ -1050,44 +1289,55 @@ public void testQueryIndex_faiss_parentIds() throws IOException { int k = 100; int efSearch = 100; - List methods = ImmutableList.of(faissMethod); - List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); - int[] parentIds = toParentIdArray(testDataNested.indexData.docs); - Map idToParentIdMap = toIdToParentIdMap(testDataNested.indexData.docs); - for (String method : methods) { - for (SpaceType spaceType : spaces) { - Path tmpFile = createTempFile(); - TestUtils.createIndex( - testDataNested.indexData.docs, - testData.loadDataToMemoryAddress(), - testDataNested.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); - - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.FAISS - ); - assertNotEquals(0, pointer); - - for (float[] query : testDataNested.queries) { - KNNQueryResult[] results = JNIService.queryIndex( - pointer, - query, - k, - Map.of("ef_search", efSearch), - KNNEngine.FAISS, - null, - 0, - parentIds + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + List methods = ImmutableList.of(faissMethod); + List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + int[] parentIds = toParentIdArray(testDataNested.indexData.docs); + Map idToParentIdMap = toIdToParentIdMap(testDataNested.indexData.docs); + for (String method : methods) { + for (SpaceType spaceType : spaces) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testDataNested.indexData.docs, + testData.loadDataToMemoryAddress(), + testDataNested.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS ); - // Verify there is no more than one result from same parent - Set parentIdSet = toParentIdSet(results, idToParentIdMap); - assertEquals(results.length, parentIdSet.size()); + assertTrue(directory.fileLength(indexFileName1) > 0); + + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + + for (float[] query : testDataNested.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + null, + 0, + parentIds + ); + // Verify there is no more than one result from same parent + Set parentIdSet = toParentIdSet(results, idToParentIdMap); + assertEquals(results.length, parentIdSet.size()); + } } } } @@ -1098,25 +1348,27 @@ public void testQueryIndex_faiss_streaming_parentIds() throws IOException { int k = 100; int efSearch = 100; - List methods = ImmutableList.of(faissMethod); - List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); - int[] parentIds = toParentIdArray(testDataNested.indexData.docs); - Map idToParentIdMap = toIdToParentIdMap(testDataNested.indexData.docs); - for (String method : methods) { - for (SpaceType spaceType : spaces) { - Path tmpFile = createTempFile(); - TestUtils.createIndex( - testDataNested.indexData.docs, - testData.loadDataToMemoryAddress(), - testDataNested.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + List methods = ImmutableList.of(faissMethod); + List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + int[] parentIds = toParentIdArray(testDataNested.indexData.docs); + Map idToParentIdMap = toIdToParentIdMap(testDataNested.indexData.docs); + for (String method : methods) { + for (SpaceType spaceType : spaces) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testDataNested.indexData.docs, + testData.loadDataToMemoryAddress(), + testDataNested.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - try (final Directory directory = new MMapDirectory(tmpFile.getParent())) { - try (IndexInput indexInput = directory.openInput(tmpFile.getFileName().toString(), IOContext.READONCE)) { + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.READONCE)) { long pointer = JNIService.loadIndex( new IndexInputWithBuffer(indexInput), ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), @@ -1140,45 +1392,61 @@ public void testQueryIndex_faiss_streaming_parentIds() throws IOException { assertEquals(results.length, parentIdSet.size()); } // End for } // End try - } // End try + } // End for } // End for - } // End for + } } @SneakyThrows public void testQueryBinaryIndex_faiss_valid() { int k = 10; List methods = ImmutableList.of(faissBinaryMethod); - for (String method : methods) { - Path tmpFile = createTempFile(); - long memoryAddr = testData.loadBinaryDataToMemoryAddress(); - TestUtils.createIndex( - testData.indexData.docs, - memoryAddr, - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of( - INDEX_DESCRIPTION_PARAMETER, - method, - KNNConstants.SPACE_TYPE, - SpaceType.HAMMING.getValue(), - KNNConstants.VECTOR_DATA_TYPE_FIELD, - VectorDataType.BINARY.getValue() - ), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + for (String method : methods) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + long memoryAddr = testData.loadBinaryDataToMemoryAddress(); + TestUtils.createIndex( + testData.indexData.docs, + memoryAddr, + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + method, + KNNConstants.SPACE_TYPE, + SpaceType.HAMMING.getValue(), + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.VECTOR_DATA_TYPE_FIELD, VectorDataType.BINARY.getValue()), - KNNEngine.FAISS - ); - assertNotEquals(0, pointer); + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + method, + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - for (byte[] query : testData.binaryQueries) { - KNNQueryResult[] results = JNIService.queryBinaryIndex(pointer, query, k, null, KNNEngine.FAISS, null, 0, null); - assertEquals(k, results.length); + for (byte[] query : testData.binaryQueries) { + KNNQueryResult[] results = JNIService.queryBinaryIndex(pointer, query, k, null, KNNEngine.FAISS, null, 0, null); + assertEquals(k, results.length); + } } } } @@ -1187,28 +1455,30 @@ public void testQueryBinaryIndex_faiss_valid() { public void testQueryBinaryIndex_faiss_streaming_valid() { int k = 10; List methods = ImmutableList.of(faissBinaryMethod); - for (String method : methods) { - Path tmpFile = createTempFile(); - long memoryAddr = testData.loadBinaryDataToMemoryAddress(); - TestUtils.createIndex( - testData.indexData.docs, - memoryAddr, - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of( - INDEX_DESCRIPTION_PARAMETER, - method, - KNNConstants.SPACE_TYPE, - SpaceType.HAMMING.getValue(), - KNNConstants.VECTOR_DATA_TYPE_FIELD, - VectorDataType.BINARY.getValue() - ), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + for (String method : methods) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + long memoryAddr = testData.loadBinaryDataToMemoryAddress(); + TestUtils.createIndex( + testData.indexData.docs, + memoryAddr, + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + method, + KNNConstants.SPACE_TYPE, + SpaceType.HAMMING.getValue(), + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - try (final Directory directory = new MMapDirectory(tmpFile.getParent())) { - try (IndexInput indexInput = directory.openInput(tmpFile.getFileName().toString(), IOContext.READONCE)) { + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.READONCE)) { long pointer = JNIService.loadIndex( new IndexInputWithBuffer(indexInput), ImmutableMap.of( @@ -1226,8 +1496,8 @@ public void testQueryBinaryIndex_faiss_streaming_valid() { assertEquals(k, results.length); } // End for } // End try - } // End try - } // End for + } // End for + } // End try } private Set toParentIdSet(KNNQueryResult[] results, Map idToParentIdMap) { @@ -1276,46 +1546,66 @@ public void testFree_invalidEngine() { public void testFree_nmslib_valid() throws IOException { - Path tmpFile = createTempFile(); - - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ); - assertTrue(tmpFile.toFile().length() > 0); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB - ); - assertNotEquals(0, pointer); + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - JNIService.free(pointer, KNNEngine.NMSLIB); + JNIService.free(pointer, KNNEngine.NMSLIB); + } } public void testFree_faiss_valid() throws IOException { - Path tmpFile = createTempFile(); - - TestUtils.createIndex( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - long pointer = JNIService.loadIndex(tmpFile.toAbsolutePath().toString(), Collections.emptyMap(), KNNEngine.FAISS); - assertNotEquals(0, pointer); + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - JNIService.free(pointer, KNNEngine.FAISS); + JNIService.free(pointer, KNNEngine.FAISS); + } } public void testTransferVectors() { @@ -1492,20 +1782,71 @@ public void createIndexFromTemplate() throws IOException { assertNotEquals(0, faissIndex.length); JNICommons.freeVectorData(trainPointer1); - Path tmpFile1 = createTempFile(); - JNIService.createIndexFromTemplate( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile1.toAbsolutePath().toString(), - faissIndex, - ImmutableMap.of(INDEX_THREAD_QTY, 1), - KNNEngine.FAISS + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + try (IndexOutput indexOutput = directory.createOutput(indexFileName1, IOContext.DEFAULT)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.createIndexFromTemplate( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + indexOutputWithBuffer, + faissIndex, + ImmutableMap.of(INDEX_THREAD_QTY, 1), + KNNEngine.FAISS + ); + } + assertTrue(directory.fileLength(indexFileName1) > 0); + + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + } + } + + @SneakyThrows + public void testCreateIndex_whenIOExceptionOccured() { + // Plain index + Map parameters = new HashMap<>( + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()) ); - assertTrue(tmpFile1.toFile().length() > 0); - long pointer = JNIService.loadIndex(tmpFile1.toAbsolutePath().toString(), Collections.emptyMap(), KNNEngine.FAISS); - assertNotEquals(0, pointer); + long trainPointer = JNIService.transferVectors(0, testData.indexData.vectors); + assertNotEquals(0, trainPointer); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .dimension(128) + .vectorDataType(VectorDataType.FLOAT) + .build(); + + byte[] faissIndex = JNIService.trainIndex(parameters, 128, trainPointer, KNNEngine.FAISS); + + assertNotEquals(0, faissIndex.length); + JNICommons.freeVectorData(trainPointer); + + final IndexOutput indexOutput = new RasingIOExceptionIndexOutput(); + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + try { + JNIService.createIndexFromTemplate( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + indexOutputWithBuffer, + faissIndex, + ImmutableMap.of(INDEX_THREAD_QTY, 1), + KNNEngine.FAISS + ); + fail("Exception thrown was expected"); + } catch (Throwable t) { + System.out.println("!!!!!!!!!!!!!!!!!!!!! " + t.getMessage()); + } } @SneakyThrows @@ -1516,35 +1857,58 @@ public void testIndexLoad_whenStateIsShared_thenSucceed() { int ivfNlist = 16; int pqM = 16; int pqCodeSize = 4; + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + String indexIVFPQPath = createFaissIVFPQIndex(directory, ivfNlist, pqM, pqCodeSize, SpaceType.L2); + + final long indexIVFPQIndexTest1; + try (IndexInput indexInput = directory.openInput(indexIVFPQPath, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + indexIVFPQIndexTest1 = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, indexIVFPQIndexTest1); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + final long indexIVFPQIndexTest2; + try (IndexInput indexInput = directory.openInput(indexIVFPQPath, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + indexIVFPQIndexTest2 = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, indexIVFPQIndexTest2); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - String indexIVFPQPath = createFaissIVFPQIndex(ivfNlist, pqM, pqCodeSize, SpaceType.L2); - - long indexIVFPQIndexTest1 = JNIService.loadIndex(indexIVFPQPath, Collections.emptyMap(), KNNEngine.FAISS); - assertNotEquals(0, indexIVFPQIndexTest1); - long indexIVFPQIndexTest2 = JNIService.loadIndex(indexIVFPQPath, Collections.emptyMap(), KNNEngine.FAISS); - assertNotEquals(0, indexIVFPQIndexTest2); - - long sharedStateAddress = JNIService.initSharedIndexState(indexIVFPQIndexTest1, KNNEngine.FAISS); - JNIService.setSharedIndexState(indexIVFPQIndexTest1, sharedStateAddress, KNNEngine.FAISS); - JNIService.setSharedIndexState(indexIVFPQIndexTest2, sharedStateAddress, KNNEngine.FAISS); + long sharedStateAddress = JNIService.initSharedIndexState(indexIVFPQIndexTest1, KNNEngine.FAISS); + JNIService.setSharedIndexState(indexIVFPQIndexTest1, sharedStateAddress, KNNEngine.FAISS); + JNIService.setSharedIndexState(indexIVFPQIndexTest2, sharedStateAddress, KNNEngine.FAISS); - assertQueryResultsMatch(testData.queries, k, List.of(indexIVFPQIndexTest1, indexIVFPQIndexTest2)); + assertQueryResultsMatch(testData.queries, k, List.of(indexIVFPQIndexTest1, indexIVFPQIndexTest2)); - // Free the first test index 1. This will ensure that the shared state persists after index that initialized - // shared state is gone. - JNIService.free(indexIVFPQIndexTest1, KNNEngine.FAISS); + // Free the first test index 1. This will ensure that the shared state persists after index that initialized + // shared state is gone. + JNIService.free(indexIVFPQIndexTest1, KNNEngine.FAISS); - long indexIVFPQIndexTest3 = JNIService.loadIndex(indexIVFPQPath, Collections.emptyMap(), KNNEngine.FAISS); - assertNotEquals(0, indexIVFPQIndexTest3); + final long indexIVFPQIndexTest3; + try (IndexInput indexInput = directory.openInput(indexIVFPQPath, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + indexIVFPQIndexTest3 = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, indexIVFPQIndexTest3); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - JNIService.setSharedIndexState(indexIVFPQIndexTest3, sharedStateAddress, KNNEngine.FAISS); + JNIService.setSharedIndexState(indexIVFPQIndexTest3, sharedStateAddress, KNNEngine.FAISS); - assertQueryResultsMatch(testData.queries, k, List.of(indexIVFPQIndexTest2, indexIVFPQIndexTest3)); + assertQueryResultsMatch(testData.queries, k, List.of(indexIVFPQIndexTest2, indexIVFPQIndexTest3)); - // Ensure everything gets freed - JNIService.free(indexIVFPQIndexTest2, KNNEngine.FAISS); - JNIService.free(indexIVFPQIndexTest3, KNNEngine.FAISS); - JNIService.freeSharedIndexState(sharedStateAddress, KNNEngine.FAISS); + // Ensure everything gets freed + JNIService.free(indexIVFPQIndexTest2, KNNEngine.FAISS); + JNIService.free(indexIVFPQIndexTest3, KNNEngine.FAISS); + JNIService.freeSharedIndexState(sharedStateAddress, KNNEngine.FAISS); + } } @SneakyThrows @@ -1552,20 +1916,32 @@ public void testIsIndexIVFPQL2() { long dummyAddress = 0; assertFalse(JNIService.isSharedIndexStateRequired(dummyAddress, KNNEngine.NMSLIB)); - String faissIVFPQL2Index = createFaissIVFPQIndex(16, 16, 4, SpaceType.L2); - long faissIVFPQL2Address = JNIService.loadIndex(faissIVFPQL2Index, Collections.emptyMap(), KNNEngine.FAISS); - assertTrue(JNIService.isSharedIndexStateRequired(faissIVFPQL2Address, KNNEngine.FAISS)); - JNIService.free(faissIVFPQL2Address, KNNEngine.FAISS); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + String faissIVFPQL2Index = createFaissIVFPQIndex(directory, 16, 16, 4, SpaceType.L2); + try (IndexInput indexInput = directory.openInput(faissIVFPQL2Index, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long faissIVFPQL2Address = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertTrue(JNIService.isSharedIndexStateRequired(faissIVFPQL2Address, KNNEngine.FAISS)); + JNIService.free(faissIVFPQL2Address, KNNEngine.FAISS); + } - String faissIVFPQIPIndex = createFaissIVFPQIndex(16, 16, 4, SpaceType.INNER_PRODUCT); - long faissIVFPQIPAddress = JNIService.loadIndex(faissIVFPQIPIndex, Collections.emptyMap(), KNNEngine.FAISS); - assertFalse(JNIService.isSharedIndexStateRequired(faissIVFPQIPAddress, KNNEngine.FAISS)); - JNIService.free(faissIVFPQIPAddress, KNNEngine.FAISS); + String faissIVFPQIPIndex = createFaissIVFPQIndex(directory, 16, 16, 4, SpaceType.INNER_PRODUCT); + try (IndexInput indexInput = directory.openInput(faissIVFPQIPIndex, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long faissIVFPQIPAddress = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertFalse(JNIService.isSharedIndexStateRequired(faissIVFPQIPAddress, KNNEngine.FAISS)); + JNIService.free(faissIVFPQIPAddress, KNNEngine.FAISS); + } - String faissHNSWIndex = createFaissHNSWIndex(SpaceType.L2); - long faissHNSWAddress = JNIService.loadIndex(faissHNSWIndex, Collections.emptyMap(), KNNEngine.FAISS); - assertFalse(JNIService.isSharedIndexStateRequired(faissHNSWAddress, KNNEngine.FAISS)); - JNIService.free(faissHNSWAddress, KNNEngine.FAISS); + String faissHNSWIndex = createFaissHNSWIndex(directory, SpaceType.L2); + try (IndexInput indexInput = directory.openInput(faissHNSWIndex, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long faissHNSWAddress = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertFalse(JNIService.isSharedIndexStateRequired(faissHNSWAddress, KNNEngine.FAISS)); + JNIService.free(faissHNSWAddress, KNNEngine.FAISS); + } + } } @SneakyThrows @@ -1594,7 +1970,8 @@ private void assertQueryResultsMatch(float[][] testQueries, int k, List in } } - private String createFaissIVFPQIndex(int ivfNlist, int pqM, int pqCodeSize, SpaceType spaceType) throws IOException { + private String createFaissIVFPQIndex(Directory directory, int ivfNlist, int pqM, int pqCodeSize, SpaceType spaceType) + throws IOException { long trainPointer = JNIService.transferVectors(0, testData.indexData.vectors); assertNotEquals(0, trainPointer); KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() @@ -1635,32 +2012,36 @@ private String createFaissIVFPQIndex(int ivfNlist, int pqM, int pqCodeSize, Spac assertNotEquals(0, faissIndex.length); JNICommons.freeVectorData(trainPointer); - Path tmpFile = createTempFile(); - JNIService.createIndexFromTemplate( - testData.indexData.docs, - testData.loadDataToMemoryAddress(), - testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), - faissIndex, - ImmutableMap.of(INDEX_THREAD_QTY, 1), - KNNEngine.FAISS - ); - assertTrue(tmpFile.toFile().length() > 0); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (IndexOutput indexOutput = directory.createOutput(indexFileName1, IOContext.DEFAULT)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.createIndexFromTemplate( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + indexOutputWithBuffer, + faissIndex, + ImmutableMap.of(INDEX_THREAD_QTY, 1), + KNNEngine.FAISS + ); + } + assertTrue(directory.fileLength(indexFileName1) > 0); - return tmpFile.toAbsolutePath().toString(); + return indexFileName1; } - private String createFaissHNSWIndex(SpaceType spaceType) throws IOException { - Path tmpFile = createTempFile(); + private String createFaissHNSWIndex(Directory directory, SpaceType spaceType) throws IOException { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; TestUtils.createIndex( testData.indexData.docs, testData.loadDataToMemoryAddress(), testData.indexData.getDimension(), - tmpFile.toAbsolutePath().toString(), + directory, + indexFileName1, ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, spaceType.getValue()), KNNEngine.FAISS ); - assertTrue(tmpFile.toFile().length() > 0); - return tmpFile.toAbsolutePath().toString(); + assertTrue(directory.fileLength(indexFileName1) > 0); + return indexFileName1; } } diff --git a/src/test/java/org/opensearch/knn/training/TrainingJobTests.java b/src/test/java/org/opensearch/knn/training/TrainingJobTests.java index 14308b915..4706bd000 100644 --- a/src/test/java/org/opensearch/knn/training/TrainingJobTests.java +++ b/src/test/java/org/opensearch/knn/training/TrainingJobTests.java @@ -12,6 +12,9 @@ package org.opensearch.knn.training; import com.google.common.collect.ImmutableMap; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexOutput; import org.opensearch.Version; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.knn.KNNTestCase; @@ -26,15 +29,16 @@ import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.memory.NativeMemoryEntryContext; import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import org.opensearch.knn.indices.Model; import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.knn.indices.ModelState; import org.opensearch.knn.jni.JNICommons; import org.opensearch.knn.jni.JNIService; -import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.util.UUID; import java.util.concurrent.ExecutionException; import static org.mockito.Mockito.doAnswer; @@ -221,17 +225,23 @@ public void testRun_success() throws IOException, ExecutionException { float[][] vectors = new float[ids.length][dimension]; fillFloatArrayRandomly(vectors); long vectorsMemoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); - Path indexPath = createTempFile(); - JNIService.createIndexFromTemplate( - ids, - vectorsMemoryAddress, - vectors[0].length, - indexPath.toString(), - model.getModelBlob(), - ImmutableMap.of(INDEX_THREAD_QTY, 1), - knnEngine - ); - assertNotEquals(0, new File(indexPath.toString()).length()); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + try (IndexOutput indexOutput = directory.createOutput(indexFileName1, IOContext.DEFAULT)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.createIndexFromTemplate( + ids, + vectorsMemoryAddress, + vectors[0].length, + indexOutputWithBuffer, + model.getModelBlob(), + ImmutableMap.of(INDEX_THREAD_QTY, 1), + knnEngine + ); + } + assertTrue(directory.fileLength(indexFileName1) > 0); + } } public void testRun_failure_onGetTrainingDataAllocation() throws ExecutionException { diff --git a/src/testFixtures/java/org/opensearch/knn/TestUtils.java b/src/testFixtures/java/org/opensearch/knn/TestUtils.java index ba3aaca7a..6fd584aee 100644 --- a/src/testFixtures/java/org/opensearch/knn/TestUtils.java +++ b/src/testFixtures/java/org/opensearch/knn/TestUtils.java @@ -9,6 +9,9 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexOutput; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.core.common.bytes.BytesArray; import org.opensearch.core.xcontent.DeprecationHandler; @@ -20,6 +23,7 @@ import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.codec.util.SerializationMode; import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import org.opensearch.knn.jni.JNICommons; import org.opensearch.knn.jni.JNIService; import org.opensearch.knn.plugin.script.KNNScoringUtil; @@ -422,14 +426,32 @@ public static class Pair { } } - public static void createIndex(int[] ids, long address, int dimension, String name, Map parameters, KNNEngine engine) { + public static void createIndex( + int[] ids, + long address, + int dimension, + Directory directory, + String fileName, + Map parameters, + KNNEngine engine + ) { if (engine != KNNEngine.FAISS) { - JNIService.createIndex(ids, address, dimension, name, parameters, engine); + try (IndexOutput indexOutput = directory.createOutput(fileName, IOContext.DEFAULT)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.createIndex(ids, address, dimension, indexOutputWithBuffer, parameters, engine); + } catch (IOException e) { + throw new RuntimeException(e); + } } else { // We can initialize numDocs as 0, this will just not reserve anything. long indexAddress = JNIService.initIndex(0, dimension, parameters, engine); JNIService.insertToIndex(ids, address, dimension, parameters, indexAddress, engine); - JNIService.writeIndex(name, indexAddress, engine, parameters); + try (IndexOutput indexOutput = directory.createOutput(fileName, IOContext.DEFAULT)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.writeIndex(indexOutputWithBuffer, indexAddress, engine, parameters); + } catch (IOException e) { + throw new RuntimeException(e); + } } } } From 3554ebf44d69715a34c94cf65edc4916b6e989b2 Mon Sep 17 00:00:00 2001 From: Kunal Kotwani Date: Thu, 7 Nov 2024 11:02:32 -0800 Subject: [PATCH 54/59] Fix backport branch deletion on merge (#2254) Signed-off-by: Kunal Kotwani --- .github/workflows/delete_backport_branch.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/delete_backport_branch.yml b/.github/workflows/delete_backport_branch.yml index d654df6b4..a4d186ca7 100644 --- a/.github/workflows/delete_backport_branch.yml +++ b/.github/workflows/delete_backport_branch.yml @@ -7,9 +7,16 @@ on: jobs: delete-branch: runs-on: ubuntu-latest - if: startsWith(github.event.pull_request.head.ref,'backport/') + permissions: + contents: write + if: github.repository == 'opensearch-project/k-NN' && startsWith(github.event.pull_request.head.ref,'backport/') steps: - - name: Delete merged branch - uses: SvanBoxel/delete-merged-branch@main - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + - name: Delete merged branch + uses: actions/github-script@v7 + with: + script: | + github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${context.payload.pull_request.head.ref}`, + }) From 7d3445631591296d00c37ea16351a07ca08ffbd3 Mon Sep 17 00:00:00 2001 From: Vijayan Balasubramanian Date: Thu, 7 Nov 2024 11:40:54 -0800 Subject: [PATCH 55/59] Update default engine to FAISS (#2221) * Update default engine to FAISS Since faiss supports more features than nmslib, and, we had seen data points that there are more number of vector search users are interesed in faiss, we will be updating default engine to be faiss. This will benefit users who preffered to use defaults while working with vector search. Signed-off-by: Vijayan Balasubramanian * Update legacy mapping Signed-off-by: Vijayan Balasubramanian * Create legacy mapping only up to V_2_17_2 Signed-off-by: Vijayan Balasubramanian * Update test engine Signed-off-by: Vijayan Balasubramanian * Update test method Signed-off-by: Vijayan Balasubramanian --------- Signed-off-by: Vijayan Balasubramanian --- .../org/opensearch/knn/bwc/IndexingIT.java | 17 ++++ .../opensearch-knn.release-notes-2.18.0.0.md | 1 + .../knn/index/engine/EngineResolver.java | 2 +- .../knn/index/engine/KNNEngine.java | 2 +- .../index/mapper/KNNVectorFieldMapper.java | 9 +- .../opensearch/knn/index/query/KNNWeight.java | 2 +- .../opensearch/knn/KNNSingleNodeTestCase.java | 18 ++++ .../knn/index/KNNIndexShardTests.java | 3 +- .../org/opensearch/knn/index/NmslibIT.java | 5 ++ .../knn/index/VectorDataTypeIT.java | 23 ----- .../KNN80DocValuesConsumerTests.java | 1 + .../knn/index/codec/KNNCodecTestCase.java | 2 +- .../knn/index/engine/EngineResolverTests.java | 6 +- .../knn/index/engine/KNNEngineTests.java | 4 + .../mapper/KNNVectorFieldMapperTests.java | 85 ++++++++++--------- .../knn/index/query/KNNWeightTests.java | 9 +- .../opensearch/knn/jni/JNIServiceTests.java | 2 +- .../transport/GetModelResponseTests.java | 9 +- .../transport/TrainingModelRequestTests.java | 6 +- 19 files changed, 129 insertions(+), 77 deletions(-) diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java index 7d2b3807a..b212d844f 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java @@ -61,6 +61,23 @@ public void testKNNIndexDefaultLegacyFieldMapping() throws Exception { } } + // ensure that index is created using legacy mapping in old cluster, and, then, add docs to both old and new cluster. + // when search is requested on new cluster it should return all docs irrespective of cluster. + public void testKNNIndexDefaultEngine() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + if (isRunningAgainstOldCluster()) { + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, 5); + // Flush to ensure that index is not re-indexed when node comes back up + flush(testIndex, true); + } else { + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, 5, 5); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 5, 5); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, 10, 10); + deleteKNNIndex(testIndex); + } + } + // Ensure that when segments created with old mapping are forcemerged in new cluster, they // succeed public void testKNNIndexDefaultLegacyFieldMappingForceMerge() throws Exception { diff --git a/release-notes/opensearch-knn.release-notes-2.18.0.0.md b/release-notes/opensearch-knn.release-notes-2.18.0.0.md index 9e85fd9ca..844605a8e 100644 --- a/release-notes/opensearch-knn.release-notes-2.18.0.0.md +++ b/release-notes/opensearch-knn.release-notes-2.18.0.0.md @@ -15,6 +15,7 @@ Compatible with OpenSearch 2.18.0 * Add CompressionLevel Calculation for PQ [#2200](https://github.com/opensearch-project/k-NN/pull/2200) * Remove FSDirectory dependency from native engine constructing side and deprecated FileWatcher [#2182](https://github.com/opensearch-project/k-NN/pull/2182) * Update approximate_threshold to 15K documents [#2229](https://github.com/opensearch-project/k-NN/pull/2229) +* Update default engine to FAISS [#2221](https://github.com/opensearch-project/k-NN/pull/2221) ### Bug Fixes * Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) * KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) diff --git a/src/main/java/org/opensearch/knn/index/engine/EngineResolver.java b/src/main/java/org/opensearch/knn/index/engine/EngineResolver.java index daae361e4..d52c21c4c 100644 --- a/src/main/java/org/opensearch/knn/index/engine/EngineResolver.java +++ b/src/main/java/org/opensearch/knn/index/engine/EngineResolver.java @@ -49,7 +49,7 @@ public KNNEngine resolveEngine( // For 1x, we need to default to faiss if mode is provided and use nmslib otherwise if (CompressionLevel.isConfigured(compressionLevel) == false || compressionLevel == CompressionLevel.x1) { - return mode == Mode.ON_DISK ? KNNEngine.FAISS : KNNEngine.DEFAULT; + return mode == Mode.ON_DISK ? KNNEngine.FAISS : KNNEngine.NMSLIB; } // Lucene is only engine that supports 4x - so we have to default to it here. diff --git a/src/main/java/org/opensearch/knn/index/engine/KNNEngine.java b/src/main/java/org/opensearch/knn/index/engine/KNNEngine.java index 80b9f32a6..1e560a11b 100644 --- a/src/main/java/org/opensearch/knn/index/engine/KNNEngine.java +++ b/src/main/java/org/opensearch/knn/index/engine/KNNEngine.java @@ -29,7 +29,7 @@ public enum KNNEngine implements KNNLibrary { FAISS(FAISS_NAME, Faiss.INSTANCE), LUCENE(LUCENE_NAME, Lucene.INSTANCE); - public static final KNNEngine DEFAULT = NMSLIB; + public static final KNNEngine DEFAULT = FAISS; private static final Set CUSTOM_SEGMENT_FILE_ENGINES = ImmutableSet.of(KNNEngine.NMSLIB, KNNEngine.FAISS); private static final Set ENGINES_SUPPORTING_FILTERS = ImmutableSet.of(KNNEngine.LUCENE, KNNEngine.FAISS); diff --git a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java index 18d4f7b64..beceadde5 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java +++ b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java @@ -500,8 +500,7 @@ private void resolveKNNMethodComponents( .build() ); - // If the original parameters are from legacy - if (builder.originalParameters.isLegacyMapping()) { + if (useKNNMethodContextFromLegacy(builder, parserContext)) { // Then create KNNMethodContext to be used from the legacy index settings builder.originalParameters.setResolvedKnnMethodContext( createKNNMethodContextFromLegacy(parserContext.getSettings(), parserContext.indexVersionCreated(), resolvedSpaceType) @@ -550,6 +549,12 @@ private void setEngine(final KNNMethodContext knnMethodContext, KNNEngine knnEng } } + static boolean useKNNMethodContextFromLegacy(Builder builder, Mapper.TypeParser.ParserContext parserContext) { + // If the original parameters are from legacy, and it is created on or before 2_17_2 since default is changed to + // FAISS starting 2_18, which doesn't support accepting algo params from index settings + return parserContext.indexVersionCreated().onOrBefore(Version.V_2_17_2) && builder.originalParameters.isLegacyMapping(); + } + // We store the version of the index with the mapper as different version of Opensearch has different default // values of KNN engine Algorithms hyperparameters. protected Version indexCreatedVersion; diff --git a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java index b5b2a5d22..04c2ce587 100644 --- a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java +++ b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java @@ -251,7 +251,7 @@ private Map doANNSearch( spaceType = modelMetadata.getSpaceType(); vectorDataType = modelMetadata.getVectorDataType(); } else { - String engineName = fieldInfo.attributes().getOrDefault(KNN_ENGINE, KNNEngine.NMSLIB.getName()); + String engineName = fieldInfo.attributes().getOrDefault(KNN_ENGINE, KNNEngine.DEFAULT.getName()); knnEngine = KNNEngine.getEngine(engineName); String spaceTypeName = fieldInfo.attributes().getOrDefault(SPACE_TYPE, SpaceType.L2.getValue()); spaceType = SpaceType.getSpace(spaceTypeName); diff --git a/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java b/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java index 587e80e5d..7bfce5b94 100644 --- a/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java +++ b/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java @@ -16,6 +16,7 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.query.KNNQueryBuilder; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.memory.NativeMemoryLoadStrategy; @@ -50,6 +51,7 @@ import static org.mockito.Mockito.when; import static org.opensearch.knn.common.KNNConstants.DIMENSION; import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; import static org.opensearch.knn.common.KNNConstants.MODEL_BLOB_PARAMETER; import static org.opensearch.knn.common.KNNConstants.MODEL_DESCRIPTION; @@ -109,6 +111,22 @@ protected void createKnnIndexMapping(String indexName, String fieldName, Integer /** * Create simple k-NN mapping with engine */ + protected void createKnnIndexMapping(String indexName, String fieldName, Integer dimensions, KNNEngine engine) throws IOException { + PutMappingRequest request = new PutMappingRequest(indexName); + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("properties"); + xContentBuilder.startObject(fieldName); + xContentBuilder.field("type", "knn_vector").field("dimension", dimensions.toString()); + xContentBuilder.startObject("method"); + xContentBuilder.field("name", METHOD_HNSW); + xContentBuilder.field(KNN_ENGINE, engine.getName()); + xContentBuilder.endObject(); + xContentBuilder.endObject(); + xContentBuilder.endObject(); + xContentBuilder.endObject(); + request.source(xContentBuilder); + OpenSearchAssertions.assertAcked(client().admin().indices().putMapping(request).actionGet()); + } + protected void updateIndexSetting(String indexName, Settings setting) { UpdateSettingsRequest request = new UpdateSettingsRequest(setting, indexName); OpenSearchAssertions.assertAcked(client().admin().indices().updateSettings(request).actionGet()); diff --git a/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java b/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java index c25d2a390..d01c3a0b4 100644 --- a/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java +++ b/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java @@ -19,6 +19,7 @@ import org.opensearch.index.IndexService; import org.opensearch.index.engine.Engine; import org.opensearch.index.shard.IndexShard; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import java.io.IOException; @@ -108,7 +109,7 @@ public void testWarmup_shardNotPresentInCache() throws InterruptedException, Exe public void testGetAllEngineFileContexts() throws IOException, ExecutionException, InterruptedException { IndexService indexService = createKNNIndex(testIndexName); - createKnnIndexMapping(testIndexName, testFieldName, dimensions); + createKnnIndexMapping(testIndexName, testFieldName, dimensions, KNNEngine.NMSLIB); updateIndexSetting(testIndexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0).build()); IndexShard indexShard = indexService.iterator().next(); diff --git a/src/test/java/org/opensearch/knn/index/NmslibIT.java b/src/test/java/org/opensearch/knn/index/NmslibIT.java index b342fdf3f..8ca436bf4 100644 --- a/src/test/java/org/opensearch/knn/index/NmslibIT.java +++ b/src/test/java/org/opensearch/knn/index/NmslibIT.java @@ -36,6 +36,7 @@ import java.util.TreeMap; import static org.hamcrest.Matchers.containsString; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; public class NmslibIT extends KNNRestTestCase { @@ -281,6 +282,7 @@ public void testCreateIndexWithValidAlgoParams_mapping() { .field("dimension", 2) .startObject(KNNConstants.KNN_METHOD) .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType) + .field(KNN_ENGINE, KNNEngine.NMSLIB.getName()) .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) .startObject(KNNConstants.PARAMETERS) .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) @@ -336,6 +338,7 @@ public void testCreateIndexWithValidAlgoParams_mappingAndSettings() { .field("dimension", 2) .startObject(KNNConstants.KNN_METHOD) .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType1) + .field(KNN_ENGINE, KNNEngine.NMSLIB.getName()) .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) .startObject(KNNConstants.PARAMETERS) .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction1) @@ -363,6 +366,7 @@ public void testCreateIndexWithValidAlgoParams_mappingAndSettings() { .field("dimension", 2) .startObject(KNNConstants.KNN_METHOD) .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType1) + .field(KNN_ENGINE, KNNEngine.NMSLIB.getName()) .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) .startObject(KNNConstants.PARAMETERS) .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction1) @@ -375,6 +379,7 @@ public void testCreateIndexWithValidAlgoParams_mappingAndSettings() { .field("dimension", 2) .startObject(KNNConstants.KNN_METHOD) .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType2) + .field(KNN_ENGINE, KNNEngine.NMSLIB.getName()) .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) .startObject(KNNConstants.PARAMETERS) .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction2) diff --git a/src/test/java/org/opensearch/knn/index/VectorDataTypeIT.java b/src/test/java/org/opensearch/knn/index/VectorDataTypeIT.java index 959d94e3e..6e0f954a7 100644 --- a/src/test/java/org/opensearch/knn/index/VectorDataTypeIT.java +++ b/src/test/java/org/opensearch/knn/index/VectorDataTypeIT.java @@ -263,29 +263,6 @@ public void testByteVectorDataTypeWithNmslibEngine() { assertTrue(ex.getMessage().contains("is not supported for vector data type")); } - @SneakyThrows - public void testByteVectorDataTypeWithLegacyFieldMapperKnnIndexSetting() { - // Create an index with byte vector data_type and index.knn as true without setting KnnMethodContext, - // which should throw an exception because the LegacyFieldMapper will use NMSLIB engine and byte data_type - // is not supported for NMSLIB engine. - XContentBuilder builder = XContentFactory.jsonBuilder() - .startObject() - .startObject(PROPERTIES_FIELD) - .startObject(FIELD_NAME) - .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) - .field(DIMENSION, 2) - .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.BYTE.getValue()) - .endObject() - .endObject() - .endObject(); - - String mapping = builder.toString(); - - ResponseException ex = expectThrows(ResponseException.class, () -> createKnnIndex(INDEX_NAME, mapping)); - assertTrue(ex.getMessage(), ex.getMessage().contains("is not supported for vector data type")); - - } - public void testDocValuesWithByteVectorDataTypeLuceneEngine() throws Exception { createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); ingestL2ByteTestData(); diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumerTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumerTests.java index e1f16006d..c11bc765f 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumerTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumerTests.java @@ -279,6 +279,7 @@ public void testAddKNNBinaryField_fromScratch_nmslibLegacy() throws IOException .addAttribute(KNNConstants.HNSW_ALGO_EF_CONSTRUCTION, "512") .addAttribute(KNNConstants.HNSW_ALGO_M, "16") .addAttribute(KNNConstants.SPACE_TYPE, spaceType.getValue()) + .addAttribute(KNNConstants.KNN_ENGINE, knnEngine.getName()) .build() }; FieldInfos fieldInfos = new FieldInfos(fieldInfoArray); diff --git a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java index 2036e14aa..315693a65 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java @@ -103,7 +103,7 @@ public class KNNCodecTestCase extends KNNTestCase { .vectorDataType(VectorDataType.DEFAULT) .build(); KNNMethodContext knnMethodContext = new KNNMethodContext( - KNNEngine.DEFAULT, + KNNEngine.NMSLIB, SpaceType.DEFAULT, new MethodComponentContext(METHOD_HNSW, ImmutableMap.of(METHOD_PARAMETER_M, 16, METHOD_PARAMETER_EF_CONSTRUCTION, 512)) ); diff --git a/src/test/java/org/opensearch/knn/index/engine/EngineResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/EngineResolverTests.java index df195883a..291f0c671 100644 --- a/src/test/java/org/opensearch/knn/index/engine/EngineResolverTests.java +++ b/src/test/java/org/opensearch/knn/index/engine/EngineResolverTests.java @@ -41,10 +41,10 @@ public void testResolveEngine_whenModeAndCompressionAreFalse_thenDefault() { ); } - public void testResolveEngine_whenModeSpecifiedAndCompressionIsNotSpecified_thenDefault() { + public void testResolveEngine_whenModeSpecifiedAndCompressionIsNotSpecified_thenNMSLIB() { assertEquals(KNNEngine.DEFAULT, ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().build(), null, false)); assertEquals( - KNNEngine.DEFAULT, + KNNEngine.NMSLIB, ENGINE_RESOLVER.resolveEngine( KNNMethodConfigContext.builder().mode(Mode.IN_MEMORY).build(), new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY, false), @@ -63,7 +63,7 @@ public void testResolveEngine_whenCompressionIs1x_thenEngineBasedOnMode() { ) ); assertEquals( - KNNEngine.DEFAULT, + KNNEngine.NMSLIB, ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().compressionLevel(CompressionLevel.x1).build(), null, false) ); } diff --git a/src/test/java/org/opensearch/knn/index/engine/KNNEngineTests.java b/src/test/java/org/opensearch/knn/index/engine/KNNEngineTests.java index 5a6ed8e52..3f356999c 100644 --- a/src/test/java/org/opensearch/knn/index/engine/KNNEngineTests.java +++ b/src/test/java/org/opensearch/knn/index/engine/KNNEngineTests.java @@ -25,6 +25,10 @@ public void testDelegateLibraryFunctions() { assertEquals(Lucene.INSTANCE.getVersion(), KNNEngine.LUCENE.getVersion()); } + public void testGetDefaultEngine_thenReturnFAISS() { + assertEquals(KNNEngine.FAISS, KNNEngine.DEFAULT); + } + /** * Test name getter */ diff --git a/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java b/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java index 98bbf42ca..714723a8e 100644 --- a/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java +++ b/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java @@ -65,6 +65,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -146,7 +147,7 @@ public void testTypeParser_build_fromKnnMethodContext() throws IOException { // Check that knnMethodContext takes precedent over both model and legacy ModelDao modelDao = mock(ModelDao.class); - SpaceType spaceType = SpaceType.COSINESIMIL; + SpaceType spaceType = SpaceType.DEFAULT; int mRight = 17; int mWrong = 71; @@ -284,36 +285,43 @@ public void testTypeParser_withDifferentSpaceTypeCombinations_thenSuccess() thro ); assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isEmpty()); - // if space type is provided and legacy mappings is hit - xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) - .field(DIMENSION_FIELD_NAME, TEST_DIMENSION) - .field(KNNConstants.TOP_LEVEL_PARAMETER_SPACE_TYPE, topLevelSpaceType.getValue()) - .endObject(); - builder = (KNNVectorFieldMapper.Builder) typeParser.parse( - "test-field-name-1", - xContentBuilderToMap(xContentBuilder), - buildParserContext("test", settings) - ); + // mock useKNNMethodContextFromLegacy to simulate index ix created before 2_18 + try (MockedStatic utilMockedStatic = Mockito.mockStatic(KNNVectorFieldMapper.class)) { + utilMockedStatic.when(() -> KNNVectorFieldMapper.useKNNMethodContextFromLegacy(any(), any())).thenReturn(true); + // if space type is provided and legacy mappings is hit + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, TEST_DIMENSION) + .field(KNNConstants.TOP_LEVEL_PARAMETER_SPACE_TYPE, topLevelSpaceType.getValue()) + .endObject(); + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + "test-field-name-1", + xContentBuilderToMap(xContentBuilder), + buildParserContext("test", settings) + ); - builderContext = new Mapper.BuilderContext(settings, new ContentPath()); - knnVectorFieldMapper = builder.build(builderContext); - assertTrue(knnVectorFieldMapper instanceof MethodFieldMapper); - assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().isPresent()); - assertEquals(topLevelSpaceType, knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().get().getSpaceType()); - // this check ensures that legacy mapping is hit, as in legacy mapping we pick M from index settings - assertEquals( - mForSetting, - knnVectorFieldMapper.fieldType() - .getKnnMappingConfig() - .getKnnMethodContext() - .get() - .getMethodComponentContext() - .getParameters() - .get(METHOD_PARAMETER_M) - ); - assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isEmpty()); + builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + knnVectorFieldMapper = builder.build(builderContext); + assertTrue(knnVectorFieldMapper instanceof MethodFieldMapper); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().isPresent()); + assertEquals( + topLevelSpaceType, + knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().get().getSpaceType() + ); + // this check ensures that legacy mapping is hit, as in legacy mapping we pick M from index settings + assertEquals( + mForSetting, + knnVectorFieldMapper.fieldType() + .getKnnMappingConfig() + .getKnnMethodContext() + .get() + .getMethodComponentContext() + .getParameters() + .get(METHOD_PARAMETER_M) + ); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isEmpty()); + } } public void testTypeParser_withSpaceTypeAndMode_thenSuccess() throws IOException { @@ -1614,7 +1622,7 @@ public void testBuilder_whenBinaryWithLegacyKNNDisabled_thenValid() { assertTrue(knnVectorFieldMapper instanceof FlatVectorFieldMapper); } - public void testTypeParser_whenBinaryWithLegacyKNNEnabled_thenException() throws IOException { + public void testTypeParser_whenBinaryWithLegacyKNNEnabled_thenValid() throws IOException { // Check legacy is picked up if model context and method context are not set ModelDao modelDao = mock(ModelDao.class); KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); @@ -1631,11 +1639,12 @@ public void testTypeParser_whenBinaryWithLegacyKNNEnabled_thenException() throws .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.BINARY.getValue()) .endObject(); - Exception ex = expectThrows(Exception.class, () -> { - typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder), buildParserContext(indexName, settings)); - }); - - assertTrue(ex.getMessage(), ex.getMessage().contains("does not support space type")); + final KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + assertEquals(SpaceType.HAMMING, builder.getOriginalParameters().getResolvedKnnMethodContext().getSpaceType()); } public void testBuild_whenInvalidCharsInFieldName_thenThrowException() { @@ -1661,7 +1670,7 @@ public void testTypeParser_whenModeAndCompressionAreSet_thenHandle() throws IOEx ModelDao modelDao = mock(ModelDao.class); KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); - // Default to nmslib and ensure legacy is in use + // Default to faiss and ensure legacy is in use XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() .startObject() .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) @@ -1676,7 +1685,7 @@ public void testTypeParser_whenModeAndCompressionAreSet_thenHandle() throws IOEx assertTrue(builder.getOriginalParameters().isLegacyMapping()); validateBuilderAfterParsing( builder, - KNNEngine.NMSLIB, + KNNEngine.DEFAULT, SpaceType.L2, VectorDataType.FLOAT, CompressionLevel.x1, diff --git a/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java index 482e7e0cb..511895026 100644 --- a/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java +++ b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java @@ -108,6 +108,7 @@ public class KNNWeightTests extends KNNTestCase { private static final int K = 5; private static final Set SEGMENT_FILES_NMSLIB = Set.of("_0.cfe", "_0_2011_target_field.hnswc"); private static final Set SEGMENT_FILES_FAISS = Set.of("_0.cfe", "_0_2011_target_field.faissc"); + private static final Set SEGMENT_FILES_DEFAULT = SEGMENT_FILES_FAISS; private static final Set SEGMENT_MULTI_FIELD_FILES_FAISS = Set.of( "_0.cfe", "_0_2011_target_field.faissc", @@ -174,7 +175,11 @@ public void tearDownAfterTest() { @SneakyThrows public void testQueryResultScoreNmslib() { for (SpaceType space : List.of(SpaceType.L2, SpaceType.L1, SpaceType.COSINESIMIL, SpaceType.INNER_PRODUCT, SpaceType.LINF)) { - testQueryScore(space::scoreTranslation, SEGMENT_FILES_NMSLIB, Map.of(SPACE_TYPE, space.getValue())); + testQueryScore( + space::scoreTranslation, + SEGMENT_FILES_NMSLIB, + Map.of(SPACE_TYPE, space.getValue(), KNN_ENGINE, KNNEngine.NMSLIB.getName()) + ); } } @@ -398,7 +403,7 @@ public void testEmptyQueryResults() { Map.of(), Sort.RELEVANCE ); - segmentInfo.setFiles(SEGMENT_FILES_NMSLIB); + segmentInfo.setFiles(SEGMENT_FILES_DEFAULT); final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); diff --git a/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java b/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java index 53a78b381..e116ef3c6 100644 --- a/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java +++ b/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java @@ -933,7 +933,7 @@ public void testLoadIndex_nmslib_valid_with_stream() throws IOException { testData.indexData.getDimension(), directory, indexFileName1, - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue(), KNN_ENGINE, KNNEngine.NMSLIB), KNNEngine.NMSLIB ); assertTrue(directory.fileLength(indexFileName1) > 0); diff --git a/src/test/java/org/opensearch/knn/plugin/transport/GetModelResponseTests.java b/src/test/java/org/opensearch/knn/plugin/transport/GetModelResponseTests.java index 49f75ab30..e503f7835 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/GetModelResponseTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/GetModelResponseTests.java @@ -29,6 +29,7 @@ import org.opensearch.knn.indices.ModelState; import java.io.IOException; +import java.util.Locale; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; @@ -75,7 +76,9 @@ public void testXContent() throws IOException { Model model = new Model(getModelMetadata(ModelState.CREATED), testModelBlob, modelId); GetModelResponse getModelResponse = new GetModelResponse(model); String expectedResponseString = String.format( - "{\"model_id\":\"test-model\",\"model_blob\":\"aGVsbG8=\",\"state\":\"created\",\"timestamp\":\"2021-03-27 10:15:30 AM +05:30\",\"description\":\"test model\",\"error\":\"\",\"space_type\":\"l2\",\"dimension\":4,\"engine\":\"nmslib\",\"training_node_assignment\":\"\",\"model_definition\":{\"name\":\"\",\"parameters\":{}},\"data_type\":\"float\",\"model_version\":\"%s\"}", + Locale.ROOT, + "{\"model_id\":\"test-model\",\"model_blob\":\"aGVsbG8=\",\"state\":\"created\",\"timestamp\":\"2021-03-27 10:15:30 AM +05:30\",\"description\":\"test model\",\"error\":\"\",\"space_type\":\"l2\",\"dimension\":4,\"engine\":\"%s\",\"training_node_assignment\":\"\",\"model_definition\":{\"name\":\"\",\"parameters\":{}},\"data_type\":\"float\",\"model_version\":\"%s\"}", + KNNEngine.DEFAULT.getName(), Version.CURRENT.toString() ); XContentBuilder xContentBuilder = XContentFactory.jsonBuilder(); @@ -93,7 +96,9 @@ public void testXContentWithNoModelBlob() throws IOException { Model model = new Model(getModelMetadata(ModelState.FAILED), null, modelId); GetModelResponse getModelResponse = new GetModelResponse(model); String expectedResponseString = String.format( - "{\"model_id\":\"test-model\",\"model_blob\":\"\",\"state\":\"failed\",\"timestamp\":\"2021-03-27 10:15:30 AM +05:30\",\"description\":\"test model\",\"error\":\"\",\"space_type\":\"l2\",\"dimension\":4,\"engine\":\"nmslib\",\"training_node_assignment\":\"\",\"model_definition\":{\"name\":\"\",\"parameters\":{}},\"data_type\":\"float\",\"model_version\":\"%s\"}", + Locale.ROOT, + "{\"model_id\":\"test-model\",\"model_blob\":\"\",\"state\":\"failed\",\"timestamp\":\"2021-03-27 10:15:30 AM +05:30\",\"description\":\"test model\",\"error\":\"\",\"space_type\":\"l2\",\"dimension\":4,\"engine\":\"%s\",\"training_node_assignment\":\"\",\"model_definition\":{\"name\":\"\",\"parameters\":{}},\"data_type\":\"float\",\"model_version\":\"%s\"}", + KNNEngine.DEFAULT.getName(), Version.CURRENT.toString() ); XContentBuilder xContentBuilder = XContentFactory.jsonBuilder(); diff --git a/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java b/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java index 2c423e0ef..6fd399434 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; public class TrainingModelRequestTests extends KNNTestCase { @@ -290,11 +291,14 @@ public void testValidation_invalid_invalidMethodContext() { String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; + MethodComponentContext methodComponentContext = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); + final KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.NMSLIB, SpaceType.DEFAULT, methodComponentContext); + ValidationException validationException = expectThrows( ValidationException.class, () -> new TrainingModelRequest( modelId, - getDefaultKNNMethodContext(), + knnMethodContext, dimension, trainingIndex, trainingField, From d9a5e51e4fed0f08957263778767d36397829f26 Mon Sep 17 00:00:00 2001 From: Navneet Verma Date: Thu, 7 Nov 2024 16:03:01 -0800 Subject: [PATCH 56/59] Fixing the BWC and rolling upgrade test after 2.18 release (#2260) Signed-off-by: Navneet Verma --- .github/workflows/backwards_compatibility_tests_workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/backwards_compatibility_tests_workflow.yml b/.github/workflows/backwards_compatibility_tests_workflow.yml index a0e4530b1..5a90d5852 100644 --- a/.github/workflows/backwards_compatibility_tests_workflow.yml +++ b/.github/workflows/backwards_compatibility_tests_workflow.yml @@ -35,7 +35,7 @@ jobs: matrix: java: [ 21 ] os: [ubuntu-latest] - bwc_version : [ "2.0.1", "2.1.0", "2.2.1", "2.3.0", "2.4.1", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.9.0", "2.10.0", "2.11.0", "2.12.0", "2.13.0", "2.14.0", "2.15.0", "2.16.0", "2.17.0", "2.18.0-SNAPSHOT"] + bwc_version : [ "2.0.1", "2.1.0", "2.2.1", "2.3.0", "2.4.1", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.9.0", "2.10.0", "2.11.0", "2.12.0", "2.13.0", "2.14.0", "2.15.0", "2.16.0", "2.17.0", "2.18.0", "2.19.0-SNAPSHOT"] opensearch_version : [ "3.0.0-SNAPSHOT" ] exclude: - os: windows-latest @@ -124,7 +124,7 @@ jobs: matrix: java: [ 21 ] os: [ubuntu-latest] - bwc_version: [ "2.18.0-SNAPSHOT" ] + bwc_version: [ "2.19.0-SNAPSHOT" ] opensearch_version: [ "3.0.0-SNAPSHOT" ] name: k-NN Rolling-Upgrade BWC Tests From a07bad1122a44abe5dd37d15a7e783523cac4ea4 Mon Sep 17 00:00:00 2001 From: Doo Yong Kim <0ctopus13prime@gmail.com> Date: Fri, 8 Nov 2024 09:33:32 -0800 Subject: [PATCH 57/59] Bump up C++ version in JNI from c++11 to c++17. (#2259) Signed-off-by: Dooyong Kim Co-authored-by: Dooyong Kim --- CHANGELOG.md | 1 + jni/CMakeLists.txt | 2 +- .../knn/index/memory/NativeMemoryCacheManagerTests.java | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c66eb2184..3c57523c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Introduced a writing layer in native engines where relies on the writing interface to process IO. (#2241)[https://github.com/opensearch-project/k-NN/pull/2241] ### Bug Fixes ### Infrastructure +* Updated C++ version in JNI from c++11 to c++17 [#2259](https://github.com/opensearch-project/k-NN/pull/2259) ### Documentation ### Maintenance * Select index settings based on cluster version[2236](https://github.com/opensearch-project/k-NN/pull/2236) diff --git a/jni/CMakeLists.txt b/jni/CMakeLists.txt index 1920453c7..6253eed15 100644 --- a/jni/CMakeLists.txt +++ b/jni/CMakeLists.txt @@ -18,7 +18,7 @@ set(TARGET_LIB_NMSLIB opensearchknn_nmslib) # nmslib JNI set(TARGET_LIB_FAISS opensearchknn_faiss) # faiss JNI set(TARGET_LIBS "") # Libs to be installed -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) option(CONFIG_FAISS "Configure faiss library build when this is on") option(CONFIG_NMSLIB "Configure nmslib library build when this is on") diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java index 4baf66cb4..5fe41c88c 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java @@ -197,7 +197,7 @@ public void testGetTrainingSize() throws ExecutionException { nativeMemoryCacheManager.get(trainingDataEntryContext, true); - assertEquals((float) allocationEntryWeight, nativeMemoryCacheManager.getTrainingSizeInKilobytes(), 0.001); + assertEquals(allocationEntryWeight, nativeMemoryCacheManager.getTrainingSizeInKilobytes(), 1e-3); assertEquals( 100 * (float) allocationEntryWeight / (float) maxWeight, nativeMemoryCacheManager.getTrainingSizeAsPercentage(), From 499273660065403e13e9781e3b9a9931b59f1bf6 Mon Sep 17 00:00:00 2001 From: Vijayan Balasubramanian Date: Mon, 18 Nov 2024 14:00:55 -0800 Subject: [PATCH 58/59] Upgrade bytebuddy and objenesis version to match OpenSearch core and, update github ci runner for macos (#2279) * Upgrade bytebuddy and objenesis version to match OpenSearch core Signed-off-by: Vijayan Balasubramanian * update github runner to macos-13 macos-12 is deprecated, upgrading to macos 13 Signed-off-by: Vijayan Balasubramanian * Install libomp before running build Signed-off-by: Vijayan Balasubramanian --------- Signed-off-by: Vijayan Balasubramanian --- .github/workflows/CI.yml | 3 ++- CHANGELOG.md | 1 + build.gradle | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f9dfffdbf..d860e743c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -99,7 +99,7 @@ jobs: name: Build and Test k-NN Plugin on MacOS needs: Get-CI-Image-Tag - runs-on: macos-12 + runs-on: macos-13 steps: - name: Checkout k-NN @@ -118,6 +118,7 @@ jobs: - name: Install dependencies on macos run: | + brew install libomp brew reinstall gcc export FC=/usr/local/Cellar/gcc/12.2.0/bin/gfortran diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c57523c3..ed2cfea00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Bug Fixes ### Infrastructure * Updated C++ version in JNI from c++11 to c++17 [#2259](https://github.com/opensearch-project/k-NN/pull/2259) +* Upgrade bytebuddy and objenesis version to match OpenSearch core and, update github ci runner for macos [#2279](https://github.com/opensearch-project/k-NN/pull/2279) ### Documentation ### Maintenance * Select index settings based on cluster version[2236](https://github.com/opensearch-project/k-NN/pull/2236) diff --git a/build.gradle b/build.gradle index 132fd7f43..25ff04032 100644 --- a/build.gradle +++ b/build.gradle @@ -295,8 +295,8 @@ dependencies { api group: 'com.google.guava', name: 'guava', version:'32.1.3-jre' api group: 'commons-lang', name: 'commons-lang', version: '2.6' testFixturesImplementation "org.opensearch.test:framework:${opensearch_version}" - testImplementation group: 'net.bytebuddy', name: 'byte-buddy', version: '1.15.4' - testImplementation group: 'org.objenesis', name: 'objenesis', version: '3.2' + testImplementation group: 'net.bytebuddy', name: 'byte-buddy', version: '1.15.10' + testImplementation group: 'org.objenesis', name: 'objenesis', version: '3.3' testImplementation group: 'net.bytebuddy', name: 'byte-buddy-agent', version: '1.15.4' testFixturesImplementation "org.opensearch:common-utils:${version}" implementation 'com.github.oshi:oshi-core:6.4.13' From 2d1a4080d5b1601bf3362fecd85384348af1f326 Mon Sep 17 00:00:00 2001 From: Navneet Verma Date: Tue, 19 Nov 2024 13:47:02 -0800 Subject: [PATCH 59/59] Fixing the bug when a segment has no vector field present for disk based vector search (#2281) Signed-off-by: Navneet Verma --- CHANGELOG.md | 1 + .../knn/index/query/ResultUtil.java | 10 +++--- .../nativelib/NativeEngineKnnVectorQuery.java | 8 ++++- .../knn/index/query/ResultUtilTests.java | 9 +++++ .../NativeEngineKNNVectorQueryTests.java | 5 ++- .../knn/integ/ModeAndCompressionIT.java | 36 +++++++++++++++++++ .../org/opensearch/knn/KNNRestTestCase.java | 13 +++++++ 7 files changed, 75 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed2cfea00..cc1f03718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Enhancements - Introduced a writing layer in native engines where relies on the writing interface to process IO. (#2241)[https://github.com/opensearch-project/k-NN/pull/2241] ### Bug Fixes +* Fix NPE in ANN search when a segment doesn't contain vector field (#2278)[https://github.com/opensearch-project/k-NN/pull/2278] ### Infrastructure * Updated C++ version in JNI from c++11 to c++17 [#2259](https://github.com/opensearch-project/k-NN/pull/2259) * Upgrade bytebuddy and objenesis version to match OpenSearch core and, update github ci runner for macos [#2279](https://github.com/opensearch-project/k-NN/pull/2279) diff --git a/src/main/java/org/opensearch/knn/index/query/ResultUtil.java b/src/main/java/org/opensearch/knn/index/query/ResultUtil.java index f62c09cb0..df1ce3827 100644 --- a/src/main/java/org/opensearch/knn/index/query/ResultUtil.java +++ b/src/main/java/org/opensearch/knn/index/query/ResultUtil.java @@ -58,17 +58,17 @@ public static void reduceToTopK(List> perLeafResults, int k) } /** - * Convert map to bit set + * Convert map to bit set, if resultMap is empty or null then returns an Optional. Returning an optional here to + * ensure that the caller is aware that BitSet may not be present * * @param resultMap Map of results - * @return BitSet of results + * @return BitSet of results; null is returned if the result map is empty * @throws IOException If an error occurs during the search. */ public static BitSet resultMapToMatchBitSet(Map resultMap) throws IOException { - if (resultMap.isEmpty()) { - return BitSet.of(DocIdSetIterator.empty(), 0); + if (resultMap == null || resultMap.isEmpty()) { + return null; } - final int maxDoc = Collections.max(resultMap.keySet()) + 1; return BitSet.of(resultMapToDocIds(resultMap, maxDoc), maxDoc); } diff --git a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java index a34a0f1ee..f782b0180 100644 --- a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java +++ b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -112,7 +113,12 @@ private List> doRescore( LeafReaderContext leafReaderContext = leafReaderContexts.get(i); int finalI = i; rescoreTasks.add(() -> { - BitSet convertedBitSet = ResultUtil.resultMapToMatchBitSet(perLeafResults.get(finalI)); + final BitSet convertedBitSet = ResultUtil.resultMapToMatchBitSet(perLeafResults.get(finalI)); + // if there is no docIds to re-score from a segment we should return early to ensure that we are not + // wasting any computation + if (convertedBitSet == null) { + return Collections.emptyMap(); + } final ExactSearcher.ExactSearcherContext exactSearcherContext = ExactSearcher.ExactSearcherContext.builder() .matchedDocs(convertedBitSet) // setting to false because in re-scoring we want to do exact search on full precision vectors diff --git a/src/test/java/org/opensearch/knn/index/query/ResultUtilTests.java b/src/test/java/org/opensearch/knn/index/query/ResultUtilTests.java index 70cb86e02..7cda1ed79 100644 --- a/src/test/java/org/opensearch/knn/index/query/ResultUtilTests.java +++ b/src/test/java/org/opensearch/knn/index/query/ResultUtilTests.java @@ -9,6 +9,7 @@ import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; import org.apache.lucene.util.BitSet; +import org.junit.Assert; import org.opensearch.knn.KNNTestCase; import java.io.IOException; @@ -48,6 +49,14 @@ public void testResultMapToMatchBitSet() throws IOException { assertResultMapToMatchBitSet(perLeafResults, resultBitset); } + public void testResultMapToMatchBitSet_whenResultMapEmpty_thenReturnEmptyOptional() throws IOException { + BitSet resultBitset = ResultUtil.resultMapToMatchBitSet(Collections.emptyMap()); + Assert.assertNull(resultBitset); + + BitSet resultBitset2 = ResultUtil.resultMapToMatchBitSet(null); + Assert.assertNull(resultBitset2); + } + public void testResultMapToDocIds() throws IOException { int firstPassK = 42; Map perLeafResults = getRandomResults(firstPassK); diff --git a/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java index 4577a34d4..01e3fa6f9 100644 --- a/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java +++ b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java @@ -229,7 +229,7 @@ public void testRescore() { when(reader.leaves()).thenReturn(leaves); int k = 2; - int firstPassK = 3; + int firstPassK = 100; Map initialLeaf1Results = new HashMap<>(Map.of(0, 21f, 1, 19f, 2, 17f, 3, 15f)); Map initialLeaf2Results = new HashMap<>(Map.of(0, 20f, 1, 18f, 2, 16f, 3, 14f)); Map rescoredLeaf1Results = new HashMap<>(Map.of(0, 18f, 1, 20f)); @@ -257,6 +257,9 @@ public void testRescore() { mockedKnnSettings.when(() -> KNNSettings.isShardLevelRescoringEnabledForDiskBasedVector(any())).thenReturn(true); mockedResultUtil.when(() -> ResultUtil.reduceToTopK(any(), anyInt())).thenAnswer(InvocationOnMock::callRealMethod); + mockedResultUtil.when(() -> ResultUtil.resultMapToMatchBitSet(any())).thenAnswer(InvocationOnMock::callRealMethod); + mockedResultUtil.when(() -> ResultUtil.resultMapToDocIds(any(), anyInt())).thenAnswer(InvocationOnMock::callRealMethod); + mockedResultUtil.when(() -> ResultUtil.resultMapToTopDocs(eq(rescoredLeaf1Results), anyInt())).thenAnswer(t -> topDocs1); mockedResultUtil.when(() -> ResultUtil.resultMapToTopDocs(eq(rescoredLeaf2Results), anyInt())).thenAnswer(t -> topDocs2); try (MockedStatic mockedStaticNativeKnnVectorQuery = mockStatic(NativeEngineKnnVectorQuery.class)) { diff --git a/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java index 0913d9b36..ad7f8b057 100644 --- a/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java +++ b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java @@ -11,6 +11,7 @@ import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.ResponseException; +import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.XContentBuilder; @@ -220,6 +221,41 @@ public void testDeletedDocsWithSegmentMerge_whenValid_ThenSucceed() { validateGreenIndex(indexName); } + @SneakyThrows + public void testCompressionIndexWithNonVectorFieldsSegment_whenValid_ThenSucceed() { + CompressionLevel compressionLevel = CompressionLevel.x32; + String indexName = INDEX_NAME + compressionLevel; + try ( + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel.getName()) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .endObject() + .endObject() + .endObject() + ) { + String mapping = builder.toString(); + Settings indexSettings = buildKNNIndexSettings(0); + createKnnIndex(indexName, indexSettings, mapping); + // since we are going to delete a document, so its better to have 1 more extra doc so that we can re-use some tests + addKNNDocs(indexName, FIELD_NAME, DIMENSION, 0, NUM_DOCS + 1); + addNonKNNDoc(indexName, String.valueOf(NUM_DOCS + 2), FIELD_NAME_NON_KNN, "Hello world"); + deleteKnnDoc(indexName, "0"); + validateGreenIndex(indexName); + validateSearch( + indexName, + METHOD_PARAMETER_EF_SEARCH, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, + compressionLevel.getName(), + Mode.ON_DISK.getName() + ); + } + } + @SneakyThrows public void testTraining_whenInvalid_thenFail() { setupTrainingIndex(); diff --git a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java index 8a4885cfe..2afbd9639 100644 --- a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java +++ b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java @@ -116,6 +116,7 @@ public class KNNRestTestCase extends ODFERestTestCase { public static final String INDEX_NAME = "test_index"; public static final String FIELD_NAME = "test_field"; + public static final String FIELD_NAME_NON_KNN = "test_field_non_knn"; public static final String PROPERTIES_FIELD = "properties"; public static final String STORE_FIELD = "store"; public static final String STORED_QUERY_FIELD = "stored_fields"; @@ -607,6 +608,18 @@ protected void addKnnDoc(String index, String docId, String fieldName, T vec assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); } + protected void addNonKNNDoc(String index, String docId, String fieldName, String text) throws IOException { + Request request = new Request("POST", "/" + index + "/_doc/" + docId + "?refresh=true"); + + XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field(fieldName, text).endObject(); + request.setJsonEntity(builder.toString()); + client().performRequest(request); + + request = new Request("POST", "/" + index + "/_refresh"); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + /** * Add a single KNN Doc to an index with a nested vector field *