diff --git a/CHANGELOG.md b/CHANGELOG.md index 106cb727..1a86ef8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,11 @@ Changelog under `yggdrasil_decision_forests/port/python/CHANGELOG.md`. - Allow configuring the truncation of NDCG losses. - Add support for distributed training for ranking gradient boosted tree models. +- Add support for AVRO data file using the "avro:" prefix. ### Misc -- Loss options are now defined +- Loss options are now defined model/gradient_boosted_trees/gradient_boosted_trees.proto (previously learner/gradient_boosted_trees/gradient_boosted_trees.proto) diff --git a/LICENSE b/LICENSE index 31dd34c3..8912cf09 100644 --- a/LICENSE +++ b/LICENSE @@ -223,7 +223,11 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -rapidjson +nlohmann_json +MIT License + +Copyright (c) 2013-2022 Niels Lohmann + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/WORKSPACE_WITH_TF b/WORKSPACE_WITH_TF index e7047602..916dbb0e 100644 --- a/WORKSPACE_WITH_TF +++ b/WORKSPACE_WITH_TF @@ -20,8 +20,8 @@ load("//third_party/tensorflow:workspace.bzl", tensorflow = "deps") #load("//third_party/farmhash:workspace.bzl", farmhash = "deps") load("//third_party/boost_math:workspace.bzl", boost_math = "deps") # load("//third_party/grpc:workspace.bzl", grpc = "deps") -load("//third_party/rapidjson:workspace.bzl", rapidjson = "deps") load("//third_party/eigen3:workspace.bzl", eigen = "deps") +load("//third_party/nlohmann_json:workspace.bzl", nlohmann_json = "deps") gtest() # absl() # We use the abseil linked in tensorflow to avoid package clashes @@ -31,8 +31,8 @@ tensorflow() #farmhash() boost_math() # grpc() # We use the protobuf linked in tensorflow. -rapidjson() # eigen() +nlohmann_json() # The initialization of YDF dependencies is commented. TensorFlow # is in charge of initializing them. diff --git a/documentation/public/docs/blog/.authors.yml b/documentation/public/docs/blog/.authors.yml new file mode 100644 index 00000000..6cc4507a --- /dev/null +++ b/documentation/public/docs/blog/.authors.yml @@ -0,0 +1,5 @@ +authors: + gbm: + name: Mathieu Guillame-Bert + description: Creator + avatar: https://avatars.githubusercontent.com/u/52443 diff --git a/documentation/public/docs/blog/index.md b/documentation/public/docs/blog/index.md new file mode 100644 index 00000000..604e22c9 --- /dev/null +++ b/documentation/public/docs/blog/index.md @@ -0,0 +1,4 @@ +# ✒️ Blog + +Here, we share playful, surprising or interesting experiments, stories, and +facts about machine learning in general and decision forests in particular. diff --git a/documentation/public/docs/blog/posts/1_how_ml_models_generalize.md b/documentation/public/docs/blog/posts/1_how_ml_models_generalize.md new file mode 100644 index 00000000..1fc44430 --- /dev/null +++ b/documentation/public/docs/blog/posts/1_how_ml_models_generalize.md @@ -0,0 +1,253 @@ +--- +date: 2024-10-04 +authors: + - gbm +--- + +# How different machine learning models generalize? + +Do you know how different machine learning models generalize? Maybe you know and +understand the theory behind models like decision forests, neural networks, +nearest neighbors, or SVMs. You might even have a good sense of which model +works best for certain problems. But do you know how different machine learning +models **see the world** and have you ever **seen** how they generalize +differently? + +![Four robots looking at nature](../../non-github-assets/blog/1/intro.png) + + + +To answer this question, I'll train different tabular ML models to reconstruct +images. My assumption is that those different models will make different types +of mistakes that can then be linked back to the model mathematical definition. +This is mostly a ludic experiment where I am hoping to get some interesting +pictures, and with some chance, improve my practical understanding of those +models. + +This article is not a scientific article in that it lacks details and +meticulousness. Instead, it is fun piece. + +To discuss this article, join me in this github discussion. + +## Experiment setup + +A numerical grayscale image can be represented as a 2D array $M$ of numerical +values, where each element $M[x,y]$ encodes the light intensity of pixel +$(x,y)$. Color images can be encoded similarly, using a separate 2D arrays for +each color channels (e.g., RGB or HSL). + +An image can also be represented as a function $f(x,y) := M[x,y]$ defined on the +integer coordinates in the images e.g. $x \in [0, \mbox{width} )$. It is not possible +to directly evaluate this function in between pixels e.g. $f(0.5, 6.1)$. My goal +is to train a machine learning model to learn this function where the x,y +coordinates are the model input features, and the function output is the label. +This gives an interesting property: (Most) ML models consume floating point +values and are able to interpolate in between the training data points a.k.a. to +generalize. + +In other words, by evaluating the learned model in between the pixels (e.g., +$f(0.5, 6.1)$), I can increase the resolution of an image: A image of size +100x100 can be converted into a image of size 1000x1000. This also means that I +don't need all the pixels to train the image. An image with dead pixels or a +missing part can be reconstructed. + +**Note #1:** Specialized up-sampling algorithms exist and will produce much better +results. My goal is not to reconstruct the images as well as possible, but +instead to see how tabular ML models fail at this task :) + +**Note #2:** Using the model, it is possible to extrapolate outside of the image. +However, since most traditionally used ML models are exceptionally bad at +extrapolation, I will not look at this. + +As for the models, I will use the following tabular data models: + +- A **decision tree**(DT) a.k.a. CART. +- A **random forest** (RF): A ensemble of decision trees trained with some + randomness. +- A **gradient boosted trees** (GBT): A set of decision trees trained sequentially + to each predict the errors of the previous trees. +- An **extremely randomized trees** (ERT): A random forest where the splits (a.k.a + conditions) are selected at random. +- A **random forest with oblique splits** (Oblique RF): A random forest allowed to + learn splits looking at multiple attributes at the same time. +- A **k-nearest neighbor** (kNN): Find the k-nearest pixels in the training + dataset, and return the average. +- A **support vector machine** (SVM): A model that used to be popular that uses + the distance to a few anchor points as an input feature of a linear model. +- A **feed-forward multi-layer perceptron** (MLP) a.k.a. a neural network: A + sequence of matrix multiplications interleaved with activation functions. + +## Reconstructing a toy image + +The first image is a black and white one with an ellipse, a square and a diamond inside of a square. The full image is on the left side, and the right side shows a zoomed-in section. + +![Ground truth for the toy image](../../non-github-assets/blog/1/ground_truth.png) + +This image contains horizontal and vertical lines which should be easy for decision tree to learn, as well as, diagonal lines and round shapes which should be hard for the trees. This is because with classical decision tree, each condition tests one attribute at a time. + +Any sufficiently large model can remember and reconstruct a image if the entire image is used for training. Instead, I reduced the image resolution by 4x and randomly dropped 80% of the remaining pixels, and ask the model to re-generate the original image. This means the models only see around $1.25\% = 1/4^2 * (1-0.8)$ of the original image data. The training image is: + +![training data for the toy image](../../non-github-assets/blog/1/toy_training_data.png) + +Let's start with a simple [decision tree](https://developers.google.com/machine-learning/decision-forests/decision-trees) (DT) model. Can it reconstruct the image? Here are its predictions. + +![decision tree predictions for the toy image](../../non-github-assets/blog/1/toy_dt.png) + +**Note:** The models I'll train are able to output probabilities so the predictions could be a grayscale image. While it would make for better looking images, plotting grayscale predictions would make it harder to see the mistakes. + +The image reconstructed by the decision tree looks terrible, but what are the problem exactly? + +The square in the bottomleft is almost perfect but the ellipse on the top-left +and diamond on the bottom-right all have a "stair steps". A little bit of +background: Here, a decision tree is a binary tree of conditions where each +condition is of the form $x \geq \mbox{some number}$ or $y \geq \mbox{some +number}$. For example, the top of the decision tree can look like. + +```raw +x >= 0.220 + ├─(pos)─ y >= 0.780 + | ├─(pos)─ ... + | └─(neg)─ ... + └─(neg)─ y >= 0.207 + ├─(pos)─ ... + └─(neg)─ ... +``` + +Because each condition only check one of the coordinate at a time, this creates strong horizontal or vertical biases in the model generalization. When those lines connect, they form the stair steps we see. I can confirm this by assigning a random color to each leaves of the decision tree: + +![decision tree nodes for the toy image](../../non-github-assets/blog/1/toy_dt_nodes.png) + +In the plot above, uniform rectangle is a leaf. The bottom-left square is represented nicely by a single block, but many blocks are necessary for the edge of the ellipse and diamond. + +In the prediction plot above, there are two littler "outgrowths" on the side of the ellipse edge. Those are the decision trees overfitting a.k.a hallucinating a pattern. Overfitting is a problem with decision trees and a reason they are not used often in the industry. + +A decision tree is a simple model. Can a [Random Forest](https://developers.google.com/machine-learning/decision-forests/intro-to-decision-forests) do better? In a Random Forest, many (e.g. 1000) decision trees are averaged together. Those trees are trained with some noise so they are not all the same. Here is what the predictions of a random forest look like. + +![random forest predictions for the toy image](../../non-github-assets/blog/1/toy_rf.png) + +This is better. The border of the ellipse and diamond are smoother even though they have stair steps. Most notably, there are no longer overfitting outgrowths. This is a good illustration of Breiman saying: [Random Forests do not overfit, [...]](https://www.stat.berkeley.edu/%7Ebreiman/randomforest2001.pdf). + +What about [gradient boosted trees](https://developers.google.com/machine-learning/decision-forests/intro-to-gbdt), the famous algorithm that powers XGBoost libraries? The prediction of a GBT looks at follow: + +![gradient boosted trees for the toy image](../../non-github-assets/blog/1/toy_gbt.png) + +The overall shapes of the ellipse and diamond are also looking better than for the decision tree, though the surface is not as smooth as for the Random Forest. There are also some outgrowth on the diamond. This is expected, since GBTs (unlike Random Forests) can overfit. + +What about more exotic decision forest models? + +[Extremely randomized trees](https://link.springer.com/article/10.1007/s10994-006-6226-1) are a modification to random forests algorithm to make decision boundaries smoother. The idea is simple: Instead of learning the tree condition using the data, they are selected randomly. Here are the predictions from extremely randomized trees: + +![extremely randomized trees predictions for the toy image](../../non-github-assets/blog/1/toy_extrem_trees.png) + +As expected, the ellipse and diamond borders are smoother than for random forest, but they are "wiggly" where stair steps used to be. Also, the square corners are now rounder. + + +[Oblique Random Forest](https://developers.google.com/machine-learning/decision-forests/conditions#axis-aligned_vs_oblique_conditions) are another type of exotic models where a single split can test multiple attributes, generally with a linear equation. This should remove the stair step. Here are the oblique random forest predictions: + +![oblique random forests predictions for the toy image](../../non-github-assets/blog/1/toy_oblique_rf.png) + +This is much better. The ellipse is smooth and both the diamond and square edges are straight. The square and diamond corners are slightly rounded, but less than for extremely randomized trees. Also, the edge of the ellipse contains flat sections.This make sense: Those are the linear splits. + +**Note:** Oblique random forests are not just good in this toy example. In practice, they are very competitive. + +Any decision forest model can be made oblique. Here are the predictions of an oblique gradient boosted trees and an oblique decision tree. + +![oblique gradient boosted trees predictions for the toy image](../../non-github-assets/blog/1/toy_oblique_gbt.png) +![oblique decision tree predictions for the toy image](../../non-github-assets/blog/1/toy_oblique_dt.png) + +The oblique gradient boosted trees is somehow similar to the oblique random forest, with maybe sharper corners and flatter sections around the ellipse. +On the other side, oblique decision trees have a lot of "sharp bit" 🔪! + +So far I looked at decision trees and decision forest models. What about other types of models? First, let's plot the predictions of a k-nearest-neighbors (kNN) model. To make a prediction, a k-nearest-neighbors looks a the k closest values and return the most common label. + +![k-nearest-neighbors predictions for the toy image](../../non-github-assets/blog/1/toy_knn.png) + +This is interesting. The k-nearest-neighbors model does not show stair steps like decision forests models. Instead, the edges have a kind of texture and look almost organic. While beautiful, this is not great in practice: This texture is an artifact of the overfitting these models typically suffer from. Note that the external rectangle is also completely missing. + +What about support vector machines (SVM)? There are the predictions: + +![support vector machine predictions for the toy image](../../non-github-assets/blog/1/toy_svm.png) + +While the k-nearest-neighbors was overfitting, the SVM is missing many of the details and the generated plot is super smooth. Note that an ellipse can be described as a linear inequality on the sum of distances to the eclipse focus points, which is exactly what an SVM model can express. If the square, diamond and border was removed from the image, the SVM model would perfectly predict it. + +Finally, this list would not be complete without neural networks. To keep things simple, here are the predictions of a multilayer-perceptron with 3 hidden layers of size 200: + +![neural network predictions for the toy image](../../non-github-assets/blog/1/toy_ann_1.png) + +Here are the predictions of a multilayer-perceptron with 8 hidden layers of size 200: + +![another neural network predictions for the toy image](../../non-github-assets/blog/1/toy_ann_2.png) + +The predictions are smooth which is great for the ellipse, but bad for the square and diamond. Also, the surrounding rectangle is completely missing. More worrisome, the edge of the predictions look like it is always overshooting (square) or undershooting (diamond) the edge of the original shapes. This shown a specificity of neural networks: While a decision forest a or kNN model learn different part of the feature space independently, neural network models learn patterns globally patterns. This is often great for generalization of complex patterns that repeat in several places (e.g., the neural network can learn the concept of a circle and reuse it in several places), but this makes the model more complex to train. + +Training parameters of a neural network also have a large and hard to predict impact on the final model. For fun of it, there are the predictions of a MLP trained with a single hidden layer, and one trained with 20 hidden layers of size 20: + +![another neural network predictions for the toy image](../../non-github-assets/blog/1/toy_ann_3.png) + +![another neural network predictions for the toy image](../../non-github-assets/blog/1/toy_ann_4.png) + +## Reconstruction of a real image + +Now, I'll apply the same reconstruction pattern on a colored picture. I expect the mistakes to be harder to interpret, but I am hoping to get some cool pictures. + +I'll use this image of a toy old hot air balloon. This image is great for multiple reasons: It contains round shapes (hard for the decision forests), it contains sharp straight edges (easy for decision forests), and it contains details. + +![an image of a ballon](../../non-github-assets/blog/1/ballon_ground_truth.png) + +Like before, I've reduce the image resolution drastically and masked some of the pixels. The training image has a resolution of 68x102 pixels (including the dead pixels): + +![training dataset for the ballon image](../../non-github-assets/blog/1/ballon_train_data.png) + +Starting simple, the predictions of a decision trees are: + +![decision tree predictions for the ballon image](../../non-github-assets/blog/1/ballon_dt.png) + +You might have expected a broken image but the model is able both to fill the dead pixels and reconstruct the image. However, the reconstructed image still look low resolution. Why? + +Essentially the trained decision tree has grown one leaf for each of the pixel in the original training images. Since pixels are squares, they are easy for the decision tree to encode, and the model can easily remember the entire image. However, the model fails to interpolate between the pixels. + +What about Random Forest predictions? + +![random forest predictions for the ballon image](../../non-github-assets/blog/1/ballon_rf.png) + +The random forest model is doing some form of interpolation / smoothing in between the individual pixels. If you look closely, the pixels in the original image are not uniform in the reconstructed image. While a random forest model cannot extrapolate (i.e., make predictions that are not seen in training), the reconstructed image contains new colors: Look at the green-ish at the base of the ballon. This can be explained by the independent interpolation in the different color channels. The model’s interpolation is not a valid interpolation in the color space, so it creates new colors. + +The predictions of a gradient boosted trees are: + +![gradient boosted tres predictions for the ballon image](../../non-github-assets/blog/1/ballon_gbt.png) + +The reconstructed image is even smoother than for the Random Forest. This makes sense: While the decision tree and random forest can create one leaf node for each pixel, the GBT model cannot because each tree is limited (in this experiment, I limited the tree depth to 6). Instead, the GBT overlaps multiple shallow trees to encode groups of pixels. This has two effects: The interpolation in between the color channels are less synchronized leading to the creation of more green, and the model generates drawing artifacts: Look at the ballon strips extending on the brown background at the top. + +What about oblique Random Forest? + +![oblique random forest predictionsfor the ballon image](../../non-github-assets/blog/1/ballon_oblique_rf.png) + +This is a pleasing image. There are a lot of smoothing and pixel interpolation, there are no drawing artifacts, and there is only a small amount of the green-ish color. The model is also able to reconstruct some of the net surrounding the ballon (look at the original image). Interestingly, there are still some horizontal and vertical patterns in the zoomed image. This is caused by the algorithm that learns the oblique splits: horizontal and vertical splits are still possible and the learning algorithm is biased toward them. + +All in all, this feels like a color filter I would use to stylize images. + +For reference, here is the output of the XBR algorithm. This algorithm is specialized to increase the resolution of very-low resolution images such as sprites of old video game consoles. + +![ballon image upsampled using the xbr algorithm](../../non-github-assets/blog/1/ballon_xbr.png) + +What about other machine learning models? The predictions of a k-nearest neighbors are: + +![knn predictions for the ballon image](../../non-github-assets/blog/1/ballon_knn.png) + +Same as before, kNN has a tendency to hallucinate texture. + +And, for the final images, let's look at the predictions of neural networks. Since the image is much more complex, I'll give more parameters to the network. Here are the predictions for a few neural network configurations. + +![another neural network predictions for the ballon image](../../non-github-assets/blog/1/ballon_ann_2.png) +![another neural network predictions for the ballon image](../../non-github-assets/blog/1/ballon_ann_4.png) + +The model cannot reconstruct the image details. Generating those images also took significantly more time than other models tested before. I am sure that with a larger model and more fiddling with the parameters, I could make the neural network learn the image. However, this experiment is a toy illustration of the development complexity and training cost of neural network models. + +## Conclusion + +This was an interesting experiment, and I hope it gave you a new understanding +on how different model generalize. Something interesting about this approach is +its capacity to consume clouds of points (not just raster images) and +reconstruct 2D but also 3D images (possibly with animations). I think this would +make for a cool follow-up. And, if you experiment with this and get some cool +pictures / videos before, let me know :). diff --git a/documentation/public/docs/cli_quickstart.md b/documentation/public/docs/cli_quickstart.md index 2efbed24..b2346081 100644 --- a/documentation/public/docs/cli_quickstart.md +++ b/documentation/public/docs/cli_quickstart.md @@ -222,7 +222,7 @@ A few remarks: provide reasonable results in most situations. We will discuss alternative default values (called hyperparameter templates) and automated tuning of hyperparameters later. The list of all hyperparameters and their default - values is available in the [hyperparameters page](hyper_parameters). + values is available in the [hyperparameters page](hyperparameters.md). - No validation dataset was provided for the training. Not all learners require a validation dataset. However, the `GRADIENT_BOOSTED_TREES` learner @@ -442,7 +442,7 @@ One vs other classes: - The test accuracy is 0.874399 with 95% confidence interval boundaries of [0.86875; 0.879882]. - The test AUC is 0.929207 with 95% confidence interval boundaries of - [0.924358 0.934056](when computed with a closed form) and [0.973397 + [0.924358 0.934056] when computed with a closed form and [0.973397 0.977947] when computed with bootstrapping. - The PR-AUC and AP metrics are also available. diff --git a/documentation/public/docs/cli_user_manual.md b/documentation/public/docs/cli_user_manual.md index aa9848c5..02d6a388 100644 --- a/documentation/public/docs/cli_user_manual.md +++ b/documentation/public/docs/cli_user_manual.md @@ -4,7 +4,7 @@ This page contains an in-depth introduction to Yggdrasil Decision Forests (YDF) CLI API. The content presented on this page is generally not necessary to use YDF, but it will help users improve their understanding and use advance options. -New users should first check out the [Quick start](cli_quick_start). +New users should first check out the [Quick start](cli_quick_start.md). Most concepts presented here apply to the other APIs, notably the C++ API. @@ -280,7 +280,7 @@ The **generic hyper-parameters** (GHPs) are an alternative representation to the quick configuration, and automated hyper-parameter optimization. GHPs are used by TensorFlow Decision Forests (TF-DF): -The [hyper-parameter](hyper_parameters) page lists the learners and their +The [hyper-parameter](hyperparameters.md) page lists the learners and their hyper-parameters. Optionally, a learner can be configured with a **deployment specification**. A @@ -468,7 +468,7 @@ The available variable importances are: Optimizing the hyperparameters of a learner can improve the quality of a model. Selecting the optimal hyper-parameters can be done manually (see -[how to improve a model](improve_model)) or using the automated hyper-parameter +[how to improve a model](guide_how_to_improve_model.md)) or using the automated hyper-parameter optimizer (HPO). The HPO automatically selects the best hyper-parameters through a sequence of trial-and-error computations. diff --git a/documentation/public/docs/guide_how_to_improve_model.md b/documentation/public/docs/guide_how_to_improve_model.md index 7ddb326c..1ad3d50a 100644 --- a/documentation/public/docs/guide_how_to_improve_model.md +++ b/documentation/public/docs/guide_how_to_improve_model.md @@ -15,7 +15,7 @@ Having a basic understanding of how decision forests work is useful to optimize them. For more information, please refer to [Google's Decision Forests class](https://developers.google.com/machine-learning/decision-forests). -The [hyper-parameter page](hyperparameters) lists and explains the available +The [hyper-parameter page](hyperparameters.md) lists and explains the available hyper-parameters. ## Random Forest or Gradient Boosted Trees? @@ -45,7 +45,7 @@ Automated hyperparameter tuning is a simple but expensive solution to improve the quality of a model. When full hyper-parameter tuning is too expensive, combining hyper-parameter tuning and manual tuning is a good solution. -See the [Tuning notebook](tutorial/tuning/) for details. +See the [Tuning notebook](tutorial/tuning.ipynb) for details. ## Hyper-parameter templates @@ -58,7 +58,7 @@ without having understood those hyper-parameters and without having to run hyper-parameter tuning, YDF have pre-configured **hyper-parameter templates**. The hyper-parameter templates are available by calling -[hyperparameter_templates](py_api/GradientBoostedTreesLearner/#ydf.GradientBoostedTreesLearner.hyperparameter_templates) +[hyperparameter_templates](py_api/GradientBoostedTreesLearner.md#ydf.GradientBoostedTreesLearner.hyperparameter_templates) on a learner. ```python diff --git a/documentation/public/docs/index.md b/documentation/public/docs/index.md index 4c8efdc2..47dda9ca 100644 --- a/documentation/public/docs/index.md +++ b/documentation/public/docs/index.md @@ -133,10 +133,10 @@ model.save("/tmp/my_model") **Modeling** -- Train [Random Forest](py_api/RandomForestLearner), - [Gradient Boosted Trees](py_api/GradientBoostedTreesLearner), - [Cart](py_api/CartLearner), and - [Isolation Forest](py_api/IsolationForestLearner) models. +- Train [Random Forest](py_api/RandomForestLearner.md), + [Gradient Boosted Trees](py_api/GradientBoostedTreesLearner.md), + [Cart](py_api/CartLearner.md), and + [Isolation Forest](py_api/IsolationForestLearner.md) models. - Train [classification](tutorial/classification.ipynb), [regression](tutorial/regression.ipynb), [ranking](tutorial/ranking.ipynb), [uplifting](tutorial/uplifting.ipynb), and @@ -159,12 +159,12 @@ model.save("/tmp/my_model") **Serving** -- [Benchmark](tutorial/getting_started/#benchmark-model-speed) model +- [Benchmark](tutorial/getting_started.md#benchmark-model-speed) model inference. - Run models in Python, [C++](tutorial/cpp.ipynb), [Go](https://github.com/google/yggdrasil-decision-forests/tree/main/yggdrasil_decision_forests/port/go), [JavaScript](https://github.com/google/yggdrasil-decision-forests/tree/main/yggdrasil_decision_forests/port/javascript), - and [CLI](cli_commands). + and [CLI](cli_commands.md). - Online inference with REST API with [TensorFlow Serving and Vertex AI](tutorial/tf_serving.ipynb). diff --git a/documentation/public/docs/javascript.md b/documentation/public/docs/javascript.md new file mode 100644 index 00000000..2c35ab8b --- /dev/null +++ b/documentation/public/docs/javascript.md @@ -0,0 +1,104 @@ +# Javascript + +YDF offers two different npm packages to run on the web: + +* [ydf-inference](https://www.npmjs.com/package/ydf-inference) Only for + generating predictions using an existing model. Models can be trained with + ydf-training (see below), YDF python, or any other YDF API. If you only need + model predictions, use this package instead of ydf-training to save on + binary size. +* [ydf-training](https://www.npmjs.com/package/ydf-training) for both training + models and generating predictions. + +Both packages are compatible with NodeJS+CommonJS, NodeJS+ES6 and Browser JS. + +## ydf-inference + +`ydf-inference` is YDF's interface for model inference on the Web. +See the [Readme on npmjs.com](https://www.npmjs.com/package/ydf-inference) for +information about downloading and testing the package. + +The following example shows how to download a YDF model and make predictions +on a Javascript dictionary of arrays. + +```html + + + +``` + +## ydf-training + +`ydf-training` is YDF's interface for training and inspecting models in +Javascript. It is implemented with Javascript and WebAssembly. +See the [Readme on npmjs.com](https://www.npmjs.com/package/ydf-training) for +information about downloading and testing the package. + +The following example shows how to train a Gradient Boosted Trees model on a +first csv dataset, and then use this model to make predictions on a second csv +dataset. + +```html + + + +``` + +### Known limitations + +`ydf-training` currently only supports a subset of the functionality of YDF's +Python surface, namely **supervised learning** with Random Forests and +Gradient Boosted Trees. Hyperparameter configuration is not yet supported. +Additionally, model evaluation and model analysis are not yet supported. + +For feature requests, please open an issue [on GitHub](https://github.com/google/yggdrasil-decision-forests). diff --git a/documentation/public/docs/py_api/util.md b/documentation/public/docs/py_api/util.md index e69ac863..f1377093 100644 --- a/documentation/public/docs/py_api/util.md +++ b/documentation/public/docs/py_api/util.md @@ -2,6 +2,6 @@ [TOC] -::: ydf.util.read_tf_record +::: ydf.util.tf_example.read_tf_record -::: ydf.util.write_tf_record +::: ydf.util.tf_example.write_tf_record diff --git a/documentation/public/docs/style/extra.css b/documentation/public/docs/style/extra.css index 162bce89..08f54733 100644 --- a/documentation/public/docs/style/extra.css +++ b/documentation/public/docs/style/extra.css @@ -11,6 +11,10 @@ h1#_1 { margin: 0 auto; } +.halfsize { + transform: scale(0.5); +} + .logo { width: 397px; margin-top: 20px; diff --git a/documentation/public/docs/tutorial/migrating_to_ydf.ipynb b/documentation/public/docs/tutorial/migrating_to_ydf.ipynb index 5e595170..013fa6ce 100644 --- a/documentation/public/docs/tutorial/migrating_to_ydf.ipynb +++ b/documentation/public/docs/tutorial/migrating_to_ydf.ipynb @@ -1,613 +1,256 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "h1ggM1ogLXPb" - }, - "source": [ - "# Migrating to YDF\n", - "\n", - "\n", - "YDF and [TensorFlow Decision Forests](https://tensorflow.org/decision_forests) (TF-DF) are both front-ends to the same high-performance C++ implementation of Decision Forests algorithms. Both libraries are developed by the same team and use the same training code, which means that models trained by either library will be identical.\n", - "\n", - "\n", - "\n", - "---\n", - "\n", - "\n", - "**YDF is the successor of TF-DF and it is both significantly more feature-rich, efficient, and easier to use than TF-DF.**\n", - "\n", - "\n", - "---\n", - "\n", - "\n", - "\n", - "### Benefits at a glance\n", - "\n", - "| | YDF | TensorFlow Decision Forests |\n", - "|---|---|---|\n", - "| Model description | `model.describe()` produces rich model description html or text report. | `model.summary()` produces a less complete text report,\u003cbr\u003e but does not work if applied on a model loaded from disk. |\n", - "| Model evaluation | `model.evaluate(ds)` evaluates a model and returs a rich model\u003cbr\u003e evaluation report. Metrics can also be accessed programmatically. | Each evaluation metric needs to be configured and run manually with\u003cbr\u003e `model.compile()` and `model.evalute()`. No evaluation report.\u003cbr\u003e No confidence intervals. No metrics for ranking and uplifting models. |\n", - "| Model analysis | `model.analyze(ds)` produces a rich model analysis html report. | Not available |\n", - "| Model benchmarking | `model.benchmark(ds)` measures and reports the model inference speed. | Not available |\n", - "| Custom losses | Available for training Gradient Boosted Trees. | Not available |\n", - "| Cross-validation | `learner.cross_validation(ds)` performs a cross-validation and return\u003cbr\u003e a rich model evaluation report. | Not available |\n", - "| Python model serving | `model.predict(ds)` makes predictions. | `model.predict(ds)` works sometimes. However, because of limitation\u003cbr\u003e in the TensorFlow SavedModel format, calling `model.predict(ds)` on\u003cbr\u003e a model loaded from disk might require signature engineering. |\n", - "| Other model serving | Model directly available in C++, Python, CLI, go and Javascript. You can also\u003cbr\u003e use utilities to generate serving code: For example, call `model.to_cpp()`\u003cbr\u003e to generate C++ serving code. Models can be exported to a TensorFlow\u003cbr\u003e SavedModel with `model.to_tensorflow_saved_model(path)`. | Call `model.save(path, signature)` to generate a TensorFlow \u003cbr\u003eSaveModel, and use the TensorFlow C++ API to run the model in C++. \u003cbr\u003e Alternatively, export the model to YDF. |\n", - "| Training speed | On a small dataset, training up to 5x faster than TensorFlow Decision Forests.\u003cbr\u003e On all dataset sizes, model inference is up to 1000x faster than TensorFlow\u003cbr\u003e Decision Forests. | On a small dataset, most of the time is spent in TensorFlow dataset reading. |\n", - "| Library size | The YDF library is smaller than 10MB. | The TF-DF library is small, but it requires TensorFlow which is ~600MB. |\n", - "| Error messages | Short, high level and actionable error messages. | Long and hard to understand error messages often about Tensor shapes. |\n", - "\n", - "\n", - "### Do I have to migrate?\n", - "\n", - "**TensorFlow Decision Forests will continue to be supported and users are not required to migrate their pipelines!** If TF-DF and the Keras work well for you, feel free to stay with TF-DF. Our team will continue to release new versions and support users through our various support channels.\n", - "\n", - "For more information, check out the [FAQ](https://ydf.readthedocs.io/en/latest/faq).\n", - "\n", - "### Outline\n", - "\n", - "This guide has three parts:\n", - "\n", - "1. Migrating your TF-DF training, inference and evaluation pipeline.\n", - "2. Importing and exporting your existing TF-DF models.\n", - "3. Advanced topics: Inspection, Building, Tuning and Distributed Training\n", - "\n", - "This guide does not cover every configuration detail of YDF. See https://ydf.readthedocs.org for a full documentation." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "4y0BMQ8BOtYb" - }, - "source": [ - "## Setup\n", - "\n", - "To use ydf, just install the corresponding Python package from [Pypi](https://pypi.org/project/ydf/)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "GVw9NG_SJvc3" - }, - "outputs": [], - "source": [ - "!pip install ydf" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "6KEp0INfPOba" - }, - "outputs": [], - "source": [ - "import ydf\n", - "import pandas as pd\n", - "import numpy as np\n", - "\n", - "# Check the version of the packages\n", - "print(\"Found YDF v\" + ydf.__version__)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "3VVJL3VvPVr9" - }, - "source": [ - "## Migrating a training pipeline\n", - "\n", - "This section goes through a simple training / evaluation pipeline in YDF." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "yT6Y-2W0q-xB" - }, - "source": [ - "### Model training\n", - "\n", - "YDF and TF-DF have the same hyperparameters and the same default values, so most training pipelines can be migrated easily." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "JtjU9dd1TbSM" - }, - "source": [ - "#### Summary of changes\n", - "\n", - "The comparison below shows the differences between the two training pipelines side-by-side.\n", - "\n", - "\u003ctable\u003e\n", - "\u003ctr\u003e\n", - "\u003cth\u003eTF-DF\u003c/th\u003e\n", - "\u003cth\u003eYDF\u003c/th\u003e\n", - "\u003c/tr\u003e\n", - "\u003ctr\u003e\n", - "\u003ctd\u003e\n", - "\n", - "```python\n", - "# Install TF-DF\n", - "!pip install tensorflow tensorflow_decision_forests\n", - "\n", - "import tensorflow_decision_forests as tfdf\n", - "import tensorflow as tf\n", - "import pandas as pd\n", - "\n", - "# Load a dataset with Pandas\n", - "train_df = pd.read_csv(\"train.csv\")\n", - "test_df = pd.read_csv(\"test.csv\")\n", - "\n", - "# Convert the dataset to a TensorFlow Dataset.\n", - "train_ds = tfdf.keras.pd_dataframe_to_tf_dataset(train_df, label=\"my_label\")\n", - "test_ds = tfdf.keras.pd_dataframe_to_tf_dataset(test_df, label=\"my_label\")\n", - "\n", - "# Train a model\n", - "model = tfdf.keras.RandomForestModel(num_trees=500)\n", - "model.fit(train_ds)\n", - "\n", - "# Evaluate model.\n", - "model.compile([tf.keras.metrics.SparseCategoricalAccuracy(),tf.keras.metrics.AUC()])\n", - "model.evaluate(test_ds)\n", - "\n", - "# Saved model\n", - "model.save(\"project/model\")\n", - "```\n", - "\n", - "\u003c/td\u003e\n", - "\u003ctd\u003e\n", - "\n", - "```python\n", - "# Install YDF\n", - "pip install ydf\n", - "\n", - "import ydf\n", - "import pandas as pd\n", - "\n", - "# Load a dataset with Pandas\n", - "train_ds = pd.read_csv(\"train.csv\")\n", - "test_ds = pd.read_csv(\"test.csv\")\n", - "\n", - "# Train a model\n", - "model = ydf.RandomForestLearner(label=\"my_label\", num_trees=500).train(train_ds)\n", - "\n", - "# Evaluate a model (e.g. roc, accuracy, confusion matrix, confidence intervals)\n", - "model.evaluate(test_ds)\n", - "\n", - "# Save the model\n", - "model.save(\"/tmp/my_model\")\n", - "```\n", - "\n", - "\u003c/td\u003e\n", - "\u003c/tr\u003e\n", - "\u003c/table\u003e\n", - "\n", - "| |YDF |TF-DF |\n", - "|-----------------|------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|\n", - "|Dataset support |Pandas DataFrame, tf.Data.Dataset, Numpy arrays, CSV files |Tensorflow Datasets, \u003cbr\u003e DataFrame via `tfdf.keras.pd_dataframe_to_tf_dataset()`|\n", - "|Model training |`ydf.RandomForestLearner(label=label).train(train_ds_pd)`|`model = tfdf.keras.RandomForestModel()`\u003cbr\u003e`model.fit(train_ds)` |\n", - "|Output verbosity |Global setting `ydf.verbose(2)` |Per-model setting `verbose=2` in the model constructor. |\n", - "|Model compilation|Not necessary |`model.compile()` needed for additional metrics. |\n", - "|Hyperparameters |Set on the learner. **Same names and defaults as in TF-DF**. |Set on the model. |\n", - "|Label column |Argument `label=` on the learner |Second \"channel\" of the input datset |\n", - "|Example weights |Argument `weights=` on the learner |Third \"channel\" of the input datset |\n", - "|Model task |Argument `task=ydf.Task.REGRESSION` on the learner| Argument `task=tfdf.keras.Task.REGRESSION` on the model|\n", - "\n", - "Next, we run the YDF training code in a real example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "OZ3wqBIrQA0t" - }, - "outputs": [], - "source": [ - "ds_path = \"https://raw.githubusercontent.com/google/yggdrasil-decision-forests/main/yggdrasil_decision_forests/test_data/dataset\"\n", - "\n", - "# Download and load the dataset as Pandas DataFrames\n", - "train_ds = pd.read_csv(f\"{ds_path}/adult_train.csv\")\n", - "test_ds = pd.read_csv(f\"{ds_path}/adult_test.csv\")\n", - "\n", - "# Name of the label column.\n", - "label = \"income\"\n", - "\n", - "# Show extended logs.\n", - "ydf.verbose(2)\n", - "\n", - "# Train a Random Forest model with a simple hyperparameter\n", - "model = ydf.RandomForestLearner(label=label, num_trees=50).train(train_ds)\n", - "\n", - "# Make predictions with the model\n", - "predictions = model.predict(test_ds)\n", - "\n", - "# Show a summary of the model\n", - "model.describe()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gwPx3yyKImA2" - }, - "source": [ - "#### Model training - the sharp bits\n", - "\n", - "* YDF does not automatically tokenize string columns for use with column type CATEGORICAL_SET. Users need to provide their own tokenization if this column type should be used.\n", - "* TF-DF often transforms categorical columns to integers, while YDF does not. The models trained by TF-DF and YDF may therefore differ, even if trained with the same hyperparameters on the same datasets.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "GGOKEkRqVJwu" - }, - "source": [ - "## Model evaluation, analysis and storage\n", - "\n", - "YDF offers more advanced model evaluation and analysis functionality." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "aJ2hwbcUIFMw" - }, - "source": [ - "### Summary of changes\n", - "\n", - "| |YDF |TF-DF |\n", - "|-----------------------|----------------------------------------------------|-----------------------------------------------|\n", - "|Evaluation |`model.evaluate()` shows rich plots and many metrics|`model.evaluate()` shows few metrics, no plots |\n", - "|Self-Evaluation |`model.self_evaluation()` |`model.make_inspector().evaluation()` |\n", - "|Model format |YDF format. Export to SavedModel is possible |TensorFlow SavedModel |\n", - "|Model loading | `ydf.load_model()` |`tf_keras.models.load_model()`|\n", - "| | Loaded models are equivalent |Loaded models are inference-only|\n", - "|Variable Importances |`model.variable_importances()` |`model.make_inspector().variable_importances()`|\n", - "|Model analysis |`model.analyze(test_ds)` |Not available |\n", - "|Serving with TF Serving|Available with `model.to_tensorflow_saved_model()` |Available by default |\n", - "| Model inspector | Not required (functionality is on the model) | Required for many tasks|" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "3AdlQg1mgCdC" - }, - "source": [ - "### Model Evaluation and Self-Evaluation\n", - "\n", - "A model can be evaluated on a test dataset.\n", - "\n", - "As a quick, low-quality alternative, YDF models also provide a self-evaluation.\n", - "The exact logic of the self-evaluation depends on the model. For example, Random Forest will use Out-of-bag evaluation while Gradient Boosted Trees will use internal train-validation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "8FPJuevuTamr" - }, - "outputs": [], - "source": [ - "# In Colab, this prints a rich evaluation object.\n", - "model.evaluate(test_ds)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "OitJwlE7gv6S" - }, - "outputs": [], - "source": [ - "# Self-evaluation is often good, though it tends to be lower quality than evaluation on test data\n", - "model.self_evaluation()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Fb73ONBwWZ25" - }, - "source": [ - "### Saving and Loading\n", - "\n", - "The model can be saved to the YDF format for later re-use. For compatibility with TF Serving and other parts of the TensorFlow ecosystem, see Section *Export to TF Serving* below." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "nRqb8AbNWiAE" - }, - "outputs": [], - "source": [ - "model.save(\"/tmp/my_ydf_model\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "3ASIlbyQWhjX" - }, - "source": [ - "If you reload the model, it is functionally equivalent to the original model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "lyrDbxs5Xs07" - }, - "outputs": [], - "source": [ - "model_reloaded = ydf.load_model(\"/tmp/my_ydf_model\")\n", - "model_reloaded.describe()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_Ogchdn4IYDl" - }, - "source": [ - "### Import from / Export to TensorFlow\n", - "\n", - "YDF models can be exported to TensorFlow, e.g. for Serving with TF-Serving. See [the TF Serving tutorial](https://ydf.readthedocs.io/en/latest/tutorial/tf_serving) for a more detailed tutorial for exporting to TensorFlow." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "9OBLHOtCBLyB" - }, - "outputs": [], - "source": [ - "# Exporting requires TF-DF installed.\n", - "# !pip install tensorflow_decision_forests\n", - "model.to_tensorflow_saved_model(\"/tmp/my_tensorflow_saved_model\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DvuGpmjf-Qde" - }, - "source": [ - "TF-DF models can be imported to YDF. The imported model is generally equivalent to the original model and should return the same predictions. As the main difference, categorical columns in the imported model must be provided as strings instead of integers.\n", - "\n", - "Note that only TF-DF models containing a single Decision Forest (e.g. a Random Forest or a Gradient Boosted Tree) can be exported in YDF. Other parts of the model graph (e.g. neural networks) cannot be imported.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "hmWDidBW--aR" - }, - "outputs": [], - "source": [ - "# Import the TF-DF model. Provide its top-level directory containing the saved_model.pb file.\n", - "model_from_tfdf = ydf.from_tensorflow_decision_forests(\"/tmp/my_tensorflow_saved_model\")\n", - "model_from_tfdf.describe()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zr2KtrHoi73m" - }, - "source": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "GHgCuMJfjeR0" - }, - "source": [ - "### Model Analysis\n", - "\n", - "YDF can compute a detailed model analysis report on a test dataset, including more advanced variable importances." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "qj905VbLSMiw" - }, - "outputs": [], - "source": [ - "# Create a rich analysis report\n", - "model.analyze(test_ds)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "se88l1L0IHPB" - }, - "source": [ - "## Advanced topics: Inspection, Building, Tuning and Distributed Training\n", - "\n", - "YDF and TF-DF support a number of advanced features. This guide only outlines the most important changes when transitioning from TF-DF to YDF. For more information, please refer to the tutorials on https://ydf.readthedocs.org, in particular\n", - "\n", - "* [Inspecting Trees](https://ydf.readthedocs.io/en/latest/tutorial/inspecting_trees)\n", - "* [Editing Trees](https://ydf.readthedocs.io/en/latest/tutorial/editing_trees/)\n", - "* [Tuning](https://ydf.readthedocs.io/en/latest/tutorial/tuning)\n", - "* [Distributed Training](https://ydf.readthedocs.io/en/latest/tutorial/distributed_training/)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "tIZ1IBdhB1Va" - }, - "source": [ - "### Model inspector and builder\n", - "\n", - "YDF gives users more powerful methods to inspect models and modify models than TF-DF. These methods are now located directly on the model and are much faster than the ones exposed in TF-DF. The `inspector` and `builder` components from TF-DF are no longer necessary." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "g4ehKkmgB0wh" - }, - "outputs": [], - "source": [ - "# Plot a tree\n", - "model.print_tree(tree_idx=0)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "io04oEdQL1_w" - }, - "outputs": [], - "source": [ - "# Structural variable importances are available programatically.\n", - "model.variable_importances()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "JHs99oixF_5m" - }, - "outputs": [], - "source": [ - "# Access a tree directly\n", - "tree = model.get_tree(tree_idx=0)\n", - "\n", - "tree" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "HPWf4T7GGMRj" - }, - "outputs": [], - "source": [ - "# Modify the tree and add it to the model\n", - "if isinstance(tree.root.condition, ydf.tree.CategoricalIsInCondition):\n", - " tree.root.condition.mask = [1]\n", - "if isinstance(tree.root.condition, ydf.tree.NumericalHigherThanCondition):\n", - " tree.root.condition.threshold = 18.22\n", - "print(tree)\n", - "model.add_tree(tree)\n", - "model.print_tree(tree_idx=model_1.num_trees()-1)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "bujiG137IBd6" - }, - "source": [ - "### Hyperparameter tuning\n", - "\n", - "Hyperparameter tuning with YDF is very similar to hyperparameter tuning with TF-DF. Simply change `tfdf.tuner.RandomSearch()` to `ydf.RandomSearchTuner()` and apply its result as an argument of the learner. YDF then runs the same tuning algorithm with the same parameters.\n", - "\n", - "The [Keras Tuner](https://www.tensorflow.org/decision_forests/tutorials/automatic_tuning_colab#training_a_model_with_keras_tuner_alternative_approach) is not supported by YDF." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "xC-5mFTeJj5-" - }, - "outputs": [], - "source": [ - "# Decrease verbosity to avoid long logs\n", - "ydf.verbose(1)\n", - "\n", - "# Define the tuner with some options.\n", - "tuner = ydf.RandomSearchTuner(num_trials=20)\n", - "tuner.choice(\"shrinkage\", [0.2, 0.1, 0.05])\n", - "tuner.choice(\"subsample\", [1.0, 0.9, 0.8])\n", - "tuner.choice(\"max_depth\", [3, 4,5, 6])\n", - "\n", - "# Train a model with the tuner\n", - "model_tuned = ydf.GradientBoostedTreesLearner(\n", - " label=\"income\",\n", - " num_trees=100, # Used for all the trials.\n", - " tuner=tuner,\n", - ").train(train_ds)\n", - "\n", - "# See the \"Tuning\" tab in the description for details.\n", - "model_tuned.describe()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "HD42ZdUIGizV" - }, - "source": [ - "### Distributed Training / Tuning\n", - "\n", - "Distributed training in YDF requires datasets as a sequence of paths to dataset files for the individual workers to open. See the [YDF distributed training tutorial](https://ydf.readthedocs.io/en/latest/tutorial/distributed_training/#download-and-split-dataset) for details. Distributed training from a finite TensorFlow distributed dataset is not supported in YDF." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_vAzQgQLMDCp" - }, - "source": [ - "## Closing remarks\n", - "\n", - "The Google Decision Forests team wants to make the migration from TF-DF to YDF as easy as possible. If you have any questions, suggestions, issues or success stories, please contact us at decision-forests-contact@google.com." - ] - } - ], - "metadata": { - "colab": { - "last_runtime": { - "build_target": "", - "kind": "local" - }, - "private_outputs": true, - "provenance": [ - { - "file_id": "1B_nFEEclz0uvkvwOcgDGLB2VYihOccvx", - "timestamp": 1706180693865 - } - ] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Migrating to YDF" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[YDF](https://ydf.readthedocs.io/en/latest/) is Google's new library to train Decision Forests and the successor of [TensorFlow Decision Forests](https://tensorflow.org/decision_forests).\n", + "\n", + "Both libraries rely on the same high-performance C++ implementation called YDF C++ which have been developed for production since 2018. However, YDF is significantly more feature-rich, efficient, and easier to use than TF-DF. Migrating to YDF will reduce your development and maintenance cost while potentially improving the quality of your model.\n", + "\n", + "Most functions in TF-DF have their equivalent in YDF. The following table shows the mapping.\n", + "\n", + "**Note:** Many features / functions in YDF do not exist in TF-DF. Therefore, reading the [YDF's Getting started guide](https://ydf.readthedocs.io/en/latest/tutorial/getting_started/) is likely a good investment of your time.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
ActionTF-DFYDF
Train a model\n", + "\n", + "```python\n", + "tf_ds = tfdf.keras.pd_dataframe_to_tf_dataset(ds, label=\"l\")\n", + "model = tfdf.keras.RandomForestModel()\n", + "model.fit(tf_ds)\n", + "```\n", + "\n", + "\n", + "\n", + "```python\n", + "model = ydf.GradientBoostedTreesLearner(label=\"l\").train(ds)\n", + "```\n", + "\n", + "
Look at a model\n", + "\n", + "```python\n", + "model.summary()\n", + "```\n", + "\n", + "\n", + "\n", + "```python\n", + "model.describe()\n", + "```\n", + "\n", + "
Evaluatea a model\n", + "\n", + "```python\n", + "model.compile([\"accuracy\", tf.keras.metrics.AUC()])\n", + "model.evaluate(test_ds)\n", + "```\n", + "\n", + "\n", + "\n", + "```python\n", + "model.evaluate(ds)\n", + "```\n", + "\n", + "
Save a model\n", + "\n", + "```python\n", + "model.save(\"project/model\")\n", + "```\n", + "\n", + "\n", + "\n", + "```python\n", + "model.save(\"project/model\")\n", + "```\n", + "\n", + "
Load a model\n", + "\n", + "```python\n", + "model = tf_keras.models.load_model(\"project/model\")\n", + "```\n", + "\n", + "\n", + "\n", + "```python\n", + "model = ydf.load_model(\"project/model\")\n", + "```\n", + "\n", + "
Export model as TF SavedModel\n", + "\n", + "```python\n", + "model.save(\"project/model\")\n", + "```\n", + "\n", + "\n", + "\n", + "```python\n", + "model.to_tensorflow_saved_model(\"project/model\", mode=\"tf\")\n", + "```\n", + "\n", + "
\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Following is a 1:1 equivalent TF-DF and YDF code." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
TF-DFYDF
\n", + "\n", + "```python\n", + "!pip install tensorflow tensorflow_decision_forests # Install TF-DF\n", + "\n", + "import tensorflow_decision_forests as tfdf\n", + "import tensorflow as tf\n", + "import pandas as pd\n", + "\n", + "# Load a dataset with Pandas\n", + "train_df = pd.read_csv(\"train.csv\")\n", + "test_df = pd.read_csv(\"test.csv\")\n", + "\n", + "# Convert the dataset to a TensorFlow Dataset.\n", + "train_ds = tfdf.keras.pd_dataframe_to_tf_dataset(train_df, label=\"my_label\")\n", + "test_ds = tfdf.keras.pd_dataframe_to_tf_dataset(test_df, label=\"my_label\")\n", + "\n", + "# Train a model\n", + "model = tfdf.keras.RandomForestModel(num_trees=500)\n", + "model.fit(train_ds)\n", + "\n", + "# Evaluate model.\n", + "model.compile([tf.keras.metrics.SparseCategoricalAccuracy(),tf.keras.metrics.AUC()])\n", + "model.evaluate(test_ds)\n", + "\n", + "# Saved model\n", + "model.save(\"/tmp/my_model\")\n", + "```\n", + "\n", + "\n", + "\n", + "```python\n", + "pip install ydf # Install YDF\n", + "\n", + "import ydf\n", + "import pandas as pd\n", + "\n", + "# Load a dataset with Pandas\n", + "train_ds = pd.read_csv(\"train.csv\")\n", + "test_ds = pd.read_csv(\"test.csv\")\n", + "\n", + "# Train a model\n", + "model = ydf.RandomForestLearner(label=\"my_label\", num_trees=500).train(train_ds)\n", + "\n", + "# Evaluate model.\n", + "model.evaluate(test_ds)\n", + "\n", + "# Save the model\n", + "model.save(\"/tmp/my_model\")\n", + "```\n", + "\n", + "
" + ] + } + ], + "metadata": { + "colab": { + "last_runtime": { + "build_target": "", + "kind": "local" + }, + "private_outputs": true, + "provenance": [ + { + "file_id": "1B_nFEEclz0uvkvwOcgDGLB2VYihOccvx", + "timestamp": 1706180693865 } + ] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 0 + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 } diff --git a/documentation/public/mkdocs.yml b/documentation/public/mkdocs.yml index 7291c235..9b39b448 100644 --- a/documentation/public/mkdocs.yml +++ b/documentation/public/mkdocs.yml @@ -46,6 +46,7 @@ nav: - ❔ FAQ: faq.md - 📖 Glossary: glossary.md - 🤸 For Googlers: http://go/ydf + - ✒️ Blog: blog/index.md - Tasks solved by YDF: - Classification: tutorial/classification.ipynb - Regression: tutorial/regression.ipynb @@ -55,7 +56,7 @@ nav: - Deploying a model: - FastAPI + Docker: tutorial/to_docker.ipynb - In C++: tutorial/cpp.ipynb - - In JavaScript: https://www.npmjs.com/package/yggdrasil-decision-forests + - In JavaScript: https://www.npmjs.com/package/ydf-inference - With TF Serving: tutorial/tf_serving.ipynb # TODO: Add Benchmarking, C++, Go, Cli, Python, F Serving, and Js - Explainability / XAI: @@ -99,7 +100,7 @@ nav: # TODO: model inspection, manual tree creation, custom loss. - Other APIs: - CLI quickstart: cli_quickstart.md - - JavaScript: https://www.npmjs.com/package/yggdrasil-decision-forests + - JavaScript: javascript.md - CLI & C++ user manual: cli_user_manual.md - CLI commands: cli_commands.md - CLI examples: https://github.com/google/yggdrasil-decision-forests/tree/main/examples @@ -138,6 +139,7 @@ extra_javascript: plugins: - search - autorefs + - blog - mkdocstrings: default_handler: python handlers: @@ -149,6 +151,7 @@ plugins: preload_modules: - ydf # For prod - yggdrasil_decision_forests # For prod + - ydf.util show_root_heading: true heading_level: 2 inherited_members: true @@ -178,6 +181,7 @@ plugins: - gen-files: scripts: - copy_external.py + - update_scs_paths.py extra: analytics: diff --git a/documentation/public/readme.md b/documentation/public/readme.md index d9ebf269..e29a2197 100644 --- a/documentation/public/readme.md +++ b/documentation/public/readme.md @@ -7,5 +7,5 @@ python3 -m pip install -r third_party/yggdrasil_decision_forests/documentation/public/requirements.txt # Start a http server with the documentation -(cd third_party/yggdrasil_decision_forests/documentation/public && mkdocs serve -a localhost:8888) +(cd third_party/yggdrasil_decision_forests && mkdocs serve -a localhost:8888 -f documentation/public/mkdocs.yml) ``` diff --git a/documentation/public/update_scs_paths.py b/documentation/public/update_scs_paths.py new file mode 100644 index 00000000..0827b2d6 --- /dev/null +++ b/documentation/public/update_scs_paths.py @@ -0,0 +1,37 @@ +# Copyright 2022 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Update path to assets hosted on SCS. + +For example: + ../../non-github-assets/ydf_blog_header.png +Will become: + https://www.gstatic.com/ydf-docs-assets/ydf_blog_header.png +""" + +import mkdocs_gen_files + + +def process_md_file(path): + with open("documentation/public/docs/" + path, "r", encoding="utf-8") as f_in: + with mkdocs_gen_files.open(path, "w") as f_out: + data = f_in.read() + data = data.replace( + "../../non-github-assets/", + "https://www.gstatic.com/ydf-docs-assets/", + ) + f_out.write(data) + + +process_md_file("blog/posts/1_how_ml_models_generalize.md") diff --git a/third_party/rapidjson/BUILD b/third_party/nlohmann_json/BUILD similarity index 100% rename from third_party/rapidjson/BUILD rename to third_party/nlohmann_json/BUILD diff --git a/third_party/nlohmann_json/workspace.bzl b/third_party/nlohmann_json/workspace.bzl new file mode 100644 index 00000000..fdfa0d0e --- /dev/null +++ b/third_party/nlohmann_json/workspace.bzl @@ -0,0 +1,10 @@ +"""nlohmann_json project.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +def deps(): + http_archive( + name = "nlohmann_json", + sha256 = "a22461d13119ac5c78f205d3df1db13403e58ce1bb1794edc9313677313f4a9d", + urls = ["https://github.com/nlohmann/json/releases/download/v3.11.3/include.zip"], + ) diff --git a/third_party/rapidjson/rapidjson.BUILD b/third_party/rapidjson/rapidjson.BUILD deleted file mode 100644 index 44d0b577..00000000 --- a/third_party/rapidjson/rapidjson.BUILD +++ /dev/null @@ -1,46 +0,0 @@ -package( - default_visibility = ["//visibility:public"], - licenses = ["notice"], -) - -cc_library( - name = "rapidjson", - srcs = [ - "include/rapidjson/internal/biginteger.h", - "include/rapidjson/internal/diyfp.h", - "include/rapidjson/internal/dtoa.h", - "include/rapidjson/internal/ieee754.h", - "include/rapidjson/internal/itoa.h", - "include/rapidjson/internal/meta.h", - "include/rapidjson/internal/pow10.h", - "include/rapidjson/internal/regex.h", - "include/rapidjson/internal/stack.h", - "include/rapidjson/internal/strfunc.h", - "include/rapidjson/internal/strtod.h", - "include/rapidjson/internal/swap.h", - ], - hdrs = [ - "include/rapidjson/allocators.h", - "include/rapidjson/document.h", - "include/rapidjson/encodedstream.h", - "include/rapidjson/encodings.h", - "include/rapidjson/error/en.h", - "include/rapidjson/error/error.h", - "include/rapidjson/filereadstream.h", - "include/rapidjson/filewritestream.h", - "include/rapidjson/fwd.h", - "include/rapidjson/istreamwrapper.h", - "include/rapidjson/memorybuffer.h", - "include/rapidjson/memorystream.h", - "include/rapidjson/ostreamwrapper.h", - "include/rapidjson/pointer.h", - "include/rapidjson/prettywriter.h", - "include/rapidjson/rapidjson.h", - "include/rapidjson/reader.h", - "include/rapidjson/schema.h", - "include/rapidjson/stream.h", - "include/rapidjson/stringbuffer.h", - "include/rapidjson/writer.h", - ], - includes = ["include"], -) diff --git a/third_party/rapidjson/workspace.bzl b/third_party/rapidjson/workspace.bzl deleted file mode 100644 index 9928f3b8..00000000 --- a/third_party/rapidjson/workspace.bzl +++ /dev/null @@ -1,12 +0,0 @@ -"""rapidjson project.""" - -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -def deps(prefix = ""): - http_archive( - name = "com_github_tencent_rapidjson", - url = "https://github.com/Tencent/rapidjson/archive/v1.1.0.zip", - sha256 = "8e00c38829d6785a2dfb951bb87c6974fa07dfe488aa5b25deec4b8bc0f6a3ab", - strip_prefix = "rapidjson-1.1.0", - build_file = prefix + "//third_party/rapidjson:rapidjson.BUILD", - ) diff --git a/yggdrasil_decision_forests/dataset/BUILD b/yggdrasil_decision_forests/dataset/BUILD index d42172b3..90d9d201 100644 --- a/yggdrasil_decision_forests/dataset/BUILD +++ b/yggdrasil_decision_forests/dataset/BUILD @@ -8,6 +8,7 @@ package( cc_library_ydf( name = "all_dataset_formats", deps = [ + ":avro_example", ":csv_example_reader", ":csv_example_writer", "//yggdrasil_decision_forests/dataset/tensorflow_no_dep:tf_record_tf_example", @@ -372,6 +373,47 @@ cc_library_ydf( ], ) +cc_library_ydf( + name = "avro", + srcs = ["avro.cc"], + hdrs = ["avro.h"], + deps = [ + ":data_spec", + ":data_spec_cc_proto", + ":data_spec_inference", + "//yggdrasil_decision_forests/utils:bytestream", + "//yggdrasil_decision_forests/utils:filesystem", + "//yggdrasil_decision_forests/utils:status_macros", + "//yggdrasil_decision_forests/utils:zlib", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + "@nlohmann_json//:json", + ], +) + +cc_library_ydf( + name = "avro_example", + srcs = ["avro_example.cc"], + hdrs = ["avro_example.h"], + deps = [ + ":avro", + ":data_spec", + ":data_spec_cc_proto", + ":data_spec_inference", + ":example_reader_interface", + "//yggdrasil_decision_forests/utils:sharded_io", + "//yggdrasil_decision_forests/utils:status_macros", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + ], + alwayslink = 1, +) + # Proto # ======== @@ -619,3 +661,42 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "avro_test", + srcs = ["avro_test.cc"], + data = [ + "//yggdrasil_decision_forests/test_data", + ], + deps = [ + ":avro", + "//yggdrasil_decision_forests/utils:bytestream", + "//yggdrasil_decision_forests/utils:filesystem", + "//yggdrasil_decision_forests/utils:logging", + "//yggdrasil_decision_forests/utils:test", + "//yggdrasil_decision_forests/utils:testing_macros", + "@com_google_absl//absl/status", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "avro_example_test", + srcs = ["avro_example_test.cc"], + data = [ + "//yggdrasil_decision_forests/test_data", + ], + deps = [ + ":avro_example", + ":data_spec", + ":data_spec_cc_proto", + ":data_spec_inference", + ":example_reader", + "//yggdrasil_decision_forests/utils:filesystem", + "//yggdrasil_decision_forests/utils:logging", + "//yggdrasil_decision_forests/utils:test", + "//yggdrasil_decision_forests/utils:testing_macros", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/yggdrasil_decision_forests/dataset/avro.cc b/yggdrasil_decision_forests/dataset/avro.cc new file mode 100644 index 00000000..8f26ecb3 --- /dev/null +++ b/yggdrasil_decision_forests/dataset/avro.cc @@ -0,0 +1,589 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "yggdrasil_decision_forests/dataset/avro.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/memory/memory.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include "yggdrasil_decision_forests/utils/bytestream.h" +#include "yggdrasil_decision_forests/utils/filesystem.h" +#include "yggdrasil_decision_forests/utils/status_macros.h" +#include "yggdrasil_decision_forests/utils/zlib.h" + +#define MAYBE_SKIP_OPTIONAL(FIELD) \ + if (field.optional) { \ + ASSIGN_OR_RETURN(const auto _has_value, \ + current_block_reader_->ReadByte()); \ + if (!_has_value) { \ + return absl::nullopt; \ + } \ + } + +namespace yggdrasil_decision_forests::dataset::avro { +namespace { +absl::Status JsonIsObject(const nlohmann::json& value) { + if (!value.is_object()) { + return absl::InvalidArgumentError("Schema is not a json object"); + } + return absl::OkStatus(); +} + +absl::StatusOr GetJsonField(const nlohmann::json& value, + const std::string& name) { + auto it = value.find(name); + if (it == value.end()) { + return absl::InvalidArgumentError( + absl::StrCat("Field \"", name, "\" not found")); + } + return &*it; +} + +absl::StatusOr GetJsonStringField( + const nlohmann::json& value, const std::string& name) { + auto it = value.find(name); + if (it == value.end()) { + return absl::InvalidArgumentError( + absl::StrCat("Field \"", name, "\" not found")); + } + if (!it->is_string()) { + return absl::InvalidArgumentError( + absl::StrCat("Field \"", name, "\" is not a string")); + } + return it->get(); +} + +} // namespace + +absl::StatusOr ParseType(absl::string_view key) { + if (key == "null") { + return AvroType::kNull; + } else if (key == "boolean") { + return AvroType::kBoolean; + } else if (key == "long") { + return AvroType::kLong; + } else if (key == "int") { + return AvroType::kInt; + } else if (key == "float") { + return AvroType::kFloat; + } else if (key == "double") { + return AvroType::kDouble; + } else if (key == "string") { + return AvroType::kString; + } else if (key == "bytes") { + return AvroType::kBytes; + } else if (key == "array") { + return AvroType::kArray; + } + return absl::InvalidArgumentError(absl::StrCat("Unsupported type=", key)); +} + +std::string TypeToString(AvroType type) { + switch (type) { + case AvroType::kNull: + return "null"; + case AvroType::kBoolean: + return "boolean"; + case AvroType::kLong: + return "long"; + case AvroType::kInt: + return "int"; + case AvroType::kFloat: + return "float"; + case AvroType::kDouble: + return "double"; + case AvroType::kString: + return "string"; + case AvroType::kBytes: + return "bytes"; + case AvroType::kArray: + return "array"; + case AvroType::kUnknown: + return "unknown"; + } +} + +std::ostream& operator<<(std::ostream& os, const AvroField& field) { + os << "AvroField(name=\"" << field.name + << "\", type=" << TypeToString(field.type) + << ", sub_type=" << TypeToString(field.sub_type) + << ", optional=" << field.optional << ")"; + return os; +} + +AvroReader::AvroReader(std::unique_ptr&& stream) + : stream_(std::move(stream)) {} + +AvroReader::~AvroReader() { Close().IgnoreError(); } + +absl::StatusOr> AvroReader::Create( + absl::string_view path) { + ASSIGN_OR_RETURN(std::unique_ptr file_handle, + file::OpenInputFile(path)); + auto reader = absl::WrapUnique(new AvroReader(std::move(file_handle))); + ASSIGN_OR_RETURN(const auto schema, reader->ReadHeader()); + return std::move(reader); +} + +absl::Status AvroReader::Close() { + if (stream_) { + RETURN_IF_ERROR(stream_->Close()); + stream_.reset(); + } + return absl::OkStatus(); +} + +absl::StatusOr AvroReader::ExtractSchema(absl::string_view path) { + ASSIGN_OR_RETURN(std::unique_ptr file_handle, + file::OpenInputFile(path)); + AvroReader reader(std::move(file_handle)); + ASSIGN_OR_RETURN(const auto schema, reader.ReadHeader()); + RETURN_IF_ERROR(reader.Close()); + return schema; +} + +absl::StatusOr AvroReader::ReadHeader() { + if (!sync_marker_.empty()) { + return absl::InvalidArgumentError("The header was already read"); + } + + // Magic number. + char buffer[4]; + ASSIGN_OR_RETURN(bool has_read, stream_->ReadExactly(buffer, 4)); + STATUS_CHECK(has_read); + if (buffer[0] != 'O' || buffer[1] != 'b' || buffer[2] != 'j' || + buffer[3] != 1) { + return absl::InvalidArgumentError("Not an Avro file"); + } + + // Read the meta-data. + std::string schema; + while (true) { + ASSIGN_OR_RETURN(const size_t num_blocks, + internal::ReadInteger(stream_.get())); + if (num_blocks == 0) { + break; + } + for (size_t block_idx = 0; block_idx < num_blocks; block_idx++) { + std::string key; + std::string value; + RETURN_IF_ERROR(internal::ReadString(stream_.get(), &key)); + RETURN_IF_ERROR(internal::ReadString(stream_.get(), &value)); + if (key == "avro.codec") { + if (value == "null") { + codec_ = AvroCodec::kNull; + } else if (value == "deflate") { + codec_ = AvroCodec::kDeflate; + } else { + return absl::InvalidArgumentError( + absl::StrCat("Unsupported codec: ", value)); + } + } else if (key == "avro.schema") { + schema = value; + } + } + } + + sync_marker_.resize(16); + ASSIGN_OR_RETURN(has_read, stream_->ReadExactly(&sync_marker_[0], 16)); + STATUS_CHECK(has_read); + + // Parse the meta-data. + nlohmann::json json_schema = nlohmann::json::parse(schema); + + if (json_schema.is_discarded()) { + return absl::InvalidArgumentError("Failed to parse schema"); + } + RETURN_IF_ERROR(JsonIsObject(json_schema)); + ASSIGN_OR_RETURN(const auto json_type, + GetJsonStringField(json_schema, "type")); + STATUS_CHECK_EQ(json_type, "record"); + ASSIGN_OR_RETURN(const auto* json_fields, + GetJsonField(json_schema, "fields")); + STATUS_CHECK(json_fields->is_array()); + + for (const auto& json_field : json_fields->items()) { + ASSIGN_OR_RETURN(const auto json_name, + GetJsonStringField(json_field.value(), "name")); + ASSIGN_OR_RETURN(const auto* json_sub_type, + GetJsonField(json_field.value(), "type")); + AvroType type = AvroType::kUnknown; + AvroType sub_type = AvroType::kUnknown; + bool optional = false; + + if (json_sub_type->is_string()) { + const std::string str_type = json_sub_type->get(); + // Scalar + ASSIGN_OR_RETURN(type, ParseType(str_type)); + } else if (json_sub_type->is_array()) { + // Optional + optional = true; + + // const auto& json_sub_type_array = json_sub_type->GetArray(); + if (json_sub_type->size() == 2 && (*json_sub_type)[0].is_string() && + (*json_sub_type)[0].get() == std::string("null")) { + if ((*json_sub_type)[1].is_string()) { + // Scalar + ASSIGN_OR_RETURN(type, + ParseType((*json_sub_type)[1].get())); + } + if ((*json_sub_type)[1].is_object()) { + // Array + ASSIGN_OR_RETURN(const auto json_sub_sub_type, + GetJsonStringField((*json_sub_type)[1], "type")); + ASSIGN_OR_RETURN(const auto json_items, + GetJsonStringField((*json_sub_type)[1], "items")); + if (json_sub_sub_type == "array") { + ASSIGN_OR_RETURN(sub_type, ParseType(json_items)); + type = AvroType::kArray; + } + } + } + } else if (json_sub_type->is_object()) { + // Array + ASSIGN_OR_RETURN(const auto json_sub_sub_type, + GetJsonStringField(*json_sub_type, "type")); + ASSIGN_OR_RETURN(const auto json_items, + GetJsonStringField(*json_sub_type, "items")); + + if (json_sub_sub_type == "array") { + ASSIGN_OR_RETURN(sub_type, ParseType(json_items)); + type = AvroType::kArray; + } + } + + if (type == AvroType::kUnknown) { + return absl::InvalidArgumentError(absl::StrCat( + "Unsupported type=", json_sub_type->get(), + " for field \"", json_name, + "\". YDF only supports the following types: null, " + "boolean, long, int, float, double, string, bytes, array of , [null, ], [null, array[]].")); + } + + fields_.push_back(AvroField{ + .name = json_name, + .type = type, + .sub_type = sub_type, + .optional = optional, + }); + } + + return schema; +} + +absl::StatusOr AvroReader::ReadNextBlock() { + const auto num_objects_in_block_or = internal::ReadInteger(stream_.get()); + if (!num_objects_in_block_or.ok()) { + return false; + } + num_objects_in_current_block_ = num_objects_in_block_or.value(); + next_object_in_current_block_ = 0; + + ASSIGN_OR_RETURN(const auto block_size, internal::ReadInteger(stream_.get())); + + current_block_.resize(block_size); + ASSIGN_OR_RETURN(bool has_read, + stream_->ReadExactly(¤t_block_[0], block_size)); + if (!has_read) { + return absl::InvalidArgumentError("Unexpected end of stream"); + } + + switch (codec_) { + case AvroCodec::kNull: + current_block_reader_ = utils::StringViewInputByteStream(current_block_); + break; + case AvroCodec::kDeflate: + zlib_working_buffer_.resize(1024 * 1024); + RETURN_IF_ERROR(utils::Inflate( + current_block_, ¤t_block_decompressed_, &zlib_working_buffer_)); + current_block_reader_ = + utils::StringViewInputByteStream(current_block_decompressed_); + break; + } + + new_sync_marker_.resize(16); + ASSIGN_OR_RETURN(has_read, stream_->ReadExactly(&new_sync_marker_[0], 16)); + STATUS_CHECK(has_read); + if (new_sync_marker_ != sync_marker_) { + return absl::InvalidArgumentError( + "Non matching sync marker. The file looks corrupted."); + } + + return true; +} + +absl::StatusOr AvroReader::ReadNextRecord() { + if (!current_block_reader_.has_value() || + current_block_reader_.value().left() == 0) { + // Read a new block of data. + DCHECK_EQ(next_object_in_current_block_, num_objects_in_current_block_); + ASSIGN_OR_RETURN(const bool has_next_block, ReadNextBlock()); + if (!has_next_block) { + return false; + } + } + next_object_in_current_block_++; + return true; +} + +absl::StatusOr> AvroReader::ReadNextFieldBoolean( + const AvroField& field) { + MAYBE_SKIP_OPTIONAL(field); + ASSIGN_OR_RETURN(const auto value, current_block_reader_->ReadByte()); + return value; +} + +absl::StatusOr> AvroReader::ReadNextFieldInteger( + const AvroField& field) { + MAYBE_SKIP_OPTIONAL(field); + return internal::ReadInteger(¤t_block_reader_.value()); +} + +absl::StatusOr> AvroReader::ReadNextFieldFloat( + const AvroField& field) { + MAYBE_SKIP_OPTIONAL(field); + return internal::ReadFloat(¤t_block_reader_.value()); +} + +absl::StatusOr> AvroReader::ReadNextFieldDouble( + const AvroField& field) { + MAYBE_SKIP_OPTIONAL(field); + return internal::ReadDouble(¤t_block_reader_.value()); +} + +absl::StatusOr AvroReader::ReadNextFieldString(const AvroField& field, + std::string* value) { + if (field.optional) { + ASSIGN_OR_RETURN(const auto has_value, current_block_reader_->ReadByte()); + if (!has_value) { + return false; + } + } + RETURN_IF_ERROR(internal::ReadString(¤t_block_reader_.value(), value)); + return true; +} + +absl::StatusOr AvroReader::ReadNextFieldArrayFloat( + const AvroField& field, std::vector* values) { + values->clear(); + if (field.optional) { + ASSIGN_OR_RETURN(const auto has_value, current_block_reader_->ReadByte()); + if (!has_value) { + return false; + } + } + while (true) { + ASSIGN_OR_RETURN(auto num_values, + internal::ReadInteger(¤t_block_reader_.value())); + values->reserve(values->size() + num_values); + if (num_values == 0) { + break; + } + if (num_values < 0) { + ASSIGN_OR_RETURN(auto block_size, + internal::ReadInteger(¤t_block_reader_.value())); + (void)block_size; + num_values = -num_values; + } + for (size_t value_idx = 0; value_idx < num_values; value_idx++) { + ASSIGN_OR_RETURN(auto value, + internal::ReadFloat(¤t_block_reader_.value())); + values->push_back(value); + } + } + + return true; +} + +absl::StatusOr AvroReader::ReadNextFieldArrayDouble( + const AvroField& field, std::vector* values) { + values->clear(); + if (field.optional) { + ASSIGN_OR_RETURN(const auto has_value, current_block_reader_->ReadByte()); + if (!has_value) { + return false; + } + } + while (true) { + ASSIGN_OR_RETURN(auto num_values, + internal::ReadInteger(¤t_block_reader_.value())); + values->reserve(values->size() + num_values); + if (num_values == 0) { + break; + } + if (num_values < 0) { + ASSIGN_OR_RETURN(auto block_size, + internal::ReadInteger(¤t_block_reader_.value())); + (void)block_size; + num_values = -num_values; + } + for (size_t value_idx = 0; value_idx < num_values; value_idx++) { + ASSIGN_OR_RETURN(auto value, + internal::ReadDouble(¤t_block_reader_.value())); + values->push_back(value); + } + } + + return true; +} + +absl::StatusOr AvroReader::ReadNextFieldArrayDoubleIntoFloat( + const AvroField& field, std::vector* values) { + values->clear(); + if (field.optional) { + ASSIGN_OR_RETURN(const auto has_value, current_block_reader_->ReadByte()); + if (!has_value) { + return false; + } + } + while (true) { + ASSIGN_OR_RETURN(auto num_values, + internal::ReadInteger(¤t_block_reader_.value())); + if (num_values == 0) { + break; + } + values->reserve(values->size() + num_values); + if (num_values < 0) { + ASSIGN_OR_RETURN(auto block_size, + internal::ReadInteger(¤t_block_reader_.value())); + (void)block_size; + num_values = -num_values; + } + for (size_t value_idx = 0; value_idx < num_values; value_idx++) { + ASSIGN_OR_RETURN(auto value, + internal::ReadDouble(¤t_block_reader_.value())); + values->push_back(value); + } + } + + return true; +} + +absl::StatusOr AvroReader::ReadNextFieldArrayString( + const AvroField& field, std::vector* values) { + if (field.optional) { + ASSIGN_OR_RETURN(const auto has_value, current_block_reader_->ReadByte()); + if (!has_value) { + return false; + } + } + + while (true) { + ASSIGN_OR_RETURN(auto num_values, + internal::ReadInteger(¤t_block_reader_.value())); + if (num_values == 0) { + break; + } + values->reserve(values->size() + num_values); + if (num_values < 0) { + ASSIGN_OR_RETURN(auto block_size, + internal::ReadInteger(¤t_block_reader_.value())); + (void)block_size; + num_values = -num_values; + } + for (size_t value_idx = 0; value_idx < num_values; value_idx++) { + std::string sub_value; + RETURN_IF_ERROR( + internal::ReadString(¤t_block_reader_.value(), &sub_value)); + values->push_back(std::move(sub_value)); + } + } + + return true; +} + +namespace internal { + +absl::StatusOr ReadInteger(utils::InputByteStream* stream) { + // Note: Integers are encoded with variable length + zigzag encoding. + + // Variable length decoding + size_t value = 0; + size_t shift = 0; + char buffer; + while (true) { + ASSIGN_OR_RETURN(bool has_read, stream->ReadExactly(&buffer, 1)); + if (!has_read) { + return absl::InvalidArgumentError("Unexpected end of stream"); + } + value |= static_cast(buffer & 0x7F) << shift; + if ((buffer & 0x80) == 0) { + break; + } + shift += 7; + } + + // Zigzag decoding + return (value >> 1) ^ -(value & 1); +} + +absl::Status ReadString(utils::InputByteStream* stream, std::string* value) { + ASSIGN_OR_RETURN(const auto length, ReadInteger(stream)); + value->resize(length); + if (length > 0) { + ASSIGN_OR_RETURN(bool has_read, stream->ReadExactly(&(*value)[0], length)); + if (!has_read) { + return absl::InvalidArgumentError("Unexpected end of stream"); + } + } + return absl::OkStatus(); +} + +absl::StatusOr ReadDouble(utils::InputByteStream* stream) { + double value; + ASSIGN_OR_RETURN(bool has_read, + stream->ReadExactly(reinterpret_cast(&value), 8)); + if (!has_read) { + return absl::InvalidArgumentError("Unexpected end of stream"); + } + return value; +} + +absl::StatusOr ReadFloat(utils::InputByteStream* stream) { + float value; + ASSIGN_OR_RETURN(bool has_read, + stream->ReadExactly(reinterpret_cast(&value), 4)); + if (!has_read) { + return absl::InvalidArgumentError("Unexpected end of stream"); + } + return value; +} + +absl::StatusOr ReadBoolean(utils::InputByteStream* stream) { + char value; + ASSIGN_OR_RETURN(bool has_read, stream->ReadExactly(&value, 1)); + if (!has_read) { + return absl::InvalidArgumentError("Unexpected end of stream"); + } + return value != 0; +} + +} // namespace internal + +} // namespace yggdrasil_decision_forests::dataset::avro diff --git a/yggdrasil_decision_forests/dataset/avro.h b/yggdrasil_decision_forests/dataset/avro.h new file mode 100644 index 00000000..bf2c7d38 --- /dev/null +++ b/yggdrasil_decision_forests/dataset/avro.h @@ -0,0 +1,159 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Utility to read Avro files. + +#ifndef YGGDRASIL_DECISION_FORESTS_DATASET_AVRO_H_ +#define YGGDRASIL_DECISION_FORESTS_DATASET_AVRO_H_ + +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "yggdrasil_decision_forests/utils/bytestream.h" +#include "yggdrasil_decision_forests/utils/filesystem.h" + +namespace yggdrasil_decision_forests::dataset::avro { + +enum class AvroType { + kUnknown = 0, + kNull = 1, + kBoolean = 2, + kInt = 3, + kLong = 4, + kFloat = 5, + kDouble = 6, + kString = 7, + kBytes = 8, + kArray = 9, +}; + +std::string TypeToString(AvroType type); +absl::StatusOr ParseType(absl::string_view key); + +enum class AvroCodec { + kNull = 0, + kDeflate = 1, +}; + +struct AvroField { + std::string name; + AvroType type; + AvroType sub_type = AvroType::kUnknown; // Only used if type==kArray. + bool optional = false; // If the field is ["null", ]. + + bool operator==(const AvroField& other) const { + return name == other.name && type == other.type && + optional == other.optional && sub_type == other.sub_type; + } + + friend std::ostream& operator<<(std::ostream& os, const AvroField& field); +}; + +// Class to read Avro files. +// Avro 1.12.0 file format: +// https://avro.apache.org/docs/1.12.0/specification/ +class AvroReader { + public: + // Creates a reader for the given Avro file. + static absl::StatusOr> Create( + absl::string_view path); + + // Extracts the schema of the given Avro file. + static absl::StatusOr ExtractSchema(absl::string_view path); + + // Reads the next record. Returns false if the end of the file is reached. + absl::StatusOr ReadNextRecord(); + + // Skips all the fields (i.e., read with ReadNextField*). + absl::Status SkipAllFieldsInRecord(); + + // Reads the next field. Returns nullopt if the field is optional and not set. + absl::StatusOr> ReadNextFieldBoolean( + const AvroField& field); + absl::StatusOr> ReadNextFieldInteger( + const AvroField& field); + absl::StatusOr> ReadNextFieldFloat( + const AvroField& field); + absl::StatusOr> ReadNextFieldDouble( + const AvroField& field); + absl::StatusOr ReadNextFieldString(const AvroField& field, + std::string* value); + absl::StatusOr ReadNextFieldArrayFloat(const AvroField& field, + std::vector* values); + absl::StatusOr ReadNextFieldArrayDouble(const AvroField& field, + std::vector* values); + absl::StatusOr ReadNextFieldArrayDoubleIntoFloat( + const AvroField& field, std::vector* values); + absl::StatusOr ReadNextFieldArrayString( + const AvroField& field, std::vector* values); + + // Closes the reader. + absl::Status Close(); + + ~AvroReader(); + + // Returns the fields of the Avro file. + const std::vector& fields() const { return fields_; } + + const std::string& sync_marker() const { return sync_marker_; } + + private: + AvroReader(std::unique_ptr&& stream); + + // Reads the header of the Avro file. Should be called only once. + absl::StatusOr ReadHeader(); + + // Reads the next block of the Avro file. + absl::StatusOr ReadNextBlock(); + + std::unique_ptr stream_; + + std::vector fields_; + + std::string sync_marker_; + std::string new_sync_marker_; + + AvroCodec codec_ = AvroCodec::kNull; + + // Raw and uncompressed data of the current block. + std::string current_block_; + std::string current_block_decompressed_; + std::string zlib_working_buffer_; + absl::optional current_block_reader_; + + size_t num_objects_in_current_block_ = 0; + size_t next_object_in_current_block_ = 0; +}; + +namespace internal { + +absl::Status ReadString(utils::InputByteStream* stream, std::string* value); +absl::StatusOr ReadInteger(utils::InputByteStream* stream); +absl::StatusOr ReadDouble(utils::InputByteStream* stream); +absl::StatusOr ReadFloat(utils::InputByteStream* stream); +absl::StatusOr ReadBoolean(utils::InputByteStream* stream); + +} // namespace internal + +} // namespace yggdrasil_decision_forests::dataset::avro + +#endif // YGGDRASIL_DECISION_FORESTS_DATASET_AVRO_H_ diff --git a/yggdrasil_decision_forests/dataset/avro_example.cc b/yggdrasil_decision_forests/dataset/avro_example.cc new file mode 100644 index 00000000..46fec75f --- /dev/null +++ b/yggdrasil_decision_forests/dataset/avro_example.cc @@ -0,0 +1,756 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "yggdrasil_decision_forests/dataset/avro_example.h" + +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "yggdrasil_decision_forests/dataset/avro.h" +#include "yggdrasil_decision_forests/dataset/data_spec.h" +#include "yggdrasil_decision_forests/dataset/data_spec.pb.h" +#include "yggdrasil_decision_forests/dataset/data_spec_inference.h" +#include "yggdrasil_decision_forests/utils/status_macros.h" + +namespace yggdrasil_decision_forests::dataset::avro { +namespace { + +// Computes the mapping from Avro field index to dataspec column index and +// dataspec unroll index. +// +// Args: +// fields: Avro fields. +// dataspec: Dataspec. +// univariate_field_idx_to_column_idx: Mapping between the Avro field index +// and the column index for the univariate features. -1's are used for +// ignored fields. +// multivariate_field_idx_to_unroll_idx: Mapping from Avro field index +// to dataspec unroll index for multivariate fields. -1's are used for +// ignored fields. +absl::Status ComputeReadingMaps( + const std::vector& fields, + const dataset::proto::DataSpecification& dataspec, + std::vector* univariate_field_idx_to_column_idx, + std::vector* multivariate_field_idx_to_unroll_idx) { + univariate_field_idx_to_column_idx->assign(fields.size(), -1); + multivariate_field_idx_to_unroll_idx->assign(fields.size(), -1); + int field_idx = 0; + for (const auto& field : fields) { + if (field.type == AvroType::kArray) { + const auto col_idx = GetOptionalColumnIdxFromName(field.name, dataspec); + if (col_idx.has_value()) { + // A multidimensional feature e.g. catset. + (*univariate_field_idx_to_column_idx)[field_idx] = col_idx.value(); + } else { + // Unroll the feature into multiple single dimensional features. + int unstacked_idx = 0; + for (const auto& unstacked : dataspec.unstackeds()) { + if (unstacked.original_name() == field.name) { + (*multivariate_field_idx_to_unroll_idx)[field_idx] = unstacked_idx; + break; + } + unstacked_idx++; + } + } + } else if (field.type != AvroType::kNull) { + // A single dimensional feature. + const auto col_idx = GetOptionalColumnIdxFromName(field.name, dataspec); + if (col_idx.has_value()) { + (*univariate_field_idx_to_column_idx)[field_idx] = col_idx.value(); + } + } + field_idx++; + } + return absl::OkStatus(); +} + +// Populates the dataspec with new columns the first time a multivariate field +// is seen (it is not possible to populate it before because the dimension is +// not known). +template +absl::Status InitializeUnstackedColumn( + const AvroField& field, const bool has_value, const std::vector& values, + const size_t record_idx, const size_t field_idx, + const std::vector& univariate_field_idx_to_column_idx, + const std::vector& multivariate_field_idx_to_unroll_idx, + const std::vector& unstacked_guides, + proto::DataSpecification* dataspec, + proto::DataSpecificationAccumulator* accumulator) { + // Check if field used. + const auto unstacked_idx = multivariate_field_idx_to_unroll_idx[field_idx]; + auto* unstacked = dataspec->mutable_unstackeds(unstacked_idx); + + if (has_value) { + if (!unstacked->has_size()) { + // First time the field is seen with values. Let's create the + // dataspec. + + // Populate unstack. + unstacked->set_begin_column_idx(dataspec->columns_size()); + unstacked->set_size(values.size()); + // Create columns. + const auto sub_col_names = + UnstackedColumnNamesV2(field.name, values.size()); + for (const auto& sub_col_name : sub_col_names) { + auto* col_spec = dataspec->add_columns(); + col_spec->set_name(sub_col_name); + col_spec->set_type(unstacked->type()); + col_spec->set_count_nas(record_idx); + RETURN_IF_ERROR(UpdateSingleColSpecWithGuideInfo( + unstacked_guides[unstacked_idx], col_spec)); + accumulator->add_columns(); + } + } else { + // Check number of values + if (values.size() != unstacked->size()) { + return absl::InvalidArgumentError( + absl::StrCat("Inconsistent number of values for field ", field.name, + ". All the non-missing values should have the same " + "length. ", + unstacked->size(), " vs ", values.size())); + } + } + } + return absl::OkStatus(); +} + +} // namespace + +absl::Status AvroExampleReader::Implementation::OpenShard( + absl::string_view path) { + const bool is_first_file = reader_ == nullptr; + auto save_previous_reader = std::move(reader_); + ASSIGN_OR_RETURN(reader_, AvroReader::Create(path)); + if (is_first_file) { + RETURN_IF_ERROR(ComputeReadingMaps(reader_->fields(), dataspec_, + &univariate_field_idx_to_column_idx_, + &multivariate_field_idx_to_unroll_idx_)); + } else { + if (save_previous_reader->fields() != reader_->fields()) { + return absl::InvalidArgumentError( + "All the files in the same shard should have the same schema."); + } + } + return absl::OkStatus(); +} + +absl::StatusOr AvroExampleReader::Implementation::NextInShard( + proto::Example* example) { + example->clear_attributes(); + while (example->attributes_size() < dataspec_.columns_size()) { + example->add_attributes(); + } + + ASSIGN_OR_RETURN(const bool has_record, reader_->ReadNextRecord()); + if (!has_record) { + return false; + } + + int field_idx = 0; + for (const auto& field : reader_->fields()) { + switch (field.type) { + case AvroType::kUnknown: + case AvroType::kNull: + break; + + case AvroType::kBoolean: { + ASSIGN_OR_RETURN(const auto value, + reader_->ReadNextFieldBoolean(field)); + const int col_idx = univariate_field_idx_to_column_idx_[field_idx]; + if (col_idx == -1) { + // Ignore field. + break; + } + if (!value.has_value()) { + break; + } + example->mutable_attributes(col_idx)->set_boolean(*value); + } break; + + case AvroType::kLong: + case AvroType::kInt: { + ASSIGN_OR_RETURN(const auto value, + reader_->ReadNextFieldInteger(field)); + const int col_idx = univariate_field_idx_to_column_idx_[field_idx]; + if (col_idx == -1) { + // Ignore field. + break; + } + if (!value.has_value()) { + break; + } + example->mutable_attributes(col_idx)->set_numerical(*value); + } break; + + case AvroType::kDouble: + case AvroType::kFloat: { + absl::optional value; + if (field.type == AvroType::kFloat) { + ASSIGN_OR_RETURN(value, reader_->ReadNextFieldFloat(field)); + } else { + ASSIGN_OR_RETURN(value, reader_->ReadNextFieldDouble(field)); + } + if (value.has_value() && std::isnan(*value)) { + value = absl::nullopt; + } + const int col_idx = univariate_field_idx_to_column_idx_[field_idx]; + if (col_idx == -1) { + // Ignore field. + break; + } + if (!value.has_value()) { + break; + } + example->mutable_attributes(col_idx)->set_numerical(*value); + } break; + + case AvroType::kBytes: + case AvroType::kString: { + std::string value; + ASSIGN_OR_RETURN(const auto has_value, + reader_->ReadNextFieldString(field, &value)); + const int col_idx = univariate_field_idx_to_column_idx_[field_idx]; + if (col_idx == -1) { + // Ignore field. + break; + } + if (!has_value) { + break; + } + ASSIGN_OR_RETURN(auto int_value, + CategoricalStringToValueWithStatus( + value, dataspec_.columns(col_idx))); + example->mutable_attributes(col_idx)->set_categorical(int_value); + } break; + + case AvroType::kArray: + switch (field.sub_type) { + case AvroType::kDouble: + case AvroType::kFloat: { + bool has_value; + std::vector values; + if (field.sub_type == AvroType::kFloat) { + ASSIGN_OR_RETURN( + has_value, reader_->ReadNextFieldArrayFloat(field, &values)); + } else { + ASSIGN_OR_RETURN( + has_value, + reader_->ReadNextFieldArrayDoubleIntoFloat(field, &values)); + } + if (!has_value) { + break; + } + + // Check if field used. + const auto unstacked_idx = + multivariate_field_idx_to_unroll_idx_[field_idx]; + if (unstacked_idx == -1) { + break; + } + + auto& unstacked = dataspec_.unstackeds(unstacked_idx); + for (int dim_idx = 0; dim_idx < values.size(); dim_idx++) { + const int col_idx = unstacked.begin_column_idx() + dim_idx; + const float value = values[dim_idx]; + if (!std::isnan(value)) { + example->mutable_attributes(col_idx)->set_numerical(value); + } + } + } break; + + case AvroType::kString: + case AvroType::kBytes: { + std::vector values; + ASSIGN_OR_RETURN(const auto has_value, + reader_->ReadNextFieldArrayString(field, &values)); + if (!has_value) { + break; + } + + const auto univariate_col_idx = + univariate_field_idx_to_column_idx_[field_idx]; + if (univariate_col_idx != -1) { + const auto& col_spec = dataspec_.columns(univariate_col_idx); + for (const auto& value : values) { + ASSIGN_OR_RETURN( + auto int_value, + CategoricalStringToValueWithStatus(value, col_spec)); + example->mutable_attributes(univariate_col_idx) + ->mutable_categorical_set() + ->add_values(int_value); + } + break; + } + + // Check if field used. + const auto unstacked_idx = + multivariate_field_idx_to_unroll_idx_[field_idx]; + if (unstacked_idx == -1) { + break; + } + + auto& unstacked = dataspec_.unstackeds(unstacked_idx); + for (int dim_idx = 0; dim_idx < values.size(); dim_idx++) { + const int col_idx = unstacked.begin_column_idx() + dim_idx; + const auto& col_spec = dataspec_.columns(col_idx); + ASSIGN_OR_RETURN(auto int_value, + CategoricalStringToValueWithStatus( + values[dim_idx], col_spec)); + example->mutable_attributes(col_idx)->set_categorical(int_value); + } + } break; + default: + return absl::UnimplementedError("Unsupported type"); + } + break; + } + field_idx++; + } + DCHECK_EQ(field_idx, reader_->fields().size()); + return true; +} + +absl::StatusOr CreateDataspec( + absl::string_view path, + const dataset::proto::DataSpecificationGuide& guide) { + // TODO: Reading of multiple paths. + + ASSIGN_OR_RETURN(const auto reader, AvroReader::Create(path)); + + // Infer the column spec for the single-dimensional features and the unstacked + // column (without the size information) for the multi-dimensional features. + std::vector unstacked_guides; + ASSIGN_OR_RETURN( + auto dataspec, + internal::InferDataspec(reader->fields(), guide, &unstacked_guides)); + + // Mapping between the Avro field index and the column index for the + // univariate features. -1's are used for ignored fields. + std::vector univariate_field_idx_to_column_idx; + // Mapping between the Avro field index and the unstacked index for the + // multivariate features. -1's are used for ignored fields. + std::vector multivariate_field_idx_to_unroll_idx; + RETURN_IF_ERROR(ComputeReadingMaps(reader->fields(), dataspec, + &univariate_field_idx_to_column_idx, + &multivariate_field_idx_to_unroll_idx)); + + // Create the accumulator for the univariate features. + proto::DataSpecificationAccumulator accumulator; + while (accumulator.columns_size() < dataspec.columns_size()) { + accumulator.add_columns(); + } + + size_t record_idx; + for (record_idx = 0; true; record_idx++) { + if (guide.max_num_scanned_rows_to_accumulate_statistics() > 0 && + record_idx >= guide.max_num_scanned_rows_to_accumulate_statistics()) { + // Enough records scanned. + break; + } + LOG_EVERY_N_SEC(INFO, 30) << record_idx << " row(s) processed"; + + ASSIGN_OR_RETURN(const bool has_record, reader->ReadNextRecord()); + if (!has_record) { + break; + } + int field_idx = 0; + for (const auto& field : reader->fields()) { + switch (field.type) { + case AvroType::kUnknown: + case AvroType::kNull: + break; + + case AvroType::kBoolean: { + ASSIGN_OR_RETURN(const auto value, + reader->ReadNextFieldBoolean(field)); + const int col_idx = univariate_field_idx_to_column_idx[field_idx]; + if (col_idx == -1) { + // Ignore field. + break; + } + auto* col_spec_ = dataspec.mutable_columns(col_idx); + if (!value.has_value()) { + col_spec_->set_count_nas(col_spec_->count_nas() + 1); + } else { + switch (col_spec_->type()) { + case proto::ColumnType::BOOLEAN: + UpdateComputeSpecBooleanFeatureWithBool(value.value(), + col_spec_); + break; + default: + return absl::InvalidArgumentError( + absl::StrCat("Unsupported type ", + proto::ColumnType_Name(col_spec_->type()), + "for kBoolean field ", field.name)); + } + } + } break; + + case AvroType::kLong: + case AvroType::kInt: { + ASSIGN_OR_RETURN(const auto value, + reader->ReadNextFieldInteger(field)); + const int col_idx = univariate_field_idx_to_column_idx[field_idx]; + if (col_idx == -1) { + // Ignore field. + break; + } + auto* col_spec_ = dataspec.mutable_columns(col_idx); + if (!value.has_value()) { + col_spec_->set_count_nas(col_spec_->count_nas() + 1); + } else { + switch (col_spec_->type()) { + case proto::ColumnType::NUMERICAL: + FillContentNumericalFeature( + value.value(), accumulator.mutable_columns(col_idx)); + break; + default: + return absl::InvalidArgumentError(absl::StrCat( + "Unsupported type ", + proto::ColumnType_Name(col_spec_->type()), "for ", + TypeToString(field.type), " field ", field.name)); + } + } + } break; + + case AvroType::kDouble: + case AvroType::kFloat: { + absl::optional value; + if (field.type == AvroType::kFloat) { + ASSIGN_OR_RETURN(value, reader->ReadNextFieldFloat(field)); + } else { + ASSIGN_OR_RETURN(value, reader->ReadNextFieldDouble(field)); + } + if (value.has_value() && std::isnan(*value)) { + value = absl::nullopt; + } + + const int col_idx = univariate_field_idx_to_column_idx[field_idx]; + if (col_idx == -1) { + // Ignore field. + break; + } + auto* col_spec_ = dataspec.mutable_columns(col_idx); + if (!value.has_value()) { + col_spec_->set_count_nas(col_spec_->count_nas() + 1); + } else { + switch (col_spec_->type()) { + case proto::ColumnType::NUMERICAL: + FillContentNumericalFeature( + value.value(), accumulator.mutable_columns(col_idx)); + break; + default: + return absl::InvalidArgumentError(absl::StrCat( + "Unsupported type ", + proto::ColumnType_Name(col_spec_->type()), "for ", + TypeToString(field.type), " field ", field.name)); + } + } + } break; + + case AvroType::kBytes: + case AvroType::kString: { + std::string value; + ASSIGN_OR_RETURN(const auto has_value, + reader->ReadNextFieldString(field, &value)); + + const int col_idx = univariate_field_idx_to_column_idx[field_idx]; + if (col_idx == -1) { + // Ignore field. + break; + } + auto* col_spec_ = dataspec.mutable_columns(col_idx); + if (!has_value) { + col_spec_->set_count_nas(col_spec_->count_nas() + 1); + } else { + switch (col_spec_->type()) { + case proto::ColumnType::CATEGORICAL: + RETURN_IF_ERROR(AddTokensToCategoricalColumnSpec( + std::vector{value}, col_spec_)); + break; + default: + return absl::InvalidArgumentError(absl::StrCat( + "Unsupported type ", + proto::ColumnType_Name(col_spec_->type()), "for ", + TypeToString(field.type), " field ", field.name)); + } + } + } break; + + case AvroType::kArray: + switch (field.sub_type) { + case AvroType::kDouble: + case AvroType::kFloat: { + bool has_value; + std::vector values; + if (field.sub_type == AvroType::kFloat) { + ASSIGN_OR_RETURN( + has_value, reader->ReadNextFieldArrayFloat(field, &values)); + } else { + ASSIGN_OR_RETURN( + has_value, + reader->ReadNextFieldArrayDoubleIntoFloat(field, &values)); + } + + // Check if field used. + const auto unstacked_idx = + multivariate_field_idx_to_unroll_idx[field_idx]; + if (unstacked_idx == -1) { + break; + } + RETURN_IF_ERROR(InitializeUnstackedColumn( + field, has_value, values, record_idx, field_idx, + univariate_field_idx_to_column_idx, + multivariate_field_idx_to_unroll_idx, unstacked_guides, + &dataspec, &accumulator)); + + // Populate column statistics. + auto& unstacked = dataspec.unstackeds(unstacked_idx); + if (unstacked.has_size()) { + for (int dim_idx = 0; dim_idx < values.size(); dim_idx++) { + const int col_idx = unstacked.begin_column_idx() + dim_idx; + const auto value = values[dim_idx]; + auto* col_spec = dataspec.mutable_columns(col_idx); + if (!has_value || std::isnan(value)) { + col_spec->set_count_nas(col_spec->count_nas() + 1); + } else { + switch (dataspec.columns(col_idx).type()) { + case proto::ColumnType::NUMERICAL: + FillContentNumericalFeature( + value, accumulator.mutable_columns(col_idx)); + break; + default: + return absl::InvalidArgumentError( + absl::StrCat("Unsupported type ", + proto::ColumnType_Name( + dataspec.columns(col_idx).type()), + "for ", TypeToString(field.type), + " field ", field.name)); + } + } + } + } + } break; + + case AvroType::kString: + case AvroType::kBytes: { + std::vector values; + ASSIGN_OR_RETURN( + const auto has_value, + reader->ReadNextFieldArrayString(field, &values)); + + const auto univariate_col_idx = + univariate_field_idx_to_column_idx[field_idx]; + if (univariate_col_idx != -1) { + auto* col_spec = dataspec.mutable_columns(univariate_col_idx); + if (!has_value) { + col_spec->set_count_nas(col_spec->count_nas() + 1); + } else { + switch (dataspec.columns(univariate_col_idx).type()) { + case proto::ColumnType::CATEGORICAL_SET: + RETURN_IF_ERROR( + AddTokensToCategoricalColumnSpec(values, col_spec)); + break; + default: + return absl::InvalidArgumentError(absl::StrCat( + "Unsupported type ", + proto::ColumnType_Name( + dataspec.columns(univariate_col_idx).type()), + "for ", TypeToString(field.type), " field ", + field.name)); + } + } + break; + } + + // Check if field used. + const auto unstacked_idx = + multivariate_field_idx_to_unroll_idx[field_idx]; + if (unstacked_idx == -1) { + break; + } + + RETURN_IF_ERROR(InitializeUnstackedColumn( + field, has_value, values, record_idx, field_idx, + univariate_field_idx_to_column_idx, + multivariate_field_idx_to_unroll_idx, unstacked_guides, + &dataspec, &accumulator)); + + // Populate column statistics. + auto& unstacked = dataspec.unstackeds(unstacked_idx); + if (unstacked.has_size()) { + for (int dim_idx = 0; dim_idx < values.size(); dim_idx++) { + const int col_idx = unstacked.begin_column_idx() + dim_idx; + auto* col_spec = dataspec.mutable_columns(col_idx); + if (!has_value) { + col_spec->set_count_nas(col_spec->count_nas() + 1); + } else { + switch (dataspec.columns(col_idx).type()) { + case proto::ColumnType::CATEGORICAL: + RETURN_IF_ERROR(AddTokensToCategoricalColumnSpec( + std::vector{values[dim_idx]}, + col_spec)); + break; + default: + return absl::InvalidArgumentError( + absl::StrCat("Unsupported type ", + proto::ColumnType_Name( + dataspec.columns(col_idx).type()), + "for ", TypeToString(field.type), + " field ", field.name)); + } + } + } + } + } break; + default: + return absl::UnimplementedError("Unsupported type"); + } + break; + } + field_idx++; + } + DCHECK_EQ(field_idx, reader->fields().size()); + } + + if (record_idx == 0) { + return absl::InvalidArgumentError("No record found"); + } + dataspec.set_created_num_rows(record_idx); + RETURN_IF_ERROR(FinalizeComputeSpec(guide, accumulator, &dataspec)); + + RETURN_IF_ERROR(reader->Close()); + return dataspec; +} + +absl::Status AvroDataSpecCreator::CreateDataspec( + const std::vector& paths, + const proto::DataSpecificationGuide& guide, + proto::DataSpecification* data_spec) { + if (paths.empty()) { + return absl::InvalidArgumentError("No path provided"); + } + ASSIGN_OR_RETURN(*data_spec, avro::CreateDataspec(paths.front(), guide)); + return absl::OkStatus(); +} + +namespace internal { + +absl::StatusOr InferDataspec( + const std::vector& fields, + const dataset::proto::DataSpecificationGuide& guide, + std::vector* unstacked_guides) { + dataset::proto::DataSpecification dataspec; + + const auto create_column = + [&dataspec](const absl::string_view key, + const proto::ColumnType representation_type, + const proto::ColumnGuide& col_guide, + const bool manual_column_guide = false) -> absl::Status { + proto::Column* column = dataspec.add_columns(); + column->set_name(std::string(key)); + if (manual_column_guide) { + column->set_is_manual_type(manual_column_guide); + column->set_type(col_guide.type()); + } else { + column->set_type(representation_type); + } + return UpdateSingleColSpecWithGuideInfo(col_guide, column); + }; + + for (const auto& field : fields) { + proto::ColumnGuide col_guide; + ASSIGN_OR_RETURN(bool has_column_guide, + BuildColumnGuide(field.name, guide, &col_guide)); + if (!has_column_guide && guide.ignore_columns_without_guides()) { + continue; + } + if (col_guide.ignore_column()) { + continue; + } + if (field.type == AvroType::kUnknown || field.type == AvroType::kNull) { + continue; + } + + switch (field.type) { + case AvroType::kUnknown: + case AvroType::kNull: + return absl::InternalError("Unknown field"); + case AvroType::kBoolean: + RETURN_IF_ERROR(create_column(field.name, proto::ColumnType::BOOLEAN, + col_guide, has_column_guide)); + break; + case AvroType::kLong: + case AvroType::kInt: + case AvroType::kFloat: + case AvroType::kDouble: + RETURN_IF_ERROR(create_column(field.name, proto::ColumnType::NUMERICAL, + col_guide, has_column_guide)); + break; + case AvroType::kBytes: + case AvroType::kString: + RETURN_IF_ERROR(create_column(field.name, + proto::ColumnType::CATEGORICAL, col_guide, + has_column_guide)); + break; + case AvroType::kArray: + switch (field.sub_type) { + case AvroType::kFloat: + case AvroType::kDouble: { + (*unstacked_guides).push_back(col_guide); + auto* unstacked = dataspec.add_unstackeds(); + unstacked->set_original_name(field.name); + unstacked->set_type(proto::ColumnType::NUMERICAL); + } break; + case AvroType::kString: + case AvroType::kBytes: { + if (has_column_guide) { + if (col_guide.type() == proto::ColumnType::CATEGORICAL_SET) { + RETURN_IF_ERROR(create_column(field.name, col_guide.type(), + col_guide, has_column_guide)); + break; + } else { + return absl::InvalidArgumentError( + absl::StrCat("Unsupported type ", + proto::ColumnType_Name(col_guide.type()), + "for kString or kBytes field ", field.name)); + } + } + + (*unstacked_guides).push_back(col_guide); + auto* unstacked = dataspec.add_unstackeds(); + unstacked->set_original_name(field.name); + unstacked->set_type(proto::ColumnType::CATEGORICAL); + } break; + default: + return absl::UnimplementedError("Unsupported type"); + } + break; + } + } + return dataspec; +} + +} // namespace internal + +} // namespace yggdrasil_decision_forests::dataset::avro diff --git a/yggdrasil_decision_forests/dataset/avro_example.h b/yggdrasil_decision_forests/dataset/avro_example.h new file mode 100644 index 00000000..5cc0092a --- /dev/null +++ b/yggdrasil_decision_forests/dataset/avro_example.h @@ -0,0 +1,120 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Utility to read Avro files. + +#ifndef YGGDRASIL_DECISION_FORESTS_DATASET_AVRO_EXAMPLE_H_ +#define YGGDRASIL_DECISION_FORESTS_DATASET_AVRO_EXAMPLE_H_ + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "yggdrasil_decision_forests/dataset/avro.h" +#include "yggdrasil_decision_forests/dataset/data_spec.pb.h" +#include "yggdrasil_decision_forests/dataset/data_spec_inference.h" +#include "yggdrasil_decision_forests/dataset/example_reader_interface.h" +#include "yggdrasil_decision_forests/utils/sharded_io.h" + +namespace yggdrasil_decision_forests::dataset::avro { + +// Creates a dataspec from the Avro file. +absl::StatusOr CreateDataspec( + absl::string_view path, + const dataset::proto::DataSpecificationGuide& guide); + +class AvroExampleReader final : public ExampleReaderInterface { + public: + explicit AvroExampleReader(const proto::DataSpecification& data_spec, + absl::optional> required_columns) + : sharded_reader_(data_spec, required_columns) {} + + absl::StatusOr Next(proto::Example* example) override { + return sharded_reader_.Next(example); + } + + absl::Status Open(absl::string_view sharded_path) override { + return sharded_reader_.Open(sharded_path); + } + + private: + class Implementation final : public utils::ShardedReader { + public: + explicit Implementation( + const proto::DataSpecification& data_spec, + const absl::optional>& required_columns) + : dataspec_(data_spec), required_columns_(required_columns) {} + + protected: + // Opens the Avro file at "path", and check that the header is as expected. + absl::Status OpenShard(absl::string_view path) override; + + // Scans a new row in the Avro file, and parses it as a proto:Example. + absl::StatusOr NextInShard(proto::Example* example) override; + + private: + // The data spec. + const proto::DataSpecification dataspec_; + + // Currently, open file; + std::unique_ptr reader_; + + // Mapping between the Avro field index and the column index for the + // univariate features. -1's are used for ignored fields. + std::vector univariate_field_idx_to_column_idx_; + + // Mapping between the Avro field index and the unstacked index for the + // multivariate features. -1's are used for ignored fields. + std::vector multivariate_field_idx_to_unroll_idx_; + + const absl::optional> required_columns_; + }; + + Implementation sharded_reader_; +}; + +REGISTER_ExampleReaderInterface(AvroExampleReader, "FORMAT_AVRO"); + +class AvroDataSpecCreator : public AbstractDataSpecCreator { + public: + absl::Status CreateDataspec(const std::vector& paths, + const proto::DataSpecificationGuide& guide, + proto::DataSpecification* data_spec) override; + + absl::StatusOr CountExamples(absl::string_view path) override { + return absl::UnimplementedError( + "CountExamples not implemented for AVRO format"); + } +}; + +REGISTER_AbstractDataSpecCreator(AvroDataSpecCreator, "FORMAT_AVRO"); + +namespace internal { + +// Infers the dataspec from the Avro file i.e. find the columns, but do not +// set the statistics. +absl::StatusOr InferDataspec( + const std::vector& fields, + const dataset::proto::DataSpecificationGuide& guide, + std::vector* unstacked_guides); +} // namespace internal + +} // namespace yggdrasil_decision_forests::dataset::avro + +#endif // YGGDRASIL_DECISION_FORESTS_DATASET_AVRO_EXAMPLE_H_ diff --git a/yggdrasil_decision_forests/dataset/avro_example_test.cc b/yggdrasil_decision_forests/dataset/avro_example_test.cc new file mode 100644 index 00000000..2ad15025 --- /dev/null +++ b/yggdrasil_decision_forests/dataset/avro_example_test.cc @@ -0,0 +1,483 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "yggdrasil_decision_forests/dataset/avro_example.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/str_cat.h" +#include "yggdrasil_decision_forests/dataset/data_spec.h" +#include "yggdrasil_decision_forests/dataset/data_spec.pb.h" +#include "yggdrasil_decision_forests/dataset/data_spec_inference.h" +#include "yggdrasil_decision_forests/dataset/example_reader.h" +#include "yggdrasil_decision_forests/utils/filesystem.h" +#include "yggdrasil_decision_forests/utils/test.h" +#include "yggdrasil_decision_forests/utils/testing_macros.h" + +namespace yggdrasil_decision_forests::dataset::avro { +namespace { + +using test::EqualsProto; + +std::string DatasetDir() { + return file::JoinPath( + test::DataRootDirectory(), + "yggdrasil_decision_forests/test_data/dataset"); +} + +TEST(AvroExample, CreateDataspec) { + dataset::proto::DataSpecificationGuide guide; + { + auto* col = guide.add_column_guides(); + col->set_column_name_pattern("^f_another_array_of_string$"); + col->set_type(proto::ColumnType::CATEGORICAL_SET); + } + + { + auto* col = guide.add_column_guides(); + col->set_column_name_pattern("^f_another_float$"); + col->set_ignore_column(true); + } + + guide.mutable_default_column_guide() + ->mutable_categorial() + ->set_min_vocab_frequency(1); + + ASSERT_OK_AND_ASSIGN( + const auto dataspec, + CreateDataspec(file::JoinPath(DatasetDir(), "toy_codex-null.avro"), + guide)); + LOG(INFO) << "Dataspec:\n" << dataset::PrintHumanReadable(dataspec); + + const dataset::proto::DataSpecification expected = PARSE_TEST_PROTO(R"pb( + columns { + type: BOOLEAN + name: "f_boolean" + boolean { count_true: 1 count_false: 1 } + } + columns { + type: NUMERICAL + name: "f_int" + numerical { mean: 5.5 min_value: 5 max_value: 6 standard_deviation: 0.5 } + } + columns { + type: NUMERICAL + name: "f_long" + numerical { + mean: 617222 + min_value: -123 + max_value: 1234567 + standard_deviation: 617345.00049850566 + } + } + columns { + type: NUMERICAL + name: "f_float" + numerical { + mean: 0.95375001430511475 + min_value: -1.234 + max_value: 3.1415 + standard_deviation: 2.1877499915544623 + } + } + columns { + type: NUMERICAL + name: "f_double" + numerical { + mean: 6.7890000343322754 + min_value: 6.789 + max_value: 6.789 + standard_deviation: 0.0011401533426769351 + } + count_nas: 1 + } + columns { + type: CATEGORICAL + name: "f_string" + categorical { + most_frequent_value: 1 + number_of_unique_values: 3 + min_value_count: 1 + max_number_of_unique_values: 2000 + is_already_integerized: false + items { + key: "" + value { index: 2 count: 1 } + } + items { + key: "" + value { index: 0 count: 0 } + } + items { + key: "hello" + value { index: 1 count: 1 } + } + } + } + columns { + type: CATEGORICAL + name: "f_bytes" + categorical { + most_frequent_value: 1 + number_of_unique_values: 3 + min_value_count: 1 + max_number_of_unique_values: 2000 + is_already_integerized: false + items { + key: "" + value { index: 2 count: 1 } + } + items { + key: "" + value { index: 0 count: 0 } + } + items { + key: "world" + value { index: 1 count: 1 } + } + } + } + columns { + type: NUMERICAL + name: "f_float_optional" + numerical { + mean: 6.0999999046325684 + min_value: 6.1 + max_value: 6.1 + standard_deviation: 0.00049795111524192609 + } + count_nas: 1 + } + columns { + type: CATEGORICAL_SET + name: "f_another_array_of_string" + is_manual_type: true + categorical { + most_frequent_value: 1 + number_of_unique_values: 5 + min_value_count: 1 + max_number_of_unique_values: 2000 + is_already_integerized: false + items { + key: "" + value { index: 0 count: 0 } + } + items { + key: "a" + value { index: 4 count: 1 } + } + items { + key: "b" + value { index: 3 count: 1 } + } + items { + key: "c" + value { index: 1 count: 2 } + } + items { + key: "def" + value { index: 2 count: 1 } + } + } + } + columns { + type: NUMERICAL + name: "f_array_of_float.0_of_3" + numerical { mean: 2.5 min_value: 1 max_value: 4 standard_deviation: 1.5 } + count_nas: 0 + } + columns { + type: NUMERICAL + name: "f_array_of_float.1_of_3" + numerical { mean: 3.5 min_value: 2 max_value: 5 standard_deviation: 1.5 } + count_nas: 0 + } + columns { + type: NUMERICAL + name: "f_array_of_float.2_of_3" + numerical { mean: 4.5 min_value: 3 max_value: 6 standard_deviation: 1.5 } + count_nas: 0 + } + columns { + type: NUMERICAL + name: "f_array_of_double.0_of_3" + numerical { mean: 25 min_value: 10 max_value: 40 standard_deviation: 15 } + count_nas: 0 + } + columns { + type: NUMERICAL + name: "f_array_of_double.1_of_3" + numerical { mean: 35 min_value: 20 max_value: 50 standard_deviation: 15 } + count_nas: 0 + } + columns { + type: NUMERICAL + name: "f_array_of_double.2_of_3" + numerical { mean: 45 min_value: 30 max_value: 60 standard_deviation: 15 } + count_nas: 0 + } + columns { + type: CATEGORICAL + name: "f_array_of_string.0_of_3" + categorical { + most_frequent_value: 1 + number_of_unique_values: 3 + min_value_count: 1 + max_number_of_unique_values: 2000 + is_already_integerized: false + items { + key: "" + value { index: 0 count: 0 } + } + items { + key: "a" + value { index: 2 count: 1 } + } + items { + key: "c" + value { index: 1 count: 1 } + } + } + count_nas: 0 + } + columns { + type: CATEGORICAL + name: "f_array_of_string.1_of_3" + categorical { + most_frequent_value: 1 + number_of_unique_values: 3 + min_value_count: 1 + max_number_of_unique_values: 2000 + is_already_integerized: false + items { + key: "" + value { index: 0 count: 0 } + } + items { + key: "a" + value { index: 2 count: 1 } + } + items { + key: "b" + value { index: 1 count: 1 } + } + } + count_nas: 0 + } + columns { + type: CATEGORICAL + name: "f_array_of_string.2_of_3" + categorical { + most_frequent_value: 1 + number_of_unique_values: 3 + min_value_count: 1 + max_number_of_unique_values: 2000 + is_already_integerized: false + items { + key: "" + value { index: 0 count: 0 } + } + items { + key: "b" + value { index: 2 count: 1 } + } + items { + key: "c" + value { index: 1 count: 1 } + } + } + count_nas: 0 + } + columns { + type: NUMERICAL + name: "f_optional_array_of_float.0_of_3" + numerical { mean: 0.5 min_value: 1 max_value: 1 standard_deviation: 0.5 } + count_nas: 0 + } + columns { + type: NUMERICAL + name: "f_optional_array_of_float.1_of_3" + numerical { mean: 1 min_value: 2 max_value: 2 standard_deviation: 1 } + count_nas: 0 + } + columns { + type: NUMERICAL + name: "f_optional_array_of_float.2_of_3" + numerical { mean: 1.5 min_value: 3 max_value: 3 standard_deviation: 1.5 } + count_nas: 0 + } + created_num_rows: 2 + unstackeds { + original_name: "f_array_of_float" + begin_column_idx: 9 + size: 3 + type: NUMERICAL + } + unstackeds { + original_name: "f_array_of_double" + begin_column_idx: 12 + size: 3 + type: NUMERICAL + } + unstackeds { + original_name: "f_array_of_string" + begin_column_idx: 15 + size: 3 + type: CATEGORICAL + } + unstackeds { + original_name: "f_optional_array_of_float" + begin_column_idx: 18 + size: 3 + type: NUMERICAL + } + )pb"); + + EXPECT_THAT(dataspec, EqualsProto(expected)); +} + +struct ReadExampleCase { + std::string filename; +}; + +SIMPLE_PARAMETERIZED_TEST(ReadExample, ReadExampleCase, + { + {"toy_codex-null.avro"}, + {"toy_codex-deflate.avro"}, + }) { + const auto& test_case = GetParam(); + dataset::proto::DataSpecificationGuide guide; + { + auto* col = guide.add_column_guides(); + col->set_column_name_pattern("^f_another_array_of_string$"); + col->set_type(proto::ColumnType::CATEGORICAL_SET); + } + + { + auto* col = guide.add_column_guides(); + col->set_column_name_pattern("^f_another_float$"); + col->set_ignore_column(true); + } + + guide.mutable_default_column_guide() + ->mutable_categorial() + ->set_min_vocab_frequency(1); + + ASSERT_OK_AND_ASSIGN( + const auto dataspec, + CreateDataspec(file::JoinPath(DatasetDir(), test_case.filename), guide)); + + AvroExampleReader reader(dataspec, {}); + ASSERT_OK(reader.Open(file::JoinPath(DatasetDir(), test_case.filename))); + proto::Example example; + ASSERT_OK_AND_ASSIGN(bool has_next, reader.Next(&example)); + ASSERT_TRUE(has_next); + + const proto::Example expected_1 = PARSE_TEST_PROTO(R"pb( + attributes { boolean: true } + attributes { numerical: 5 } + attributes { numerical: 1234567 } + attributes { numerical: 3.1415 } + attributes {} + attributes { categorical: 1 } + attributes { categorical: 1 } + attributes { numerical: 6.1 } + attributes { categorical_set { values: 4 values: 3 values: 1 } } + attributes { numerical: 1 } + attributes { numerical: 2 } + attributes { numerical: 3 } + attributes { numerical: 10 } + attributes { numerical: 20 } + attributes { numerical: 30 } + attributes { categorical: 2 } + attributes { categorical: 1 } + attributes { categorical: 1 } + attributes { numerical: 1 } + attributes { numerical: 2 } + attributes { numerical: 3 } + )pb"); + EXPECT_THAT(example, EqualsProto(expected_1)); + + ASSERT_OK_AND_ASSIGN(has_next, reader.Next(&example)); + ASSERT_TRUE(has_next); + const proto::Example expected_2 = PARSE_TEST_PROTO(R"pb( + attributes { boolean: false } + attributes { numerical: 6 } + attributes { numerical: -123 } + attributes { numerical: -1.234 } + attributes { numerical: 6.789 } + attributes { categorical: 2 } + attributes { categorical: 2 } + attributes {} + attributes { categorical_set { values: 1 values: 2 } } + attributes { numerical: 4 } + attributes { numerical: 5 } + attributes { numerical: 6 } + attributes { numerical: 40 } + attributes { numerical: 50 } + attributes { numerical: 60 } + attributes { categorical: 1 } + attributes { categorical: 2 } + attributes { categorical: 2 } + attributes {} + attributes {} + attributes {} + )pb"); + EXPECT_THAT(example, EqualsProto(expected_2)); + + ASSERT_OK_AND_ASSIGN(has_next, reader.Next(&example)); + ASSERT_FALSE(has_next); +} + +TEST(CreateReaderRegistration, Base) { + const auto dataset_path = absl::StrCat( + "avro:", file::JoinPath(DatasetDir(), "toy_codex-deflate.avro")); + proto::DataSpecificationGuide guide; + { + auto* col = guide.add_column_guides(); + col->set_column_name_pattern("^f_another_array_of_string$"); + col->set_type(proto::ColumnType::CATEGORICAL_SET); + } + + { + auto* col = guide.add_column_guides(); + col->set_column_name_pattern("^f_another_float$"); + col->set_ignore_column(true); + } + + guide.mutable_default_column_guide() + ->mutable_categorial() + ->set_min_vocab_frequency(1); + + proto::DataSpecification data_spec; + CreateDataSpec(dataset_path, false, guide, &data_spec); + LOG(INFO) << "Dataspec:\n" << dataset::PrintHumanReadable(data_spec); + EXPECT_EQ(data_spec.columns_size(), 21); + EXPECT_EQ(data_spec.unstackeds_size(), 4); + EXPECT_EQ(data_spec.created_num_rows(), 2); + + auto reader = CreateExampleReader(dataset_path, data_spec).value(); + proto::Example example; + int num_rows = 0; + while (reader->Next(&example).value()) { + num_rows++; + } + EXPECT_EQ(num_rows, 2); +} + +} // namespace +} // namespace yggdrasil_decision_forests::dataset::avro diff --git a/yggdrasil_decision_forests/dataset/avro_test.cc b/yggdrasil_decision_forests/dataset/avro_test.cc new file mode 100644 index 00000000..749c661b --- /dev/null +++ b/yggdrasil_decision_forests/dataset/avro_test.cc @@ -0,0 +1,346 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "yggdrasil_decision_forests/dataset/avro.h" + +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "yggdrasil_decision_forests/utils/bytestream.h" +#include "yggdrasil_decision_forests/utils/filesystem.h" +#include "yggdrasil_decision_forests/utils/test.h" +#include "yggdrasil_decision_forests/utils/testing_macros.h" + +namespace yggdrasil_decision_forests::dataset::avro { +namespace { + +using test::EqualsProto; +using ::testing::ElementsAre; + +std::string DatasetDir() { + return file::JoinPath( + test::DataRootDirectory(), + "yggdrasil_decision_forests/test_data/dataset"); +} + +struct ReadIntegerCase { + std::string input; + int64_t expected_value; +}; + +SIMPLE_PARAMETERIZED_TEST(ReadInteger, ReadIntegerCase, + { + {std::string("\x00", 1), 0}, + {"\x01", -1}, + {"\x02", 1}, + {"\x03", -2}, + {"\x04", 2}, + {"\x05", -3}, + {"\x06", 3}, + {"\x07", -4}, + {"\x7F", -64}, + {"\x80\x01", 64}, + {"\x80\x02", 128}, + {"\x82\x01", 65}, + {"\x80\x03", 192}, + {"\x80\x04", 256}, + {"\xFE\x01", 127}, + {"\x80\x80\x01", 1 << (7 * 2 - 1)}, + {"\x80\x80\x80\x01", 1 << (7 * 3 - 1)}, + {"\x80\x80\x80\x80\x80\x80\x80\x01", + static_cast(1) << (7 * 7 - 1)}, + }) { + const auto& test_case = GetParam(); + utils::StringInputByteStream stream(test_case.input); + ASSERT_OK_AND_ASSIGN(const auto value, internal::ReadInteger(&stream)); + EXPECT_EQ(value, test_case.expected_value); +} + +struct ReadFloatCase { + std::string input; + float expected_value; +}; + +SIMPLE_PARAMETERIZED_TEST(ReadFloat, ReadFloatCase, + { + {std::string("\x00\x00\x00\x00", 4), 0.f}, + {std::string("\x00\x00\x00\x40", 4), 2.f}, + {std::string("\x00\x00\x40\x40", 4), 3.f}, + {std::string("\x00\x00\x60\x40", 4), 3.5f}, + {std::string("\x00\x00\x80\x7F", 4), + std::numeric_limits::infinity()}, + {std::string("\x00\x00\x80\xFF", 4), + -std::numeric_limits::infinity()}, + }) { + const auto& test_case = GetParam(); + utils::StringInputByteStream stream(test_case.input); + ASSERT_OK_AND_ASSIGN(const auto value, internal::ReadFloat(&stream)); + EXPECT_EQ(value, test_case.expected_value); +} + +TEST(ReadFloat, IsNan) { + utils::StringInputByteStream stream("\xFF\xFF\xFF\x7F"); + ASSERT_OK_AND_ASSIGN(const auto value, internal::ReadFloat(&stream)); + EXPECT_TRUE(std::isnan(value)); +} + +struct ReadDoubleCase { + std::string input; + double expected_value; +}; + +SIMPLE_PARAMETERIZED_TEST( + ReadDouble, ReadDoubleCase, + { + {std::string("\x00\x00\x00\x00\x00\x00\x00\x00", 8), 0.}, + {std::string("\x00\x00\x00\x00\x00\x00\x00\x40", 8), 2.}, + {std::string("\x00\x00\x00\x00\x00\x00\x40\x40", 8), 32.}, + {std::string("\x00\x00\x00\x00\x00\x00\x60\x40", 8), 128.}, + }) { + const auto& test_case = GetParam(); + utils::StringInputByteStream stream(test_case.input); + ASSERT_OK_AND_ASSIGN(const auto value, internal::ReadDouble(&stream)); + EXPECT_EQ(value, test_case.expected_value); +} + +struct ReadBooleanCase { + std::string input; + bool expected_value; +}; + +SIMPLE_PARAMETERIZED_TEST(ReadBoolean, ReadBooleanCase, + { + {std::string("\x00", 1), false}, + {std::string("\x01", 1), true}, + }) { + const auto& test_case = GetParam(); + utils::StringInputByteStream stream(test_case.input); + ASSERT_OK_AND_ASSIGN(const auto value, internal::ReadBoolean(&stream)); + EXPECT_EQ(value, test_case.expected_value); +} + +struct ReadStringCase { + std::string input; + std::string expected_value; +}; + +SIMPLE_PARAMETERIZED_TEST(ReadString, ReadStringCase, + { + {std::string("\x00", 1), ""}, + {"\x02\x41", "A"}, + {"\x16Hello World", "Hello World"}, + {"\x06\x66\x6F\x6F", "foo"}, + }) { + const auto& test_case = GetParam(); + utils::StringInputByteStream stream(test_case.input); + std::string value; + ASSERT_OK(internal::ReadString(&stream, &value)); + EXPECT_EQ(value, test_case.expected_value); +} + +TEST(AvroExample, ExtractSchema) { + ASSERT_OK_AND_ASSIGN(const auto schema, + AvroReader::ExtractSchema(file::JoinPath( + DatasetDir(), "toy_codex-null.avro"))); + EXPECT_EQ( + schema, + R"({"type": "record", "name": "ToyDataset", "fields": [{"name": "f_null", "type": "null"}, {"name": "f_boolean", "type": "boolean"}, {"name": "f_int", "type": "int"}, {"name": "f_long", "type": "long"}, {"name": "f_float", "type": "float"}, {"name": "f_another_float", "type": "float"}, {"name": "f_double", "type": "double"}, {"name": "f_string", "type": "string"}, {"name": "f_bytes", "type": "bytes"}, {"name": "f_float_optional", "type": ["null", "float"]}, {"name": "f_array_of_float", "type": {"type": "array", "items": "float"}}, {"name": "f_array_of_double", "type": {"type": "array", "items": "double"}}, {"name": "f_array_of_string", "type": {"type": "array", "items": "string"}}, {"name": "f_another_array_of_string", "type": {"type": "array", "items": "string"}}, {"name": "f_optional_array_of_float", "type": ["null", {"type": "array", "items": "float"}]}], "__fastavro_parsed": true})"); +} + +TEST(AvroExample, ExtractSchemaCompressed) { + ASSERT_OK_AND_ASSIGN(const auto schema, + AvroReader::ExtractSchema(file::JoinPath( + DatasetDir(), "toy_codex-deflate.avro"))); + EXPECT_EQ( + schema, + R"({"type": "record", "name": "ToyDataset", "fields": [{"name": "f_null", "type": "null"}, {"name": "f_boolean", "type": "boolean"}, {"name": "f_int", "type": "int"}, {"name": "f_long", "type": "long"}, {"name": "f_float", "type": "float"}, {"name": "f_another_float", "type": "float"}, {"name": "f_double", "type": "double"}, {"name": "f_string", "type": "string"}, {"name": "f_bytes", "type": "bytes"}, {"name": "f_float_optional", "type": ["null", "float"]}, {"name": "f_array_of_float", "type": {"type": "array", "items": "float"}}, {"name": "f_array_of_double", "type": {"type": "array", "items": "double"}}, {"name": "f_array_of_string", "type": {"type": "array", "items": "string"}}, {"name": "f_another_array_of_string", "type": {"type": "array", "items": "string"}}, {"name": "f_optional_array_of_float", "type": ["null", {"type": "array", "items": "float"}]}], "__fastavro_parsed": true})"); +} + +TEST(AvroExample, Reader) { + ASSERT_OK_AND_ASSIGN( + const auto reader, + AvroReader::Create(file::JoinPath(DatasetDir(), "toy_codex-null.avro"))); + EXPECT_THAT( + reader->fields(), + ElementsAre( + AvroField{"f_null", AvroType::kNull}, + AvroField{"f_boolean", AvroType::kBoolean}, + AvroField{"f_int", AvroType::kInt}, + AvroField{"f_long", AvroType::kLong}, + AvroField{"f_float", AvroType::kFloat}, + AvroField{"f_another_float", AvroType::kFloat}, + AvroField{"f_double", AvroType::kDouble}, + AvroField{"f_string", AvroType::kString}, + AvroField{"f_bytes", AvroType::kBytes}, + AvroField{"f_float_optional", AvroType::kFloat, AvroType::kUnknown, + true}, + AvroField{"f_array_of_float", AvroType::kArray, AvroType::kFloat}, + AvroField{"f_array_of_double", AvroType::kArray, AvroType::kDouble}, + AvroField{"f_array_of_string", AvroType::kArray, AvroType::kString}, + AvroField{"f_another_array_of_string", AvroType::kArray, + AvroType::kString}, + AvroField{"f_optional_array_of_float", AvroType::kArray, + AvroType::kFloat, true})); + + EXPECT_EQ(reader->sync_marker(), std::string("\xB6\xC2" + "A\x88\xB8\xC3" + "A\x8C\xDBQF\x92\xDC dB", + 16)); + + // TODO: Add tests for reading the data. + + int record_idx = 0; + while (true) { + LOG(INFO) << "Record " << record_idx; + ASSERT_OK_AND_ASSIGN(const bool has_record, reader->ReadNextRecord()); + if (!has_record) { + break; + } + for (const auto& field : reader->fields()) { + switch (field.type) { + case AvroType::kUnknown: + ASSERT_OK(absl::UnimplementedError("Unknown field")); + break; + case AvroType::kNull: { + // Nothing to read. + } break; + case AvroType::kBoolean: { + ASSERT_OK_AND_ASSIGN(const auto value, + reader->ReadNextFieldBoolean(field)); + EXPECT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), record_idx == 0); + } break; + case AvroType::kLong: + case AvroType::kInt: { + ASSERT_OK_AND_ASSIGN(const auto value, + reader->ReadNextFieldInteger(field)); + EXPECT_TRUE(value.has_value()); + if (field.type == AvroType::kInt) { + EXPECT_EQ(value.value(), (record_idx == 0) ? 5 : 6); + } else { + EXPECT_EQ(value.value(), (record_idx == 0) ? 1234567 : -123); + } + } break; + case AvroType::kFloat: { + ASSERT_OK_AND_ASSIGN(const auto value, + reader->ReadNextFieldFloat(field)); + + if (field.name == "f_float") { + EXPECT_TRUE(value.has_value()); + EXPECT_NEAR(value.value(), (record_idx == 0) ? 3.1415 : -1.234, + 0.0001); + } else if (field.name == "f_float_optional") { + EXPECT_EQ(value.has_value(), record_idx == 0); + if (record_idx == 0) { + EXPECT_NEAR(value.value(), 6.1, 0.001); + } + } else { + EXPECT_TRUE(value.has_value()); + EXPECT_NEAR(value.value(), (record_idx == 0) ? 5.0 : 6.0, 0.0001); + } + } break; + case AvroType::kDouble: { + ASSERT_OK_AND_ASSIGN(const auto value, + reader->ReadNextFieldDouble(field)); + EXPECT_TRUE(value.has_value()); + if (record_idx == 0) { + EXPECT_TRUE(std::isnan(value.value())); + } else { + EXPECT_EQ(value.value(), 6.789); + } + } break; + case AvroType::kBytes: + case AvroType::kString: { + std::string value; + ASSERT_OK_AND_ASSIGN(const auto has_value, + reader->ReadNextFieldString(field, &value)); + EXPECT_TRUE(has_value); + if (field.type == AvroType::kString) { + EXPECT_EQ(value, (record_idx == 0) ? "hello" : ""); + } else { + EXPECT_EQ(value, (record_idx == 0) ? "world" : ""); + } + } break; + case AvroType::kArray: + switch (field.sub_type) { + case AvroType::kFloat: { + std::vector values; + ASSERT_OK_AND_ASSIGN( + const auto has_value, + reader->ReadNextFieldArrayFloat(field, &values)); + if (field.name == "f_array_of_float") { + EXPECT_TRUE(has_value); + if (record_idx == 0) { + EXPECT_THAT(values, ElementsAre(1.0, 2.0, 3.0)); + } else { + EXPECT_THAT(values, ElementsAre(4.0, 5.0, 6.0)); + } + } else { + EXPECT_EQ(has_value, record_idx == 0); + if (record_idx == 0) { + EXPECT_THAT(values, ElementsAre(1.0, 2.0, 3.0)); + } + } + } break; + case AvroType::kDouble: { + std::vector values; + ASSERT_OK_AND_ASSIGN( + const auto has_value, + reader->ReadNextFieldArrayDouble(field, &values)); + EXPECT_TRUE(has_value); + if (record_idx == 0) { + EXPECT_THAT(values, ElementsAre(10.0, 20.0, 30.0)); + } else { + EXPECT_THAT(values, ElementsAre(40.0, 50.0, 60.0)); + } + } break; + case AvroType::kString: + case AvroType::kBytes: { + std::vector values; + ASSERT_OK_AND_ASSIGN( + const auto has_value, + reader->ReadNextFieldArrayString(field, &values)); + EXPECT_TRUE(has_value); + if (record_idx == 0) { + EXPECT_THAT(values, ElementsAre("a", "b", "c")); + } else { + if (field.name == "f_array_of_string") { + EXPECT_THAT(values, ElementsAre("c", "a", "b")); + } else { + EXPECT_THAT(values, ElementsAre("c", "def")); + } + } + } break; + default: + ASSERT_OK(absl::UnimplementedError("Unsupported type")); + } + break; + } + } + record_idx++; + } + + EXPECT_EQ(record_idx, 2); + ASSERT_OK(reader->Close()); +} + +} // namespace +} // namespace yggdrasil_decision_forests::dataset::avro diff --git a/yggdrasil_decision_forests/dataset/data_spec.cc b/yggdrasil_decision_forests/dataset/data_spec.cc index 2ef58fbb..6f4e2419 100644 --- a/yggdrasil_decision_forests/dataset/data_spec.cc +++ b/yggdrasil_decision_forests/dataset/data_spec.cc @@ -994,5 +994,18 @@ std::string UnstackedColumnName(const absl::string_view original_name, return absl::StrFormat("%s__%05d", original_name, dim_idx); } +std::vector UnstackedColumnNamesV2(absl::string_view original_name, + int num_dims) { + const int num_leading_zeroes = std::log10(num_dims) + 1; + std::vector result; + result.reserve(num_dims); + for (int dim_idx = 0; dim_idx < num_dims; dim_idx++) { + result.push_back(absl::StrFormat("%s.%0*d_of_%0*d", original_name, + num_leading_zeroes, dim_idx, + num_leading_zeroes, num_dims)); + } + return result; +} + } // namespace dataset } // namespace yggdrasil_decision_forests diff --git a/yggdrasil_decision_forests/dataset/data_spec.h b/yggdrasil_decision_forests/dataset/data_spec.h index 3f23e040..701ad062 100644 --- a/yggdrasil_decision_forests/dataset/data_spec.h +++ b/yggdrasil_decision_forests/dataset/data_spec.h @@ -252,6 +252,12 @@ inline uint64_t HashColumnInteger(int64_t value) { // Name of an unrolled column. std::string UnstackedColumnName(absl::string_view original_name, int dim_idx); +// Generate the unrolled column names following the naming convention used in +// PYDF. This is the convention used in all new codes. +// See "unrolled_feature_names" in "port/python/ydf/dataset/io/dataset_io.py" +std::vector UnstackedColumnNamesV2(absl::string_view original_name, + int num_dims); + } // namespace dataset } // namespace yggdrasil_decision_forests diff --git a/yggdrasil_decision_forests/dataset/data_spec_inference.cc b/yggdrasil_decision_forests/dataset/data_spec_inference.cc index 9b4bf591..5ce2ccce 100644 --- a/yggdrasil_decision_forests/dataset/data_spec_inference.cc +++ b/yggdrasil_decision_forests/dataset/data_spec_inference.cc @@ -167,7 +167,12 @@ void UpdateComputeSpecDiscretizedNumerical( } void UpdateComputeSpecBooleanFeature(float value, proto::Column* column) { - if (value >= 0.5f) { + UpdateComputeSpecBooleanFeatureWithBool(value >= 0.5f, column); +} + +void UpdateComputeSpecBooleanFeatureWithBool(bool value, + proto::Column* column) { + if (value) { column->mutable_boolean()->set_count_true(column->boolean().count_true() + 1); } else { @@ -399,6 +404,26 @@ void CreateDataSpec(absl::string_view typed_path, bool use_flume, CHECK_OK(CreateDataSpecWithStatus(typed_path, use_flume, guide, data_spec)); } +absl::Status AbstractDataSpecCreator::CreateDataspec( + const std::vector& paths, + const proto::DataSpecificationGuide& guide, + proto::DataSpecification* data_spec) { + // Detect the column names and semantics. + RETURN_IF_ERROR(InferColumnsAndTypes(paths, guide, data_spec)); + FinalizeInferTypes(guide, data_spec); + LOG(INFO) << data_spec->columns_size() << " column(s) found"; + + // Computes the statistics (e.g. dictionaries, ratio of missing values) for + // each column. + proto::DataSpecificationAccumulator accumulator; + InitializeDataspecAccumulator(*data_spec, &accumulator); + // TODO: Call ComputeColumnStatistics in parallel on the other paths. + RETURN_IF_ERROR( + ComputeColumnStatistics(paths, guide, data_spec, &accumulator)); + RETURN_IF_ERROR(FinalizeComputeSpec(guide, accumulator, data_spec)); + return absl::OkStatus(); +} + absl::Status CreateDataSpecWithStatus( const absl::string_view typed_path, const bool use_flume, const proto::DataSpecificationGuide& guide, @@ -421,21 +446,7 @@ absl::Status CreateDataSpecWithStatus( // Create the dataspec creator. const auto& format_name = proto::DatasetFormat_Name(format); auto creator = AbstractDataSpecCreatorRegisterer::Create(format_name).value(); - - // Detect the column names and semantics. - RETURN_IF_ERROR(creator->InferColumnsAndTypes(paths, guide, data_spec)); - FinalizeInferTypes(guide, data_spec); - LOG(INFO) << data_spec->columns_size() << " column(s) found"; - - // Computes the statistics (e.g. dictionaries, ratio of missing values) for - // each column. - proto::DataSpecificationAccumulator accumulator; - InitializeDataspecAccumulator(*data_spec, &accumulator); - // TODO: Call ComputeColumnStatistics in parallel other the different - // paths. - RETURN_IF_ERROR( - creator->ComputeColumnStatistics(paths, guide, data_spec, &accumulator)); - RETURN_IF_ERROR(FinalizeComputeSpec(guide, accumulator, data_spec)); + RETURN_IF_ERROR(creator->CreateDataspec(paths, guide, data_spec)); LOG(INFO) << "Finalizing [" << data_spec->created_num_rows() << " row(s) found]"; diff --git a/yggdrasil_decision_forests/dataset/data_spec_inference.h b/yggdrasil_decision_forests/dataset/data_spec_inference.h index 568a8299..7ded9e29 100644 --- a/yggdrasil_decision_forests/dataset/data_spec_inference.h +++ b/yggdrasil_decision_forests/dataset/data_spec_inference.h @@ -68,14 +68,24 @@ class AbstractDataSpecCreator { virtual absl::Status InferColumnsAndTypes( const std::vector& paths, const proto::DataSpecificationGuide& guide, - proto::DataSpecification* data_spec) = 0; + proto::DataSpecification* data_spec) { + return absl::UnimplementedError("Not implemented"); + } // Accumulate statistics about each features. virtual absl::Status ComputeColumnStatistics( const std::vector& paths, const proto::DataSpecificationGuide& guide, proto::DataSpecification* data_spec, - proto::DataSpecificationAccumulator* accumulator) = 0; + proto::DataSpecificationAccumulator* accumulator) { + return absl::UnimplementedError("Not implemented"); + } + + // End2End function to create a dataspec from a dataset. + virtual absl::Status CreateDataspec( + const std::vector& paths, + const proto::DataSpecificationGuide& guide, + proto::DataSpecification* data_spec); // Counts the number of examples. virtual absl::StatusOr CountExamples(absl::string_view path) = 0; @@ -160,6 +170,10 @@ void UpdateComputeSpecDiscretizedNumerical( // observation. void UpdateComputeSpecBooleanFeature(float value, proto::Column* column); +// Update the accumulator with a boolean feature value with a boolean +// observation. +void UpdateComputeSpecBooleanFeatureWithBool(bool value, proto::Column* column); + // Update all the columns in a data spec with the appropriate guide information. // Used when inferring the column type from a csv file. absl::Status UpdateColSpecsWithGuideInfo( diff --git a/yggdrasil_decision_forests/dataset/data_spec_test.cc b/yggdrasil_decision_forests/dataset/data_spec_test.cc index 41514ba3..64139c89 100644 --- a/yggdrasil_decision_forests/dataset/data_spec_test.cc +++ b/yggdrasil_decision_forests/dataset/data_spec_test.cc @@ -654,6 +654,23 @@ TEST(Dataset, BuildColIdxToFeatureLabelIdx) { EXPECT_THAT(map, ElementsAre(0, -1)); } +TEST(Dataset, UnstackedColumnNamesV2) { + EXPECT_THAT(UnstackedColumnNamesV2("a", 3), + ElementsAre("a.0_of_3", "a.1_of_3", "a.2_of_3")); + + const auto item_30 = UnstackedColumnNamesV2("a", 30); + EXPECT_EQ(item_30.size(), 30); + EXPECT_EQ(item_30[0], "a.00_of_30"); + EXPECT_EQ(item_30[1], "a.01_of_30"); + EXPECT_EQ(item_30[29], "a.29_of_30"); + + const auto item_300 = UnstackedColumnNamesV2("a", 300); + EXPECT_EQ(item_300.size(), 300); + EXPECT_EQ(item_300[0], "a.000_of_300"); + EXPECT_EQ(item_300[1], "a.001_of_300"); + EXPECT_EQ(item_300[299], "a.299_of_300"); +} + } // namespace } // namespace dataset } // namespace yggdrasil_decision_forests diff --git a/yggdrasil_decision_forests/dataset/example_reader_test.cc b/yggdrasil_decision_forests/dataset/example_reader_test.cc index 806d4409..68cb1f90 100644 --- a/yggdrasil_decision_forests/dataset/example_reader_test.cc +++ b/yggdrasil_decision_forests/dataset/example_reader_test.cc @@ -94,6 +94,7 @@ TEST(ExampleReader, CreateExampleReader) { TEST(ExampleReader, IsFormatSupported) { EXPECT_TRUE(IsFormatSupported("csv:/path/to/ds").value()); + EXPECT_TRUE(IsFormatSupported("avro:/path/to/ds").value()); EXPECT_FALSE(IsFormatSupported("capacitor:/path/to/ds").value()); EXPECT_FALSE(IsFormatSupported("non-existing-format:/path/to/ds").value()); } diff --git a/yggdrasil_decision_forests/dataset/formats.cc b/yggdrasil_decision_forests/dataset/formats.cc index ccc415c6..4c938cd1 100644 --- a/yggdrasil_decision_forests/dataset/formats.cc +++ b/yggdrasil_decision_forests/dataset/formats.cc @@ -67,6 +67,7 @@ const std::vector& GetFormats() { formats->push_back({ .extension = "tfrecord", .prefix = FORMAT_TFE_TFRECORDV2, + .prefix_alias = "tfrecord-nocompression", .proto_format = proto::FORMAT_TFE_TFRECORDV2, }); @@ -79,6 +80,12 @@ const std::vector& GetFormats() { .proto_format = proto::FORMAT_TFE_TFRECORD_COMPRESSED_V2, }); + formats->push_back({ + .extension = "avro", + .prefix = "avro", + .proto_format = proto::FORMAT_AVRO, + }); + // Partially computed (e.g. non indexed) dataset cache. formats->push_back({ .extension = "partial_dataset_cache", diff --git a/yggdrasil_decision_forests/dataset/formats.proto b/yggdrasil_decision_forests/dataset/formats.proto index 2abfa595..73ee49b3 100644 --- a/yggdrasil_decision_forests/dataset/formats.proto +++ b/yggdrasil_decision_forests/dataset/formats.proto @@ -28,4 +28,5 @@ enum DatasetFormat { FORMAT_TFE_TFRECORDV2 = 8; FORMAT_TFE_TFRECORD_COMPRESSED_V2 = 9; FORMAT_PARTIAL_DATASET_CACHE = 7; + FORMAT_AVRO = 10; } diff --git a/yggdrasil_decision_forests/learner/isolation_forest/isolation_forest_test.cc b/yggdrasil_decision_forests/learner/isolation_forest/isolation_forest_test.cc index 45153ccf..b6b2d3a0 100644 --- a/yggdrasil_decision_forests/learner/isolation_forest/isolation_forest_test.cc +++ b/yggdrasil_decision_forests/learner/isolation_forest/isolation_forest_test.cc @@ -187,7 +187,7 @@ TEST_F(IsolationForestOnAdult, Oblique) { isolation_forest::proto::isolation_forest_config); if_config->mutable_decision_tree()->mutable_sparse_oblique_split(); TrainAndEvaluateModel(); - EXPECT_NEAR(metric::Accuracy(evaluation_), 0.719, 0.02); + EXPECT_NEAR(metric::Accuracy(evaluation_), 0.719, 0.025); EXPECT_NEAR(evaluation_.classification().rocs(1).auc(), 0.501, 0.04); } diff --git a/yggdrasil_decision_forests/library.bzl b/yggdrasil_decision_forests/library.bzl index db4e8276..753743d6 100644 --- a/yggdrasil_decision_forests/library.bzl +++ b/yggdrasil_decision_forests/library.bzl @@ -1,14 +1,17 @@ +"""Inject YDF dependencies.""" + +load("//third_party/absl:workspace.bzl", absl = "deps") +load("//third_party/boost_math:workspace.bzl", boost_math = "deps") +load("//third_party/eigen3:workspace.bzl", eigen = "deps") +load("//third_party/farmhash:workspace.bzl", farmhash = "deps") +load("//third_party/grpc:workspace.bzl", grpc = "deps") + # Third party libraries load("//third_party/gtest:workspace.bzl", gtest = "deps") -load("//third_party/absl:workspace.bzl", absl = "deps") +load("//third_party/nlohmann_json:workspace.bzl", nlohmann_json = "deps") load("//third_party/protobuf:workspace.bzl", protobuf = "deps") -load("//third_party/zlib:workspace.bzl", zlib = "deps") load("//third_party/tensorflow:workspace.bzl", tensorflow = "deps") -load("//third_party/farmhash:workspace.bzl", farmhash = "deps") -load("//third_party/boost_math:workspace.bzl", boost_math = "deps") -load("//third_party/grpc:workspace.bzl", grpc = "deps") -load("//third_party/rapidjson:workspace.bzl", rapidjson = "deps") -load("//third_party/eigen3:workspace.bzl", eigen = "deps") +load("//third_party/zlib:workspace.bzl", zlib = "deps") def load_dependencies(repo_name = "", exclude_repo = []): if "gtest" not in exclude_repo: @@ -35,8 +38,8 @@ def load_dependencies(repo_name = "", exclude_repo = []): if "grpc" not in exclude_repo: grpc() - if "rapidjson" not in exclude_repo: - rapidjson(prefix = repo_name) - if "eigen" not in exclude_repo: eigen() + + if "nlohmann_json" not in exclude_repo: + nlohmann_json() diff --git a/yggdrasil_decision_forests/port/javascript/BUILD b/yggdrasil_decision_forests/port/javascript/BUILD index 94d1d235..75be57c5 100644 --- a/yggdrasil_decision_forests/port/javascript/BUILD +++ b/yggdrasil_decision_forests/port/javascript/BUILD @@ -1,111 +1,11 @@ -load("@emsdk//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary") - package( - default_visibility = ["//yggdrasil_decision_forests/port/javascript:users"], + default_visibility = ["//visibility:public"], licenses = ["notice"], ) package_group( name = "users", packages = [ - "//learning/lib/ami/simple_ml/ports/javascript/...", "//yggdrasil_decision_forests/port/javascript/...", ], ) - -exports_files(["wrapper.js"]) - -# Change the extension of the wrapper js file. This is necessary for Emscripten. -genrule( - name = "wrapper", - srcs = ["wrapper.js"], - outs = ["wrapper.lds"], - cmd = "cat $(SRCS) > $@", - tags = ["manual"], - visibility = ["//visibility:private"], -) - -# Web assembly logic (part 1). -# -# See https://github.com/emscripten-core/emscripten/blob/main/src/settings.js for the description -# of the linkops. -cc_binary( - name = "inference", - srcs = ["inference.cc"], - defines = [], - linkopts = [ - "--bind", - "--minify=0", - "-s USE_PTHREADS=0", - "-s EXPORTED_RUNTIME_METHODS=FS", # To access YDF output file from JS. - "-s ALLOW_MEMORY_GROWTH=1", - "-s EXIT_RUNTIME=0", - "-s MALLOC=emmalloc", - "-s MODULARIZE=1", - "-s EXPORT_ES6=0", - "-s DYNAMIC_EXECUTION=0", - "-s EXPORT_NAME=YggdrasilDecisionForests", - "-s FILESYSTEM=1", # Link filesystem (should be automatic in some cases). - # "-s -g", # Function names in stack trace. - # "-s ASSERTIONS=2", # Runtime checks for common memory allocation errors. - # "-s DEMANGLE_SUPPORT=1", # Better function name in stack stace. - # fetchSettings is included to bypass CORS issues during development - "-s INCOMING_MODULE_JS_API=onRuntimeInitialized,fetchSettings,print,printErr,locateFile", - "--post-js yggdrasil_decision_forests/port/javascript/wrapper.js", - ], - tags = [ - "manual", - ], - visibility = ["//visibility:private"], - deps = [ - "//yggdrasil_decision_forests/learner:learner_library", - "//yggdrasil_decision_forests/learner/cart", - "//yggdrasil_decision_forests/learner/gradient_boosted_trees", - "//yggdrasil_decision_forests/learner/random_forest", - "//yggdrasil_decision_forests/model:model_library", - "//yggdrasil_decision_forests/serving:example_set", - "//yggdrasil_decision_forests/serving:fast_engine", - "//yggdrasil_decision_forests/utils:logging", - ], -) - -# Web assembly logic (part 2). -wasm_cc_binary( - name = "inference_wasm", - cc_target = ":inference", - tags = [ - "manual", - ], -) - -# Extract the emscriptten wasm file. -genrule( - name = "extract_wasm_file", - srcs = [":inference_wasm"], - outs = ["inference.wasm"], - cmd = "cp $(BINDIR)/yggdrasil_decision_forests/port/javascript/inference_wasm/inference.wasm $(@D)/", - tags = ["manual"], -) - -# Extract the merged emscriptten js + wrapper file. -genrule( - name = "extract_js_file", - srcs = [":inference_wasm"], - outs = ["inference.js"], - cmd = "cp $(BINDIR)/yggdrasil_decision_forests/port/javascript/inference_wasm/inference.js $(@D)/", - tags = ["manual"], -) - -# Zip the library. -genrule( - name = "create_release", - srcs = [ - ":extract_wasm_file", - ":extract_js_file", - ], - outs = ["ydf.zip"], - cmd = "zip -j $@ $(locations :extract_wasm_file) $(locations :extract_js_file) && " + - "echo Zipfile information: && zipinfo $@ && " + - "echo Zipfile ls: && ls -lh $@", - tags = ["manual"], -) diff --git a/yggdrasil_decision_forests/port/javascript/CHANGELOG.md b/yggdrasil_decision_forests/port/javascript/CHANGELOG.md new file mode 100644 index 00000000..cb60fb27 --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/CHANGELOG.md @@ -0,0 +1,7 @@ +## Training 0.0.1, Inference 0.0.4 - 2024-10-15 + +Initial release of ydf-training, name change for ydf-inference + +#### Release music + +Tiger Rag. Eddie Edwards, Nick LaRocca, Henry Ragas, Tony Sbarbaro, Larry Shields, Harry DeCosta \ No newline at end of file diff --git a/yggdrasil_decision_forests/port/javascript/README.md b/yggdrasil_decision_forests/port/javascript/README.md index 5b33b0b8..3197405c 100644 --- a/yggdrasil_decision_forests/port/javascript/README.md +++ b/yggdrasil_decision_forests/port/javascript/README.md @@ -1,8 +1,12 @@ # Yggdrasil / TensorFlow Decision Forests in Javascript -The JavaScript API makes it possible to run an Yggdrasil Decision Forests model -or a TensorFlow Decision Forests model in a webpage. +The JavaScript API makes it possible to run a YDF model +or a TensorFlow Decision Forests model on the web. + +There are two packages available: + +- Inference only: [ydf-inference npm package](https://www.npmjs.com/package/ydf-inference) + (previously known as yggdrasil-decision-forests). + +- Training and inference: [ydf-training npm package](https://www.npmjs.com/package/ydf-training) -Check the -[yggdrasil-decision-forests npm package](https://www.npmjs.com/package/yggdrasil-decision-forests) -for details. diff --git a/yggdrasil_decision_forests/port/javascript/example/BUILD b/yggdrasil_decision_forests/port/javascript/example/BUILD index 90603be0..26255d49 100644 --- a/yggdrasil_decision_forests/port/javascript/example/BUILD +++ b/yggdrasil_decision_forests/port/javascript/example/BUILD @@ -1,5 +1,5 @@ package( - default_visibility = ["//yggdrasil_decision_forests/port/javascript:users"], + default_visibility = ["//visibility:public"], licenses = ["notice"], ) diff --git a/yggdrasil_decision_forests/port/javascript/example/README.md b/yggdrasil_decision_forests/port/javascript/example/README.md index ad471830..504e9372 100644 --- a/yggdrasil_decision_forests/port/javascript/example/README.md +++ b/yggdrasil_decision_forests/port/javascript/example/README.md @@ -1,5 +1,7 @@ # Usage example for Yggdrasil Decision Forests in JavaScript +## Inference + See "../README.md" for details. See @@ -38,3 +40,8 @@ head /tmp/predictions.csv # 0.219888,0.780112 # 0.88848,0.11152 ``` + + +## Training + +See `training_example.html` for an example of the training code. \ No newline at end of file diff --git a/yggdrasil_decision_forests/port/javascript/example/example.html b/yggdrasil_decision_forests/port/javascript/example/example.html index 0e381946..77400f5f 100644 --- a/yggdrasil_decision_forests/port/javascript/example/example.html +++ b/yggdrasil_decision_forests/port/javascript/example/example.html @@ -2,7 +2,7 @@ - Yggdrasil Decision Forests in Javascript + Yggdrasil Decision Forests Inference in Javascript diff --git a/yggdrasil_decision_forests/port/javascript/example/training_example.html b/yggdrasil_decision_forests/port/javascript/example/training_example.html new file mode 100644 index 00000000..94dcfd1e --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/example/training_example.html @@ -0,0 +1,195 @@ + + + + + Training YDF models on the Web + + + + + + + + + + + + +

Training YDF models on the Web

+ +

+ This example demonstrates how to train + YDF + models in Javascript. +

+ +

+ + +

+

+ + +

+

+ + +

+

+ + +

+ +
+ + + + +
+ +

+
+ +

+ +

+
+ +

+ + diff --git a/yggdrasil_decision_forests/port/javascript/inference/BUILD b/yggdrasil_decision_forests/port/javascript/inference/BUILD new file mode 100644 index 00000000..424746ad --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/inference/BUILD @@ -0,0 +1,103 @@ +load("@emsdk//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary") + +package( + default_visibility = ["//visibility:public"], + licenses = ["notice"], +) + +exports_files(["wrapper.js"]) + +# Change the extension of the wrapper js file. This is necessary for Emscripten. +genrule( + name = "wrapper", + srcs = ["wrapper.js"], + outs = ["wrapper.lds"], + cmd = "cat $(SRCS) > $@", + tags = ["manual"], + visibility = ["//visibility:private"], +) + +# Web assembly logic (part 1). +# +# See https://github.com/emscripten-core/emscripten/blob/main/src/settings.js for the description +# of the linkops. +cc_binary( + name = "inference", + srcs = ["inference.cc"], + defines = [], + linkopts = [ + "--bind", + "--minify=0", + "-s USE_PTHREADS=0", + "-s EXPORTED_RUNTIME_METHODS=FS", # To access YDF output file from JS. + "-s ALLOW_MEMORY_GROWTH=1", + "-s EXIT_RUNTIME=0", + "-s MALLOC=emmalloc", + "-s MODULARIZE=1", + "-s EXPORT_ES6=0", + "-s DYNAMIC_EXECUTION=0", + "-s EXPORT_NAME=YDFInference", + "-s FILESYSTEM=1", # Link filesystem (should be automatic in some cases). + # "-s -g", # Function names in stack trace. + # "-s ASSERTIONS=2", # Runtime checks for common memory allocation errors. + # "-s DEMANGLE_SUPPORT=1", # Better function name in stack stace. + # fetchSettings is included to bypass CORS issues during development + "-s INCOMING_MODULE_JS_API=onRuntimeInitialized,fetchSettings,print,printErr,locateFile", + "--post-js yggdrasil_decision_forests/port/javascript/inference/wrapper.js", + ], + tags = [ + "manual", + ], + visibility = ["//visibility:private"], + deps = [ + "//yggdrasil_decision_forests/learner:learner_library", + "//yggdrasil_decision_forests/learner/cart", + "//yggdrasil_decision_forests/learner/gradient_boosted_trees", + "//yggdrasil_decision_forests/learner/random_forest", + "//yggdrasil_decision_forests/model:model_library", + "//yggdrasil_decision_forests/serving:example_set", + "//yggdrasil_decision_forests/serving:fast_engine", + "//yggdrasil_decision_forests/utils:logging", + ], +) + +# Web assembly logic (part 2). +wasm_cc_binary( + name = "inference_wasm", + cc_target = ":inference", + tags = [ + "manual", + ], +) + +# Extract the emscriptten wasm file. +genrule( + name = "extract_wasm_file", + srcs = [":inference_wasm"], + outs = ["inference.wasm"], + cmd = "cp $(BINDIR)/yggdrasil_decision_forests/port/javascript/inference/inference_wasm/inference.wasm $(@D)/", + tags = ["manual"], +) + +# Extract the merged emscriptten js + wrapper file. +genrule( + name = "extract_js_file", + srcs = [":inference_wasm"], + outs = ["inference.js"], + cmd = "cp $(BINDIR)/yggdrasil_decision_forests/port/javascript/inference/inference_wasm/inference.js $(@D)/", + tags = ["manual"], +) + +# Zip the library. +genrule( + name = "create_release", + srcs = [ + ":extract_wasm_file", + ":extract_js_file", + ], + outs = ["ydf.zip"], + cmd = "zip -j $@ $(locations :extract_wasm_file) $(locations :extract_js_file) && " + + "echo Zipfile information: && zipinfo $@ && " + + "echo Zipfile ls: && ls -lh $@", + tags = ["manual"], +) diff --git a/yggdrasil_decision_forests/port/javascript/externs.js b/yggdrasil_decision_forests/port/javascript/inference/externs.js similarity index 100% rename from yggdrasil_decision_forests/port/javascript/externs.js rename to yggdrasil_decision_forests/port/javascript/inference/externs.js diff --git a/yggdrasil_decision_forests/port/javascript/inference.cc b/yggdrasil_decision_forests/port/javascript/inference/inference.cc similarity index 100% rename from yggdrasil_decision_forests/port/javascript/inference.cc rename to yggdrasil_decision_forests/port/javascript/inference/inference.cc diff --git a/yggdrasil_decision_forests/port/javascript/karma.conf.js b/yggdrasil_decision_forests/port/javascript/inference/karma.conf.js similarity index 94% rename from yggdrasil_decision_forests/port/javascript/karma.conf.js rename to yggdrasil_decision_forests/port/javascript/inference/karma.conf.js index 3356ea4f..39d2763c 100644 --- a/yggdrasil_decision_forests/port/javascript/karma.conf.js +++ b/yggdrasil_decision_forests/port/javascript/inference/karma.conf.js @@ -21,14 +21,14 @@ module.exports = function(config) { basePath = 'third_party/yggdrasil_decision_forests/port/javascript/'; config.files.push({ - pattern: basePath + 'inference_wasm/inference.js', + pattern: basePath + 'inference/inference_wasm/inference.js', watched: false, served: true, nocache: false, included: true, }); config.files.push({ - pattern: basePath + 'inference_wasm/inference.wasm', + pattern: basePath + 'inference/inference_wasm/inference.wasm', watched: false, served: true, nocache: false, diff --git a/yggdrasil_decision_forests/port/javascript/npm/README.md b/yggdrasil_decision_forests/port/javascript/inference/npm/README.md similarity index 94% rename from yggdrasil_decision_forests/port/javascript/npm/README.md rename to yggdrasil_decision_forests/port/javascript/inference/npm/README.md index 858c4980..70c5a685 100644 --- a/yggdrasil_decision_forests/port/javascript/npm/README.md +++ b/yggdrasil_decision_forests/port/javascript/inference/npm/README.md @@ -40,7 +40,7 @@ Then: ```js (async function (){ // Load the YDF library - const ydf = await require("yggdrasil-decision-forests")(); + const ydf = await require("ydf-inference")(); // Load the model const fs = require("node:fs"); @@ -77,10 +77,10 @@ Then: ```js import * as fs from "node:fs"; -import YggdrasilDecisionForests from 'yggdrasil-decision-forests'; +import YDFInference from 'ydf-inference'; // Load the YDF library -let ydf = await YggdrasilDecisionForests(); +let ydf = await YDFInference(); // Load the model let model = await ydf.loadModelFromZipBlob(fs.readFileSync("./model.zip")); @@ -114,10 +114,10 @@ model.unload(); ### Run the model with in Browser ```html - + + + +``` + +### Run the model with NodeJS and CommonJS + +```js +(async function (){ + // Load the YDF library. + const ydf = await require("ydf-training")(); + + // Load the model. + const fs = require("node:fs"); + const data = fs.readFileSync("data.csv", 'utf-8'); + const task = "CLASSIFICATION"; + const label = "label"; + const model = new ydf.GradientBoostedTreesLearner(label, task).train(data); + + // Make predictions. + const predictions = model.predict(data); + console.log("predictions:", predictions); + + // Describe the model. + const description = model.describe(); + console.log( predictions); + + // Save the model to disk. + var fileReader = new FileReader(); + fileReader.onload = function() { + fs.writeFileSync('model.zip', Buffer.from(new Uint8Array(this.result))); + }; + const blob = await model.save(); + fileReader.readAsArrayBuffer(blob); + + // Release model + model.unload(); +}()) +``` + +### Run the model with NodeJS and ES6 + +```js +import * as fs from "node:fs"; +import YDFTraining from 'ydf-training'; + +// Load the YDF library +let ydf = await YDFTraining(); + +const data = fs.readFileSync("data.csv", 'utf-8'); +const task = "CLASSIFICATION"; +const label = "label"; +const model = new ydf.GradientBoostedTreesLearner(label, task).train(data); + +// Make predictions. +const predictions = model.predict(data); +console.log("predictions:", predictions); + +// Describe the model. +const description = model.describe(); +console.log( predictions); + +// Save the model to disk. +var fileReader = new FileReader(); +fileReader.onload = function() { + fs.writeFileSync('model.zip', Buffer.from(new Uint8Array(this.result))); +}; +const blob = await model.save(); +fileReader.readAsArrayBuffer(blob); + +// Release model +model.unload(); +``` + +## For developers + +### Run unit tests + +```sh +npm test +``` + +### Update the binary bundle + +Building the binary bundle requires Bazel and Node.js installed. + +```sh +# Assume the shell is located in a clone of: +# https://github.com/google/yggdrasil-decision-forests.git + +# Compile the YDF Training +yggdrasil_decision_forests/port/javascript/tools/build_zipped_library.sh +``` + +You can find the compiled bundle in +`third_party/yggdrasil_decision_forests/port/javascript/training/npm/` \ No newline at end of file diff --git a/yggdrasil_decision_forests/port/javascript/training/npm/dist/readme.txt b/yggdrasil_decision_forests/port/javascript/training/npm/dist/readme.txt new file mode 100644 index 00000000..64cc9d4a --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/npm/dist/readme.txt @@ -0,0 +1,3 @@ +Before submitting the npm package, populate this directory with training.js and +training.wasm generated by +yggdrasil_decision_forests/port/javascript/tools/build_zipped_library.sh. \ No newline at end of file diff --git a/yggdrasil_decision_forests/port/javascript/training/npm/package-lock.json b/yggdrasil_decision_forests/port/javascript/training/npm/package-lock.json new file mode 100644 index 00000000..e69de29b diff --git a/yggdrasil_decision_forests/port/javascript/training/npm/package.json b/yggdrasil_decision_forests/port/javascript/training/npm/package.json new file mode 100644 index 00000000..17deac32 --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/npm/package.json @@ -0,0 +1,37 @@ +{ + "name": "ydf-training", + "version": "0.0.1", + "description": "Training YDF models in Javascript.", + "main": "dist/training.js", + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/google/yggdrasil-decision-forests.git" + }, + "keywords": [ + "ydf", + "machine-learning", + "random-forest", + "gradient-boosting", + "tabular-data", + "interpretable", + "decision-forest", + "decision-tree", + "tensorflow-decision-forest" + ], + "author": "Google Decision Forests (https://https://github.com/google/yggdrasil-decision-forests/)", + "contributors": ["Mathieu Guillame-Bert", "Richard Stotz"], + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/google/yggdrasil-decision-forests/issues" + }, + "homepage": "https://ydf.readthedocs.io", + "dependencies": { + "jszip": "^3.10.1" + }, + "devDependencies": { + "jest": "^29.7.0" + } +} diff --git a/yggdrasil_decision_forests/port/javascript/training/npm/test/abalone.csv b/yggdrasil_decision_forests/port/javascript/training/npm/test/abalone.csv new file mode 100644 index 00000000..0259445d --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/npm/test/abalone.csv @@ -0,0 +1,4178 @@ +Type,LongestShell,Diameter,Height,WholeWeight,ShuckedWeight,VisceraWeight,ShellWeight,Rings +M,0.455,0.365,0.095,0.514,0.2245,0.101,0.15,15 +M,0.35,0.265,0.09,0.2255,0.0995,0.0485,0.07,7 +F,0.53,0.42,0.135,0.677,0.2565,0.1415,0.21,9 +M,0.44,0.365,0.125,0.516,0.2155,0.114,0.155,10 +I,0.33,0.255,0.08,0.205,0.0895,0.0395,0.055,7 +I,0.425,0.3,0.095,0.3515,0.141,0.0775,0.12,8 +F,0.53,0.415,0.15,0.7775,0.237,0.1415,0.33,20 +F,0.545,0.425,0.125,0.768,0.294,0.1495,0.26,16 +M,0.475,0.37,0.125,0.5095,0.2165,0.1125,0.165,9 +F,0.55,0.44,0.15,0.8945,0.3145,0.151,0.32,19 +F,0.525,0.38,0.14,0.6065,0.194,0.1475,0.21,14 +M,0.43,0.35,0.11,0.406,0.1675,0.081,0.135,10 +M,0.49,0.38,0.135,0.5415,0.2175,0.095,0.19,11 +F,0.535,0.405,0.145,0.6845,0.2725,0.171,0.205,10 +F,0.47,0.355,0.1,0.4755,0.1675,0.0805,0.185,10 +M,0.5,0.4,0.13,0.6645,0.258,0.133,0.24,12 +I,0.355,0.28,0.085,0.2905,0.095,0.0395,0.115,7 +F,0.44,0.34,0.1,0.451,0.188,0.087,0.13,10 +M,0.365,0.295,0.08,0.2555,0.097,0.043,0.1,7 +M,0.45,0.32,0.1,0.381,0.1705,0.075,0.115,9 +M,0.355,0.28,0.095,0.2455,0.0955,0.062,0.075,11 +I,0.38,0.275,0.1,0.2255,0.08,0.049,0.085,10 +F,0.565,0.44,0.155,0.9395,0.4275,0.214,0.27,12 +F,0.55,0.415,0.135,0.7635,0.318,0.21,0.2,9 +F,0.615,0.48,0.165,1.1615,0.513,0.301,0.305,10 +F,0.56,0.44,0.14,0.9285,0.3825,0.188,0.3,11 +F,0.58,0.45,0.185,0.9955,0.3945,0.272,0.285,11 +M,0.59,0.445,0.14,0.931,0.356,0.234,0.28,12 +M,0.605,0.475,0.18,0.9365,0.394,0.219,0.295,15 +M,0.575,0.425,0.14,0.8635,0.393,0.227,0.2,11 +M,0.58,0.47,0.165,0.9975,0.3935,0.242,0.33,10 +F,0.68,0.56,0.165,1.639,0.6055,0.2805,0.46,15 +M,0.665,0.525,0.165,1.338,0.5515,0.3575,0.35,18 +F,0.68,0.55,0.175,1.798,0.815,0.3925,0.455,19 +F,0.705,0.55,0.2,1.7095,0.633,0.4115,0.49,13 +M,0.465,0.355,0.105,0.4795,0.227,0.124,0.125,8 +F,0.54,0.475,0.155,1.217,0.5305,0.3075,0.34,16 +F,0.45,0.355,0.105,0.5225,0.237,0.1165,0.145,8 +F,0.575,0.445,0.135,0.883,0.381,0.2035,0.26,11 +M,0.355,0.29,0.09,0.3275,0.134,0.086,0.09,9 +F,0.45,0.335,0.105,0.425,0.1865,0.091,0.115,9 +F,0.55,0.425,0.135,0.8515,0.362,0.196,0.27,14 +I,0.24,0.175,0.045,0.07,0.0315,0.0235,0.02,5 +I,0.205,0.15,0.055,0.042,0.0255,0.015,0.012,5 +I,0.21,0.15,0.05,0.042,0.0175,0.0125,0.015,4 +I,0.39,0.295,0.095,0.203,0.0875,0.045,0.075,7 +M,0.47,0.37,0.12,0.5795,0.293,0.227,0.14,9 +F,0.46,0.375,0.12,0.4605,0.1775,0.11,0.15,7 +I,0.325,0.245,0.07,0.161,0.0755,0.0255,0.045,6 +F,0.525,0.425,0.16,0.8355,0.3545,0.2135,0.245,9 +I,0.52,0.41,0.12,0.595,0.2385,0.111,0.19,8 +M,0.4,0.32,0.095,0.303,0.1335,0.06,0.1,7 +M,0.485,0.36,0.13,0.5415,0.2595,0.096,0.16,10 +F,0.47,0.36,0.12,0.4775,0.2105,0.1055,0.15,10 +M,0.405,0.31,0.1,0.385,0.173,0.0915,0.11,7 +F,0.5,0.4,0.14,0.6615,0.2565,0.1755,0.22,8 +M,0.445,0.35,0.12,0.4425,0.192,0.0955,0.135,8 +M,0.47,0.385,0.135,0.5895,0.2765,0.12,0.17,8 +I,0.245,0.19,0.06,0.086,0.042,0.014,0.025,4 +F,0.505,0.4,0.125,0.583,0.246,0.13,0.175,7 +M,0.45,0.345,0.105,0.4115,0.18,0.1125,0.135,7 +M,0.505,0.405,0.11,0.625,0.305,0.16,0.175,9 +F,0.53,0.41,0.13,0.6965,0.302,0.1935,0.2,10 +M,0.425,0.325,0.095,0.3785,0.1705,0.08,0.1,7 +M,0.52,0.4,0.12,0.58,0.234,0.1315,0.185,8 +M,0.475,0.355,0.12,0.48,0.234,0.1015,0.135,8 +F,0.565,0.44,0.16,0.915,0.354,0.1935,0.32,12 +F,0.595,0.495,0.185,1.285,0.416,0.224,0.485,13 +F,0.475,0.39,0.12,0.5305,0.2135,0.1155,0.17,10 +I,0.31,0.235,0.07,0.151,0.063,0.0405,0.045,6 +M,0.555,0.425,0.13,0.7665,0.264,0.168,0.275,13 +F,0.4,0.32,0.11,0.353,0.1405,0.0985,0.1,8 +F,0.595,0.475,0.17,1.247,0.48,0.225,0.425,20 +M,0.57,0.48,0.175,1.185,0.474,0.261,0.38,11 +F,0.605,0.45,0.195,1.098,0.481,0.2895,0.315,13 +F,0.6,0.475,0.15,1.0075,0.4425,0.221,0.28,15 +M,0.595,0.475,0.14,0.944,0.3625,0.189,0.315,9 +F,0.6,0.47,0.15,0.922,0.363,0.194,0.305,10 +F,0.555,0.425,0.14,0.788,0.282,0.1595,0.285,11 +F,0.615,0.475,0.17,1.1025,0.4695,0.2355,0.345,14 +F,0.575,0.445,0.14,0.941,0.3845,0.252,0.285,9 +M,0.62,0.51,0.175,1.615,0.5105,0.192,0.675,12 +F,0.52,0.425,0.165,0.9885,0.396,0.225,0.32,16 +M,0.595,0.475,0.16,1.3175,0.408,0.234,0.58,21 +M,0.58,0.45,0.14,1.013,0.38,0.216,0.36,14 +F,0.57,0.465,0.18,1.295,0.339,0.2225,0.44,12 +M,0.625,0.465,0.14,1.195,0.4825,0.205,0.4,13 +M,0.56,0.44,0.16,0.8645,0.3305,0.2075,0.26,10 +F,0.46,0.355,0.13,0.517,0.2205,0.114,0.165,9 +F,0.575,0.45,0.16,0.9775,0.3135,0.231,0.33,12 +M,0.565,0.425,0.135,0.8115,0.341,0.1675,0.255,15 +M,0.555,0.44,0.15,0.755,0.307,0.1525,0.26,12 +M,0.595,0.465,0.175,1.115,0.4015,0.254,0.39,13 +F,0.625,0.495,0.165,1.262,0.507,0.318,0.39,10 +M,0.695,0.56,0.19,1.494,0.588,0.3425,0.485,15 +M,0.665,0.535,0.195,1.606,0.5755,0.388,0.48,14 +M,0.535,0.435,0.15,0.725,0.269,0.1385,0.25,9 +M,0.47,0.375,0.13,0.523,0.214,0.132,0.145,8 +M,0.47,0.37,0.13,0.5225,0.201,0.133,0.165,7 +F,0.475,0.375,0.125,0.5785,0.2775,0.085,0.155,10 +I,0.36,0.265,0.095,0.2315,0.105,0.046,0.075,7 +M,0.55,0.435,0.145,0.843,0.328,0.1915,0.255,15 +M,0.53,0.435,0.16,0.883,0.316,0.164,0.335,15 +M,0.53,0.415,0.14,0.724,0.3105,0.1675,0.205,10 +M,0.605,0.47,0.16,1.1735,0.4975,0.2405,0.345,12 +F,0.52,0.41,0.155,0.727,0.291,0.1835,0.235,12 +F,0.545,0.43,0.165,0.802,0.2935,0.183,0.28,11 +F,0.5,0.4,0.125,0.6675,0.261,0.1315,0.22,10 +F,0.51,0.39,0.135,0.6335,0.231,0.179,0.2,9 +F,0.435,0.395,0.105,0.3635,0.136,0.098,0.13,9 +M,0.495,0.395,0.125,0.5415,0.2375,0.1345,0.155,9 +M,0.465,0.36,0.105,0.431,0.172,0.107,0.175,9 +I,0.435,0.32,0.08,0.3325,0.1485,0.0635,0.105,9 +M,0.425,0.35,0.105,0.393,0.13,0.063,0.165,9 +F,0.545,0.41,0.125,0.6935,0.2975,0.146,0.21,11 +F,0.53,0.415,0.115,0.5915,0.233,0.1585,0.18,11 +F,0.49,0.375,0.135,0.6125,0.2555,0.102,0.22,11 +M,0.44,0.34,0.105,0.402,0.1305,0.0955,0.165,10 +F,0.56,0.43,0.15,0.8825,0.3465,0.172,0.31,9 +M,0.405,0.305,0.085,0.2605,0.1145,0.0595,0.085,8 +F,0.47,0.365,0.105,0.4205,0.163,0.1035,0.14,9 +I,0.385,0.295,0.085,0.2535,0.103,0.0575,0.085,7 +F,0.515,0.425,0.14,0.766,0.304,0.1725,0.255,14 +M,0.37,0.265,0.075,0.214,0.09,0.051,0.07,6 +I,0.36,0.28,0.08,0.1755,0.081,0.0505,0.07,6 +I,0.27,0.195,0.06,0.073,0.0285,0.0235,0.03,5 +I,0.375,0.275,0.09,0.238,0.1075,0.0545,0.07,6 +I,0.385,0.29,0.085,0.2505,0.112,0.061,0.08,8 +M,0.7,0.535,0.16,1.7255,0.63,0.2635,0.54,19 +M,0.71,0.54,0.165,1.959,0.7665,0.261,0.78,18 +M,0.595,0.48,0.165,1.262,0.4835,0.283,0.41,17 +F,0.44,0.35,0.125,0.4035,0.175,0.063,0.129,9 +F,0.325,0.26,0.09,0.1915,0.085,0.036,0.062,7 +I,0.35,0.26,0.095,0.211,0.086,0.056,0.068,7 +I,0.265,0.2,0.065,0.0975,0.04,0.0205,0.028,7 +F,0.425,0.33,0.115,0.406,0.1635,0.081,0.1355,8 +F,0.305,0.23,0.08,0.156,0.0675,0.0345,0.048,7 +M,0.345,0.255,0.09,0.2005,0.094,0.0295,0.063,9 +F,0.405,0.325,0.11,0.3555,0.151,0.063,0.117,9 +M,0.375,0.285,0.095,0.253,0.096,0.0575,0.0925,9 +F,0.565,0.445,0.155,0.826,0.341,0.2055,0.2475,10 +F,0.55,0.45,0.145,0.741,0.295,0.1435,0.2665,10 +M,0.65,0.52,0.19,1.3445,0.519,0.306,0.4465,16 +M,0.56,0.455,0.155,0.797,0.34,0.19,0.2425,11 +M,0.475,0.375,0.13,0.5175,0.2075,0.1165,0.17,10 +F,0.49,0.38,0.125,0.549,0.245,0.1075,0.174,10 +M,0.46,0.35,0.12,0.515,0.224,0.108,0.1565,10 +I,0.28,0.205,0.08,0.127,0.052,0.039,0.042,9 +I,0.175,0.13,0.055,0.0315,0.0105,0.0065,0.0125,5 +I,0.17,0.13,0.095,0.03,0.013,0.008,0.01,4 +M,0.59,0.475,0.145,1.053,0.4415,0.262,0.325,15 +F,0.605,0.5,0.185,1.1185,0.469,0.2585,0.335,9 +F,0.635,0.515,0.19,1.3715,0.5065,0.305,0.45,10 +F,0.605,0.485,0.16,1.0565,0.37,0.2355,0.355,10 +F,0.565,0.45,0.135,0.9885,0.387,0.1495,0.31,12 +M,0.515,0.405,0.13,0.722,0.32,0.131,0.21,10 +F,0.575,0.46,0.19,0.994,0.392,0.2425,0.34,13 +M,0.645,0.485,0.215,1.514,0.546,0.2615,0.635,16 +F,0.58,0.455,0.17,0.9075,0.374,0.2135,0.285,13 +F,0.575,0.46,0.165,1.124,0.2985,0.1785,0.44,13 +M,0.605,0.465,0.165,1.056,0.4215,0.2475,0.34,13 +F,0.605,0.485,0.16,1.222,0.53,0.2575,0.28,13 +M,0.61,0.485,0.175,1.2445,0.544,0.297,0.345,12 +F,0.725,0.56,0.21,2.141,0.65,0.398,1.005,18 +F,0.65,0.545,0.23,1.752,0.5605,0.2895,0.815,16 +M,0.725,0.57,0.19,2.55,1.0705,0.483,0.725,14 +F,0.725,0.575,0.175,2.124,0.765,0.4515,0.85,20 +F,0.68,0.57,0.205,1.842,0.625,0.408,0.65,20 +M,0.705,0.56,0.22,1.981,0.8175,0.3085,0.76,14 +F,0.68,0.515,0.175,1.6185,0.5125,0.409,0.62,12 +M,0.695,0.55,0.215,1.9565,0.7125,0.541,0.59,14 +F,0.53,0.395,0.145,0.775,0.308,0.169,0.255,7 +M,0.525,0.435,0.155,1.065,0.486,0.233,0.285,8 +F,0.52,0.405,0.115,0.776,0.32,0.1845,0.22,8 +I,0.235,0.16,0.04,0.048,0.0185,0.018,0.015,5 +I,0.36,0.26,0.09,0.1785,0.0645,0.037,0.075,7 +I,0.315,0.21,0.06,0.125,0.06,0.0375,0.035,5 +I,0.315,0.245,0.085,0.1435,0.053,0.0475,0.05,8 +I,0.225,0.16,0.045,0.0465,0.025,0.015,0.015,4 +M,0.58,0.475,0.15,0.97,0.385,0.2165,0.35,11 +M,0.57,0.48,0.18,0.9395,0.399,0.2,0.295,14 +M,0.64,0.51,0.175,1.368,0.515,0.266,0.57,21 +F,0.56,0.45,0.16,1.0235,0.429,0.268,0.3,10 +F,0.62,0.475,0.175,1.0165,0.4355,0.214,0.325,10 +F,0.645,0.51,0.2,1.5675,0.621,0.367,0.46,12 +M,0.62,0.49,0.19,1.218,0.5455,0.2965,0.355,13 +F,0.63,0.48,0.15,1.0525,0.392,0.336,0.285,12 +F,0.63,0.5,0.185,1.383,0.54,0.3315,0.38,10 +F,0.63,0.48,0.16,1.199,0.5265,0.335,0.315,11 +F,0.585,0.46,0.17,0.9325,0.365,0.271,0.29,9 +M,0.615,0.48,0.18,1.1595,0.4845,0.2165,0.325,13 +M,0.61,0.485,0.17,1.0225,0.419,0.2405,0.36,12 +M,0.58,0.45,0.15,0.927,0.276,0.1815,0.36,14 +I,0.355,0.275,0.085,0.22,0.092,0.06,0.15,8 +F,0.51,0.4,0.14,0.8145,0.459,0.1965,0.195,10 +M,0.5,0.405,0.155,0.772,0.346,0.1535,0.245,12 +F,0.505,0.41,0.15,0.644,0.285,0.145,0.21,11 +M,0.64,0.5,0.185,1.3035,0.4445,0.2635,0.465,16 +M,0.56,0.45,0.16,0.922,0.432,0.178,0.26,15 +M,0.585,0.46,0.185,0.922,0.3635,0.213,0.285,10 +F,0.45,0.345,0.12,0.4165,0.1655,0.095,0.135,9 +M,0.5,0.4,0.165,0.825,0.254,0.205,0.285,13 +F,0.5,0.4,0.145,0.63,0.234,0.1465,0.23,12 +F,0.53,0.435,0.17,0.8155,0.2985,0.155,0.275,13 +M,0.42,0.335,0.115,0.369,0.171,0.071,0.12,8 +F,0.44,0.34,0.14,0.482,0.186,0.1085,0.16,9 +I,0.4,0.3,0.11,0.315,0.109,0.067,0.12,9 +I,0.435,0.34,0.11,0.3795,0.1495,0.085,0.12,8 +F,0.525,0.415,0.17,0.8325,0.2755,0.1685,0.31,13 +I,0.37,0.28,0.095,0.2655,0.122,0.052,0.08,7 +F,0.49,0.365,0.145,0.6345,0.1995,0.1625,0.22,10 +M,0.335,0.25,0.09,0.181,0.0755,0.0415,0.06,7 +F,0.415,0.325,0.105,0.38,0.1595,0.0785,0.12,12 +M,0.5,0.405,0.14,0.6155,0.241,0.1355,0.205,9 +F,0.485,0.395,0.16,0.66,0.2475,0.128,0.235,14 +M,0.55,0.405,0.14,0.8025,0.244,0.1635,0.255,10 +M,0.45,0.35,0.13,0.46,0.174,0.111,0.135,8 +I,0.405,0.3,0.12,0.324,0.1265,0.07,0.11,7 +M,0.47,0.36,0.135,0.501,0.1665,0.115,0.165,10 +F,0.415,0.305,0.13,0.32,0.1305,0.0755,0.105,8 +F,0.445,0.325,0.125,0.455,0.1785,0.1125,0.14,9 +F,0.47,0.35,0.145,0.5175,0.187,0.1235,0.18,11 +F,0.49,0.375,0.15,0.5755,0.22,0.144,0.19,9 +F,0.445,0.355,0.15,0.485,0.181,0.125,0.155,11 +I,0.425,0.38,0.105,0.3265,0.1285,0.0785,0.1,10 +F,0.5,0.37,0.135,0.45,0.1715,0.1055,0.155,9 +F,0.39,0.29,0.125,0.3055,0.121,0.082,0.09,7 +I,0.365,0.27,0.085,0.205,0.078,0.0485,0.07,7 +F,0.58,0.465,0.165,1.1015,0.404,0.2095,0.35,11 +F,0.53,0.415,0.16,0.783,0.2935,0.158,0.245,15 +M,0.555,0.445,0.135,0.836,0.336,0.1625,0.275,13 +M,0.565,0.44,0.175,0.9025,0.31,0.193,0.325,14 +M,0.625,0.505,0.215,1.4455,0.496,0.287,0.435,22 +I,0.275,0.215,0.075,0.1155,0.0485,0.029,0.035,7 +I,0.44,0.35,0.135,0.435,0.1815,0.083,0.125,12 +I,0.295,0.225,0.08,0.124,0.0485,0.032,0.04,9 +I,0.075,0.055,0.01,0.002,0.001,5e-04,0.0015,1 +I,0.13,0.1,0.03,0.013,0.0045,0.003,0.004,3 +I,0.11,0.09,0.03,0.008,0.0025,0.002,0.003,3 +I,0.16,0.12,0.035,0.021,0.0075,0.0045,0.005,5 +M,0.565,0.425,0.16,0.9425,0.3495,0.2185,0.275,17 +I,0.27,0.2,0.07,0.1,0.034,0.0245,0.035,5 +I,0.23,0.175,0.065,0.0645,0.026,0.0105,0.02,5 +I,0.3,0.23,0.08,0.1275,0.0435,0.0265,0.04,8 +I,0.33,0.255,0.085,0.1655,0.063,0.039,0.06,8 +I,0.35,0.26,0.085,0.174,0.0705,0.0345,0.06,10 +I,0.32,0.245,0.08,0.1585,0.0635,0.0325,0.05,13 +I,0.36,0.275,0.085,0.1975,0.0745,0.0415,0.07,9 +I,0.305,0.245,0.075,0.156,0.0675,0.038,0.045,7 +I,0.345,0.27,0.11,0.2135,0.082,0.0545,0.07,7 +I,0.33,0.25,0.105,0.1715,0.0655,0.035,0.06,7 +M,0.59,0.47,0.18,1.1235,0.4205,0.2805,0.36,13 +F,0.595,0.455,0.155,1.0605,0.5135,0.2165,0.3,12 +F,0.575,0.46,0.185,1.094,0.4485,0.217,0.345,15 +M,0.6,0.495,0.165,1.2415,0.485,0.2775,0.34,15 +M,0.56,0.45,0.175,1.011,0.3835,0.2065,0.37,15 +M,0.56,0.45,0.185,1.07,0.3805,0.175,0.41,19 +M,0.545,0.46,0.16,0.8975,0.341,0.1655,0.345,10 +F,0.635,0.505,0.17,1.415,0.605,0.297,0.365,15 +F,0.59,0.475,0.16,1.1015,0.4775,0.2555,0.295,13 +F,0.54,0.475,0.155,0.928,0.394,0.194,0.26,11 +F,0.57,0.44,0.125,0.865,0.3675,0.1725,0.27,12 +M,0.53,0.42,0.165,0.8945,0.319,0.239,0.245,11 +I,0.245,0.195,0.06,0.095,0.0445,0.0245,0.026,4 +M,0.27,0.2,0.08,0.1205,0.0465,0.028,0.04,6 +F,0.46,0.38,0.13,0.639,0.3,0.1525,0.16,11 +M,0.52,0.45,0.15,0.895,0.3615,0.186,0.235,14 +M,0.35,0.275,0.11,0.2925,0.1225,0.0635,0.0905,8 +M,0.47,0.39,0.15,0.6355,0.2185,0.0885,0.255,9 +F,0.45,0.36,0.125,0.4995,0.2035,0.1,0.17,13 +F,0.64,0.525,0.215,1.779,0.4535,0.2855,0.55,22 +M,0.59,0.5,0.2,1.187,0.412,0.2705,0.37,16 +M,0.62,0.485,0.205,1.219,0.3875,0.2505,0.385,14 +M,0.63,0.505,0.225,1.525,0.56,0.3335,0.45,15 +M,0.63,0.515,0.155,1.259,0.4105,0.197,0.41,13 +M,0.655,0.54,0.215,1.844,0.7425,0.327,0.585,22 +F,0.66,0.53,0.185,1.3485,0.493,0.245,0.49,12 +M,0.61,0.5,0.24,1.642,0.532,0.3345,0.69,18 +M,0.635,0.525,0.205,1.484,0.55,0.3115,0.43,20 +F,0.515,0.425,0.135,0.712,0.2665,0.1605,0.25,11 +F,0.535,0.415,0.185,0.8415,0.314,0.1585,0.3,15 +I,0.36,0.285,0.105,0.2415,0.0915,0.057,0.075,7 +F,0.455,0.355,0.12,0.4495,0.177,0.104,0.15,9 +M,0.485,0.395,0.14,0.6295,0.2285,0.127,0.225,14 +M,0.515,0.38,0.175,0.9565,0.325,0.158,0.31,14 +F,0.535,0.415,0.17,0.879,0.295,0.1965,0.285,10 +M,0.53,0.435,0.155,0.699,0.288,0.1595,0.205,10 +F,0.495,0.4,0.155,0.6445,0.242,0.1325,0.205,17 +M,0.44,0.355,0.125,0.4775,0.132,0.0815,0.19,9 +F,0.535,0.435,0.16,0.8105,0.3155,0.1795,0.24,10 +M,0.54,0.435,0.18,0.996,0.3835,0.226,0.325,17 +F,0.565,0.505,0.21,1.2765,0.501,0.279,0.355,12 +M,0.61,0.475,0.165,1.116,0.428,0.2205,0.315,15 +F,0.565,0.455,0.175,1.013,0.342,0.207,0.35,19 +M,0.6,0.495,0.195,1.0575,0.384,0.19,0.375,26 +I,0.295,0.215,0.085,0.128,0.049,0.034,0.04,6 +I,0.275,0.205,0.075,0.1105,0.045,0.0285,0.035,6 +I,0.28,0.21,0.085,0.1065,0.039,0.0295,0.03,4 +M,0.49,0.395,0.14,0.549,0.2215,0.1275,0.15,11 +M,0.37,0.28,0.105,0.234,0.0905,0.0585,0.075,9 +F,0.405,0.305,0.095,0.3485,0.1455,0.0895,0.1,9 +F,0.54,0.435,0.175,0.892,0.322,0.174,0.335,13 +M,0.37,0.28,0.1,0.252,0.1065,0.0595,0.074,8 +M,0.36,0.27,0.1,0.217,0.0885,0.0495,0.0715,6 +F,0.47,0.36,0.13,0.472,0.182,0.114,0.15,10 +I,0.2,0.145,0.06,0.037,0.0125,0.0095,0.011,4 +I,0.165,0.12,0.03,0.0215,0.007,0.005,0.005,3 +M,0.645,0.515,0.24,1.5415,0.471,0.369,0.535,13 +M,0.55,0.41,0.125,0.7605,0.2505,0.1635,0.195,14 +M,0.57,0.435,0.145,0.9055,0.3925,0.2355,0.275,10 +F,0.63,0.485,0.19,1.2435,0.4635,0.3055,0.39,21 +M,0.56,0.44,0.14,0.971,0.443,0.2045,0.265,14 +M,0.595,0.455,0.195,1.3305,0.4595,0.3235,0.345,19 +F,0.62,0.47,0.2,1.2255,0.381,0.27,0.435,23 +M,0.63,0.485,0.175,1.3,0.4335,0.2945,0.46,23 +I,0.45,0.355,0.11,0.4585,0.194,0.067,0.14,8 +F,0.635,0.535,0.19,1.242,0.576,0.2475,0.39,14 +M,0.45,0.35,0.1,0.3675,0.1465,0.1015,0.12,10 +F,0.58,0.455,0.155,0.8365,0.315,0.1385,0.32,18 +I,0.33,0.255,0.095,0.172,0.066,0.0255,0.06,6 +I,0.265,0.21,0.06,0.0965,0.0425,0.022,0.03,5 +I,0.19,0.145,0.04,0.038,0.0165,0.0065,0.015,4 +M,0.385,0.31,0.1,0.2845,0.1065,0.075,0.1,11 +I,0.265,0.205,0.07,0.1055,0.039,0.041,0.035,5 +M,0.335,0.265,0.105,0.222,0.0935,0.056,0.075,7 +I,0.355,0.275,0.09,0.251,0.097,0.053,0.08,7 +I,0.32,0.255,0.1,0.1755,0.073,0.0415,0.065,7 +M,0.51,0.4,0.13,0.6435,0.27,0.1665,0.205,12 +M,0.36,0.295,0.105,0.241,0.0865,0.053,0.095,8 +I,0.36,0.28,0.09,0.2255,0.0885,0.04,0.09,8 +M,0.5,0.38,0.155,0.5955,0.2135,0.161,0.2,12 +F,0.4,0.325,0.12,0.3185,0.134,0.0565,0.095,8 +I,0.3,0.22,0.08,0.121,0.0475,0.042,0.035,5 +I,0.235,0.175,0.04,0.0705,0.0335,0.015,0.02,5 +F,0.74,0.6,0.195,1.974,0.598,0.4085,0.71,16 +M,0.62,0.465,0.19,1.3415,0.5705,0.3175,0.355,11 +M,0.6,0.475,0.19,1.0875,0.403,0.2655,0.325,14 +M,0.59,0.45,0.185,1.283,0.473,0.276,0.425,16 +M,0.62,0.475,0.185,1.325,0.6045,0.325,0.33,13 +F,0.565,0.45,0.195,1.0035,0.406,0.2505,0.285,15 +M,0.575,0.455,0.145,1.165,0.581,0.2275,0.3,14 +F,0.62,0.51,0.205,1.3475,0.4775,0.2565,0.48,14 +M,0.62,0.465,0.185,1.274,0.579,0.3065,0.32,12 +F,0.505,0.375,0.18,0.568,0.2325,0.1495,0.17,12 +F,0.46,0.425,0.155,0.746,0.3005,0.152,0.24,8 +M,0.49,0.39,0.14,0.707,0.2795,0.2185,0.18,13 +F,0.525,0.42,0.16,0.756,0.2745,0.173,0.275,9 +I,0.34,0.26,0.08,0.2,0.08,0.0555,0.055,6 +I,0.375,0.305,0.115,0.2715,0.092,0.074,0.09,8 +M,0.61,0.48,0.15,1.2,0.56,0.2455,0.28,14 +F,0.61,0.495,0.185,1.153,0.536,0.2905,0.245,8 +F,0.585,0.45,0.17,0.8685,0.3325,0.1635,0.27,22 +M,0.57,0.46,0.14,0.9535,0.4465,0.2065,0.245,12 +M,0.58,0.455,0.17,0.93,0.408,0.259,0.22,9 +M,0.635,0.515,0.17,1.275,0.509,0.286,0.34,16 +M,0.7,0.58,0.205,2.13,0.7415,0.49,0.58,20 +M,0.675,0.525,0.185,1.587,0.6935,0.336,0.395,13 +F,0.645,0.525,0.19,1.8085,0.7035,0.3885,0.395,18 +M,0.745,0.585,0.215,2.499,0.9265,0.472,0.7,17 +F,0.685,0.545,0.18,1.768,0.7495,0.392,0.485,16 +M,0.605,0.49,0.18,1.227,0.48,0.287,0.35,18 +F,0.59,0.465,0.15,0.997,0.392,0.246,0.34,12 +F,0.65,0.525,0.175,1.4225,0.61,0.2995,0.445,20 +F,0.6,0.48,0.15,1.029,0.4085,0.2705,0.295,16 +F,0.62,0.5,0.175,1.186,0.4985,0.3015,0.35,12 +M,0.63,0.515,0.16,1.016,0.4215,0.244,0.355,19 +M,0.58,0.465,0.145,0.887,0.4405,0.1655,0.265,11 +F,0.58,0.455,0.12,1.0735,0.479,0.2735,0.265,10 +M,0.63,0.49,0.18,1.13,0.458,0.2765,0.315,12 +F,0.69,0.56,0.215,1.719,0.68,0.299,0.47,17 +F,0.65,0.545,0.165,1.566,0.6645,0.3455,0.415,16 +F,0.66,0.565,0.195,1.7605,0.692,0.3265,0.5,16 +F,0.68,0.58,0.2,1.787,0.585,0.453,0.6,19 +F,0.7,0.575,0.17,1.31,0.5095,0.314,0.42,14 +M,0.685,0.52,0.15,1.343,0.4635,0.292,0.4,13 +F,0.675,0.545,0.195,1.7345,0.6845,0.3695,0.605,20 +M,0.63,0.49,0.19,1.1775,0.4935,0.3365,0.285,11 +F,0.585,0.45,0.16,1.077,0.4995,0.2875,0.25,10 +M,0.565,0.465,0.175,0.995,0.3895,0.183,0.37,15 +F,0.61,0.495,0.185,1.1085,0.3705,0.3135,0.33,12 +M,0.605,0.47,0.18,1.1405,0.3755,0.2805,0.385,15 +M,0.535,0.42,0.145,0.791,0.33,0.189,0.25,10 +M,0.485,0.4,0.135,0.663,0.313,0.137,0.2,10 +M,0.47,0.375,0.12,0.5565,0.226,0.122,0.195,12 +M,0.545,0.425,0.135,0.8445,0.373,0.21,0.235,10 +F,0.455,0.37,0.105,0.4925,0.216,0.1245,0.135,9 +M,0.54,0.42,0.155,0.7385,0.3515,0.152,0.215,12 +M,0.46,0.38,0.135,0.482,0.207,0.1225,0.145,10 +M,0.49,0.42,0.125,0.609,0.239,0.1435,0.22,14 +I,0.465,0.375,0.12,0.471,0.222,0.119,0.14,9 +I,0.415,0.325,0.1,0.3215,0.1535,0.0595,0.105,10 +M,0.475,0.375,0.125,0.593,0.277,0.115,0.18,10 +F,0.47,0.375,0.125,0.5615,0.252,0.137,0.18,10 +I,0.365,0.295,0.095,0.25,0.1075,0.0545,0.08,9 +I,0.345,0.275,0.095,0.1995,0.0755,0.0535,0.07,6 +I,0.39,0.31,0.1,0.302,0.116,0.064,0.115,11 +F,0.5,0.395,0.14,0.7155,0.3165,0.176,0.24,10 +M,0.47,0.38,0.145,0.5865,0.2385,0.144,0.185,8 +M,0.535,0.44,0.15,0.6765,0.256,0.139,0.26,12 +M,0.585,0.455,0.15,0.987,0.4355,0.2075,0.31,11 +F,0.485,0.365,0.12,0.5885,0.27,0.131,0.175,9 +M,0.515,0.455,0.135,0.7225,0.295,0.1625,0.235,9 +F,0.435,0.325,0.11,0.4335,0.178,0.0985,0.155,7 +F,0.515,0.415,0.14,0.6935,0.3115,0.152,0.2,10 +I,0.44,0.345,0.12,0.365,0.1655,0.083,0.11,7 +F,0.525,0.44,0.15,0.8425,0.3685,0.1985,0.24,12 +M,0.45,0.355,0.115,0.479,0.2125,0.1045,0.15,8 +M,0.59,0.485,0.12,0.911,0.39,0.182,0.29,16 +M,0.555,0.45,0.145,0.915,0.4,0.246,0.285,11 +M,0.57,0.44,0.095,0.827,0.3395,0.2215,0.235,8 +M,0.59,0.5,0.165,1.1045,0.4565,0.2425,0.34,15 +M,0.585,0.475,0.12,0.945,0.41,0.2115,0.28,14 +F,0.58,0.46,0.12,0.9935,0.4625,0.2385,0.28,11 +M,0.545,0.44,0.12,0.8565,0.3475,0.1715,0.24,12 +F,0.605,0.495,0.17,1.2385,0.528,0.2465,0.39,14 +F,0.62,0.47,0.14,1.0325,0.3605,0.224,0.36,15 +F,0.63,0.5,0.17,1.3135,0.5595,0.267,0.4,20 +M,0.63,0.515,0.165,1.352,0.488,0.349,0.45,20 +F,0.63,0.5,0.155,1.005,0.367,0.199,0.36,16 +M,0.545,0.41,0.14,0.625,0.223,0.16,0.235,13 +F,0.67,0.54,0.165,1.5015,0.518,0.358,0.505,14 +I,0.49,0.38,0.12,0.529,0.2165,0.139,0.155,11 +F,0.49,0.39,0.135,0.5785,0.2465,0.123,0.2,13 +I,0.29,0.225,0.07,0.101,0.036,0.0235,0.035,8 +I,0.26,0.2,0.07,0.092,0.037,0.02,0.03,6 +M,0.58,0.45,0.175,1.068,0.425,0.203,0.32,13 +F,0.61,0.485,0.165,1.0915,0.3935,0.2435,0.33,18 +M,0.6,0.5,0.16,1.015,0.3995,0.1735,0.33,19 +F,0.56,0.455,0.125,0.943,0.344,0.129,0.375,21 +F,0.575,0.45,0.17,1.0475,0.3775,0.1705,0.385,18 +F,0.57,0.45,0.175,0.9555,0.38,0.1665,0.295,18 +M,0.6,0.47,0.155,1.036,0.4375,0.196,0.325,20 +M,0.565,0.455,0.17,0.9065,0.342,0.156,0.32,18 +M,0.545,0.42,0.14,0.7505,0.2475,0.13,0.255,22 +I,0.44,0.345,0.1,0.366,0.122,0.0905,0.12,13 +M,0.5,0.41,0.15,0.662,0.2815,0.137,0.22,11 +I,0.36,0.275,0.095,0.217,0.084,0.0435,0.09,7 +I,0.385,0.305,0.095,0.252,0.0915,0.055,0.09,14 +M,0.39,0.3,0.09,0.3055,0.143,0.0645,0.085,9 +M,0.5,0.415,0.165,0.6885,0.249,0.138,0.25,13 +I,0.36,0.275,0.11,0.2335,0.095,0.0525,0.085,10 +I,0.335,0.26,0.1,0.192,0.0785,0.0585,0.07,8 +F,0.505,0.425,0.14,0.85,0.275,0.1625,0.285,19 +I,0.395,0.295,0.1,0.2715,0.134,0.0325,0.085,10 +F,0.41,0.325,0.105,0.3635,0.159,0.077,0.12,10 +F,0.56,0.455,0.19,0.714,0.283,0.129,0.275,9 +M,0.565,0.435,0.185,0.9815,0.329,0.136,0.39,13 +M,0.565,0.455,0.185,0.9265,0.354,0.1575,0.375,16 +M,0.605,0.5,0.175,1.098,0.4765,0.232,0.375,12 +F,0.565,0.455,0.15,0.8205,0.365,0.159,0.26,18 +M,0.725,0.565,0.215,1.891,0.6975,0.4725,0.58,16 +F,0.675,0.535,0.16,1.41,0.592,0.3175,0.42,16 +F,0.665,0.555,0.195,1.4385,0.581,0.354,0.36,17 +F,0.565,0.49,0.155,0.9245,0.405,0.2195,0.255,11 +F,0.645,0.55,0.175,1.2915,0.57,0.3045,0.33,14 +M,0.575,0.47,0.14,0.8375,0.3485,0.1735,0.24,11 +F,0.64,0.54,0.175,1.221,0.51,0.259,0.39,15 +I,0.36,0.28,0.105,0.199,0.0695,0.045,0.08,9 +I,0.415,0.31,0.11,0.2965,0.123,0.057,0.0995,10 +F,0.525,0.41,0.135,0.7085,0.293,0.1525,0.235,11 +M,0.38,0.285,0.1,0.2665,0.115,0.061,0.075,11 +F,0.585,0.465,0.17,0.9915,0.3865,0.224,0.265,12 +I,0.24,0.185,0.07,0.0715,0.026,0.018,0.025,6 +I,0.22,0.165,0.055,0.0545,0.0215,0.012,0.02,5 +I,0.255,0.195,0.07,0.0735,0.0255,0.02,0.025,6 +I,0.175,0.125,0.05,0.0235,0.008,0.0035,0.008,5 +F,0.67,0.55,0.19,1.3905,0.5425,0.3035,0.4,12 +M,0.655,0.53,0.195,1.388,0.567,0.2735,0.41,13 +F,0.68,0.55,0.21,1.7445,0.5975,0.305,0.625,17 +M,0.675,0.555,0.2,1.4385,0.545,0.2665,0.465,21 +F,0.53,0.44,0.135,0.7835,0.313,0.1715,0.2185,9 +F,0.515,0.405,0.12,0.646,0.2895,0.1405,0.177,10 +I,0.43,0.34,0.12,0.3575,0.151,0.0645,0.1045,9 +F,0.52,0.405,0.12,0.627,0.2645,0.1415,0.181,11 +F,0.545,0.415,0.16,0.7715,0.272,0.1455,0.2765,10 +M,0.53,0.415,0.175,0.7395,0.261,0.1395,0.2645,17 +F,0.465,0.35,0.115,0.421,0.1565,0.091,0.1345,9 +M,0.665,0.54,0.175,1.347,0.4955,0.254,0.415,17 +M,0.735,0.59,0.225,1.756,0.637,0.3405,0.58,21 +M,0.66,0.545,0.185,1.32,0.5305,0.2635,0.455,16 +F,0.7,0.585,0.185,1.8075,0.7055,0.3215,0.475,29 +M,0.575,0.4,0.155,0.9325,0.3605,0.2445,0.3,17 +M,0.57,0.465,0.125,0.849,0.3785,0.1765,0.24,15 +F,0.58,0.46,0.15,0.9955,0.429,0.212,0.26,19 +M,0.63,0.48,0.145,1.0115,0.4235,0.237,0.305,12 +F,0.585,0.465,0.14,0.908,0.381,0.1615,0.315,13 +M,0.55,0.45,0.13,0.92,0.378,0.2385,0.29,11 +F,0.625,0.515,0.15,1.2415,0.5235,0.3065,0.36,15 +M,0.54,0.42,0.135,0.8075,0.3485,0.1795,0.235,11 +F,0.57,0.455,0.165,1.0595,0.44,0.2195,0.285,14 +M,0.59,0.455,0.145,1.073,0.475,0.19,0.285,14 +M,0.58,0.46,0.13,0.921,0.357,0.181,0.29,13 +F,0.655,0.51,0.155,1.2895,0.5345,0.2855,0.41,11 +M,0.655,0.53,0.175,1.2635,0.486,0.2635,0.415,15 +M,0.625,0.5,0.195,1.369,0.5875,0.2185,0.37,17 +F,0.625,0.5,0.15,0.953,0.3445,0.2235,0.305,15 +F,0.64,0.52,0.175,1.248,0.4245,0.2595,0.48,12 +F,0.605,0.485,0.165,1.0105,0.435,0.209,0.3,19 +F,0.615,0.525,0.155,1.0385,0.427,0.2315,0.345,11 +M,0.555,0.45,0.175,0.874,0.3275,0.202,0.305,10 +F,0.58,0.44,0.18,0.854,0.3665,0.1635,0.245,12 +F,0.62,0.52,0.225,1.1835,0.378,0.27,0.395,23 +F,0.62,0.47,0.225,1.115,0.378,0.2145,0.36,15 +F,0.6,0.505,0.19,1.129,0.4385,0.256,0.36,13 +F,0.625,0.485,0.19,1.1745,0.4385,0.2305,0.42,17 +M,0.6,0.47,0.175,1.105,0.4865,0.247,0.315,15 +M,0.56,0.46,0.235,0.8395,0.3325,0.157,0.305,12 +M,0.585,0.455,0.225,1.055,0.3815,0.221,0.365,15 +M,0.56,0.435,0.18,0.889,0.36,0.204,0.25,11 +I,0.56,0.445,0.155,0.8735,0.3005,0.209,0.275,16 +I,0.68,0.53,0.185,1.1095,0.439,0.245,0.34,10 +F,0.455,0.35,0.14,0.5185,0.221,0.1265,0.135,10 +F,0.49,0.38,0.145,0.6725,0.249,0.181,0.21,10 +M,0.31,0.22,0.085,0.146,0.061,0.0365,0.045,6 +F,0.275,0.195,0.07,0.08,0.031,0.0215,0.025,5 +M,0.27,0.195,0.08,0.1,0.0385,0.0195,0.03,6 +M,0.4,0.29,0.115,0.2795,0.1115,0.0575,0.075,9 +M,0.28,0.2,0.08,0.0915,0.033,0.0215,0.03,5 +M,0.325,0.23,0.09,0.147,0.06,0.034,0.045,4 +F,0.345,0.25,0.09,0.203,0.078,0.059,0.055,6 +M,0.21,0.15,0.05,0.0385,0.0155,0.0085,0.01,3 +F,0.36,0.27,0.09,0.1885,0.0845,0.0385,0.055,5 +I,0.365,0.26,0.115,0.218,0.0935,0.0445,0.07,9 +M,0.2,0.14,0.055,0.035,0.0145,0.008,0.01,5 +M,0.235,0.16,0.06,0.0545,0.0265,0.0095,0.015,4 +M,0.175,0.125,0.04,0.024,0.0095,0.006,0.005,4 +M,0.155,0.11,0.04,0.0155,0.0065,0.003,0.005,3 +F,0.57,0.445,0.155,0.733,0.282,0.159,0.235,14 +F,0.57,0.45,0.16,0.9715,0.3965,0.255,0.26,12 +M,0.385,0.3,0.095,0.24,0.0885,0.059,0.085,9 +I,0.53,0.42,0.185,0.752,0.299,0.156,0.205,20 +F,0.46,0.355,0.13,0.458,0.192,0.1055,0.13,13 +I,0.47,0.37,0.12,0.4705,0.1845,0.1055,0.155,12 +F,0.435,0.335,0.11,0.38,0.1695,0.086,0.11,9 +I,0.47,0.37,0.14,0.4985,0.2095,0.1225,0.145,10 +I,0.465,0.38,0.13,0.454,0.1895,0.08,0.155,11 +I,0.52,0.405,0.14,0.5775,0.2,0.145,0.179,11 +M,0.29,0.23,0.075,0.1165,0.043,0.0255,0.04,7 +M,0.275,0.205,0.07,0.094,0.0335,0.02,0.0325,5 +F,0.375,0.29,0.115,0.2705,0.093,0.066,0.0885,10 +F,0.5,0.375,0.14,0.604,0.242,0.1415,0.179,15 +F,0.44,0.355,0.115,0.415,0.1585,0.0925,0.131,11 +M,0.42,0.325,0.115,0.2885,0.1,0.057,0.1135,15 +M,0.445,0.35,0.115,0.3615,0.1565,0.0695,0.117,8 +F,0.38,0.29,0.105,0.257,0.099,0.051,0.085,10 +M,0.32,0.245,0.075,0.1555,0.0585,0.038,0.049,11 +M,0.255,0.195,0.065,0.08,0.0315,0.018,0.027,8 +M,0.205,0.155,0.045,0.0425,0.017,0.0055,0.0155,7 +F,0.565,0.45,0.16,0.795,0.3605,0.1555,0.23,12 +I,0.555,0.425,0.18,0.875,0.3695,0.2005,0.255,11 +I,0.65,0.515,0.16,1.1625,0.495,0.203,0.33,17 +I,0.615,0.49,0.155,0.9885,0.4145,0.195,0.345,13 +I,0.56,0.44,0.165,0.8,0.335,0.1735,0.25,12 +I,0.48,0.37,0.12,0.514,0.2075,0.131,0.155,13 +I,0.485,0.39,0.125,0.591,0.287,0.141,0.12,9 +I,0.5,0.385,0.15,0.6265,0.2605,0.1665,0.16,10 +I,0.525,0.405,0.15,0.795,0.3075,0.205,0.255,14 +F,0.66,0.5,0.165,1.1905,0.4585,0.298,0.37,12 +F,0.66,0.53,0.17,1.326,0.519,0.2625,0.44,13 +I,0.52,0.4,0.145,0.66,0.267,0.1055,0.22,13 +F,0.44,0.34,0.105,0.364,0.148,0.0805,0.1175,8 +I,0.515,0.4,0.12,0.659,0.2705,0.179,0.17,13 +F,0.475,0.35,0.115,0.452,0.1715,0.092,0.155,11 +F,0.545,0.415,0.15,0.7335,0.2795,0.163,0.2185,11 +F,0.47,0.355,0.13,0.5465,0.2005,0.126,0.185,14 +M,0.35,0.255,0.065,0.179,0.0705,0.0385,0.06,10 +I,0.485,0.355,0.13,0.581,0.245,0.132,0.168,12 +I,0.435,0.33,0.125,0.406,0.1685,0.1055,0.096,12 +M,0.28,0.21,0.08,0.1085,0.041,0.0265,0.0345,7 +F,0.41,0.32,0.115,0.387,0.165,0.1005,0.0985,11 +I,0.45,0.35,0.14,0.474,0.21,0.109,0.1275,16 +I,0.45,0.345,0.135,0.443,0.1975,0.0875,0.1175,14 +F,0.59,0.455,0.155,1.066,0.382,0.2275,0.415,20 +F,0.57,0.44,0.14,0.9535,0.3785,0.201,0.305,17 +I,0.61,0.475,0.15,0.9665,0.4145,0.2,0.345,10 +F,0.61,0.475,0.14,1.133,0.5275,0.2355,0.35,11 +I,0.56,0.425,0.14,0.9175,0.4005,0.1975,0.26,10 +F,0.585,0.435,0.175,0.982,0.4055,0.2495,0.27,10 +I,0.58,0.445,0.15,0.8865,0.383,0.209,0.255,11 +F,0.63,0.48,0.175,1.3675,0.5015,0.3035,0.515,17 +F,0.625,0.49,0.175,1.233,0.5565,0.247,0.365,11 +I,0.55,0.425,0.15,0.806,0.376,0.171,0.245,14 +F,0.645,0.525,0.19,1.4635,0.6615,0.3435,0.435,19 +I,0.46,0.355,0.14,0.4935,0.216,0.133,0.115,13 +F,0.41,0.305,0.1,0.363,0.1735,0.065,0.11,11 +I,0.495,0.39,0.125,0.6655,0.284,0.162,0.2,11 +I,0.52,0.425,0.17,0.6805,0.28,0.174,0.195,10 +F,0.55,0.41,0.145,0.8285,0.3095,0.1905,0.25,13 +M,0.45,0.335,0.14,0.4625,0.164,0.076,0.15,14 +F,0.405,0.31,0.12,0.3095,0.138,0.058,0.095,13 +I,0.51,0.4,0.15,0.745,0.2865,0.1675,0.235,13 +F,0.37,0.29,0.115,0.25,0.111,0.057,0.075,9 +I,0.525,0.41,0.175,0.874,0.3585,0.207,0.205,18 +F,0.66,0.52,0.18,1.514,0.526,0.2975,0.42,19 +M,0.535,0.42,0.15,0.6995,0.2575,0.153,0.24,12 +I,0.575,0.455,0.18,0.8525,0.3015,0.1825,0.3,13 +F,0.55,0.43,0.14,0.7135,0.2565,0.186,0.225,9 +I,0.605,0.47,0.14,0.939,0.3385,0.201,0.32,13 +I,0.605,0.495,0.145,1.054,0.369,0.2255,0.36,12 +F,0.56,0.445,0.195,0.981,0.305,0.2245,0.335,16 +I,0.535,0.42,0.145,0.926,0.398,0.1965,0.25,17 +F,0.385,0.315,0.11,0.286,0.1225,0.0635,0.0835,10 +F,0.39,0.3,0.1,0.265,0.1075,0.06,0.0865,13 +I,0.47,0.345,0.115,0.4885,0.2005,0.108,0.166,11 +I,0.515,0.39,0.14,0.5555,0.2,0.1135,0.2235,12 +I,0.425,0.345,0.125,0.425,0.16,0.0795,0.154,13 +M,0.345,0.27,0.09,0.195,0.078,0.0455,0.059,9 +I,0.485,0.37,0.13,0.458,0.181,0.113,0.136,10 +M,0.37,0.285,0.1,0.228,0.0675,0.0675,0.081,10 +M,0.35,0.265,0.09,0.1775,0.0575,0.042,0.068,12 +F,0.44,0.345,0.17,0.4085,0.15,0.0825,0.1515,12 +M,0.195,0.145,0.05,0.032,0.01,0.008,0.012,4 +M,0.325,0.24,0.075,0.155,0.0475,0.0355,0.06,9 +I,0.495,0.37,0.125,0.4775,0.185,0.0705,0.169,18 +I,0.45,0.35,0.145,0.525,0.2085,0.1,0.1655,15 +M,0.415,0.345,0.135,0.3865,0.128,0.07,0.148,13 +F,0.47,0.355,0.14,0.433,0.1525,0.095,0.152,12 +M,0.32,0.24,0.085,0.17,0.0655,0.047,0.049,7 +M,0.31,0.225,0.075,0.1295,0.0455,0.0335,0.044,9 +M,0.235,0.17,0.055,0.0515,0.018,0.0105,0.0195,7 +M,0.345,0.255,0.08,0.169,0.06,0.0425,0.054,10 +I,0.485,0.38,0.14,0.673,0.2175,0.13,0.195,18 +F,0.5,0.385,0.115,0.6785,0.2945,0.138,0.195,12 +F,0.5,0.385,0.105,0.498,0.1795,0.1095,0.17,17 +I,0.465,0.36,0.105,0.498,0.214,0.116,0.14,15 +F,0.525,0.405,0.16,0.658,0.2655,0.1125,0.225,12 +F,0.425,0.335,0.095,0.322,0.1205,0.061,0.125,10 +F,0.38,0.305,0.095,0.2815,0.1255,0.0525,0.09,8 +I,0.53,0.415,0.145,0.944,0.3845,0.185,0.265,21 +M,0.34,0.265,0.085,0.1835,0.077,0.046,0.065,10 +I,0.475,0.365,0.115,0.49,0.223,0.1235,0.1335,9 +F,0.43,0.34,0.12,0.391,0.1555,0.095,0.1405,7 +M,0.46,0.365,0.125,0.467,0.1895,0.0945,0.158,10 +I,0.47,0.36,0.13,0.5225,0.198,0.1065,0.165,9 +M,0.36,0.295,0.1,0.2105,0.066,0.0525,0.075,9 +M,0.355,0.265,0.09,0.168,0.05,0.041,0.063,8 +M,0.38,0.235,0.1,0.258,0.1055,0.054,0.08,7 +M,0.355,0.26,0.085,0.1905,0.081,0.0485,0.055,6 +I,0.44,0.345,0.12,0.487,0.1965,0.108,0.16,14 +F,0.51,0.4,0.13,0.5735,0.219,0.1365,0.195,13 +M,0.325,0.24,0.085,0.173,0.0795,0.038,0.05,7 +I,0.62,0.485,0.18,1.1785,0.4675,0.2655,0.39,13 +F,0.59,0.45,0.16,0.9,0.358,0.156,0.315,19 +M,0.33,0.255,0.095,0.1875,0.0735,0.045,0.06,7 +M,0.45,0.34,0.13,0.3715,0.1605,0.0795,0.105,9 +I,0.445,0.33,0.12,0.347,0.12,0.084,0.105,11 +M,0.33,0.215,0.075,0.1145,0.045,0.0265,0.035,6 +M,0.48,0.375,0.145,0.777,0.216,0.13,0.17,9 +I,0.46,0.35,0.12,0.4885,0.193,0.105,0.155,11 +F,0.475,0.36,0.125,0.447,0.1695,0.081,0.14,9 +M,0.255,0.18,0.065,0.079,0.034,0.014,0.025,5 +I,0.335,0.245,0.09,0.1665,0.0595,0.04,0.06,6 +I,0.47,0.35,0.13,0.466,0.1845,0.099,0.145,11 +M,0.31,0.225,0.08,0.1345,0.054,0.024,0.05,7 +F,0.37,0.28,0.11,0.2305,0.0945,0.0465,0.075,10 +M,0.295,0.215,0.075,0.129,0.05,0.0295,0.04,7 +F,0.555,0.435,0.165,0.97,0.336,0.2315,0.295,17 +F,0.615,0.515,0.17,1.14,0.4305,0.2245,0.42,16 +I,0.58,0.49,0.195,1.3165,0.5305,0.254,0.41,18 +F,0.585,0.475,0.185,0.9585,0.4145,0.1615,0.33,11 +I,0.65,0.525,0.18,1.626,0.597,0.3445,0.53,18 +I,0.535,0.45,0.17,0.781,0.3055,0.1555,0.295,11 +F,0.415,0.34,0.13,0.3675,0.146,0.0885,0.12,10 +F,0.38,0.305,0.105,0.281,0.1045,0.0615,0.09,12 +I,0.45,0.355,0.12,0.412,0.1145,0.0665,0.16,19 +F,0.395,0.295,0.095,0.2245,0.078,0.054,0.08,10 +M,0.455,0.35,0.12,0.4835,0.1815,0.144,0.16,11 +F,0.485,0.38,0.15,0.605,0.2155,0.14,0.18,15 +M,0.55,0.425,0.155,0.9175,0.2775,0.243,0.335,13 +F,0.45,0.35,0.145,0.5425,0.1765,0.123,0.175,13 +M,0.475,0.385,0.145,0.6175,0.235,0.108,0.215,14 +F,0.5,0.38,0.155,0.655,0.2405,0.143,0.205,17 +F,0.53,0.41,0.165,0.8115,0.24,0.169,0.24,19 +M,0.49,0.39,0.15,0.573,0.225,0.124,0.17,21 +F,0.49,0.385,0.15,0.7865,0.241,0.14,0.24,23 +F,0.52,0.395,0.18,0.64,0.158,0.11,0.245,22 +M,0.54,0.415,0.145,0.74,0.2635,0.168,0.245,12 +F,0.5,0.375,0.115,0.5945,0.185,0.148,0.19,11 +F,0.45,0.38,0.165,0.8165,0.25,0.1915,0.265,23 +F,0.37,0.275,0.1,0.2225,0.093,0.026,0.08,8 +I,0.37,0.275,0.1,0.2295,0.0885,0.0465,0.07,7 +M,0.485,0.37,0.14,0.5725,0.204,0.1415,0.175,10 +F,0.435,0.325,0.115,0.3915,0.154,0.094,0.12,7 +M,0.535,0.405,0.185,0.8345,0.3175,0.1725,0.29,16 +M,0.51,0.4,0.14,0.6515,0.2455,0.1665,0.185,10 +M,0.565,0.44,0.185,0.909,0.344,0.2325,0.255,15 +F,0.535,0.4,0.15,0.8045,0.3345,0.2125,0.21,13 +F,0.535,0.405,0.125,0.927,0.26,0.1425,0.345,16 +M,0.525,0.4,0.17,0.7305,0.279,0.2055,0.195,11 +M,0.59,0.44,0.15,0.9555,0.366,0.2425,0.295,11 +M,0.5,0.375,0.15,0.636,0.2535,0.145,0.19,10 +I,0.255,0.19,0.075,0.0865,0.0345,0.0205,0.025,5 +F,0.43,0.325,0.115,0.3865,0.1475,0.1065,0.11,11 +M,0.38,0.29,0.12,0.283,0.1175,0.0655,0.085,9 +I,0.165,0.11,0.02,0.019,0.0065,0.0025,0.005,4 +I,0.315,0.23,0.09,0.1285,0.043,0.04,0.04,7 +I,0.155,0.105,0.05,0.0175,0.005,0.0035,0.005,4 +M,0.28,0.205,0.1,0.1165,0.0545,0.0285,0.03,5 +F,0.43,0.335,0.12,0.444,0.155,0.1145,0.14,13 +F,0.395,0.315,0.105,0.3515,0.1185,0.091,0.1195,16 +M,0.385,0.285,0.105,0.2905,0.1215,0.0685,0.0875,12 +F,0.48,0.385,0.135,0.536,0.1895,0.142,0.173,14 +F,0.445,0.33,0.105,0.4525,0.18,0.103,0.123,9 +M,0.395,0.295,0.115,0.316,0.1205,0.0595,0.1105,12 +M,0.4,0.3,0.125,0.417,0.191,0.09,0.1175,9 +M,0.415,0.325,0.14,0.417,0.1535,0.1015,0.144,10 +M,0.315,0.25,0.09,0.203,0.0615,0.037,0.0795,11 +F,0.345,0.26,0.09,0.207,0.0775,0.0435,0.0765,10 +M,0.36,0.295,0.13,0.2765,0.0895,0.057,0.1005,10 +I,0.295,0.225,0.09,0.1105,0.0405,0.0245,0.032,7 +I,0.325,0.25,0.08,0.176,0.0595,0.0355,0.063,7 +M,0.375,0.3,0.1,0.2465,0.104,0.0475,0.083,11 +I,0.28,0.205,0.055,0.1135,0.045,0.0275,0.0335,7 +M,0.355,0.265,0.085,0.201,0.069,0.053,0.0695,8 +M,0.35,0.255,0.08,0.1915,0.08,0.0385,0.063,9 +I,0.275,0.2,0.065,0.1035,0.0475,0.0205,0.03,7 +I,0.29,0.205,0.07,0.0975,0.036,0.019,0.035,8 +I,0.25,0.19,0.06,0.0765,0.036,0.0115,0.0245,6 +I,0.18,0.125,0.035,0.0265,0.0095,0.0055,0.0085,4 +I,0.15,0.1,0.025,0.015,0.0045,0.004,0.005,2 +I,0.16,0.11,0.025,0.018,0.0065,0.0055,0.005,3 +M,0.555,0.455,0.16,1.0575,0.3925,0.228,0.293,13 +M,0.555,0.44,0.15,1.092,0.416,0.212,0.4405,15 +M,0.525,0.41,0.13,0.99,0.3865,0.243,0.295,15 +M,0.465,0.36,0.08,0.488,0.191,0.125,0.155,11 +F,0.49,0.36,0.11,0.5005,0.161,0.107,0.195,17 +M,0.4,0.305,0.085,0.297,0.108,0.0705,0.1,10 +F,0.48,0.375,0.105,0.525,0.2185,0.1195,0.155,12 +M,0.505,0.4,0.125,0.77,0.2735,0.159,0.255,13 +F,0.52,0.4,0.12,0.6515,0.261,0.2015,0.165,15 +M,0.525,0.4,0.13,0.8295,0.2405,0.1825,0.275,11 +M,0.545,0.42,0.13,0.879,0.374,0.1695,0.23,13 +M,0.52,0.4,0.12,0.823,0.298,0.1805,0.265,15 +M,0.505,0.38,0.13,0.656,0.227,0.1785,0.22,13 +M,0.525,0.425,0.12,0.8665,0.2825,0.176,0.29,18 +M,0.51,0.39,0.125,0.6565,0.262,0.1835,0.175,10 +M,0.52,0.385,0.115,0.669,0.2385,0.172,0.205,12 +F,0.52,0.405,0.125,0.6435,0.2415,0.1735,0.21,12 +M,0.535,0.41,0.135,0.862,0.2855,0.1525,0.32,14 +M,0.445,0.345,0.09,0.3795,0.143,0.074,0.125,10 +M,0.53,0.44,0.205,0.835,0.32,0.2175,0.245,14 +F,0.36,0.265,0.09,0.2065,0.078,0.057,0.06,8 +F,0.535,0.42,0.15,0.7365,0.2785,0.186,0.215,14 +F,0.52,0.405,0.14,0.8175,0.2795,0.183,0.26,17 +M,0.53,0.415,0.13,0.8425,0.275,0.1945,0.265,20 +F,0.53,0.42,0.13,1.001,0.34,0.226,0.265,17 +F,0.66,0.52,0.2,1.676,0.673,0.4805,0.45,17 +M,0.52,0.385,0.14,0.6595,0.2485,0.2035,0.16,9 +M,0.535,0.42,0.13,0.8055,0.301,0.181,0.28,14 +M,0.695,0.515,0.175,1.5165,0.578,0.4105,0.39,15 +F,0.51,0.39,0.105,0.612,0.187,0.15,0.195,13 +M,0.485,0.355,0.12,0.547,0.215,0.1615,0.14,10 +F,0.605,0.46,0.17,1.122,0.347,0.3045,0.315,13 +F,0.58,0.455,0.165,1.1365,0.369,0.3005,0.275,13 +M,0.65,0.515,0.175,1.4805,0.5295,0.272,0.525,20 +M,0.62,0.505,0.185,1.5275,0.69,0.368,0.35,13 +M,0.615,0.525,0.155,1.1375,0.367,0.236,0.37,20 +F,0.605,0.495,0.19,1.437,0.469,0.2655,0.41,15 +M,0.57,0.44,0.155,1.116,0.4775,0.2315,0.27,13 +M,0.57,0.43,0.12,1.0615,0.348,0.167,0.31,15 +M,0.585,0.405,0.15,1.2565,0.435,0.202,0.325,15 +F,0.55,0.44,0.155,0.946,0.313,0.1825,0.335,16 +F,0.54,0.44,0.135,0.959,0.2385,0.221,0.3,17 +M,0.64,0.51,0.19,1.613,0.6215,0.361,0.47,14 +F,0.61,0.47,0.145,1.153,0.403,0.296,0.32,14 +M,0.545,0.45,0.15,0.978,0.3365,0.1905,0.3,11 +F,0.59,0.445,0.13,1.1325,0.3825,0.234,0.32,13 +M,0.345,0.27,0.095,0.197,0.0665,0.05,0.07,9 +F,0.55,0.43,0.155,0.785,0.289,0.227,0.233,11 +F,0.53,0.425,0.17,0.949,0.3485,0.2395,0.278,17 +F,0.53,0.455,0.165,0.9805,0.3155,0.2815,0.2965,11 +I,0.485,0.375,0.14,0.521,0.2,0.123,0.17,8 +M,0.385,0.275,0.115,0.2685,0.0975,0.0825,0.085,8 +M,0.455,0.34,0.135,0.462,0.1675,0.158,0.12,9 +M,0.49,0.38,0.14,0.7605,0.245,0.167,0.185,10 +M,0.53,0.41,0.165,0.732,0.189,0.17,0.31,11 +M,0.505,0.385,0.145,0.6775,0.236,0.179,0.2,15 +M,0.49,0.38,0.14,0.6385,0.2305,0.142,0.195,13 +M,0.465,0.35,0.14,0.5755,0.2015,0.1505,0.19,15 +F,0.47,0.36,0.145,0.537,0.1725,0.1375,0.195,15 +M,0.56,0.41,0.165,0.93,0.3505,0.237,0.3,13 +M,0.505,0.385,0.15,0.6415,0.246,0.152,0.215,12 +M,0.515,0.435,0.145,0.8815,0.292,0.206,0.255,10 +I,0.385,0.28,0.125,0.244,0.102,0.038,0.085,6 +I,0.215,0.155,0.06,0.0525,0.021,0.0165,0.015,5 +M,0.55,0.415,0.175,1.042,0.3295,0.2325,0.2905,15 +F,0.515,0.39,0.13,0.5755,0.1975,0.13,0.1845,9 +M,0.495,0.385,0.135,0.709,0.211,0.1375,0.262,12 +F,0.505,0.39,0.16,0.644,0.2475,0.2025,0.1635,9 +F,0.6,0.465,0.165,0.8875,0.309,0.246,0.262,12 +F,0.57,0.465,0.16,0.8935,0.3145,0.2575,0.263,10 +F,0.485,0.375,0.135,0.556,0.1925,0.1315,0.1685,10 +M,0.47,0.37,0.18,0.51,0.1915,0.1285,0.1625,9 +M,0.575,0.45,0.165,0.9215,0.3275,0.225,0.256,12 +M,0.58,0.465,0.16,1.0345,0.315,0.26,0.3635,12 +M,0.515,0.405,0.145,0.695,0.215,0.1635,0.234,15 +M,0.53,0.41,0.155,0.7155,0.2805,0.1685,0.214,11 +M,0.44,0.335,0.11,0.394,0.157,0.096,0.122,9 +M,0.52,0.42,0.16,0.745,0.255,0.157,0.2885,11 +F,0.425,0.345,0.11,0.3665,0.125,0.081,0.117,11 +M,0.46,0.34,0.135,0.495,0.1655,0.117,0.185,10 +M,0.45,0.335,0.125,0.349,0.119,0.1055,0.115,10 +M,0.425,0.33,0.13,0.4405,0.152,0.0935,0.155,9 +I,0.37,0.275,0.1,0.22,0.094,0.045,0.065,7 +M,0.515,0.38,0.135,0.6615,0.2875,0.2095,0.155,10 +M,0.405,0.305,0.12,0.3185,0.1235,0.0905,0.095,7 +I,0.28,0.205,0.07,0.1015,0.041,0.03,0.03,6 +F,0.48,0.4,0.125,0.759,0.2125,0.179,0.24,15 +F,0.44,0.34,0.13,0.4195,0.153,0.1155,0.13,10 +F,0.52,0.41,0.115,0.807,0.2855,0.179,0.235,12 +M,0.505,0.405,0.14,0.875,0.2665,0.174,0.285,12 +F,0.49,0.365,0.13,0.6835,0.165,0.1315,0.205,21 +I,0.235,0.175,0.055,0.067,0.027,0.0125,0.018,6 +I,0.255,0.185,0.06,0.088,0.0365,0.021,0.023,5 +I,0.315,0.24,0.085,0.1715,0.071,0.0345,0.0535,7 +I,0.325,0.25,0.08,0.1735,0.0765,0.0345,0.049,7 +I,0.335,0.25,0.08,0.183,0.0735,0.04,0.0575,6 +I,0.35,0.27,0.09,0.2055,0.075,0.0575,0.062,6 +I,0.35,0.25,0.07,0.18,0.0655,0.048,0.054,6 +I,0.36,0.3,0.085,0.27,0.1185,0.064,0.0745,7 +I,0.365,0.275,0.135,0.24,0.108,0.0445,0.0735,7 +I,0.37,0.275,0.14,0.2215,0.097,0.0455,0.0615,6 +I,0.38,0.275,0.095,0.1375,0.086,0.0585,0.0605,7 +I,0.385,0.29,0.095,0.312,0.143,0.0635,0.086,6 +I,0.385,0.3,0.1,0.2895,0.1215,0.063,0.09,7 +I,0.395,0.29,0.095,0.319,0.138,0.08,0.082,7 +I,0.395,0.29,0.095,0.304,0.127,0.084,0.077,6 +I,0.4,0.31,0.1,0.306,0.13,0.06,0.094,6 +I,0.41,0.325,0.1,0.394,0.208,0.0655,0.106,6 +I,0.415,0.32,0.11,0.3735,0.175,0.0755,0.109,7 +M,0.415,0.305,0.1,0.325,0.156,0.0505,0.091,6 +I,0.425,0.325,0.1,0.398,0.1185,0.0645,0.0945,6 +I,0.44,0.365,0.115,0.501,0.2435,0.084,0.1465,9 +I,0.445,0.335,0.1,0.4895,0.2745,0.086,0.1105,7 +I,0.445,0.325,0.1,0.378,0.1795,0.1,0.089,7 +I,0.45,0.35,0.13,0.547,0.245,0.1405,0.1405,8 +M,0.47,0.375,0.12,0.5805,0.266,0.0935,0.169,8 +I,0.475,0.365,0.125,0.5465,0.229,0.1185,0.172,9 +F,0.48,0.365,0.135,0.6395,0.2945,0.113,0.175,8 +I,0.485,0.355,0.105,0.498,0.2175,0.096,0.1525,9 +M,0.49,0.385,0.125,0.609,0.3065,0.096,0.1775,8 +F,0.495,0.41,0.125,0.7555,0.3355,0.129,0.214,9 +M,0.5,0.4,0.125,0.5975,0.27,0.1275,0.166,9 +M,0.505,0.44,0.14,0.8275,0.3415,0.1855,0.239,8 +M,0.525,0.395,0.13,0.7635,0.3375,0.1425,0.225,8 +M,0.54,0.405,0.125,0.891,0.4815,0.1915,0.202,9 +F,0.54,0.42,0.14,0.805,0.369,0.1725,0.21,11 +F,0.545,0.44,0.135,0.9185,0.429,0.2015,0.2375,10 +F,0.55,0.43,0.125,0.923,0.4035,0.175,0.283,8 +M,0.55,0.45,0.15,1.0145,0.407,0.2015,0.2875,10 +F,0.55,0.45,0.15,0.875,0.362,0.1755,0.2765,10 +M,0.555,0.435,0.145,0.9685,0.4985,0.168,0.2385,9 +M,0.565,0.45,0.155,1.0595,0.4735,0.24,0.265,10 +M,0.57,0.455,0.15,0.952,0.3895,0.2155,0.2745,9 +M,0.57,0.435,0.13,0.7535,0.349,0.1755,0.194,10 +F,0.575,0.465,0.14,0.958,0.442,0.1815,0.2705,9 +M,0.59,0.475,0.165,1.077,0.4545,0.244,0.3095,9 +M,0.59,0.46,0.13,1.102,0.455,0.2055,0.33,12 +F,0.595,0.48,0.15,1.11,0.498,0.228,0.33,10 +F,0.595,0.48,0.16,1.2095,0.5225,0.296,0.32,8 +F,0.595,0.475,0.16,1.1405,0.547,0.231,0.271,6 +F,0.595,0.465,0.14,1.113,0.5175,0.244,0.305,12 +M,0.6,0.475,0.175,1.3445,0.549,0.2875,0.36,11 +F,0.6,0.475,0.155,1.21,0.653,0.1695,0.3205,10 +M,0.6,0.495,0.175,1.29,0.606,0.276,0.3445,11 +F,0.605,0.475,0.175,1.382,0.609,0.2325,0.3985,10 +M,0.605,0.455,0.16,1.1035,0.421,0.3015,0.325,9 +F,0.615,0.5,0.175,1.377,0.5585,0.33,0.292,12 +F,0.615,0.52,0.15,1.3435,0.629,0.2605,0.345,10 +M,0.615,0.51,0.15,1.296,0.545,0.3315,0.32,9 +M,0.615,0.505,0.165,1.34,0.5315,0.2815,0.41,12 +F,0.62,0.505,0.16,1.3725,0.6285,0.275,0.3685,11 +M,0.62,0.5,0.165,1.307,0.6355,0.2545,0.315,9 +F,0.625,0.49,0.155,1.2085,0.465,0.162,0.411,11 +F,0.625,0.49,0.2,1.3825,0.5895,0.285,0.381,11 +M,0.63,0.505,0.165,1.26,0.4525,0.2755,0.406,14 +M,0.635,0.51,0.17,1.3555,0.619,0.305,0.39,9 +F,0.635,0.5,0.15,1.376,0.6495,0.361,0.31,10 +F,0.635,0.485,0.165,1.2945,0.668,0.2605,0.2715,9 +F,0.64,0.51,0.165,1.486,0.7595,0.332,0.321,8 +M,0.65,0.525,0.175,1.4715,0.675,0.315,0.399,11 +M,0.655,0.52,0.165,1.4095,0.586,0.291,0.405,9 +M,0.655,0.58,0.205,2.0805,0.959,0.3415,0.601,17 +M,0.66,0.53,0.17,1.3905,0.5905,0.212,0.453,15 +M,0.66,0.52,0.19,1.558,0.755,0.298,0.4,10 +F,0.67,0.585,0.16,1.309,0.5445,0.2945,0.413,10 +F,0.675,0.525,0.17,1.8095,0.784,0.391,0.455,12 +F,0.675,0.525,0.155,1.4785,0.628,0.3405,0.42,9 +F,0.68,0.56,0.195,1.7775,0.861,0.322,0.415,11 +F,0.685,0.54,0.16,1.6675,0.833,0.3775,0.475,11 +F,0.695,0.56,0.22,1.834,0.8455,0.422,0.455,11 +M,0.73,0.595,0.23,2.8255,1.1465,0.419,0.897,17 +I,0.205,0.14,0.05,0.046,0.0165,0.012,0.0135,6 +I,0.24,0.175,0.055,0.0705,0.025,0.014,0.021,5 +I,0.24,0.175,0.065,0.0665,0.031,0.0135,0.017,3 +I,0.255,0.19,0.05,0.083,0.0295,0.0215,0.027,6 +I,0.255,0.18,0.055,0.083,0.031,0.0215,0.02,4 +I,0.265,0.195,0.06,0.092,0.0345,0.025,0.0245,6 +I,0.28,0.12,0.075,0.117,0.0455,0.029,0.0345,4 +I,0.295,0.23,0.08,0.1625,0.065,0.05,0.0385,5 +I,0.3,0.235,0.08,0.131,0.05,0.0265,0.043,4 +I,0.3,0.23,0.095,0.1385,0.056,0.0365,0.037,6 +I,0.305,0.22,0.07,0.141,0.062,0.031,0.037,5 +I,0.315,0.235,0.075,0.1485,0.0585,0.0375,0.0425,6 +I,0.315,0.23,0.07,0.144,0.053,0.0305,0.04,8 +I,0.32,0.24,0.09,0.1575,0.07,0.0265,0.0425,5 +I,0.325,0.24,0.075,0.187,0.0825,0.0445,0.05,6 +I,0.33,0.265,0.085,0.196,0.0775,0.0305,0.0445,6 +I,0.335,0.25,0.075,0.1825,0.0705,0.044,0.055,7 +I,0.335,0.25,0.075,0.186,0.0945,0.038,0.0445,7 +I,0.34,0.25,0.075,0.1785,0.0665,0.0455,0.045,5 +I,0.34,0.25,0.07,0.2225,0.104,0.0425,0.055,7 +I,0.345,0.265,0.1,0.2455,0.111,0.0535,0.065,7 +I,0.37,0.29,0.095,0.249,0.1045,0.058,0.067,6 +I,0.37,0.28,0.095,0.2865,0.1505,0.069,0.0795,7 +I,0.375,0.28,0.09,0.215,0.084,0.06,0.055,6 +I,0.385,0.265,0.08,0.251,0.124,0.037,0.07,6 +I,0.41,0.31,0.09,0.339,0.155,0.0695,0.09,7 +I,0.41,0.305,0.09,0.3535,0.157,0.0745,0.1,7 +I,0.41,0.31,0.09,0.3335,0.1635,0.061,0.091,6 +I,0.415,0.33,0.09,0.3595,0.17,0.081,0.09,6 +I,0.42,0.32,0.115,0.376,0.169,0.092,0.1,5 +I,0.42,0.315,0.1,0.3435,0.157,0.0795,0.09,6 +I,0.425,0.34,0.1,0.382,0.164,0.096,0.1,6 +I,0.425,0.315,0.1,0.377,0.1645,0.072,0.105,6 +I,0.43,0.325,0.1,0.3645,0.1575,0.0825,0.105,7 +I,0.43,0.325,0.09,0.425,0.217,0.087,0.095,7 +I,0.435,0.325,0.12,0.3995,0.1815,0.061,0.1125,8 +I,0.435,0.34,0.115,0.3925,0.1825,0.078,0.1145,6 +I,0.44,0.345,0.13,0.4495,0.209,0.0835,0.134,6 +I,0.44,0.325,0.09,0.35,0.148,0.067,0.105,7 +F,0.445,0.335,0.11,0.4355,0.2025,0.1095,0.1195,6 +I,0.445,0.35,0.13,0.4195,0.1695,0.0945,0.1195,7 +I,0.45,0.36,0.13,0.478,0.191,0.127,0.137,7 +I,0.45,0.355,0.105,0.4445,0.197,0.093,0.1335,8 +I,0.45,0.345,0.11,0.47,0.2355,0.0855,0.1135,7 +I,0.45,0.335,0.105,0.447,0.2335,0.153,0.119,7 +I,0.455,0.355,0.125,0.5325,0.225,0.126,0.1465,7 +I,0.455,0.375,0.12,0.497,0.2355,0.1055,0.1295,6 +I,0.46,0.36,0.1,0.4635,0.2325,0.093,0.115,7 +I,0.46,0.345,0.105,0.449,0.196,0.0945,0.1265,7 +I,0.465,0.365,0.115,0.467,0.2315,0.0925,0.113,7 +I,0.465,0.37,0.115,0.534,0.261,0.098,0.143,7 +I,0.465,0.345,0.11,0.4415,0.1755,0.0905,0.12,7 +F,0.465,0.35,0.125,0.482,0.23,0.106,0.1095,6 +M,0.47,0.365,0.12,0.612,0.327,0.15,0.14,8 +F,0.47,0.365,0.12,0.582,0.29,0.092,0.146,8 +M,0.475,0.37,0.125,0.537,0.222,0.1215,0.15,9 +F,0.475,0.36,0.12,0.5915,0.3245,0.11,0.127,6 +M,0.48,0.375,0.115,0.6765,0.3205,0.1065,0.17,6 +M,0.48,0.385,0.145,0.64,0.2925,0.1405,0.1575,6 +M,0.48,0.36,0.1,0.439,0.194,0.099,0.115,8 +M,0.48,0.365,0.12,0.6015,0.312,0.117,0.14,7 +F,0.485,0.37,0.115,0.4785,0.1995,0.0955,0.129,7 +M,0.49,0.385,0.125,0.649,0.32,0.124,0.1695,8 +M,0.495,0.395,0.135,0.6335,0.3035,0.1295,0.1495,8 +M,0.495,0.4,0.135,0.61,0.272,0.1435,0.144,7 +M,0.5,0.39,0.135,0.6595,0.3145,0.1535,0.1565,6 +I,0.5,0.385,0.12,0.56,0.2835,0.103,0.135,8 +M,0.5,0.385,0.135,0.6425,0.3195,0.129,0.1535,7 +M,0.5,0.4,0.125,0.6725,0.336,0.12,0.1825,7 +F,0.505,0.39,0.13,0.674,0.3165,0.141,0.1785,9 +I,0.505,0.39,0.15,0.685,0.362,0.131,0.156,8 +M,0.505,0.41,0.125,0.642,0.289,0.133,0.155,9 +I,0.505,0.355,0.125,0.601,0.25,0.1205,0.185,8 +M,0.51,0.39,0.135,0.769,0.3935,0.1455,0.19,8 +I,0.51,0.375,0.1,0.5785,0.238,0.1225,0.175,7 +I,0.51,0.405,0.135,0.769,0.3655,0.1585,0.18,7 +M,0.51,0.405,0.15,0.7035,0.347,0.134,0.1885,8 +M,0.51,0.41,0.145,0.796,0.3865,0.1815,0.1955,8 +F,0.515,0.43,0.14,0.834,0.367,0.2,0.23,8 +M,0.515,0.39,0.155,0.7125,0.3695,0.137,0.155,7 +F,0.525,0.415,0.14,0.724,0.3475,0.173,0.175,8 +M,0.525,0.4,0.14,0.7325,0.334,0.1575,0.17,11 +F,0.53,0.425,0.13,0.7585,0.325,0.197,0.205,8 +F,0.53,0.425,0.15,0.8495,0.328,0.232,0.202,8 +M,0.53,0.405,0.125,0.6515,0.2715,0.1605,0.186,7 +F,0.535,0.4,0.135,0.8215,0.3935,0.196,0.205,8 +M,0.535,0.43,0.14,0.7165,0.2855,0.1595,0.2155,8 +M,0.535,0.435,0.14,0.874,0.3735,0.229,0.2195,8 +F,0.55,0.445,0.155,0.9905,0.544,0.178,0.218,9 +F,0.55,0.43,0.14,0.8105,0.368,0.161,0.275,9 +F,0.56,0.455,0.16,0.967,0.4525,0.207,0.274,9 +F,0.565,0.4,0.13,0.6975,0.3075,0.1665,0.18,8 +M,0.57,0.45,0.155,1.195,0.5625,0.2565,0.295,10 +M,0.57,0.45,0.155,1.1935,0.513,0.21,0.343,10 +F,0.57,0.455,0.15,1.107,0.54,0.255,0.27,8 +M,0.57,0.445,0.14,1.0635,0.5265,0.2195,0.24,8 +M,0.57,0.46,0.17,0.9035,0.4075,0.1935,0.214,7 +M,0.575,0.475,0.16,1.114,0.4955,0.2745,0.29,9 +F,0.575,0.46,0.16,1.103,0.538,0.221,0.249,9 +F,0.58,0.46,0.15,1.1155,0.5575,0.2255,0.29,7 +F,0.58,0.46,0.18,1.0515,0.4095,0.2595,0.276,8 +M,0.58,0.455,0.15,1.012,0.4985,0.2115,0.2835,10 +F,0.58,0.45,0.145,1.137,0.5585,0.22,0.29,8 +M,0.58,0.49,0.13,1.1335,0.586,0.2565,0.237,9 +M,0.59,0.465,0.155,1.136,0.5245,0.2615,0.275,11 +M,0.59,0.47,0.16,1.206,0.479,0.2425,0.309,8 +F,0.59,0.455,0.145,1.063,0.5155,0.2445,0.25,8 +F,0.595,0.47,0.155,1.121,0.4515,0.178,0.155,11 +F,0.595,0.45,0.15,1.114,0.5865,0.2205,0.25,11 +M,0.595,0.475,0.165,1.213,0.621,0.2435,0.274,9 +F,0.595,0.46,0.14,1.0045,0.4655,0.2095,0.2515,9 +M,0.595,0.455,0.15,1.044,0.518,0.2205,0.27,9 +F,0.605,0.49,0.15,1.1345,0.5265,0.2645,0.295,9 +M,0.605,0.475,0.155,1.161,0.572,0.2455,0.275,9 +M,0.605,0.47,0.165,1.2315,0.6025,0.262,0.2925,11 +M,0.61,0.47,0.15,1.1625,0.565,0.258,0.3085,11 +M,0.61,0.475,0.155,1.168,0.554,0.239,0.3295,10 +F,0.615,0.48,0.16,1.2525,0.585,0.2595,0.33,8 +F,0.62,0.51,0.18,1.3315,0.594,0.276,0.388,11 +F,0.625,0.48,0.17,1.3525,0.6235,0.278,0.365,10 +M,0.625,0.49,0.175,1.3325,0.5705,0.271,0.405,10 +F,0.625,0.475,0.175,1.1435,0.4755,0.2475,0.349,10 +F,0.625,0.5,0.165,1.288,0.573,0.3035,0.315,9 +F,0.625,0.485,0.2,1.38,0.5845,0.302,0.401,9 +M,0.63,0.485,0.155,1.278,0.637,0.275,0.31,8 +F,0.63,0.495,0.165,1.3075,0.599,0.284,0.315,11 +M,0.63,0.48,0.15,1.1785,0.5185,0.248,0.3235,8 +M,0.635,0.49,0.175,1.375,0.623,0.2705,0.395,11 +M,0.635,0.525,0.185,1.4065,0.684,0.3,0.3745,10 +M,0.64,0.505,0.155,1.4025,0.705,0.2655,0.335,10 +F,0.64,0.5,0.17,1.5175,0.693,0.326,0.409,11 +F,0.64,0.5,0.175,1.394,0.4935,0.291,0.4,10 +F,0.645,0.5,0.155,1.2205,0.6145,0.236,0.3185,10 +M,0.645,0.52,0.175,1.636,0.779,0.342,0.432,11 +M,0.645,0.52,0.175,1.561,0.709,0.3555,0.4,8 +F,0.645,0.505,0.165,1.4325,0.684,0.308,0.336,8 +M,0.645,0.5,0.175,1.3385,0.633,0.299,0.349,11 +F,0.645,0.5,0.16,1.2465,0.5475,0.327,0.3,10 +F,0.645,0.515,0.15,1.212,0.515,0.2055,0.385,10 +M,0.65,0.495,0.16,1.304,0.57,0.312,0.3725,9 +M,0.65,0.52,0.21,1.6785,0.6665,0.308,0.46,11 +M,0.65,0.525,0.185,1.622,0.6645,0.3225,0.477,10 +F,0.655,0.46,0.16,1.494,0.6895,0.331,0.1825,9 +F,0.655,0.51,0.175,1.6525,0.8515,0.3365,0.403,10 +F,0.66,0.505,0.185,1.528,0.69,0.3025,0.441,11 +M,0.66,0.535,0.19,1.5905,0.6425,0.297,0.5175,9 +M,0.66,0.495,0.195,1.6275,0.594,0.3595,0.485,10 +F,0.66,0.475,0.18,1.3695,0.641,0.294,0.335,6 +M,0.67,0.525,0.165,1.6085,0.682,0.3145,0.4005,11 +F,0.675,0.57,0.225,1.587,0.739,0.2995,0.435,10 +F,0.675,0.565,0.195,1.8375,0.7645,0.3615,0.553,12 +M,0.68,0.535,0.185,1.607,0.7245,0.3215,0.498,12 +M,0.69,0.525,0.175,1.7005,0.8255,0.362,0.405,8 +M,0.69,0.505,0.2,1.872,0.893,0.4015,0.48,10 +F,0.695,0.535,0.175,1.8385,0.8035,0.396,0.503,10 +F,0.705,0.535,0.18,1.685,0.693,0.42,0.4045,12 +M,0.71,0.565,0.205,2.198,1.012,0.5225,0.5475,11 +M,0.715,0.565,0.175,1.9525,0.7645,0.4185,0.4135,10 +F,0.715,0.525,0.185,1.56,0.6655,0.383,0.405,11 +F,0.735,0.6,0.22,2.555,1.1335,0.44,0.6,11 +M,0.765,0.6,0.22,2.302,1.007,0.509,0.6205,12 +I,0.185,0.13,0.045,0.029,0.012,0.0075,0.0095,4 +I,0.195,0.15,0.045,0.0375,0.018,0.006,0.011,3 +I,0.195,0.135,0.04,0.0325,0.0135,0.005,0.0095,4 +I,0.2,0.155,0.04,0.0435,0.0155,0.009,0.007,4 +I,0.225,0.165,0.055,0.059,0.027,0.0125,0.015,4 +I,0.245,0.18,0.065,0.071,0.03,0.013,0.0215,4 +I,0.25,0.18,0.065,0.0685,0.0245,0.0155,0.0225,5 +I,0.265,0.195,0.055,0.084,0.0365,0.0175,0.025,7 +I,0.275,0.195,0.065,0.106,0.054,0.02,0.028,6 +I,0.28,0.21,0.085,0.1075,0.0415,0.024,0.034,5 +I,0.285,0.22,0.065,0.096,0.0405,0.0205,0.03,5 +I,0.3,0.22,0.08,0.1255,0.055,0.0265,0.039,6 +I,0.315,0.235,0.055,0.151,0.065,0.027,0.039,6 +I,0.32,0.225,0.085,0.1415,0.0675,0.0295,0.0405,6 +I,0.34,0.265,0.08,0.2015,0.09,0.0475,0.055,5 +I,0.37,0.28,0.1,0.221,0.1165,0.0265,0.0635,6 +I,0.375,0.28,0.08,0.2345,0.1125,0.0455,0.067,6 +I,0.375,0.275,0.1,0.2325,0.1165,0.042,0.065,6 +I,0.385,0.29,0.08,0.2485,0.122,0.0495,0.065,7 +I,0.4,0.32,0.095,0.348,0.194,0.053,0.087,6 +I,0.405,0.3,0.11,0.32,0.172,0.044,0.093,7 +I,0.41,0.3,0.1,0.282,0.1255,0.057,0.0875,7 +I,0.41,0.325,0.1,0.3245,0.132,0.072,0.106,6 +I,0.42,0.3,0.105,0.316,0.1255,0.07,0.1035,7 +I,0.42,0.32,0.11,0.3625,0.174,0.0635,0.105,7 +I,0.42,0.31,0.095,0.279,0.1255,0.051,0.088,6 +I,0.425,0.325,0.115,0.3685,0.162,0.0865,0.1045,7 +M,0.43,0.335,0.12,0.397,0.1985,0.0865,0.1035,7 +I,0.435,0.33,0.11,0.413,0.2055,0.096,0.096,6 +I,0.435,0.345,0.115,0.418,0.222,0.0735,0.106,7 +I,0.44,0.33,0.11,0.3705,0.1545,0.084,0.12,7 +I,0.445,0.345,0.105,0.409,0.1675,0.1015,0.117,7 +I,0.445,0.34,0.145,0.434,0.1945,0.0905,0.13,7 +I,0.445,0.335,0.11,0.411,0.1985,0.0935,0.109,8 +I,0.45,0.365,0.125,0.462,0.2135,0.0985,0.1315,8 +I,0.45,0.34,0.12,0.4925,0.241,0.1075,0.12,6 +I,0.45,0.33,0.105,0.3715,0.1865,0.0785,0.0975,7 +I,0.45,0.33,0.1,0.411,0.1945,0.1,0.098,6 +I,0.45,0.33,0.11,0.3685,0.16,0.0885,0.102,6 +I,0.46,0.35,0.115,0.4155,0.18,0.098,0.1175,7 +M,0.47,0.36,0.105,0.544,0.27,0.1395,0.129,7 +I,0.47,0.38,0.125,0.4845,0.211,0.1075,0.142,6 +I,0.475,0.35,0.11,0.4565,0.206,0.099,0.13,6 +I,0.475,0.35,0.1,0.4545,0.2165,0.111,0.115,7 +I,0.48,0.38,0.125,0.6245,0.3395,0.1085,0.1665,8 +M,0.49,0.465,0.125,0.5225,0.235,0.13,0.141,7 +I,0.5,0.375,0.14,0.5495,0.248,0.112,0.1585,7 +I,0.5,0.375,0.12,0.542,0.215,0.116,0.17,9 +I,0.5,0.38,0.125,0.519,0.2485,0.1135,0.134,8 +M,0.5,0.39,0.125,0.5215,0.2485,0.117,0.131,6 +F,0.505,0.39,0.125,0.5445,0.246,0.15,0.1405,7 +I,0.51,0.405,0.125,0.6795,0.3465,0.1395,0.182,8 +F,0.51,0.4,0.125,0.545,0.261,0.115,0.1385,6 +I,0.51,0.4,0.125,0.5575,0.2615,0.1195,0.1525,9 +I,0.51,0.38,0.115,0.5155,0.215,0.1135,0.166,8 +I,0.515,0.385,0.125,0.6115,0.3175,0.1265,0.15,8 +M,0.52,0.4,0.145,0.7765,0.3525,0.1845,0.185,9 +I,0.52,0.38,0.135,0.5395,0.2295,0.133,0.157,8 +I,0.52,0.38,0.125,0.5545,0.288,0.1295,0.167,8 +F,0.52,0.46,0.15,1.019,0.523,0.1985,0.254,7 +I,0.525,0.4,0.13,0.6455,0.325,0.1245,0.17,8 +I,0.525,0.4,0.14,0.601,0.2625,0.1285,0.1835,9 +M,0.525,0.405,0.12,0.7555,0.3755,0.1555,0.201,9 +I,0.525,0.395,0.12,0.608,0.297,0.1395,0.1405,8 +I,0.53,0.4,0.125,0.617,0.279,0.127,0.19,8 +I,0.535,0.39,0.125,0.599,0.2595,0.149,0.169,9 +I,0.54,0.42,0.14,0.6665,0.3125,0.138,0.1895,10 +M,0.545,0.39,0.135,0.7835,0.4225,0.1815,0.156,7 +M,0.545,0.41,0.12,0.793,0.434,0.1405,0.19,9 +M,0.545,0.415,0.14,0.82,0.4615,0.127,0.218,9 +F,0.55,0.415,0.135,0.8145,0.427,0.1855,0.175,8 +F,0.55,0.43,0.15,0.84,0.395,0.195,0.223,8 +M,0.55,0.425,0.15,0.8315,0.411,0.1765,0.2165,10 +M,0.56,0.43,0.145,0.8995,0.464,0.1775,0.234,9 +M,0.56,0.445,0.16,0.8965,0.42,0.2175,0.2215,8 +F,0.56,0.44,0.155,0.6405,0.336,0.1765,0.245,8 +M,0.56,0.415,0.145,0.852,0.43,0.1885,0.205,8 +M,0.565,0.455,0.15,0.9595,0.4565,0.2395,0.23,9 +M,0.565,0.435,0.15,0.99,0.5795,0.1825,0.206,8 +F,0.565,0.45,0.175,1.0095,0.447,0.2375,0.2645,9 +M,0.57,0.46,0.15,1.0375,0.5415,0.2035,0.25,9 +F,0.57,0.445,0.145,0.8775,0.412,0.217,0.22,8 +I,0.57,0.44,0.15,0.755,0.3425,0.16,0.224,8 +F,0.575,0.46,0.145,0.9945,0.466,0.229,0.265,7 +F,0.575,0.45,0.16,1.068,0.556,0.214,0.2575,10 +M,0.575,0.435,0.14,0.8455,0.401,0.191,0.222,9 +F,0.575,0.47,0.165,0.869,0.435,0.197,0.238,9 +M,0.575,0.455,0.135,0.907,0.4245,0.197,0.26,9 +I,0.575,0.435,0.13,0.805,0.3155,0.2155,0.245,10 +M,0.575,0.445,0.17,1.0225,0.549,0.2175,0.228,9 +M,0.575,0.445,0.145,0.847,0.415,0.1945,0.22,9 +M,0.58,0.455,0.15,1.114,0.4765,0.2155,0.265,8 +M,0.58,0.455,0.195,1.859,0.945,0.426,0.441,9 +M,0.58,0.445,0.135,0.814,0.3775,0.1915,0.22,9 +M,0.58,0.45,0.14,0.9615,0.486,0.1815,0.253,9 +M,0.58,0.45,0.145,1.0025,0.547,0.1975,0.2295,8 +F,0.58,0.45,0.155,0.93,0.385,0.246,0.265,9 +M,0.585,0.46,0.145,0.9335,0.478,0.1825,0.235,9 +M,0.585,0.465,0.16,0.9555,0.4595,0.236,0.265,7 +M,0.59,0.47,0.15,0.9955,0.481,0.232,0.24,8 +F,0.6,0.475,0.16,1.0265,0.485,0.2495,0.2565,9 +M,0.6,0.455,0.17,1.1915,0.696,0.2395,0.24,8 +F,0.6,0.465,0.15,1.1025,0.5455,0.262,0.25,8 +M,0.6,0.465,0.155,1.0165,0.512,0.2465,0.225,10 +F,0.605,0.47,0.165,1.1775,0.611,0.2275,0.292,9 +M,0.605,0.475,0.14,1.1175,0.555,0.257,0.274,9 +M,0.605,0.48,0.17,1.1835,0.582,0.2365,0.317,10 +F,0.605,0.475,0.165,1.056,0.433,0.2195,0.357,9 +M,0.61,0.485,0.16,1.0145,0.5315,0.212,0.2415,8 +M,0.61,0.485,0.145,1.3305,0.783,0.2255,0.2865,9 +M,0.61,0.47,0.165,1.052,0.498,0.242,0.267,9 +M,0.615,0.46,0.17,1.0565,0.4815,0.272,0.27,10 +F,0.615,0.465,0.15,0.923,0.4615,0.1825,0.2415,9 +F,0.615,0.475,0.155,1.027,0.447,0.25,0.285,9 +M,0.62,0.47,0.135,1.0195,0.5315,0.2005,0.2475,8 +M,0.62,0.45,0.2,0.858,0.4285,0.1525,0.2405,8 +F,0.62,0.48,0.16,1.1125,0.5635,0.2445,0.281,8 +F,0.625,0.485,0.175,1.3745,0.7335,0.2715,0.332,9 +M,0.625,0.48,0.185,1.2065,0.587,0.29,0.286,8 +M,0.63,0.47,0.155,1.1325,0.589,0.211,0.287,8 +M,0.63,0.5,0.175,1.2645,0.5635,0.3065,0.3425,10 +F,0.635,0.495,0.015,1.1565,0.5115,0.308,0.2885,9 +M,0.64,0.515,0.165,1.369,0.632,0.3415,0.358,10 +M,0.645,0.53,0.195,1.39,0.6465,0.2945,0.3735,10 +F,0.645,0.48,0.17,1.1345,0.528,0.254,0.305,10 +F,0.65,0.5,0.19,1.464,0.6415,0.339,0.4245,9 +M,0.65,0.5,0.155,1.202,0.565,0.3135,0.294,11 +M,0.655,0.515,0.16,1.31,0.553,0.369,0.345,11 +F,0.655,0.51,0.175,1.415,0.5885,0.3725,0.364,10 +F,0.66,0.53,0.185,1.346,0.546,0.2705,0.476,11 +M,0.665,0.525,0.16,1.363,0.629,0.279,0.34,8 +I,0.665,0.5,0.17,1.2975,0.6035,0.291,0.3595,9 +F,0.67,0.505,0.205,1.3645,0.6075,0.3025,0.353,9 +F,0.685,0.54,0.215,1.7025,0.664,0.3655,0.4735,14 +M,0.685,0.52,0.165,1.519,0.699,0.3685,0.4,10 +F,0.69,0.54,0.155,1.454,0.624,0.3105,0.39,9 +M,0.69,0.53,0.21,1.583,0.7355,0.405,0.3865,12 +F,0.69,0.53,0.17,1.5535,0.7945,0.3485,0.3695,9 +M,0.695,0.56,0.185,1.74,0.885,0.3715,0.4375,10 +M,0.7,0.565,0.18,1.751,0.895,0.3355,0.446,9 +M,0.7,0.575,0.19,2.273,1.095,0.418,0.638,12 +F,0.7,0.525,0.19,1.6465,0.8545,0.307,0.3995,9 +F,0.705,0.55,0.17,1.219,0.6395,0.236,0.301,9 +F,0.71,0.56,0.18,1.652,0.735,0.381,0.4525,11 +M,0.715,0.55,0.19,2.0045,1.0465,0.407,0.5075,12 +M,0.715,0.535,0.19,1.6755,0.889,0.313,0.42,10 +F,0.72,0.58,0.195,2.103,1.0265,0.48,0.5375,10 +F,0.72,0.55,0.2,1.9965,0.9035,0.469,0.5215,10 +M,0.72,0.565,0.145,1.187,0.691,0.1945,0.2685,8 +M,0.725,0.505,0.185,1.978,1.026,0.4255,0.4505,12 +F,0.73,0.575,0.185,1.8795,0.931,0.38,0.4825,12 +M,0.735,0.585,0.185,2.124,0.952,0.55,0.5,11 +M,0.745,0.565,0.215,1.931,0.896,0.4585,0.5,11 +F,0.75,0.57,0.21,2.236,1.109,0.5195,0.545,11 +F,0.755,0.625,0.21,2.505,1.1965,0.513,0.6785,11 +M,0.755,0.58,0.205,2.0065,0.8295,0.4015,0.595,10 +F,0.78,0.63,0.215,2.657,1.488,0.4985,0.586,11 +I,0.185,0.375,0.12,0.4645,0.196,0.1045,0.15,6 +I,0.245,0.205,0.06,0.0765,0.034,0.014,0.0215,4 +I,0.25,0.185,0.065,0.0685,0.0295,0.014,0.0225,5 +I,0.25,0.19,0.065,0.0835,0.039,0.015,0.025,5 +I,0.275,0.195,0.09,0.1125,0.0545,0.0295,0.0355,6 +I,0.305,0.215,0.065,0.1075,0.044,0.0205,0.038,5 +I,0.31,0.225,0.07,0.1055,0.435,0.015,0.04,5 +I,0.315,0.23,0.08,0.1375,0.0545,0.031,0.0445,5 +I,0.315,0.23,0.07,0.1145,0.046,0.0235,0.0385,5 +I,0.325,0.225,0.075,0.139,0.0565,0.032,0.09,6 +I,0.33,0.25,0.095,0.2085,0.102,0.0395,0.052,7 +I,0.33,0.205,0.095,0.1595,0.077,0.032,0.0435,5 +I,0.335,0.245,0.09,0.2015,0.096,0.0405,0.048,7 +I,0.34,0.25,0.09,0.179,0.0775,0.033,0.055,6 +I,0.345,0.255,0.095,0.1945,0.0925,0.037,0.055,6 +I,0.345,0.255,0.085,0.2005,0.105,0.037,0.05,5 +I,0.35,0.27,0.075,0.215,0.1,0.036,0.065,6 +I,0.35,0.255,0.09,0.1785,0.0855,0.0305,0.0525,8 +I,0.36,0.27,0.085,0.196,0.0875,0.035,0.064,4 +I,0.365,0.27,0.085,0.1875,0.081,0.042,0.058,6 +I,0.365,0.27,0.085,0.196,0.0825,0.0375,0.06,7 +I,0.365,0.265,0.085,0.213,0.0945,0.049,0.06,7 +I,0.37,0.29,0.09,0.2445,0.089,0.0655,0.075,7 +I,0.37,0.28,0.085,0.217,0.1095,0.035,0.062,6 +I,0.375,0.29,0.095,0.213,0.096,0.041,0.061,5 +I,0.375,0.29,0.085,0.2385,0.118,0.045,0.0695,7 +I,0.375,0.275,0.09,0.218,0.093,0.0405,0.0755,6 +I,0.375,0.275,0.095,0.2465,0.11,0.0415,0.0775,6 +I,0.375,0.28,0.08,0.2025,0.0825,0.048,0.065,8 +I,0.375,0.27,0.085,0.218,0.0945,0.039,0.07,7 +I,0.38,0.275,0.11,0.256,0.11,0.0535,0.0755,6 +I,0.38,0.27,0.08,0.2105,0.0865,0.042,0.07,8 +I,0.385,0.29,0.09,0.2615,0.111,0.0595,0.0745,9 +I,0.385,0.28,0.085,0.2175,0.097,0.038,0.067,8 +I,0.385,0.3,0.095,0.302,0.152,0.0615,0.0735,7 +I,0.385,0.28,0.09,0.228,0.1025,0.042,0.0655,5 +I,0.39,0.3,0.095,0.3265,0.1665,0.0575,0.089,7 +I,0.395,0.305,0.105,0.284,0.1135,0.0595,0.0945,8 +I,0.395,0.295,0.095,0.2725,0.115,0.0625,0.085,8 +I,0.395,0.27,0.1,0.2985,0.1445,0.061,0.082,5 +I,0.4,0.29,0.1,0.2675,0.1205,0.0605,0.0765,5 +I,0.405,0.285,0.09,0.2645,0.1265,0.0505,0.075,6 +I,0.41,0.335,0.11,0.33,0.157,0.0705,0.17,7 +I,0.42,0.305,0.09,0.328,0.168,0.0615,0.082,6 +I,0.425,0.325,0.11,0.3335,0.173,0.045,0.1,7 +I,0.425,0.32,0.1,0.3055,0.126,0.06,0.106,7 +I,0.425,0.31,0.09,0.301,0.1385,0.065,0.08,7 +I,0.43,0.34,0,0.428,0.2065,0.086,0.115,8 +I,0.43,0.315,0.095,0.378,0.175,0.08,0.1045,8 +I,0.435,0.315,0.11,0.3685,0.1615,0.0715,0.12,7 +I,0.44,0.34,0.12,0.438,0.2115,0.083,0.12,9 +I,0.45,0.33,0.105,0.448,0.208,0.089,0.12,9 +I,0.455,0.345,0.105,0.4005,0.164,0.0755,0.126,8 +F,0.455,0.365,0.115,0.4305,0.184,0.108,0.1245,8 +I,0.455,0.33,0.1,0.372,0.358,0.0775,0.11,8 +I,0.46,0.36,0.105,0.466,0.2225,0.099,0.11,7 +I,0.46,0.35,0.105,0.3705,0.1575,0.077,0.114,9 +F,0.46,0.365,0.125,0.4785,0.206,0.1045,0.141,8 +I,0.465,0.34,0.11,0.346,0.1425,0.073,0.113,11 +I,0.47,0.365,0.1,0.411,0.175,0.0855,0.135,8 +I,0.47,0.355,0.18,0.48,0.2055,0.105,0.1505,8 +I,0.47,0.355,0.12,0.393,0.167,0.0885,0.115,8 +I,0.475,0.355,0.1,0.5035,0.2535,0.091,0.14,8 +I,0.475,0.38,0.12,0.441,0.1785,0.0885,0.1505,8 +I,0.475,0.36,0.11,0.492,0.211,0.11,0.15,8 +I,0.48,0.37,0.125,0.5435,0.244,0.101,0.165,9 +I,0.48,0.355,0.115,0.4725,0.2065,0.112,0.132,8 +I,0.48,0.365,0.1,0.461,0.2205,0.0835,0.135,8 +I,0.495,0.355,0.12,0.4965,0.214,0.1045,0.1495,8 +I,0.495,0.38,0.13,0.5125,0.2185,0.116,0.16,7 +M,0.495,0.395,0.12,0.553,0.224,0.1375,0.167,8 +I,0.5,0.38,0.135,0.594,0.2945,0.104,0.1565,9 +M,0.5,0.42,0.135,0.6765,0.302,0.1415,0.2065,9 +I,0.5,0.375,0.145,0.5795,0.239,0.1375,0.185,9 +I,0.5,0.41,0.14,0.6615,0.2585,0.1625,0.196,9 +I,0.5,0.375,0.125,0.5695,0.259,0.124,0.157,7 +I,0.5,0.395,0.14,0.6215,0.2925,0.1205,0.195,9 +I,0.505,0.405,0.13,0.6015,0.3015,0.11,0.18,8 +I,0.505,0.38,0.12,0.594,0.2595,0.1435,0.18,7 +I,0.505,0.395,0.105,0.551,0.248,0.103,0.171,8 +I,0.515,0.38,0.12,0.625,0.3265,0.1295,0.16,7 +I,0.515,0.42,0.135,0.711,0.337,0.144,0.205,13 +I,0.515,0.4,0.135,0.6965,0.32,0.1255,0.175,9 +I,0.52,0.4,0.13,0.5825,0.233,0.1365,0.18,10 +I,0.52,0.395,0.125,0.663,0.3005,0.131,0.1905,9 +I,0.525,0.4,0.125,0.6965,0.369,0.1385,0.164,9 +M,0.525,0.42,0.155,0.842,0.428,0.1415,0.2045,9 +I,0.53,0.415,0.13,0.694,0.3905,0.111,0.167,9 +I,0.53,0.42,0.155,0.81,0.4725,0.111,0.192,10 +I,0.53,0.415,0.11,0.5745,0.2525,0.1235,0.189,9 +I,0.53,0.425,0.13,0.7675,0.419,0.1205,0.21,9 +I,0.535,0.4,0.135,0.6025,0.2895,0.121,0.154,9 +I,0.535,0.415,0.15,0.5765,0.3595,0.135,0.225,8 +F,0.535,0.41,0.13,0.7145,0.335,0.144,0.2075,9 +M,0.535,0.435,0.15,0.717,0.3475,0.1445,0.194,9 +F,0.54,0.42,0.145,0.8655,0.4315,0.163,0.2175,10 +I,0.54,0.42,0.14,0.7265,0.3205,0.1445,0.229,9 +I,0.545,0.435,0.135,0.7715,0.372,0.148,0.227,8 +F,0.545,0.445,0.15,0.8,0.3535,0.163,0.207,9 +I,0.545,0.43,0.15,0.7285,0.302,0.1315,0.2545,10 +I,0.545,0.405,0.135,0.5945,0.27,0.1185,0.185,8 +I,0.55,0.43,0.145,0.7895,0.3745,0.171,0.223,11 +F,0.55,0.405,0.125,0.651,0.2965,0.137,0.2,9 +M,0.55,0.43,0.15,0.8745,0.413,0.1905,0.248,9 +I,0.55,0.435,0.14,0.7535,0.3285,0.1555,0.2325,10 +I,0.55,0.425,0.135,0.7305,0.3325,0.1545,0.215,9 +M,0.555,0.44,0.14,0.8705,0.407,0.156,0.255,9 +I,0.555,0.43,0.155,0.7395,0.3135,0.1435,0.28,10 +I,0.555,0.43,0.14,0.7665,0.341,0.165,0.23,9 +I,0.555,0.425,0.145,0.7905,0.3485,0.1765,0.225,9 +I,0.56,0.425,0.135,0.8205,0.3715,0.185,0.236,9 +I,0.56,0.425,0.145,0.688,0.3095,0.1305,0.2165,9 +F,0.56,0.445,0.155,1.224,0.5565,0.3225,0.2695,10 +I,0.56,0.455,0.145,0.974,0.547,0.1615,0.235,9 +I,0.565,0.44,0.175,0.8735,0.414,0.21,0.21,11 +F,0.565,0.45,0.145,0.8495,0.4215,0.1685,0.225,8 +M,0.565,0.445,0.15,0.796,0.3635,0.184,0.219,8 +M,0.565,0.39,0.125,0.744,0.352,0.13,0.1685,11 +I,0.57,0.45,0.145,0.751,0.2825,0.2195,0.2215,10 +I,0.57,0.45,0.135,0.794,0.3815,0.1415,0.245,8 +F,0.57,0.46,0.135,0.9795,0.397,0.2525,0.2655,9 +M,0.57,0.435,0.17,0.873,0.382,0.183,0.2705,10 +I,0.57,0.44,0.13,0.7665,0.347,0.1785,0.202,10 +M,0.57,0.435,0.125,0.8965,0.383,0.1835,0.275,9 +F,0.575,0.42,0.135,0.857,0.461,0.147,0.2125,10 +F,0.575,0.48,0.165,1.078,0.511,0.2095,0.306,9 +M,0.575,0.46,0.155,0.892,0.4415,0.176,0.22,10 +M,0.58,0.46,0.155,1.4395,0.6715,0.273,0.2955,10 +M,0.58,0.455,0.135,0.7955,0.405,0.167,0.204,10 +F,0.58,0.445,0.15,0.858,0.4,0.156,0.253,8 +M,0.585,0.465,0.155,0.9145,0.4555,0.1965,0.235,9 +M,0.585,0.49,0.185,1.171,0.522,0.2535,0.335,10 +I,0.585,0.475,0.16,1.0505,0.48,0.234,0.285,10 +M,0.585,0.46,0.165,1.1135,0.5825,0.2345,0.274,10 +M,0.585,0.47,0.165,1.409,0.8,0.229,0.295,10 +M,0.585,0.475,0.15,1.065,0.5315,0.199,0.2885,10 +M,0.585,0.45,0.18,0.7995,0.336,0.1855,0.237,8 +I,0.59,0.445,0.135,0.7715,0.328,0.1745,0.23,9 +M,0.59,0.47,0.18,1.187,0.5985,0.227,0.31,9 +M,0.59,0.455,0.155,0.8855,0.388,0.188,0.275,10 +F,0.595,0.465,0.15,0.98,0.4115,0.196,0.2255,10 +F,0.595,0.465,0.155,1.026,0.4645,0.112,0.305,12 +M,0.6,0.475,0.17,1.1315,0.508,0.272,0.309,10 +M,0.6,0.48,0.155,1.014,0.451,0.1885,0.325,11 +I,0.6,0.475,0.15,1.12,0.565,0.2465,0.27,10 +F,0.6,0.465,0.155,1.04,0.4755,0.25,0.28,11 +F,0.6,0.455,0.145,0.8895,0.419,0.1715,0.269,10 +M,0.6,0.46,0.155,0.9595,0.4455,0.189,0.295,11 +I,0.605,0.485,0.15,1.238,0.6315,0.226,0.33,11 +M,0.605,0.49,0.14,0.9755,0.419,0.206,0.315,10 +I,0.605,0.435,0.13,0.9025,0.432,0.174,0.26,11 +F,0.605,0.475,0.175,1.076,0.463,0.2195,0.335,9 +F,0.605,0.47,0.16,1.0835,0.5405,0.2215,0.275,12 +M,0.61,0.45,0.15,0.871,0.407,0.1835,0.25,10 +M,0.61,0.48,0.165,1.244,0.6345,0.257,0.305,12 +M,0.61,0.475,0.17,1.0265,0.435,0.2335,0.3035,10 +I,0.61,0.465,0.15,0.9605,0.4495,0.1725,0.286,9 +M,0.61,0.48,0.17,1.137,0.4565,0.29,0.347,10 +M,0.61,0.46,0.16,1,0.494,0.197,0.275,10 +F,0.615,0.475,0.155,1.004,0.4475,0.193,0.2895,10 +M,0.615,0.47,0.165,1.128,0.4465,0.2195,0.34,10 +M,0.615,0.5,0.17,1.054,0.4845,0.228,0.295,10 +F,0.615,0.475,0.165,1.023,0.4905,0.1955,0.3035,12 +M,0.615,0.475,0.17,1.129,0.4795,0.302,0.3,10 +M,0.615,0.48,0.175,1.118,0.446,0.3195,0.3,9 +F,0.615,0.475,0.155,1.115,0.484,0.2115,0.355,10 +M,0.62,0.51,0.175,1.2815,0.5715,0.2385,0.39,10 +M,0.62,0.495,0.18,1.2555,0.5765,0.254,0.355,12 +F,0.62,0.5,0.15,1.293,0.596,0.3135,0.354,10 +F,0.62,0.475,0.16,1.1295,0.463,0.2685,0.33,10 +M,0.625,0.455,0.17,1.082,0.4955,0.2345,0.315,9 +F,0.625,0.505,0.175,1.15,0.5475,0.256,0.3045,11 +F,0.625,0.515,0.16,1.264,0.5715,0.326,0.321,9 +F,0.625,0.48,0.155,1.2035,0.5865,0.239,0.3185,12 +F,0.63,0.485,0.17,1.3205,0.5945,0.345,0.345,9 +I,0.63,0.505,0.18,1.272,0.6025,0.295,0.315,11 +M,0.63,0.485,0.145,1.062,0.5065,0.1785,0.3365,12 +I,0.63,0.475,0.145,1.0605,0.5165,0.2195,0.28,10 +M,0.63,0.495,0.16,1.093,0.497,0.221,0.315,12 +M,0.635,0.49,0.16,1.101,0.534,0.1865,0.3455,10 +F,0.635,0.5,0.165,1.4595,0.705,0.2645,0.39,9 +F,0.635,0.495,0.175,1.211,0.707,0.2725,0.323,9 +M,0.635,0.475,0.17,1.1935,0.5205,0.2695,0.3665,10 +M,0.635,0.51,0.155,0.986,0.405,0.2255,0.31,10 +M,0.64,0.565,0.23,1.521,0.644,0.372,0.406,15 +M,0.64,0.525,0.18,1.3135,0.4865,0.2995,0.4075,10 +M,0.645,0.51,0.16,1.1835,0.556,0.2385,0.345,11 +M,0.645,0.5,0.195,1.401,0.6165,0.3515,0.3725,10 +M,0.645,0.525,0.16,1.5075,0.7455,0.245,0.4325,11 +F,0.65,0.505,0.165,1.16,0.4785,0.274,0.349,11 +F,0.65,0.59,0.22,1.662,0.77,0.378,0.435,11 +M,0.65,0.525,0.175,1.5365,0.6865,0.3585,0.405,11 +M,0.65,0.51,0.19,1.542,0.7155,0.3735,0.375,9 +F,0.65,0.51,0.17,1.567,0.7245,0.349,0.391,10 +F,0.655,0.525,0.19,1.3595,0.564,0.3215,0.3985,10 +M,0.655,0.535,0.205,1.6445,0.7305,0.3595,0.46,13 +F,0.655,0.52,0.19,1.4545,0.6,0.3865,0.383,10 +M,0.655,0.49,0.175,1.3585,0.6395,0.294,0.365,10 +F,0.66,0.495,0.21,1.548,0.724,0.3525,0.3925,10 +F,0.66,0.515,0.17,1.337,0.615,0.3125,0.3575,10 +F,0.665,0.53,0.18,1.491,0.6345,0.342,0.435,10 +F,0.67,0.53,0.225,1.5615,0.63,0.487,0.3725,11 +F,0.67,0.505,0.175,1.0145,0.4375,0.271,0.3745,10 +M,0.675,0.545,0.185,1.7375,0.876,0.3135,0.469,13 +M,0.685,0.545,0.205,1.7925,0.8145,0.416,0.461,9 +F,0.695,0.565,0.19,1.7635,0.7465,0.399,0.4975,11 +F,0.7,0.545,0.13,1.556,0.6725,0.374,0.195,12 +M,0.705,0.565,0.515,2.21,1.1075,0.4865,0.512,10 +M,0.705,0.555,0.215,2.141,1.0465,0.383,0.528,11 +F,0.705,0.57,0.18,1.5345,0.96,0.4195,0.43,12 +F,0.71,0.55,0.17,1.614,0.743,0.345,0.45,11 +F,0.72,0.575,0.17,1.9335,0.913,0.389,0.51,13 +M,0.72,0.575,0.215,2.173,0.9515,0.564,0.5365,12 +F,0.725,0.6,0.2,1.737,0.697,0.3585,0.595,11 +F,0.73,0.58,0.19,1.7375,0.6785,0.4345,0.52,11 +F,0.735,0.565,0.205,2.1275,0.949,0.46,0.565,12 +F,0.745,0.57,0.215,2.25,1.1565,0.446,0.558,9 +F,0.75,0.61,0.235,2.5085,1.232,0.519,0.612,14 +F,0.815,0.65,0.25,2.255,0.8905,0.42,0.7975,14 +I,0.14,0.105,0.035,0.014,0.0055,0.0025,0.004,3 +I,0.23,0.165,0.06,0.0515,0.019,0.0145,0.036,4 +I,0.365,0.265,0.135,0.2215,0.105,0.047,0.0605,7 +I,0.365,0.255,0.08,0.1985,0.0785,0.0345,0.053,5 +I,0.37,0.27,0.095,0.232,0.1325,0.041,0.0615,6 +I,0.375,0.28,0.085,0.3155,0.187,0.046,0.067,7 +I,0.385,0.3,0.09,0.247,0.1225,0.044,0.0675,5 +I,0.395,0.295,0.09,0.3025,0.143,0.0665,0.0765,5 +I,0.4,0.29,0.11,0.329,0.188,0.0455,0.0825,6 +I,0.4,0.3,0.09,0.2815,0.1185,0.061,0.08,7 +I,0.405,0.31,0.095,0.3425,0.1785,0.064,0.0855,8 +I,0.405,0.29,0.09,0.2825,0.112,0.075,0.0815,7 +I,0.405,0.3,0.105,0.304,0.1455,0.061,0.0805,6 +I,0.41,0.32,0.095,0.2905,0.141,0.063,0.073,5 +M,0.415,0.315,0.115,0.3895,0.2015,0.065,0.103,9 +I,0.425,0.34,0.105,0.389,0.2015,0.0905,0.088,6 +I,0.43,0.34,0.105,0.4405,0.2385,0.0745,0.1075,6 +I,0.44,0.34,0.105,0.369,0.164,0.08,0.1015,5 +M,0.44,0.32,0.12,0.4565,0.2435,0.092,0.1025,8 +I,0.44,0.365,0.11,0.4465,0.213,0.089,0.1135,9 +M,0.45,0.335,0.125,0.4475,0.2165,0.126,0.11,6 +I,0.455,0.335,0.135,0.501,0.274,0.0995,0.1065,7 +I,0.46,0.355,0.11,0.436,0.1975,0.096,0.125,8 +I,0.47,0.345,0.14,0.4615,0.229,0.1105,0.116,9 +I,0.47,0.35,0.125,0.4315,0.19,0.1165,0.1175,6 +I,0.47,0.355,0.12,0.3685,0.126,0.0835,0.1365,6 +M,0.475,0.37,0.125,0.649,0.347,0.136,0.142,8 +I,0.475,0.365,0.115,0.459,0.2175,0.093,0.1165,7 +F,0.475,0.365,0.115,0.566,0.281,0.117,0.1335,7 +I,0.48,0.36,0.125,0.542,0.2795,0.1025,0.147,7 +I,0.485,0.38,0.12,0.4725,0.2075,0.1075,0.147,6 +M,0.485,0.39,0.085,0.6435,0.2945,0.103,0.198,8 +M,0.485,0.37,0.13,0.526,0.2485,0.105,0.1555,6 +F,0.495,0.38,0.12,0.573,0.2655,0.1285,0.144,7 +M,0.505,0.385,0.105,0.5525,0.239,0.1245,0.1555,9 +F,0.505,0.38,0.135,0.6855,0.361,0.1565,0.161,9 +I,0.515,0.395,0.125,0.556,0.2695,0.096,0.17,8 +M,0.515,0.425,0.145,0.9365,0.497,0.181,0.2185,8 +I,0.515,0.4,0.125,0.5625,0.25,0.1245,0.17,7 +M,0.52,0.4,0.125,0.559,0.254,0.139,0.149,8 +M,0.525,0.4,0.14,0.7205,0.3685,0.145,0.1735,8 +I,0.53,0.43,0.13,0.7045,0.346,0.1415,0.189,9 +M,0.53,0.4,0.125,0.7575,0.398,0.151,0.175,8 +F,0.545,0.41,0.14,0.7405,0.3565,0.1775,0.203,9 +F,0.55,0.43,0.14,0.84,0.375,0.218,0.1945,8 +M,0.55,0.425,0.16,0.793,0.343,0.2035,0.215,9 +F,0.56,0.43,0.15,0.8745,0.453,0.161,0.22,8 +F,0.56,0.435,0.15,0.8715,0.4755,0.1835,0.1835,9 +M,0.57,0.445,0.15,0.9875,0.504,0.207,0.249,8 +M,0.575,0.465,0.15,1.08,0.595,0.2065,0.238,9 +M,0.575,0.46,0.165,0.9155,0.4005,0.2465,0.2385,8 +F,0.58,0.46,0.175,1.165,0.65,0.2205,0.3055,9 +F,0.58,0.435,0.14,0.953,0.475,0.2165,0.2095,9 +M,0.585,0.455,0.15,0.906,0.4095,0.23,0.2335,8 +M,0.59,0.44,0.15,0.8725,0.387,0.215,0.245,8 +F,0.59,0.465,0.15,1.151,0.613,0.239,0.2515,9 +F,0.59,0.46,0.145,0.9905,0.453,0.2205,0.275,8 +F,0.595,0.455,0.16,1.04,0.452,0.2655,0.288,9 +M,0.6,0.455,0.155,0.945,0.4365,0.2085,0.25,8 +M,0.6,0.465,0.2,1.259,0.6405,0.1985,0.357,9 +F,0.605,0.485,0.165,0.9515,0.4535,0.193,0.2765,11 +F,0.605,0.485,0.16,1.201,0.417,0.2875,0.38,9 +F,0.605,0.515,0.17,1.289,0.6,0.2945,0.3315,9 +F,0.61,0.485,0.17,1.1005,0.5125,0.229,0.305,11 +I,0.615,0.475,0.13,0.8425,0.353,0.1915,0.251,8 +M,0.62,0.485,0.155,1.049,0.462,0.231,0.25,10 +F,0.62,0.435,0.155,1.012,0.477,0.236,0.275,8 +M,0.62,0.48,0.165,1.0725,0.4815,0.235,0.312,9 +M,0.625,0.52,0.175,1.4105,0.691,0.322,0.3465,10 +M,0.625,0.47,0.18,1.136,0.451,0.3245,0.305,11 +M,0.63,0.47,0.145,1.1005,0.52,0.26,0.276,9 +F,0.63,0.5,0.175,1.1105,0.467,0.268,0.329,10 +M,0.63,0.455,0.15,1.1315,0.481,0.2745,0.305,9 +M,0.63,0.48,0.15,1.271,0.6605,0.2425,0.31,11 +F,0.63,0.49,0.225,1.336,0.6805,0.259,0.3245,10 +F,0.635,0.505,0.145,1.1345,0.505,0.2655,0.315,10 +M,0.635,0.51,0.185,1.308,0.544,0.318,0.377,8 +F,0.64,0.515,0.205,1.5335,0.6635,0.3345,0.4025,9 +F,0.645,0.515,0.175,1.546,0.7035,0.365,0.415,10 +M,0.645,0.51,0.155,1.539,0.6405,0.3585,0.43,11 +F,0.645,0.505,0.165,1.318,0.55,0.3015,0.335,11 +F,0.65,0.545,0.175,1.5245,0.59,0.326,0.495,10 +M,0.65,0.515,0.175,1.466,0.677,0.3045,0.4,10 +F,0.65,0.5,0.16,1.3825,0.702,0.304,0.3195,9 +M,0.65,0.485,0.14,1.175,0.475,0.2435,0.215,8 +F,0.655,0.54,0.215,1.5555,0.695,0.296,0.444,11 +M,0.655,0.51,0.215,1.7835,0.8885,0.4095,0.4195,11 +M,0.66,0.505,0.165,1.374,0.589,0.351,0.345,10 +F,0.665,0.515,0.18,1.389,0.5945,0.324,0.395,10 +M,0.67,0.545,0.2,1.7025,0.833,0.374,0.41,11 +M,0.67,0.51,0.175,1.5265,0.651,0.4475,0.345,10 +M,0.67,0.5,0.19,1.519,0.616,0.388,0.415,10 +F,0.68,0.5,0.185,1.741,0.7665,0.3255,0.4685,12 +M,0.68,0.515,0.17,1.6115,0.8415,0.306,0.395,11 +M,0.69,0.525,0.2,1.7825,0.9165,0.3325,0.461,12 +F,0.7,0.55,0.17,1.684,0.7535,0.3265,0.32,11 +M,0.7,0.555,0.2,1.858,0.73,0.3665,0.595,11 +M,0.705,0.56,0.165,1.675,0.797,0.4095,0.388,10 +M,0.72,0.565,0.2,2.1055,1.017,0.363,0.494,12 +M,0.725,0.575,0.24,2.21,1.351,0.413,0.5015,13 +M,0.74,0.57,0.18,1.8725,0.9115,0.427,0.446,10 +M,0.75,0.55,0.18,1.893,0.942,0.397,0.445,11 +I,0.21,0.17,0.045,0.0475,0.019,0.011,0.013,5 +I,0.285,0.21,0.055,0.101,0.0415,0.017,0.0335,5 +I,0.295,0.215,0.07,0.121,0.047,0.0155,0.0405,6 +I,0.3,0.23,0.085,0.117,0.05,0.0175,0.0415,6 +I,0.305,0.225,0.09,0.1465,0.063,0.034,0.0415,6 +I,0.335,0.255,0.08,0.168,0.079,0.0355,0.05,5 +I,0.35,0.26,0.075,0.18,0.09,0.0245,0.055,5 +I,0.355,0.27,0.075,0.1775,0.079,0.0315,0.054,6 +I,0.355,0.26,0.09,0.1985,0.0715,0.0495,0.058,7 +I,0.36,0.27,0.095,0.2,0.073,0.056,0.061,8 +I,0.36,0.275,0.075,0.2205,0.0985,0.044,0.066,7 +I,0.36,0.265,0.075,0.1845,0.083,0.0365,0.055,7 +I,0.365,0.27,0.085,0.2225,0.0935,0.0525,0.066,7 +I,0.37,0.27,0.095,0.2175,0.097,0.046,0.065,6 +I,0.375,0.28,0.08,0.2165,0.0935,0.0925,0.07,7 +I,0.38,0.285,0.095,0.243,0.0895,0.0665,0.075,7 +I,0.38,0.29,0.1,0.237,0.108,0.0395,0.082,6 +I,0.385,0.29,0.09,0.2365,0.1,0.0505,0.076,8 +I,0.385,0.28,0.095,0.257,0.119,0.059,0.07,7 +I,0.385,0.3,0.09,0.308,0.1525,0.056,0.0835,8 +I,0.39,0.3,0.09,0.252,0.1065,0.053,0.08,7 +I,0.39,0.285,0.1,0.281,0.1275,0.062,0.077,7 +I,0.39,0.29,0.1,0.2225,0.095,0.0465,0.073,7 +I,0.41,0.3,0.09,0.304,0.129,0.071,0.0955,8 +I,0.41,0.3,0.09,0.28,0.141,0.0575,0.075,8 +I,0.415,0.325,0.1,0.313,0.139,0.0625,0.0965,7 +I,0.425,0.325,0.11,0.317,0.135,0.048,0.09,8 +I,0.425,0.315,0.08,0.303,0.131,0.0585,0.095,7 +I,0.435,0.335,0.1,0.3295,0.129,0.07,0.11,7 +I,0.435,0.325,0.11,0.367,0.1595,0.08,0.105,6 +I,0.45,0.34,0.095,0.3245,0.1385,0.064,0.105,8 +I,0.45,0.335,0.11,0.4195,0.181,0.085,0.1345,7 +I,0.455,0.36,0.115,0.457,0.2085,0.0855,0.147,10 +I,0.46,0.35,0.11,0.4,0.176,0.083,0.1205,7 +I,0.46,0.355,0.11,0.4255,0.2015,0.081,0.13,7 +I,0.465,0.37,0.12,0.4365,0.188,0.0815,0.147,9 +I,0.465,0.345,0.11,0.393,0.1825,0.0735,0.12,8 +I,0.47,0.355,0.125,0.499,0.21,0.0985,0.155,8 +I,0.475,0.36,0.145,0.6325,0.2825,0.137,0.19,8 +M,0.475,0.36,0.1,0.4285,0.1965,0.099,0.112,7 +I,0.475,0.36,0.125,0.4905,0.205,0.1305,0.125,8 +I,0.48,0.37,0.125,0.474,0.179,0.1035,0.175,9 +I,0.48,0.37,0.12,0.536,0.251,0.114,0.15,8 +M,0.48,0.355,0.16,0.464,0.221,0.106,0.239,8 +I,0.485,0.375,0.13,0.6025,0.2935,0.1285,0.16,7 +I,0.49,0.375,0.115,0.4615,0.204,0.0945,0.143,8 +I,0.49,0.4,0.135,0.624,0.3035,0.1285,0.169,8 +I,0.495,0.37,0.125,0.4715,0.2075,0.091,0.15,8 +I,0.495,0.4,0.105,0.602,0.2505,0.1265,0.19,8 +I,0.5,0.4,0.12,0.616,0.261,0.143,0.1935,8 +I,0.5,0.39,0.12,0.5955,0.2455,0.147,0.173,8 +I,0.5,0.375,0.14,0.559,0.2375,0.135,0.169,9 +I,0.51,0.395,0.13,0.6025,0.281,0.143,0.162,7 +F,0.515,0.375,0.11,0.6065,0.3005,0.131,0.15,6 +I,0.515,0.36,0.125,0.4725,0.1815,0.125,0.138,9 +I,0.515,0.35,0.105,0.4745,0.213,0.123,0.1275,10 +I,0.515,0.395,0.125,0.6635,0.32,0.14,0.17,8 +I,0.515,0.39,0.125,0.5705,0.238,0.1265,0.185,8 +I,0.52,0.41,0.145,0.646,0.2965,0.1595,0.165,9 +I,0.52,0.39,0.13,0.5545,0.2355,0.1095,0.1895,7 +M,0.525,0.415,0.145,0.845,0.3525,0.1635,0.2875,8 +I,0.525,0.39,0.12,0.664,0.3115,0.147,0.178,9 +I,0.525,0.38,0.135,0.615,0.261,0.159,0.175,8 +I,0.525,0.4,0.14,0.654,0.305,0.16,0.169,7 +M,0.525,0.4,0.155,0.707,0.282,0.1605,0.225,9 +I,0.53,0.42,0.12,0.5965,0.2555,0.141,0.177,7 +I,0.53,0.43,0.135,0.6255,0.245,0.1455,0.2135,10 +I,0.53,0.4,0.145,0.555,0.1935,0.1305,0.195,9 +I,0.53,0.42,0.13,0.8365,0.3745,0.167,0.249,11 +I,0.535,0.4,0.13,0.657,0.2835,0.162,0.175,7 +I,0.54,0.43,0.17,0.836,0.3725,0.1815,0.24,9 +I,0.54,0.425,0.14,0.742,0.32,0.1395,0.25,9 +I,0.54,0.43,0.14,0.8195,0.3935,0.1725,0.2295,9 +M,0.54,0.455,0.14,0.972,0.419,0.255,0.269,10 +I,0.54,0.42,0.14,0.6275,0.2505,0.1175,0.235,9 +I,0.54,0.425,0.13,0.7205,0.2955,0.169,0.225,10 +I,0.54,0.425,0.135,0.686,0.3475,0.1545,0.213,8 +I,0.545,0.4,0.13,0.686,0.3285,0.1455,0.18,9 +I,0.545,0.375,0.12,0.543,0.2375,0.1155,0.1725,8 +I,0.545,0.42,0.125,0.717,0.358,0.112,0.22,8 +M,0.55,0.435,0.14,0.7625,0.327,0.1685,0.259,10 +I,0.55,0.425,0.15,0.639,0.269,0.1345,0.217,9 +I,0.55,0.42,0.135,0.816,0.3995,0.1485,0.23,12 +I,0.55,0.415,0.145,0.7815,0.373,0.16,0.2215,8 +I,0.55,0.425,0.15,0.7665,0.339,0.176,0.21,8 +I,0.555,0.395,0.13,0.5585,0.222,0.1245,0.17,9 +I,0.555,0.435,0.14,0.765,0.3945,0.15,0.206,8 +I,0.555,0.46,0.145,0.9005,0.3845,0.158,0.2765,11 +I,0.56,0.445,0.15,0.8225,0.3685,0.187,0.236,10 +I,0.56,0.44,0.13,0.7235,0.349,0.149,0.2,8 +M,0.56,0.425,0.135,0.849,0.3265,0.221,0.2645,10 +I,0.565,0.42,0.155,0.743,0.31,0.186,0.231,9 +F,0.565,0.44,0.15,0.863,0.435,0.149,0.27,9 +M,0.565,0.44,0.125,0.802,0.3595,0.1825,0.215,9 +M,0.565,0.43,0.15,0.831,0.4245,0.1735,0.219,10 +F,0.57,0.45,0.135,0.7805,0.3345,0.185,0.21,8 +M,0.57,0.45,0.14,0.795,0.3385,0.148,0.245,9 +I,0.57,0.435,0.17,0.848,0.4,0.166,0.25,9 +I,0.57,0.43,0.145,0.833,0.354,0.144,0.2815,10 +I,0.57,0.445,0.155,0.867,0.3705,0.1705,0.28,9 +I,0.57,0.445,0.145,0.7405,0.306,0.172,0.1825,12 +M,0.575,0.455,0.165,0.867,0.3765,0.1805,0.268,8 +I,0.575,0.425,0.135,0.7965,0.364,0.196,0.239,10 +F,0.575,0.47,0.155,1.116,0.509,0.238,0.34,10 +I,0.575,0.45,0.125,0.78,0.3275,0.188,0.235,9 +M,0.575,0.47,0.185,0.985,0.3745,0.2175,0.355,10 +F,0.575,0.465,0.195,0.9965,0.417,0.247,0.47,8 +I,0.575,0.445,0.17,0.8015,0.3475,0.1465,0.25,9 +I,0.575,0.45,0.135,0.807,0.3615,0.176,0.254,10 +F,0.575,0.435,0.15,1.0305,0.4605,0.218,0.36,8 +M,0.575,0.445,0.16,0.839,0.4005,0.198,0.239,9 +M,0.575,0.44,0.16,0.9615,0.483,0.166,0.275,13 +F,0.58,0.435,0.15,0.834,0.428,0.1515,0.23,8 +M,0.58,0.46,0.155,1.0335,0.469,0.2225,0.295,10 +M,0.58,0.43,0.13,0.798,0.365,0.173,0.2285,10 +I,0.58,0.445,0.125,0.7095,0.303,0.1405,0.235,9 +F,0.585,0.445,0.14,0.913,0.4305,0.2205,0.253,10 +M,0.59,0.49,0.165,1.207,0.559,0.235,0.309,10 +I,0.59,0.45,0.145,1.022,0.428,0.268,0.265,10 +I,0.59,0.46,0.145,0.9015,0.419,0.1785,0.26,11 +F,0.595,0.435,0.15,0.9,0.4175,0.17,0.265,8 +M,0.595,0.45,0.14,0.838,0.3965,0.194,0.217,10 +M,0.595,0.45,0.145,0.959,0.463,0.2065,0.2535,10 +I,0.595,0.46,0.15,0.8335,0.377,0.1925,0.235,8 +F,0.6,0.46,0.155,0.9735,0.427,0.2045,0.3,8 +F,0.6,0.475,0.15,1.13,0.575,0.196,0.305,9 +M,0.6,0.48,0.165,0.9165,0.4135,0.1965,0.2725,9 +I,0.6,0.48,0.17,0.9175,0.38,0.2225,0.29,8 +F,0.6,0.48,0.18,1.0645,0.4495,0.2455,0.325,10 +M,0.6,0.47,0.165,1.059,0.504,0.241,0.275,9 +M,0.6,0.47,0.16,1.194,0.5625,0.3045,0.2635,10 +F,0.605,0.455,0.145,0.9775,0.468,0.1775,0.275,9 +M,0.605,0.475,0.145,0.884,0.3835,0.1905,0.27,8 +I,0.605,0.47,0.145,0.8025,0.379,0.2265,0.22,9 +F,0.605,0.48,0.14,0.991,0.4735,0.2345,0.24,8 +F,0.605,0.47,0.155,0.974,0.393,0.224,0.3345,9 +F,0.605,0.505,0.18,1.434,0.7285,0.264,0.431,11 +M,0.61,0.475,0.155,0.983,0.4565,0.228,0.266,10 +F,0.61,0.465,0.16,1.0725,0.4835,0.2515,0.28,10 +F,0.61,0.485,0.15,1.2405,0.6025,0.2915,0.3085,12 +M,0.61,0.47,0.16,1.022,0.449,0.2345,0.2945,9 +F,0.61,0.475,0.16,1.1155,0.3835,0.223,0.379,10 +I,0.61,0.465,0.125,0.9225,0.436,0.19,0.26,9 +M,0.61,0.47,0.17,1.1185,0.5225,0.2405,0.31,9 +F,0.61,0.485,0.18,1.2795,0.5735,0.2855,0.355,7 +M,0.615,0.47,0.16,1.0175,0.473,0.2395,0.28,10 +M,0.615,0.475,0.175,1.224,0.6035,0.261,0.311,9 +I,0.62,0.485,0.18,1.154,0.4935,0.256,0.315,12 +F,0.62,0.515,0.155,1.3255,0.6685,0.2605,0.335,12 +M,0.62,0.515,0.175,1.221,0.535,0.241,0.395,13 +F,0.62,0.54,0.165,1.139,0.4995,0.2435,0.357,11 +I,0.62,0.49,0.16,1.066,0.446,0.246,0.305,11 +F,0.62,0.48,0.18,1.2215,0.582,0.2695,0.313,12 +I,0.62,0.47,0.14,0.8565,0.3595,0.16,0.295,9 +I,0.62,0.45,0.135,0.924,0.358,0.2265,0.2965,10 +M,0.62,0.48,0.15,1.266,0.6285,0.2575,0.309,12 +F,0.62,0.48,0.175,1.0405,0.464,0.2225,0.3,9 +M,0.625,0.49,0.165,1.1165,0.4895,0.2615,0.3325,11 +M,0.625,0.475,0.16,1.0845,0.5005,0.2355,0.3105,10 +M,0.625,0.5,0.17,1.0985,0.4645,0.22,0.354,9 +I,0.625,0.47,0.155,1.1955,0.643,0.2055,0.3145,12 +F,0.625,0.485,0.175,1.362,0.6765,0.2615,0.3705,10 +I,0.625,0.485,0.15,1.044,0.438,0.2865,0.278,9 +M,0.63,0.505,0.17,1.0915,0.4615,0.266,0.3,9 +F,0.63,0.5,0.18,1.1965,0.514,0.2325,0.3995,8 +M,0.63,0.49,0.17,1.1745,0.5255,0.273,0.339,11 +M,0.63,0.485,0.165,1.233,0.6565,0.2315,0.3035,10 +M,0.63,0.495,0.175,1.2695,0.605,0.271,0.328,11 +I,0.635,0.5,0.165,1.489,0.715,0.3445,0.3615,13 +M,0.635,0.5,0.17,1.4345,0.611,0.309,0.418,12 +F,0.635,0.49,0.175,1.2435,0.5805,0.313,0.305,10 +F,0.635,0.49,0.17,1.2615,0.5385,0.2665,0.38,9 +F,0.64,0.505,0.165,1.2235,0.5215,0.2695,0.36,10 +M,0.64,0.515,0.18,1.247,0.5475,0.2925,0.3685,10 +M,0.64,0.525,0.185,1.707,0.763,0.4205,0.4435,11 +M,0.645,0.505,0.15,1.1605,0.519,0.2615,0.335,10 +M,0.645,0.5,0.175,1.286,0.5645,0.288,0.386,12 +M,0.645,0.5,0.19,1.5595,0.741,0.3715,0.3845,14 +M,0.645,0.51,0.19,1.4745,0.605,0.345,0.48,9 +M,0.645,0.51,0.195,1.226,0.5885,0.2215,0.3745,10 +M,0.645,0.51,0.16,1.33,0.6665,0.309,0.317,9 +F,0.645,0.51,0.16,1.2415,0.5815,0.276,0.315,9 +M,0.645,0.5,0.175,1.3375,0.554,0.308,0.415,10 +F,0.645,0.51,0.19,1.363,0.573,0.362,0.36,10 +M,0.645,0.485,0.15,1.2215,0.5695,0.2735,0.33,9 +F,0.645,0.48,0.19,1.371,0.6925,0.2905,0.35,12 +F,0.65,0.495,0.155,1.337,0.615,0.3195,0.335,9 +M,0.65,0.505,0.19,1.274,0.59,0.23,0.391,11 +M,0.65,0.525,0.185,1.488,0.665,0.337,0.378,11 +M,0.65,0.51,0.16,1.3835,0.6385,0.2905,0.3665,9 +M,0.655,0.55,0.18,1.274,0.586,0.281,0.365,10 +F,0.655,0.51,0.15,1.043,0.4795,0.223,0.305,9 +F,0.655,0.505,0.19,1.3485,0.5935,0.2745,0.425,12 +F,0.655,0.505,0.195,1.4405,0.688,0.3805,0.363,11 +M,0.66,0.5,0.165,1.3195,0.667,0.269,0.341,9 +F,0.66,0.535,0.175,1.5175,0.711,0.3125,0.415,12 +M,0.66,0.53,0.195,1.5505,0.6505,0.3295,0.495,10 +M,0.66,0.51,0.165,1.6375,0.7685,0.3545,0.3925,14 +M,0.665,0.525,0.175,1.443,0.6635,0.3845,0.353,11 +M,0.665,0.505,0.16,1.289,0.6145,0.253,0.3665,11 +F,0.665,0.505,0.16,1.2915,0.631,0.2925,0.32,11 +M,0.665,0.52,0.175,1.3725,0.606,0.32,0.395,12 +M,0.665,0.5,0.175,1.2975,0.6075,0.314,0.315,9 +M,0.67,0.505,0.16,1.2585,0.6255,0.311,0.308,12 +M,0.67,0.52,0.165,1.39,0.711,0.2865,0.3,11 +F,0.67,0.52,0.19,1.32,0.5235,0.3095,0.4275,13 +F,0.67,0.55,0.155,1.566,0.858,0.339,0.354,10 +F,0.67,0.54,0.195,1.619,0.74,0.3305,0.465,11 +M,0.675,0.525,0.16,1.2835,0.572,0.2755,0.3545,13 +F,0.675,0.51,0.195,1.382,0.6045,0.3175,0.3965,10 +M,0.68,0.52,0.195,1.4535,0.592,0.391,0.4125,10 +F,0.68,0.51,0.2,1.6075,0.714,0.339,0.4705,11 +M,0.685,0.52,0.15,1.3735,0.7185,0.293,0.32,11 +F,0.685,0.565,0.175,1.638,0.7775,0.375,0.438,11 +F,0.69,0.55,0.2,1.569,0.687,0.3675,0.46,12 +M,0.7,0.565,0.175,1.8565,0.8445,0.3935,0.54,10 +F,0.7,0.535,0.175,1.773,0.6805,0.48,0.512,15 +F,0.705,0.545,0.17,1.58,0.6435,0.4565,0.265,11 +M,0.71,0.575,0.215,2.009,0.9895,0.4475,0.502,11 +F,0.71,0.57,0.195,1.9805,0.9925,0.4925,0.48,12 +F,0.71,0.54,0.205,1.5805,0.802,0.287,0.435,10 +M,0.71,0.56,0.22,2.015,0.9215,0.454,0.566,11 +M,0.72,0.57,0.2,1.8275,0.919,0.366,0.485,10 +M,0.72,0.55,0.205,2.125,1.1455,0.4425,0.511,13 +F,0.72,0.525,0.18,1.445,0.631,0.3215,0.435,7 +F,0.725,0.565,0.21,2.1425,1.03,0.487,0.503,14 +F,0.73,0.56,0.19,1.9425,0.799,0.5195,0.5655,11 +M,0.735,0.59,0.215,1.747,0.7275,0.403,0.557,11 +F,0.74,0.565,0.205,2.119,0.9655,0.5185,0.482,12 +F,0.75,0.565,0.215,1.938,0.7735,0.4825,0.575,11 +M,0.75,0.595,0.205,2.2205,1.083,0.421,0.63,12 +M,0.77,0.62,0.195,2.5155,1.1155,0.6415,0.642,12 +M,0.775,0.63,0.25,2.7795,1.3485,0.76,0.578,12 +I,0.275,0.175,0.09,0.2315,0.096,0.057,0.0705,5 +I,0.375,0.245,0.1,0.394,0.166,0.091,0.1125,6 +F,0.375,0.27,0.135,0.597,0.272,0.131,0.1675,7 +M,0.39,0.28,0.125,0.564,0.3035,0.0955,0.143,7 +I,0.435,0.3,0.12,0.5965,0.259,0.139,0.1645,8 +M,0.445,0.32,0.12,0.414,0.199,0.09,0.117,7 +I,0.455,0.335,0.105,0.422,0.229,0.0865,0.1,6 +I,0.455,0.325,0.135,0.82,0.4005,0.1715,0.211,8 +I,0.455,0.345,0.11,0.434,0.207,0.0855,0.1215,8 +I,0.465,0.325,0.14,0.7615,0.362,0.1535,0.209,10 +M,0.465,0.36,0.115,0.5795,0.295,0.1395,0.12,7 +I,0.485,0.365,0.105,0.5205,0.195,0.123,0.182,8 +M,0.485,0.37,0.155,0.968,0.419,0.2455,0.2365,9 +I,0.485,0.345,0.16,0.869,0.3085,0.185,0.319,9 +F,0.49,0.355,0.16,0.8795,0.3485,0.215,0.2825,8 +M,0.5,0.37,0.15,1.0615,0.494,0.223,0.296,9 +M,0.515,0.35,0.155,0.9225,0.4185,0.198,0.273,9 +M,0.515,0.395,0.135,1.007,0.472,0.2495,0.252,8 +M,0.525,0.365,0.17,0.9605,0.438,0.2225,0.276,10 +M,0.525,0.38,0.125,0.65,0.303,0.155,0.159,7 +M,0.53,0.41,0.14,0.7545,0.3495,0.1715,0.2105,8 +F,0.535,0.425,0.135,0.771,0.3765,0.1815,0.1795,8 +I,0.535,0.385,0.18,1.0835,0.4955,0.2295,0.304,8 +I,0.545,0.42,0.165,0.8935,0.4235,0.2195,0.228,8 +F,0.545,0.415,0.2,1.358,0.567,0.318,0.403,10 +F,0.545,0.385,0.15,1.1185,0.5425,0.2445,0.2845,9 +F,0.55,0.38,0.165,1.205,0.543,0.294,0.3345,10 +M,0.55,0.42,0.16,1.3405,0.6325,0.311,0.344,10 +M,0.57,0.455,0.175,1.02,0.4805,0.2145,0.29,9 +M,0.575,0.44,0.185,1.025,0.5075,0.2245,0.2485,10 +I,0.575,0.45,0.13,0.8145,0.403,0.1715,0.213,10 +F,0.58,0.43,0.17,1.48,0.6535,0.324,0.4155,10 +M,0.585,0.455,0.145,0.953,0.3945,0.2685,0.258,10 +I,0.585,0.45,0.15,0.8915,0.3975,0.2035,0.253,8 +M,0.6,0.495,0.175,1.3005,0.6195,0.284,0.3285,11 +M,0.6,0.465,0.165,1.038,0.4975,0.2205,0.251,9 +M,0.605,0.475,0.175,1.2525,0.5575,0.3055,0.343,9 +M,0.605,0.475,0.15,1.15,0.575,0.232,0.297,10 +F,0.61,0.475,0.15,1.1135,0.5195,0.2575,0.3005,11 +F,0.615,0.455,0.145,1.1155,0.5045,0.238,0.315,10 +M,0.62,0.47,0.145,1.0865,0.511,0.2715,0.2565,10 +M,0.625,0.495,0.175,1.254,0.5815,0.286,0.3185,9 +M,0.625,0.49,0.185,1.169,0.5275,0.2535,0.344,11 +M,0.635,0.495,0.195,1.172,0.445,0.3115,0.3475,11 +F,0.635,0.475,0.15,1.1845,0.533,0.307,0.291,10 +F,0.64,0.475,0.14,1.0725,0.4895,0.2295,0.31,8 +M,0.645,0.5,0.16,1.3815,0.672,0.326,0.315,9 +M,0.65,0.525,0.19,1.6125,0.777,0.3685,0.3965,11 +M,0.65,0.485,0.16,1.7395,0.5715,0.2785,0.3075,10 +F,0.655,0.52,0.2,1.5475,0.713,0.314,0.466,9 +M,0.655,0.545,0.19,1.4245,0.6325,0.333,0.378,10 +F,0.665,0.515,0.185,1.3405,0.5595,0.293,0.4375,11 +F,0.675,0.53,0.175,1.4465,0.6775,0.33,0.389,10 +F,0.685,0.535,0.175,1.5845,0.7175,0.3775,0.4215,9 +F,0.695,0.55,0.185,1.679,0.805,0.4015,0.3965,10 +M,0.695,0.53,0.19,1.726,0.7625,0.436,0.455,11 +F,0.705,0.545,0.18,1.5395,0.6075,0.3675,0.4645,13 +F,0.72,0.55,0.195,2.073,1.0715,0.4265,0.5015,9 +M,0.72,0.56,0.18,1.5865,0.691,0.375,0.4425,11 +M,0.73,0.575,0.21,2.069,0.9285,0.409,0.643,11 +I,0.185,0.135,0.04,0.027,0.0105,0.0055,0.009,5 +I,0.24,0.18,0.055,0.0555,0.0235,0.013,0.018,4 +I,0.31,0.215,0.075,0.1275,0.0565,0.0275,0.036,7 +I,0.34,0.26,0.085,0.1885,0.0815,0.0335,0.06,6 +I,0.35,0.265,0.08,0.2,0.09,0.042,0.06,7 +I,0.365,0.27,0.085,0.197,0.0815,0.0325,0.065,6 +I,0.365,0.275,0.085,0.223,0.098,0.0375,0.075,7 +I,0.365,0.27,0.075,0.2215,0.095,0.0445,0.07,6 +I,0.39,0.31,0.105,0.2665,0.1185,0.0525,0.081,8 +I,0.405,0.3,0.09,0.269,0.103,0.067,0.11,6 +I,0.41,0.315,0.095,0.2805,0.114,0.0345,0.11,7 +I,0.41,0.335,0.105,0.3305,0.1405,0.064,0.105,7 +I,0.415,0.31,0.09,0.2815,0.1245,0.0615,0.085,6 +I,0.415,0.31,0.1,0.2805,0.114,0.0565,0.0975,6 +I,0.415,0.31,0.095,0.311,0.1125,0.0625,0.115,8 +I,0.42,0.325,0.1,0.368,0.1675,0.0625,0.1135,11 +I,0.43,0.34,0.1,0.3405,0.1395,0.0665,0.12,8 +I,0.435,0.335,0.1,0.3245,0.135,0.0785,0.098,7 +I,0.435,0.33,0.11,0.38,0.1515,0.0945,0.11,7 +I,0.435,0.33,0.105,0.335,0.156,0.0555,0.105,8 +I,0.435,0.345,0.12,0.3215,0.13,0.056,0.1185,7 +I,0.445,0.33,0.11,0.358,0.1525,0.067,0.1185,8 +I,0.465,0.37,0.11,0.445,0.1635,0.096,0.166,7 +I,0.47,0.375,0.12,0.487,0.196,0.099,0.135,8 +I,0.475,0.34,0.105,0.4535,0.203,0.08,0.1465,9 +I,0.485,0.385,0.13,0.568,0.2505,0.178,0.154,7 +I,0.485,0.36,0.12,0.5155,0.2465,0.1025,0.147,8 +I,0.485,0.37,0.115,0.457,0.1885,0.0965,0.15,9 +I,0.495,0.38,0.135,0.5095,0.2065,0.1165,0.165,8 +I,0.495,0.38,0.145,0.5,0.205,0.148,0.1505,8 +I,0.495,0.375,0.14,0.494,0.181,0.0975,0.191,8 +I,0.5,0.38,0.11,0.5605,0.28,0.106,0.15,9 +I,0.505,0.405,0.13,0.599,0.2245,0.1175,0.225,11 +I,0.505,0.4,0.145,0.7045,0.334,0.1425,0.207,8 +F,0.51,0.4,0.12,0.7005,0.347,0.1105,0.195,10 +I,0.515,0.415,0.135,0.7125,0.285,0.152,0.245,10 +I,0.515,0.42,0.15,0.6725,0.2555,0.1335,0.235,10 +M,0.515,0.385,0.11,0.5785,0.253,0.16,0.14,8 +I,0.52,0.41,0.11,0.5185,0.2165,0.0915,0.184,8 +I,0.52,0.415,0.14,0.6375,0.308,0.1335,0.168,9 +I,0.52,0.395,0.125,0.5805,0.2445,0.146,0.165,9 +I,0.52,0.38,0.115,0.6645,0.3285,0.17,0.1425,7 +I,0.52,0.385,0.115,0.581,0.2555,0.156,0.143,10 +I,0.525,0.415,0.12,0.596,0.2805,0.12,0.1695,9 +I,0.525,0.405,0.145,0.6965,0.3045,0.1535,0.21,8 +I,0.525,0.4,0.145,0.6095,0.248,0.159,0.175,9 +I,0.53,0.43,0.14,0.677,0.298,0.0965,0.23,8 +I,0.53,0.43,0.16,0.7245,0.321,0.1275,0.24,9 +I,0.53,0.395,0.13,0.575,0.247,0.115,0.183,9 +I,0.53,0.405,0.12,0.632,0.2715,0.148,0.1875,9 +I,0.535,0.455,0.14,1.0015,0.53,0.1765,0.244,9 +F,0.54,0.425,0.16,0.9455,0.3675,0.2005,0.295,9 +I,0.54,0.395,0.135,0.6555,0.2705,0.155,0.192,9 +I,0.54,0.39,0.125,0.6255,0.2525,0.158,0.19,8 +I,0.545,0.425,0.14,0.8145,0.305,0.231,0.244,10 +I,0.545,0.43,0.14,0.687,0.2615,0.1405,0.25,9 +I,0.55,0.435,0.14,0.7995,0.295,0.1905,0.238,10 +I,0.55,0.45,0.13,0.804,0.3375,0.1405,0.23,6 +M,0.555,0.435,0.14,0.7495,0.341,0.1645,0.214,8 +M,0.555,0.41,0.125,0.599,0.2345,0.1465,0.194,8 +M,0.555,0.4,0.13,0.7075,0.332,0.1585,0.18,7 +I,0.555,0.45,0.175,0.738,0.304,0.1755,0.22,9 +M,0.555,0.455,0.135,0.837,0.382,0.171,0.235,9 +I,0.56,0.445,0.165,0.832,0.3455,0.179,0.279,9 +F,0.565,0.445,0.125,0.8305,0.3135,0.1785,0.23,11 +M,0.565,0.415,0.125,0.667,0.302,0.1545,0.185,7 +M,0.565,0.455,0.155,0.9355,0.421,0.183,0.26,11 +I,0.565,0.435,0.145,0.8445,0.3975,0.158,0.255,9 +M,0.565,0.45,0.16,0.895,0.415,0.195,0.246,9 +I,0.565,0.46,0.155,0.8715,0.3755,0.215,0.25,10 +M,0.57,0.46,0.155,1.0005,0.454,0.205,0.265,11 +M,0.57,0.455,0.155,0.832,0.3585,0.174,0.277,11 +M,0.57,0.44,0.175,0.9415,0.3805,0.2285,0.283,9 +M,0.57,0.415,0.13,0.88,0.4275,0.1955,0.238,13 +F,0.57,0.44,0.12,0.803,0.382,0.1525,0.234,9 +M,0.575,0.45,0.13,0.785,0.318,0.193,0.2265,9 +M,0.575,0.45,0.155,0.9765,0.495,0.2145,0.235,9 +M,0.575,0.435,0.135,0.992,0.432,0.2225,0.239,10 +M,0.575,0.455,0.155,1.013,0.4685,0.2085,0.295,11 +M,0.575,0.445,0.145,0.876,0.3795,0.1615,0.27,10 +F,0.575,0.465,0.175,1.099,0.4735,0.202,0.35,9 +I,0.575,0.45,0.135,0.8715,0.45,0.162,0.225,10 +I,0.575,0.45,0.135,0.8245,0.3375,0.2115,0.239,11 +F,0.575,0.43,0.155,0.7955,0.3485,0.1925,0.22,9 +M,0.575,0.475,0.145,0.857,0.3665,0.173,0.269,9 +F,0.58,0.45,0.195,0.8265,0.4035,0.173,0.225,9 +F,0.58,0.5,0.165,0.925,0.37,0.185,0.3005,10 +M,0.58,0.44,0.15,1.0465,0.518,0.2185,0.2795,10 +I,0.58,0.44,0.145,0.7905,0.3525,0.1645,0.242,10 +M,0.58,0.44,0.16,0.8295,0.3365,0.2005,0.2485,9 +M,0.595,0.455,0.15,0.886,0.4315,0.201,0.223,10 +F,0.6,0.47,0.135,0.97,0.4655,0.1955,0.264,11 +M,0.6,0.46,0.17,1.1805,0.456,0.337,0.329,11 +M,0.6,0.475,0.15,0.99,0.386,0.2195,0.3105,10 +F,0.6,0.465,0.16,1.133,0.466,0.2885,0.298,11 +I,0.605,0.49,0.165,1.071,0.482,0.1935,0.352,10 +F,0.605,0.455,0.145,0.862,0.334,0.1985,0.3,9 +M,0.605,0.47,0.18,1.1155,0.479,0.2565,0.321,10 +M,0.61,0.48,0.14,1.031,0.4375,0.2615,0.27,8 +F,0.61,0.46,0.145,1.1185,0.478,0.2945,0.2985,10 +F,0.61,0.46,0.155,0.957,0.4255,0.1975,0.265,8 +F,0.61,0.47,0.165,1.1785,0.566,0.2785,0.294,11 +M,0.615,0.47,0.145,1.0285,0.4435,0.2825,0.285,11 +M,0.615,0.47,0.15,1.0875,0.4975,0.283,0.2685,9 +F,0.615,0.495,0.16,1.255,0.5815,0.3195,0.3225,12 +M,0.615,0.495,0.2,1.219,0.564,0.227,0.3885,10 +M,0.62,0.49,0.16,1.035,0.44,0.2525,0.285,11 +M,0.62,0.49,0.15,1.195,0.4605,0.302,0.355,9 +F,0.62,0.495,0.17,1.062,0.372,0.213,0.34,11 +M,0.62,0.495,0.195,1.5145,0.579,0.346,0.5195,15 +M,0.62,0.47,0.15,1.309,0.587,0.4405,0.325,9 +M,0.62,0.485,0.155,1.0295,0.425,0.2315,0.335,12 +M,0.625,0.495,0.155,1.0485,0.487,0.212,0.3215,11 +M,0.625,0.515,0.17,1.331,0.5725,0.3005,0.361,9 +M,0.625,0.505,0.185,1.1565,0.52,0.2405,0.3535,10 +F,0.625,0.445,0.16,1.09,0.46,0.2965,0.304,11 +F,0.625,0.52,0.18,1.354,0.4845,0.351,0.375,11 +F,0.625,0.47,0.145,0.984,0.475,0.2,0.265,11 +M,0.63,0.49,0.155,1.2525,0.63,0.246,0.289,9 +F,0.635,0.485,0.165,1.2695,0.5635,0.3065,0.3395,11 +F,0.635,0.52,0.165,1.3405,0.5065,0.296,0.412,11 +F,0.635,0.505,0.155,1.2895,0.594,0.314,0.345,11 +M,0.635,0.525,0.16,1.195,0.5435,0.246,0.335,12 +M,0.635,0.5,0.165,1.273,0.6535,0.213,0.365,12 +M,0.635,0.515,0.165,1.229,0.5055,0.2975,0.3535,10 +M,0.64,0.53,0.165,1.1895,0.4765,0.3,0.35,11 +F,0.64,0.48,0.145,1.1145,0.508,0.24,0.34,10 +F,0.64,0.515,0.165,1.3115,0.4945,0.2555,0.41,10 +I,0.64,0.49,0.135,1.1,0.488,0.2505,0.2925,10 +M,0.64,0.49,0.155,1.1285,0.477,0.269,0.34,9 +F,0.64,0.485,0.185,1.4195,0.6735,0.3465,0.3255,11 +F,0.645,0.51,0.18,1.6195,0.7815,0.322,0.4675,12 +M,0.645,0.49,0.175,1.32,0.6525,0.2375,0.3385,11 +F,0.645,0.52,0.21,1.5535,0.616,0.3655,0.474,16 +I,0.65,0.52,0.15,1.238,0.5495,0.296,0.3305,10 +F,0.65,0.51,0.155,1.189,0.483,0.278,0.3645,13 +F,0.65,0.51,0.185,1.375,0.531,0.384,0.3985,10 +F,0.655,0.515,0.18,1.412,0.6195,0.2485,0.497,11 +F,0.655,0.525,0.175,1.348,0.5855,0.2605,0.394,10 +M,0.655,0.52,0.17,1.1445,0.53,0.223,0.348,9 +F,0.66,0.535,0.205,1.4415,0.5925,0.2775,0.49,10 +M,0.66,0.51,0.175,1.218,0.5055,0.303,0.37,11 +F,0.665,0.5,0.15,1.2475,0.4625,0.2955,0.3595,10 +M,0.665,0.515,0.2,1.2695,0.5115,0.2675,0.436,12 +M,0.665,0.525,0.18,1.429,0.6715,0.29,0.4,12 +F,0.67,0.53,0.205,1.4015,0.643,0.2465,0.416,12 +M,0.675,0.515,0.15,1.312,0.556,0.2845,0.4115,11 +F,0.675,0.51,0.185,1.473,0.6295,0.3025,0.4245,11 +M,0.68,0.54,0.19,1.623,0.7165,0.354,0.4715,12 +M,0.68,0.54,0.155,1.534,0.671,0.379,0.384,10 +M,0.685,0.535,0.155,1.3845,0.6615,0.2145,0.4075,10 +M,0.69,0.55,0.18,1.6915,0.6655,0.402,0.5,11 +M,0.695,0.545,0.185,1.5715,0.6645,0.3835,0.4505,13 +F,0.7,0.575,0.205,1.773,0.605,0.447,0.538,13 +M,0.7,0.55,0.175,1.4405,0.6565,0.2985,0.375,12 +M,0.7,0.55,0.195,1.6245,0.675,0.347,0.535,13 +F,0.705,0.535,0.22,1.866,0.929,0.3835,0.4395,10 +F,0.72,0.575,0.18,1.6705,0.732,0.3605,0.501,12 +M,0.72,0.565,0.19,2.081,1.0815,0.4305,0.503,11 +F,0.725,0.57,0.205,1.6195,0.744,0.315,0.488,11 +F,0.75,0.55,0.195,1.8325,0.83,0.366,0.44,11 +M,0.76,0.605,0.215,2.173,0.801,0.4915,0.646,13 +I,0.135,0.13,0.04,0.029,0.0125,0.0065,0.008,4 +I,0.16,0.11,0.025,0.0195,0.0075,0.005,0.006,4 +I,0.21,0.15,0.055,0.0465,0.017,0.012,0.015,5 +I,0.28,0.21,0.075,0.1195,0.053,0.0265,0.03,6 +I,0.28,0.2,0.065,0.0895,0.036,0.0185,0.03,7 +I,0.285,0.215,0.06,0.0935,0.031,0.023,0.03,6 +I,0.29,0.21,0.07,0.1115,0.048,0.0205,0.03,5 +I,0.29,0.21,0.06,0.1195,0.056,0.0235,0.03,6 +I,0.29,0.21,0.065,0.097,0.0375,0.022,0.03,6 +I,0.32,0.24,0.07,0.133,0.0585,0.0255,0.041,6 +I,0.325,0.25,0.07,0.1745,0.0875,0.0355,0.04,7 +I,0.335,0.25,0.08,0.1695,0.0695,0.044,0.0495,6 +I,0.35,0.235,0.08,0.17,0.0725,0.0465,0.0495,7 +I,0.35,0.25,0.07,0.1605,0.0715,0.0335,0.046,6 +I,0.355,0.27,0.105,0.271,0.1425,0.0525,0.0735,9 +I,0.36,0.27,0.085,0.2185,0.1065,0.038,0.062,6 +I,0.36,0.27,0.085,0.196,0.0905,0.034,0.053,7 +I,0.375,0.28,0.08,0.226,0.105,0.047,0.065,6 +I,0.375,0.275,0.085,0.22,0.109,0.05,0.0605,7 +I,0.395,0.29,0.095,0.3,0.158,0.068,0.078,7 +I,0.405,0.25,0.09,0.2875,0.128,0.063,0.0805,7 +I,0.415,0.325,0.11,0.316,0.1385,0.0795,0.0925,8 +I,0.425,0.315,0.095,0.3675,0.1865,0.0675,0.0985,7 +I,0.43,0.32,0.11,0.3675,0.1675,0.102,0.105,8 +I,0.435,0.325,0.12,0.346,0.159,0.084,0.095,7 +M,0.45,0.33,0.105,0.4955,0.2575,0.082,0.129,8 +I,0.46,0.35,0.11,0.4675,0.2125,0.099,0.1375,7 +M,0.47,0.365,0.135,0.522,0.2395,0.1525,0.145,10 +I,0.47,0.375,0.105,0.441,0.167,0.0865,0.145,10 +I,0.475,0.365,0.12,0.5185,0.268,0.1095,0.1365,8 +M,0.505,0.39,0.12,0.653,0.3315,0.1385,0.167,9 +M,0.505,0.395,0.135,0.5915,0.288,0.1315,0.185,12 +M,0.505,0.385,0.115,0.4825,0.21,0.1035,0.1535,10 +I,0.51,0.455,0.135,0.6855,0.2875,0.154,0.2035,9 +M,0.515,0.4,0.14,0.6335,0.288,0.145,0.168,9 +M,0.525,0.41,0.13,0.6875,0.3435,0.1495,0.1765,9 +F,0.53,0.43,0.15,0.741,0.325,0.1855,0.196,9 +F,0.53,0.405,0.13,0.6355,0.2635,0.1565,0.185,9 +M,0.545,0.44,0.14,0.8395,0.356,0.1905,0.2385,11 +F,0.55,0.47,0.15,0.9205,0.381,0.2435,0.2675,10 +F,0.56,0.41,0.16,0.8215,0.342,0.184,0.253,9 +M,0.565,0.445,0.145,0.9255,0.4345,0.212,0.2475,9 +F,0.57,0.435,0.15,0.8295,0.3875,0.156,0.245,10 +M,0.58,0.46,0.16,1.063,0.513,0.2705,0.2625,9 +M,0.59,0.465,0.165,1.115,0.5165,0.273,0.275,10 +F,0.6,0.45,0.14,0.837,0.37,0.177,0.2425,10 +M,0.605,0.445,0.14,0.982,0.4295,0.2085,0.295,12 +M,0.61,0.49,0.16,1.112,0.465,0.228,0.341,10 +F,0.625,0.515,0.18,1.3485,0.5255,0.252,0.3925,14 +M,0.66,0.515,0.195,1.5655,0.7345,0.353,0.386,9 +I,0.255,0.19,0.06,0.086,0.04,0.0185,0.025,5 +I,0.27,0.195,0.065,0.1065,0.0475,0.0225,0.0285,5 +I,0.28,0.215,0.08,0.132,0.072,0.022,0.033,5 +I,0.285,0.215,0.07,0.1075,0.051,0.0225,0.027,6 +I,0.32,0.255,0.085,0.1745,0.072,0.033,0.057,8 +I,0.325,0.24,0.07,0.152,0.0565,0.0305,0.054,8 +I,0.385,0.28,0.1,0.2755,0.1305,0.061,0.0725,8 +I,0.395,0.295,0.1,0.293,0.14,0.062,0.082,7 +F,0.4,0.305,0.16,0.368,0.173,0.0705,0.105,7 +I,0.405,0.31,0.09,0.312,0.138,0.06,0.087,8 +I,0.415,0.305,0.12,0.336,0.165,0.076,0.0805,7 +I,0.42,0.315,0.115,0.355,0.1895,0.065,0.087,6 +I,0.44,0.305,0.115,0.379,0.162,0.091,0.11,9 +I,0.445,0.32,0.12,0.378,0.152,0.0825,0.12,8 +M,0.45,0.35,0.13,0.4655,0.2075,0.1045,0.135,8 +F,0.455,0.355,1.13,0.594,0.332,0.116,0.1335,8 +M,0.46,0.345,0.12,0.4935,0.2435,0.1175,0.132,8 +M,0.46,0.345,0.11,0.4595,0.235,0.0885,0.116,7 +M,0.465,0.36,0.11,0.4955,0.2665,0.085,0.121,7 +I,0.465,0.355,0.09,0.4325,0.2005,0.074,0.1275,9 +F,0.475,0.38,0.14,0.689,0.3165,0.1315,0.1955,7 +I,0.48,0.35,0.135,0.5465,0.2735,0.0995,0.158,8 +M,0.485,0.39,0.135,0.617,0.25,0.1345,0.1635,8 +I,0.49,0.37,0.11,0.538,0.271,0.1035,0.139,8 +M,0.5,0.39,0.135,0.7815,0.361,0.1575,0.2385,9 +F,0.5,0.38,0.14,0.6355,0.277,0.143,0.1785,8 +M,0.505,0.385,0.13,0.6435,0.3135,0.149,0.1515,7 +M,0.525,0.385,0.1,0.5115,0.246,0.1005,0.1455,8 +M,0.535,0.42,0.125,0.738,0.355,0.1895,0.1795,8 +F,0.535,0.42,0.13,0.699,0.3125,0.1565,0.2035,8 +F,0.54,0.385,0.14,0.7655,0.3265,0.116,0.2365,10 +F,0.54,0.42,0.13,0.7505,0.368,0.1675,0.1845,9 +F,0.545,0.43,0.16,0.844,0.3945,0.1855,0.231,9 +M,0.55,0.41,0.13,0.8705,0.4455,0.2115,0.213,9 +I,0.55,0.42,0.115,0.668,0.2925,0.137,0.209,11 +F,0.565,0.44,0.135,0.83,0.393,0.1735,0.238,9 +M,0.58,0.45,0.12,0.8685,0.418,0.1475,0.2605,8 +F,0.58,0.435,0.15,0.839,0.3485,0.207,0.192,7 +F,0.585,0.485,0.15,1.079,0.4145,0.2115,0.356,11 +M,0.595,0.465,0.15,0.919,0.4335,0.1765,0.262,9 +F,0.6,0.47,0.19,1.1345,0.492,0.2595,0.3375,10 +F,0.61,0.43,0.14,0.909,0.438,0.2,0.22,8 +M,0.61,0.48,0.165,1.2435,0.5575,0.2675,0.372,8 +F,0.62,0.49,0.16,1.056,0.493,0.244,0.2725,9 +M,0.645,0.495,0.15,1.2095,0.603,0.2225,0.339,9 +M,0.65,0.5,0.14,1.238,0.6165,0.2355,0.32,8 +F,0.665,0.525,0.21,1.644,0.818,0.3395,0.4275,10 +M,0.685,0.55,0.2,1.7725,0.813,0.387,0.49,11 +F,0.69,0.54,0.195,1.2525,0.73,0.3975,0.462,12 +F,0.705,0.57,0.185,1.761,0.747,0.3725,0.488,10 +F,0.71,0.5,0.15,1.3165,0.6835,0.2815,0.28,10 +M,0.72,0.585,0.22,1.914,0.9155,0.448,0.479,11 +F,0.72,0.575,0.215,2.1,0.8565,0.4825,0.602,12 +F,0.73,0.555,0.18,1.6895,0.6555,0.1965,0.4935,10 +M,0.775,0.57,0.22,2.032,0.735,0.4755,0.6585,17 +F,0.505,0.39,0.115,0.66,0.3045,0.1555,0.175,8 +M,0.53,0.425,0.13,0.7455,0.2995,0.1355,0.245,10 +F,0.505,0.385,0.115,0.616,0.243,0.1075,0.21,11 +I,0.405,0.305,0.09,0.2825,0.114,0.0575,0.095,7 +M,0.415,0.3,0.1,0.3355,0.1545,0.0685,0.095,7 +M,0.5,0.39,0.145,0.651,0.273,0.132,0.22,11 +M,0.425,0.33,0.08,0.361,0.134,0.0825,0.125,7 +M,0.47,0.35,0.1,0.4775,0.1885,0.0885,0.175,8 +F,0.4,0.31,0.115,0.3465,0.1475,0.0695,0.115,10 +I,0.37,0.29,0.1,0.25,0.1025,0.0505,0.085,10 +M,0.5,0.38,0.155,0.66,0.2655,0.1365,0.215,19 +I,0.41,0.31,0.11,0.315,0.124,0.082,0.095,9 +M,0.375,0.29,0.1,0.276,0.1175,0.0565,0.085,9 +F,0.49,0.385,0.125,0.5395,0.2175,0.128,0.165,11 +M,0.585,0.48,0.185,1.04,0.434,0.265,0.285,10 +M,0.595,0.455,0.155,1.041,0.416,0.2105,0.365,14 +F,0.675,0.55,0.18,1.6885,0.562,0.3705,0.6,15 +M,0.665,0.535,0.225,2.1835,0.7535,0.391,0.885,27 +M,0.62,0.49,0.17,1.2105,0.5185,0.2555,0.335,13 +I,0.325,0.25,0.055,0.166,0.076,0.051,0.045,5 +I,0.455,0.355,0.08,0.452,0.2165,0.0995,0.125,9 +M,0.525,0.405,0.13,0.7185,0.3265,0.1975,0.175,8 +I,0.385,0.29,0.09,0.232,0.0855,0.0495,0.08,7 +I,0.13,0.095,0.035,0.0105,0.005,0.0065,0.0035,4 +I,0.18,0.13,0.045,0.0275,0.0125,0.01,0.009,3 +I,0.31,0.225,0.05,0.1445,0.0675,0.0385,0.045,6 +F,0.375,0.29,0.08,0.282,0.1405,0.0725,0.08,7 +F,0.48,0.38,0.12,0.608,0.2705,0.1405,0.185,8 +I,0.455,0.37,0.125,0.433,0.201,0.1265,0.145,9 +M,0.425,0.325,0.1,0.3295,0.1365,0.0725,0.11,7 +I,0.475,0.36,0.11,0.4555,0.177,0.0965,0.145,9 +F,0.435,0.35,0.12,0.4585,0.192,0.1,0.13,11 +F,0.29,0.21,0.075,0.275,0.113,0.0675,0.035,6 +M,0.385,0.295,0.095,0.335,0.147,0.094,0.09,7 +M,0.47,0.375,0.115,0.4265,0.1685,0.0755,0.15,8 +F,0.5,0.4,0.125,0.5765,0.2395,0.126,0.185,10 +I,0.4,0.31,0.1,0.127,0.106,0.071,0.085,7 +M,0.62,0.51,0.175,1.1505,0.4375,0.2265,0.4,12 +M,0.595,0.47,0.15,0.8915,0.359,0.2105,0.245,12 +M,0.585,0.455,0.14,0.97,0.462,0.185,0.295,9 +M,0.32,0.24,0.08,0.18,0.08,0.0385,0.055,6 +F,0.52,0.41,0.125,0.6985,0.2945,0.1625,0.215,10 +M,0.44,0.35,0.11,0.4585,0.2,0.0885,0.13,9 +F,0.44,0.33,0.115,0.4005,0.143,0.113,0.12,8 +M,0.565,0.425,0.1,0.7145,0.3055,0.166,0.18,12 +F,0.56,0.425,0.125,0.932,0.361,0.213,0.335,9 +F,0.59,0.455,0.175,0.966,0.391,0.2455,0.31,10 +F,0.57,0.465,0.18,0.9995,0.405,0.277,0.295,16 +M,0.68,0.53,0.205,1.496,0.5825,0.337,0.465,14 +F,0.45,0.36,0.125,0.5065,0.222,0.105,0.16,10 +I,0.32,0.24,0.075,0.1735,0.076,0.0355,0.05,7 +I,0.46,0.35,0.11,0.3945,0.1685,0.0865,0.125,9 +M,0.47,0.37,0.105,0.4665,0.2025,0.1015,0.155,10 +M,0.455,0.35,0.105,0.401,0.1575,0.083,0.135,9 +F,0.415,0.325,0.115,0.3455,0.1405,0.0765,0.11,9 +M,0.465,0.35,0.12,0.5205,0.2015,0.1625,0.185,11 +M,0.46,0.375,0.135,0.4935,0.186,0.0845,0.17,12 +M,0.415,0.31,0.09,0.3245,0.1305,0.0735,0.115,8 +M,0.27,0.195,0.07,0.106,0.0465,0.018,0.036,7 +M,0.445,0.355,0.11,0.4415,0.1805,0.1035,0.1505,10 +F,0.745,0.585,0.19,1.966,0.8435,0.437,0.5855,18 +F,0.4,0.3,0.115,0.3025,0.1335,0.0465,0.0935,8 +I,0.28,0.2,0.075,0.1225,0.0545,0.0115,0.035,5 +M,0.55,0.44,0.135,0.879,0.368,0.2095,0.265,10 +M,0.58,0.46,0.165,1.2275,0.473,0.1965,0.435,16 +M,0.61,0.5,0.165,1.2715,0.4915,0.185,0.49,12 +M,0.62,0.495,0.175,1.806,0.643,0.3285,0.725,17 +M,0.56,0.42,0.195,0.8085,0.3025,0.1795,0.285,14 +F,0.64,0.51,0.2,1.3905,0.61,0.3315,0.41,12 +M,0.69,0.55,0.2,1.8465,0.732,0.472,0.57,19 +F,0.715,0.565,0.24,2.1995,0.7245,0.465,0.885,17 +F,0.71,0.565,0.195,1.817,0.785,0.492,0.49,11 +F,0.55,0.47,0.15,0.897,0.377,0.184,0.29,9 +M,0.375,0.305,0.09,0.3245,0.1395,0.0565,0.095,5 +F,0.61,0.45,0.16,1.136,0.414,0.311,0.3,9 +I,0.38,0.28,0.085,0.2735,0.115,0.061,0.085,6 +F,0.37,0.275,0.085,0.2405,0.104,0.0535,0.07,5 +M,0.335,0.235,0.085,0.1545,0.066,0.0345,0.045,6 +I,0.165,0.115,0.015,0.0145,0.0055,0.003,0.005,4 +M,0.285,0.21,0.075,0.1185,0.055,0.0285,0.04,7 +I,0.19,0.13,0.03,0.0295,0.0155,0.015,0.01,6 +I,0.215,0.15,0.03,0.0385,0.0115,0.005,0.01,5 +M,0.595,0.465,0.125,0.799,0.3245,0.2,0.23,10 +F,0.645,0.5,0.17,1.1845,0.4805,0.274,0.355,13 +M,0.575,0.45,0.185,0.925,0.342,0.197,0.35,12 +F,0.57,0.45,0.17,1.098,0.414,0.187,0.405,20 +F,0.58,0.45,0.235,1.071,0.3,0.206,0.395,14 +F,0.595,0.48,0.2,0.975,0.358,0.2035,0.34,15 +F,0.595,0.47,0.25,1.283,0.462,0.2475,0.445,14 +F,0.625,0.42,0.165,1.0595,0.358,0.165,0.445,21 +M,0.535,0.42,0.165,0.9195,0.3355,0.1985,0.26,16 +M,0.55,0.43,0.16,0.9295,0.317,0.1735,0.355,13 +M,0.495,0.4,0.155,0.8085,0.2345,0.1155,0.35,6 +I,0.32,0.235,0.08,0.1485,0.064,0.031,0.045,6 +M,0.445,0.34,0.12,0.4475,0.193,0.1035,0.13,9 +F,0.52,0.4,0.125,0.6865,0.295,0.1715,0.185,9 +M,0.495,0.385,0.135,0.6335,0.2,0.1225,0.26,14 +M,0.47,0.37,0.135,0.547,0.222,0.1325,0.17,12 +F,0.49,0.37,0.14,0.585,0.243,0.115,0.195,10 +M,0.58,0.47,0.165,0.927,0.3215,0.1985,0.315,11 +M,0.645,0.495,0.185,1.4935,0.5265,0.2785,0.455,15 +F,0.575,0.485,0.165,1.0405,0.419,0.264,0.3,14 +I,0.215,0.17,0.055,0.0605,0.0205,0.014,0.02,6 +I,0.43,0.325,0.11,0.3675,0.1355,0.0935,0.12,13 +I,0.26,0.215,0.08,0.099,0.037,0.0255,0.045,5 +I,0.37,0.28,0.09,0.233,0.0905,0.0545,0.07,11 +I,0.405,0.305,0.105,0.3625,0.1565,0.0705,0.125,10 +I,0.27,0.19,0.08,0.081,0.0265,0.0195,0.03,6 +F,0.68,0.55,0.2,1.596,0.525,0.4075,0.585,21 +F,0.65,0.515,0.195,1.4005,0.5195,0.36,0.44,13 +F,0.645,0.49,0.215,1.406,0.4265,0.2285,0.51,25 +M,0.57,0.405,0.16,0.9245,0.3445,0.2185,0.295,19 +M,0.615,0.48,0.19,1.36,0.5305,0.2375,0.47,18 +M,0.42,0.345,0.105,0.43,0.175,0.096,0.13,7 +I,0.275,0.22,0.08,0.1365,0.0565,0.0285,0.042,6 +F,0.29,0.225,0.075,0.14,0.0515,0.0235,0.04,5 +M,0.42,0.34,0.115,0.4215,0.175,0.093,0.135,8 +F,0.625,0.525,0.215,1.5765,0.5115,0.2595,0.665,16 +F,0.55,0.465,0.18,1.2125,0.3245,0.205,0.525,27 +M,0.66,0.505,0.2,1.6305,0.4865,0.297,0.61,18 +M,0.565,0.47,0.195,1.142,0.387,0.258,0.35,17 +F,0.595,0.495,0.235,1.366,0.5065,0.219,0.52,13 +M,0.63,0.51,0.23,1.539,0.5635,0.2815,0.57,17 +F,0.43,0.325,0.12,0.445,0.165,0.0995,0.155,8 +F,0.455,0.35,0.14,0.5725,0.1965,0.1325,0.175,10 +I,0.33,0.26,0.08,0.19,0.0765,0.0385,0.065,7 +F,0.515,0.415,0.13,0.764,0.276,0.196,0.25,13 +M,0.495,0.39,0.15,0.853,0.3285,0.189,0.27,14 +F,0.485,0.375,0.145,0.5885,0.2385,0.1155,0.19,13 +F,0.535,0.46,0.145,0.7875,0.3395,0.2005,0.2,8 +M,0.58,0.465,0.175,1.035,0.401,0.1865,0.385,17 +F,0.625,0.525,0.195,1.352,0.4505,0.2445,0.53,13 +F,0.555,0.455,0.18,0.958,0.296,0.195,0.39,14 +F,0.55,0.425,0.145,0.797,0.297,0.15,0.265,9 +M,0.59,0.475,0.155,0.857,0.356,0.174,0.28,13 +I,0.355,0.28,0.11,0.2235,0.0815,0.0525,0.08,7 +I,0.275,0.2,0.075,0.086,0.0305,0.019,0.03,7 +F,0.505,0.39,0.175,0.692,0.267,0.15,0.215,12 +M,0.37,0.28,0.095,0.2225,0.0805,0.051,0.075,7 +M,0.555,0.43,0.165,0.7575,0.2735,0.1635,0.275,13 +F,0.505,0.4,0.165,0.729,0.2675,0.155,0.25,9 +F,0.56,0.445,0.18,0.903,0.3575,0.2045,0.295,9 +M,0.595,0.475,0.17,1.0965,0.419,0.229,0.35,17 +F,0.57,0.45,0.165,0.903,0.3305,0.1845,0.295,14 +M,0.6,0.48,0.175,1.229,0.4125,0.2735,0.415,13 +F,0.56,0.435,0.185,1.106,0.422,0.2435,0.33,15 +M,0.585,0.465,0.19,1.171,0.3905,0.2355,0.4,17 +I,0.46,0.335,0.11,0.444,0.225,0.0745,0.11,8 +F,0.46,0.36,0.115,0.4755,0.2105,0.105,0.16,8 +M,0.415,0.315,0.125,0.388,0.068,0.09,0.125,12 +F,0.435,0.32,0.12,0.3785,0.152,0.0915,0.125,11 +F,0.475,0.38,0.135,0.486,0.1735,0.07,0.185,7 +M,0.465,0.36,0.13,0.5265,0.2105,0.1185,0.165,10 +I,0.355,0.28,0.1,0.2275,0.0935,0.0455,0.085,11 +M,0.46,0.375,0.14,0.5105,0.192,0.1045,0.205,9 +F,0.38,0.325,0.11,0.3105,0.12,0.074,0.105,10 +F,0.47,0.365,0.12,0.543,0.2295,0.1495,0.15,9 +M,0.36,0.27,0.09,0.2225,0.083,0.053,0.075,6 +F,0.585,0.455,0.165,0.998,0.345,0.2495,0.315,12 +M,0.655,0.59,0.2,1.5455,0.654,0.3765,0.415,11 +M,0.6,0.485,0.175,1.2675,0.4995,0.2815,0.38,13 +F,0.57,0.46,0.17,1.1,0.4125,0.2205,0.38,14 +F,0.645,0.5,0.2,1.4285,0.639,0.305,0.36,11 +M,0.65,0.495,0.18,1.793,0.8005,0.339,0.53,14 +M,0.51,0.395,0.145,0.6185,0.216,0.1385,0.24,12 +M,0.52,0.38,0.135,0.5825,0.2505,0.1565,0.175,8 +M,0.495,0.415,0.165,0.7485,0.264,0.134,0.285,13 +M,0.43,0.335,0.115,0.406,0.166,0.0935,0.135,8 +F,0.59,0.465,0.16,1.1005,0.506,0.2525,0.295,13 +M,0.55,0.46,0.175,0.869,0.3155,0.1825,0.32,10 +M,0.585,0.43,0.16,0.955,0.3625,0.176,0.27,11 +F,0.58,0.455,0.16,0.9215,0.312,0.196,0.3,17 +F,0.62,0.51,0.15,1.456,0.581,0.2875,0.32,13 +I,0.59,0.45,0.16,0.893,0.2745,0.2185,0.345,14 +F,0.72,0.575,0.215,2.226,0.8955,0.405,0.62,13 +F,0.635,0.51,0.175,1.2125,0.5735,0.261,0.36,14 +F,0.61,0.48,0.175,1.0675,0.391,0.216,0.42,15 +F,0.545,0.445,0.175,0.8525,0.3465,0.189,0.295,13 +M,0.57,0.45,0.16,0.8615,0.3725,0.2175,0.255,12 +F,0.6,0.475,0.18,1.162,0.511,0.2675,0.32,18 +F,0.52,0.41,0.17,0.8705,0.3735,0.219,0.25,14 +M,0.635,0.51,0.21,1.598,0.6535,0.2835,0.58,15 +F,0.67,0.52,0.15,1.406,0.519,0.348,0.37,13 +M,0.695,0.57,0.2,2.033,0.751,0.4255,0.685,15 +M,0.655,0.525,0.185,1.259,0.487,0.2215,0.445,20 +F,0.62,0.48,0.23,1.0935,0.403,0.245,0.355,14 +F,0.6,0.475,0.18,1.1805,0.4345,0.2475,0.425,19 +M,0.51,0.405,0.13,0.7175,0.3725,0.158,0.17,9 +M,0.525,0.405,0.135,0.7575,0.3305,0.216,0.195,10 +M,0.44,0.375,0.13,0.487,0.226,0.0965,0.155,9 +I,0.485,0.415,0.14,0.5705,0.25,0.134,0.185,8 +F,0.495,0.385,0.13,0.6905,0.3125,0.179,0.175,10 +I,0.435,0.345,0.12,0.4475,0.221,0.112,0.125,7 +I,0.405,0.315,0.105,0.347,0.1605,0.0785,0.1,9 +I,0.42,0.33,0.1,0.352,0.1635,0.089,0.1,9 +F,0.5,0.395,0.15,0.7145,0.3235,0.173,0.195,9 +F,0.385,0.305,0.105,0.3315,0.1365,0.0745,0.1,7 +I,0.33,0.265,0.09,0.18,0.068,0.036,0.06,6 +F,0.58,0.475,0.155,0.974,0.4305,0.23,0.285,10 +I,0.325,0.27,0.1,0.185,0.08,0.0435,0.065,6 +M,0.475,0.375,0.12,0.563,0.2525,0.1205,0.185,10 +F,0.38,0.3,0.09,0.3215,0.1545,0.075,0.095,9 +I,0.34,0.26,0.09,0.179,0.076,0.0525,0.055,6 +M,0.525,0.425,0.12,0.702,0.3335,0.1465,0.22,12 +F,0.52,0.415,0.145,0.8045,0.3325,0.1725,0.285,10 +F,0.535,0.45,0.135,0.8075,0.322,0.181,0.25,13 +M,0.475,0.36,0.12,0.578,0.2825,0.12,0.17,8 +I,0.415,0.325,0.1,0.385,0.167,0.08,0.125,7 +I,0.495,0.385,0.125,0.585,0.2755,0.1235,0.165,8 +F,0.48,0.405,0.13,0.6375,0.277,0.1445,0.21,10 +F,0.52,0.425,0.15,0.813,0.385,0.2015,0.23,10 +M,0.46,0.375,0.13,0.5735,0.2505,0.119,0.195,9 +F,0.58,0.455,0.12,0.94,0.399,0.257,0.265,11 +M,0.59,0.49,0.135,1.008,0.422,0.2245,0.285,11 +F,0.55,0.415,0.135,0.775,0.302,0.179,0.26,23 +F,0.65,0.5,0.165,1.1445,0.485,0.218,0.365,12 +F,0.465,0.375,0.135,0.6,0.2225,0.129,0.23,16 +M,0.455,0.355,0.13,0.515,0.2,0.1275,0.175,11 +M,0.47,0.375,0.13,0.5795,0.2145,0.164,0.195,13 +F,0.435,0.35,0.11,0.384,0.143,0.1005,0.125,13 +M,0.35,0.265,0.11,0.2965,0.1365,0.063,0.085,7 +I,0.315,0.24,0.07,0.137,0.0545,0.0315,0.04,8 +M,0.595,0.47,0.145,0.991,0.4035,0.1505,0.34,16 +F,0.58,0.475,0.135,0.925,0.391,0.165,0.275,14 +M,0.575,0.435,0.15,0.805,0.293,0.1625,0.27,17 +M,0.535,0.435,0.155,0.8915,0.3415,0.177,0.25,13 +M,0.515,0.42,0.14,0.769,0.2505,0.154,0.29,13 +F,0.505,0.385,0.135,0.6185,0.251,0.1175,0.2,12 +F,0.505,0.395,0.145,0.6515,0.2695,0.153,0.205,15 +I,0.4,0.31,0.1,0.2875,0.1145,0.0635,0.095,10 +M,0.49,0.395,0.135,0.5545,0.213,0.0925,0.215,14 +M,0.53,0.435,0.135,0.7365,0.3275,0.1315,0.22,12 +I,0.395,0.325,0.105,0.306,0.111,0.0735,0.095,8 +F,0.665,0.535,0.19,1.496,0.5775,0.2815,0.475,17 +F,0.415,0.305,0.105,0.3605,0.12,0.082,0.1,10 +M,0.43,0.345,0.115,0.3045,0.0925,0.055,0.12,11 +M,0.475,0.395,0.135,0.592,0.2465,0.1645,0.2,13 +F,0.525,0.425,0.145,0.7995,0.3345,0.209,0.24,15 +I,0.48,0.39,0.145,0.5825,0.2315,0.121,0.255,15 +I,0.42,0.345,0.115,0.3435,0.1515,0.0795,0.115,9 +M,0.59,0.46,0.155,0.906,0.327,0.1485,0.335,15 +F,0.515,0.42,0.135,0.6295,0.2815,0.127,0.215,9 +M,0.695,0.55,0.22,1.5515,0.566,0.3835,0.445,13 +F,0.8,0.63,0.195,2.526,0.933,0.59,0.62,23 +M,0.61,0.49,0.15,1.103,0.425,0.2025,0.36,23 +F,0.565,0.48,0.175,0.957,0.3885,0.215,0.275,18 +M,0.56,0.455,0.165,0.86,0.4015,0.1695,0.245,11 +M,0.655,0.485,0.195,1.62,0.6275,0.358,0.485,17 +M,0.64,0.52,0.2,1.407,0.566,0.304,0.455,17 +F,0.59,0.47,0.17,0.9,0.355,0.1905,0.25,11 +I,0.31,0.24,0.09,0.1455,0.0605,0.0315,0.045,7 +I,0.255,0.185,0.07,0.075,0.028,0.018,0.025,6 +I,0.17,0.125,0.055,0.0235,0.009,0.0055,0.008,6 +M,0.67,0.55,0.17,1.247,0.472,0.2455,0.4,21 +F,0.71,0.565,0.195,1.7265,0.638,0.3365,0.565,17 +F,0.56,0.43,0.125,0.8025,0.313,0.1715,0.263,13 +M,0.505,0.4,0.13,0.764,0.3035,0.189,0.2175,11 +M,0.525,0.43,0.165,0.8645,0.376,0.1945,0.2515,16 +F,0.45,0.36,0.105,0.4715,0.2035,0.0935,0.149,9 +F,0.515,0.435,0.17,0.631,0.2765,0.111,0.216,12 +M,0.59,0.475,0.16,0.9455,0.3815,0.184,0.27,19 +M,0.7,0.53,0.19,1.3185,0.548,0.233,0.42,18 +F,0.72,0.56,0.175,1.7265,0.637,0.3415,0.525,17 +M,0.635,0.495,0.15,1.081,0.4825,0.242,0.31,11 +M,0.555,0.44,0.135,0.9025,0.3805,0.2105,0.28,13 +M,0.575,0.47,0.15,1.1415,0.4515,0.204,0.4,13 +M,0.585,0.455,0.125,1.027,0.391,0.212,0.25,17 +F,0.61,0.485,0.21,1.3445,0.535,0.2205,0.515,20 +F,0.645,0.525,0.2,1.449,0.601,0.2565,0.505,13 +F,0.545,0.44,0.175,0.7745,0.2985,0.1875,0.265,11 +M,0.55,0.45,0.155,0.7895,0.343,0.159,0.25,12 +F,0.66,0.525,0.205,1.3665,0.5005,0.291,0.41,18 +M,0.57,0.475,0.195,1.0295,0.4635,0.1905,0.305,18 +F,0.6,0.47,0.2,1.031,0.392,0.2035,0.29,15 +F,0.63,0.505,0.165,1.065,0.4595,0.216,0.315,12 +M,0.695,0.57,0.23,1.885,0.8665,0.435,0.5,19 +M,0.65,0.545,0.16,1.2425,0.487,0.296,0.48,15 +F,0.72,0.595,0.225,1.969,0.8045,0.423,0.66,16 +I,0.56,0.44,0.17,0.9445,0.3545,0.2175,0.3,12 +I,0.42,0.325,0.115,0.354,0.1625,0.064,0.105,8 +M,0.18,0.125,0.05,0.023,0.0085,0.0055,0.01,3 +F,0.405,0.325,0.11,0.3575,0.145,0.0725,0.11,12 +F,0.5,0.405,0.15,0.5965,0.253,0.126,0.185,12 +I,0.435,0.335,0.11,0.383,0.1555,0.0675,0.135,12 +M,0.34,0.275,0.09,0.2065,0.0725,0.043,0.07,10 +F,0.43,0.34,0.11,0.382,0.154,0.0955,0.109,8 +I,0.535,0.41,0.155,0.6315,0.2745,0.1415,0.1815,12 +I,0.415,0.325,0.115,0.3285,0.1405,0.051,0.106,12 +F,0.36,0.265,0.09,0.2165,0.096,0.037,0.0735,10 +M,0.175,0.135,0.04,0.0305,0.011,0.0075,0.01,5 +M,0.155,0.115,0.025,0.024,0.009,0.005,0.0075,5 +I,0.525,0.43,0.15,0.7365,0.3225,0.161,0.215,11 +F,0.525,0.39,0.135,0.6005,0.2265,0.131,0.21,16 +F,0.44,0.345,0.105,0.4285,0.165,0.083,0.132,11 +F,0.45,0.345,0.115,0.496,0.1905,0.117,0.14,12 +F,0.485,0.365,0.14,0.6195,0.2595,0.1445,0.177,14 +I,0.47,0.35,0.135,0.567,0.2315,0.1465,0.1525,11 +I,0.515,0.375,0.14,0.6505,0.2495,0.141,0.2215,10 +M,0.42,0.34,0.125,0.4495,0.165,0.1125,0.144,11 +F,0.455,0.35,0.125,0.4485,0.1585,0.102,0.1335,16 +M,0.37,0.29,0.09,0.241,0.11,0.045,0.069,10 +M,0.33,0.25,0.09,0.197,0.085,0.041,0.0605,10 +I,0.3,0.22,0.09,0.1425,0.057,0.0335,0.043,7 +I,0.625,0.46,0.16,1.2395,0.55,0.273,0.38,14 +I,0.61,0.475,0.17,1.0385,0.4435,0.241,0.32,14 +I,0.625,0.465,0.155,0.972,0.404,0.1845,0.35,14 +I,0.635,0.505,0.19,1.3315,0.5805,0.252,0.435,17 +I,0.5,0.385,0.155,0.762,0.3795,0.161,0.19,14 +F,0.53,0.43,0.17,0.775,0.35,0.152,0.235,17 +I,0.445,0.33,0.1,0.437,0.163,0.0755,0.17,13 +F,0.585,0.415,0.155,0.6985,0.3,0.146,0.195,12 +I,0.44,0.355,0.165,0.435,0.159,0.105,0.14,16 +M,0.29,0.225,0.08,0.1295,0.0535,0.026,0.045,10 +I,0.555,0.455,0.17,0.8435,0.309,0.1905,0.3,15 +I,0.655,0.515,0.145,1.25,0.5265,0.283,0.315,15 +F,0.58,0.46,0.185,1.017,0.3515,0.2,0.32,10 +I,0.625,0.43,0.175,1.411,0.572,0.297,0.395,12 +I,0.62,0.485,0.17,1.208,0.4805,0.3045,0.33,15 +F,0.64,0.5,0.15,1.0705,0.371,0.2705,0.36,8 +F,0.505,0.375,0.115,0.5895,0.2635,0.12,0.167,10 +I,0.5,0.395,0.12,0.537,0.2165,0.1085,0.1785,9 +M,0.31,0.245,0.095,0.15,0.0525,0.034,0.048,7 +F,0.505,0.38,0.145,0.651,0.2935,0.19,0.17,12 +I,0.42,0.305,0.11,0.28,0.094,0.0785,0.0955,9 +M,0.4,0.315,0.105,0.287,0.1135,0.037,0.113,10 +M,0.425,0.315,0.125,0.3525,0.1135,0.0565,0.13,18 +M,0.31,0.235,0.06,0.12,0.0415,0.033,0.04,11 +F,0.465,0.35,0.13,0.494,0.1945,0.103,0.155,18 +F,0.465,0.36,0.12,0.4765,0.192,0.1125,0.16,10 +M,0.35,0.255,0.085,0.2145,0.1,0.0465,0.06,13 +I,0.52,0.415,0.16,0.595,0.2105,0.142,0.26,15 +F,0.475,0.365,0.13,0.4805,0.1905,0.114,0.1475,12 +F,0.41,0.315,0.11,0.321,0.1255,0.0655,0.095,10 +M,0.26,0.2,0.065,0.096,0.044,0.027,0.03,6 +I,0.575,0.45,0.17,0.9315,0.358,0.2145,0.26,13 +I,0.565,0.435,0.155,0.782,0.2715,0.168,0.285,14 +M,0.26,0.19,0.075,0.0945,0.0445,0.02,0.03,6 +F,0.53,0.385,0.125,0.6695,0.289,0.151,0.18,10 +M,0.34,0.255,0.095,0.213,0.081,0.034,0.07,9 +I,0.52,0.38,0.14,0.525,0.1775,0.115,0.185,11 +F,0.635,0.5,0.18,1.312,0.529,0.2485,0.485,18 +F,0.61,0.485,0.165,1.087,0.4255,0.232,0.38,11 +F,0.66,0.515,0.18,1.523,0.54,0.3365,0.555,16 +I,0.635,0.5,0.18,1.319,0.5485,0.292,0.49,16 +F,0.465,0.38,0.135,0.579,0.208,0.1095,0.22,14 +M,0.515,0.4,0.16,0.8175,0.2515,0.156,0.3,23 +I,0.335,0.24,0.095,0.17,0.062,0.039,0.055,9 +F,0.515,0.4,0.17,0.796,0.258,0.1755,0.28,16 +F,0.345,0.255,0.1,0.197,0.071,0.051,0.06,9 +M,0.465,0.355,0.125,0.5255,0.2025,0.135,0.145,13 +M,0.54,0.415,0.17,0.879,0.339,0.208,0.255,10 +M,0.475,0.355,0.125,0.4625,0.186,0.107,0.145,9 +F,0.445,0.335,0.14,0.4565,0.1785,0.114,0.14,11 +M,0.5,0.355,0.14,0.528,0.2125,0.149,0.14,9 +M,0.5,0.38,0.135,0.5835,0.2295,0.1265,0.18,12 +F,0.55,0.435,0.17,0.884,0.2875,0.1645,0.28,14 +I,0.275,0.205,0.08,0.096,0.036,0.0185,0.03,6 +F,0.35,0.265,0.09,0.1855,0.0745,0.0415,0.06,7 +F,0.37,0.285,0.105,0.27,0.1125,0.0585,0.0835,9 +F,0.42,0.33,0.125,0.463,0.186,0.11,0.145,10 +M,0.35,0.26,0.09,0.198,0.0725,0.056,0.06,10 +M,0.395,0.305,0.105,0.282,0.0975,0.065,0.096,9 +I,0.325,0.2,0.08,0.0995,0.0395,0.0225,0.032,8 +I,0.275,0.2,0.065,0.092,0.0385,0.0235,0.027,5 +I,0.235,0.17,0.065,0.0625,0.023,0.014,0.022,6 +I,0.25,0.18,0.06,0.073,0.028,0.017,0.0225,5 +I,0.25,0.185,0.065,0.071,0.027,0.0185,0.0225,5 +I,0.2,0.145,0.05,0.036,0.0125,0.008,0.011,4 +F,0.585,0.47,0.17,1.099,0.3975,0.2325,0.358,20 +M,0.445,0.35,0.14,0.5905,0.2025,0.158,0.19,14 +F,0.5,0.385,0.13,0.768,0.2625,0.095,0.27,13 +M,0.44,0.325,0.08,0.413,0.144,0.1015,0.13,8 +M,0.515,0.405,0.14,0.8505,0.312,0.146,0.315,17 +F,0.52,0.405,0.14,0.6915,0.276,0.137,0.215,11 +M,0.5,0.39,0.13,0.709,0.275,0.168,0.18,11 +M,0.425,0.325,0.12,0.3755,0.142,0.1065,0.105,9 +M,0.51,0.415,0.14,0.8185,0.3025,0.2155,0.235,16 +F,0.37,0.275,0.08,0.227,0.093,0.0625,0.07,8 +M,0.54,0.415,0.13,0.8245,0.272,0.226,0.24,13 +M,0.615,0.475,0.17,1.1825,0.474,0.2895,0.24,11 +M,0.565,0.44,0.175,1.122,0.393,0.2,0.375,20 +M,0.645,0.515,0.175,1.6115,0.6745,0.384,0.385,14 +F,0.615,0.47,0.175,1.2985,0.5135,0.343,0.32,14 +M,0.605,0.49,0.145,1.3,0.517,0.3285,0.31,14 +F,0.59,0.455,0.165,1.161,0.38,0.2455,0.28,12 +M,0.645,0.485,0.155,1.489,0.5915,0.312,0.38,18 +M,0.57,0.42,0.155,1.008,0.377,0.193,0.34,13 +F,0.47,0.355,0.18,0.441,0.1525,0.1165,0.135,8 +F,0.5,0.44,0.155,0.742,0.2025,0.2005,0.2115,14 +F,0.52,0.425,0.145,0.7,0.207,0.1905,0.24,13 +M,0.39,0.285,0.095,0.271,0.11,0.06,0.08,8 +M,0.52,0.4,0.165,0.8565,0.2745,0.201,0.21,12 +F,0.54,0.415,0.175,0.8975,0.275,0.241,0.275,14 +M,0.46,0.36,0.135,0.6105,0.1955,0.107,0.235,14 +I,0.355,0.26,0.09,0.1925,0.077,0.038,0.065,8 +F,0.49,0.4,0.145,0.6635,0.21,0.1295,0.2515,13 +F,0.63,0.51,0.185,1.235,0.5115,0.349,0.3065,11 +M,0.5,0.385,0.145,0.7615,0.246,0.195,0.204,14 +M,0.49,0.39,0.135,0.592,0.242,0.096,0.1835,15 +M,0.44,0.325,0.115,0.39,0.163,0.087,0.113,7 +F,0.515,0.395,0.165,0.7565,0.1905,0.17,0.3205,10 +F,0.475,0.38,0.145,0.57,0.167,0.118,0.187,11 +I,0.42,0.31,0.1,0.2865,0.115,0.0735,0.085,8 +M,0.4,0.305,0.13,0.2935,0.096,0.0675,0.105,9 +M,0.45,0.36,0.16,0.567,0.174,0.1245,0.225,12 +F,0.52,0.4,0.13,0.6245,0.215,0.2065,0.17,15 +M,0.505,0.4,0.155,0.8415,0.2715,0.1775,0.285,12 +M,0.495,0.4,0.14,0.7775,0.2015,0.18,0.25,15 +M,0.54,0.41,0.145,0.989,0.2815,0.213,0.355,19 +F,0.48,0.39,0.125,0.6905,0.219,0.155,0.2,12 +F,0.33,0.26,0.08,0.2,0.0625,0.05,0.07,9 +I,0.285,0.21,0.07,0.109,0.044,0.0265,0.033,5 +I,0.3,0.23,0.075,0.127,0.052,0.03,0.0345,6 +I,0.31,0.24,0.105,0.2885,0.118,0.065,0.083,6 +I,0.34,0.255,0.075,0.18,0.0745,0.04,0.0525,6 +I,0.375,0.3,0.075,0.144,0.059,0.03,0.044,7 +I,0.415,0.325,0.1,0.4665,0.2285,0.1065,0.114,7 +I,0.415,0.315,0.105,0.33,0.1405,0.0705,0.095,6 +I,0.415,0.315,0.09,0.3625,0.175,0.0835,0.093,6 +I,0.42,0.32,0.1,0.34,0.1745,0.05,0.0945,8 +I,0.425,0.31,0.105,0.365,0.159,0.0825,0.105,6 +M,0.465,0.375,0.11,0.5,0.21,0.113,0.1505,8 +F,0.465,0.35,0.135,0.6265,0.259,0.1445,0.175,8 +I,0.47,0.37,0.11,0.5555,0.25,0.115,0.163,8 +F,0.47,0.375,0.12,0.6015,0.2765,0.1455,0.135,8 +I,0.475,0.365,0.12,0.53,0.2505,0.0975,0.1625,10 +M,0.48,0.37,0.135,0.6315,0.3445,0.1015,0.161,7 +M,0.5,0.4,0.13,0.7715,0.37,0.16,0.211,8 +I,0.505,0.39,0.185,0.6125,0.267,0.142,0.172,7 +M,0.525,0.425,0.19,0.872,0.4625,0.1725,0.199,9 +M,0.54,0.42,0.12,0.8115,0.392,0.1455,0.2235,9 +M,0.545,0.45,0.15,0.8795,0.387,0.15,0.2625,11 +F,0.565,0.44,0.15,0.983,0.4475,0.2355,0.2485,9 +M,0.58,0.46,0.18,1.145,0.48,0.277,0.325,11 +M,0.59,0.455,0.16,1.09,0.5,0.2215,0.292,9 +M,0.59,0.48,0.16,1.262,0.5685,0.2725,0.335,9 +M,0.595,0.49,0.185,1.185,0.482,0.2015,0.361,10 +F,0.6,0.475,0.135,1.4405,0.5885,0.191,0.3175,9 +F,0.6,0.5,0.155,1.332,0.6235,0.2835,0.35,8 +F,0.6,0.485,0.165,1.1405,0.587,0.2175,0.288,9 +M,0.605,0.475,0.175,1.201,0.5395,0.275,0.309,10 +F,0.625,0.49,0.155,1.33,0.6675,0.259,0.33,10 +M,0.63,0.5,0.185,1.362,0.5785,0.3125,0.384,10 +M,0.64,0.585,0.195,1.647,0.7225,0.331,0.471,12 +F,0.64,0.5,0.18,1.4995,0.593,0.314,0.431,11 +F,0.655,0.545,0.165,1.6225,0.6555,0.299,0.513,12 +I,0.66,0.525,0.215,1.786,0.6725,0.3615,0.4065,11 +M,0.66,0.535,0.2,1.791,0.733,0.318,0.54,15 +F,0.675,0.555,0.205,1.925,0.713,0.358,0.4535,13 +F,0.675,0.55,0.175,1.689,0.694,0.371,0.474,13 +F,0.69,0.55,0.18,1.659,0.8715,0.2655,0.4395,9 +F,0.695,0.53,0.2,2.0475,0.75,0.4195,0.6095,14 +F,0.7,0.525,0.19,1.6015,0.707,0.365,0.43,10 +F,0.73,0.57,0.165,2.0165,1.0685,0.418,0.435,10 +I,0.205,0.15,0.065,0.04,0.02,0.011,0.013,4 +I,0.225,0.17,0.07,0.0565,0.024,0.013,0.016,4 +I,0.23,0.18,0.05,0.064,0.0215,0.0135,0.02,5 +I,0.275,0.195,0.07,0.0875,0.0345,0.022,0.0255,4 +I,0.28,0.21,0.055,0.106,0.0415,0.0265,0.031,5 +I,0.28,0.22,0.08,0.1315,0.066,0.024,0.03,5 +I,0.295,0.22,0.07,0.126,0.0515,0.0275,0.035,6 +I,0.31,0.225,0.075,0.155,0.065,0.037,0.0365,6 +I,0.315,0.235,0.07,0.149,0.058,0.0325,0.047,7 +I,0.34,0.265,0.07,0.185,0.0625,0.0395,0.07,7 +I,0.37,0.29,0.08,0.2545,0.108,0.0565,0.07,6 +I,0.38,0.285,0.085,0.237,0.115,0.0405,0.07,6 +I,0.39,0.295,0.1,0.279,0.1155,0.059,0.08,7 +I,0.405,0.31,0.065,0.3205,0.1575,0.066,0.088,6 +I,0.415,0.325,0.1,0.3335,0.1445,0.0715,0.095,7 +I,0.44,0.335,0.11,0.3885,0.175,0.0835,0.111,7 +I,0.44,0.345,0.115,0.545,0.269,0.111,0.1305,6 +I,0.44,0.325,0.1,0.4165,0.185,0.0865,0.11,6 +I,0.44,0.355,0.12,0.495,0.231,0.11,0.125,7 +I,0.45,0.35,0.125,0.4775,0.2235,0.089,0.118,6 +I,0.45,0.35,0.12,0.468,0.2005,0.1065,0.1325,8 +F,0.455,0.35,0.12,0.4555,0.1945,0.1045,0.1375,7 +F,0.46,0.35,0.115,0.46,0.2025,0.1115,0.1165,6 +I,0.46,0.345,0.12,0.4155,0.198,0.0885,0.107,7 +I,0.46,0.345,0.115,0.4215,0.1895,0.102,0.111,6 +I,0.465,0.355,0.11,0.474,0.23,0.1005,0.12,7 +M,0.465,0.34,0.105,0.486,0.231,0.1035,0.1225,9 +I,0.475,0.385,0.11,0.5735,0.311,0.1025,0.136,7 +I,0.475,0.355,0.105,0.468,0.201,0.1115,0.12,8 +M,0.48,0.37,0.1,0.5135,0.243,0.1015,0.135,8 +M,0.5,0.375,0.145,0.6215,0.274,0.166,0.1485,7 +I,0.5,0.38,0.11,0.494,0.218,0.09,0.1325,7 +I,0.505,0.385,0.12,0.6005,0.239,0.142,0.185,7 +M,0.515,0.395,0.12,0.646,0.285,0.1365,0.172,9 +M,0.525,0.415,0.135,0.7945,0.394,0.189,0.202,7 +M,0.525,0.425,0.125,0.812,0.4035,0.1705,0.195,8 +F,0.53,0.42,0.17,0.828,0.41,0.208,0.1505,6 +M,0.53,0.41,0.14,0.681,0.3095,0.1415,0.1835,6 +F,0.53,0.405,0.15,0.889,0.4055,0.2275,0.215,8 +M,0.54,0.435,0.14,0.7345,0.33,0.1595,0.213,9 +F,0.55,0.425,0.125,0.964,0.5475,0.159,0.215,8 +F,0.555,0.425,0.14,0.963,0.44,0.224,0.24,7 +F,0.57,0.445,0.15,0.995,0.504,0.185,0.2505,9 +F,0.57,0.435,0.14,0.8585,0.3905,0.196,0.2295,8 +M,0.575,0.45,0.155,0.948,0.429,0.206,0.259,7 +F,0.58,0.445,0.145,0.888,0.41,0.1815,0.2425,8 +F,0.585,0.45,0.16,0.9045,0.405,0.2215,0.2335,8 +M,0.59,0.465,0.14,1.046,0.4695,0.263,0.263,7 +F,0.595,0.47,0.155,1.1775,0.542,0.269,0.31,9 +F,0.595,0.465,0.15,1.0765,0.491,0.22,0.287,9 +F,0.595,0.465,0.15,1.0255,0.412,0.2745,0.289,11 +F,0.6,0.46,0.145,0.9325,0.3985,0.2245,0.248,8 +F,0.6,0.46,0.15,1.235,0.6025,0.274,0.29,8 +M,0.6,0.46,0.15,1.247,0.5335,0.2735,0.29,9 +M,0.61,0.48,0.15,1.1495,0.564,0.274,0.264,8 +F,0.615,0.485,0.16,1.1575,0.5005,0.2495,0.315,10 +F,0.615,0.5,0.165,1.327,0.6,0.3015,0.355,10 +M,0.615,0.47,0.155,1.2,0.5085,0.32,0.292,8 +F,0.62,0.51,0.175,1.2705,0.5415,0.323,0.3225,9 +F,0.62,0.485,0.175,1.2155,0.545,0.253,0.345,10 +F,0.62,0.475,0.16,1.3245,0.6865,0.233,0.3275,9 +M,0.625,0.48,0.17,1.3555,0.671,0.268,0.3385,10 +F,0.625,0.49,0.165,1.127,0.477,0.2365,0.3185,9 +F,0.625,0.49,0.175,1.1075,0.4485,0.2165,0.3595,8 +F,0.63,0.495,0.2,1.4255,0.659,0.336,0.38,11 +F,0.63,0.495,0.145,1.147,0.5455,0.266,0.2885,9 +M,0.63,0.48,0.165,1.286,0.604,0.271,0.35,8 +F,0.635,0.495,0.18,1.596,0.617,0.317,0.37,11 +F,0.635,0.495,0.195,1.297,0.556,0.2985,0.37,11 +M,0.645,0.49,0.16,1.251,0.5355,0.3345,0.3165,9 +M,0.645,0.5,0.175,1.5105,0.6735,0.3755,0.3775,12 +F,0.65,0.5,0.185,1.4415,0.741,0.2955,0.341,9 +M,0.67,0.52,0.19,1.6385,0.8115,0.369,0.391,9 +F,0.69,0.545,0.205,1.933,0.7855,0.429,0.498,13 +M,0.69,0.54,0.185,1.71,0.7725,0.3855,0.4325,8 +F,0.695,0.55,0.155,1.8495,0.767,0.442,0.4175,10 +M,0.695,0.525,0.175,1.742,0.696,0.389,0.505,12 +F,0.7,0.575,0.205,1.7975,0.7295,0.3935,0.5165,13 +F,0.705,0.56,0.205,2.381,0.9915,0.5005,0.624,10 +M,0.765,0.585,0.18,2.398,1.128,0.512,0.5335,12 +M,0.77,0.6,0.215,2.1945,1.0515,0.482,0.584,10 +I,0.22,0.16,0.05,0.049,0.0215,0.01,0.015,4 +I,0.275,0.205,0.07,0.1055,0.495,0.019,0.0315,5 +I,0.29,0.21,0.06,0.1045,0.0415,0.022,0.035,5 +I,0.33,0.24,0.075,0.163,0.0745,0.033,0.048,6 +I,0.355,0.285,0.095,0.2275,0.0955,0.0475,0.0715,6 +I,0.375,0.29,0.1,0.219,0.0925,0.038,0.075,6 +I,0.415,0.315,0.1,0.3645,0.1765,0.0795,0.095,8 +I,0.425,0.33,0.115,0.3265,0.1315,0.077,0.103,6 +I,0.425,0.34,0.1,0.3515,0.1625,0.082,0.094,7 +I,0.43,0.32,0.1,0.3465,0.1635,0.08,0.09,7 +I,0.44,0.34,0.1,0.407,0.209,0.0735,0.103,7 +I,0.44,0.335,0.115,0.4215,0.173,0.0765,0.113,7 +I,0.46,0.345,0.11,0.3755,0.1525,0.058,0.125,7 +I,0.46,0.37,0.12,0.5335,0.2645,0.108,0.1345,6 +I,0.465,0.355,0.105,0.442,0.2085,0.0975,0.1185,7 +I,0.475,0.365,0.1,0.1315,0.2025,0.0875,0.123,7 +I,0.475,0.375,0.115,0.5205,0.233,0.119,0.1455,7 +I,0.485,0.375,0.13,0.5535,0.266,0.112,0.157,8 +I,0.49,0.375,0.125,0.5445,0.279,0.115,0.13,8 +M,0.49,0.38,0.11,0.554,0.2935,0.1005,0.15,8 +I,0.495,0.38,0.12,0.512,0.233,0.1205,0.136,7 +I,0.5,0.39,0.125,0.583,0.294,0.132,0.1605,8 +M,0.5,0.38,0.12,0.5765,0.273,0.135,0.145,9 +M,0.505,0.4,0.135,0.723,0.377,0.149,0.178,7 +I,0.51,0.395,0.155,0.5395,0.2465,0.1085,0.167,8 +I,0.51,0.385,0.15,0.625,0.3095,0.119,0.1725,8 +I,0.515,0.4,0.125,0.5925,0.265,0.1175,0.168,9 +I,0.52,0.395,0.135,0.633,0.2985,0.1295,0.175,9 +F,0.545,0.43,0.14,0.832,0.4355,0.17,0.201,9 +M,0.545,0.42,0.145,0.778,0.3745,0.1545,0.205,7 +M,0.545,0.42,0.12,0.7865,0.403,0.185,0.17,7 +F,0.545,0.4,0.14,0.778,0.368,0.215,0.18,9 +I,0.55,0.42,0.13,0.636,0.294,0.144,0.1755,8 +F,0.55,0.44,0.135,0.8435,0.434,0.1995,0.185,8 +I,0.555,0.425,0.13,0.648,0.2835,0.133,0.2105,8 +M,0.565,0.43,0.13,0.784,0.3495,0.1885,0.213,9 +F,0.57,0.45,0.18,0.908,0.4015,0.217,0.255,9 +M,0.57,0.45,0.135,1.02,0.546,0.204,0.25,9 +F,0.57,0.43,0.16,0.811,0.3875,0.159,0.2285,9 +F,0.575,0.48,0.15,0.897,0.4235,0.1905,0.248,8 +M,0.58,0.455,0.13,0.852,0.41,0.1725,0.225,8 +F,0.585,0.45,0.15,0.938,0.467,0.203,0.225,7 +F,0.585,0.435,0.14,0.6955,0.3085,0.129,0.2245,8 +M,0.59,0.47,0.15,0.861,0.413,0.164,0.249,8 +M,0.59,0.46,0.14,1.004,0.496,0.2165,0.26,9 +F,0.59,0.46,0.16,1.0115,0.445,0.2615,0.2565,8 +F,0.595,0.465,0.15,1.1005,0.5415,0.166,0.265,8 +M,0.595,0.47,0.165,1.108,0.4915,0.2325,0.3345,9 +M,0.595,0.46,0.14,0.852,0.4215,0.2255,0.227,9 +M,0.6,0.49,0.21,1.9875,1.005,0.419,0.491,10 +F,0.605,0.48,0.15,1.079,0.4505,0.2835,0.293,10 +F,0.615,0.475,0.17,1.055,0.543,0.246,0.2345,9 +M,0.615,0.45,0.15,1.198,0.707,0.2095,0.2505,7 +F,0.615,0.47,0.155,1.084,0.5885,0.209,0.246,9 +M,0.615,0.475,0.175,1.103,0.4635,0.3095,0.2725,10 +M,0.62,0.49,0.155,1.1,0.505,0.2475,0.31,9 +M,0.62,0.48,0.15,1.1015,0.4965,0.243,0.305,10 +M,0.625,0.495,0.185,1.3835,0.7105,0.3005,0.345,11 +F,0.625,0.49,0.155,1.115,0.484,0.277,0.3095,9 +M,0.625,0.48,0.145,1.085,0.4645,0.2445,0.327,10 +M,0.63,0.505,0.15,1.3165,0.6325,0.2465,0.37,11 +M,0.63,0.51,0.175,1.3415,0.6575,0.262,0.375,10 +M,0.63,0.465,0.15,1.027,0.537,0.188,0.176,8 +M,0.645,0.515,0.16,1.1845,0.506,0.311,0.335,9 +M,0.645,0.48,0.15,1.192,0.6055,0.2595,0.285,9 +F,0.645,0.52,0.18,1.285,0.5775,0.352,0.317,9 +M,0.65,0.515,0.125,1.1805,0.5235,0.283,0.3275,9 +M,0.65,0.52,0.175,1.2655,0.615,0.2775,0.336,9 +F,0.65,0.535,0.175,1.2895,0.6095,0.2765,0.344,10 +M,0.65,0.51,0.155,1.407,0.7215,0.298,0.335,9 +F,0.65,0.49,0.155,1.122,0.545,0.228,0.3055,9 +M,0.66,0.515,0.165,1.4465,0.694,0.298,0.3755,10 +F,0.665,0.505,0.165,1.349,0.5985,0.3175,0.36,9 +M,0.67,0.5,0.2,1.269,0.576,0.2985,0.351,11 +M,0.67,0.51,0.18,1.68,0.926,0.2975,0.3935,13 +F,0.675,0.55,0.19,1.551,0.7105,0.3685,0.412,13 +M,0.68,0.52,0.165,1.4775,0.724,0.279,0.406,11 +M,0.68,0.53,0.18,1.529,0.7635,0.3115,0.4025,11 +M,0.7,0.525,0.175,1.7585,0.8745,0.3615,0.47,10 +M,0.7,0.55,0.2,1.523,0.693,0.306,0.4405,13 +F,0.725,0.53,0.19,1.7315,0.83,0.398,0.405,11 +M,0.725,0.55,0.2,1.51,0.8735,0.4265,0.5085,9 +M,0.735,0.57,0.175,1.88,0.9095,0.387,0.488,11 +F,0.74,0.575,0.22,2.012,0.8915,0.5265,0.471,12 +M,0.75,0.555,0.215,2.201,1.0615,0.5235,0.5285,11 +I,0.19,0.14,0.03,0.0315,0.0125,0.005,0.0105,3 +I,0.21,0.15,0.045,0.04,0.0135,0.008,0.0105,4 +I,0.25,0.175,0.06,0.0635,0.0275,0.008,0.02,4 +I,0.29,0.215,0.065,0.0985,0.0425,0.021,0.031,5 +I,0.335,0.25,0.08,0.167,0.0675,0.0325,0.0575,6 +I,0.34,0.245,0.085,0.2015,0.1005,0.038,0.053,6 +I,0.345,0.255,0.095,0.183,0.075,0.0385,0.06,6 +I,0.355,0.255,0.08,0.187,0.078,0.0505,0.058,7 +I,0.36,0.26,0.08,0.1795,0.074,0.0315,0.06,5 +I,0.37,0.275,0.09,0.2065,0.096,0.0395,0.058,7 +I,0.375,0.29,0.14,0.3,0.14,0.0625,0.0825,8 +I,0.375,0.275,0.095,0.2295,0.095,0.0545,0.066,7 +I,0.385,0.3,0.125,0.343,0.1705,0.0735,0.081,7 +I,0.385,0.285,0.085,0.244,0.1215,0.0445,0.068,8 +I,0.395,0.32,0.1,0.3075,0.149,0.0535,0.09,8 +I,0.4,0.305,0.1,0.3415,0.176,0.0625,0.0865,7 +I,0.405,0.305,0.1,0.271,0.0965,0.061,0.091,7 +I,0.405,0.31,0.11,0.91,0.416,0.2075,0.0995,8 +I,0.405,0.305,0.1,0.268,0.1145,0.053,0.085,7 +I,0.405,0.3,0.09,0.2885,0.138,0.0635,0.0765,6 +I,0.41,0.315,0.1,0.3,0.124,0.0575,0.1,8 +I,0.41,0.325,0.11,0.326,0.1325,0.075,0.101,8 +I,0.415,0.335,0.1,0.358,0.169,0.067,0.105,7 +I,0.42,0.325,0.115,0.314,0.1295,0.0635,0.1,8 +I,0.42,0.315,0.11,0.4025,0.1855,0.083,0.1015,8 +I,0.43,0.34,0.11,0.3645,0.159,0.0855,0.105,7 +I,0.445,0.36,0.11,0.4235,0.182,0.0765,0.14,9 +M,0.45,0.325,0.115,0.4305,0.2235,0.0785,0.1155,8 +I,0.45,0.335,0.095,0.3505,0.1615,0.0625,0.1185,7 +I,0.455,0.34,0.115,0.486,0.261,0.0655,0.1315,8 +I,0.46,0.35,0.1,0.471,0.252,0.077,0.123,8 +I,0.46,0.345,0.105,0.415,0.187,0.087,0.11,8 +I,0.475,0.355,0.115,0.5195,0.279,0.088,0.1325,7 +M,0.48,0.375,0.12,0.5895,0.2535,0.128,0.172,11 +I,0.485,0.38,0.125,0.5215,0.2215,0.118,0.16,8 +I,0.485,0.365,0.14,0.4475,0.1895,0.0925,0.2305,8 +I,0.49,0.365,0.125,0.5585,0.252,0.126,0.1615,10 +I,0.505,0.385,0.125,0.596,0.245,0.097,0.21,9 +I,0.505,0.38,0.135,0.5385,0.2645,0.095,0.165,9 +I,0.51,0.385,0.145,0.7665,0.3985,0.14,0.1805,8 +F,0.515,0.395,0.135,0.516,0.2015,0.132,0.162,9 +M,0.515,0.41,0.14,0.7355,0.3065,0.137,0.2,7 +I,0.515,0.39,0.11,0.531,0.2415,0.098,0.1615,8 +I,0.525,0.385,0.13,0.607,0.2355,0.125,0.195,8 +F,0.525,0.415,0.15,0.7055,0.329,0.147,0.199,10 +I,0.525,0.4,0.13,0.6445,0.345,0.1285,0.2,8 +I,0.525,0.375,0.12,0.6315,0.3045,0.114,0.19,9 +M,0.535,0.43,0.155,0.7845,0.3285,0.169,0.245,10 +F,0.545,0.44,0.15,0.9475,0.366,0.239,0.275,8 +I,0.55,0.43,0.145,0.712,0.3025,0.152,0.225,10 +I,0.55,0.425,0.145,0.89,0.4325,0.171,0.236,10 +I,0.55,0.42,0.155,0.912,0.495,0.1805,0.205,9 +I,0.55,0.425,0.135,0.656,0.257,0.17,0.203,10 +I,0.55,0.465,0.15,0.936,0.481,0.174,0.2435,9 +I,0.555,0.435,0.145,0.6975,0.262,0.1575,0.24,11 +F,0.555,0.445,0.175,1.1465,0.551,0.244,0.2785,8 +I,0.56,0.44,0.14,0.825,0.402,0.139,0.245,10 +I,0.56,0.435,0.135,0.72,0.329,0.103,0.251,11 +I,0.565,0.43,0.15,0.8215,0.332,0.1685,0.29,11 +F,0.57,0.445,0.155,1.017,0.5265,0.2025,0.265,10 +F,0.575,0.435,0.155,0.8975,0.4115,0.2325,0.23,9 +M,0.58,0.44,0.175,1.2255,0.5405,0.2705,0.3265,10 +F,0.58,0.465,0.145,0.9865,0.47,0.2155,0.25,11 +F,0.58,0.425,0.15,0.844,0.3645,0.185,0.2705,9 +I,0.585,0.46,0.145,0.8465,0.339,0.167,0.295,10 +M,0.585,0.465,0.165,0.885,0.4025,0.1625,0.274,10 +I,0.585,0.42,0.145,0.6735,0.2895,0.1345,0.22,9 +F,0.585,0.455,0.13,0.8755,0.411,0.2065,0.225,8 +M,0.59,0.47,0.145,0.9235,0.4545,0.173,0.254,9 +M,0.59,0.475,0.14,0.977,0.4625,0.2025,0.275,10 +M,0.595,0.475,0.14,1.0305,0.4925,0.217,0.278,10 +M,0.6,0.48,0.09,1.05,0.457,0.2685,0.28,8 +M,0.6,0.495,0.185,1.1145,0.5055,0.2635,0.367,11 +M,0.6,0.45,0.145,0.877,0.4325,0.155,0.24,9 +M,0.6,0.51,0.185,1.285,0.6095,0.2745,0.315,9 +M,0.61,0.48,0.185,1.3065,0.6895,0.2915,0.29,10 +F,0.61,0.45,0.13,0.8725,0.389,0.1715,0.272,11 +F,0.615,0.46,0.15,1.0265,0.4935,0.201,0.2745,10 +F,0.62,0.465,0.14,1.1605,0.6005,0.2195,0.307,9 +F,0.62,0.48,0.165,1.0125,0.5325,0.4365,0.324,10 +M,0.625,0.5,0.14,1.096,0.5445,0.2165,0.295,10 +M,0.625,0.49,0.165,1.205,0.5175,0.3105,0.3465,10 +M,0.63,0.505,0.175,1.221,0.555,0.252,0.34,12 +F,0.63,0.475,0.155,1.0005,0.452,0.252,0.265,10 +M,0.63,0.47,0.15,1.1355,0.539,0.2325,0.3115,12 +M,0.63,0.525,0.195,1.3135,0.4935,0.2565,0.465,10 +M,0.64,0.505,0.155,1.1955,0.5565,0.211,0.346,11 +M,0.64,0.485,0.15,1.098,0.5195,0.222,0.3175,10 +M,0.64,0.495,0.17,1.139,0.5395,0.282,0.285,10 +F,0.64,0.495,0.17,1.2265,0.49,0.377,0.2875,11 +M,0.64,0.515,0.08,1.042,0.515,0.1755,0.175,10 +M,0.65,0.52,0.155,1.368,0.6185,0.288,0.365,9 +M,0.65,0.51,0.175,1.446,0.6485,0.2705,0.45,12 +F,0.66,0.505,0.19,1.4045,0.6255,0.3375,0.3745,9 +F,0.66,0.525,0.2,1.463,0.6525,0.2995,0.422,11 +F,0.675,0.525,0.17,1.711,0.8365,0.352,0.475,9 +M,0.7,0.54,0.205,1.74,0.7885,0.373,0.4865,13 +F,0.705,0.54,0.205,1.757,0.8265,0.417,0.461,9 +M,0.71,0.565,0.2,1.601,0.706,0.321,0.45,11 +M,0.72,0.55,0.205,2.165,1.1055,0.525,0.404,10 +M,0.725,0.57,0.19,2.3305,1.253,0.541,0.52,9 +I,0.24,0.17,0.05,0.0545,0.0205,0.016,0.0155,5 +I,0.255,0.195,0.055,0.0725,0.0285,0.017,0.021,4 +I,0.275,0.2,0.055,0.0925,0.038,0.021,0.026,4 +I,0.32,0.235,0.09,0.183,0.098,0.0335,0.042,7 +I,0.325,0.24,0.075,0.1525,0.072,0.0645,0.043,6 +I,0.33,0.225,0.075,0.187,0.0945,0.0395,0.0425,7 +I,0.36,0.27,0.09,0.232,0.12,0.0435,0.056,8 +I,0.375,0.265,0.095,0.196,0.085,0.042,0.0585,5 +I,0.375,0.285,0.09,0.2545,0.119,0.0595,0.0675,6 +I,0.39,0.29,0.09,0.2625,0.117,0.054,0.077,7 +I,0.45,0.335,0.105,0.362,0.1575,0.0795,0.1095,7 +I,0.455,0.35,0.105,0.4445,0.213,0.107,0.1115,7 +I,0.46,0.365,0.115,0.511,0.2365,0.118,0.123,7 +I,0.495,0.375,0.12,0.589,0.3075,0.1215,0.1405,8 +M,0.5,0.365,0.13,0.5945,0.309,0.1085,0.1535,9 +I,0.5,0.375,0.12,0.529,0.2235,0.123,0.16,8 +M,0.52,0.4,0.105,0.872,0.4515,0.1615,0.1985,9 +I,0.52,0.395,0.145,0.77,0.424,0.142,0.1895,7 +F,0.525,0.43,0.135,0.8435,0.4325,0.18,0.1815,9 +M,0.535,0.405,0.14,0.818,0.402,0.1715,0.189,7 +F,0.54,0.42,0.14,0.8035,0.38,0.1805,0.21,9 +F,0.54,0.415,0.15,0.8115,0.3875,0.1875,0.2035,9 +F,0.57,0.425,0.13,0.782,0.3695,0.1745,0.1965,8 +M,0.57,0.42,0.14,0.8745,0.416,0.165,0.25,8 +M,0.58,0.445,0.16,0.984,0.49,0.201,0.27,9 +F,0.58,0.445,0.135,0.95,0.484,0.182,0.2325,8 +M,0.59,0.47,0.155,1.1735,0.6245,0.233,0.2595,9 +F,0.59,0.455,0.15,0.976,0.465,0.2055,0.2765,10 +M,0.59,0.485,0.155,1.0785,0.4535,0.2435,0.31,9 +M,0.595,0.435,0.16,1.057,0.4255,0.224,0.31,9 +M,0.6,0.475,0.175,1.11,0.5105,0.256,0.285,9 +M,0.6,0.45,0.16,1.142,0.539,0.225,0.307,10 +M,0.605,0.475,0.19,1.1255,0.59,0.247,0.26,10 +F,0.62,0.48,0.17,1.1045,0.535,0.25,0.287,10 +M,0.625,0.475,0.175,1.3405,0.656,0.283,0.337,10 +M,0.625,0.5,0.13,1.082,0.5785,0.2045,0.25,8 +F,0.625,0.485,0.16,1.254,0.591,0.259,0.3485,9 +M,0.63,0.49,0.165,1.2005,0.575,0.273,0.294,10 +M,0.63,0.485,0.16,1.243,0.623,0.275,0.3,10 +F,0.635,0.51,0.185,1.286,0.526,0.295,0.4105,12 +F,0.645,0.49,0.16,1.1665,0.4935,0.3155,0.299,9 +F,0.645,0.49,0.16,1.144,0.5015,0.289,0.319,8 +F,0.65,0.525,0.19,1.385,0.8875,0.3095,0.405,11 +F,0.655,0.515,0.155,1.309,0.524,0.346,0.385,11 +F,0.655,0.515,0.17,1.527,0.8485,0.2635,0.331,11 +M,0.665,0.515,0.19,1.6385,0.831,0.3575,0.371,11 +M,0.695,0.54,0.195,1.691,0.768,0.363,0.4755,11 +F,0.72,0.565,0.18,1.719,0.8465,0.407,0.3875,11 +F,0.72,0.55,0.18,1.52,0.637,0.325,0.435,10 +F,0.72,0.565,0.17,1.613,0.723,0.3255,0.4945,12 +M,0.735,0.57,0.21,2.2355,1.1705,0.463,0.5315,10 +M,0.74,0.595,0.19,2.3235,1.1495,0.5115,0.505,11 +I,0.31,0.23,0.07,0.1245,0.0505,0.0265,0.038,6 +I,0.315,0.235,0.075,0.1285,0.051,0.028,0.0405,4 +I,0.32,0.205,0.08,0.181,0.088,0.034,0.0495,5 +I,0.325,0.25,0.075,0.1585,0.075,0.0305,0.0455,6 +I,0.335,0.26,0.09,0.1965,0.0875,0.041,0.056,7 +I,0.37,0.28,0.085,0.198,0.0805,0.0455,0.058,5 +I,0.37,0.27,0.09,0.1855,0.07,0.0425,0.065,7 +I,0.375,0.28,0.085,0.2145,0.0855,0.0485,0.072,7 +I,0.4,0.315,0.09,0.3245,0.151,0.073,0.088,8 +I,0.41,0.305,0.095,0.2625,0.1,0.0515,0.09,6 +I,0.425,0.34,0.1,0.371,0.15,0.0865,0.115,8 +I,0.435,0.335,0.095,0.298,0.109,0.058,0.115,7 +I,0.445,0.31,0.09,0.336,0.1555,0.09,0.0855,7 +I,0.46,0.36,0.14,0.447,0.161,0.087,0.16,9 +F,0.465,0.35,0.11,0.4085,0.165,0.102,0.131,8 +I,0.47,0.385,0.13,0.587,0.264,0.117,0.174,8 +I,0.475,0.375,0.11,0.494,0.211,0.109,0.1545,8 +I,0.495,0.375,0.12,0.614,0.2855,0.1365,0.161,8 +I,0.5,0.39,0.13,0.5075,0.2115,0.104,0.1755,9 +I,0.5,0.37,0.12,0.5445,0.249,0.1065,0.152,8 +I,0.505,0.425,0.125,0.6115,0.245,0.1375,0.2,9 +I,0.505,0.4,0.125,0.5605,0.2255,0.1435,0.17,8 +M,0.505,0.365,0.115,0.521,0.25,0.096,0.15,8 +I,0.51,0.4,0.145,0.5775,0.231,0.143,0.177,9 +I,0.51,0.4,0.125,0.5935,0.239,0.13,0.204,8 +I,0.52,0.4,0.11,0.597,0.2935,0.1155,0.16,8 +M,0.52,0.465,0.15,0.9505,0.456,0.199,0.255,8 +I,0.53,0.38,0.125,0.616,0.292,0.113,0.185,8 +M,0.53,0.405,0.15,0.8315,0.352,0.187,0.2525,10 +F,0.535,0.445,0.125,0.8725,0.417,0.199,0.24,8 +I,0.54,0.425,0.13,0.8155,0.3675,0.1365,0.246,11 +I,0.54,0.415,0.11,0.619,0.2755,0.15,0.1765,10 +I,0.545,0.43,0.13,0.7595,0.358,0.153,0.2055,8 +I,0.545,0.43,0.15,0.742,0.3525,0.158,0.208,10 +I,0.55,0.435,0.165,0.804,0.34,0.194,0.244,8 +I,0.55,0.425,0.13,0.664,0.2695,0.163,0.21,8 +F,0.55,0.435,0.14,0.745,0.347,0.174,0.2265,9 +I,0.56,0.43,0.13,0.728,0.3355,0.1435,0.2175,8 +I,0.56,0.435,0.13,0.777,0.354,0.173,0.222,9 +F,0.575,0.425,0.15,0.8765,0.455,0.18,0.228,8 +I,0.575,0.455,0.16,0.9895,0.495,0.195,0.246,9 +M,0.575,0.45,0.165,0.9655,0.498,0.19,0.23,8 +M,0.58,0.465,0.15,0.9065,0.371,0.1965,0.29,8 +M,0.58,0.46,0.15,1.049,0.5205,0.1935,0.305,10 +F,0.58,0.45,0.17,0.9705,0.4615,0.232,0.248,9 +F,0.58,0.45,0.15,0.92,0.393,0.212,0.2895,9 +M,0.58,0.445,0.15,0.9525,0.4315,0.1945,0.287,11 +F,0.58,0.44,0.125,0.7855,0.363,0.1955,0.195,11 +I,0.585,0.45,0.135,0.855,0.3795,0.187,0.26,9 +M,0.59,0.5,0.15,1.142,0.485,0.265,0.345,9 +I,0.59,0.46,0.125,0.755,0.334,0.15,0.238,9 +I,0.59,0.475,0.145,0.9745,0.4675,0.207,0.259,10 +M,0.595,0.47,0.155,1.2015,0.492,0.3865,0.265,10 +M,0.595,0.46,0.17,1.1295,0.57,0.2555,0.265,10 +I,0.6,0.445,0.135,0.9205,0.445,0.2035,0.253,9 +F,0.6,0.48,0.17,1.056,0.4575,0.2435,0.3135,10 +M,0.6,0.45,0.195,1.34,0.617,0.3255,0.3605,10 +F,0.6,0.45,0.15,0.9625,0.4375,0.2225,0.2775,9 +M,0.6,0.465,0.165,1.0475,0.465,0.2345,0.315,11 +F,0.605,0.495,0.17,1.0915,0.4365,0.2715,0.335,13 +M,0.605,0.49,0.18,1.167,0.457,0.29,0.3745,9 +I,0.605,0.48,0.155,0.9995,0.425,0.1985,0.3,10 +I,0.61,0.425,0.155,1.0485,0.507,0.1955,0.274,11 +F,0.61,0.47,0.195,1.2735,0.469,0.3315,0.398,12 +M,0.61,0.48,0.14,1.0625,0.516,0.225,0.2915,11 +I,0.61,0.49,0.16,1.1545,0.5865,0.2385,0.2915,11 +F,0.615,0.475,0.175,1.194,0.559,0.259,0.3165,11 +F,0.615,0.515,0.135,1.1215,0.545,0.2305,0.29,9 +M,0.615,0.455,0.15,0.9335,0.382,0.247,0.2615,10 +F,0.615,0.495,0.165,1.198,0.5415,0.2865,0.3185,10 +F,0.62,0.475,0.15,0.9545,0.455,0.1865,0.277,9 +M,0.62,0.475,0.195,1.3585,0.5935,0.3365,0.3745,10 +M,0.625,0.495,0.175,1.2075,0.531,0.281,0.3525,11 +M,0.625,0.515,0.165,1.217,0.667,0.2065,0.3115,10 +F,0.625,0.5,0.16,1.217,0.5725,0.207,0.355,11 +F,0.625,0.49,0.145,0.92,0.437,0.1735,0.28,10 +M,0.625,0.49,0.12,0.8765,0.456,0.18,0.233,10 +F,0.63,0.48,0.165,1.2615,0.5505,0.277,0.3885,10 +M,0.63,0.53,0.18,1.2795,0.618,0.256,0.315,9 +F,0.63,0.485,0.185,1.167,0.548,0.2485,0.34,10 +M,0.63,0.51,0.17,1.1885,0.4915,0.3065,0.348,7 +F,0.635,0.485,0.19,1.3765,0.634,0.2885,0.406,11 +M,0.635,0.52,0.175,1.292,0.6,0.269,0.367,11 +M,0.635,0.485,0.18,1.1795,0.4785,0.2775,0.355,10 +F,0.635,0.5,0.19,1.29,0.593,0.3045,0.352,8 +M,0.635,0.515,0.16,1.2075,0.5385,0.282,0.345,11 +M,0.64,0.505,0.18,1.297,0.59,0.3125,0.363,11 +M,0.64,0.575,0.175,1.4585,0.625,0.266,0.4395,11 +F,0.645,0.485,0.15,1.151,0.5935,0.2315,0.293,12 +F,0.645,0.52,0.17,1.197,0.526,0.2925,0.317,11 +M,0.645,0.495,0.19,1.539,0.6115,0.408,0.445,12 +M,0.65,0.52,0.195,1.676,0.693,0.44,0.47,15 +F,0.65,0.565,0.2,1.6645,0.753,0.367,0.43,12 +F,0.655,0.5,0.205,1.528,0.6215,0.3725,0.4535,11 +F,0.655,0.515,0.2,1.494,0.7255,0.309,0.405,12 +F,0.66,0.525,0.16,1.277,0.4975,0.319,0.394,13 +F,0.66,0.525,0.18,1.5965,0.7765,0.397,0.3605,10 +F,0.665,0.51,0.175,1.3805,0.675,0.2985,0.325,10 +I,0.67,0.485,0.175,1.2565,0.5355,0.322,0.386,9 +F,0.67,0.525,0.19,1.527,0.5755,0.353,0.44,12 +M,0.67,0.525,0.17,1.4005,0.715,0.3025,0.387,9 +M,0.67,0.525,0.195,1.4405,0.6595,0.2675,0.425,9 +M,0.67,0.54,0.175,1.482,0.739,0.2925,0.365,10 +M,0.68,0.515,0.16,1.2345,0.618,0.2625,0.325,11 +F,0.68,0.505,0.17,1.3435,0.657,0.297,0.355,12 +M,0.685,0.505,0.19,1.533,0.667,0.4055,0.41,10 +M,0.69,0.515,0.18,1.8445,0.9815,0.4655,0.341,13 +M,0.715,0.55,0.175,1.825,0.938,0.3805,0.44,11 +M,0.72,0.58,0.19,2.0885,0.9955,0.478,0.5305,13 +M,0.735,0.59,0.205,2.087,0.909,0.474,0.625,12 +M,0.745,0.575,0.2,1.884,0.954,0.336,0.495,12 +I,0.32,0.215,0.095,0.305,0.14,0.067,0.0885,6 +I,0.43,0.345,0.115,0.4295,0.212,0.108,0.109,8 +I,0.43,0.33,0.1,0.449,0.254,0.0825,0.097,6 +M,0.485,0.365,0.155,1.029,0.4235,0.2285,0.313,8 +M,0.49,0.355,0.155,0.981,0.465,0.2015,0.2505,8 +I,0.5,0.37,0.115,0.5745,0.306,0.112,0.141,7 +F,0.505,0.38,0.13,0.693,0.391,0.1195,0.1515,8 +F,0.51,0.37,0.21,1.183,0.508,0.292,0.343,9 +F,0.525,0.41,0.135,0.7905,0.4065,0.198,0.177,8 +F,0.535,0.4,0.15,1.224,0.618,0.275,0.2875,10 +I,0.535,0.4,0.135,0.775,0.368,0.208,0.2055,8 +M,0.535,0.405,0.175,1.2705,0.548,0.3265,0.337,13 +M,0.555,0.405,0.19,1.406,0.6115,0.342,0.389,10 +M,0.555,0.425,0.15,0.873,0.4625,0.1845,0.1965,9 +M,0.56,0.425,0.135,0.9415,0.509,0.2015,0.1975,9 +F,0.59,0.44,0.14,1.007,0.4775,0.2105,0.2925,9 +M,0.595,0.485,0.15,1.0835,0.5305,0.231,0.276,8 +I,0.595,0.43,0.165,0.9845,0.4525,0.207,0.2725,8 +F,0.595,0.43,0.21,1.5245,0.653,0.396,0.41,11 +M,0.61,0.475,0.175,1.024,0.409,0.261,0.322,9 +M,0.61,0.485,0.17,1.281,0.597,0.3035,0.33,9 +F,0.62,0.5,0.17,1.148,0.5475,0.22,0.3315,10 +F,0.625,0.49,0.11,1.136,0.5265,0.1915,0.2925,9 +F,0.635,0.51,0.17,1.2235,0.532,0.271,0.354,9 +F,0.635,0.525,0.18,1.3695,0.634,0.318,0.363,11 +M,0.64,0.485,0.16,1.006,0.456,0.2245,0.2835,9 +M,0.64,0.495,0.165,1.307,0.678,0.292,0.266,11 +M,0.645,0.505,0.185,1.463,0.592,0.3905,0.416,10 +F,0.655,0.505,0.175,1.2905,0.6205,0.2965,0.326,10 +F,0.67,0.515,0.17,1.4265,0.6605,0.3395,0.37,11 +M,0.68,0.54,0.21,1.7885,0.8345,0.408,0.437,13 +M,0.7,0.545,0.185,1.6135,0.75,0.4035,0.3685,11 +M,0.73,0.585,0.225,2.2305,1.2395,0.422,0.563,14 +F,0.75,0.615,0.205,2.2635,0.821,0.423,0.726,12 +I,0.255,0.185,0.065,0.074,0.0305,0.0165,0.02,4 +I,0.375,0.26,0.08,0.2075,0.09,0.0415,0.07,6 +I,0.375,0.285,0.09,0.237,0.106,0.0395,0.08,8 +I,0.39,0.3,0.1,0.2665,0.1105,0.059,0.084,7 +I,0.39,0.28,0.09,0.215,0.0845,0.034,0.079,8 +I,0.395,0.3,0.09,0.253,0.1155,0.05,0.075,6 +I,0.42,0.32,0.11,0.309,0.115,0.0645,0.0945,6 +I,0.435,0.335,0.105,0.3535,0.156,0.05,0.1135,7 +I,0.435,0.325,0.105,0.335,0.136,0.065,0.115,8 +I,0.44,0.32,0.105,0.3875,0.1755,0.074,0.12,9 +I,0.45,0.33,0.115,0.365,0.14,0.0825,0.1245,8 +I,0.45,0.34,0.125,0.4045,0.171,0.07,0.1345,8 +I,0.455,0.355,0.105,0.372,0.138,0.0765,0.135,9 +I,0.46,0.37,0.11,0.3965,0.1485,0.0855,0.1455,8 +I,0.47,0.375,0.125,0.5225,0.2265,0.104,0.162,8 +I,0.475,0.375,0.11,0.456,0.182,0.099,0.16,9 +I,0.495,0.33,0.1,0.44,0.177,0.095,0.15,7 +I,0.495,0.375,0.115,0.507,0.241,0.103,0.15,8 +I,0.5,0.38,0.135,0.5285,0.226,0.123,0.209,8 +I,0.515,0.385,0.125,0.572,0.237,0.1435,0.165,7 +I,0.52,0.41,0.14,0.6625,0.2775,0.1555,0.196,11 +I,0.52,0.395,0.115,0.6445,0.3155,0.1245,0.186,11 +I,0.525,0.4,0.11,0.6275,0.3015,0.126,0.18,8 +I,0.535,0.42,0.145,0.6885,0.273,0.1515,0.237,9 +M,0.535,0.41,0.12,0.6835,0.3125,0.1655,0.159,8 +M,0.54,0.42,0.19,0.6855,0.293,0.163,0.38,10 +I,0.55,0.405,0.15,0.6755,0.3015,0.1465,0.21,10 +I,0.55,0.445,0.145,0.783,0.3045,0.157,0.265,11 +M,0.56,0.45,0.145,0.894,0.3885,0.2095,0.264,9 +I,0.565,0.44,0.135,0.768,0.3305,0.1385,0.2475,9 +M,0.57,0.45,0.145,0.95,0.4005,0.2235,0.2845,10 +F,0.57,0.47,0.14,0.871,0.385,0.211,0.2315,10 +M,0.575,0.47,0.15,0.9785,0.4505,0.196,0.276,9 +I,0.575,0.43,0.13,0.7425,0.2895,0.2005,0.22,8 +M,0.575,0.445,0.14,0.737,0.325,0.1405,0.237,10 +I,0.575,0.445,0.16,0.9175,0.45,0.1935,0.24,9 +F,0.58,0.435,0.155,0.8785,0.425,0.1685,0.2425,10 +M,0.585,0.45,0.175,1.1275,0.4925,0.262,0.335,11 +M,0.59,0.435,0.165,0.9765,0.4525,0.2395,0.235,9 +I,0.59,0.47,0.145,0.974,0.453,0.236,0.289,8 +M,0.59,0.405,0.15,0.853,0.326,0.2615,0.245,9 +M,0.595,0.47,0.175,0.991,0.382,0.2395,0.5,12 +M,0.595,0.48,0.14,0.9125,0.4095,0.1825,0.289,9 +F,0.595,0.46,0.16,0.921,0.4005,0.2025,0.2875,9 +F,0.6,0.45,0.14,0.869,0.3425,0.195,0.291,11 +M,0.6,0.45,0.15,0.8665,0.3695,0.1955,0.255,12 +F,0.61,0.495,0.16,1.089,0.469,0.198,0.384,11 +M,0.615,0.485,0.215,0.9615,0.422,0.176,0.29,11 +M,0.615,0.49,0.17,1.145,0.4915,0.208,0.343,13 +I,0.62,0.475,0.16,0.907,0.371,0.167,0.3075,11 +F,0.625,0.515,0.155,1.1635,0.4875,0.259,0.355,11 +M,0.63,0.515,0.175,1.1955,0.492,0.247,0.37,11 +M,0.63,0.495,0.18,1.31,0.495,0.295,0.4695,10 +F,0.635,0.505,0.165,1.251,0.577,0.227,0.3825,11 +F,0.635,0.49,0.155,1.145,0.4775,0.3035,0.3155,9 +M,0.635,0.5,0.18,1.154,0.4405,0.2315,0.387,9 +F,0.64,0.485,0.145,1.1335,0.5525,0.2505,0.3015,11 +F,0.64,0.5,0.15,1.2015,0.559,0.231,0.3355,9 +M,0.65,0.505,0.17,1.5595,0.695,0.3515,0.395,11 +M,0.65,0.51,0.175,1.3165,0.6345,0.2605,0.364,12 +M,0.655,0.54,0.165,1.403,0.6955,0.2385,0.42,11 +F,0.655,0.49,0.16,1.204,0.5455,0.2615,0.3225,9 +F,0.655,0.455,0.17,1.2895,0.587,0.3165,0.3415,11 +F,0.66,0.53,0.18,1.5175,0.7765,0.302,0.401,10 +M,0.665,0.525,0.155,1.3575,0.5325,0.3045,0.4485,10 +M,0.675,0.52,0.145,1.3645,0.557,0.3405,0.385,11 +F,0.68,0.52,0.185,1.494,0.615,0.3935,0.406,11 +F,0.68,0.56,0.195,1.664,0.58,0.3855,0.545,11 +M,0.685,0.51,0.165,1.545,0.686,0.3775,0.4055,10 +F,0.695,0.535,0.2,1.5855,0.667,0.334,0.471,11 +F,0.7,0.555,0.22,1.666,0.647,0.4285,0.455,11 +M,0.71,0.56,0.175,1.724,0.566,0.4575,0.4625,13 +F,0.73,0.55,0.205,1.908,0.5415,0.3565,0.5965,14 +F,0.755,0.575,0.2,2.073,1.0135,0.4655,0.48,11 +I,0.225,0.17,0.05,0.0515,0.019,0.012,0.017,4 +I,0.23,0.17,0.05,0.057,0.026,0.013,0.016,5 +I,0.255,0.185,0.06,0.0925,0.039,0.021,0.025,6 +I,0.355,0.27,0.075,0.204,0.3045,0.046,0.0595,7 +I,0.425,0.31,0.095,0.3075,0.139,0.0745,0.093,7 +I,0.425,0.32,0.085,0.262,0.1235,0.067,0.0725,8 +M,0.455,0.35,0.11,0.458,0.2,0.111,0.1305,8 +M,0.46,0.355,0.14,0.491,0.207,0.115,0.174,10 +M,0.495,0.38,0.12,0.474,0.197,0.1065,0.1545,10 +M,0.51,0.395,0.125,0.5805,0.244,0.1335,0.188,11 +F,0.52,0.43,0.15,0.728,0.302,0.1575,0.235,11 +M,0.525,0.4,0.13,0.622,0.2655,0.147,0.184,9 +M,0.53,0.415,0.12,0.706,0.3355,0.1635,0.1345,9 +F,0.53,0.395,0.115,0.5685,0.249,0.1375,0.161,9 +M,0.545,0.435,0.145,0.9385,0.3685,0.1245,0.345,11 +F,0.55,0.43,0.15,0.655,0.2635,0.122,0.221,8 +M,0.575,0.48,0.15,0.9465,0.4355,0.2605,0.2505,9 +M,0.58,0.43,0.125,0.9115,0.446,0.2075,0.121,10 +M,0.595,0.455,0.145,0.942,0.43,0.182,0.277,11 +M,0.6,0.465,0.18,1.193,0.5145,0.315,0.3055,8 +M,0.645,0.5,0.18,1.461,0.5985,0.2425,0.439,11 +M,0.66,0.525,0.2,1.489,0.6065,0.3795,0.421,10 +I,0.29,0.215,0.06,0.1115,0.053,0.0185,0.032,5 +I,0.3,0.22,0.065,0.1235,0.059,0.026,0.0315,5 +I,0.37,0.275,0.1,0.2815,0.1505,0.0505,0.068,5 +I,0.375,0.285,0.08,0.226,0.0975,0.04,0.0725,7 +I,0.38,0.29,0.085,0.2285,0.088,0.0465,0.075,7 +I,0.395,0.3,0.12,0.2995,0.1265,0.068,0.0895,8 +I,0.41,0.325,0.105,0.361,0.1605,0.0665,0.103,8 +I,0.415,0.32,0.115,0.3045,0.1215,0.0735,0.094,7 +I,0.425,0.325,0.105,0.3975,0.1815,0.081,0.1175,7 +I,0.44,0.34,0.1,0.379,0.1725,0.0815,0.101,7 +I,0.44,0.34,0.12,0.4995,0.2965,0.0945,0.1185,6 +M,0.465,0.405,0.135,0.7775,0.436,0.1715,0.1455,10 +F,0.47,0.36,0.1,0.4705,0.1635,0.089,0.1385,8 +M,0.51,0.415,0.145,0.751,0.3295,0.1835,0.203,8 +F,0.525,0.4,0.135,0.714,0.318,0.138,0.208,10 +F,0.525,0.4,0.13,0.6995,0.3115,0.131,0.223,9 +F,0.55,0.425,0.14,0.952,0.4895,0.1945,0.2185,7 +M,0.56,0.42,0.15,0.8755,0.44,0.1965,0.2315,8 +M,0.575,0.45,0.135,0.9215,0.354,0.209,0.2365,9 +F,0.575,0.45,0.135,0.8285,0.362,0.1655,0.236,10 +M,0.585,0.46,0.15,1.206,0.581,0.216,0.323,10 +M,0.615,0.495,0.155,1.2865,0.435,0.293,0.3245,11 +F,0.62,0.485,0.155,1.1945,0.5105,0.271,0.352,9 +F,0.63,0.495,0.19,1.1655,0.536,0.2115,0.1625,10 +F,0.63,0.49,0.17,1.2155,0.4625,0.2045,0.3105,10 +M,0.67,0.515,0.165,1.1735,0.526,0.285,0.316,11 +M,0.675,0.505,0.16,1.532,0.74,0.357,0.3815,11 +F,0.685,0.53,0.17,1.5105,0.7385,0.3525,0.3725,10 +F,0.485,0.39,0.1,0.5565,0.2215,0.1155,0.185,9 +M,0.46,0.36,0.125,0.547,0.2165,0.1105,0.19,8 +M,0.46,0.35,0.125,0.5165,0.1885,0.1145,0.185,9 +M,0.535,0.42,0.125,0.764,0.312,0.1505,0.265,11 +M,0.465,0.36,0.105,0.488,0.188,0.0845,0.19,10 +M,0.51,0.4,0.14,0.6905,0.259,0.151,0.23,10 +I,0.335,0.26,0.09,0.1835,0.078,0.024,0.065,11 +M,0.55,0.425,0.16,0.97,0.2885,0.139,0.48,20 +I,0.18,0.135,0.08,0.033,0.0145,0.007,0.01,5 +I,0.215,0.165,0.055,0.059,0.0265,0.0125,0.0185,5 +I,0.2,0.15,0.04,0.046,0.021,0.007,0.0065,4 +F,0.625,0.48,0.2,1.3235,0.6075,0.3055,0.355,9 +M,0.55,0.42,0.17,0.8465,0.336,0.2405,0.245,13 +M,0.585,0.45,0.15,1.047,0.4315,0.276,0.315,14 +F,0.645,0.5,0.18,1.2785,0.5345,0.2995,0.345,13 +F,0.71,0.53,0.195,1.8745,0.6755,0.4065,0.6855,12 +F,0.7,0.54,0.215,1.978,0.6675,0.3125,0.71,24 +F,0.655,0.505,0.165,1.367,0.5835,0.3515,0.396,10 +F,0.665,0.5,0.175,1.742,0.595,0.3025,0.725,21 +F,0.47,0.375,0.105,0.513,0.232,0.142,0.13,11 +M,0.425,0.335,0.1,0.4085,0.1755,0.092,0.135,9 +M,0.54,0.41,0.13,0.56,0.2375,0.1065,0.175,7 +M,0.505,0.395,0.125,0.635,0.29,0.1555,0.175,9 +M,0.535,0.44,0.165,0.875,0.279,0.18,0.3,10 +F,0.43,0.35,0.09,0.397,0.1575,0.089,0.12,9 +M,0.55,0.435,0.11,0.806,0.3415,0.203,0.215,9 +F,0.34,0.255,0.085,0.204,0.097,0.021,0.05,6 +I,0.275,0.2,0.065,0.1165,0.0565,0.013,0.035,7 +F,0.335,0.22,0.07,0.17,0.076,0.0365,0.05,6 +M,0.64,0.49,0.14,1.194,0.4445,0.238,0.375,15 +F,0.55,0.44,0.125,0.765,0.33,0.2125,0.245,9 +F,0.64,0.475,0.19,1.151,0.4365,0.281,0.3805,13 +F,0.545,0.41,0.115,0.6765,0.29,0.158,0.22,9 +F,0.64,0.54,0.175,1.571,0.627,0.271,0.475,18 +M,0.605,0.49,0.155,1.153,0.503,0.2505,0.295,15 +M,0.605,0.47,0.115,1.114,0.3925,0.291,0.31,15 +M,0.56,0.45,0.155,0.9125,0.3595,0.271,0.35,10 +F,0.57,0.465,0.155,0.872,0.3245,0.239,0.285,14 +M,0.525,0.405,0.16,0.792,0.316,0.1455,0.28,13 +F,0.505,0.405,0.18,0.606,0.239,0.1235,0.18,11 +M,0.35,0.265,0.09,0.2265,0.0995,0.0575,0.065,6 +M,0.45,0.355,0.12,0.3955,0.147,0.0765,0.145,9 +I,0.51,0.405,0.12,0.61,0.229,0.131,0.235,11 +F,0.49,0.38,0.13,0.539,0.229,0.1355,0.165,12 +F,0.505,0.41,0.135,0.657,0.291,0.133,0.195,15 +M,0.38,0.3,0.1,0.2505,0.106,0.0535,0.0775,8 +I,0.27,0.195,0.07,0.102,0.045,0.0135,0.034,8 +F,0.37,0.295,0.1,0.2685,0.1165,0.056,0.0835,7 +M,0.5,0.385,0.135,0.551,0.2245,0.0715,0.206,11 +M,0.645,0.505,0.165,1.307,0.4335,0.262,0.52,10 +M,0.565,0.44,0.115,0.9185,0.404,0.1785,0.29,11 +F,0.67,0.545,0.175,1.707,0.6995,0.387,0.575,13 +F,0.59,0.415,0.15,0.8805,0.3645,0.234,0.235,11 +F,0.47,0.36,0.11,0.4965,0.237,0.127,0.13,6 +F,0.51,0.385,0.135,0.632,0.282,0.145,0.17,8 +M,0.72,0.575,0.23,2.2695,0.8835,0.3985,0.665,16 +M,0.55,0.405,0.15,0.9235,0.412,0.2135,0.24,7 +I,0.2,0.145,0.025,0.0345,0.011,0.0075,0.01,5 +M,0.65,0.515,0.18,1.3315,0.5665,0.347,0.405,13 +F,0.525,0.405,0.115,0.72,0.3105,0.1915,0.2,14 +M,0.565,0.435,0.185,1.032,0.354,0.2045,0.31,20 +F,0.61,0.47,0.16,1.017,0.426,0.2255,0.32,12 +F,0.545,0.405,0.175,0.98,0.2585,0.207,0.38,18 +I,0.325,0.245,0.075,0.1495,0.0605,0.033,0.045,5 +I,0.31,0.235,0.075,0.1515,0.056,0.0315,0.05,7 +M,0.45,0.335,0.14,0.478,0.1865,0.115,0.16,11 +F,0.49,0.38,0.155,0.578,0.2395,0.1255,0.18,9 +F,0.505,0.405,0.16,0.6835,0.271,0.145,0.215,10 +F,0.385,0.3,0.1,0.2725,0.1115,0.057,0.08,6 +F,0.62,0.485,0.22,1.511,0.5095,0.284,0.51,17 +F,0.635,0.505,0.185,1.3035,0.501,0.295,0.41,17 +F,0.665,0.53,0.185,1.3955,0.456,0.3205,0.49,15 +M,0.335,0.265,0.095,0.1975,0.0795,0.0375,0.07,9 +I,0.295,0.215,0.075,0.116,0.037,0.0295,0.04,8 +I,0.48,0.38,0.125,0.523,0.2105,0.1045,0.175,15 +I,0.32,0.25,0.08,0.1565,0.057,0.034,0.06,9 +I,0.43,0.34,0.125,0.384,0.1375,0.061,0.146,14 +M,0.565,0.45,0.14,1.0055,0.3785,0.244,0.265,12 +F,0.6,0.48,0.165,1.1345,0.4535,0.27,0.335,10 +F,0.585,0.46,0.17,1.0835,0.3745,0.326,0.325,14 +F,0.555,0.42,0.14,0.868,0.33,0.243,0.21,13 +F,0.57,0.495,0.16,1.0915,0.452,0.275,0.315,14 +F,0.62,0.485,0.175,1.271,0.531,0.3075,0.37,11 +M,0.63,0.51,0.19,1.4985,0.4125,0.3075,0.545,16 +M,0.425,0.34,0.12,0.388,0.149,0.087,0.125,10 +F,0.64,0.505,0.19,1.2355,0.4435,0.3105,0.365,14 +M,0.675,0.525,0.175,1.402,0.483,0.3205,0.465,16 +M,0.5,0.4,0.145,0.6025,0.216,0.138,0.21,11 +M,0.385,0.305,0.09,0.2775,0.109,0.0515,0.1,9 +M,0.52,0.435,0.195,0.973,0.2985,0.2135,0.355,18 +M,0.52,0.415,0.175,0.753,0.258,0.171,0.255,8 +M,0.64,0.525,0.2,1.3765,0.44,0.3075,0.47,16 +I,0.44,0.35,0.12,0.375,0.1425,0.0965,0.115,9 +F,0.42,0.32,0.13,0.4135,0.1645,0.106,0.119,10 +F,0.45,0.35,0.135,0.56,0.231,0.137,0.145,13 +I,0.42,0.325,0.125,0.3915,0.1575,0.1025,0.115,9 +F,0.64,0.505,0.19,1.2765,0.4835,0.328,0.4,12 +M,0.57,0.455,0.15,0.96,0.387,0.2385,0.275,11 +M,0.41,0.325,0.12,0.3745,0.158,0.081,0.125,12 +M,0.485,0.41,0.15,0.696,0.2405,0.1625,0.265,13 +F,0.61,0.48,0.19,1.2955,0.5215,0.3225,0.365,12 +F,0.59,0.485,0.205,1.2315,0.4525,0.238,0.42,13 +M,0.665,0.535,0.155,1.383,0.596,0.2565,0.485,14 +I,0.345,0.285,0.1,0.2225,0.0865,0.058,0.075,8 +M,0.635,0.51,0.155,1.156,0.428,0.289,0.315,18 +M,0.695,0.53,0.15,1.477,0.6375,0.3025,0.43,14 +F,0.69,0.54,0.185,1.5715,0.6935,0.318,0.47,15 +M,0.555,0.435,0.135,0.858,0.377,0.1585,0.29,15 +M,0.65,0.525,0.19,1.4995,0.6265,0.4005,0.395,14 +M,0.635,0.48,0.19,1.467,0.5825,0.303,0.42,15 +F,0.655,0.51,0.16,1.092,0.396,0.2825,0.37,14 +F,0.69,0.555,0.205,1.8165,0.7785,0.4395,0.515,19 +F,0.695,0.55,0.16,1.6365,0.694,0.3005,0.44,13 +M,0.55,0.435,0.16,0.906,0.342,0.219,0.295,13 +F,0.61,0.495,0.19,1.213,0.464,0.306,0.365,15 +M,0.595,0.5,0.165,1.06,0.402,0.28,0.275,11 +M,0.3,0.24,0.09,0.161,0.0725,0.039,0.05,6 +F,0.435,0.35,0.125,0.459,0.197,0.1145,0.145,9 +I,0.455,0.375,0.125,0.533,0.233,0.106,0.185,8 +M,0.48,0.38,0.13,0.6175,0.3,0.142,0.175,12 +I,0.43,0.35,0.105,0.366,0.1705,0.0855,0.11,6 +F,0.435,0.35,0.105,0.4195,0.194,0.1005,0.13,7 +I,0.3,0.23,0.075,0.15,0.0605,0.042,0.045,5 +F,0.575,0.48,0.15,0.8745,0.375,0.193,0.29,12 +M,0.505,0.385,0.11,0.655,0.3185,0.15,0.185,9 +M,0.455,0.375,0.125,0.484,0.2155,0.102,0.165,7 +M,0.64,0.505,0.165,1.4435,0.6145,0.3035,0.39,18 +F,0.56,0.435,0.125,0.8775,0.3345,0.2145,0.29,13 +F,0.645,0.52,0.19,1.3105,0.58,0.288,0.37,12 +F,0.595,0.485,0.145,1.2515,0.5035,0.2925,0.33,14 +M,0.565,0.45,0.115,0.9085,0.398,0.197,0.29,17 +F,0.655,0.5,0.14,1.1705,0.5405,0.3175,0.285,12 +M,0.48,0.38,0.135,0.528,0.2,0.1395,0.16,14 +F,0.495,0.385,0.135,0.6625,0.3005,0.1635,0.185,11 +F,0.4,0.335,0.115,0.4335,0.2105,0.1205,0.12,10 +M,0.41,0.31,0.125,0.3595,0.1415,0.0885,0.115,11 +F,0.595,0.465,0.145,1.107,0.402,0.2415,0.31,12 +F,0.625,0.475,0.13,0.8595,0.3195,0.1775,0.24,13 +M,0.52,0.425,0.155,0.7735,0.297,0.123,0.255,17 +M,0.465,0.36,0.125,0.4365,0.169,0.1075,0.145,11 +F,0.475,0.375,0.14,0.501,0.192,0.1175,0.175,13 +F,0.5,0.405,0.14,0.6735,0.265,0.124,0.25,18 +M,0.46,0.355,0.11,0.415,0.215,0.082,0.13,12 +M,0.485,0.385,0.125,0.4775,0.2,0.0785,0.17,12 +F,0.465,0.39,0.14,0.5555,0.213,0.1075,0.215,15 +M,0.525,0.415,0.16,0.6445,0.26,0.1575,0.22,12 +F,0.655,0.53,0.19,1.428,0.493,0.318,0.565,18 +M,0.69,0.54,0.185,1.6195,0.533,0.353,0.555,24 +M,0.55,0.45,0.17,0.81,0.317,0.157,0.22,11 +F,0.58,0.475,0.165,1.0385,0.414,0.26,0.305,13 +F,0.59,0.475,0.155,0.9715,0.371,0.235,0.28,11 +M,0.565,0.44,0.155,0.868,0.348,0.217,0.26,11 +F,0.665,0.57,0.185,1.522,0.6965,0.3025,0.405,13 +F,0.62,0.51,0.175,1.1255,0.4985,0.227,0.315,14 +M,0.55,0.46,0.13,0.7085,0.305,0.1455,0.205,12 +F,0.605,0.475,0.145,1.0185,0.4695,0.225,0.27,15 +M,0.535,0.42,0.16,0.72,0.275,0.164,0.225,15 +F,0.51,0.395,0.12,0.6175,0.262,0.122,0.193,12 +M,0.53,0.405,0.13,0.738,0.2845,0.17,0.193,9 +F,0.495,0.375,0.15,0.597,0.2615,0.135,0.178,11 +M,0.575,0.455,0.185,1.156,0.5525,0.243,0.295,13 +F,0.63,0.5,0.16,1.22,0.4905,0.3,0.345,14 +M,0.59,0.45,0.12,0.7485,0.3345,0.1315,0.22,14 +F,0.605,0.485,0.165,1.0735,0.437,0.205,0.33,14 +M,0.645,0.5,0.19,1.229,0.524,0.278,0.395,17 +F,0.62,0.5,0.175,1.146,0.477,0.23,0.39,13 +M,0.605,0.485,0.175,1.145,0.4325,0.27,0.405,16 +F,0.615,0.5,0.205,1.1055,0.4445,0.227,0.39,16 +F,0.66,0.525,0.19,1.67,0.6525,0.4875,0.49,11 +F,0.71,0.575,0.175,1.555,0.6465,0.3705,0.52,15 +F,0.565,0.45,0.185,0.9285,0.302,0.1805,0.265,12 +F,0.57,0.435,0.14,0.8085,0.3235,0.183,0.22,16 +I,0.6,0.445,0.175,1.057,0.383,0.216,0.355,16 +I,0.41,0.3,0.115,0.2595,0.097,0.0515,0.08,10 +F,0.45,0.325,0.135,0.438,0.1805,0.1165,0.11,9 +M,0.275,0.2,0.08,0.099,0.037,0.024,0.03,5 +I,0.485,0.355,0.12,0.5085,0.21,0.122,0.135,9 +F,0.62,0.485,0.165,1.166,0.483,0.238,0.355,13 +F,0.48,0.38,0.135,0.507,0.1915,0.1365,0.155,12 +F,0.505,0.41,0.15,0.6345,0.243,0.1335,0.215,17 +M,0.4,0.31,0.11,0.314,0.138,0.057,0.1,11 +I,0.45,0.355,0.115,0.4385,0.184,0.108,0.1125,11 +M,0.35,0.26,0.09,0.195,0.0745,0.041,0.0655,9 +M,0.44,0.35,0.14,0.451,0.171,0.0705,0.184,16 +M,0.265,0.2,0.065,0.084,0.034,0.0105,0.03,7 +M,0.165,0.125,0.04,0.0245,0.0095,0.0045,0.008,4 +F,0.705,0.555,0.2,1.4685,0.4715,0.3235,0.52,19 +F,0.535,0.425,0.155,0.7765,0.302,0.1565,0.25,16 +I,0.49,0.385,0.14,0.5425,0.198,0.127,0.175,11 +F,0.48,0.37,0.13,0.5885,0.2475,0.1505,0.1595,15 +F,0.395,0.3,0.105,0.3375,0.1435,0.0755,0.098,12 +I,0.375,0.28,0.1,0.2565,0.1165,0.0585,0.0725,12 +M,0.345,0.265,0.09,0.163,0.0615,0.037,0.0485,10 +I,0.55,0.415,0.135,0.8095,0.2985,0.2015,0.28,12 +I,0.635,0.48,0.2,1.3655,0.6255,0.2595,0.425,16 +I,0.575,0.475,0.17,0.967,0.3775,0.284,0.275,13 +F,0.545,0.435,0.15,0.6855,0.2905,0.145,0.225,10 +F,0.385,0.305,0.125,0.314,0.146,0.0555,0.08,10 +F,0.51,0.34,0.18,0.7005,0.312,0.165,0.2,11 +I,0.44,0.34,0.125,0.4895,0.1735,0.0875,0.2,13 +I,0.45,0.36,0.125,0.45,0.191,0.0865,0.145,12 +I,0.39,0.3,0.105,0.259,0.0955,0.038,0.085,8 +F,0.425,0.325,0.135,0.382,0.1465,0.079,0.14,12 +F,0.45,0.35,0.125,0.4435,0.185,0.09,0.145,11 +I,0.66,0.525,0.18,1.6935,0.6025,0.4005,0.42,15 +F,0.685,0.525,0.175,1.71,0.5415,0.309,0.58,16 +F,0.585,0.475,0.185,0.8575,0.3465,0.1785,0.275,12 +I,0.54,0.435,0.145,0.97,0.4285,0.22,0.264,17 +F,0.49,0.39,0.135,0.59,0.215,0.125,0.1845,12 +M,0.43,0.33,0.095,0.34,0.1315,0.085,0.112,14 +F,0.455,0.365,0.11,0.385,0.166,0.046,0.1345,13 +I,0.495,0.38,0.145,0.515,0.175,0.098,0.212,13 +F,0.48,0.38,0.145,0.59,0.232,0.141,0.23,12 +I,0.47,0.4,0.16,0.51,0.1615,0.073,0.198,14 +M,0.415,0.32,0.1,0.3005,0.1215,0.0575,0.104,11 +I,0.49,0.385,0.115,0.683,0.3265,0.1615,0.165,13 +I,0.47,0.375,0.105,0.468,0.1665,0.108,0.17,10 +I,0.445,0.345,0.13,0.4075,0.1365,0.0645,0.18,11 +F,0.51,0.38,0.13,0.584,0.224,0.1355,0.185,13 +F,0.52,0.405,0.145,0.829,0.3535,0.1685,0.205,15 +I,0.475,0.365,0.14,0.4545,0.171,0.118,0.158,8 +F,0.455,0.36,0.11,0.4385,0.206,0.098,0.125,10 +I,0.435,0.34,0.11,0.407,0.1685,0.073,0.13,10 +I,0.39,0.3,0.1,0.3085,0.1385,0.0735,0.085,6 +I,0.375,0.285,0.1,0.239,0.105,0.0555,0.07,8 +M,0.285,0.215,0.075,0.106,0.0415,0.023,0.035,5 +I,0.58,0.445,0.17,1.178,0.3935,0.2165,0.315,20 +F,0.58,0.44,0.175,1.073,0.4005,0.2345,0.335,19 +M,0.41,0.315,0.095,0.306,0.121,0.0735,0.09,9 +M,0.41,0.3,0.1,0.301,0.124,0.069,0.09,9 +I,0.54,0.405,0.15,0.7585,0.307,0.2075,0.19,10 +M,0.33,0.245,0.085,0.171,0.0655,0.0365,0.055,11 +I,0.44,0.31,0.115,0.3625,0.134,0.082,0.12,11 +M,0.28,0.21,0.065,0.0905,0.035,0.02,0.03,5 +I,0.59,0.465,0.195,1.0885,0.3685,0.187,0.375,17 +I,0.61,0.48,0.165,1.097,0.4215,0.264,0.335,13 +I,0.61,0.46,0.17,1.278,0.41,0.257,0.37,17 +M,0.455,0.345,0.125,0.44,0.169,0.1065,0.135,12 +M,0.33,0.235,0.09,0.163,0.0615,0.034,0.055,10 +I,0.44,0.33,0.135,0.522,0.17,0.0905,0.195,16 +M,0.54,0.405,0.155,0.9715,0.3225,0.194,0.29,19 +F,0.475,0.375,0.125,0.588,0.237,0.1715,0.155,10 +F,0.46,0.33,0.15,0.5325,0.2085,0.1805,0.125,10 +I,0.31,0.235,0.09,0.127,0.048,0.031,0.04,6 +I,0.255,0.19,0.07,0.0815,0.028,0.016,0.031,5 +M,0.335,0.255,0.075,0.1635,0.0615,0.0345,0.057,8 +I,0.295,0.21,0.08,0.1,0.038,0.026,0.031,8 +I,0.19,0.13,0.045,0.0265,0.009,0.005,0.009,5 +M,0.545,0.435,0.165,0.9955,0.3245,0.2665,0.325,19 +M,0.495,0.4,0.12,0.6605,0.2605,0.161,0.19,15 +M,0.5,0.375,0.13,0.721,0.3055,0.1725,0.22,14 +F,0.305,0.225,0.07,0.1485,0.0585,0.0335,0.045,7 +F,0.475,0.35,0.115,0.487,0.194,0.1455,0.125,13 +M,0.515,0.4,0.125,0.955,0.341,0.2535,0.26,13 +M,0.545,0.41,0.145,0.873,0.3035,0.196,0.31,18 +M,0.74,0.535,0.185,1.65,0.734,0.4505,0.335,13 +M,0.565,0.465,0.15,1.1285,0.377,0.3525,0.33,16 +M,0.56,0.44,0.16,1.1115,0.5035,0.2785,0.26,10 +M,0.545,0.42,0.125,0.9745,0.353,0.174,0.305,13 +M,0.645,0.515,0.185,1.4605,0.5835,0.3155,0.41,19 +M,0.575,0.435,0.13,1.0105,0.368,0.222,0.32,10 +M,0.62,0.48,0.16,1.0765,0.412,0.253,0.3,13 +F,0.605,0.45,0.165,1.2225,0.357,0.202,0.385,13 +M,0.605,0.475,0.16,1.616,0.5495,0.332,0.34,18 +F,0.475,0.375,0.15,0.559,0.1955,0.1215,0.1945,12 +M,0.365,0.285,0.085,0.2205,0.0855,0.0515,0.07,9 +F,0.46,0.35,0.115,0.44,0.19,0.1025,0.13,8 +M,0.53,0.43,0.135,0.879,0.28,0.2165,0.25,10 +M,0.48,0.395,0.15,0.6815,0.2145,0.1405,0.2495,18 +M,0.455,0.345,0.15,0.5795,0.1685,0.125,0.215,13 +I,0.35,0.265,0.11,0.209,0.066,0.059,0.075,9 +M,0.37,0.28,0.105,0.224,0.0815,0.0575,0.075,8 +I,0.34,0.25,0.075,0.1765,0.0785,0.0405,0.05,7 +I,0.35,0.28,0.075,0.196,0.082,0.04,0.064,8 +I,0.35,0.265,0.08,0.192,0.081,0.0465,0.053,6 +I,0.39,0.315,0.09,0.3095,0.147,0.05,0.09,7 +I,0.395,0.31,0.095,0.313,0.131,0.072,0.093,7 +I,0.415,0.31,0.105,0.3595,0.167,0.083,0.0915,6 +I,0.43,0.32,0.1,0.3855,0.192,0.0745,0.1,7 +I,0.48,0.355,0.115,0.5785,0.25,0.106,0.184,8 +M,0.49,0.395,0.12,0.674,0.3325,0.1235,0.185,9 +F,0.49,0.37,0.105,0.5265,0.249,0.1005,0.148,7 +F,0.56,0.465,0.16,1.0315,0.432,0.2025,0.337,9 +M,0.56,0.45,0.14,0.9,0.472,0.182,0.218,7 +M,0.58,0.46,0.15,1.0165,0.491,0.221,0.265,9 +F,0.58,0.48,0.18,1.2495,0.4945,0.27,0.371,8 +M,0.59,0.47,0.135,1.1685,0.539,0.279,0.28,8 +F,0.595,0.475,0.165,1.148,0.444,0.214,0.37,10 +M,0.6,0.475,0.15,1.089,0.5195,0.223,0.292,11 +M,0.61,0.47,0.155,1.0325,0.497,0.2175,0.2785,9 +F,0.63,0.475,0.15,1.172,0.536,0.254,0.316,11 +M,0.64,0.51,0.17,1.3715,0.567,0.307,0.409,10 +F,0.65,0.545,0.185,1.5055,0.6565,0.341,0.43,10 +M,0.71,0.55,0.2,1.9045,0.882,0.44,0.5,13 +M,0.74,0.605,0.2,2.4925,1.1455,0.575,0.5235,13 +I,0.25,0.18,0.065,0.0805,0.0345,0.0185,0.0215,4 +I,0.28,0.21,0.065,0.111,0.0425,0.0285,0.03,6 +I,0.325,0.24,0.075,0.152,0.065,0.0305,0.045,6 +I,0.35,0.265,0.095,0.199,0.073,0.049,0.06,5 +I,0.36,0.27,0.09,0.219,0.097,0.0405,0.065,6 +I,0.365,0.27,0.105,0.2155,0.0915,0.0475,0.063,6 +I,0.37,0.28,0.09,0.2565,0.1255,0.0645,0.0645,6 +I,0.375,0.285,0.09,0.257,0.1045,0.062,0.075,7 +I,0.38,0.275,0.095,0.2505,0.0945,0.0655,0.075,6 +I,0.395,0.3,0.09,0.279,0.134,0.049,0.075,8 +I,0.43,0.335,0.105,0.378,0.188,0.0785,0.09,6 +I,0.44,0.35,0.125,0.456,0.21,0.0955,0.131,8 +I,0.465,0.37,0.1,0.5055,0.234,0.11,0.14,7 +F,0.465,0.355,0.115,0.4705,0.1955,0.118,0.126,7 +M,0.48,0.37,0.13,0.643,0.349,0.1155,0.135,8 +I,0.485,0.37,0.1,0.513,0.219,0.1075,0.13,7 +F,0.49,0.4,0.115,0.569,0.256,0.1325,0.145,9 +I,0.495,0.4,0.145,0.578,0.2545,0.1305,0.1645,8 +I,0.5,0.385,0.11,0.596,0.3015,0.104,0.151,8 +F,0.505,0.39,0.12,0.5725,0.2555,0.1325,0.146,8 +M,0.52,0.39,0.12,0.6435,0.2885,0.157,0.161,7 +M,0.52,0.395,0.125,0.8115,0.4035,0.166,0.2,7 +F,0.525,0.44,0.125,0.7115,0.3205,0.159,0.1915,7 +M,0.55,0.44,0.155,0.9155,0.3645,0.195,0.25,8 +F,0.555,0.44,0.145,0.8815,0.43,0.1975,0.2155,8 +F,0.555,0.42,0.11,0.931,0.4445,0.171,0.225,8 +F,0.575,0.46,0.165,1.065,0.4985,0.2145,0.2815,8 +M,0.6,0.475,0.155,1.1385,0.502,0.2295,0.31,9 +F,0.61,0.48,0.16,1.234,0.598,0.238,0.315,12 +F,0.61,0.495,0.175,1.2635,0.53,0.315,0.3455,10 +F,0.61,0.47,0.16,1.0745,0.4925,0.236,0.29,8 +M,0.615,0.505,0.19,1.403,0.6715,0.2925,0.365,8 +M,0.62,0.485,0.165,1.1325,0.5235,0.2505,0.2825,9 +F,0.625,0.495,0.16,1.1115,0.4495,0.2825,0.345,11 +F,0.625,0.47,0.17,1.255,0.525,0.2415,0.405,10 +M,0.625,0.485,0.17,1.437,0.5855,0.293,0.475,11 +M,0.635,0.495,0.155,1.3635,0.583,0.2985,0.295,10 +F,0.64,0.48,0.195,1.1435,0.4915,0.2345,0.353,9 +M,0.64,0.5,0.17,1.4545,0.642,0.3575,0.354,9 +M,0.66,0.525,0.18,1.478,0.5815,0.381,0.372,10 +F,0.665,0.52,0.165,1.6885,0.7295,0.407,0.4265,11 +F,0.715,0.585,0.23,2.0725,0.8655,0.4095,0.565,10 +M,0.72,0.565,0.2,1.787,0.718,0.385,0.529,11 +F,0.725,0.58,0.185,1.523,0.8045,0.3595,0.4375,9 +I,0.165,0.12,0.05,0.021,0.0075,0.0045,0.014,3 +I,0.21,0.15,0.055,0.0455,0.02,0.0065,0.013,4 +I,0.355,0.265,0.085,0.2435,0.122,0.0525,0.06,6 +I,0.4,0.315,0.085,0.2675,0.116,0.0585,0.0765,6 +I,0.4,0.29,0.1,0.258,0.104,0.059,0.0815,7 +I,0.4,0.3,0.11,0.2985,0.1375,0.071,0.075,6 +I,0.435,0.335,0.11,0.411,0.2025,0.0945,0.1,7 +I,0.44,0.33,0.11,0.38,0.197,0.079,0.09,7 +I,0.45,0.34,0.105,0.4385,0.21,0.0925,0.12,8 +I,0.465,0.345,0.105,0.4015,0.242,0.0345,0.109,6 +I,0.47,0.355,0.145,0.4485,0.156,0.102,0.123,7 +I,0.47,0.355,0.115,0.4155,0.167,0.084,0.139,7 +I,0.475,0.42,0.16,0.7095,0.35,0.1505,0.1845,8 +I,0.485,0.37,0.115,0.637,0.38,0.1335,0.128,7 +F,0.505,0.475,0.16,1.1155,0.509,0.239,0.3065,8 +I,0.51,0.405,0.13,0.599,0.3065,0.1155,0.1485,8 +I,0.52,0.38,0.13,0.5345,0.2375,0.122,0.1535,8 +F,0.53,0.42,0.14,0.627,0.2905,0.1165,0.183,8 +M,0.535,0.42,0.16,0.7465,0.348,0.1515,0.2185,10 +M,0.55,0.44,0.16,0.985,0.4645,0.201,0.27,8 +M,0.555,0.44,0.145,0.85,0.4165,0.1685,0.23,8 +M,0.555,0.44,0.15,0.838,0.4155,0.146,0.23,8 +F,0.555,0.43,0.135,0.812,0.4055,0.163,0.2215,9 +M,0.56,0.415,0.13,0.7615,0.3695,0.17,0.1955,8 +M,0.575,0.44,0.145,0.87,0.3945,0.2195,0.225,8 +F,0.585,0.45,0.145,0.9835,0.4845,0.242,0.22,9 +M,0.59,0.46,0.145,0.929,0.38,0.24,0.255,10 +F,0.595,0.47,0.165,1.0155,0.491,0.1905,0.289,9 +M,0.6,0.41,0.145,0.939,0.4475,0.196,0.268,8 +M,0.6,0.475,0.16,1.164,0.5045,0.2635,0.335,12 +M,0.61,0.47,0.175,1.214,0.5315,0.2835,0.325,10 +F,0.615,0.49,0.19,1.1345,0.4695,0.257,0.348,11 +F,0.62,0.51,0.18,1.233,0.592,0.274,0.322,10 +M,0.625,0.495,0.18,1.0815,0.4715,0.254,0.3135,10 +M,0.625,0.47,0.175,1.179,0.605,0.258,0.271,9 +F,0.64,0.5,0.165,1.1635,0.554,0.239,0.32,11 +F,0.64,0.475,0.175,1.1545,0.4865,0.341,0.288,9 +F,0.645,0.52,0.175,1.3345,0.667,0.2665,0.355,10 +M,0.65,0.505,0.18,1.469,0.7115,0.3335,0.38,9 +M,0.655,0.52,0.18,1.492,0.7185,0.36,0.355,11 +F,0.655,0.54,0.175,1.5585,0.7285,0.402,0.385,11 +F,0.66,0.5,0.175,1.3275,0.556,0.2805,0.4085,9 +M,0.67,0.525,0.18,1.6615,0.8005,0.3645,0.43,10 +F,0.69,0.525,0.19,1.492,0.6425,0.3905,0.42,12 +F,0.7,0.575,0.2,1.7365,0.7755,0.3965,0.461,11 +F,0.7,0.56,0.175,1.6605,0.8605,0.3275,0.398,11 +M,0.71,0.57,0.195,1.348,0.8985,0.4435,0.4535,11 +M,0.715,0.545,0.18,1.7405,0.871,0.347,0.449,10 +F,0.72,0.545,0.185,1.7185,0.7925,0.401,0.468,11 +I,0.215,0.15,0.055,0.041,0.015,0.009,0.0125,3 +I,0.24,0.185,0.06,0.0655,0.0295,5e-04,0.02,4 +I,0.26,0.205,0.07,0.097,0.0415,0.019,0.0305,4 +I,0.32,0.24,0.085,0.131,0.0615,0.0265,0.038,6 +I,0.33,0.23,0.085,0.1695,0.079,0.026,0.0505,6 +I,0.335,0.26,0.085,0.192,0.097,0.03,0.054,6 +I,0.35,0.26,0.09,0.1765,0.072,0.0355,0.0575,7 +I,0.35,0.265,0.085,0.1735,0.0775,0.034,0.056,6 +I,0.36,0.265,0.075,0.1785,0.0785,0.035,0.054,6 +I,0.36,0.265,0.09,0.2055,0.096,0.037,0.0585,7 +I,0.365,0.275,0.09,0.2345,0.108,0.051,0.0625,7 +I,0.38,0.285,0.09,0.2305,0.1005,0.039,0.0775,7 +I,0.4,0.31,0.115,0.314,0.1545,0.0595,0.087,6 +I,0.4,0.315,0.09,0.33,0.151,0.068,0.08,6 +I,0.4,0.265,0.1,0.2775,0.1245,0.0605,0.08,9 +I,0.425,0.325,0.11,0.405,0.1695,0.092,0.1065,8 +I,0.43,0.325,0.105,0.309,0.119,0.08,0.098,6 +M,0.435,0.335,0.11,0.4385,0.2075,0.0715,0.1315,7 +I,0.435,0.34,0.12,0.396,0.1775,0.081,0.125,8 +I,0.445,0.355,0.095,0.3615,0.1415,0.0785,0.12,8 +I,0.45,0.35,0.11,0.514,0.253,0.1045,0.14,8 +I,0.455,0.435,0.11,0.4265,0.195,0.09,0.1205,8 +I,0.46,0.34,0.09,0.384,0.1795,0.068,0.11,8 +I,0.475,0.355,0.125,0.4865,0.2155,0.1105,0.142,9 +I,0.475,0.36,0.135,0.4355,0.196,0.0925,0.125,8 +I,0.475,0.35,0.115,0.498,0.2375,0.099,0.14,7 +I,0.48,0.355,0.125,0.494,0.2385,0.0835,0.15,9 +F,0.495,0.37,0.12,0.594,0.28,0.11,0.1375,7 +I,0.5,0.365,0.125,0.528,0.229,0.103,0.1645,9 +M,0.505,0.39,0.115,0.5585,0.2575,0.119,0.1535,8 +I,0.515,0.4,0.135,0.636,0.3055,0.1215,0.1855,9 +I,0.525,0.39,0.105,0.567,0.2875,0.1075,0.16,8 +I,0.53,0.405,0.13,0.6615,0.2945,0.1395,0.19,9 +I,0.53,0.42,0.13,0.658,0.296,0.1245,0.198,8 +M,0.535,0.415,0.135,0.78,0.3165,0.169,0.2365,8 +I,0.535,0.41,0.13,0.6075,0.268,0.1225,0.1975,9 +I,0.54,0.41,0.135,0.7025,0.31,0.177,0.2,8 +I,0.55,0.425,0.155,0.8725,0.412,0.187,0.2425,10 +F,0.565,0.45,0.175,1.2365,0.5305,0.2455,0.308,10 +M,0.57,0.47,0.155,1.186,0.6355,0.2315,0.277,10 +I,0.57,0.42,0.13,0.7745,0.3535,0.1505,0.2365,9 +F,0.57,0.42,0.16,0.8875,0.4315,0.1915,0.223,8 +I,0.575,0.455,0.155,0.8725,0.349,0.2095,0.285,8 +I,0.575,0.44,0.125,0.8515,0.4555,0.1715,0.1965,9 +F,0.575,0.475,0.16,0.895,0.3605,0.221,0.271,9 +M,0.575,0.45,0.155,0.886,0.3605,0.211,0.2575,9 +I,0.58,0.46,0.14,0.9265,0.4135,0.1845,0.27,10 +I,0.58,0.46,0.14,0.8295,0.3915,0.165,0.238,10 +I,0.58,0.47,0.15,0.907,0.444,0.1855,0.2445,11 +M,0.58,0.47,0.165,1.041,0.54,0.166,0.279,9 +F,0.585,0.465,0.165,0.9355,0.4035,0.2275,0.259,9 +F,0.585,0.46,0.165,1.058,0.486,0.25,0.294,9 +F,0.595,0.465,0.145,0.7955,0.3425,0.1795,0.2425,10 +F,0.6,0.47,0.17,1.0805,0.4995,0.2245,0.3205,9 +M,0.6,0.47,0.15,0.928,0.4225,0.183,0.275,8 +F,0.6,0.475,0.155,1.059,0.441,0.19,0.39,11 +M,0.6,0.475,0.23,1.157,0.522,0.2235,0.36,11 +F,0.6,0.475,0.17,1.088,0.4905,0.2475,0.31,10 +F,0.6,0.485,0.145,0.776,0.3545,0.1585,0.239,9 +F,0.62,0.48,0.165,1.043,0.4835,0.221,0.31,10 +M,0.625,0.48,0.16,1.1415,0.5795,0.2145,0.29,9 +F,0.625,0.475,0.16,1.3335,0.605,0.2875,0.319,10 +F,0.625,0.5,0.175,1.273,0.564,0.302,0.374,9 +M,0.625,0.49,0.165,1.1835,0.517,0.2375,0.39,11 +M,0.625,0.485,0.16,1.2135,0.631,0.2235,0.302,9 +I,0.63,0.465,0.15,1.0315,0.4265,0.24,0.325,11 +M,0.635,0.495,0.17,1.3695,0.657,0.3055,0.365,10 +M,0.65,0.515,0.185,1.3745,0.75,0.1805,0.369,12 +M,0.65,0.515,0.18,1.463,0.658,0.3135,0.4115,11 +F,0.65,0.52,0.195,1.6275,0.689,0.3905,0.432,11 +F,0.65,0.475,0.165,1.3875,0.58,0.3485,0.3095,9 +M,0.655,0.525,0.16,1.46,0.686,0.311,0.405,11 +F,0.655,0.53,0.165,1.2835,0.583,0.1255,0.4,8 +F,0.66,0.5,0.155,1.3765,0.6485,0.288,0.335,12 +M,0.66,0.515,0.2,1.6465,0.749,0.422,0.401,11 +M,0.675,0.515,0.145,1.265,0.6025,0.299,0.325,10 +M,0.685,0.53,0.17,1.56,0.647,0.383,0.465,11 +M,0.715,0.52,0.18,1.6,0.708,0.3525,0.445,12 +M,0.735,0.555,0.22,2.333,1.2395,0.3645,0.6195,12 +I,0.175,0.125,0.04,0.028,0.0095,0.008,0.009,4 +I,0.37,0.285,0.095,0.226,0.1135,0.0515,0.0675,8 +I,0.395,0.3,0.09,0.2855,0.1385,0.0625,0.077,5 +I,0.42,0.325,0.11,0.325,0.1245,0.0755,0.1025,7 +I,0.455,0.37,0.11,0.514,0.2385,0.1235,0.126,8 +I,0.495,0.375,0.115,0.5755,0.31,0.1145,0.1395,8 +F,0.51,0.375,0.11,0.5805,0.2865,0.118,0.148,7 +M,0.515,0.39,0.14,0.678,0.341,0.1325,0.119,8 +M,0.545,0.43,0.155,0.8035,0.409,0.144,0.228,7 +F,0.555,0.405,0.12,0.913,0.4585,0.196,0.2065,9 +M,0.58,0.45,0.16,0.8675,0.3935,0.221,0.215,9 +F,0.59,0.465,0.17,1.0425,0.4635,0.24,0.27,10 +M,0.6,0.46,0.18,1.14,0.423,0.2575,0.365,10 +F,0.61,0.49,0.17,1.3475,0.7045,0.25,0.3045,11 +M,0.615,0.475,0.155,1.0735,0.4375,0.2585,0.31,11 +M,0.615,0.475,0.19,1.4335,0.7315,0.305,0.3285,9 +M,0.615,0.495,0.2,1.304,0.5795,0.3115,0.371,14 +M,0.62,0.46,0.16,0.9505,0.4915,0.2,0.228,9 +M,0.63,0.515,0.17,1.385,0.6355,0.2955,0.38,11 +F,0.64,0.5,0.17,1.12,0.4955,0.2645,0.32,12 +F,0.64,0.5,0.17,1.2645,0.565,0.3375,0.315,9 +F,0.655,0.455,0.17,1.275,0.583,0.303,0.333,8 +M,0.655,0.505,0.165,1.27,0.6035,0.262,0.335,10 +M,0.66,0.53,0.175,1.583,0.7395,0.3505,0.405,10 +F,0.665,0.5,0.175,1.4355,0.643,0.345,0.37,9 +F,0.67,0.525,0.195,1.42,0.573,0.368,0.3905,10 +M,0.69,0.53,0.19,1.5955,0.678,0.331,0.48,10 +M,0.715,0.525,0.2,1.89,0.95,0.436,0.4305,10 +F,0.735,0.565,0.225,2.037,0.87,0.5145,0.5675,13 +I,0.27,0.205,0.05,0.084,0.03,0.0185,0.029,6 +I,0.285,0.225,0.07,0.1005,0.0425,0.0185,0.035,7 +I,0.295,0.22,0.085,0.1285,0.0585,0.027,0.0365,5 +I,0.3,0.225,0.075,0.1345,0.057,0.028,0.044,5 +I,0.3,0.22,0.065,0.1195,0.052,0.0155,0.035,5 +I,0.36,0.265,0.085,0.1895,0.0725,0.0515,0.055,6 +I,0.37,0.275,0.095,0.257,0.1015,0.055,0.0825,6 +I,0.39,0.29,0.09,0.2745,0.135,0.0455,0.078,8 +I,0.435,0.325,0.1,0.342,0.1335,0.0835,0.105,6 +I,0.44,0.34,0.105,0.344,0.123,0.081,0.125,8 +I,0.44,0.32,0.095,0.3275,0.1495,0.059,0.1,8 +I,0.445,0.345,0.12,0.4035,0.169,0.0825,0.13,7 +I,0.465,0.37,0.115,0.4075,0.1515,0.0935,0.1455,9 +I,0.465,0.355,0.12,0.4975,0.2375,0.099,0.14,8 +I,0.47,0.345,0.12,0.3685,0.1525,0.0615,0.125,8 +I,0.475,0.365,0.105,0.4175,0.1645,0.099,0.127,7 +I,0.475,0.335,0.1,0.4425,0.1895,0.086,0.135,9 +I,0.475,0.35,0.125,0.4225,0.1905,0.079,0.1355,9 +I,0.485,0.365,0.125,0.426,0.163,0.0965,0.151,8 +I,0.49,0.39,0.12,0.511,0.2205,0.103,0.1745,9 +I,0.515,0.405,0.13,0.573,0.213,0.134,0.195,9 +I,0.52,0.415,0.14,0.6385,0.2945,0.1405,0.171,8 +I,0.525,0.405,0.125,0.657,0.2985,0.1505,0.168,10 +F,0.525,0.425,0.14,0.8735,0.4205,0.182,0.2225,10 +I,0.53,0.425,0.13,0.781,0.3905,0.2005,0.215,9 +I,0.53,0.42,0.14,0.6765,0.256,0.1855,0.208,9 +M,0.53,0.41,0.125,0.769,0.346,0.173,0.215,9 +I,0.53,0.395,0.125,0.6235,0.2975,0.108,0.195,11 +M,0.535,0.405,0.14,0.7315,0.336,0.156,0.19,7 +I,0.535,0.45,0.155,0.8075,0.3655,0.148,0.2595,10 +M,0.545,0.41,0.14,0.737,0.349,0.15,0.212,9 +F,0.545,0.41,0.125,0.654,0.2945,0.1315,0.205,10 +I,0.55,0.415,0.15,0.7915,0.3535,0.176,0.236,10 +I,0.55,0.45,0.14,0.753,0.3445,0.1325,0.24,8 +I,0.55,0.4,0.135,0.717,0.3315,0.1495,0.221,9 +I,0.555,0.43,0.15,0.783,0.345,0.1755,0.247,9 +I,0.575,0.45,0.145,0.872,0.4675,0.18,0.217,9 +I,0.575,0.44,0.15,0.983,0.486,0.215,0.239,8 +F,0.585,0.42,0.155,1.034,0.437,0.2225,0.32,11 +F,0.585,0.465,0.145,0.9855,0.4325,0.2145,0.2845,10 +I,0.585,0.46,0.14,0.7635,0.326,0.153,0.265,9 +M,0.59,0.465,0.135,0.9895,0.4235,0.199,0.28,8 +I,0.595,0.47,0.135,0.9365,0.434,0.184,0.287,10 +F,0.595,0.44,0.135,0.964,0.5005,0.1715,0.2575,10 +F,0.595,0.46,0.155,1.0455,0.4565,0.24,0.3085,10 +F,0.595,0.45,0.165,1.081,0.49,0.2525,0.279,12 +M,0.6,0.47,0.16,1.012,0.441,0.2015,0.305,10 +F,0.6,0.5,0.16,1.122,0.5095,0.256,0.309,10 +M,0.605,0.49,0.165,1.1245,0.492,0.222,0.3555,11 +F,0.605,0.49,0.15,1.1345,0.4305,0.2525,0.35,10 +M,0.61,0.45,0.19,1.0805,0.517,0.2495,0.2935,10 +F,0.61,0.495,0.165,1.0835,0.4525,0.273,0.317,9 +M,0.615,0.47,0.175,1.242,0.5675,0.287,0.317,11 +M,0.62,0.5,0.18,1.3915,0.726,0.2795,0.332,11 +M,0.62,0.525,0.155,1.085,0.454,0.1965,0.35,10 +I,0.62,0.47,0.155,0.966,0.447,0.171,0.284,11 +M,0.62,0.48,0.165,1.0855,0.481,0.2575,0.305,10 +F,0.625,0.485,0.135,1.3025,0.61,0.2675,0.3605,14 +I,0.625,0.485,0.16,1.15,0.5255,0.257,0.3315,11 +I,0.63,0.49,0.17,1.217,0.5515,0.212,0.31,11 +F,0.63,0.505,0.195,1.306,0.516,0.3305,0.375,9 +M,0.64,0.5,0.175,1.273,0.5065,0.2925,0.405,13 +M,0.645,0.51,0.19,1.4865,0.6445,0.296,0.425,12 +M,0.65,0.52,0.17,1.3655,0.6155,0.2885,0.36,11 +M,0.65,0.495,0.17,1.276,0.6215,0.2305,0.399,11 +M,0.65,0.495,0.16,1.2075,0.55,0.2695,0.32,10 +F,0.65,0.52,0.195,1.281,0.5985,0.246,0.3825,10 +M,0.65,0.525,0.205,1.4275,0.69,0.306,0.4355,13 +M,0.65,0.51,0.175,1.155,0.4955,0.2025,0.385,12 +F,0.65,0.51,0.175,1.35,0.575,0.3155,0.3885,10 +M,0.65,0.525,0.19,1.3685,0.5975,0.296,0.4,11 +F,0.66,0.53,0.17,1.431,0.622,0.309,0.398,10 +M,0.66,0.51,0.18,1.261,0.5,0.2335,0.339,10 +F,0.665,0.54,0.195,1.764,0.8505,0.3615,0.47,11 +F,0.67,0.51,0.155,1.278,0.5605,0.3045,0.358,11 +M,0.67,0.54,0.195,1.217,0.532,0.2735,0.3315,11 +F,0.67,0.54,0.2,1.46,0.6435,0.328,0.4165,9 +F,0.675,0.535,0.185,1.5575,0.7035,0.402,0.4,11 +M,0.675,0.51,0.17,1.527,0.809,0.318,0.341,11 +F,0.675,0.53,0.195,1.4985,0.62,0.375,0.425,9 +M,0.685,0.55,0.19,1.885,0.89,0.41,0.4895,10 +M,0.685,0.535,0.175,1.432,0.637,0.247,0.46,11 +M,0.705,0.55,0.21,1.4385,0.655,0.3255,0.462,11 +F,0.705,0.53,0.17,1.564,0.612,0.394,0.44,10 +M,0.71,0.555,0.175,2.14,1.2455,0.3725,0.434,11 +F,0.725,0.56,0.185,1.792,0.873,0.367,0.435,11 +M,0.78,0.6,0.21,2.548,1.1945,0.5745,0.6745,11 +I,0.235,0.13,0.075,0.1585,0.0685,0.037,0.0465,5 +I,0.35,0.25,0.1,0.4015,0.1725,0.063,0.1255,7 +I,0.36,0.25,0.115,0.465,0.21,0.1055,0.128,7 +I,0.38,0.28,0.095,0.2885,0.165,0.0435,0.067,7 +F,0.38,0.32,0.115,0.6475,0.323,0.1325,0.164,7 +M,0.43,0.31,0.13,0.6485,0.2735,0.163,0.184,9 +I,0.465,0.36,0.105,0.452,0.22,0.159,0.1035,9 +I,0.47,0.355,0.12,0.4915,0.1765,0.1125,0.1325,9 +F,0.485,0.365,0.15,0.9145,0.4145,0.199,0.273,7 +M,0.495,0.375,0.155,0.976,0.45,0.2285,0.2475,9 +I,0.5,0.395,0.145,0.7865,0.332,0.1815,0.2455,8 +M,0.505,0.4,0.15,0.775,0.3445,0.157,0.185,7 +I,0.51,0.375,0.15,0.8415,0.3845,0.156,0.255,10 +M,0.51,0.38,0.135,0.681,0.3435,0.142,0.17,9 +M,0.515,0.37,0.115,0.6145,0.3415,0.155,0.146,9 +F,0.55,0.415,0.18,1.1655,0.502,0.301,0.311,9 +F,0.575,0.42,0.19,1.764,0.914,0.377,0.4095,10 +M,0.605,0.455,0.16,1.1215,0.533,0.273,0.271,10 +M,0.615,0.505,0.165,1.167,0.4895,0.2955,0.345,10 +M,0.615,0.475,0.15,1.0375,0.476,0.2325,0.283,9 +M,0.625,0.48,0.18,1.223,0.565,0.2975,0.3375,10 +M,0.625,0.47,0.15,1.124,0.556,0.2315,0.287,9 +F,0.635,0.505,0.17,1.2635,0.512,0.322,0.355,9 +F,0.65,0.525,0.165,1.238,0.647,0.2485,0.3005,9 +F,0.65,0.5,0.17,1.4045,0.694,0.318,0.3235,11 +F,0.67,0.525,0.195,1.37,0.6065,0.2955,0.407,12 +F,0.695,0.525,0.205,1.8185,0.819,0.4025,0.4525,13 +F,0.705,0.555,0.195,1.7525,0.7105,0.4215,0.516,12 +I,0.275,0.205,0.065,0.101,0.041,0.021,0.034,5 +I,0.285,0.205,0.07,0.106,0.039,0.0285,0.034,5 +I,0.36,0.265,0.085,0.1865,0.0675,0.037,0.0615,7 +I,0.385,0.29,0.1,0.2575,0.1,0.061,0.086,6 +I,0.4,0.315,0.1,0.3225,0.143,0.0735,0.091,6 +I,0.43,0.33,0.095,0.32,0.118,0.065,0.123,7 +I,0.435,0.375,0.11,0.4155,0.17,0.076,0.145,8 +I,0.45,0.335,0.115,0.3935,0.195,0.071,0.11,7 +I,0.475,0.355,0.135,0.4775,0.2145,0.09,0.1435,8 +I,0.475,0.36,0.11,0.452,0.191,0.099,0.13,8 +I,0.485,0.37,0.14,0.5065,0.2425,0.088,0.1465,8 +I,0.51,0.395,0.105,0.5525,0.234,0.127,0.165,8 +I,0.515,0.39,0.12,0.565,0.235,0.135,0.179,9 +I,0.52,0.41,0.14,0.699,0.3395,0.129,0.1945,10 +I,0.525,0.4,0.14,0.6055,0.2605,0.108,0.21,9 +M,0.53,0.425,0.155,0.7905,0.307,0.171,0.2595,9 +M,0.53,0.425,0.13,0.702,0.2975,0.1395,0.22,9 +M,0.53,0.42,0.135,0.675,0.294,0.156,0.1825,10 +I,0.53,0.395,0.115,0.475,0.2025,0.101,0.148,8 +I,0.53,0.41,0.15,0.612,0.2435,0.1525,0.1895,11 +I,0.535,0.4,0.145,0.705,0.3065,0.1365,0.22,10 +I,0.535,0.45,0.135,0.728,0.2845,0.1845,0.265,9 +F,0.555,0.44,0.14,0.846,0.346,0.1715,0.2735,10 +M,0.555,0.46,0.16,0.86,0.3345,0.1935,0.275,10 +M,0.56,0.465,0.145,0.8875,0.3345,0.22,0.2695,9 +F,0.56,0.43,0.145,0.898,0.3895,0.2325,0.245,9 +I,0.565,0.43,0.125,0.6545,0.2815,0.139,0.21,9 +I,0.575,0.45,0.145,0.795,0.364,0.1505,0.26,10 +M,0.575,0.465,0.12,1.0535,0.516,0.2185,0.235,9 +F,0.575,0.46,0.15,0.927,0.333,0.207,0.2985,9 +I,0.58,0.42,0.14,0.701,0.3285,0.102,0.2255,9 +M,0.58,0.45,0.155,0.8275,0.321,0.1975,0.2445,8 +F,0.585,0.42,0.155,0.9845,0.442,0.2155,0.2875,13 +M,0.585,0.47,0.145,0.9565,0.4025,0.2365,0.265,9 +I,0.59,0.45,0.125,0.86,0.437,0.1515,0.245,9 +M,0.595,0.48,0.185,1.1785,0.526,0.2975,0.314,10 +M,0.615,0.48,0.185,1.2205,0.4985,0.315,0.33,10 +M,0.615,0.455,0.13,0.9685,0.49,0.182,0.2655,10 +F,0.62,0.5,0.175,1.107,0.4895,0.24,0.343,11 +I,0.62,0.48,0.18,1.1305,0.5285,0.2655,0.306,12 +M,0.62,0.48,0.155,1.2555,0.527,0.374,0.3175,11 +M,0.625,0.495,0.155,1.177,0.5055,0.278,0.345,9 +M,0.625,0.5,0.185,1.2425,0.5995,0.248,0.335,10 +M,0.63,0.49,0.16,1.09,0.407,0.224,0.354,12 +F,0.63,0.475,0.15,1.072,0.433,0.2975,0.315,8 +F,0.645,0.51,0.155,1.129,0.5015,0.24,0.342,10 +F,0.65,0.505,0.175,1.2075,0.5105,0.262,0.39,10 +F,0.65,0.495,0.175,1.227,0.528,0.258,0.37,11 +F,0.655,0.52,0.175,1.472,0.6275,0.27,0.45,13 +F,0.665,0.525,0.18,1.5785,0.678,0.229,0.456,14 +M,0.67,0.52,0.175,1.4755,0.6275,0.379,0.374,10 +M,0.675,0.54,0.175,1.5545,0.6645,0.278,0.512,12 +F,0.675,0.54,0.21,1.593,0.686,0.318,0.45,11 +M,0.695,0.58,0.2,1.8995,0.675,0.478,0.5295,13 +F,0.695,0.535,0.175,1.361,0.5465,0.2815,0.465,10 +F,0.705,0.56,0.17,1.4575,0.607,0.318,0.44,11 +M,0.74,0.58,0.205,2.381,0.8155,0.4695,0.488,12 +I,0.205,0.155,0.045,0.0495,0.0235,0.011,0.014,3 +I,0.305,0.23,0.075,0.1455,0.0595,0.0305,0.05,6 +I,0.32,0.23,0.06,0.129,0.0615,0.0275,0.0355,7 +I,0.355,0.27,0.1,0.2255,0.11,0.042,0.064,7 +M,0.425,0.305,0.11,0.359,0.173,0.0875,0.0975,9 +I,0.425,0.31,0.095,0.3505,0.1645,0.071,0.1,8 +F,0.45,0.365,0.115,0.5885,0.318,0.121,0.1325,8 +M,0.515,0.385,0.13,0.623,0.2855,0.1285,0.175,10 +F,0.52,0.375,0.135,0.5375,0.221,0.117,0.17,8 +I,0.525,0.4,0.125,0.5655,0.2435,0.119,0.175,8 +M,0.555,0.445,0.13,0.8625,0.4225,0.155,0.24,9 +F,0.61,0.49,0.17,1.137,0.4605,0.2825,0.344,12 +I,0.35,0.26,0.095,0.221,0.0985,0.043,0.07,8 +I,0.38,0.275,0.095,0.2425,0.106,0.0485,0.21,6 +I,0.46,0.34,0.1,0.386,0.1805,0.0875,0.0965,8 +M,0.465,0.355,0.12,0.5315,0.2725,0.097,0.1395,8 +M,0.475,0.385,0.12,0.562,0.289,0.0905,0.153,8 +M,0.565,0.445,0.14,0.836,0.406,0.1605,0.2245,9 +M,0.57,0.45,0.14,0.9275,0.477,0.1605,0.2515,8 +M,0.57,0.44,0.145,0.8815,0.3605,0.1955,0.2735,10 +M,0.595,0.46,0.155,1.03,0.4275,0.207,0.3305,10 +F,0.605,0.48,0.175,1.1685,0.4815,0.2305,0.356,9 +F,0.615,0.455,0.135,1.059,0.4735,0.263,0.274,9 +M,0.62,0.46,0.17,1.127,0.535,0.2635,0.296,7 +M,0.625,0.47,0.17,1.1665,0.4605,0.2565,0.3945,11 +F,0.68,0.52,0.185,1.541,0.5985,0.395,0.4575,10 +M,0.68,0.54,0.195,1.7825,0.5565,0.3235,0.4285,11 +M,0.68,0.52,0.175,1.543,0.7525,0.351,0.374,11 +F,0.71,0.555,0.17,1.47,0.5375,0.38,0.431,12 +M,0.5,0.385,0.12,0.6335,0.2305,0.125,0.235,14 +F,0.545,0.42,0.175,0.754,0.256,0.1775,0.275,10 +F,0.46,0.365,0.115,0.4485,0.165,0.083,0.17,14 +M,0.535,0.41,0.15,0.8105,0.345,0.187,0.24,11 +M,0.335,0.26,0.075,0.22,0.0855,0.04,0.085,6 +F,0.425,0.35,0.1,0.4425,0.175,0.0755,0.175,7 +M,0.41,0.325,0.1,0.3555,0.146,0.072,0.105,9 +I,0.17,0.105,0.035,0.034,0.012,0.0085,0.005,4 +I,0.335,0.25,0.095,0.185,0.0795,0.0495,0.055,8 +M,0.52,0.425,0.125,0.79,0.372,0.205,0.19,8 +F,0.53,0.41,0.145,0.8255,0.375,0.204,0.245,9 +M,0.5,0.42,0.125,0.62,0.255,0.15,0.205,11 +F,0.615,0.475,0.145,0.9525,0.3915,0.195,0.32,9 +M,0.575,0.45,0.16,0.955,0.44,0.1685,0.27,16 +M,0.57,0.45,0.155,0.91,0.326,0.1895,0.355,14 +M,0.455,0.35,0.105,0.416,0.1625,0.097,0.145,11 +I,0.37,0.275,0.085,0.2045,0.096,0.056,0.08,6 +M,0.445,0.37,0.125,0.515,0.2495,0.087,0.159,9 +F,0.675,0.535,0.22,1.604,0.6175,0.4255,0.453,14 +M,0.385,0.3,0.115,0.3435,0.1645,0.085,0.1025,6 +F,0.375,0.295,0.11,0.3005,0.1255,0.0575,0.1035,7 +M,0.56,0.44,0.13,0.8255,0.2425,0.202,0.285,10 +M,0.55,0.41,0.15,0.785,0.282,0.186,0.275,12 +F,0.57,0.465,0.155,0.9685,0.446,0.261,0.255,9 +F,0.485,0.4,0.155,0.731,0.236,0.183,0.255,11 +M,0.41,0.335,0.115,0.4405,0.19,0.085,0.135,8 +I,0.335,0.255,0.085,0.1785,0.071,0.0405,0.055,9 +M,0.655,0.515,0.2,1.373,0.443,0.3375,0.49,16 +F,0.565,0.45,0.165,0.9765,0.322,0.244,0.37,12 +F,0.57,0.44,0.19,1.018,0.447,0.207,0.265,9 +F,0.55,0.465,0.15,1.082,0.3575,0.194,0.19,14 +F,0.63,0.475,0.175,1.423,0.4155,0.3385,0.49,14 +M,0.475,0.37,0.125,0.655,0.266,0.1725,0.185,10 +F,0.655,0.5,0.18,1.4155,0.508,0.314,0.445,18 +I,0.32,0.235,0.065,0.1385,0.058,0.0225,0.05,5 +M,0.525,0.395,0.165,0.782,0.285,0.1405,0.285,19 +F,0.525,0.43,0.165,0.717,0.289,0.1745,0.195,10 +F,0.5,0.39,0.13,0.6355,0.2505,0.1635,0.195,15 +F,0.44,0.34,0.135,0.3975,0.1505,0.0945,0.135,8 +F,0.49,0.385,0.16,0.656,0.2455,0.171,0.205,9 +M,0.545,0.44,0.165,0.744,0.2875,0.204,0.25,15 +F,0.45,0.36,0.11,0.447,0.203,0.082,0.13,12 +F,0.515,0.4,0.115,0.578,0.191,0.1445,0.17,9 +I,0.33,0.25,0.075,0.1405,0.056,0.035,0.05,5 +F,0.525,0.41,0.15,0.708,0.274,0.151,0.25,12 +M,0.295,0.225,0.09,0.1385,0.048,0.046,0.05,9 +M,0.545,0.45,0.16,0.8615,0.2925,0.1545,0.365,16 +F,0.645,0.5,0.225,1.626,0.587,0.4055,0.41,15 +M,0.45,0.355,0.115,0.478,0.18,0.1185,0.155,10 +F,0.61,0.49,0.17,1.1775,0.5655,0.2385,0.295,15 +I,0.38,0.3,0.1,0.286,0.1305,0.056,0.09,7 +F,0.565,0.455,0.13,1.058,0.439,0.2645,0.3,10 +F,0.67,0.545,0.16,1.5415,0.5985,0.2565,0.495,15 +M,0.54,0.425,0.12,0.817,0.2945,0.153,0.195,10 +I,0.29,0.225,0.075,0.152,0.071,0.059,0.045,9 +I,0.41,0.33,0.105,0.335,0.1525,0.074,0.11,7 +F,0.46,0.375,0.12,0.4915,0.2205,0.088,0.17,7 +F,0.56,0.44,0.155,0.9705,0.4315,0.263,0.255,9 +F,0.575,0.45,0.1,0.9315,0.431,0.222,0.235,12 +M,0.62,0.5,0.2,1.221,0.4605,0.263,0.43,12 +M,0.515,0.4,0.14,0.7365,0.2955,0.184,0.185,16 +F,0.56,0.46,0.18,0.97,0.342,0.196,0.355,12 +F,0.5,0.4,0.15,0.8085,0.273,0.112,0.295,13 +I,0.435,0.355,0.125,0.4075,0.1535,0.074,0.165,9 +M,0.495,0.38,0.135,0.6295,0.263,0.1425,0.215,12 +F,0.595,0.5,0.18,1.053,0.4405,0.192,0.39,13 +M,0.76,0.575,0.19,1.829,0.7035,0.386,0.56,14 +F,0.615,0.5,0.165,1.1765,0.488,0.244,0.345,17 +F,0.565,0.46,0.15,0.8765,0.3455,0.1925,0.275,10 +I,0.14,0.105,0.035,0.0145,0.005,0.0035,0.005,4 +M,0.445,0.345,0.14,0.476,0.2055,0.1015,0.1085,15 +F,0.525,0.43,0.125,0.813,0.3315,0.166,0.1775,12 +I,0.16,0.12,0.02,0.018,0.0075,0.0045,0.005,4 +M,0.635,0.48,0.235,1.064,0.413,0.228,0.36,16 +M,0.575,0.47,0.165,0.853,0.292,0.179,0.35,16 +M,0.38,0.27,0.095,0.219,0.0835,0.0515,0.07,6 +M,0.245,0.18,0.065,0.0635,0.0245,0.0135,0.02,4 +I,0.48,0.39,0.15,0.6275,0.276,0.134,0.185,13 +I,0.455,0.365,0.135,0.441,0.1515,0.1165,0.145,9 +F,0.455,0.375,0.125,0.458,0.1985,0.111,0.12,10 +M,0.455,0.355,0.135,0.4745,0.1865,0.0935,0.168,13 +I,0.355,0.27,0.1,0.216,0.083,0.037,0.075,10 +I,0.52,0.405,0.14,0.6765,0.2865,0.146,0.205,15 +I,0.54,0.4,0.145,0.757,0.315,0.181,0.215,11 +I,0.52,0.39,0.14,0.7325,0.2415,0.144,0.26,19 +I,0.56,0.445,0.165,1.0285,0.4535,0.253,0.275,11 +F,0.52,0.41,0.16,0.712,0.2845,0.153,0.225,10 +I,0.615,0.46,0.19,1.066,0.4335,0.226,0.33,13 +F,0.645,0.49,0.19,1.3065,0.479,0.3565,0.345,18 +I,0.565,0.43,0.135,0.8545,0.321,0.1775,0.275,11 +M,0.295,0.23,0.085,0.125,0.042,0.0285,0.043,8 +M,0.375,0.28,0.095,0.2225,0.0875,0.043,0.08,10 +I,0.525,0.4,0.14,0.6955,0.2405,0.16,0.253,10 +M,0.395,0.28,0.08,0.266,0.0995,0.066,0.09,12 +F,0.5,0.4,0.165,0.7105,0.27,0.1455,0.225,20 +F,0.47,0.35,0.115,0.487,0.1955,0.127,0.155,8 +I,0.58,0.42,0.16,0.728,0.2725,0.19,0.19,14 +I,0.5,0.38,0.155,0.6675,0.2745,0.156,0.18,12 +I,0.725,0.55,0.22,2.0495,0.7735,0.4405,0.655,10 +F,0.65,0.515,0.215,1.498,0.564,0.323,0.425,16 +F,0.67,0.535,0.185,1.597,0.6275,0.35,0.47,21 +I,0.55,0.44,0.165,0.8605,0.312,0.169,0.3,17 +F,0.49,0.37,0.115,0.541,0.171,0.1175,0.185,11 +I,0.235,0.18,0.06,0.058,0.022,0.0145,0.018,6 +I,0.235,0.175,0.08,0.0645,0.0215,0.0175,0.0215,5 +M,0.52,0.41,0.115,0.77,0.263,0.157,0.26,11 +F,0.475,0.4,0.115,0.541,0.186,0.1025,0.21,13 +M,0.53,0.425,0.11,0.739,0.237,0.161,0.295,13 +F,0.35,0.275,0.065,0.205,0.0745,0.0465,0.07,10 +M,0.555,0.42,0.145,0.8695,0.3075,0.2575,0.25,14 +M,0.505,0.39,0.105,0.6555,0.2595,0.18,0.19,11 +F,0.54,0.44,0.16,1.0905,0.391,0.2295,0.355,15 +F,0.525,0.4,0.115,0.6295,0.2555,0.144,0.18,11 +M,0.55,0.45,0.175,1.0985,0.3765,0.215,0.4,14 +M,0.55,0.44,0.16,0.991,0.348,0.168,0.375,20 +I,0.235,0.175,0.065,0.0615,0.0205,0.02,0.019,6 +M,0.525,0.41,0.165,0.8005,0.2635,0.1985,0.25,13 +M,0.475,0.365,0.14,0.6175,0.202,0.1445,0.19,16 +F,0.53,0.4,0.165,0.772,0.2855,0.1975,0.23,12 +F,0.525,0.415,0.15,0.7155,0.2355,0.171,0.27,13 +F,0.53,0.425,0.13,0.717,0.2115,0.166,0.255,13 +F,0.465,0.39,0.11,0.6355,0.1815,0.157,0.225,13 +I,0.315,0.235,0.08,0.18,0.08,0.045,0.047,5 +I,0.465,0.355,0.12,0.5805,0.255,0.0915,0.184,8 +M,0.485,0.385,0.105,0.556,0.296,0.104,0.133,7 +I,0.49,0.385,0.12,0.591,0.271,0.1125,0.1775,9 +F,0.515,0.395,0.14,0.686,0.281,0.1255,0.22,12 +F,0.555,0.44,0.155,1.016,0.4935,0.1855,0.263,10 +F,0.61,0.5,0.18,1.438,0.5185,0.3735,0.3345,9 +F,0.68,0.55,0.19,1.807,0.8225,0.3655,0.515,11 +M,0.69,0.55,0.195,1.777,0.769,0.38,0.4305,11 +M,0.695,0.55,0.205,2.173,1.133,0.4665,0.496,10 +F,0.72,0.575,0.195,2.1505,1.0745,0.382,0.585,10 +I,0.27,0.205,0.075,0.118,0.059,0.031,0.0305,4 +I,0.27,0.19,0.06,0.099,0.0445,0.017,0.03,5 +I,0.295,0.22,0.07,0.1365,0.0575,0.0295,0.035,6 +I,0.295,0.22,0.065,0.1295,0.052,0.028,0.035,6 +I,0.315,0.23,0.07,0.164,0.0625,0.04,0.045,6 +I,0.375,0.29,0.095,0.2875,0.123,0.0605,0.08,6 +I,0.38,0.3,0.09,0.277,0.1655,0.0625,0.082,6 +I,0.385,0.285,0.09,0.248,0.0935,0.066,0.07,6 +I,0.4,0.295,0.095,0.252,0.1105,0.0575,0.066,6 +M,0.415,0.315,0.12,0.4015,0.199,0.087,0.097,8 +I,0.415,0.33,0.1,0.3905,0.1925,0.0755,0.1025,7 +I,0.42,0.32,0.115,0.409,0.2055,0.0935,0.105,8 +I,0.44,0.33,0.135,0.4095,0.163,0.1005,0.119,6 +I,0.45,0.35,0.135,0.494,0.2205,0.0945,0.1405,7 +I,0.475,0.35,0.12,0.4905,0.2035,0.13,0.135,7 +M,0.485,0.39,0.12,0.599,0.251,0.1345,0.169,8 +M,0.495,0.375,0.115,0.6245,0.282,0.143,0.155,6 +F,0.525,0.41,0.115,0.7745,0.416,0.163,0.18,7 +M,0.565,0.455,0.15,0.9795,0.444,0.205,0.275,8 +I,0.58,0.435,0.15,0.8915,0.363,0.1925,0.2515,6 +F,0.585,0.45,0.125,0.874,0.3545,0.2075,0.225,6 +M,0.6,0.465,0.155,1.262,0.6245,0.2455,0.33,10 +M,0.63,0.48,0.185,1.21,0.53,0.2555,0.322,11 +F,0.645,0.525,0.17,1.37,0.6135,0.283,0.34,10 +F,0.655,0.545,0.185,1.759,0.6865,0.313,0.547,11 +M,0.665,0.515,0.165,1.3855,0.621,0.302,0.3445,8 +F,0.67,0.52,0.195,1.8065,0.758,0.3735,0.5055,11 +M,0.67,0.51,0.2,1.5945,0.6705,0.3845,0.4505,10 +M,0.685,0.51,0.18,1.4545,0.6315,0.3105,0.3725,9 +M,0.7,0.6,0.23,2.003,0.8105,0.4045,0.5755,10 +M,0.72,0.6,0.235,2.2385,0.984,0.411,0.621,12 +I,0.185,0.135,0.045,0.032,0.011,0.0065,0.01,4 +I,0.245,0.175,0.055,0.0785,0.04,0.018,0.02,5 +I,0.315,0.23,0,0.134,0.0575,0.0285,0.3505,6 +I,0.36,0.27,0.09,0.2075,0.098,0.039,0.062,6 +I,0.375,0.28,0.08,0.2235,0.115,0.043,0.055,6 +I,0.415,0.31,0.095,0.34,0.181,0.057,0.083,6 +I,0.455,0.35,0.135,0.5365,0.2855,0.0855,0.1325,7 +I,0.48,0.35,0.105,0.635,0.352,0.127,0.135,6 +I,0.485,0.375,0.125,0.562,0.2505,0.1345,0.1525,8 +I,0.51,0.39,0.125,0.597,0.293,0.1265,0.1555,8 +M,0.52,0.395,0.125,0.5815,0.2565,0.1265,0.17,10 +F,0.555,0.43,0.14,0.7545,0.3525,0.1835,0.2015,9 +M,0.585,0.465,0.15,0.98,0.4315,0.2545,0.247,9 +F,0.585,0.46,0.15,1.0035,0.503,0.2105,0.2515,11 +M,0.585,0.455,0.155,1.133,0.5515,0.223,0.305,12 +M,0.61,0.49,0.16,1.146,0.597,0.246,0.265,8 +M,0.61,0.475,0.15,1.142,0.62,0.237,0.245,9 +M,0.615,0.53,0.17,1.12,0.5775,0.2095,0.286,9 +F,0.62,0.465,0.14,1.011,0.479,0.2385,0.255,8 +M,0.625,0.505,0.175,1.131,0.5425,0.2265,0.323,8 +M,0.625,0.48,0.175,1.065,0.4865,0.259,0.285,10 +M,0.635,0.48,0.145,1.181,0.665,0.229,0.225,10 +F,0.64,0.525,0.175,1.382,0.646,0.3115,0.37,9 +M,0.66,0.505,0.19,1.4385,0.6775,0.285,0.178,11 +M,0.66,0.485,0.155,1.2275,0.61,0.274,0.3,8 +M,0.66,0.515,0.155,1.4415,0.7055,0.3555,0.335,10 +F,0.68,0.55,0.175,1.473,0.713,0.282,0.4295,11 +F,0.69,0.58,0.195,1.658,0.708,0.3615,0.4715,10 +M,0.72,0.545,0.195,1.7475,0.8215,0.383,0.4705,11 +I,0.275,0.2,0.07,0.096,0.037,0.0225,0.03,6 +I,0.33,0.245,0.065,0.1445,0.058,0.032,0.0505,6 +I,0.33,0.26,0.085,0.1965,0.0915,0.0425,0.055,7 +I,0.365,0.28,0.09,0.196,0.0865,0.036,0.0605,7 +I,0.365,0.27,0.09,0.2155,0.1005,0.049,0.0655,6 +I,0.42,0.31,0.1,0.2805,0.1125,0.0615,0.0925,8 +I,0.435,0.335,0.11,0.334,0.1355,0.0775,0.0965,7 +I,0.435,0.325,0.1,0.366,0.174,0.0725,0.109,7 +I,0.44,0.325,0.11,0.4965,0.258,0.1195,0.1075,8 +I,0.485,0.365,0.09,0.651,0.3165,0.132,0.18,8 +I,0.495,0.385,0.125,0.5125,0.2075,0.1155,0.172,10 +M,0.51,0.405,0.125,0.6925,0.327,0.155,0.1805,7 +I,0.52,0.41,0.14,0.5995,0.242,0.1375,0.182,11 +I,0.54,0.42,0.14,0.74,0.3595,0.159,0.1985,8 +I,0.54,0.415,0.155,0.702,0.322,0.167,0.19,10 +I,0.55,0.445,0.125,0.672,0.288,0.1365,0.21,11 +I,0.56,0.44,0.155,0.811,0.3685,0.178,0.235,11 +F,0.575,0.45,0.12,0.9585,0.447,0.169,0.275,12 +I,0.575,0.45,0.15,0.858,0.449,0.166,0.215,10 +F,0.575,0.46,0.165,0.9575,0.4815,0.1945,0.236,10 +F,0.58,0.46,0.135,0.926,0.4025,0.208,0.275,8 +F,0.58,0.425,0.155,0.873,0.3615,0.249,0.239,10 +M,0.59,0.45,0.16,0.998,0.445,0.214,0.301,9 +M,0.6,0.46,0.155,0.6655,0.285,0.149,0.269,11 +M,0.62,0.485,0.145,1.003,0.4655,0.2195,0.28,11 +F,0.625,0.495,0.16,1.234,0.6335,0.192,0.35,13 +M,0.625,0.495,0.155,1.025,0.46,0.1945,0.34,9 +M,0.625,0.495,0.175,1.2935,0.5805,0.317,0.355,9 +M,0.625,0.5,0.175,1.0565,0.4615,0.258,0.305,10 +M,0.625,0.47,0.145,1.7855,0.675,0.247,0.3245,13 +F,0.625,0.485,0.165,1.2255,0.5075,0.296,0.36,10 +F,0.635,0.5,0.18,1.2565,0.539,0.292,0.35,10 +F,0.645,0.5,0.15,1.159,0.4675,0.3355,0.31,9 +M,0.645,0.51,0.165,1.403,0.5755,0.2515,0.4545,11 +F,0.69,0.535,0.185,1.826,0.797,0.409,0.499,11 +F,0.695,0.56,0.185,1.7715,0.8195,0.331,0.437,10 +M,0.515,0.39,0.12,0.6125,0.302,0.1365,0.1415,8 +I,0.545,0.405,0.13,0.658,0.327,0.1445,0.174,8 +M,0.62,0.465,0.145,0.911,0.375,0.2145,0.278,10 +M,0.63,0.49,0.15,1.1955,0.5845,0.257,0.3,9 +F,0.63,0.515,0.16,1.336,0.553,0.3205,0.35,11 +F,0.64,0.49,0.18,1.36,0.653,0.347,0.305,9 +I,0.37,0.275,0.08,0.2325,0.093,0.056,0.072,6 +I,0.395,0.31,0.085,0.317,0.153,0.0505,0.0935,7 +I,0.4,0.3,0.115,0.318,0.1335,0.0725,0.0935,6 +I,0.41,0.305,0.1,0.2645,0.1,0.0655,0.085,7 +I,0.455,0.335,0.105,0.4055,0.175,0.092,0.1185,8 +I,0.48,0.335,0.125,0.524,0.246,0.1095,0.145,7 +I,0.485,0.375,0.11,0.464,0.2015,0.09,0.149,8 +I,0.5,0.36,0.12,0.439,0.1875,0.1055,0.1305,8 +I,0.515,0.395,0.125,0.5805,0.2365,0.1075,0.19,9 +I,0.52,0.4,0.14,0.622,0.278,0.1455,0.169,8 +M,0.545,0.45,0.15,0.7805,0.3795,0.1625,0.216,8 +I,0.545,0.43,0.14,0.772,0.289,0.19,0.2615,8 +I,0.55,0.435,0.125,0.741,0.348,0.1585,0.206,9 +M,0.55,0.43,0.18,0.8265,0.4405,0.159,0.225,10 +M,0.55,0.385,0.13,0.7275,0.343,0.1625,0.19,8 +I,0.555,0.43,0.125,0.7005,0.3395,0.1355,0.2095,8 +M,0.56,0.45,0.145,0.9355,0.425,0.1645,0.2725,11 +I,0.565,0.465,0.15,1.1815,0.581,0.2215,0.3095,9 +M,0.57,0.445,0.16,1.0145,0.516,0.164,0.3,10 +F,0.575,0.48,0.17,1.1,0.506,0.2485,0.31,10 +M,0.585,0.51,0.16,1.218,0.639,0.241,0.3,11 +M,0.59,0.45,0.155,0.874,0.369,0.2135,0.24,8 +I,0.595,0.475,0.155,0.984,0.4865,0.184,0.2755,10 +M,0.6,0.47,0.13,1.0105,0.423,0.219,0.298,9 +M,0.61,0.365,0.155,1.0765,0.488,0.249,0.27,9 +M,0.615,0.475,0.205,1.337,0.5995,0.2815,0.37,11 +M,0.625,0.5,0.18,1.3705,0.645,0.303,0.3705,12 +F,0.625,0.49,0.19,1.7015,0.7465,0.4105,0.3855,11 +M,0.63,0.485,0.18,1.2435,0.5175,0.308,0.37,11 +M,0.63,0.53,0.175,1.4135,0.667,0.2945,0.3555,13 +F,0.635,0.485,0.155,1.073,0.467,0.1975,0.35,11 +F,0.635,0.5,0.175,1.477,0.684,0.3005,0.39,12 +M,0.635,0.5,0.18,1.2915,0.594,0.2695,0.37,9 +F,0.65,0.495,0.16,1.3105,0.577,0.3315,0.355,9 +M,0.67,0.525,0.18,1.4915,0.728,0.343,0.381,9 +F,0.675,0.52,0.175,1.494,0.7365,0.3055,0.37,9 +F,0.675,0.51,0.15,1.1965,0.475,0.304,0.386,11 +M,0.68,0.545,0.185,1.672,0.7075,0.364,0.48,11 +M,0.7,0.545,0.215,1.9125,0.8825,0.4385,0.506,10 +F,0.71,0.545,0.175,1.907,0.8725,0.4565,0.475,11 +F,0.715,0.565,0.18,1.79,0.844,0.3535,0.5385,9 +F,0.72,0.59,0.205,1.7495,0.7755,0.4225,0.48,11 +I,0.42,0.305,0.1,0.3415,0.1645,0.0775,0.086,7 +I,0.48,0.35,0.1,0.519,0.2365,0.1275,0.126,7 +M,0.48,0.365,0.13,0.5305,0.2405,0.127,0.139,8 +M,0.51,0.41,0.155,1.2825,0.569,0.291,0.3795,9 +I,0.515,0.4,0.14,0.7165,0.3495,0.1595,0.1785,8 +F,0.56,0.42,0.18,1.6645,0.7755,0.35,0.4525,9 +I,0.56,0.42,0.14,0.837,0.414,0.214,0.2,8 +F,0.57,0.45,0.15,0.9645,0.531,0.189,0.209,9 +F,0.605,0.465,0.155,1.1,0.547,0.2665,0.2585,10 +M,0.625,0.48,0.16,1.2415,0.6575,0.2625,0.2785,9 +F,0.64,0.505,0.175,1.3185,0.6185,0.302,0.3315,9 +M,0.65,0.525,0.185,1.3455,0.586,0.278,0.3865,9 +I,0.3,0.215,0.05,0.1185,0.048,0.0225,0.042,4 +M,0.35,0.265,0.09,0.197,0.073,0.0365,0.077,7 +I,0.455,0.35,0.13,0.4725,0.215,0.0745,0.15,9 +I,0.46,0.365,0.11,0.4495,0.1755,0.102,0.15,8 +I,0.49,0.375,0.115,0.557,0.2275,0.1335,0.1765,8 +I,0.5,0.385,0.12,0.516,0.197,0.1305,0.165,8 +I,0.54,0.415,0.135,0.709,0.3195,0.174,0.185,9 +M,0.55,0.42,0.145,0.7385,0.321,0.1485,0.252,11 +I,0.55,0.445,0.11,0.7935,0.378,0.142,0.26,10 +M,0.555,0.435,0.145,0.9205,0.404,0.2275,0.255,8 +I,0.57,0.425,0.14,0.7655,0.331,0.14,0.24,10 +M,0.58,0.45,0.14,0.824,0.3465,0.1765,0.263,10 +I,0.58,0.425,0.145,0.83,0.379,0.1605,0.2575,11 +I,0.585,0.47,0.17,0.985,0.3695,0.2395,0.315,10 +M,0.585,0.45,0.15,0.997,0.4055,0.283,0.251,11 +F,0.595,0.455,0.14,0.914,0.3895,0.2225,0.271,9 +F,0.6,0.5,0.17,1.13,0.4405,0.267,0.335,11 +F,0.615,0.495,0.155,1.0805,0.52,0.19,0.32,9 +M,0.63,0.505,0.155,1.105,0.492,0.226,0.325,11 +M,0.63,0.49,0.155,1.229,0.535,0.29,0.335,11 +F,0.635,0.495,0.175,1.2355,0.5205,0.3085,0.347,10 +F,0.645,0.535,0.19,1.2395,0.468,0.2385,0.424,10 +F,0.65,0.505,0.165,1.357,0.5725,0.281,0.43,11 +M,0.655,0.525,0.18,1.402,0.624,0.2935,0.365,13 +F,0.655,0.5,0.22,1.359,0.642,0.3255,0.405,13 +M,0.67,0.535,0.19,1.669,0.7465,0.2935,0.508,11 +M,0.67,0.525,0.2,1.7405,0.6205,0.297,0.657,11 +M,0.695,0.53,0.21,1.51,0.664,0.4095,0.385,10 +M,0.695,0.55,0.195,1.6645,0.727,0.36,0.445,11 +M,0.77,0.605,0.175,2.0505,0.8005,0.526,0.355,11 +I,0.28,0.215,0.07,0.124,0.063,0.0215,0.03,6 +I,0.33,0.23,0.08,0.14,0.0565,0.0365,0.046,7 +I,0.35,0.25,0.075,0.1695,0.0835,0.0355,0.041,6 +I,0.37,0.28,0.09,0.218,0.0995,0.0545,0.0615,7 +I,0.43,0.315,0.115,0.384,0.1885,0.0715,0.11,8 +I,0.435,0.33,0.095,0.393,0.219,0.075,0.0885,6 +I,0.44,0.35,0.11,0.3805,0.1575,0.0895,0.115,6 +M,0.475,0.37,0.11,0.4895,0.2185,0.107,0.146,8 +M,0.475,0.36,0.14,0.5135,0.241,0.1045,0.155,8 +I,0.48,0.355,0.11,0.4495,0.201,0.089,0.14,8 +F,0.56,0.44,0.135,0.8025,0.35,0.1615,0.259,9 +F,0.585,0.475,0.165,1.053,0.458,0.217,0.3,11 +F,0.585,0.455,0.17,0.9945,0.4255,0.263,0.2845,11 +M,0.385,0.255,0.1,0.3175,0.137,0.068,0.092,8 +I,0.39,0.31,0.085,0.344,0.181,0.0695,0.079,7 +I,0.39,0.29,0.1,0.2845,0.1255,0.0635,0.081,7 +I,0.405,0.3,0.085,0.3035,0.15,0.0505,0.088,7 +I,0.475,0.365,0.115,0.499,0.232,0.0885,0.156,10 +M,0.5,0.38,0.125,0.577,0.269,0.1265,0.1535,9 +F,0.515,0.4,0.125,0.615,0.2865,0.123,0.1765,8 +M,0.52,0.385,0.165,0.791,0.375,0.18,0.1815,10 +M,0.55,0.43,0.13,0.8395,0.3155,0.1955,0.2405,10 +M,0.56,0.43,0.155,0.8675,0.4,0.172,0.229,8 +F,0.565,0.45,0.165,0.887,0.37,0.239,0.249,11 +M,0.59,0.44,0.135,0.966,0.439,0.2145,0.2605,10 +M,0.6,0.475,0.205,1.176,0.5255,0.2875,0.308,9 +F,0.625,0.485,0.15,1.0945,0.531,0.261,0.296,10 +M,0.71,0.555,0.195,1.9485,0.9455,0.3765,0.495,12 diff --git a/yggdrasil_decision_forests/port/javascript/training/npm/test/training.test.js b/yggdrasil_decision_forests/port/javascript/training/npm/test/training.test.js new file mode 100644 index 00000000..20ee6511 --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/npm/test/training.test.js @@ -0,0 +1,69 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const fs = require('node:fs'); + +describe('YDF Training', () => { + beforeAll(async () => { + ydf = await require('ydf-training')({ + "print": console.log, + "printErr": console.log, + }); + }); + + it('train GBT regression', () => { + abalone_csv = fs.readFileSync('./test/abalone.csv', 'utf-8'); + const learner = new ydf.GradientBoostedTreesLearner("Rings", "REGRESSION"); + const model = learner.train(abalone_csv); + const description = model.describe(); + const expectedStart = 'Type: "GRADIENT_BOOSTED_TREES"\nTask: REGRESSION\nLabel: "Rings"\n\nInput Features (8):\n\tType\n\tLongestShell\n\tDiameter\n\tHeight\n\tWholeWeight\n\tShuckedWeight\n\tVisceraWeight\n\tShellWeight\n\nNo weights'; + expect(description.startsWith(expectedStart)).toBe(true); + const regex = /Number of trees: (\d+)/; + const match = description.match(regex); + expect(match).not.toBeNull(); // Check if the pattern is found + if (match) { + const numTrees = parseInt(match[1], 10); + expect(numTrees).toBeGreaterThan(20); + } + }); + + it('train RF regression', () => { + abalone_csv = fs.readFileSync('./test/abalone.csv', 'utf-8'); + const learner = new ydf.RandomForestLearner("Rings", "REGRESSION"); + const model = learner.train(abalone_csv); + const description = model.describe(); + const expectedStart = 'Type: "RANDOM_FOREST"\nTask: REGRESSION\nLabel: "Rings"\n\nInput Features (8):\n\tType\n\tLongestShell\n\tDiameter\n\tHeight\n\tWholeWeight\n\tShuckedWeight\n\tVisceraWeight\n\tShellWeight\n\nNo weights'; + expect(description.startsWith(expectedStart)).toBe(true); + expect(description).toContain("Number of trees: 300"); + }); + + afterAll(async () => { + }); +}); diff --git a/yggdrasil_decision_forests/port/javascript/training/training.cc b/yggdrasil_decision_forests/port/javascript/training/training.cc new file mode 100644 index 00000000..2b4a54b4 --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/training.cc @@ -0,0 +1,81 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef __EMSCRIPTEN__ +#include +#include +#endif // __EMSCRIPTEN__ + +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "yggdrasil_decision_forests/dataset/data_spec_inference.h" +#include "yggdrasil_decision_forests/learner/abstract_learner.h" +#include "yggdrasil_decision_forests/learner/abstract_learner.pb.h" +#include "yggdrasil_decision_forests/learner/learner_library.h" +#include "yggdrasil_decision_forests/model/abstract_model.h" +#include "yggdrasil_decision_forests/model/abstract_model.pb.h" +#include "yggdrasil_decision_forests/port/javascript/training/dataset/dataset.h" +#include "yggdrasil_decision_forests/port/javascript/training/learner/learner.h" +#include "yggdrasil_decision_forests/port/javascript/training/model/model.h" +#include "yggdrasil_decision_forests/port/javascript/training/util/status_casters.h" +#include "yggdrasil_decision_forests/utils/logging.h" + +namespace yggdrasil_decision_forests::port::javascript { + +namespace { + +std::vector CreateVectorString(size_t reserved) { + std::vector v; + v.reserve(reserved); + return v; +} + +std::vector CreateVectorInt(size_t reserved) { + std::vector v; + v.reserve(reserved); + return v; +} + +std::vector CreateVectorFloat(size_t reserved) { + std::vector v; + v.reserve(reserved); + return v; +} +} // namespace + +#ifdef __EMSCRIPTEN__ +// Expose some of the class/functions to JS. +EMSCRIPTEN_BINDINGS(m) { + init_dataset(); + init_model(); + init_learner(); + + emscripten::function("CreateVectorString", &CreateVectorString); + emscripten::function("CreateVectorInt", &CreateVectorInt); + emscripten::function("CreateVectorFloat", &CreateVectorFloat); + + emscripten::register_vector("vectorFloat"); + emscripten::register_vector("vectorInt"); + emscripten::register_vector>("vectorVectorFloat"); + emscripten::register_vector("vectorString"); +} +#endif // __EMSCRIPTEN__ +} // namespace yggdrasil_decision_forests::port::javascript diff --git a/yggdrasil_decision_forests/port/javascript/training/training.ts b/yggdrasil_decision_forests/port/javascript/training/training.ts new file mode 100644 index 00000000..c8fa9b59 --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/training.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {MainModule} from './training_for_types'; + +import { + CartLearner, + GradientBoostedTreesLearner, + RandomForestLearner, +} from './learner/learner'; +import {loadModelFromZipBlob} from './model/model'; + +declare var Module: MainModule; + +export { + CartLearner, + GradientBoostedTreesLearner, + loadModelFromZipBlob, + RandomForestLearner, +}; + +(Module as any)['RandomForestLearner'] = RandomForestLearner; +(Module as any)['GradientBoostedTreesLearner'] = GradientBoostedTreesLearner; +(Module as any)['CartLearner'] = CartLearner; +(Module as any)['loadModelFromZipBlob'] = loadModelFromZipBlob; diff --git a/yggdrasil_decision_forests/port/javascript/training/training_for_types.d.ts b/yggdrasil_decision_forests/port/javascript/training/training_for_types.d.ts new file mode 100755 index 00000000..365cdbba --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/training_for_types.d.ts @@ -0,0 +1,126 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TypeScript bindings for emscripten-generated code. +// Used for OSS only. +interface WasmModule {} + +type EmbindString = + | ArrayBuffer + | Uint8Array + | Uint8ClampedArray + | Int8Array + | string; +export interface Dataset { + addCategoricalColumn(_0: EmbindString, _1: vectorString, _2: boolean): void; + addNumericalColumn(_0: EmbindString, _1: vectorFloat): void; + delete(): void; +} + +export interface InternalModel { + getInputFeatures(): vectorInputFeature; + predict(): vectorFloat; + getLabelClasses(): vectorString; + newBatchOfExamples(_0: number): void; + setBoolean(_0: number, _1: number, _2: boolean): void; + setCategoricalInt(_0: number, _1: number, _2: number): void; + setCategoricalSetString(_0: number, _1: number, _2: vectorString): void; + setCategoricalSetInt(_0: number, _1: number, _2: vectorInt): void; + setNumerical(_0: number, _1: number, _2: number): void; + predictFromPath(_0: EmbindString): vectorFloat; + describe(): string; + save(_0: EmbindString): void; + setCategoricalString(_0: number, _1: number, _2: EmbindString): void; + delete(): void; +} + +export interface vectorInputFeature { + size(): number; + get(_0: number): InputFeature | undefined; + push_back(_0: InputFeature): void; + resize(_0: number, _1: InputFeature): void; + set(_0: number, _1: InputFeature): boolean; + delete(): void; +} + +export interface InternalLearner { + trainFromDataset(_0: Dataset): InternalModel; + init(_0: EmbindString, _1: EmbindString, _2: EmbindString): void; + trainFromPath(_0: EmbindString): InternalModel; + delete(): void; +} + +export interface vectorFloat { + size(): number; + get(_0: number): number | undefined; + push_back(_0: number): void; + resize(_0: number, _1: number): void; + set(_0: number, _1: number): boolean; + delete(): void; +} + +export interface vectorInt { + push_back(_0: number): void; + resize(_0: number, _1: number): void; + size(): number; + get(_0: number): number | undefined; + set(_0: number, _1: number): boolean; + delete(): void; +} + +export interface vectorVectorFloat { + push_back(_0: vectorFloat): void; + resize(_0: number, _1: vectorFloat): void; + size(): number; + get(_0: number): vectorFloat | undefined; + set(_0: number, _1: vectorFloat): boolean; + delete(): void; +} + +export interface vectorString { + size(): number; + get(_0: number): EmbindString | undefined; + push_back(_0: EmbindString): void; + resize(_0: number, _1: EmbindString): void; + set(_0: number, _1: EmbindString): boolean; + delete(): void; +} + +export type InputFeature = { + name: EmbindString; + type: EmbindString; + internalIdx: number; + specIdx: number; +}; + +interface EmbindModule { + Dataset: {new (): Dataset}; + InternalModel: {new (): InternalModel}; + vectorInputFeature: {new (): vectorInputFeature}; + InternalLearner: {new (): InternalLearner}; + vectorFloat: {new (): vectorFloat}; + vectorInt: {new (): vectorInt}; + vectorVectorFloat: {new (): vectorVectorFloat}; + vectorString: {new (): vectorString}; + CreateVectorString(_0: number): vectorString; + CreateVectorInt(_0: number): vectorInt; + CreateVectorFloat(_0: number): vectorFloat; + InternalLoadModel(_0: EmbindString): InternalModel; +} + +export type MainModule = WasmModule & EmbindModule; +export default function MainModuleFactory( + options?: unknown, +): Promise; diff --git a/yggdrasil_decision_forests/port/javascript/training/training_test.ts b/yggdrasil_decision_forests/port/javascript/training/training_test.ts new file mode 100644 index 00000000..5ee90c05 --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/training_test.ts @@ -0,0 +1,261 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import 'jasmine'; + +import * as YDFTrainingwrapper from './training'; + +declare global { + interface Window { + YDFTraining: (options: unknown) => Promise; + } +} + +function csvToDictionary(data: string): { + [key: string]: Array; +} { + const rows = data.trim().split('\n'); + const headers = rows.shift()!.split(','); + const result: {[key: string]: Array} = {}; + + headers.forEach((header) => { + result[header.trim()] = []; + }); + + rows.forEach((row) => { + const values = row.split(','); + values.forEach((value, index) => { + const header = headers[index].trim(); + const trimmedValue = value.trim(); + const numberValue = Number(trimmedValue); + result[header].push(isNaN(numberValue) ? trimmedValue : numberValue); + }); + }); + + return result; +} + +describe('YDF Training', () => { + let YDFTraining: any | null = null; + + const adultTrainUrl = + '/base/third_party/yggdrasil_decision_forests/test_data/dataset/adult_train.csv'; + const adultTestUrl = + '/base/third_party/yggdrasil_decision_forests/test_data/dataset/adult_test.csv'; + const abaloneUrl = + '/base/third_party/yggdrasil_decision_forests/test_data/dataset/abalone.csv'; + + beforeAll(async () => { + // Load library + await window + .YDFTraining({ + locateFile: (filename: string, dir: string) => { + return dir + filename; + }, + }) + .then((m) => { + YDFTraining = m; + console.log('The library is loaded'); + }); + }); + + it('trains a model on the CSV Adult dataset', async () => { + expect(YDFTraining).not.toBe(null); + const learner: YDFTrainingwrapper.RandomForestLearner = + new YDFTraining.RandomForestLearner('income', 'CLASSIFICATION'); + const adultTrain = await fetch(adultTrainUrl) + .then((r) => r.blob()) + .then((b) => b.text()); + const adultTest = await fetch(adultTestUrl) + .then((r) => r.blob()) + .then((b) => b.text()); + const model = learner.train(adultTrain); + const predictions = model.predict(adultTest); + expect(predictions).toHaveSize(9769); + model.unload(); + }); + + it('trains a RF model on an in-memory dataset', async () => { + expect(YDFTraining).not.toBe(null); + const learner: YDFTrainingwrapper.RandomForestLearner = + new YDFTraining.RandomForestLearner('col_label', 'CLASSIFICATION'); + const data = { + 'col_1': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + 'col_2': ['a', 'b', 'a', 'b', 'a', 'b', 'a', 'b', 'a', 'b'], + 'col_label': ['x', 'x', 'y', 'y', 'x', 'x', 'y', 'y', 'x', 'x'], + }; + const model = learner.train(data); + const predictions = model.predict(data); + expect(predictions).toHaveSize(10); + model.unload(); + }); + + it('trains a GBT model on an in-memory dataset', async () => { + expect(YDFTraining).not.toBe(null); + const learner: YDFTrainingwrapper.GradientBoostedTreesLearner = + new YDFTraining.GradientBoostedTreesLearner( + 'col_label', + 'CLASSIFICATION', + ); + const data = { + 'col_1': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + 'col_2': ['a', 'b', 'a', 'b', 'a', 'b', 'a', 'b', 'a', 'b'], + 'col_label': ['x', 'x', 'y', 'y', 'x', 'x', 'y', 'y', 'x', 'x'], + }; + const model = learner.train(data); + const predictions = model.predict(data); + expect(predictions).toHaveSize(10); + model.unload(); + }); + + it('trains a CART model on an in-memory dataset', () => { + expect(YDFTraining).not.toBe(null); + const learner: YDFTrainingwrapper.CartLearner = new YDFTraining.CartLearner( + 'col_label', + 'CLASSIFICATION', + ); + const data = { + 'col_1': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + 'col_2': ['a', 'b', 'a', 'b', 'a', 'b', 'a', 'b', 'a', 'b'], + 'col_label': ['x', 'x', 'y', 'y', 'x', 'x', 'y', 'y', 'x', 'x'], + }; + const model = learner.train(data); + const predictions = model.predict(data); + expect(predictions).toHaveSize(10); + model.unload(); + }); + + it('in-memory dataset and csv dataset models match', async () => { + expect(YDFTraining).not.toBe(null); + const learner: YDFTrainingwrapper.GradientBoostedTreesLearner = + new YDFTraining.GradientBoostedTreesLearner('Rings', 'REGRESSION'); + const abaloneCSV = await fetch(abaloneUrl) + .then((r) => r.blob()) + .then((b) => b.text()); + // Manually convert to dict of columns + const abaloneDict = csvToDictionary(abaloneCSV); + const modelFromCsv = learner.train(abaloneCSV); + const predictionsFromCsv = modelFromCsv.predict(abaloneCSV); + const modelFromDict = learner.train(abaloneDict); + const predictionsFromDict = modelFromDict.predict(abaloneDict); + expect(predictionsFromCsv).toEqual(predictionsFromDict); + modelFromCsv.unload(); + modelFromDict.unload(); + }); + + it('describes a model', () => { + expect(YDFTraining).not.toBe(null); + const learner: YDFTrainingwrapper.CartLearner = new YDFTraining.CartLearner( + 'col_label', + 'CLASSIFICATION', + ); + const data = { + 'col_1': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + 'col_2': ['a', 'b', 'a', 'b', 'a', 'b', 'a', 'b', 'a', 'b'], + 'col_label': ['x', 'x', 'y', 'y', 'x', 'x', 'y', 'y', 'x', 'x'], + }; + const model = learner.train(data); + const expectedDescription = `Type: "RANDOM_FOREST" +Task: CLASSIFICATION +Label: "col_label" + +Input Features (2): + col_1 + col_2 + +No weights + +Variable Importance: INV_MEAN_MIN_DEPTH: + +Variable Importance: NUM_AS_ROOT: + +Variable Importance: NUM_NODES: + +Variable Importance: SUM_SCORE: + + +Cannot compute model self evaluation:This model does not support evaluation reports. + +Winner takes all: false +Out-of-bag evaluation disabled. +Number of trees: 1 +Total number of nodes: 1 + +Number of nodes by tree: +Count: 1 Average: 1 StdDev: 0 +Min: 1 Max: 1 Ignored: 0 +---------------------------------------------- +[ 1, 1] 1 100.00% 100.00% ########## + +Depth by leafs: +Count: 1 Average: 0 StdDev: 0 +Min: 0 Max: 0 Ignored: 0 +---------------------------------------------- +[ 0, 0] 1 100.00% 100.00% ########## + +Number of training obs by leaf: +Count: 1 Average: 10 StdDev: 0 +Min: 10 Max: 10 Ignored: 0 +---------------------------------------------- +[ 10, 10] 1 100.00% 100.00% ########## + +Attribute in nodes: + +Attribute in nodes with depth <= 0: + +Attribute in nodes with depth <= 1: + +Attribute in nodes with depth <= 2: + +Attribute in nodes with depth <= 3: + +Attribute in nodes with depth <= 5: + +Condition type in nodes: +Condition type in nodes with depth <= 0: +Condition type in nodes with depth <= 1: +Condition type in nodes with depth <= 2: +Condition type in nodes with depth <= 3: +Condition type in nodes with depth <= 5: +Node format: NOT_SET +`; + expect(model.describe()).toBe(expectedDescription); + model.unload(); + }); + + it('saves a model to ZIP', async () => { + expect(YDFTraining).not.toBe(null); + const learner: YDFTrainingwrapper.CartLearner = new YDFTraining.CartLearner( + 'col_label', + 'CLASSIFICATION', + ); + const data = { + 'col_1': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + 'col_2': ['a', 'b', 'a', 'b', 'a', 'b', 'a', 'b', 'a', 'b'], + 'col_label': ['x', 'x', 'y', 'y', 'x', 'x', 'y', 'y', 'x', 'x'], + }; + const model = learner.train(data); + const predictions = model.predict(data); + const blob = await model.save(); + const loadedModel = await YDFTraining.loadModelFromZipBlob(blob); + const loadedPredictions = loadedModel.predict(data); + expect(loadedPredictions).toEqual(predictions); + + model.unload(); + loadedModel.unload(); + }); + + afterAll(async () => {}); +}); diff --git a/yggdrasil_decision_forests/port/javascript/training/tsconfig.json b/yggdrasil_decision_forests/port/javascript/training/tsconfig.json new file mode 100644 index 00000000..ef860564 --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "ES6", + "sourceMap": true, + "target": "es2017", + "lib": ["es2017", "dom"], + }, + "include": ["**/*.ts",], + "exclude": ["**/*_test.ts"] +} \ No newline at end of file diff --git a/yggdrasil_decision_forests/port/javascript/training/util/BUILD b/yggdrasil_decision_forests/port/javascript/training/util/BUILD new file mode 100644 index 00000000..9fe811ae --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/util/BUILD @@ -0,0 +1,19 @@ +# ts_library + +package( + default_visibility = ["//visibility:public"], + licenses = ["notice"], +) + +cc_library( + name = "status_casters", + srcs = ["status_casters.cc"], + hdrs = ["status_casters.h"], + copts = ["-fexceptions"], + features = ["-use_header_modules"], + tags = ["manual"], + deps = [ + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + ], +) diff --git a/yggdrasil_decision_forests/port/javascript/training/util/conversion.ts b/yggdrasil_decision_forests/port/javascript/training/util/conversion.ts new file mode 100644 index 00000000..6f7378dd --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/util/conversion.ts @@ -0,0 +1,117 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + EmbindString, + MainModule, + vectorFloat, + vectorString, +} from '../training_for_types'; + +declare var Module: MainModule; + +/** + * Converts a JavaScript string array to a C++ vector of EmbindString. + * The vector must be manually freed after use to avoid memory leaks. + */ +export function stringArrayToVectorString(src: string[]): vectorString { + const vector = Module.CreateVectorString(src.length); + for (const value of src) { + vector.push_back(value as EmbindString); + } + return vector; +} + +/** + * Converts a JavaScript number array to a C++ vector of float. + * The vector must be manually freed after use to avoid memory leaks. + */ +export function numberArrayToVectorFloat(src: number[]): vectorFloat { + const vector = Module.CreateVectorFloat(src.length); + for (const value of src) { + vector.push_back(value); + } + return vector; +} + +/** + * Converts a C++ EmbindString to a JavaScript string. + */ +export function embindStringToString( + src: EmbindString, + decoder: TextDecoder, +): string { + if (typeof src === 'string') { + return src; + } else if (src instanceof ArrayBuffer) { + return decoder.decode(src); + } else { + // Uint8Array, Uint8ClampedArray, or Int8Array + return decoder.decode(src.buffer); + } +} + +/** + * Converts a C++ vector of EmbindString to a JavaScript string array. + */ +export function ccVectorStringToJSVector(src: vectorString): string[] { + const res: string[] = []; + const decoder = new TextDecoder('utf-8'); + for (let i = 0; i < src.size(); i++) { + const item = src.get(i); + if (item === undefined) { + throw new Error(`Found undefined element at index ${i}`); + } + res.push(embindStringToString(item, decoder)); + } + return res; +} + +/** + * Converts a C++ vector of float to a JavaScript number array. + */ +export function ccVectorFloatToJSVector(src: vectorFloat): number[] { + const res: number[] = []; + for (let i = 0; i < src.size(); i++) { + const item = src.get(i); + if (item === undefined) { + throw new Error(`Found undefined element at index ${i}`); + } + res.push(item); + } + return res; +} + +/** + * Converts a Blob into a promise resolving to an arrayBuffer. + */ +export function blobToArrayBuffer(blob: Blob) { + if (blob.arrayBuffer) { + return blob.arrayBuffer(); + } else { + return new Promise((resolve) => { + const fileReader = new FileReader(); + + fileReader.readAsArrayBuffer(blob); + fileReader.onload = (event) => { + if (event.target !== null) { + resolve(event.target.result); + } else { + throw new Error('Internal Error'); + } + }; + }); + } +} diff --git a/yggdrasil_decision_forests/port/javascript/training/util/status_casters.cc b/yggdrasil_decision_forests/port/javascript/training/util/status_casters.cc new file mode 100644 index 00000000..6f7c431a --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/util/status_casters.cc @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "yggdrasil_decision_forests/port/javascript/training/util/status_casters.h" + +#ifdef __EMSCRIPTEN__ +#include +#endif // __EMSCRIPTEN__ + +#include + +#include "absl/status/status.h" + +namespace yggdrasil_decision_forests::port::javascript { + +namespace { +#ifdef __EMSCRIPTEN__ +EM_JS(void, ThrowErrorWithMessage, (const char* message), + { throw new Error(UTF8ToString(message)); }); +#else +void ThrowErrorWithMessage(const char* message) {} +#endif // __EMSCRIPTEN__ +} // namespace + +void CheckOrThrowError(const absl::Status status) { + if (!status.ok()) { + std::string message = std::string(status.message()); + ThrowErrorWithMessage(message.c_str()); + } +} + +} // namespace yggdrasil_decision_forests::port::javascript diff --git a/yggdrasil_decision_forests/port/javascript/training/util/status_casters.h b/yggdrasil_decision_forests/port/javascript/training/util/status_casters.h new file mode 100644 index 00000000..dde58e94 --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/util/status_casters.h @@ -0,0 +1,22 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "absl/status/status.h" + +namespace yggdrasil_decision_forests::port::javascript { +void CheckOrThrowError(absl::Status status); + +} // namespace yggdrasil_decision_forests::port::javascript diff --git a/yggdrasil_decision_forests/port/javascript/training/webpack.config.js b/yggdrasil_decision_forests/port/javascript/training/webpack.config.js new file mode 100644 index 00000000..ecb5cf74 --- /dev/null +++ b/yggdrasil_decision_forests/port/javascript/training/webpack.config.js @@ -0,0 +1,41 @@ +/* + * Copyright 2022 Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const path = require('path'); + +module.exports = { + mode: "development", + devtool: "inline-source-map", + entry: { + main: "./training.ts", + }, + module: { + rules: [ + { + test: /\.ts$/, + use: 'ts-loader', + exclude: [/node_modules/, /\_test.ts$/], + }, + ], + }, + output: { + path: path.resolve(__dirname, '.'), + filename: "training.bundle.js" + }, + resolve: { + preferAbsolute: true, + extensions: ['.ts', '.js'], + }, +}; \ No newline at end of file diff --git a/yggdrasil_decision_forests/port/python/CHANGELOG.md b/yggdrasil_decision_forests/port/python/CHANGELOG.md index fa6e1d19..8a13d74a 100644 --- a/yggdrasil_decision_forests/port/python/CHANGELOG.md +++ b/yggdrasil_decision_forests/port/python/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## NEXT HEAD + +### Feature + +- Add support for Avro file for path / distributed training with the "avro:" + prefix. + +## HEAD + +### Breaking + +- Change typo partial_depepence_plot to partial_dependence_plot on + model.analyze(). + +### Feature + +- Expose MRR for ranking models. +- Add `model.predict_class` to generate the most likely predicted class of + classification models. + ## 0.8.0 - 2024-09-23 ### Breaking diff --git a/yggdrasil_decision_forests/port/python/dev_requirements.txt b/yggdrasil_decision_forests/port/python/dev_requirements.txt index b8448cb8..5e627d07 100644 --- a/yggdrasil_decision_forests/port/python/dev_requirements.txt +++ b/yggdrasil_decision_forests/port/python/dev_requirements.txt @@ -13,4 +13,6 @@ jax; platform_machine != 'aarch64' and platform_system != 'Windows' jaxlib; platform_machine != 'aarch64' and platform_system != 'Windows' optax; platform_machine != 'aarch64' and platform_system != 'Windows' and python_version >= '3.9' flatbuffers; platform_machine != 'aarch64' and platform_system != 'Windows' and python_version >= '3.12' -tensorflow-datasets; platform_machine != 'aarch64' and platform_system != 'Windows' and python_version >= '3.9' \ No newline at end of file +tensorflow-datasets; platform_machine != 'aarch64' and platform_system != 'Windows' and python_version >= '3.9' +grain; platform_machine != 'aarch64' and platform_system != 'Windows' +fastavro diff --git a/yggdrasil_decision_forests/port/python/ydf/BUILD b/yggdrasil_decision_forests/port/python/ydf/BUILD index 7e29aea9..f6c0dc23 100644 --- a/yggdrasil_decision_forests/port/python/ydf/BUILD +++ b/yggdrasil_decision_forests/port/python/ydf/BUILD @@ -103,7 +103,9 @@ py_test( ":api", # absl/logging dep, # absl/testing:absltest dep, + # fastavro dep, # jax dep, # buildcleaner: keep + # numpy dep, # pandas dep, # sklearn dep, # buildcleaner: keep # tensorflow dep, # buildcleaner: keep diff --git a/yggdrasil_decision_forests/port/python/ydf/api_test.py b/yggdrasil_decision_forests/port/python/ydf/api_test.py index 479dd996..5d91c555 100644 --- a/yggdrasil_decision_forests/port/python/ydf/api_test.py +++ b/yggdrasil_decision_forests/port/python/ydf/api_test.py @@ -21,6 +21,8 @@ from absl import logging from absl.testing import absltest +import fastavro +import numpy as np import pandas as pd from sklearn import ensemble as skl_ensemble import sklearn.datasets @@ -275,6 +277,34 @@ def test_io_benchmark(self): create_workdir=False, ) + def test_train_on_avro_files(self): + # TODO: Use Polars's Avro writer instead. + schema = fastavro.parse_schema({ + "name": "ToyDataset", + "doc": "A toy dataset.", + "type": "record", + "fields": [ + {"name": "f1", "type": "float"}, + {"name": "f2", "type": "float"}, + {"name": "f3", "type": ["null", "float"]}, + {"name": "l", "type": "float"}, + ], + }) + ds = pd.DataFrame({ + "f1": np.random.rand(100), + "f2": np.random.rand(100), + "f3": np.random.rand(100), + "l": np.random.rand(100), + }) + + ds_path = os.path.join(self.create_tempdir().full_path, "ds.avro") + with open(ds_path, "wb") as out: + fastavro.writer(out, schema, ds.to_dict("records"), codec="deflate") + + learner = ydf.RandomForestLearner(label="l", task=ydf.Task.REGRESSION) + model = learner.train("avro:" + ds_path) + logging.info(model) + if __name__ == "__main__": absltest.main() diff --git a/yggdrasil_decision_forests/port/python/ydf/dataset/io/dataset_io_types.py b/yggdrasil_decision_forests/port/python/ydf/dataset/io/dataset_io_types.py index 9f639ae4..3304d3e3 100644 --- a/yggdrasil_decision_forests/port/python/ydf/dataset/io/dataset_io_types.py +++ b/yggdrasil_decision_forests/port/python/ydf/dataset/io/dataset_io_types.py @@ -90,7 +90,12 @@ 8. A PyGrain DataLoader or Dataset (experimental, Linux only). The supported file formats and corresponding prefixes are: - - CSV file. prefix 'csv:' - - Non-compressed TFRecord of Tensorflow Examples. prefix 'tfrecordv2+tfe:' - - Compressed TFRecord of Tensorflow Examples. prefix 'tfrecord+tfe:'; not available in default public build. + - 'csv:' CSV files. Great for small datasets and integration with other + tools. CSV file do not support multi-dimensional columns. + - 'avro:' Avro files. Great for large datasets. Avro files support + multi-dimensional columns. + - 'tfrecord:' Compressed TFRecord of Tensorflow Examples. Official dataset format for TensorFlow. Great to work with TensorFlow and TFX pipelines. + - 'tfrecord-nocompression:' Non-compressed TFRecord of Tensorflow Examples. Only here is you forgot to enable compression when creating the TFRecord. + +For Googlers, see go/ydf for more formats. """ diff --git a/yggdrasil_decision_forests/port/python/ydf/metric/display_metric.py b/yggdrasil_decision_forests/port/python/ydf/metric/display_metric.py index 591c2507..afdf599a 100644 --- a/yggdrasil_decision_forests/port/python/ydf/metric/display_metric.py +++ b/yggdrasil_decision_forests/port/python/ydf/metric/display_metric.py @@ -148,6 +148,7 @@ def evaluation_to_str(e: metric.Evaluation) -> str: # Ranking text += _field_to_str("NDCG", e.ndcg) + text += _field_to_str("MRR", e.mrr) # Uplifting text += _field_to_str("QINI", e.qini) @@ -214,6 +215,7 @@ def evaluation_to_html_str(e: metric.Evaluation, add_style: bool = True) -> str: # Ranking _field_to_html(doc, html_metric_grid, "NDCG", e.ndcg) + _field_to_html(doc, html_metric_grid, "MRR", e.mrr) # Uplifting _field_to_html(doc, html_metric_grid, "QINI", e.qini) diff --git a/yggdrasil_decision_forests/port/python/ydf/metric/metric.py b/yggdrasil_decision_forests/port/python/ydf/metric/metric.py index 8e5523e1..b415f1f9 100644 --- a/yggdrasil_decision_forests/port/python/ydf/metric/metric.py +++ b/yggdrasil_decision_forests/port/python/ydf/metric/metric.py @@ -279,7 +279,8 @@ class Evaluation: rmse: Root Mean Square Error. Only available for regression task. rmse_ci95_bootstrap: 95% confidence interval of the RMSE computed using bootstrapping. Only available for regression task. - ndcg: Normalized Discounted Cumulative Gain. For Ranking. + ndcg: Normalized Discounted Cumulative Gain. Used for ranking tasks. + mrr: Mean Reciprocal Rank. Used for ranking tasks. qini: For uplifting. auuc: For uplifting. custom_metrics: User custom metrics dictionary. @@ -464,6 +465,13 @@ def ndcg(self) -> Optional[float]: if rank.HasField("ndcg"): return rank.ndcg.value + @property + def mrr(self) -> Optional[float]: + if self._evaluation_proto.HasField("ranking"): + rank = self._evaluation_proto.ranking + if rank.HasField("mrr"): + return rank.mrr.value + @property def qini(self) -> Optional[float]: if self._evaluation_proto.HasField("uplift"): @@ -503,6 +511,7 @@ def add_item(key, value): add_item("rmse", self.rmse) add_item("rmse_ci95_bootstrap", self.rmse_ci95_bootstrap) add_item("ndcg", self.ndcg) + add_item("mrr", self.mrr) add_item("qini", self.qini) add_item("auuc", self.auuc) return output diff --git a/yggdrasil_decision_forests/port/python/ydf/metric/metric_test.py b/yggdrasil_decision_forests/port/python/ydf/metric/metric_test.py index ebe135b8..dba4ddd3 100644 --- a/yggdrasil_decision_forests/port/python/ydf/metric/metric_test.py +++ b/yggdrasil_decision_forests/port/python/ydf/metric/metric_test.py @@ -257,7 +257,8 @@ def test_ranking(self): count_predictions=3, label_column=ds_pb.Column(name="my_label"), ranking=metric_pb2.EvaluationResults.Ranking( - ndcg=metric_pb2.MetricEstimate(value=5) + ndcg=metric_pb2.MetricEstimate(value=5), + mrr=metric_pb2.MetricEstimate(value=9), ), ) evaluation = metric.Evaluation(proto_eval) @@ -267,6 +268,7 @@ def test_ranking(self): { "loss": 2.0, "ndcg": 5.0, + "mrr": 9.0, "num_examples": 1, "num_examples_weighted": 3, }, @@ -276,6 +278,7 @@ def test_ranking(self): str(evaluation), textwrap.dedent("""\ NDCG: 5 + MRR: 9 loss: 2 num examples: 1 num examples (weighted): 3 diff --git a/yggdrasil_decision_forests/port/python/ydf/model/BUILD b/yggdrasil_decision_forests/port/python/ydf/model/BUILD index c16ec4fc..596ac865 100644 --- a/yggdrasil_decision_forests/port/python/ydf/model/BUILD +++ b/yggdrasil_decision_forests/port/python/ydf/model/BUILD @@ -237,6 +237,7 @@ py_test( "@ydf_cc//yggdrasil_decision_forests/test_data", ], python_version = "PY3", + shard_count = 4, deps = [ ":analysis", ":generic_model", diff --git a/yggdrasil_decision_forests/port/python/ydf/model/export_docker.py b/yggdrasil_decision_forests/port/python/ydf/model/export_docker.py index ab19b9ce..97d78442 100644 --- a/yggdrasil_decision_forests/port/python/ydf/model/export_docker.py +++ b/yggdrasil_decision_forests/port/python/ydf/model/export_docker.py @@ -273,7 +273,7 @@ def content_dockerfile() -> str: def content_requirements() -> str: return """\ -fastapi +fastapi[all] pydantic ydf """ diff --git a/yggdrasil_decision_forests/port/python/ydf/model/export_tf.py b/yggdrasil_decision_forests/port/python/ydf/model/export_tf.py index b9a667d8..a46f21cf 100644 --- a/yggdrasil_decision_forests/port/python/ydf/model/export_tf.py +++ b/yggdrasil_decision_forests/port/python/ydf/model/export_tf.py @@ -131,6 +131,17 @@ def ydf_model_to_tensorflow_saved_model( ]: if value != expected: raise ValueError(f"{name!r} is not supported for `keras` mode.") + + if input_model_signature_fn is None: + logging.warning( + 'to_tensorflow_saved_model with mode="keras" is deprecated. Use' + ' mode="tf" instead. See:' + " https://ydf.readthedocs.io/en/latest/py_api/GenericModel/#ydf.GenericModel.to_tensorflow_function." + " mode='tf' natively support Servo API and other functionnalities" + " while at the same time creating a simpler and easier to use" + " SavedModel." + ) + ydf_model_to_tensorflow_saved_model_keras_mode( ydf_model=ydf_model, path=path, diff --git a/yggdrasil_decision_forests/port/python/ydf/model/generic_model.py b/yggdrasil_decision_forests/port/python/ydf/model/generic_model.py index e40bf892..a8d34069 100644 --- a/yggdrasil_decision_forests/port/python/ydf/model/generic_model.py +++ b/yggdrasil_decision_forests/port/python/ydf/model/generic_model.py @@ -17,7 +17,6 @@ import dataclasses import enum import os -import platform from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple, TypeVar, Union from absl import logging @@ -261,11 +260,11 @@ def benchmark( seconds. During the warmup phase, the benchmark is run without being timed. This allows warming up caches. The benchmark will always run at least one batch for warmup. This parameter must be > 0. - batch_size: Size of batches when feeding examples to the inference - engines. The impact of this parameter on the results depends on the - architecture running the benchmark (notably, cache sizes). - num_threads: Number of threads used for the multi-threaded benchmark. If - not specified, the number of threads is set to the number of cpu cores. + batch_size: Size of batches when feeding examples to the inference + engines. The impact of this parameter on the results depends on the + architecture running the benchmark (notably, cache sizes). + num_threads: Number of threads used for the multi-threaded benchmark. If + not specified, the number of threads is set to the number of cpu cores. Returns: Benchmark results. @@ -296,15 +295,15 @@ def benchmark( required_columns=self.input_feature_names(), ) result = self._model.Benchmark( - vds._dataset, + vds._dataset, # pylint: disable=protected-access benchmark_duration, warmup_duration, batch_size, - num_threads, # pylint: disable=protected-access + num_threads, ) return result - def save(self, path, advanced_options=ModelIOOptions()) -> None: + def save(self, path: str, advanced_options=ModelIOOptions()) -> None: """Save the model to disk. YDF uses a proprietary model format for saving models. A model consists of @@ -396,7 +395,7 @@ def predict( self, data: dataset.InputDataset, *, - use_slow_engine=False, + use_slow_engine: bool = False, num_threads: Optional[int] = None, ) -> np.ndarray: """Returns the predictions of the model on the given dataset. @@ -415,6 +414,73 @@ def predict( predictions = model.predict(test_ds) ``` + The predictions are a NumPy array of float32 values. The structure of this + array depends on the model's task and, in some cases, the number of classes. + + **Classification (`model.task() == ydf.Task.CLASSIFICATION`)** + + * *Binary Classification:* For models with two classes + (`len(model.label_classes()) == 2`), the output is an array of shape + `[num_examples]`. Each value represents the predicted probability of the + positive class ( `model.label_classes()[1]` ). To get the probability of + the negative class, use `1 - model.predict(dataset)`. + + Here is an example of how to get the most probably class: + + ```python + prediction_proba = model.predict(test_ds) + predicted_classes = np.take(model.label_classes(), prediction_proba + >= 0.5) + + # Or simply + predicted_classes = model.predict_class(test_ds) + ``` + + * *Multi-class Classification:* For models with more than two classes, the + output is an array of shape `[num_examples, num_classes]`. The value at + index `[i, j]` is the probability of class `j` for example `i`. + + Here is an example of how to get the most probably class: + + ```python + prediction_proba = model.predict(test_ds) + prediction_class_idx = np.argmax(prediction_proba, axis=1) + predicted_classes = np.take(model.label_classes(), + prediction_class_idx) + + # Or simply + predicted_classes = model.predict_class(test_ds) + ``` + + **Regression (`model.task() == ydf.Task.REGRESSION`)** + + The output is an array of shape `[num_examples]`, where each value is + the predicted value for the corresponding example. + + **Ranking (`model.task() == ydf.Task.RANKING`)** + + The output is an array of shape `[num_examples]`, where each value + represents the score of the corresponding example. Higher scores + indicate higher ranking. + + **Categorical Uplift (`model.task() == ydf.Task.CATEGORICAL_UPLIFT`)** + + The output is an array of shape `[num_examples]`, where each value + represents the predicted uplift. Positive values indicate a positive + effect of the treatment on the outcome, while values close to zero + indicate little to no effect. + + **Numerical Uplift (`model.task() == ydf.Task.NUMERICAL_UPLIFT`)** + + The output is an array of shape `[num_examples]`, with the + interpretation being the same as for Categorical Uplift. + + **Anomaly Detection (`model.task() == ydf.Task.ANOMALY_DETECTION`)** + + The output is an array of shape `[num_examples]`, where each value is + the anomaly score for the corresponding example. Scores range from 0 + (most normal) to 1 (most anomalous). + Args: data: Dataset. Supported formats: VerticalDataset, (typed) path, list of (typed) paths, Pandas DataFrame, Xarray Dataset, TensorFlow Dataset, @@ -428,6 +494,9 @@ def predict( categorical conditions. It is only in these cases, that users should use the slow engine and report the issue to the YDF developers. num_threads: Number of threads used to run the model. + + Returns: + The predictions of the model on the given dataset. """ if num_threads is None: @@ -442,10 +511,75 @@ def predict( required_columns=self.input_feature_names(), ) result = self._model.Predict( - ds._dataset, use_slow_engine, num_threads=num_threads - ) # pylint: disable=protected-access + ds._dataset, use_slow_engine, num_threads=num_threads # pylint: disable=protected-access + ) return result + def predict_class( + self, + data: dataset.InputDataset, + *, + use_slow_engine=False, + num_threads: Optional[int] = None, + ) -> np.ndarray: + """Returns the most likely predicted class for a classification model. + + Usage example: + + ```python + import pandas as pd + import ydf + + # Train model + train_ds = pd.read_csv("train.csv") + model = ydf.RandomForestLearner(label="label").train(train_ds) + + test_ds = pd.read_csv("test.csv") + predictions = model.predict_class(test_ds) + ``` + + This method returns a numpy array of string of shape `[num_examples]`. Each + value represents the most likely class for the corresponding example. This + method can only be used for classification models. + + In case of ties, the first class in`model.label_classes()` is returned. + + See `model.predict` to generate the full prediction probabilities. + + Args: + data: Dataset. Supported formats: VerticalDataset, (typed) path, list of + (typed) paths, Pandas DataFrame, Xarray Dataset, TensorFlow Dataset, + PyGrain DataLoader and Dataset (experimental, Linux only), dictionary of + string to NumPy array or lists. If the dataset contains the label + column, that column is ignored. + use_slow_engine: If true, uses the slow engine for making predictions. The + slow engine of YDF is an order of magnitude slower than the other + prediction engines. There exist very rare edge cases where predictions + with the regular engines fail, e.g., models with a very large number of + categorical conditions. It is only in these cases that users should use + the slow engine and report the issue to the YDF developers. + num_threads: Number of threads used to run the model. + + Returns: + The most likely predicted class for each example. + """ + + if self.task() != Task.CLASSIFICATION: + raise ValueError( + "predict_class is only supported for classification models." + ) + + label_classes = self.label_classes() + prediction_proba = self.predict( + data, use_slow_engine=use_slow_engine, num_threads=num_threads + ) + + if len(label_classes) == 2: + return np.take(label_classes, prediction_proba > 0.5) + else: + prediction_class_idx = np.argmax(prediction_proba, axis=1) + return np.take(label_classes, prediction_class_idx) + def evaluate( self, data: dataset.InputDataset, @@ -456,6 +590,7 @@ def evaluate( group: Optional[str] = None, bootstrapping: Union[bool, int] = False, ndcg_truncation: int = 5, + mrr_truncation: int = 5, evaluation_task: Optional[Task] = None, use_slow_engine: bool = False, num_threads: Optional[int] = None, @@ -525,8 +660,10 @@ def evaluate( to an integer, it specifies the number of bootstrapping samples to use. In this case, if the number is less than 100, an error is raised as bootstrapping will not yield useful results. - ndcg_truncation: Controls at which ranking position the NDCG loss should + ndcg_truncation: Controls at which ranking position the NDCG metric should be truncated. Default to 5. Ignored for non-ranking models. + mrr_truncation: Controls at which ranking position the MRR metric loss + should be truncated. Default to 5. Ignored for non-ranking models. evaluation_task: Deprecated. Use `task` instead. use_slow_engine: If true, uses the slow engine for making predictions. The slow engine of YDF is an order of magnitude slower than the other @@ -596,7 +733,7 @@ def evaluate( effective_dataspec, label_col_idx, group_col_idx = ( self._build_evaluation_dataspec( - override_task=task._to_proto_type(), + override_task=task._to_proto_type(), # pylint: disable=protected-access override_label=label, override_group=group, ) @@ -608,20 +745,22 @@ def evaluate( bootstrapping_samples=bootstrapping_samples, task=task._to_proto_type(), # pylint: disable=protected-access ranking=metric_pb2.EvaluationOptions.Ranking( - ndcg_truncation=ndcg_truncation - ) if task == Task.RANKING else None, + ndcg_truncation=ndcg_truncation, mrr_truncation=mrr_truncation + ) + if task == Task.RANKING + else None, num_threads=num_threads, ) evaluation_proto = self._model.Evaluate( - ds._dataset, + ds._dataset, # pylint: disable=protected-access options_proto, weighted=weighted, label_col_idx=label_col_idx, group_col_idx=group_col_idx, use_slow_engine=use_slow_engine, num_threads=num_threads, - ) # pylint: disable=protected-access + ) return metric.Evaluation(evaluation_proto) def analyze_prediction( @@ -677,7 +816,7 @@ def analyze( data: dataset.InputDataset, sampling: float = 1.0, num_bins: int = 50, - partial_depepence_plot: bool = True, + partial_dependence_plot: bool = True, conditional_expectation_plot: bool = True, permutation_variable_importance_rounds: int = 1, num_threads: Optional[int] = None, @@ -722,7 +861,7 @@ def analyze( 0.01. num_bins: Number of bins used to accumulate statistics. A large value increase the resolution of the plots but takes more time to compute. - partial_depepence_plot: Compute partial dependency plots a.k.a PDPs. + partial_dependence_plot: Compute partial dependency plots a.k.a PDPs. Expensive to compute. conditional_expectation_plot: Compute the conditional expectation plots a.k.a. CEP. Cheap to compute. @@ -752,7 +891,7 @@ def analyze( num_threads=num_threads, maximum_duration_seconds=maximum_duration, pdp=model_analysis_pb2.Options.PlotConfig( - enabled=partial_depepence_plot, + enabled=partial_dependence_plot, example_sampling=sampling, num_numerical_bins=num_bins, ), @@ -1459,6 +1598,16 @@ def _build_evaluation_dataspec( override_label: Optional[str], override_group: Optional[str], ) -> Tuple[data_spec_pb2.DataSpecification, int, int]: + """Creates a dataspec for evaluation. + + Args: + override_task: Override task to use for the evaluation. + override_label: Override name of the label column. + override_group: Override name of the group column. + + Returns: + The dataspec, the label column index and the group column index. + """ # Default dataspec of the model effective_dataspec = self._model.data_spec() diff --git a/yggdrasil_decision_forests/port/python/ydf/model/jax_model_test.py b/yggdrasil_decision_forests/port/python/ydf/model/jax_model_test.py index 48195431..7d6213d8 100644 --- a/yggdrasil_decision_forests/port/python/ydf/model/jax_model_test.py +++ b/yggdrasil_decision_forests/port/python/ydf/model/jax_model_test.py @@ -961,7 +961,9 @@ def test_densify_conditions( "split_axis": "SPARSE_OBLIQUE", "sparse_oblique_normalization": "STANDARD_DEVIATION", }, - False, # TODO: Check conversion when bug solved. + # TODO: b/364685350 - Re-enable when compiler fix oss sync + # is released. + False ), ) def test_to_jax_function( @@ -1013,6 +1015,7 @@ def test_to_jax_function( jax_predictions = jax_model.predict(input_values) # Test predictions + # Note: This test sometime fails with OOS build. np.testing.assert_allclose( jax_predictions, ydf_predictions, diff --git a/yggdrasil_decision_forests/port/python/ydf/model/model_test.py b/yggdrasil_decision_forests/port/python/ydf/model/model_test.py index 09585a96..589695e3 100644 --- a/yggdrasil_decision_forests/port/python/ydf/model/model_test.py +++ b/yggdrasil_decision_forests/port/python/ydf/model/model_test.py @@ -113,12 +113,21 @@ def test_predict_adult_rf(self): ) test_df = pd.read_csv(dataset_path) - predictions = self.adult_binary_class_rf.predict(test_df) - predictions_df = pd.read_csv(predictions_path) + predictions_gt = pd.read_csv(predictions_path) - expected_predictions = predictions_df[">50K"].to_numpy() + # Test probability predictions + predictions = self.adult_binary_class_rf.predict(test_df) + expected_predictions = predictions_gt[">50K"].to_numpy() npt.assert_almost_equal(predictions, expected_predictions, decimal=5) + prediction_classes = self.adult_binary_class_rf.predict_class(test_df) + expected_prediction_classes = np.take( + ["<=50K", ">50K"], expected_predictions > 0.5 + ) + self.assertEqual(prediction_classes.shape, (len(test_df),)) + self.assertEqual(prediction_classes.dtype.type, np.str_) + npt.assert_equal(prediction_classes, expected_prediction_classes) + def test_predict_adult_gbt(self): dataset_path = os.path.join( test_utils.ydf_test_data_path(), "dataset", "adult_test.csv" @@ -136,6 +145,72 @@ def test_predict_adult_gbt(self): expected_predictions = predictions_df[">50K"].to_numpy() npt.assert_almost_equal(predictions, expected_predictions, decimal=5) + def test_predict_iris_gbt(self): + dataset_path = os.path.join( + test_utils.ydf_test_data_path(), "dataset", "iris.csv" + ) + test_df = pd.read_csv(dataset_path) + predictions = self.iris_multi_class_gbdt.predict(test_df) + prediction_classes = self.iris_multi_class_gbdt.predict_class(test_df) + + self.assertEqual(predictions.shape, (len(test_df), 3)) + self.assertEqual(predictions.dtype.type, np.float32) + + self.assertEqual(prediction_classes.shape, (len(test_df),)) + self.assertEqual(prediction_classes.dtype.type, np.str_) + + self.assertAlmostEqual( + np.mean( + np.take( + self.iris_multi_class_gbdt.label_classes(), + np.argmax(predictions, axis=1), + ) + == test_df["class"] + ), + 0.96666, + delta=0.0001, + ) + + self.assertAlmostEqual( + np.mean(prediction_classes == test_df["class"]), 0.96666, delta=0.0001 + ) + + npt.assert_equal( + np.take( + self.iris_multi_class_gbdt.label_classes(), + np.argmax(predictions, axis=1), + ), + prediction_classes, + ) + + @parameterized.named_parameters( + { + "testcase_name": "regression", + "model_name": "abalone_regression_gbdt", + "test_ds": "abalone.csv", + }, + { + "testcase_name": "ranking", + "model_name": "synthetic_ranking_gbdt", + "test_ds": "synthetic_ranking_test.csv", + }, + { + "testcase_name": "uplift", + "model_name": "sim_pte_categorical_uplift_rf", + "test_ds": "sim_pte_test.csv", + }, + ) + def test_predict_class_not_allowed(self, model_name, test_ds): + model = getattr(self, model_name) + dataset_path = os.path.join( + test_utils.ydf_test_data_path(), "dataset", test_ds + ) + with self.assertRaisesRegex( + ValueError, + "predict_class is only supported for classification models", + ): + _ = model.predict_class(dataset_path) + def test_predict_without_label_column(self): dataset_path = os.path.join( test_utils.ydf_test_data_path(), "dataset", "adult_test.csv" @@ -234,7 +309,7 @@ def test_analyze_programmatic_data_access_classification(self): test_df = pd.read_csv(dataset_path) # Large maximum duration reduces test flakiness. analysis = self.adult_binary_class_gbdt.analyze( - test_df, num_bins=4, maximum_duration=60 + test_df, num_bins=4, maximum_duration=600 ) # Checked against report. @@ -1033,6 +1108,29 @@ def test_evaluate_ranking_ndcg_truncation(self, truncation, expected_ndcg): ) self.assertAlmostEqual(evaluation.ndcg, expected_ndcg) + @parameterized.named_parameters( + { + "testcase_name": "mrr@5", + "truncation": 5, + "expected_mrr": 0.8242574257, + }, + { + "testcase_name": "mrr@2", + "truncation": 2, + "expected_mrr": 0.7920792079, + }, + { + "testcase_name": "mrr@10", + "truncation": 10, + "expected_mrr": 0.8259075907, + }, + ) + def test_evaluate_ranking_mrr_truncation(self, truncation, expected_mrr): + evaluation = self.synthetic_ranking_gbdt.evaluate( + self.synthetic_ranking_gbdt_test_ds, mrr_truncation=truncation + ) + self.assertAlmostEqual(evaluation.mrr, expected_mrr) + if __name__ == "__main__": absltest.main() diff --git a/yggdrasil_decision_forests/test_data/dataset/README.md b/yggdrasil_decision_forests/test_data/dataset/README.md index 0561f158..e675109d 100644 --- a/yggdrasil_decision_forests/test_data/dataset/README.md +++ b/yggdrasil_decision_forests/test_data/dataset/README.md @@ -128,3 +128,95 @@ def gen_ds(n_samples: int = 120, n_outliers: int = 40, seed: int = 0): ]) return features, labels ``` + +## toy_codex-null.avro & toy_codex-deflate.avro + +Avro files generated by the following script: + +```python +import math +from fastavro import parse_schema, reader, writer + +schema = { + "name": "ToyDataset", + "doc": "A toy dataset.", + "type": "record", + "fields": [ + {"name": "f_null", "type": "null"}, + {"name": "f_boolean", "type": "boolean"}, + {"name": "f_int", "type": "int"}, + {"name": "f_long", "type": "long"}, + {"name": "f_float", "type": "float"}, + {"name": "f_another_float", "type": "float"}, + {"name": "f_double", "type": "double"}, + {"name": "f_string", "type": "string"}, + {"name": "f_bytes", "type": "bytes"}, + {"name": "f_float_optional", "type": ["null", "float"]}, + { + "name": "f_array_of_float", + "type": {"type": "array", "items": "float"}, + }, + { + "name": "f_array_of_double", + "type": {"type": "array", "items": "double"}, + }, + { + "name": "f_array_of_string", + "type": {"type": "array", "items": "string"}, + }, + { + "name": "f_another_array_of_string", + "type": {"type": "array", "items": "string"}, + }, + { + "name": "f_optional_array_of_float", + "type": ["null", {"type": "array", "items": "float"}], + }, + ], +} +parsed_schema = parse_schema(schema) +print(parsed_schema) + +records = [ + { + "f_null": None, + "f_boolean": True, + "f_int": 5, + "f_long": 1234567, + "f_float": 3.1415, + "f_another_float": 5.0, + "f_double": math.nan, + "f_string": "hello", + "f_bytes": b"world", + "f_float_optional": 6.1, + "f_array_of_float": [1.0, 2.0, 3.0], + "f_array_of_double": [10.0, 20.0, 30.0], + "f_array_of_string": ["a", "b", "c"], + "f_another_array_of_string": ["a", "b", "c"], + "f_optional_array_of_float": [1.0, 2.0, 3.0], + }, + { + "f_null": None, + "f_boolean": False, + "f_int": 6, + "f_long": -123, + "f_float": -1.234, + "f_another_float": 6.0, + "f_double": 6.789, + "f_string": "", + "f_bytes": b"", + "f_float_optional": None, + "f_array_of_float": [4.0, 5.0, 6.0], + "f_array_of_double": [40.0, 50.0, 60.0], + "f_array_of_string": ["c", "a", "b"], + "f_another_array_of_string": ["c", "def"], + "f_optional_array_of_float": None, + }, +] + +with open("/tmp/toy_codex-null.avro", "wb") as out: + writer(out, parsed_schema, records, codec="null") + +with open("/tmp/toy_codex-deflate.avro", "wb") as out: + writer(out, parsed_schema, records, codec="deflate") +``` diff --git a/yggdrasil_decision_forests/test_data/dataset/toy_codex-deflate.avro b/yggdrasil_decision_forests/test_data/dataset/toy_codex-deflate.avro new file mode 100644 index 00000000..fe90535c Binary files /dev/null and b/yggdrasil_decision_forests/test_data/dataset/toy_codex-deflate.avro differ diff --git a/yggdrasil_decision_forests/test_data/dataset/toy_codex-null.avro b/yggdrasil_decision_forests/test_data/dataset/toy_codex-null.avro new file mode 100644 index 00000000..0c27c62a Binary files /dev/null and b/yggdrasil_decision_forests/test_data/dataset/toy_codex-null.avro differ diff --git a/yggdrasil_decision_forests/utils/bytestream.cc b/yggdrasil_decision_forests/utils/bytestream.cc index 70c3d45d..3b906e52 100644 --- a/yggdrasil_decision_forests/utils/bytestream.cc +++ b/yggdrasil_decision_forests/utils/bytestream.cc @@ -40,6 +40,15 @@ absl::StatusOr InputByteStream::ReadAll() { return std::string(result); } +absl::StatusOr InputByteStream::ReadByte() { + char value; + ASSIGN_OR_RETURN(const auto has_value, ReadExactly(&value, 1)); + if (!has_value) { + return absl::OutOfRangeError("Insufficient available bytes"); + } + return value; +} + absl::StatusOr StringInputByteStream::ReadUpTo(char* buffer, int max_read) { return stream_.ReadUpTo(buffer, max_read); @@ -52,8 +61,8 @@ absl::StatusOr StringInputByteStream::ReadExactly(char* buffer, absl::StatusOr StringViewInputByteStream::ReadUpTo(char* buffer, int max_read) { - const int num_read = - std::min(static_cast(content_.size()) - current_, max_read); + const int num_read = std::min( + static_cast(content_.size()) - static_cast(current_), max_read); if (num_read > 0) { std::memcpy(buffer, content_.data() + current_, num_read); } diff --git a/yggdrasil_decision_forests/utils/bytestream.h b/yggdrasil_decision_forests/utils/bytestream.h index 42d1d392..f15eaf28 100644 --- a/yggdrasil_decision_forests/utils/bytestream.h +++ b/yggdrasil_decision_forests/utils/bytestream.h @@ -42,6 +42,9 @@ class InputByteStream { // all bytes where read. virtual absl::StatusOr ReadExactly(char* buffer, int num_read) = 0; + // Reads a single byte. + absl::StatusOr ReadByte(); + // Reads and returns the entire content of the stream. absl::StatusOr ReadAll(); @@ -61,12 +64,14 @@ class StringViewInputByteStream : public InputByteStream { absl::StatusOr ReadExactly(char* buffer, int num_read) override; + size_t left() const { return content_.size() - current_; } + private: // Content. absl::string_view content_; // Next character to read in "content_". - int current_ = 0; + size_t current_ = 0; }; // Wraps a InputByteStream around a std::string. diff --git a/yggdrasil_decision_forests/utils/own_or_borrow.h b/yggdrasil_decision_forests/utils/own_or_borrow.h index f6f7bf59..2f58964d 100644 --- a/yggdrasil_decision_forests/utils/own_or_borrow.h +++ b/yggdrasil_decision_forests/utils/own_or_borrow.h @@ -29,7 +29,7 @@ template class VectorOwnOrBorrow { public: // Empty owned. - VectorOwnOrBorrow() : values_(owned_values_), owner_(true) {} + VectorOwnOrBorrow() = default; // Not copyable, not movable (for now). VectorOwnOrBorrow(const VectorOwnOrBorrow&) = delete; @@ -70,9 +70,9 @@ class VectorOwnOrBorrow { } private: - absl::Span values_; std::vector owned_values_; - bool owner_; + absl::Span values_ = owned_values_; + bool owner_ = true; }; } // namespace yggdrasil_decision_forests::utils diff --git a/yggdrasil_decision_forests/utils/test.h b/yggdrasil_decision_forests/utils/test.h index 8385b610..f10486f1 100644 --- a/yggdrasil_decision_forests/utils/test.h +++ b/yggdrasil_decision_forests/utils/test.h @@ -79,6 +79,13 @@ std::string DataRootDirectory(); // Temporary directory. std::string TmpDirectory(); +// Utility to create parameterized test easily. +#define SIMPLE_PARAMETERIZED_TEST(NAME, CASE_CLASS, ...) \ + using NAME##Test = ::testing::TestWithParam; \ + INSTANTIATE_TEST_SUITE_P(NAME##Instantiation, NAME##Test, \ + testing::ValuesIn(__VA_ARGS__)); \ + TEST_P(NAME##Test, Simple) + // Parse a proto from its text representation. Infers the proto message from // the destination variable. // diff --git a/yggdrasil_decision_forests/utils/zlib.cc b/yggdrasil_decision_forests/utils/zlib.cc index ecb3a6bb..78f3461d 100644 --- a/yggdrasil_decision_forests/utils/zlib.cc +++ b/yggdrasil_decision_forests/utils/zlib.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "absl/log/check.h" @@ -44,19 +45,11 @@ GZipInputByteStream::Create(std::unique_ptr&& stream, size_t buffer_size) { auto gz_stream = std::make_unique(std::move(stream), buffer_size); - - gz_stream->deflate_stream_.zalloc = Z_NULL; - gz_stream->deflate_stream_.zfree = Z_NULL; - gz_stream->deflate_stream_.opaque = Z_NULL; - gz_stream->deflate_stream_.avail_in = 0; - gz_stream->deflate_stream_.next_in = Z_NULL; + std::memset(&gz_stream->deflate_stream_, 0, + sizeof(gz_stream->deflate_stream_)); if (inflateInit2(&gz_stream->deflate_stream_, 16 + MAX_WBITS) != Z_OK) { return absl::InternalError("Cannot initialize gzip stream"); } - // gz_stream->deflate_stream_.next_in = gz_stream->input_buffer_.data(); - // gz_stream->deflate_stream_.avail_in = 0; - // gz_stream->deflate_stream_.next_out = gz_stream->output_buffer_.data(); - // gz_stream->deflate_stream_.avail_out = 0; gz_stream->deflate_stream_is_allocated_ = true; return gz_stream; } @@ -163,12 +156,8 @@ GZipOutputByteStream::Create(std::unique_ptr&& stream, } auto gz_stream = std::make_unique(std::move(stream), buffer_size); - - gz_stream->deflate_stream_.zalloc = Z_NULL; - gz_stream->deflate_stream_.zfree = Z_NULL; - gz_stream->deflate_stream_.opaque = Z_NULL; - gz_stream->deflate_stream_.avail_in = 0; - gz_stream->deflate_stream_.next_in = Z_NULL; + std::memset(&gz_stream->deflate_stream_, 0, + sizeof(gz_stream->deflate_stream_)); if (deflateInit2(&gz_stream->deflate_stream_, compression_level, Z_DEFLATED, MAX_WBITS + 16, /*memLevel=*/8, // 8 is the recommended default @@ -255,6 +244,45 @@ absl::Status GZipOutputByteStream::CloseInflateStream() { return absl::OkStatus(); } +absl::Status Inflate(absl::string_view input, std::string* output, + std::string* working_buffer) { + if (working_buffer->size() < 1024) { + return absl::InvalidArgumentError( + "worker buffer should be at least 1024 bytes"); + } + z_stream stream; + std::memset(&stream, 0, sizeof(stream)); + // Note: A negative window size indicate to use the raw deflate algorithm (!= + // zlib or gzip). + if (inflateInit2(&stream, -15) != Z_OK) { + return absl::InternalError("Cannot initialize gzip stream"); + } + stream.next_in = reinterpret_cast(input.data()); + stream.avail_in = input.size(); + + while (true) { + stream.next_out = reinterpret_cast(&(*working_buffer)[0]); + stream.avail_out = working_buffer->size(); + const auto zlib_error = inflate(&stream, Z_NO_FLUSH); + if (zlib_error != Z_OK && zlib_error != Z_STREAM_END) { + inflateEnd(&stream); + return absl::InternalError(absl::StrCat("Internal error", zlib_error)); + } + if (stream.avail_out == 0) { + break; + } + const size_t produced_bytes = working_buffer->size() - stream.avail_out; + absl::StrAppend(output, + absl::string_view{working_buffer->data(), produced_bytes}); + if (zlib_error == Z_STREAM_END) { + break; + } + } + inflateEnd(&stream); + + return absl::OkStatus(); +} + } // namespace yggdrasil_decision_forests::utils #endif // THIRD_PARTY_YGGDRASIL_DECISION_FORESTS_UTILS_GZIP_H_ diff --git a/yggdrasil_decision_forests/utils/zlib.h b/yggdrasil_decision_forests/utils/zlib.h index bc26ee2f..e3a98b01 100644 --- a/yggdrasil_decision_forests/utils/zlib.h +++ b/yggdrasil_decision_forests/utils/zlib.h @@ -18,10 +18,12 @@ #include #include +#include #include #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "absl/strings/string_view.h" #include "yggdrasil_decision_forests/utils/bytestream.h" #define ZLIB_CONST @@ -94,6 +96,9 @@ class GZipOutputByteStream : public utils::OutputByteStream { bool deflate_stream_is_allocated_ = false; }; +absl::Status Inflate(absl::string_view input, std::string* output, + std::string* working_buffer); + } // namespace yggdrasil_decision_forests::utils #endif // THIRD_PARTY_YGGDRASIL_DECISION_FORESTS_UTILS_ZLIB_H_ diff --git a/yggdrasil_decision_forests/utils/zlib_test.cc b/yggdrasil_decision_forests/utils/zlib_test.cc index 512e07d5..359d156a 100644 --- a/yggdrasil_decision_forests/utils/zlib_test.cc +++ b/yggdrasil_decision_forests/utils/zlib_test.cc @@ -25,6 +25,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/log/log.h" +#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "yggdrasil_decision_forests/utils/filesystem.h" #include "yggdrasil_decision_forests/utils/logging.h" @@ -138,5 +139,14 @@ TEST_P(GZipTestCaseTest, WriteAndRead) { } } +TEST(RawDeflate, Base) { + const auto input = + absl::HexStringToBytes("05804109000008c4aa184ec1c7e0c08ff5c70ea43e470b"); + std::string output; + std::string working_buffer(1024, 0); + ASSERT_OK(Inflate(input, &output, &working_buffer)); + EXPECT_EQ(output, "hello world"); +} + } // namespace } // namespace yggdrasil_decision_forests::utils