tipos sirve sintaxis que programas programa para ejemplos datos con c++ parsing config configuration-files mesh

sirve - C++: ¿Cómo leer una gran cantidad de datos de archivos de texto con formato en el programa?



sintaxis c++ (5)

Asumiendo:

  • No desea utilizar un formato existente para las mallas.
  • no desea utilizar un formato de texto genérico (json, yml, ...)
  • no quieres un formato binario (aunque quieras algo eficiente)

En pocas palabras, realmente necesitas tu propio formato de texto.

Puede utilizar cualquier generador de analizador para comenzar. Si bien es probable que pueda analizar su archivo de configuración, ya que solo usa expresiones regulares, a la larga, pueden estar realmente limitados. Así que sugeriré un analizador de gramática libre de contexto , generado con el espíritu Boost :: x3 .

AST

El árbol de sintaxis abstracta contendrá el resultado final del analizador.

#include <string> #include <utility> #include <vector> #include <variant> namespace AST { using Identifier = std::string; // Variable name. using Value = std::variant<int,double>; // Variable value. using Assignment = std::pair<Identifier,Value>; // Identifier = Value. using Root = std::vector<Assignment>; // Whole file: all assignments. }

Analizador

Descripción de la gramática:

#include <boost/fusion/adapted/std_pair.hpp> #include <boost/spirit/home/x3.hpp> namespace Parser { using namespace x3; // Line: Identifier = value const x3::rule<class assignment, AST::Assignment> assignment = "assignment"; // Line: comment const x3::rule<class comment> comment = "comment"; // Variable name const x3::rule<class identifier, AST::Identifier> identifier = "identifier"; // File const x3::rule<class root, AST::Root> root = "root"; // Any valid value in the config file const x3::rule<class value, AST::Value> value = "value"; // Semantic action auto emplace_back = [](const auto& ctx) { x3::_val(ctx).emplace_back(x3::_attr(ctx)); }; // Grammar const auto assignment_def = skip(blank)[identifier >> ''='' >> value]; const auto comment_def = ''%'' >> omit[*(char_ - eol)]; const auto identifier_def = lexeme[alpha >> +(alnum | char_(''_''))]; const auto root_def = *((comment | assignment[emplace_back]) >> eol) >> omit[*blank]; const auto value_def = double_ | int_; BOOST_SPIRIT_DEFINE(root, assignment, comment, identifier, value); }

Uso

// Takes iterators on string/stream... // Returns the AST of the input. template<typename IteratorType> AST::Root parse(IteratorType& begin, const IteratorType& end) { AST::Root result; bool parsed = x3::parse(begin, end, Parser::root, result); if (!parsed || begin != end) { throw std::domain_error("Parser received an invalid input."); } return result; }

Demo en vivo

Evoluciones

  • Para cambiar dónde se permiten espacios en blanco, agregue / mueva x3::skip(blank) en las expresiones xxxx_def .
  • Actualmente el archivo debe terminar con una nueva línea. Reescribiendo la expresión root_def puede arreglar eso.
  • Seguramente querrá saber por qué falló el análisis en entradas no válidas. Vea el tutorial de manejo de errores para eso.
  • Estás a solo unas pocas reglas de analizar cosas más complicadas:

    // 100 X_n Y_n const auto point_def = lit("N_Points") >> '':'' >> int_ >> eol >> *(double_ >> double_ >> eol)

Estoy escribiendo un solucionador de CFD para problemas específicos de fluidos. Hasta ahora, la malla se genera cada vez que se ejecuta la simulación, y al cambiar la geometría y las propiedades de los fluidos, el programa necesita ser recompilado.

Para problemas de tamaño pequeño con un número bajo de celdas, funciona bien. Pero para los casos con más de 1 millón de células, y las propiedades de los fluidos deben cambiarse con mucha frecuencia, es bastante ineficiente.

Obviamente, necesitamos almacenar los datos de configuración de la simulación en un archivo de configuración, y la información de geometría en un archivo de malla con formato.

  1. Archivo simulation.config

% Dimension: 2D or 3D N_Dimension= 2 % Number of fluid phases N_Phases= 1 % Fluid density (kg/m3) Density_Phase1= 1000.0 Density_Phase2= 1.0 % Kinematic viscosity (m^2/s) Viscosity_Phase1= 1e-6 Viscosity_Phase2= 1.48e-05 ...

  1. Archivo Geometry.mesh

% Dimension: 2D or 3D N_Dimension= 2 % Points (index: x, y, z) N_Points: 100 x0 y0 x1 y1 ... x99 y99 % Faces (Lines in 2D: P1->p2) N_Faces: 55 0 2 3 4 ... % Cells (polygons in 2D: Cell-Type and Points clock-wise). 6: triangle; 9: quad N_Cells: 20 9 0 1 6 20 9 1 3 4 7 ... % Boundary Faces (index) Left_Faces: 4 0 1 2 3 Bottom_Faces: 6 7 8 9 10 11 12 ...

Es fácil escribir información de configuración y malla en archivos de texto con formato. El problema es, ¿cómo leemos estos datos de manera eficiente en el programa? Me pregunto si hay alguna biblioteca de c ++ fácil de usar para hacer este trabajo.


