Related
I have two valid polygons. When I take their union, I am getting an invalid polygon (there are self intersections). Is this a bug? I would expect that the union operation would always produce a valid polygon. I have provided an example below along with visualizations. Can anyone explain why this isn't a bug, or explain if there's a way to fix it?
#include <fstream>
#include <iostream>
#include <boost/geometry.hpp> // read_wkt
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/multi_polygon.hpp>
#include <boost/geometry/algorithms/union.hpp>
using PointType = boost::geometry::model::d2::point_xy<double>;
using PolygonType = boost::geometry::model::polygon<PointType>;
using MultiPolygonType = boost::geometry::model::multi_polygon<PolygonType>;
template <typename TPolygon>
void WritePolygonsToSVG(const std::vector<TPolygon>& polygons, const std::string& filename)
{
std::ofstream svg(filename);
boost::geometry::svg_mapper<PointType> mapper(svg, 400, 400);
for(unsigned int i = 0; i < polygons.size(); ++i) {
mapper.add(polygons[i]);
mapper.map(polygons[i], "fill:rgb(255,128,0);stroke:rgb(0,0,100);stroke-width:1");
}
}
int main(int, char**)
{
/// Create two polygons
PolygonType singlePolygon1;
PolygonType singlePolygon2;
boost::geometry::read_wkt("POLYGON((-52.8018 -26.744,-57.5465 -27.9916,-62.2844 -29.2642,-63.19 -26.066,-57.564 -24.5243,-53.7273 -23.3394,-52.8018 -26.744))", singlePolygon1);
boost::geometry::read_wkt("POLYGON((-56.9695 -33.6407,-58.009 -33.0132,-58.5119 -32.8011,-59.0182 -32.6562,-59.5392 -32.5779,-60.0858 -32.5658,"
"-61.3005 -32.7384,-62.2844 -29.2642,-59.997 -28.7264,-58.0701 -28.125,-56.7078 -27.7124,-55.3109 -27.4556,-55.0955 -27.4599,"
"-54.8762 -27.503,-54.6545 -27.5806,-54.4318 -27.6887,-53.9893 -27.98,-53.5601 -28.344,-52.7879 -29.1592,-52.207 -29.8722,-56.9695 -33.6407))", singlePolygon2);
boost::geometry::correct(singlePolygon1);
boost::geometry::correct(singlePolygon2);
/// Run union and check validity (should fail check)
MultiPolygonType unionResult;
boost::geometry::union_(singlePolygon1, singlePolygon2, unionResult);
boost::geometry::validity_failure_type failure_type;
if(!boost::geometry::is_valid(unionResult, failure_type)) {
std::cout << "Result of union operation is not valid! " << failure_type << std::endl; // failure_type is 21 == failure_self_intersections
}
// Put these into the same type so that they can be passed to WritePolygonsToSVG
MultiPolygonType polygon1;
polygon1.push_back(singlePolygon1);
MultiPolygonType polygon2;
polygon2.push_back(singlePolygon2);
std::vector<MultiPolygonType> polygons = {polygon1, polygon2, unionResult};
WritePolygonsToSVG(polygons, "allPolygons.svg");
return EXIT_SUCCESS;
}
Polygon1:
Polygon2:
Both input polygons:
Union polygon (self intersections):
The actual problem that I'm running into is that if I then try to union the (invalid) output polygon with another polygon, it throws an exception:
terminate called after throwing an instance of 'boost::geometry::overlay_invalid_input_exception'
what(): Boost.Geometry Overlay invalid input exception
This expanded example demonstrates the exception:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/multi_polygon.hpp>
#include <boost/geometry/algorithms/assign.hpp>
#include <boost/geometry/algorithms/intersection.hpp>
#include <boost/geometry/algorithms/union.hpp>
int main(int argc, char** argv) {
using PointType = boost::geometry::model::d2::point_xy<double>;
using PolygonType = boost::geometry::model::polygon<PointType>;
using MultiPolygonType = boost::geometry::model::multi_polygon<PolygonType>;
/// Read in two polygons
PolygonType polygon1;
PolygonType polygon2;
boost::geometry::read_wkt("POLYGON((-52.8018 -26.744,-57.5465 -27.9916,-62.2844 -29.2642,-63.19 -26.066,-57.564 -24.5243,-53.7273 -23.3394,-52.8018 -26.744))", polygon1);
boost::geometry::read_wkt("POLYGON((-56.9695 -33.6407,-58.009 -33.0132,-58.5119 -32.8011,-59.0182 -32.6562,-59.5392 -32.5779,-60.0858 -32.5658,"
"-61.3005 -32.7384,-62.2844 -29.2642,-59.997 -28.7264,-58.0701 -28.125,-56.7078 -27.7124,-55.3109 -27.4556,-55.0955 -27.4599,"
"-54.8762 -27.503,-54.6545 -27.5806,-54.4318 -27.6887,-53.9893 -27.98,-53.5601 -28.344,-52.7879 -29.1592,-52.207 -29.8722,-56.9695 -33.6407))", polygon2);
/// Run union and check validity (should fail check)
MultiPolygonType unionResult1;
boost::geometry::union_(polygon1, polygon2, unionResult1);
boost::geometry::validity_failure_type failure_type;
if(!boost::geometry::is_valid(unionResult1, failure_type)) {
std::cout << "Result of union operation is not valid!" << std::endl;
}
/// Perform second union with third polygon (should throw)
MultiPolygonType polygon3;
boost::geometry::read_wkt("MULTIPOLYGON(((-36.5181 -22.1798,-39.072 -22.991,-41.6641 -23.6833,-52.8018 -26.744,-53.7273 -23.3394,-45.7888 -21.0022,"
"-41.8671 -19.6963,-37.9783 -18.2852,-36.5181 -22.1798)),((-52.207 -29.8722,-51.6368 -30.7643,-51.1556 -31.8424,-50.7568 -33.0527,"
"-50.4336 -34.3414,-49.9875 -36.9394,-49.8512 -38.1412,-49.7639 -39.2066,-54.0482 -39.4903,-54.1623 -38.8728,-54.4115 -38.1178,"
"-55.1907 -36.4023,-56.1365 -34.758,-56.594 -34.092,-57.0005 -33.5988,-57.0016 -33.5993,-57.0036 -33.6033,-57.0033 -33.6044,"
"-57.0029 -33.6055,-57.0014 -33.6079,-56.9995 -33.6106,-56.9941 -33.6165,-56.9695 -33.6407,-52.207 -29.8722)),((-61.3005 -32.7384,"
"-65.9902 -34.1028,-70.659 -35.5553,-75.2871 -37.1219,-79.8551 -38.829,-84.6551 -40.8483,-87.0116 -41.984,-89.2911 -43.2272,-92.5791 -37.6053,"
"-89.0742 -35.6754,-85.5037 -33.9031,-78.2194 -30.6022,-74.8627 -29.2273,-71.9907 -28.2985,-69.0648 -27.5347,-63.19 -26.066,-61.3005 -32.7384)),"
"((75.3002 -38.7765,72.918 -39.2671,70.5558 -39.533,68.2188 -39.5707,65.912 -39.3766,63.6406 -38.9473,61.4095 -38.2792,59.2239 -37.3688,"
"57.089 -36.2125,55.5367 -35.1756,54.0721 -34.0078,52.7036 -32.7209,51.4398 -31.3265,50.2893 -29.8362,49.2607 -28.2616,48.3624 -26.6144,"
"47.6032 -24.9062,47.158 -23.7161,46.803 -22.6211,46.3142 -20.5856,46.0379 -18.5376,45.9485 -17.427,45.8751 -16.215,45.5849 -10.2471,"
"45.4467 -7.25562,45.2962 -4.26324,45.0667 0.0336182,44.8334 4.32845,44.3738 10.6768,42.9884 29.2827,42.3055 38.5987,41.6559 47.9296,"
"45.1275 48.1622,45.7738 38.8379,46.4466 29.5288,47.7834 10.934,48.2323 4.58677,48.587 0.296648,48.9992 -4.00063,49.299 -6.95733,"
"49.6132 -9.93119,50.1835 -15.8585,50.3639 -17.8241,50.6215 -19.6471,51.0408 -21.4515,51.7068 -23.3609,52.3326 -24.729,53.0701 -26.047,"
"53.9117 -27.3065,54.8496 -28.4987,55.8759 -29.6152,56.9829 -30.6474,58.1629 -31.5867,59.408 -32.4246,60.9346 -33.2618,62.5325 -33.9455,"
"64.1878 -34.476,65.8864 -34.8539,67.3309 -35.051,68.7878 -35.1355,70.2399 -35.1053,72.7143 -34.7658,74.0086 -34.5489,75.3002 -38.7765)))", polygon3);
MultiPolygonType unionResult2;
boost::geometry::union_(unionResult1, polygon3, unionResult2); // throws
std::cout << "Result: " << boost::geometry::wkt(unionResult2) << std::endl;
return EXIT_SUCCESS;
}
I think whatever was the bug, was fixed.
Here's the result of the extended sample on my box with GCC 5.4 and Boost 1.62.0:
Result: MULTIPOLYGON(((75.3002 -38.7765,72.918 -39.2671,70.5558 -39.533,68.2188
-39.5707,65.912 -39.3766,63.6406 -38.9473,61.4095 -38.2792,59.2239
-37.3688,57.089 -36.2125,55.5367 -35.1756,54.0721 -34.0078,52.7036
-32.7209,51.4398 -31.3265,50.2893 -29.8362,49.2607 -28.2616,48.3624
-26.6144,47.6032 -24.9062,47.158 -23.7161,46.803 -22.6211,46.3142
-20.5856,46.0379 -18.5376,45.9485 -17.427,45.8751 -16.215,45.5849
-10.2471,45.4467 -7.25562,45.2962 -4.26324,45.0667 0.0336182,44.8334
4.32845,44.3738 10.6768,42.9884 29.2827,42.3055 38.5987,41.6559 47.9296,45.1275
48.1622,45.7738 38.8379,46.4466 29.5288,47.7834 10.934,48.2323 4.58677,48.587
0.296648,48.9992 -4.00063,49.299 -6.95733,49.6132 -9.93119,50.1835
-15.8585,50.3639 -17.8241,50.6215 -19.6471,51.0408 -21.4515,51.7068
-23.3609,52.3326 -24.729,53.0701 -26.047,53.9117 -27.3065,54.8496
-28.4987,55.8759 -29.6152,56.9829 -30.6474,58.1629 -31.5867,59.408
-32.4246,60.9346 -33.2618,62.5325 -33.9455,64.1878 -34.476,65.8864
-34.8539,67.3309 -35.051,68.7878 -35.1355,70.2399 -35.1053,72.7143
-34.7658,74.0086 -34.5489,75.3002 -38.7765),(-62.2843 -29.2642,-59.997
-28.7264,-58.2365 -28.1769,-62.2843 -29.2642)))
Some background:
I wrote a single layer multi output perceptron class in C++. It uses the typical WX + b discriminant function and allows for user-defined activation functions. I have tested everything pretty throughly and it all seems to be working as I expect it to. I noticed a small logical error in my code, and when I attempted to fix it the network performed much worse than before. The error is as follows:
I evaluate the value at each output neuron using the following code:
output[i] =
activate_(std::inner_product(weights_[i].begin(), weights_[i].end(),
features.begin(), -1 * biases_[i]));
Here I treat the bias input as a fixed -1, but when I apply the learning rule to each bias, I treat the input as +1.
// Bias can be treated as a weight with a constant feature value of 1.
biases_[i] = weight_update(1, error, learning_rate_, biases_[i]);
So I attempted to fix my mistake by changing the call to weight_updated to be conistent with the output evaluation:
biases_[i] = weight_update(-1, error, learning_rate_, biases_[i]);
But doing so results in a 20% drop in accuracy!
I have been pulling my hair out for the past few days trying to find some other logical error in my code which might explain this strange behaviour, but have come up empty handed. Can anyone with more knowledge than I provide any insight into this? I have provided the entire class below for reference. Thank you in advance.
#ifndef SINGLE_LAYER_PERCEPTRON_H
#define SINGLE_LAYER_PERCEPTRON_H
#include <cassert>
#include <functional>
#include <numeric>
#include <vector>
#include "functional.h"
#include "random.h"
namespace qp {
namespace rf {
namespace {
template <typename Feature>
double weight_update(const Feature& feature, const double error,
const double learning_rate, const double current_weight) {
return current_weight + (learning_rate * error * feature);
}
template <typename T>
using Matrix = std::vector<std::vector<T>>;
} // namespace
template <typename Feature, typename Label, typename ActivationFn>
class SingleLayerPerceptron {
public:
// For testing only.
SingleLayerPerceptron(const Matrix<double>& weights,
const std::vector<double>& biases, double learning_rate)
: weights_(weights),
biases_(biases),
n_inputs_(weights.front().size()),
n_outputs_(biases.size()),
learning_rate_(learning_rate) {}
// Initialize the layer with random weights and biases in [-1, 1].
SingleLayerPerceptron(std::size_t n_inputs, std::size_t n_outputs,
double learning_rate)
: n_inputs_(n_inputs),
n_outputs_(n_outputs),
learning_rate_(learning_rate) {
weights_.resize(n_outputs_);
std::for_each(
weights_.begin(), weights_.end(), [this](std::vector<double>& wv) {
generate_back_n(wv, n_inputs_,
std::bind(random_real_range<double>, -1, 1));
});
generate_back_n(biases_, n_outputs_,
std::bind(random_real_range<double>, -1, 1));
}
std::vector<double> predict(const std::vector<Feature>& features) const {
std::vector<double> output(n_outputs_);
for (auto i = 0ul; i < n_outputs_; ++i) {
output[i] =
activate_(std::inner_product(weights_[i].begin(), weights_[i].end(),
features.begin(), -1 * biases_[i]));
}
return output;
}
void learn(const std::vector<Feature>& features,
const std::vector<double>& true_output) {
const auto actual_output = predict(features);
for (auto i = 0ul; i < n_outputs_; ++i) {
const auto error = true_output[i] - actual_output[i];
for (auto weight = 0ul; weight < n_inputs_; ++weight) {
weights_[i][weight] = weight_update(
features[weight], error, learning_rate_, weights_[i][weight]);
}
// Bias can be treated as a weight with a constant feature value of 1.
biases_[i] = weight_update(1, error, learning_rate_, biases_[i]);
}
}
private:
Matrix<double> weights_; // n_outputs x n_inputs
std::vector<double> biases_; // 1 x n_outputs
std::size_t n_inputs_;
std::size_t n_outputs_;
ActivationFn activate_;
double learning_rate_;
};
struct StepActivation {
double operator()(const double x) const { return x > 0 ? 1 : -1; }
};
} // namespace rf
} // namespace qp
#endif /* SINGLE_LAYER_PERCEPTRON_H */
I ended up figuring it out...
My fix was indeed correct and the loss of accuracy was just a consequence of having a lucky (or unlucky) dataset.
Most of the time when I use boost::geometry::difference it does what I'd expect. However, when I have two polygons whose edges are almost coincident, I get some weird behavior. Consider this example:
#include <iostream>
#include <fstream>
#include <list>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/multi_polygon.hpp>
using PointType = boost::geometry::model::d2::point_xy<double>;
using PolygonType = boost::geometry::model::polygon<PointType>;
using MultiPolygonType = boost::geometry::model::multi_polygon<PolygonType>;
template <typename TPolygon>
void printValidity(const TPolygon& polygons)
{
boost::geometry::validity_failure_type failure;
bool valid = boost::geometry::is_valid(polygons, failure);
if(!valid) {
std::cout << "not valid: " << failure << std::endl;
}
else {
std::cout << "valid." << std::endl;
}
}
template <typename TPolygon>
void WritePolygonsToSVG(const std::vector<TPolygon>& polygons, const std::string& filename)
{
std::ofstream svg(filename);
boost::geometry::svg_mapper<PointType> mapper(svg, 400, 400);
for(size_t polygonID = 0; polygonID < polygons.size(); ++polygonID) {
mapper.add(polygons[polygonID]);
int redValue = 50 * polygonID; // NOTE: This will break with more than 5 polygons
mapper.map(polygons[polygonID], "fill:rgb(" + std::to_string(redValue) + ",128,0);stroke:rgb(0,0,100);stroke-width:1");
}
}
int main()
{
MultiPolygonType polygon1;
boost::geometry::read_wkt("MULTIPOLYGON(((-23.8915 -12.2238,-23.7604 -10.2739,-23.1774 -7.83411,-22.196 -5.52561,-20.8436 -3.41293,-19.7976 -2.26009,-19.8108 -2.00306,24.8732 2.51519,26.0802 -9.42191,-23.6662 -14.452,-23.8915 -12.2238)))", polygon1);
WritePolygonsToSVG(polygon1, "polygon1.svg");
MultiPolygonType polygon2;
boost::geometry::read_wkt("MULTIPOLYGON(((-8.85138 -12.954,-8.89712 -12.7115,-8.9307 -12.5279,-3.35773 -12.078,-3.42937 -11.5832,-3.50013 -11.0882,-3.57007 -10.5932,-3.63925 -10.098,-3.70775 -9.60273,-3.77561 -9.10737,-3.84289 -8.61192,-3.90967 -8.1164,-3.976 -7.62081,-4.04194 -7.12517,-4.10756 -6.62948,-4.17291 -6.13375,-4.23805 -5.63799,-4.30305 -5.14222,-4.36797 -4.64643,-4.43287 -4.15064,-4.49781 -3.65485,-4.56285 -3.15909,-4.62806 -2.66331,-4.69349 -2.16756,-4.75921 -1.67185,-4.82528 -1.17619,-4.89175 -0.680597,-4.91655 -0.497015,17.8166 1.80166,17.8143 1.61653,17.8078 1.11656,17.8009 0.61658,17.7937 0.116605,17.7863 -0.383369,17.7786 -0.883343,17.7707 -1.3833,17.7627 -1.88324,17.7545 -2.38318,17.7463 -2.88312,17.7381 -3.38307,17.7299 -3.88301,17.7218 -4.38295,17.7139 -4.8829,17.706 -5.38285,17.6984 -5.88279,17.6911 -6.38274,17.684 -6.88269,17.6772 -7.38265,17.6709 -7.8826,17.6649 -8.38256,17.6595 -8.88251,17.6545 -9.38247,17.6501 -9.88244,17.6471 -10.2746,-8.85138 -12.954)))", polygon2);
WritePolygonsToSVG(polygon2, "polygon2.svg");
MultiPolygonType differencePolygon;
boost::geometry::difference(polygon1, polygon2, differencePolygon);
WritePolygonsToSVG(differencePolygon, "difference.svg");
printValidity(differencePolygon);
MultiPolygonType realDifference;
boost::geometry::read_wkt("MULTIPOLYGON(((-23.8915 -12.2238,-23.7604 -10.2739,-23.1774 -7.83411,-22.196 -5.52561,-20.8436 -3.41293,-19.7976 -2.26009,-19.8108 -2.00306,24.8732 2.51519,26.0802 -9.42191,-23.6662 -14.452,-23.8915 -12.2238),(-8.85138 -12.954,17.6471 -10.2746,17.6501 -9.88244,17.6545 -9.38247,17.6595 -8.88251,17.6649 -8.38256,17.6709 -7.8826,17.6772 -7.38265,17.684 -6.88269,17.6911 -6.38274,17.6984 -5.88279,17.706 -5.38285,17.7139 -4.8829,17.7218 -4.38295,17.7299 -3.88301,17.7381 -3.38307,17.7463 -2.88312,17.7545 -2.38318,17.7627 -1.88324,17.7707 -1.3833,17.7786 -0.883343,17.7863 -0.383369,17.7937 0.116605,17.8009 0.61658,17.8078 1.11656,17.8143 1.61653,17.8166 1.80166,-4.91655 -0.497015,-4.89175 -0.680597,-4.82528 -1.17619,-4.75921 -1.67185,-4.69349 -2.16756,-4.62806 -2.66331,-4.56285 -3.15909,-4.49781 -3.65485,-4.43287 -4.15064,-4.36797 -4.64643,-4.30305 -5.14222,-4.23805 -5.63799,-4.17291 -6.13375,-4.10756 -6.62948,-4.04194 -7.12517,-3.976 -7.62081,-3.90967 -8.1164,-3.84289 -8.61192,-3.77561 -9.10737,-3.70775 -9.60273,-3.63925 -10.098,-3.57007 -10.5932,-3.50013 -11.0882,-3.42937 -11.5832,-3.35773 -12.078,-8.9307 -12.5279,-8.89712 -12.7115,-8.85138 -12.954)))", realDifference);
WritePolygonsToSVG(realDifference, "realDifference.svg");
printValidity(realDifference);
return 0;
}
The 'differencePolygon' computed using boost::geometry::difference in this code is "valid", though it has an infinitesimally thin region:
The 'realDifference' polygon is what I output from my "real code" (where the input was slightly different due to (what I'm guessing) is the precision of the WKT writer). You can see this polygon has two infinitesimally small regions, and actually returns not valid (21: failure_self_intersections):
Is there some kind of tolerance parameter than can be set to avoid these infinitesimally small regions during the difference operation (which apparently sometimes break the polygon)? Or is there a function to "correct" the polygon by removing these self intersections? (I know the correct() function only fixes ring ordering).
I'm trying to create a Qt application and I need a math expression evaluator to evaluate things like this e.g. (4+5)*2-9/3.
I included the .hpp file of this library (http://www.partow.net/programming/exprtk/) to my project in the Qt Creator and tried to launch the following example of code:
#include <cstdio>
#include <string>
#include "exprtk.hpp"
int main()
{
typedef exprtk::expression<double> expression_t;
typedef exprtk::parser<double> parser_t;
std::string expression_string = "3 + sqrt(5) + pow(3,2) + log(5)";
expression_t expression;
parser_t parser;
if (parser.compile(expression_string,expression))
{
double result = expression.value();
printf("Result: %19.15\n",result);
}
else
printf("Error in expression\n.");
return 0;
}
When I try to compile and run it I get the following output:
debug\main.o:-1: error: too many sections (62303)
What could be the problem?
Using just pure Qt you can do something like this:
QString expression_string("3 + Math.sqrt(5) + Math.pow(3,2) + Math.log(5)");
QScriptEngine expression;
double my_val=expression.evaluate(expression_string).toNumber();
you can do much more, see HERE and HERE
Actually, on my machine (Qt 5.5, Ubuntu 16.04 with g++ 5.3), the code above does not work.
Despite the answer is quite old, I put my solution in case someone finds it useful.
QScriptEngine uses the JavaScript syntax. So to make the above code work, I had to change the syntax to:
QString expression_string("3 + Math.sqrt(5) + Math.pow(3,2) + Math.log(5)");
QScriptEngine expression;
double my_val=expression.evaluate(expression_string).toNumber();
Following the request in comments, here is how to implement an arithmetic parser using boost::spirit. First, you need to download the boost tarball, don't try to just clone Spirit alone from GitHub, because it has dependencies from other boost libraries.
Boost is huge, so if you want just a subset enough for a parser, you can extract it using bcp. From boost source directory:
cd tools/build/src/engine
./build.sh
cd ../../../bcp
../build/src/engine/b2
cd ../..
dist/bin/bcp fusion/include hana/functional spirit/home/x3 /some/path
bcp will copy all dependencies. You can leave only /some/path/boost directory, because all libraries we need are header only.
Finally, here is the full code of the parser.
#include <iostream>
#include <numeric>
#include <stdexcept>
#include <string>
#include <vector>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/hana/functional/fix.hpp>
#include <boost/hana/functional/overload.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
using namespace boost::spirit;
namespace hana = boost::hana;
// Define AST. The root is `ast::expr`, which is the first left-hand side
// operand and a list of all operations on the right-hand side. Each operand is
// a recursive `variant` that has `ast::expr` inside.
namespace ast
{
struct nil {};
struct signed_;
struct expr;
struct operand : x3::variant<
nil
, double
, x3::forward_ast<signed_>
, x3::forward_ast<expr>
>
{
using base_type::base_type;
using base_type::operator=;
};
struct signed_
{
char sign;
operand operand_;
};
struct operation
{
char operator_;
operand operand_;
};
struct expr
{
operand first;
std::vector<operation> rest;
};
} // namespace ast
// Give the grammar access to the fields of AST.
BOOST_FUSION_ADAPT_STRUCT(ast::signed_, sign, operand_)
BOOST_FUSION_ADAPT_STRUCT(ast::operation, operator_, operand_)
BOOST_FUSION_ADAPT_STRUCT(ast::expr, first, rest)
// Arithmetic expression grammar definition.
namespace ArithExpr
{
x3::rule<class expression, ast::expr > const expression("expression");
x3::rule<class term, ast::expr > const term("term");
x3::rule<class factor, ast::operand> const factor("factor");
auto const expression_def =
term
>> *(
(x3::char_('+') >> term)
| (x3::char_('-') >> term)
);
auto const term_def =
factor
>> *(
(x3::char_('*') >> factor)
| (x3::char_('/') >> factor)
);
auto const factor_def =
x3::double_
| '(' >> expression >> ')'
| (x3::char_('-') >> factor)
| (x3::char_('+') >> factor);
BOOST_SPIRIT_DEFINE(expression, term, factor);
auto calc = expression;
} // namespace ArithExpr
template <typename Iterator>
double CalcArithExpr(Iterator const &first, Iterator last) {
ast::expr expr;
// Build AST.
if (!x3::phrase_parse(first, last, ArithExpr::calc, x3::ascii::space, expr)) {
throw std::runtime_error("Cannot parse arithmetic expression");
}
// Parse the AST and calculate the result.
// hana::fix allows recursive lambda call
auto astEval = hana::fix([](auto self, auto expr) -> double {
// hana::overload calls a lambda corresponding to the type in the variant
return hana::overload(
[](ast::nil) -> double {
BOOST_ASSERT(0);
return 0;
},
[](double x) -> double { return x; },
[&](ast::signed_ const &x) -> double {
double rhs = boost::apply_visitor(self, x.operand_);
switch (x.sign) {
case '-': return -rhs;
case '+': return +rhs;
}
BOOST_ASSERT(0);
return 0;
},
[&](ast::expr const &x) -> double {
return std::accumulate(
x.rest.begin(), x.rest.end(),
// evaluate recursively left-hand side
boost::apply_visitor(self, x.first),
[&](double lhs, const ast::operation &op) -> double {
// evaluate recursively right-hand side
double rhs = boost::apply_visitor(self, op.operand_);
switch (op.operator_) {
case '+': return lhs + rhs;
case '-': return lhs - rhs;
case '*': return lhs * rhs;
case '/': return lhs / rhs;
}
BOOST_ASSERT(0);
return 0;
}
);
}
)(expr);
});
return astEval(expr);
}
int main(int argc, char *argv[]) {
auto expr = std::string{"-(4.5 + 5e-1) * 2.22 - 9.1 / 3.45"};
std::cout << CalcArithExpr(expr.begin(), expr.end()) << std::endl;
}
It calculates -(4.5 + 5e-1) * 2.22 - 9.1 / 3.45 and outputs -13.7377.
Update
Here are instructions how to build bcp and copy selected headers on Windows. Though, without any guarantee. In Linux everything just works, on Windows it is always jumps over some hoops, and the direction of jumps are always unpredictable.
This being said, open PowerShell command line. There
Import-Module 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\Tools\Microsoft.VisualStudio.DevShell.dll'
Install-Module VSSetup -Scope CurrentUser
Get-VSSetupInstance
Substitute 2019 above with your version of VS. You have to do it only once for your PowerShell. The rest is every time you need to build bcp. Get-VSSetupInstance above will print information about the instances of Visual Studio you have on your machine. Write down InstanceId that you would like to use. Now change to the boost directory in the PowerShell, and:
Enter-VsDevShell InstanceId -DevCmdArguments '-arch=x64' -SkipAutomaticLocation
Where InstanceId is the ID you got from Get-VSSetupInstance. Then from the same command prompt
cd tools\build\src\engine
& .\build.bat
cd ..\..\..\bcp
..\build\src\engine\b2 address-model=64
cd ..\..
dist\bin\bcp fusion\include hana\functional spirit\home\x3 X:\some\path\boost
I feed a series of text into my sip parser.the first one takes the longest time, no matter which is the first one.I wonder if there is any initialization work when spirit::lex do the first parsing?
template <typename Lexer>
struct sip_token : lex::lexer<Lexer>
{
sip_token()
{
this->self.add_pattern
("KSIP", "sip:")
("KSIPS", "sips:")
("USERINFO", "[0-9a-zA-Z-_.!~*'()]+(:[0-9a-zA-Z-_.!~*'()&=+$,]*)?#")
("DOMAINLBL", "([0-9a-zA-Z]|([0-9a-zA-Z][0-9a-zA-Z-]*[0-9a-zA-Z]))")
("TOPLBL", "[a-zA-Z]|([a-zA-Z][0-9a-zA-Z-]*[0-9a-zA-Z-])")
("INVITE", "INVITE")
("ACK", "ACK")
("OPTIONS", "OPTIONS")
("BYE", "BYE")
("CANCEL", "CANCEL")
("REGISTER", "REGISTER")
("METHOD", "({INVITE}|{ACK}|{OPTIONS}|{BYE}|{CANCEL}|{REGISTER})")
("SIPVERSION", "SIP\\/[0-9]\\.[0-9]")
("PROTOCOAL", "SIP\\/[^/]+\\/UDP")
("IPV4ADDR", "(\\d{1,3}\\.){3}\\d{1,3}")
("HOSTNAME", "[^ \t\r\n]+")
("SIPURL", "{KSIP}{USERINFO}?{HOSTNAME}(:[0-9]+)?")
("SIPSURL", "{KSIPS}{USERINFO}?{HOSTNAME}(:[0-9]+)?")
("SENTBY", "({HOSTNAME}|{IPV4ADDR})(:[0-9]+)?")
("GENPARM", "[^ ;\\n]+=[^ ;\r\\n]+")
("TOKEN", "[0-9a-zA-Z-.!%*_+~`']+")
("NAMEADDR", "({TOKEN} )?<({SIPURL}|{SIPSURL})>")
("STATUSCODE", "\\d{3}")
("REASONPHRASE", "[0-9a-zA-Z-_.!~*'()&=+$,]*")
("CR", "\\r")
("LF", "\\n")
;
this->self.add
("{METHOD} {SIPURL} {SIPVERSION}", T_REQ_LINE)
("{SIPVERSION} {STATUSCODE} {REASONPHRASE}", T_STAT_LINE)
("{CR}?{LF}", T_CRLF)
("Via: {PROTOCOAL} {SENTBY}(;{GENPARM})*", T_VIA)
("To: {NAMEADDR}(;{GENPARM})*", T_TO)
("From: {NAMEADDR}(;{GENPARM})*", T_FROM)
("[0-9a-zA-Z -_.!~*'()&=+$,;/?:#]+", T_OTHER)
;
}
};
grammar:
template <typename Iterator>
struct sip_grammar : qi::grammar<Iterator>
{
template <typename TokenDef>
sip_grammar(TokenDef const& tok)
: sip_grammar::base_type(start)
{
using boost::phoenix::ref;
using boost::phoenix::size;
using boost::spirit::qi::eol;
start = request | response;
response = stat_line >> *(msg_header) >> qi::token(T_CRLF);
request = req_line >> *(msg_header) >> qi::token(T_CRLF);
stat_line = qi::token(T_STAT_LINE) >> qi::token(T_CRLF);
req_line = qi::token(T_REQ_LINE) >> qi::token(T_CRLF);
msg_header = (qi::token(T_VIA) | qi::token(T_TO) | qi::token(T_FROM) | qi::token(T_OTHER))
>> qi::token(T_CRLF);
}
std::size_t c, w, l;
qi::rule<Iterator> start, response, request, stat_line, req_line, msg_header;
};
timing:
gettimeofday(&t1, NULL);
bool r = lex::tokenize_and_parse(first, last, siplexer, g);
gettimeofday(&t2, NULL);
result:
pkt1 time=40945(us)
pkt2 time=140
pkt3 time=60
pkt4 time=74
pkt5 time=58
pkt6 time=51
Clearly, it does :)
Lex will likely generate a DFA (one for each Lexer state, maybe). This is most likely the thing that takes the most time. Use a profiler to be certain :/
Now, you can
make sure the tables are initialized before first use, or
use the The Static Lexer Model to prevent the startup cost
This means you'll write an 'extra' main to generate the DFA as C++ code:
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/lex_generate_static_lexertl.hpp>
#include <fstream>
#include "sip_token.hpp"
using namespace boost::spirit;
int main(int argc, char* argv[])
{
// create the lexer object instance needed to invoke the generator
sip_token<lex::lexertl::lexer<> > my_lexer; // the token definition
std::ofstream out(argc < 2 ? "sip_token_static.hpp" : argv[1]);
// invoke the generator, passing the token definition, the output stream
// and the name suffix of the tables and functions to be generated
//
// The suffix "sip" used below results in a type lexertl::static_::lexer_sip
// to be generated, which needs to be passed as a template parameter to the
// lexertl::static_lexer template (see word_count_static.cpp).
return lex::lexertl::generate_static_dfa(my_lexer, out, "sip") ? 0 : -1;
}
An example of the code generated is here (in the word-count example from the tutorial): http://www.boost.org/doc/libs/1_54_0/libs/spirit/example/lex/static_lexer/word_count_static.hpp