femap-neutral-parser 0.14.0
FEMAP neutral file parser
License: MIT license
Requires Python: >=3.8
Keywords: femap_neutral_parser

====================
FEMAP neutral Parser
====================


FEMAP neutral file parser


* Free software: MIT license


Features and limitations
========================

Parse and render FEMAP neutral files. For now, three blocks are interpreted:

* Block 100 "Neutral File Header"
* Block 450 "Output Sets"
* Block 451 "Output Data Vectors"

Usage
=====


To use FEMAP neutral Parser in a project::

        >>> from femap_neutral_parser import Parser

To instantiate a new parser, just pass a file path::

        >>> neu = Parser("tests/data/mystran_00.NEU")

To have a list of available blocks::

        >>> neu.available_blocks()
        {'header': 100, 'output_sets': 450, '_output_vectors': 451}

Or maybe in a more human-friendly way is to use `info()` method, which prints
available results and outputs::

        >>> neu.info()
        <BLANKLINE>
        Analysis
        ========
         * subcase 1: Analyse. NASTRAN SPC 1 - lc1. test (MYSTRAN::Static)
         * subcase 2: Analyse. NASTRAN SPC 1 - lc2. test (MYSTRAN::Static)
        <BLANKLINE>
        Outputs
        =======
        access to one of them using `._output_vectors[<title>][<SubcaseID>]['record']
        <BLANKLINE>
         * displacements::t_total -> RSS translation
         * displacements::t1 -> T1 translation
         * displacements::t2 -> T2 translation
         * ...
         * cbar_stress::min_comb_a -> BAR EndA Min Stress
         * cbar_stress::min_comb_b -> BAR EndB Min Stress

Access available blocs by attribute::

        >>> from pprint import pprint as pp
        >>> pp(neu.output_sets)
        {1: {'anal_type': 'Static',
             'from_prog': 'Unknown',
             'integer_format': None,
             'notes': '',
             'process_type': None,
             'title': 'Analyse. NASTRAN SPC 1 - lc1. test',
             'value': 0.0},
         2: {'anal_type': 'Static',
             'from_prog': 'Unknown',
             'integer_format': None,
             'notes': '',
             'process_type': None,
             'title': 'Analyse. NASTRAN SPC 1 - lc2. test',
             'value': 0.0}}

Low-level data access
---------------------


Under the hood, output vectors (block451) are organized as nested dictionaries ``[<vector title>][<LCID>]``::

        >>> pp(neu._output_vectors["displacements::t_total"][2])
        {'abs_max': 2.578386,
         'calc_warn': True,
         'cent_total': True,
         'comp_dir': 1,
         'component_vec': [10002.0,
                           10003.0,
                           10004.0,
                           0.0,
                           0.0,
                           0.0,
                           0.0,
                           0.0,
                           0.0,
                           0.0],
         'id_max': 7,
         'id_min': 1,
         'max_val': 2.578386,
         'min_val': 0.0,
         'record': array([( 1, 0.000000e+00), ( 2, 2.045391e-01), ( 3, 0.000000e+00),
               ( 4, 1.468270e-02), ( 5, 9.231050e-05), ( 6, 6.276400e-01),
               ( 7, 2.578386e+00), ( 8, 1.025100e-01), ( 9, 2.578363e+00),
               (10, 1.916094e+00), (11, 1.100510e+00), (12, 2.389742e+00)],
              dtype=[('NodeID', '<i8'), ('displacements::t_total', '<f8')]),
         'vecID': 10001}


Aggregated Outputs
------------------

Slightly highest level than `_output_vectors` access, aggregated output is available using `Parser.vectors()` method. For example, to get all outputs for translations vectors::

        >>> arr = neu.vectors(("displacements::t1", "displacements::t2", "displacements::t3"))
        >>> arr
        rec.array([( 1,  0.        , 0.,  0.000000e+00, 1),
                   ( 2, -0.1870816 , 0.,  0.000000e+00, 1),
                   ...
                   (11,  0.        , 0., -1.100510e+00, 2),
                   (12,  0.        , 0., -2.389742e+00, 2)],
                  dtype=[('NodeID', '<i8'), ('displacements::t1', '<f8'), ('displacements::t2', '<f8'), ('displacements::t3', '<f8'), ('SubcaseID', '<i8')])

Returned value is a numpy structured array (`<https://numpy.org/doc/stable/user/basics.rec.html>`_). If Pandas is available, you can request to have a DataFrame instead::

        >>> neu.vectors(("displacements::t1", "displacements::t2", "displacements::t3"), asdf=True)
                           displacements::t1  displacements::t2  displacements::t3
        SubcaseID NodeID                              
        1         1       0.000000       0.0  0.000000
                  2      -0.187082       0.0  0.000000
        ...
                  11      0.000000       0.0 -0.956073
                  12      0.000000       0.0 -1.602912
        2         1       0.000000       0.0  0.000000
                  2      -0.204539       0.0  0.000000
        ...
                  11      0.000000       0.0 -1.100510
                  12      0.000000       0.0 -2.389742   