Bien, bien, puede implementar su propia API basada en una colección de elementos finitos, un diccionario, algunos Regex y, después de todo, aplicar la práctica de apuestas de acuerdo con algún estándar internacional.

O puedes echarle un vistazo a eso:

GMSH_IO

OpenMesh:

Acabo de usar OpenMesh en mi última implementación para el proyecto C ++ OpenGL.


Como una solución de primera iteración para obtener algo tolerable: tome la suggestion @ JosmarBarbosa y use un formato establecido para su tipo de datos, que probablemente también tenga bibliotecas de código abierto y gratuitas para su uso. Un ejemplo es OpenMesh desarrollado en RWTH Aachen. Es compatible con:

  • Representación de mallas poligonales arbitrarias (el caso general) y de triángulos puros (proporcionando algoritmos más eficientes y especializados)
  • Representación explícita de vértices, halfedges, aristas y caras.
  • Acceso rápido al vecindario, especialmente el vecindario de un solo anillo (ver más abajo).
  • [Personalización]

Pero si realmente necesita acelerar su lectura de datos de malla, considere hacer lo siguiente:

  1. Separe los metadatos de tamaño limitado de los datos de malla más grandes e ilimitados;
  2. Coloque los metadatos de tamaño limitado en un archivo separado y léalos de la forma que desee, no importa.
  3. Organice los datos de la malla como varias matrices de elementos de tamaño fijo o estructuras de tamaño fijo (por ejemplo, celdas, caras, puntos, etc.).
  4. Almacene cada una de las matrices de ancho fijo de los datos de malla en su propio archivo, sin usar la transmisión de valores individuales en cualquier lugar: simplemente lea o escriba la matriz como está, directamente. Aquí hay un ejemplo de cómo se vería una lectura . Usted sabrá el tamaño apropiado de la lectura mirando el tamaño del archivo o los metadatos.

Finalmente, podría evitar la lectura explícita por completo y utilizar la asignación de memoria para cada uno de los archivos de datos. Ver

¿La técnica más rápida para leer un archivo en la memoria?

Notas / advertencias:

  • Si escribe y lee datos binarios en sistemas con un diseño de memoria diferente de ciertos valores (por ejemplo, little-endian vs big-endian), tendrá que barajar los bytes en la memoria. Vea también esta pregunta SO sobre la endianidad.
  • Puede que no valga la pena optimizar la velocidad de lectura tanto como sea posible. Debe considerar la ley de Amdahl y optimizarla solo hasta un punto en el que ya no sea una fracción significativa de su tiempo de ejecución general. Es mejor perder algunos puntos porcentuales del tiempo de ejecución, pero obtener archivos de datos legibles para el ser humano que se pueden usar con otras herramientas que admiten un formato establecido.

En la siguiente respuesta supongo:

  1. Que si el primer carácter de una línea es % entonces será ignorado como un comentario.
  2. Cualquier otra línea está estructurada exactamente de la siguiente manera: identifier= value .

El código que presento analizará un archivo de configuración siguiendo las suposiciones mencionadas correctamente. Este es el código (espero que toda la explicación necesaria esté en los comentarios):

#include <fstream> //required for file IO #include <iostream> //required for console IO #include <unordered_map> //required for creating a hashtable to store the identifiers int main() { std::unordered_map<std::string, double> identifiers; std::string configPath; std::cout << "Enter config path: "; std::cin >> configPath; std::ifstream config(configPath); //open the specified file if (!config.is_open()) //error if failed to open file { std::cerr << "Cannot open config file!"; return -1; } std::string line; while (std::getline(config, line)) //read each line of the file { if (line[0] == ''%'') //line is a comment continue; std::size_t identifierLenght = 0; while (line[identifierLenght] != ''='') ++identifierLenght; identifiers.emplace( line.substr(0, identifierLenght), std::stod(line.substr(identifierLenght + 2)) ); //add entry to identifiers } for (const auto& entry : identifiers) std::cout << entry.first << " = " << entry.second << ''/n''; }

Después de leer los identificadores, puedes, por supuesto, hacer lo que necesites hacer con ellos. Los imprimo como ejemplo para mostrar cómo recuperarlos. Para obtener más información sobre std::unordered_map mira here . Para una gran cantidad de muy buena información sobre cómo hacer analizadores, eche un vistazo here .

Si desea que la entrada del proceso de su programa sea más rápida, inserte la siguiente línea al comienzo de main : std::ios_base::sync_with_stdio(false) . Esto desincronizará C ++ IO con C IO y, como resultado, lo hará más rápido.


Si no necesita un formato de archivo de texto específico, pero tiene una gran cantidad de datos y le importa el rendimiento, le recomiendo usar algunos marcos de datos de serialización de datos existentes.

Por ejemplo, los buffers de protocolo de Google permiten una serialización y deserialización eficientes con muy poco código. El archivo es binario, por lo que suele ser mucho más pequeño que el archivo de texto, y la serialización binaria es mucho más rápida que el análisis de texto. También admite datos estructurados (matrices, estructuras anidadas), control de versiones de datos y otros recursos.

https://developers.google.com/protocol-buffers/