===================
Writing PSOUT Files
===================

.. py:currentmodule:: mhi.psout

Setup
=====

Writing data to ``*.psout`` files is supported beginning with version 2.0 of the
``mhi.psout`` module.

Use ``py -m pip install --upgrade mhi.psout`` to install or upgrade the
``mhi.psout`` module.


Creating a new File
===================

To read an existing ``*.psout`` file, only the name of file is required.
To create a new file, additional parameters are needed.

Open Type
---------

The :class:`.OpenType` enumeration controls what happens when the named file
already exists.

.. list-table:: Open Types
   :header-rows: 1

   * - :class:`.OpenType`
     - Description
   * - CREATE_NEW
     - Create a brand new file; fails if name already exists
   * - OPEN_CREATE
     - Create a new file or open an existing one for appending
   * - CREATE_OVERWRITE
     - Create a new file; delete any existing file
   * - CREATE_RENAME
     - Create a new file, using a new name if given name exists

Page Size
---------

The :class:`.PageSize` enumeration determines the size of pages (blocks of memory)
used to access the file.
Using larger pages can be faster, but may result in wasted space.

Page sizes are in powers of 2, starting at 1024 bytes, and ending as 65536.

Reserve Size
------------

The :class:`.ReserveSize` enumeration determines the initial size of the file
in pages.
A larger initial size can improve performance, since it will postpone the first
resizing operation, which is semi-expensive.

Reserve sizes are in powers of 2, starting at 1024 pages,
and ending at 1048576 pages.

Growth Size
-----------

The :class:`.GrowthSize` enumeration determines the incremental size of the file
in pages.
A larger growth size can improve performance, since fewer resizing operations
will be required.

Reserve sizes are in powers of 2, starting at 256 pages,
and ending at 1048576 pages.

Example
-------

Create "SinCos.psout", overwriting any existing file, using a page size of 1024,
and leaving reserve and growth sizes with their default values.
Leave the file empty (for now)::

    from mhi.psout import File, OpenType, PageSize

    with File('SinCos.psout', open_type=OpenType.CREATE_OVERWRITE,
              page_size=PageSize.PS_1024) as file:
        pass



Building a Call Tree
====================

Information in the PSOUT file is organized in a call tree.
This allows information to be arranged in groups,
with groups inside of groups, and groups inside of those groups,
and so on.
The grouping may be based on function, voltage levels, geographical areas, etc.

Each file automatically has one call node called the "root".
Additional child call nodes are first added to that node,
and then grandchild call nodes can be added to the child nodes,
and then great-grandchild call nodes can be added to the grandchild nodes,
and so on.

The call tree nodes do not actually contain any data;
they only describe of the data.
The data will actually be store in traces attached to runs.
The call tree merely provides organization of the data.


Types of Call Nodes
-------------------

Apart from the root call node, there are 2 other call node types:

.. list-table:: Call Types
   :header-rows: 1

   * - :class:`.CallType`
     - Allows children
     - Maps to a trace
     - Created with
   * - MODULE
     - Yes
     - No
     - :meth:`.Call.add_call`
   * - TRACE
     - No
     - Yes
     - :meth:`.Call.add_trace_call`

Information
-----------

Each call must have an ID number.
The ID number must be unique among its siblings,
but can be reused under a different parent call node.

Apart from the ID, every call node can have a set of variables,
which are simply name-value pairs.
In each name-value pair, the name must be a string,
but the value may be a string, integer, float, boolean, or blob of binary data.
There is no requirement for any specific name-value pairs to be used,
but it is recommended to at least have a `Name="..."` for each call node.

Note that while the ID numbers must be unique within a given parent call node,
any `Name="..."` names do not need to be.


Example
-------

Beneath the root node, we'll create a call tree with 1 module and 3 traces.
Two of the traces will be inside the module:

- <ROOT>

  - a "time" trace
  - a "Trig functions" module

    - a "Sine" trace
    - a "Cosine" trace

.. literalinclude:: sin_cos.py
   :language: python
   :start-at: from mhi.psout
   :end-at: cos_call =

Adding a Run
============

A run contains a set of traces, indexed by the trace call nodes in the call tree.
However, each run does not need contain a trace for every trace call node in the call tree.

Each run must have a unique ID number.
If no ID number is given, they are assigned sequentially starting from one.

Additionally, each run may have a set of variables,
again, which are simply name-value pairs.

.. literalinclude:: sin_cos.py
   :language: python
   :start-at: from mhi.psout
   :end-at: run =

Add Traces to the Run
---------------------

A trace is simply a collection of values, which must all be of the same type.

.. literalinclude:: sin_cos.py
   :language: python
   :start-at: from math
   :end-at: run.add_trace(cos_call

Seeing the output
-----------------

We can see the results using ``pyplot`` from ``matplotlib``.
Use ``py -m pip install matplotlib`` to install ``matplotlib``:

.. literalinclude:: sin_cos.py
   :language: python
   :start-at: from matplotlib
   :end-before: plt.savefig
   :append: plt.show()

.. image:: sin_cos.png

PSCAD Flavored PSOUT
====================

PSCAD outputs a more complicated call tree, with additional structural requirements.
Enerplot 1.1 only knows how to read "PSCAD-Flavored" PSOUT files.

Requirements
------------

Call Tree

- The call tree must have top-level MODULE call node with ``Name="Root"``,
  ``Description="Root"``, and ``Source="Module"``.
- Beneath that "Root" node, MODULE call nodes with ``Name="..."`` and
  ``Source="Module"`` may be nested to any depth.
- Inside of those MODULE call nodes, there may be MODULE call nodes with
  ``Name="..."``, ``Description="Output Channel"``, and ``Source="PGB"``

  - Inside of the "PGB" node, there must be a MODULE call node with
    ``Name="Record"``, ``Description=""``, ``Source="Data"``

    - Finally, inside of the "Record" call nodes, there can be 1 or more TRACE
      call nodes with ``Name="PGB:Data"``, ``Description="...:PGB:...:#"``,
      ``Source="Trace"``.

- A top-level TRACE call node must exist wth ``Name="PGB:Domain"``,
  ``Description="PGB:Domain"``,  which is used as the domain trace for above
  "PGB" traces.

Traces

- The "PGB:Domain" trace must be created with ``Name="Domain"``,
  ``DataName="PGB:Domain"``, ``Group="Domain"``, ``Description="Domain"``, and
  ``Component="Domain"``.
- The PGB traces must be created with ``DataName="PGB:Data"``,
  ``Description="Output Channel"``.

To simplify the creating of the PSOUT file conforming to the above requirements,
the following functions have been added to the ``mhi.psout`` module:

- :meth:`.File.make_pscad_root`
- :meth:`.File.make_pscad_domain`
- :meth:`.Call.add_module`
- :meth:`.Call.add_pgb_call`
- :meth:`.Call.add_pgb_calls`
- :meth:`.Run.add_pscad_domain`
- :meth:`.Run.add_pscad_trace`

Example
-------

Like the earlier example, we'll create a "SinCos.psout" file,
containing a "Trig Funcs" module.  We'll add two "PGB" traces to
that module: "Sine" and "Cosine".

Unlike the previous example, we'll also add two "runs" to the file,
one for "50 Hertz" and one for "60 Hertz", to demonstrate that capability.

.. literalinclude:: pgbs.py
   :language: python

This ``SinCos.psout`` file may now be loaded in Enerplot and the traces plot,
as seen here:

.. image:: pgbs.png