You can also request sub-cases IDs, or request raw headers::
 
        >>> neu.vectors(("displacements::t1", "displacements::t2", "displacements::t3"), asdf=True, raw=True, 
        ...              SubcaseIDs=2)
                          T1 translation  T2 translation  T3 translation
        SubcaseID NodeID                                                
        2         1             0.000000             0.0        0.000000
                  2            -0.204539             0.0        0.000000
        ...
                  11            0.000000             0.0       -1.100510
                  12            0.000000             0.0       -2.389742

High-Level access
-----------------

At highest level, you can use the `get` method that already organize vectors for you::

        >>> neu.get(what="displacements", asdf=True)
                                t1   t2        t3        r1        r2            r3
        SubcaseID NodeID                                                           
        1         1       0.000000  0.0  0.000000 -0.000432  0.008923  4.699029e-03
                  2      -0.187082  0.0  0.000000  0.000432  0.008923  4.666047e-03
        ...
        2         1       0.000000  0.0  0.000000 -0.000007  0.009755  5.137517e-03
                  2      -0.204539  0.0  0.000000  0.000007  0.009755  5.101457e-03
        ...
                  11      0.000000  0.0 -1.100510  0.000000  0.010988  1.006013e-06
                  12      0.000000  0.0 -2.389742  0.000000  0.004149  1.360364e-08

One can get the list of high-level shortcuts using ``Parser.get_vectors()`` which
will return a ``set`` of available headers::

        >>> neu.get_vectors() == {
        ... 'elem_gpf',
        ... 'force_vectors',
        ... 'displacements',
        ... 'summed_gpf',
        ... 'mpc_forces',
        ... 'constraint_gpf',
        ... 'cbar_force',
        ... 'cbar_stress',
        ... 'cbush_force',
        ... 'cbush_stress',
        ... 'cbar_ms',
        ... 'applied_gpf',
        ... 'spc_forces',
        ... 'cbeam_force'
        ... }
        True


Requirements
============

Beside Python>=3.8, only `numpy` is required. `numpy` arrays are released as
`<https://numpy.org/doc/stable/user/basics.rec.html>`_, which makes conversions
to Pandas a breeze.

If Pandas is installed (which is advised), the `asdf` parameters are valid.

Testing
=======

For testing, making docs or coding, all the dev requirements are provided in `requirements_dev.txt`. 

From a blank virtual environment, clone this repo::

        git clone https://framagit.org/numenic/femap_neutral_parser.git


Create a Python virtual environment, and activate it::

        python -m venv fnp
        source fnp/bin/activate

Install requirements::

        cd femap_neutral_parser
        pip install -r requirements.txt  # install numpy
        pip install -r requirements_dev.txt
        pip install -e .  # install femap-neutral-parser in new venv

Now testing::

        make test  # or make coverage

Building docs::

        make docs


        


History
=======

0.8.3 (2021-09-12)
------------------

Major internal refactoring

* ``output_vectors`` becomes internal (``_output_vectors``). The recommended ways to access data is now ``Parser.vectors()`` or ``Parser.get()`` at a higher level.
* no more ``autotranslate`` initialisation parameter. Everything is harmonized under the hood.


0.6.0 (2021-07-12)
------------------

* add engineering shortcuts
* harmonize NodeID and SubcaseID with ``pynastran``
* vectors are now case insensible


0.5.0 (2021-04-16)
------------------

* improve parsing speed on large files


0.4.0 (2021-04-16)
------------------

add:

* ``Parser.info()``
* ``Parser.vectors()``


0.3.0 (2021-04-16)
------------------

* add ``Parser.info()`` and ``Parser.vectors()``
* update documentation
* add info() ``doprint`` option


0.2.0 - 0.3.0 (2021-04-16)
--------------------------

* Update Documentation


0.1.0 (2021-03-24)
------------------

First release on PyPI.