text:
#== # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Functions to print the tables.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ==#

export pretty_table

################################################################################
#                               Public Functions
################################################################################

"""
    pretty_table([io::IO | String,] table[, header::AbstractVecOrMat];  kwargs...)

Print to `io` the table `table` with header `header`. If `conf` is omitted, then
the default configuration will be used. If `io` is omitted, then it defaults to
`stdout`. If `String` is passed in the place of `io`, then a `String` with the
printed table will be returned by the function.

The `header` can be a `Vector` or a `Matrix`. If it is a `Matrix`, then each row
will be a header line. The first line is called *header* and the others are
called *sub-headers* . If `header` is empty or missing, then it will be
automatically filled with "Col.  i" for the *i*-th column.

When printing, it will be verified if `table` complies with **Tables.jl** API.
If it is compliant, then this interface will be used to print the table. If it
is not compliant, then only the following types are supported:

1. `AbstractVector`: any vector can be printed. In this case, the `header`
   **must** be a vector, where the first element is considered the header and
   the others are the sub-headers.
2. `AbstractMatrix`: any matrix can be printed.
3. `Dict`: any `Dict` can be printed. In this case, the special keyword
   `sortkeys` can be used to select whether or not the user wants to print the
   dictionary with the keys sorted. If it is `false`, then the elements will be
   printed on the same order returned by the functions `keys` and `values`.
   Notice that this assumes that the keys are sortable, if they are not, then an
   error will be thrown.

# Keywords

* `alignment`: Select the alignment of the columns (see the section `Alignment`).
* `backend`: Select which back-end will be used to print the table (see the
             section `Backend`). Notice that the additional configuration in
             `kwargs...` depends on the selected backend. (see the section
             `Backend`).
* `cell_alignment`: A tuple of functions with the signature `f(data,i,j)` that
                    overrides the alignment of the cell `(i,j)` to the value
                    returned by `f`. It can also be a single function, when it
                    is assumed that only one alignment function is required, or
                    `nothing`, when no cell alignment modification will be
                    performed. If the function `f` does not return a valid
                    alignment symbol as shown in section `Alignment`, then it
                    will be discarded. For convenience, it can also be a
                    dictionary of type `(i,j) => a` that overrides the
                    alignment of the cell `(i,j)` to `a`. `a` must be a symbol
                    like specified in the section `Alignment`.
  !!! note

      If more than one alignment function is passed to `cell_alignment`, then
      the functions will be evaluated in the same order of the tuple. The
      first one that returns a valid alignment symbol for each cell is applied,
      and the rest is discarded.

  (**Default** = `nothing`)
* `cell_first_line_only`: If `true`, then only the first line of each cell will
  be printed. (**Default** = `false`)
* `compact_printing`: Select if the option `:compact` will be used when printing
                      the data. (**Default** = `true`)
* `filters_row`: Filters for the rows (see the section `Filters`).
* `filters_col`: Filters for the columns (see the section `Filters`).
* `formatters`: See the section `Formatters`.
* `header_alignment`: Select the alignment of the header columns (see the
                      section `Alignment`). If the symbol that specifies the
                      alignment is `:s` for a specific column, then the same
                      alignment in the keyword `alignment` for that column will
                      be used. (**Default** = `:s`)
* `header_cell_alignment`: This keyword has the same structure of
                           `cell_alignment` but in this case it operates in the
                           header. Thus, `(i,j)` will be a cell in the header
                           matrix that contains the header and sub-headers. This
                           means that the `data` field in the functions will be
                           the same value passed in the keyword `header`.
  !!! note

      If more than one alignment function is passed to `header_cell_alignment`,
      then the functions will be evaluated in the same order of the tuple. The
      first one that returns a valid alignment symbol for each cell is applied,
      and the rest is discarded.

  (**Default** = `nothing`)
* `renderer`: A symbol that indicates which function should be used to convert
              an object to a string. It can be `:print` to use the function
              `print` or `:show` to use the function `show`. Notice that this
              selection is applicable only to the table data. Headers,
              sub-headers, and row name column are always rendered with print.
              (**Default** = `:print`)
* `row_names`: A vector containing the row names that will be appended to the
               left of the table. If it is `nothing`, then the column with the
               row names will not be shown. Notice that the size of this vector
               must match the number of rows in the table.
               (**Default** = `nothing`)
* `row_name_alignment`: Alignment of the column with the rows name (see the
                        section `Alignment`).
* `row_name_column_title`: Title of the column with the row names.
                           (**Default** = "")
* `title`: The title of the table. If it is empty, then no title will be
           printed. (**Default** = "")
* `title_alignment`: Alignment of the title, which must be a symbol as explained
                     in the section `Alignment`. This argument is ignored in the
                     LaTeX backend. (**Default** = :l)

!!! note

    Notice that all back-ends have the keyword `tf` to specify the table
    printing format. Thus, if the keyword `backend` is not present or if it is
    `nothing`, then the back-end will be automatically inferred from the type of
    the keyword `tf`. In this case, if `tf` is also not present, then it just
    fall-back to the text back-end.

# Alignment

The keyword `alignment` can be a `Symbol` or a vector of `Symbol`.

If it is a symbol, we have the following behavior:

* `:l` or `:L`: the text of all columns will be left-aligned;
* `:c` or `:C`: the text of all columns will be center-aligned;
* `:r` or `:R`: the text of all columns will be right-aligned;
* Otherwise it defaults to `:r`.

If it is a vector, then it must have the same number of symbols as the number of
columns in `data`. The *i*-th symbol in the vector specify the alignment of the
*i*-th column using the same symbols as described previously.

# Filters

It is possible to specify filters to filter the data that will be printed. There
are two types of filters: the row filters, which are specified by the keyword
`filters_row`, and the column filters, which are specified by the keyword
`filters_col`.

The filters are a tuple of functions that must have the following signature:

```julia
f(data,i)::Bool
```

in which `data` is a pointer to the matrix that is being printed and `i` is the
i-th row in the case of the row filters or the i-th column in the case of column
filters. If this function returns `true` for `i`, then the i-th row (in case of
`filters_row`) or the i-th column (in case of `filters_col`) will be printed.
Otherwise, it will be omitted.

A set of filters can be passed inside of a tuple. Notice that, in this case,
**all filters** for a specific row or column must be return `true` so that it
can be printed, *i.e* the set of filters has an `AND` logic.

If the keyword is set to `nothing`, which is the default, then no filtering will
be applied to the data.

!!! note

    The filters do not change the row and column numbering for the others
    modifiers such as column width specification, formatters, and highlighters.
    Thus, for example, if only the 4-th row is printed, then it will also be
    referenced inside the formatters and highlighters as 4 instead of 1.

---

# Pretty table text back-end

This back-end produces text tables. This back-end can be used by selecting
`back-end = :text`.

# Keywords

* `border_crayon`: Crayon to print the border.
* `header_crayon`: Crayon to print the header.
* `subheaders_crayon`: Crayon to print sub-headers.
* `rownum_header_crayon`: Crayon for the header of the column with the row
                          numbers.
* `text_crayon`: Crayon to print default text.
* `autowrap`: If `true`, then the text will be wrapped on spaces to fit the
              column. Notice that this function requires `linebreaks = true` and
              the column must have a fixed size (see `columns_width`).
* `body_hlines`: A vector of `Int` indicating row numbers in which an additional
                 horizontal line should be drawn after the row. Notice that
                 numbers lower than 1 and equal or higher than the number of
                 printed rows will be neglected. This vector will be appended to
                 the one in `hlines`, but the indices here are related to the
                 printed rows of the body. Thus, if `1` is added to
                 `body_hlines`, then a horizontal line will be drawn after the
                 first data row. (**Default** = `Int[]`)
* `body_hlines_format`: A tuple of 4 characters specifying the format of the
                        horizontal lines that will be drawn by `body_hlines`.
                        The characters must be the left intersection, the middle
                        intersection, the right intersection, and the row. If it
                        is `nothing`, then it will use the same format specified
                        in `tf`. (**Default** = `nothing`)
* `columns_width`: A set of integers specifying the width of each column. If the
                   width is equal or lower than 0, then it will be automatically
                   computed to fit the large cell in the column. If it is
                   a single integer, then this number will be used as the size
                   of all columns. (**Default** = 0)
* `crop`: Select the printing behavior when the data is bigger than the
          available display size (see `display_size`). It can be `:both` to crop
          on vertical and horizontal direction, `:horizontal` to crop only on
          horizontal direction, `:vertical` to crop only on vertical direction,
          or `:none` to do not crop the data at all. If the `io` has
          `:limit => true`, then `crop` is set to `:both` by default.
          Otherwise, it is set to `:none` by default.
* `crop_num_lines_at_beginning`: Number of lines to be left at the beginning of
                                 the printing when vertically cropping the
                                 output. Notice that the lines required to show
                                 the title are automatically computed.
                                 (**Default** = 0)
* `crop_subheader`: If `true`, then the sub-header size will not be taken into
                    account when computing the column size. Hence, the print
                    algorithm can crop it to save space. This has no effect if
                    the user selects a fixed column width.
                    (**Default** = `false`)
* `continuation_row_alignment`: A symbol that defines the alignment of the cells
                                in the continuation row. This row is printed if
                                the table is vertically cropped.
                                (**Default** = `:c`)
* `display_size`: A tuple of two integers that defines the display size (num. of
                  rows, num. of columns) that is available to print the table.
                  It is used to crop the data depending on the value of the
                  keyword `crop`. Notice that if a dimension is not positive,
                  then it will be treated as unlimited.
                  (**Default** = `displaysize(io)`)
* `ellipsis_line_skip`: An integer defining how many lines will be skipped from
                        showing the ellipsis that indicates the text was
                        cropped. (**Default** = 0)
* `equal_columns_width`: If `true`, then all the columns will have the same
                         width. (**Default** = `false`)
* `highlighters`: An instance of `Highlighter` or a tuple with a list of
                  text highlighters (see the section `Text highlighters`).
* `hlines`: This variable controls where the horizontal lines will be drawn. It
            can be `nothing`, `:all`, `:none` or a vector of integers.
    - If it is `nothing`, which is the default, then the configuration will be
      obtained from the table format in the variable `tf` (see `TextFormat`).
    - If it is `:all`, then all horizontal lines will be drawn.
    - If it is `:none`, then no horizontal line will be drawn.
    - If it is a vector of integers, then the horizontal lines will be drawn
      only after the rows in the vector. Notice that the top line will be drawn
      if `0` is in `hlines`, and the header and subheaders are considered as
      only 1 row. Furthermore, it is important to mention that the row number in
      this variable is related to the **printed rows**. Thus, it is affected by
      filters, and by the option to suppress the header `noheader`. Finally, for
      convenience, the top and bottom lines can be drawn by adding the symbols
      `:begin` and `:end` to this vector, respectively, and the line after the
      header can be drawn by adding the symbol `:header`.
  !!! info

      The values of `body_hlines` will be appended to this vector. Thus,
      horizontal lines can be drawn even if `hlines` is `:none`.

  (**Default** = `nothing`)
* `linebreaks`: If `true`, then `\\n` will break the line inside the cells.
                (**Default** = `false`)
* `maximum_columns_width`: A set of integers specifying the maximum width of
                           each column. If the width is equal or lower than 0,
                           then it will be ignored. If it is a single integer,
                           then this number will be used as the maximum width
                           of all columns. Notice that the parameter
                           `columns_width` has precedence over this one.
                           (**Default** = 0)
* `minimum_columns_width`: A set of integers specifying the minimum width of
                           each column. If the width is equal or lower than 0,
                           then it will be ignored. If it is a single integer,
                           then this number will be used as the minimum width
                           of all columns. Notice that the parameter
                           `columns_width` has precedence over this one.
                           (**Default** = 0)
* `newline_at_end`: If `false`, then the table will not end with a newline
                    character. (**Default** = `true`)
* `noheader`: If `true`, then the header will not be printed. Notice that all
              keywords and parameters related to the header and sub-headers will
              be ignored. (**Default** = `false`)
* `nosubheader`: If `true`, then the sub-header will not be printed, *i.e.* the
                 header will contain only one line. Notice that this option has
                 no effect if `noheader = true`. (**Default** = `false`)
* `omitted_cell_summary_crayon`: Crayon used to print the omitted cell summary.
* `overwrite`: If `true`, then the same number of lines in the printed table
               will be deleted from the output `io`. This can be used to update
               the table in the display continuously. (**Default** = `false`)
* `row_number_alignment`: Select the alignment of the row number column (see the
                          section `Alignment`). (**Default** = `:r`)
* `row_number_column_title`: The title of the column that shows the row numbers.
                             (**Default** = "Row")
* `show_omitted_cell_summary`: If `true`, then a summary will be printed after
                               the table with the number of columns and rows
                               that were omitted. (**Default** = `true`)
* `show_row_number`: If `true`, then a new column will be printed showing the
                     row number. (**Default** = `false`)
* `tf`: Table format used to print the table (see `TextFormat`).
        (**Default** = `tf_unicode`)
* `title_autowrap`: If `true`, then the title text will be wrapped considering
                    the title size. Otherwise, lines larger than the title size
                    will be cropped. (**Default** = `false`)
* `title_crayon`: Crayon to print the title.
* `title_same_width_as_table`: If `true`, then the title width will match that
                               of the table. Otherwise, the title size will be
                               equal to the display width.
                               (**Default** = `false`)
* `vcrop_mode`: This variable defines the vertical crop behavior. If it is
                `:bottom`, then the data, if required, will be cropped in the
                bottom. On the other hand, if it is `:middle`, then the data
                will be cropped in the middle if necessary.
                (**Default** = `:bottom`)
* `vlines`: This variable controls where the vertical lines will be drawn. It
            can be `nothing`, `:all`, `:none` or a vector of integers.
    - If it is `nothing`, which is the default, then the configuration will be
      obtained from the table format in the variable `tf` (see `TextFormat`).
    - If it is `:all`, then all vertical lines will be drawn.
    - If it is `:none`, then no vertical line will be drawn.
    - If it is a vector of integers, then the vertical lines will be drawn only
      after the columns in the vector. Notice that the top line will be drawn if
      `0` is in `vlines`. Furthermore, it is important to mention that the
      column number in this variable is related to the **printed column**. Thus,
      it is affected by filters, and by the options `row_names` and
      `show_row_number`. Finally, for convenience, the left and right vertical
      lines can be drawn by adding the symbols `:begin` and `:end` to this
      vector, respectively, and the line after the header can be drawn by adding
      the symbol `:header`.

  (**Default** = `nothing`)

The keywords `header_crayon` and `subheaders_crayon` can be a `Crayon` or a
`Vector{Crayon}`. In the first case, the `Crayon` will be applied to all the
elements. In the second, each element can have its own crayon, but the length of
the vector must be equal to the number of columns in the data.

## Crayons

A `Crayon` is an object that handles a style for text printed on terminals. It
is defined in the package
[Crayons.jl](https://github.com/KristofferC/Crayons.jl). There are many options
available to customize the style, such as foreground color, background color,
bold text, etc.

A `Crayon` can be created in two different ways:

```julia-repl
julia> Crayon(foreground = :blue, background = :black, bold = :true)

julia> crayon"blue bg:black bold"
```

For more information, see the package documentation.

## Text highlighters

A set of highlighters can be passed as a `Tuple` to the `highlighters` keyword.
Each highlighter is an instance of the structure `Highlighter` that contains
three fields:

* `f`: Function with the signature `f(data,i,j)` in which should return `true`
       if the element `(i,j)` in `data` must be highlighter, or `false`
       otherwise.
* `fd`: Function with the signature `f(h,data,i,j)` in which `h` is the
        highlighter. This function must return the `Crayon` to be applied to the
        cell that must be highlighted.
* `crayon`: The `Crayon` to be applied to the highlighted cell if the default
            `fd` is used.

The function `f` has the following signature:

    f(data, i, j)

in which `data` is a reference to the data that is being printed, and `i` and
`j` are the element coordinates that are being tested. If this function returns
`true`, then the cell `(i,j)` will be highlighted.

If the function `f` returns true, then the function `fd(h,data,i,j)` will be
called and must return a `Crayon` that will be applied to the cell.

A highlighter can be constructed using three helpers:

    Highlighter(f::Function; kwargs...)

where it will construct a `Crayon` using the keywords in `kwargs` and apply it
to the highlighted cell,

    Highlighter(f::Function, crayon::Crayon)

where it will apply the `crayon` to the highlighted cell, and

    Highlighter(f::Function, fd::Function)

where it will apply the `Crayon` returned by the function `fd` to the
highlighted cell.

!!! info

    If only a single highlighter is wanted, then it can be passed directly to
    the keyword `highlighter` without being inside a `Tuple`.

!!! note

    If multiple highlighters are valid for the element `(i,j)`, then the applied
    style will be equal to the first match considering the order in the tuple
    `highlighters`.

!!! note

    If the highlighters are used together with [Formatters](@ref), then the
    change in the format **will not** affect the parameter `data` passed to the
    highlighter function `f`. It will always receive the original, unformatted
    value.

---

# Pretty table HTML backend

This backend produces HTML tables. This backend can be used by selecting
`backend = :html`.

# Keywords

* `highlighters`: An instance of `HTMLHighlighter` or a tuple with a list of
                  HTML highlighters (see the section `HTML highlighters`).
* `linebreaks`: If `true`, then `\\n` will be replaced by `<br>`.
                (**Default** = `false`)
* `noheader`: If `true`, then the header will not be printed. Notice that all
              keywords and parameters related to the header and sub-headers will
              be ignored. (**Default** = `false`)
* `nosubheader`: If `true`, then the sub-header will not be printed, *i.e.* the
                 header will contain only one line. Notice that this option has
                 no effect if `noheader = true`. (**Default** = `false`)
* `standalone`: If `true`, then a complete HTML page will be generated.
                Otherwise, only the content between the tags `<table>` and
                `</table>` will be printed (with the tags included).
                (**Default** = `true`)
* `tf`: An instance of the structure `HTMLTableFormat` that defines the general
        format of the HTML table.

## HTML highlighters

A set of highlighters can be passed as a `Tuple` to the `highlighters` keyword.
Each highlighter is an instance of the structure [`HTMLHighlighter`](@ref). It
contains the following two public fields:

* `f`: Function with the signature `f(data,i,j)` in which should return `true`
       if the element `(i,j)` in `data` must be highlighted, or `false`
       otherwise.
* `fd`: Function with the signature `f(h,data,i,j)` in which `h` is the
        highlighter. This function must return the `HTMLDecoration` to be
        applied to the cell that must be highlighted.

The function `f` has the following signature:

    f(data, i, j)

in which `data` is a reference to the data that is being printed, and `i` and
`j` are the element coordinates that are being tested. If this function returns
`true`, then the highlight style will be applied to the `(i,j)` element.
Otherwise, the default style will be used.

If the function `f` returns true, then the function `fd(h,data,i,j)` will be
called and must return an element of type `HTMLDecoration` that contains the
decoration to be applied to the cell.

A HTML highlighter can be constructed using two helpers:

    HTMLHighlighter(f::Function, decoration::HTMLDecoration)

    HTMLHighlighter(f::Function, fd::Function)

The first will apply a fixed decoration to the highlighted cell specified in
`decoration` whereas the second let the user select the desired decoration by
specifying the function `fd`.

!!! info

    If only a single highlighter is wanted, then it can be passed directly to
    the keyword `highlighter` without being inside a `Tuple`.

!!! note

    If multiple highlighters are valid for the element `(i,j)`, then the applied
    style will be equal to the first match considering the order in the tuple
    `highlighters`.

!!! note

    If the highlighters are used together with [Formatters](@ref), then the
    change in the format **will not** affect the parameter `data` passed to the
    highlighter function `f`. It will always receive the original, unformatted
    value.

---

# Pretty table LaTeX backend

This backend produces LaTeX tables. This backend can be used by selecting
`backend = :latex`.

# Keywords

* `body_hlines`: A vector of `Int` indicating row numbers in which an additional
                 horizontal line should be drawn after the row. Notice that
                 numbers lower than 1 and equal or higher than the number of
                 printed rows will be neglected. This vector will be appended to
                 the one in `hlines`, but the indices here are related to the
                 printed rows of the body. Thus, if `1` is added to
                 `body_hlines`, then a horizontal line will be drawn after the
                 first data row. (**Default** = `Int[]`)
* `highlighters`: An instance of `LatexHighlighter` or a tuple with a list of
                  LaTeX highlighters (see the section `LaTeX highlighters`).
* `hlines`: This variable controls where the horizontal lines will be drawn. It
            can be `nothing`, `:all`, `:none` or a vector of integers.
    - If it is `nothing`, which is the default, then the configuration will be
      obtained from the table format in the variable `tf` (see
      `LatexTableFormat`).
    - If it is `:all`, then all horizontal lines will be drawn.
    - If it is `:none`, then no horizontal line will be drawn.
    - If it is a vector of integers, then the horizontal lines will be drawn
      only after the rows in the vector. Notice that the top line will be drawn
      if `0` is in `hlines`, and the header and subheaders are considered as
      only 1 row. Furthermore, it is important to mention that the row number in
      this variable is related to the **printed rows**. Thus, it is affected by
      filters, and by the option to suppress the header `noheader`. Finally, for
      convenience, the top and bottom lines can be drawn by adding the symbols
      `:begin` and `:end` to this vector, respectively, and the line after the
      header can be drawn by adding the symbol `:header`.
  !!! info

      The values of `body_hlines` will be appended to this vector. Thus,
      horizontal lines can be drawn even if `hlines` is `:none`.

  (**Default** = `nothing`)
* `longtable_footer`: The string that will be drawn in the footer of the tables
                      before a page break. This only works if `table_type` is
                      `:longtable`. If it is `nothing`, then no footer will be
                      used. (**Default** = `nothing`)
* `noheader`: If `true`, then the header will not be printed. Notice that all
              keywords and parameters related to the header and sub-headers will
              be ignored. (**Default** = `false`)
* `nosubheader`: If `true`, then the sub-header will not be printed, *i.e.* the
                 header will contain only one line. Notice that this option has
                 no effect if `noheader = true`. (**Default** = `false`)
* `row_number_alignment`: Select the alignment of the row number column (see the
                          section `Alignment`). (**Default** = `:r`)
* `table_type`: Select which LaTeX environment will be used to print the table.
                Currently supported options are `:tabular` for `tabular` or
                `:longtable` for `longtable`. (**Default** = `:tabular`)
* `tf`: An instance of the structure `LatexTableFormat` that defines the general
        format of the LaTeX table.
* `vlines`: This variable controls where the vertical lines will be drawn. It
            can be `:all`, `:none` or a vector of integers. In the first case
            (the default behavior), all vertical lines will be drawn. In the
            second case, no vertical line will be drawn. In the third case,
            the vertical lines will be drawn only after the columns in the
            vector. Notice that the left border will be drawn if `0` is in
            `vlines`. Furthermore, it is important to mention that the column
            number in this variable is related to the **printed columns**. Thus,
            it is affected by filters, and by the columns added using the
            variable `show_row_number`. Finally, for convenience, the left and
            right border can be drawn by adding the symbols `:begin` and `:end`
            to this vector, respectively. (**Default** = `:none`)

## LaTeX highlighters

A set of highlighters can be passed as a `Tuple` to the `highlighters` keyword.
Each highlighter is an instance of the structure `LatexHighlighter`. It contains
the following two fields:

* `f`: Function with the signature `f(data,i,j)` in which should return `true`
       if the element `(i,j)` in `data` must be highlighted, or `false`
       otherwise.
* `fd`: A function with the signature `f(data,i,j,str)::String` in which
        `data` is the matrix, `(i,j)` is the element position in the table, and
        `str` is the data converted to string. This function must return a
        string that will be placed in the cell.

The function `f` has the following signature:

    f(data, i, j)

in which `data` is a reference to the data that is being printed, `i` and `j`
are the element coordinates that are being tested. If this function returns
`true`, then the highlight style will be applied to the `(i,j)` element.
Otherwise, the default style will be used.

If the function `f` returns true, then the function `fd(data,i,j,str)` will be
called and must return the LaTeX string that will be placed in the cell.

There are two helpers that can be used to create LaTeX highlighters:

    LatexHighlighter(f::Function, envs::Union{String,Vector{String}})

    LatexHighlighter(f::Function, fd::Function)

The first will apply recursively all the LaTeX environments in `envs` to the
highlighted text whereas the second let the user select the desired decoration
by specifying the function `fd`.

Thus, for example:

    LatexHighlighter((data,i,j)->true, ["textbf", "small"])

will wrap all the cells in the table in the following environment:

    \\textbf{\\small{<Cell text>}}

!!! info

    If only a single highlighter is wanted, then it can be passed directly to
    the keyword `highlighter` without being inside a `Tuple`.

!!! note

    If multiple highlighters are valid for the element `(i,j)`, then the applied
    style will be equal to the first match considering the order in the tuple
    `highlighters`.

!!! note

    If the highlighters are used together with [Formatters](@ref), then the
    change in the format **will not** affect the parameter `data` passed to the
    highlighter function `f`. It will always receive the original, unformatted
    value.

---

# Formatters

The keyword `formatters` can be used to pass functions to format the values in
the columns. It must be a tuple of functions in which each function has the
following signature:

    f(v, i, j)

where `v` is the value in the cell, `i` is the row number, and `j` is the column
number. Thus, it must return the formatted value of the cell `(i,j)` that has
the value `v`. Notice that the returned value will be converted to string after
using the function `sprint`.

This keyword can also be a single function, meaning that only one formatter is
available, or `nothing`, meaning that no formatter will be used.

For example, if we want to multiply all values in odd rows of the column 2 by π,
then the formatter should look like:

    formatters = (v,i,j) -> (j == 2 && isodd(i)) ? v*π : v

If multiple formatters are available, then they will be applied in the same
order as they are located in the tuple. Thus, for the following `formatters`:

    formatters = (f1, f2, f3)

each element `v` in the table (i-th row and j-th column) will be formatted by:

    v = f1(v,i,j)
    v = f2(v,i,j)
    v = f3(v,i,j)

Thus, the user must be ensure that the type of `v` between the calls are
compatible.

"""
@inline function pretty_table(data; kwargs...)
    io = IOContext(stdout, :limit => true)
    _pretty_table(io, data, String[]; kwargs...)
end

@inline function pretty_table(data, header::AbstractVecOrMat; kwargs...)
    io = IOContext(stdout, :limit => true)
    _pretty_table(io, data, header; kwargs...)
end

# This definition is required to avoid ambiguities.
@inline function pretty_table(data::AbstractVecOrMat,
                              header::AbstractVecOrMat;
                              kwargs...)
    io = IOContext(stdout, :limit => true)
    _pretty_table(io, data, header; kwargs...)
end

# This definition is required to avoid ambiguities.
pretty_table(io::IO, data::AbstractVecOrMat; kwargs...) =
    _pretty_table(io, data, String[]; kwargs...)

pretty_table(io::IO, data; kwargs...) =
    _pretty_table(io, data, String[]; kwargs...)

pretty_table(io::IO, data, header::AbstractVecOrMat; kwargs...) =
    _pretty_table(io, data, header; kwargs...)

# This definition is required to avoid ambiguities.
pretty_table(::Type{String}, data::AbstractVecOrMat; kwargs...) =
    pretty_table(String, data, String[]; kwargs...)

pretty_table(::Type{String}, data; kwargs...) =
    pretty_table(String, data, String[]; kwargs...)

function pretty_table(::Type{String}, data, header::AbstractVecOrMat; kwargs...)
    io = IOBuffer()
    _pretty_table(io, data, header; kwargs...)
    return String(take!(io))
end


text:
module DynamicObstaclesUndirectedModule

import ..GridWorlds as GW
import Random
import ReinforcementLearningBase as RLBase

#####
##### game logic
#####

const NUM_OBJECTS = 4
const AGENT = 1
const WALL = 2
const GOAL = 3
const OBSTACLE = 4
const NUM_ACTIONS = 4

mutable struct DynamicObstaclesUndirected{R, RNG} <: GW.AbstractGridWorld
    tile_map::BitArray{3}
    agent_position::CartesianIndex{2}
    reward::R
    rng::RNG
    done::Bool
    terminal_reward::R
    terminal_penalty::R
    goal_position::CartesianIndex{2}
    num_obstacles::Int
    obstacle_positions::Vector{CartesianIndex{2}}
end

function DynamicObstaclesUndirected(; R = Float32, height = 8, width = 8, num_obstacles = floor(Int, sqrt(height * width) / 2), rng = Random.GLOBAL_RNG)
    tile_map = falses(NUM_OBJECTS, height, width)

    tile_map[WALL, 1, :] .= true
    tile_map[WALL, height, :] .= true
    tile_map[WALL, :, 1] .= true
    tile_map[WALL, :, width] .= true

    agent_position = GW.sample_empty_position(rng, tile_map)
    tile_map[AGENT, agent_position] = true

    goal_position = GW.sample_empty_position(rng, tile_map)
    tile_map[GOAL, goal_position] = true

    obstacle_positions = Array{CartesianIndex{2}}(undef, num_obstacles)
    for i in 1:num_obstacles
        obstacle_position = GW.sample_empty_position(rng, tile_map)
        obstacle_positions[i] = obstacle_position
        tile_map[OBSTACLE, obstacle_position] = true
    end

    reward = zero(R)
    done = false
    terminal_reward = one(R)
    terminal_penalty = -one(R)

    env = DynamicObstaclesUndirected(tile_map, agent_position, reward, rng, done, terminal_reward, terminal_penalty, goal_position, num_obstacles, obstacle_positions)

    # GW.reset!(env)

    return env
end

function GW.reset!(env::DynamicObstaclesUndirected)
    tile_map = env.tile_map
    rng = env.rng

    tile_map[AGENT, env.agent_position] = false
    tile_map[GOAL, env.goal_position] = false

    for i in 1:env.num_obstacles
        tile_map[OBSTACLE, env.obstacle_positions[i]] = false
    end

    new_agent_position = GW.sample_empty_position(rng, tile_map)
    env.agent_position = new_agent_position
    tile_map[AGENT, new_agent_position] = true

    new_goal_position = GW.sample_empty_position(rng, tile_map)
    env.goal_position = new_goal_position
    tile_map[GOAL, new_goal_position] = true

    for i in 1:env.num_obstacles
        new_obstacle_position = GW.sample_empty_position(rng, tile_map)
        env.obstacle_positions[i] = new_obstacle_position
        tile_map[OBSTACLE, new_obstacle_position] = true
    end

    env.reward = zero(env.reward)
    env.done = false

    return nothing
end

function GW.act!(env::DynamicObstaclesUndirected, action)
    @assert action in Base.OneTo(NUM_ACTIONS) "Invalid action $(action). Action must be in Base.OneTo($(NUM_ACTIONS))"

    tile_map = env.tile_map
    update_obstacles!(env)

    agent_position = env.agent_position

    if action == 1
        new_agent_position = GW.move_up(agent_position)
    elseif action == 2
        new_agent_position = GW.move_down(agent_position)
    elseif action == 3
        new_agent_position = GW.move_left(agent_position)
    else
        new_agent_position = GW.move_right(agent_position)
    end

    if !tile_map[WALL, new_agent_position]
        tile_map[AGENT, agent_position] = false
        env.agent_position = new_agent_position
        tile_map[AGENT, new_agent_position] = true
    end

    if tile_map[GOAL, env.agent_position]
        env.reward = env.terminal_reward
        env.done = true
    elseif tile_map[OBSTACLE, env.agent_position]
        env.reward = env.terminal_penalty
        env.done = true
    else
        env.reward = zero(env.reward)
        env.done = false
    end

    return nothing
end

function update_obstacles!(env::DynamicObstaclesUndirected)
    tile_map = env.tile_map
    rng = env.rng

    for (i, position) in enumerate(env.obstacle_positions)
        tile_map[OBSTACLE, position] = false
        neighbors = (position, CartesianIndex(position[1] - 1, position[2]), CartesianIndex(position[1], position[2] - 1), CartesianIndex(position[1], position[2] + 1), CartesianIndex(position[1] + 1, position[2]))
        new_obstacle_position = rand(rng, neighbors)
        while tile_map[WALL, new_obstacle_position] || tile_map[GOAL, new_obstacle_position] || tile_map[OBSTACLE, new_obstacle_position]
            new_obstacle_position = rand(rng, neighbors)
        end
        env.obstacle_positions[i] = new_obstacle_position
        tile_map[OBSTACLE, new_obstacle_position] = true
    end

    return nothing
end

#####
##### miscellaneous
#####

GW.get_height(env::DynamicObstaclesUndirected) = size(env.tile_map, 2)
GW.get_width(env::DynamicObstaclesUndirected) = size(env.tile_map, 3)

GW.get_action_names(env::DynamicObstaclesUndirected) = (:MOVE_UP, :MOVE_DOWN, :MOVE_LEFT, :MOVE_RIGHT)
GW.get_object_names(env::DynamicObstaclesUndirected) = (:AGENT, :WALL, :GOAL, :OBSTACLE)

function GW.get_pretty_tile_map(env::DynamicObstaclesUndirected, position::CartesianIndex{2})
    characters = ('☻', '█', '♥', '⊗', '⋅')

    object = findfirst(@view env.tile_map[:, position])
    if isnothing(object)
        return characters[end]
    else
        return characters[object]
    end
end

function GW.get_pretty_sub_tile_map(env::DynamicObstaclesUndirected, window_size, position::CartesianIndex{2})
    tile_map = env.tile_map
    agent_position = env.agent_position

    characters = ('☻', '█', '♥', '⊗', '⋅')

    sub_tile_map = GW.get_sub_tile_map(tile_map, agent_position, window_size)

    object = findfirst(@view sub_tile_map[:, position])
    if isnothing(object)
        return characters[end]
    else
        return characters[object]
    end
end

function Base.show(io::IO, ::MIME"text/plain", env::DynamicObstaclesUndirected)
    str = "tile_map:\n"
    str = str * GW.get_pretty_tile_map(env)
    str = str * "\nsub_tile_map:\n"
    str = str * GW.get_pretty_sub_tile_map(env, GW.get_window_size(env))
    str = str * "\nreward: $(env.reward)"
    str = str * "\ndone: $(env.done)"
    str = str * "\naction_names: $(GW.get_action_names(env))"
    str = str * "\nobject_names: $(GW.get_object_names(env))"
    print(io, str)
    return nothing
end

GW.get_action_keys(env::DynamicObstaclesUndirected) = ('w', 's', 'a', 'd')

#####
##### DynamicObstaclesUndirected
#####

RLBase.StateStyle(env::GW.RLBaseEnv{E}) where {E <: DynamicObstaclesUndirected} = RLBase.InternalState{Any}()
RLBase.state_space(env::GW.RLBaseEnv{E}, ::RLBase.InternalState) where {E <: DynamicObstaclesUndirected} = nothing
RLBase.state(env::GW.RLBaseEnv{E}, ::RLBase.InternalState) where {E <: DynamicObstaclesUndirected} = env.env.tile_map

RLBase.reset!(env::GW.RLBaseEnv{E}) where {E <: DynamicObstaclesUndirected} = GW.reset!(env.env)

RLBase.action_space(env::GW.RLBaseEnv{E}) where {E <: DynamicObstaclesUndirected} = Base.OneTo(NUM_ACTIONS)
(env::GW.RLBaseEnv{E})(action) where {E <: DynamicObstaclesUndirected} = GW.act!(env.env, action)

RLBase.reward(env::GW.RLBaseEnv{E}) where {E <: DynamicObstaclesUndirected} = env.env.reward
RLBase.is_terminated(env::GW.RLBaseEnv{E}) where {E <: DynamicObstaclesUndirected} = env.env.done

end # module

ext: jl


text:
"""
$(SIGNATURES)
Takes a list of pairs of `variables=>values` and an ordered list of variables
and creates the array of values in the correct order with default values when
applicable.
"""
function varmap_to_vars(varmap::Dict, varlist; defaults=Dict())
    varmap = merge(defaults, varmap) # prefers the `varmap`
    varmap = Dict(value(k)=>value(varmap[k]) for k in keys(varmap))
    # resolve symbolic parameter expressions
    for (p, v) in pairs(varmap)
        varmap[p] = fixpoint_sub(v, varmap)
    end
    T′ = eltype(values(varmap))
    T = Base.isconcretetype(T′) ? T′ : Base.promote_typeof(values(varmap)...)
    out = Vector{T}(undef, length(varlist))
    missingvars = setdiff(varlist, keys(varmap))
    isempty(missingvars) || throw(ArgumentError("$missingvars are missing from the variable map."))

    for (i, var) in enumerate(varlist)
        out[i] = varmap[var]
    end
    out
end

function varmap_to_vars(varmap::Union{AbstractArray,Tuple},varlist; kw...)
    if eltype(varmap) <: Pair
        out = varmap_to_vars(Dict(varmap), varlist; kw...)
        if varmap isa Tuple
            (out..., )
        else
            # Note that `varmap` might be longer than `varlist`
            construct_state(varmap, out)
        end
    else
        varmap
    end
end
varmap_to_vars(varmap::DiffEqBase.NullParameters,varlist; kw...) = varmap
varmap_to_vars(varmap::Nothing,varlist; kw...) = varmap

construct_state(x::StaticArray, y) = StaticArrays.similar_type(x, eltype(y), StaticArrays.Size(size(y)...))(y)
construct_state(x::Array, y) = y


text:
# This file is a part of StruckVMEDevices.jl, licensed under the MIT License (MIT).

module MemRegisters

using ArgCheck
using ArraysOfArrays
using BitOperations
using ElasticArrays
using Sockets
using StructArrays
using Tables
using TypedTables
using UnsafeArrays

include("registers.jl")

end # module


text:
mutable struct gpuCTPF <: TopicModel
	"GPU accelerated collaborative topic Poisson factorization."

	K::Int
	M::Int
	V::Int
	U::Int
	N::Vector{Int}
	C::Vector{Int}
	R::Vector{Int}
	corp::Corpus
	topics::VectorList{Int}
	scores::Matrix{Float32}
	libs::VectorList{Int}
	drecs::VectorList{Int}
	urecs::VectorList{Int}
	a::Float32
	b::Float32
	c::Float32
	d::Float32
	e::Float32
	f::Float32
	g::Float32
	h::Float32
	alef::Matrix{Float32}
	he::Matrix{Float32}
	bet::Vector{Float32}
	vav::Vector{Float32}
	gimel::VectorList{Float32}
	gimel_old::VectorList{Float32}
	zayin::VectorList{Float32}
	dalet::Vector{Float32}
	het::Vector{Float32}
	phi::MatrixList{Float32}
	xi::MatrixList{Float32}
	elbo::Float32
	device::cl.Device
	context::cl.Context
	queue::cl.CmdQueue
	alef_kernel::cl.Kernel
	he_kernel::cl.Kernel
	bet_kernel::cl.Kernel
	vav_kernel::cl.Kernel
	gimel_kernel::cl.Kernel
	zayin_kernel::cl.Kernel
	dalet_kernel::cl.Kernel
	het_kernel::cl.Kernel
	phi_kernel::cl.Kernel
	phi_norm_kernel::cl.Kernel
	xi_kernel::cl.Kernel
	xi_norm_kernel::cl.Kernel
	N_cumsum_buffer::cl.Buffer{Int}
	J_cumsum_buffer::cl.Buffer{Int}
	R_cumsum_buffer::cl.Buffer{Int}
	Y_cumsum_buffer::cl.Buffer{Int}
	terms_buffer::cl.Buffer{Int}
	terms_sortperm_buffer::cl.Buffer{Int}
	counts_buffer::cl.Buffer{Int}
	readers_buffer::cl.Buffer{Int}
	ratings_buffer::cl.Buffer{Int}
	readers_sortperm_buffer::cl.Buffer{Int}
	alef_buffer::cl.Buffer{Float32}
	he_buffer::cl.Buffer{Float32}
	bet_buffer::cl.Buffer{Float32}
	vav_buffer::cl.Buffer{Float32}
	gimel_buffer::cl.Buffer{Float32}
	zayin_buffer::cl.Buffer{Float32}
	dalet_buffer::cl.Buffer{Float32}
	het_buffer::cl.Buffer{Float32}
	phi_buffer::cl.Buffer{Float32}
	xi_buffer::cl.Buffer{Float32}

	function gpuCTPF(corp::Corpus, K::Integer)
		check_corp(corp)
		K > 0 || throw(ArgumentError("Number of topics must be a positive integer."))

		M, V, U = size(corp)
		N = [length(doc) for doc in corp]
		C = [size(doc) for doc in corp]
		R = [length(doc.readers) for doc in corp]

		topics = [collect(1:V) for _ in 1:K]
		scores = zeros(M, U)

		libs = [Int[] for _ in 1:U]
		for d in 1:M, u in corp[d].readers
			push!(libs[u], d)
		end

		urecs = VectorList{Int}(undef, U)
		for u in 1:U
			ur = trues(M)
			ur[libs[u]] .= false
			urecs[u] = findall(ur)
		end

		drecs = VectorList{Int}(undef, M)
		for d in 1:M
			nr = trues(U)
			nr[corp[d].readers] .= false
			drecs[d] = findall(nr)
		end

		a, b, c, d, e, f, g, h = fill(0.1f0, 8)

		alef = exp.(rand(Dirichlet(V, 1.0f0), K)' .- 0.5f0)
		he = ones(Float32, K, U)
		bet = ones(Float32, K)
		vav = ones(Float32, K)
		gimel = [ones(Float32, K) for _ in 1:M]
		gimel_old = deepcopy(gimel)
		zayin = [ones(Float32, K) for _ in 1:M]
		dalet = ones(Float32, K)
		het = ones(Float32, K)
		phi = [fill(Float32(1/K), K, N[d]) for d in 1:M]
		xi = [fill(Float32(1/2K), 2K, R[d]) for d in 1:M]
		elbo = 0f0

		device, context, queue = cl.create_compute_context()

		alef_program = cl.Program(context, source=CTPF_ALEF_c) |> cl.build!
		he_program = cl.Program(context, source=CTPF_HE_c) |> cl.build!
		bet_program = cl.Program(context, source=CTPF_BET_c) |> cl.build!
		vav_program = cl.Program(context, source=CTPF_VAV_c) |> cl.build!
		gimel_program = cl.Program(context, source=CTPF_GIMEL_c) |> cl.build!
		zayin_program = cl.Program(context, source=CTPF_ZAYIN_c) |> cl.build!
		dalet_program = cl.Program(context, source=CTPF_DALET_c) |> cl.build!
		het_program = cl.Program(context, source=CTPF_HET_c) |> cl.build!
		phi_program = cl.Program(context, source=CTPF_PHI_c) |> cl.build!
		phi_norm_program = cl.Program(context, source=CTPF_PHI_NORM_c) |> cl.build!
		xi_program = cl.Program(context, source=CTPF_XI_c) |> cl.build!
		xi_norm_program = cl.Program(context, source=CTPF_XI_NORM_c) |> cl.build!

		alef_kernel = cl.Kernel(alef_program, "update_alef")
		he_kernel = cl.Kernel(he_program, "update_he")
		bet_kernel = cl.Kernel(bet_program, "update_bet")
		vav_kernel = cl.Kernel(vav_program, "update_vav")
		gimel_kernel = cl.Kernel(gimel_program, "update_gimel")
		zayin_kernel = cl.Kernel(zayin_program, "update_zayin")
		dalet_kernel = cl.Kernel(dalet_program, "update_dalet")
		het_kernel = cl.Kernel(het_program, "update_het")
		phi_kernel = cl.Kernel(phi_program, "update_phi")
		phi_norm_kernel = cl.Kernel(phi_norm_program, "normalize_phi")
		xi_kernel = cl.Kernel(xi_program, "update_xi")
		xi_norm_kernel = cl.Kernel(xi_norm_program, "normalize_xi")

		model = new(K, M, V, U, N, C, R, copy(corp), topics, scores, libs, drecs, urecs, a, b, c, d, e, f, g, h, alef, he, bet, vav, gimel, gimel_old, zayin, dalet, het, phi, xi, elbo, device, context, queue, alef_kernel, he_kernel, bet_kernel, vav_kernel, gimel_kernel, zayin_kernel, dalet_kernel, het_kernel, phi_kernel, phi_norm_kernel, xi_kernel, xi_norm_kernel)
		return model
	end
end

function Elogpya(model::gpuCTPF, d::Int)
	"Compute E_q[log(P(ya))]."

	x = -dot(model.gimel[d] ./ (model.dalet .* model.vav), sum(model.he, dims=2))
	readers, ratings = model.corp[d].readers, model.corp[d].ratings
	for (u, (re, ra)) in enumerate(zip(readers, ratings)), i in 1:model.K
		binom = Binomial(ra, model.xi[d][i,u])
		x += (ra * model.xi[d][i,u] * (digamma(model.gimel[d][i]) - log(model.dalet[i]) + digamma(model.he[i,re]) - log(model.vav[i])) - sum([pdf(binom, y) * loggamma(y + 1) for y in 0:ra]))
	end
	return x
end

function Elogpyb(model::gpuCTPF, d::Int)
	"Compute E_q[log(P(yb))]."

	x = -dot(model.zayin[d] ./ (model.het .* model.vav), sum(model.he, dims=2))
	readers, ratings = model.corp[d].readers, model.corp[d].ratings
	for (u, (re, ra)) in enumerate(zip(readers, ratings)), i in 1:model.K
		binom = Binomial(ra, model.xi[d][model.K+i,u])
		x += (ra * model.xi[d][model.K+i,u] * (digamma(model.zayin[d][i]) - log(model.het[i]) + digamma(model.he[i,re]) - log(model.vav[i])) - sum([pdf(binom, y) * loggamma(y + 1) for y in 0:ra]))
	end
	return x
end

function Elogpz(model::gpuCTPF, d::Int)
	"Compute E_q[log(P(z))]."

	x = -dot(model.gimel[d] ./ (model.dalet .* model.bet), sum(model.alef, dims=2))
	terms, counts = model.corp[d].terms, model.corp[d].counts
	for (n, (j, c)) in enumerate(zip(terms, counts)), i in 1:model.K
		binom = Binomial(c, model.phi[d][i,n])
		x += (c * model.phi[d][i,n] * (digamma(model.gimel[d][i]) - log(model.dalet[i]) + digamma(model.alef[i,j]) - log(model.bet[i])) - sum([pdf(binom, z) * loggamma(z + 1) for z in 0:c]))
	end
	return x
end

function Elogpbeta(model::gpuCTPF)
	"Compute E_q[log(P(beta))]."

	x = model.V * model.K * (model.a * log(model.b) - loggamma(model.a))
	for j in 1:model.V, i in 1:model.K
		x += (model.a - 1) * (digamma(model.alef[i,j]) - log(model.bet[i])) - model.b * model.alef[i,j] / model.bet[i]
	end
	return x
end

function Elogptheta(model::gpuCTPF, d::Int)
	"Compute E_q[log(P(theta))]."

	x = model.K * (model.c * log(model.d) - loggamma(model.c))
	for i in 1:model.K
		x += (model.c - 1) * (digamma(model.gimel[d][i]) - log(model.dalet[i])) - model.d * model.gimel[d][i] / model.dalet[i]
	end
	return x
end

function Elogpeta(model::gpuCTPF)
	"Compute E_q[log(P(eta))]."

	x = model.U * model.K * (model.e * log(model.f) - loggamma(model.e))
	for u in 1:model.U, i in 1:model.K
		x += (model.e - 1) * (digamma(model.he[i,u]) - log(model.vav[i])) - model.f * model.he[i,u] / model.vav[i]
	end
	return x
end

function Elogpepsilon(model::gpuCTPF, d::Int)
	"Compute E_q[log(P(epsilon))]."

	x = model.K * (model.g * log(model.h) - loggamma(model.g))
	for i in 1:model.K
		x += (model.g - 1) * (digamma(model.zayin[d][i]) - log(model.het[i])) - model.h * model.zayin[d][i] / model.het[i]
	end
	return x
end

function Elogqy(model::gpuCTPF, d::Int)
	"Compute E_q[log(q(y))]."

	x = 0
	for (u, ra) in enumerate(model.corp[d].ratings)
		x -= entropy(Multinomial(ra, model.xi[d][:,u]))
	end
	return x
end

function Elogqz(model::gpuCTPF, d::Int)
	"Compute E_q[log(q(z))]."

	x = 0
	for (n, c) in enumerate(model.corp[d].counts)
		x -= entropy(Multinomial(c, model.phi[d][:,n]))
	end
	return x
end

function Elogqbeta(model::gpuCTPF)
	"Compute E_q[log(q(beta))]."

	x = 0
	for j in 1:model.V, i in 1:model.K
		x -= entropy(Gamma(model.alef[i,j], 1 / model.bet[i]))
	end
	return x
end

function Elogqtheta(model::gpuCTPF, d::Int)
	"Compute E_q[log(q(theta))]."

	x = 0
	for i in 1:model.K
		x -= entropy(Gamma(model.gimel[d][i], 1 / model.dalet[i]))
	end
	return x
end

function Elogqeta(model::gpuCTPF)
	"Compute E_q[log(q(eta))]."

	x = 0
	for u in 1:model.U, i in 1:model.K
		x -= entropy(Gamma(model.he[i,u], 1 / model.vav[i]))
	end
	return x
end

function Elogqepsilon(model::gpuCTPF, d::Int)
	"Compute E_q[log(q(epsilon))]."

	x = 0
	for i in 1:model.K
		x -= entropy(Gamma(model.zayin[d][i], 1 / model.het[i]))
	end
	return x
end

function update_elbo!(model::gpuCTPF)
	"Update the evidence lower bound."

	model.elbo = Elogpbeta(model) + Elogpeta(model) - Elogqbeta(model) - Elogqeta(model)
	for d in 1:model.M
		model.elbo += Elogpya(model, d) + Elogpyb(model, d) + Elogpz(model, d) + Elogptheta(model, d) + Elogpepsilon(model, d) - Elogqy(model, d) - Elogqz(model, d) - Elogqtheta(model, d) - Elogqepsilon(model, d)
	end

	return model.elbo
end

const CTPF_ALEF_c =
"""
kernel void
update_alef(long K,
			float a,
			const global long *J_cumsum,
			const global long *counts,
			const global long *terms_sortperm,
			const global float *phi,
			global float *alef)

			{
			long i = get_global_id(0);
			long j = get_global_id(1);

			float acc = 0.0f;

			for (long w=J_cumsum[j]; w<J_cumsum[j+1]; w++)
				acc += counts[terms_sortperm[w]] * phi[K * terms_sortperm[w] + i];

			alef[K * j + i] = a + acc;
			}
			"""

function update_alef!(model::gpuCTPF)
	"Update alef."
	"Analytic."

	model.queue(model.alef_kernel, (model.K, model.V), nothing, model.K, model.a, model.J_cumsum_buffer, model.counts_buffer, model.terms_sortperm_buffer, model.phi_buffer, model.alef_buffer)
end

const CTPF_BET_c =
"""
kernel void
update_bet(	long K,
			long M,
			float b,
			const global float *alef,
			const global float *gimel,
			const global float *dalet,
			global float *bet)

			{
			long i = get_global_id(0);

			float acc = 0.0f;

			for (long d=0; d<M; d++)
				acc += gimel[K * d + i];

			bet[i] = b + acc / dalet[i];
			}
			"""

function update_bet!(model::gpuCTPF)
	"Update bet."
	"Analytic."

	model.queue(model.bet_kernel, model.K, nothing, model.K, model.M, model.b, model.alef_buffer, model.gimel_buffer, model.dalet_buffer, model.bet_buffer)
end

const CTPF_GIMEL_c =
"""
kernel void
update_gimel(	long K,
				float c,
				const global long *N_cumsum,
				const global long *R_cumsum,
				const global long *counts,
				const global long *ratings,
				const global float *phi,
				const global float *xi,
				global float *gimel)

				{
				long i = get_global_id(0);
				long d = get_global_id(1);

				float acc_phi = 0.0f;
				float acc_xi = 0.0f;

				for (long n=N_cumsum[d]; n<N_cumsum[d+1]; n++)
					acc_phi += phi[K * n + i] * counts[n];

				for (long r=R_cumsum[d]; r<R_cumsum[d+1]; r++)
					acc_xi += xi[2 * K * r + i] * ratings[r];

				gimel[K * d + i] = c + acc_phi + acc_xi;
				}
				"""

function update_gimel!(model::gpuCTPF)
	"Update gimel."
	"Analytic."

	model.gimel_old = model.gimel

	model.queue(model.gimel_kernel, (model.K, model.M), nothing, model.K, model.c, model.N_cumsum_buffer, model.R_cumsum_buffer, model.counts_buffer, model.ratings_buffer, model.phi_buffer, model.xi_buffer, model.gimel_buffer)
	@host model.gimel_buffer
end

const CTPF_DALET_c =
"""
kernel void
update_dalet(	long K,
				long V,
				long U,
				float d,
				const global float *alef,
				const global float *bet,
				const global float *he,
				const global float *vav,
				global float *dalet)

				{
				long i = get_global_id(0);

				float acc_alef = 0.0f;
				float acc_he = 0.0f;

				for (long j=0; j<V; j++)
					acc_alef += alef[K * j + i];

				for (long u=0; u<U; u++)
					acc_he += he[K * u + i];

				dalet[i] = d + acc_alef / bet[i] + acc_he / vav[i];
				}
				"""

function update_dalet!(model::gpuCTPF)
	"Update dalet."
	"Analytic."

	model.queue(model.dalet_kernel, model.K, nothing, model.K, model.V, model.U, model.d, model.alef_buffer, model.bet_buffer, model.he_buffer, model.vav_buffer, model.dalet_buffer)
end

const CTPF_HE_c =
"""
kernel void
update_he(	long K,
			float e,
			const global long *Y_cumsum,
			const global long *ratings,
			const global long *readers_sortperm,
			const global float *xi,
			global float *he)

			{
			long i = get_global_id(0);
			long u = get_global_id(1);

			float acc = 0.0f;

			for (long r=Y_cumsum[u]; r<Y_cumsum[u+1]; r++)
				acc += ratings[readers_sortperm[r]] * (xi[2 * K * readers_sortperm[r] + i] + xi[K * (2 * readers_sortperm[r] + 1) + i]);

			he[K * u + i] = e + acc;
			}
			"""

function update_he!(model::gpuCTPF)
	"Update he."
	"Analytic."

	(model.U > 0) && model.queue(model.he_kernel, (model.K, model.U), nothing, model.K, model.e, model.Y_cumsum_buffer, model.ratings_buffer, model.readers_sortperm_buffer, model.xi_buffer, model.he_buffer)
end

const CTPF_VAV_c =
"""
kernel void
update_vav(	long K,
			long M,
			float f,
			const global float *gimel,
			const global float *dalet,
			const global float *zayin,
			const global float *het,
			global float *vav)

			{
			long i = get_global_id(0);

			float acc_gimel = 0.0f;
			float acc_zayin = 0.0f;

			for (long d=0; d<M; d++)
			{
				acc_gimel += gimel[K * d + i];
				acc_zayin += zayin[K * d + i];
			}

			vav[i] = f + acc_gimel / dalet[i] + acc_zayin / het[i];
			}
			"""

function update_vav!(model::gpuCTPF)
	"Update vav."
	"Analytic."

	model.queue(model.vav_kernel, model.K, nothing, model.K, model.M, model.f, model.gimel_buffer, model.dalet_buffer, model.zayin_buffer, model.het_buffer, model.vav_buffer)
end

const CTPF_ZAYIN_c =
"""
kernel void
update_zayin(	long K,
				float g,
				const global long *R_cumsum,
				const global long *ratings,
				const global float *xi,
				global float *zayin)

				{
				long i = get_global_id(0);
				long d = get_global_id(1);

				float acc = 0.0f;

				for (long r=R_cumsum[d]; r<R_cumsum[d+1]; r++)
					acc += xi[K * (2 * r + 1) + i] * ratings[r];

				zayin[K * d + i] = g + acc;
				}
				"""

function update_zayin!(model::gpuCTPF)
	"Update zayin."
	"Analytic."

	model.queue(model.zayin_kernel, (model.K, model.M), nothing, model.K, model.g, model.R_cumsum_buffer, model.ratings_buffer, model.xi_buffer, model.zayin_buffer)
end

const CTPF_HET_c =
"""
kernel void
update_het(	long K,
			long U,
			float h,
			const global float *he,
			const global float *vav,
			global float *het)

			{
			long i = get_global_id(0);

			float acc = 0.0f;

			for (long u=0; u<U; u++)
				acc += he[K * u + i];

			het[i] = h + acc / vav[i];
			}
			"""

function update_het!(model::gpuCTPF)
	"Update het."
	"Analytic."

	model.queue(model.het_kernel, model.K, nothing, model.K, model.U, model.h, model.he_buffer, model.vav_buffer, model.het_buffer)
end

const CTPF_PHI_c =
"""
$(DIGAMMA_c)

kernel void
update_phi(	long K,
			const global long *N_cumsum,
			const global long *terms,
			const global float *alef,
			const global float *bet,
			const global float *gimel,
			const global float *dalet,
			global float *phi)

			{

			long i = get_global_id(0);
			long d = get_global_id(1);

			float gdb = digamma(gimel[K * d + i]) - log(dalet[i]) - log(bet[i]);

			for (long n=N_cumsum[d]; n<N_cumsum[d+1]; n++)
				phi[K * n + i] = gdb + digamma(alef[K * terms[n] + i]);
			}
			"""

const CTPF_PHI_NORM_c =
"""
kernel void
normalize_phi(	long K,
				global float *phi)

				{
				long dn = get_global_id(0);

				float maxval = 0.0f;

				for (long i=0; i<K; i++)
					if (phi[K * dn + i] > maxval)
						maxval = phi[K * dn + i];

				float normalizer = 0.0f;

				for (long i=0; i<K; i++)
				{
					phi[K * dn + i] -= maxval;
					normalizer += exp(phi[K * dn + i]);
				}

				for (long i=0; i<K; i++)
					phi[K * dn + i] = exp(phi[K * dn + i]) / normalizer;
				}
				"""

function update_phi!(model::gpuCTPF)
	"Update phi."
	"Analytic."

	model.queue(model.phi_kernel, (model.K, model.M), nothing, model.K, model.N_cumsum_buffer, model.terms_buffer, model.alef_buffer, model.bet_buffer, model.gimel_buffer, model.dalet_buffer, model.phi_buffer)
	model.queue(model.phi_norm_kernel, sum(model.N), nothing, model.K, model.phi_buffer)
end

const CTPF_XI_c =
"""
$(DIGAMMA_c)

kernel void
update_xi(	long K,
			const global long *R_cumsum,
			const global long *readers,
			const global float *bet,
			const global float *gimel,
			const global float *dalet,
			const global float *he,
			const global float *vav,
			const global float *zayin,
			const global float *het,
			global float *xi)

			{
			long i = get_global_id(0);
			long d = get_global_id(1);

			float gdv = digamma(gimel[K * d + i]) - log(dalet[i]) - log(bet[i]);
			float zhv = digamma(zayin[K * d + i]) - log(het[i]) - log(vav[i]);

			for (long r=R_cumsum[d]; r<R_cumsum[d+1]; r++)
			{
				xi[2 * K * r + i] = gdv + digamma(he[K * readers[r] + i]);
				xi[K * (2 * r + 1) + i] = zhv + digamma(he[K * readers[r] + i]);
			}
			}
			"""

const CTPF_XI_NORM_c =
"""
kernel void
normalize_xi(	long K,
				global float *xi)

				{
				long dr = get_global_id(0);

				float maxval = 0.0f;

				for (long i=0; i<2*K; i++)
					if (xi[2 * K * dr + i] > maxval)
						maxval = xi[2 * K * dr + i];

				float normalizer = 0.0f;

				for (long i=0; i<2*K; i++)
				{
					xi[2 * K * dr + i] -= maxval;
					normalizer += exp(xi[2 * K * dr + i]);
				}

				for (long i=0; i<2*K; i++)
					xi[2 * K * dr + i] = exp(xi[2 * K * dr + i]) / normalizer;
				}
				"""

function update_xi!(model::gpuCTPF)
	"Update xi."
	"Analytic."

	if sum(model.R) > 0
		model.queue(model.xi_kernel, (model.K, model.M), nothing, model.K, model.R_cumsum_buffer, model.readers_buffer, model.bet_buffer, model.gimel_buffer, model.dalet_buffer, model.he_buffer, model.vav_buffer, model.zayin_buffer, model.het_buffer, model.xi_buffer)
		model.queue(model.xi_norm_kernel, sum(model.R), nothing, model.K, model.xi_buffer)
	end
end

function train!(model::gpuCTPF; iter::Integer=150, tol::Real=1.0, viter::Integer=10, vtol::Real=1/model.K^2, checkelbo::Real=1, printelbo::Bool=true)
	"Coordinate ascent optimization procedure for GPU accelerated collaborative topic Poisson factorization variational Bayes algorithm."

	check_model(model)
	all([tol, vtol] .>= 0)												|| throw(ArgumentError("Tolerance parameters must be nonnegative."))
	all([iter, viter] .>= 0)											|| throw(ArgumentError("Iteration parameters must be nonnegative."))
	(isa(checkelbo, Integer) & (checkelbo > 0)) | (checkelbo == Inf)	|| throw(ArgumentError("checkelbo parameter must be a positive integer or Inf."))
	all([isempty(doc) for doc in model.corp]) ? (iter = 0) : update_buffer!(model)
	(checkelbo <= iter) && update_elbo!(model)

	for k in 1:iter
		for _ in 1:viter
			update_xi!(model)
			update_phi!(model)
			update_zayin!(model)
			update_gimel!(model)

			if sum([norm(model.gimel[d] - model.gimel_old[d]) for d in 1:model.M]) < model.M * vtol
				break
			end
		end
		update_he!(model)
		update_alef!(model)
		update_dalet!(model)
		update_het!(model)
		update_bet!(model)
		update_vav!(model)

		if check_elbo!(model, checkelbo, printelbo, k, tol)
			break
		end
	end

	(iter > 0) && update_host!(model)
	Ebeta = model.alef ./ model.bet
	model.topics = [reverse(sortperm(vec(Ebeta[i,:]))) for i in 1:model.K]

	Eeta = model.he ./ model.vav
	for d in 1:model.M
		Etheta = model.gimel[d] ./ model.dalet
		Eepsilon = model.zayin[d] ./ model.het
		model.scores[d,:] = sum(Eeta .* (Etheta + Eepsilon), dims=1)
	end

	model.urecs = VectorList{Int}(undef, model.U)
	for u in 1:model.U
		ur = trues(model.M)
    	ur[model.libs[u]] .= false
		model.urecs[u] = findall(ur)[reverse(sortperm(model.scores[ur,u]))]
	end

	model.drecs = VectorList{Int}(undef, model.M)
	for d in 1:model.M
    	nr = trues(model.U)
    	nr[model.corp[d].readers] .= false
    	model.drecs[d] = findall(nr)[reverse(sortperm(model.scores[d,nr]))]
	end
	nothing
end


text:
function (meshtype::Type{T})(c::Pyramid) where T <: AbstractMesh
    T(vertices = decompose(vertextype(T), c), faces = decompose(facetype(T), c))
end

"""
Standard way of creating a mesh from a primitive.
Just walk through all attributes of the mesh and try to decompose it.
If there are attributes missing, just hope it will get managed by the mesh constructor.
(E.g. normal calculation, which needs to have vertices and faces present)
"""
function (meshtype::Type{T})(c::GeometryPrimitive, args...) where T <: AbstractMesh
    attribs = attributes(T)
    newattribs = Dict{Symbol, Any}()
    for (fieldname, typ) in attribs
        if isdecomposable(eltype(typ), c)
            newattribs[fieldname] = decompose(eltype(typ), c, args...)
        end
    end
    return T(newattribs)
end

function to_pointn(::Type{Point{N, T}}, p::StaticVector{N2}, d = T(0)) where {T, N, N2}
    return Point{N, T}(ntuple(i-> i <= N2 ? T(p[i]) : T(d), N))
end

function (::Type{T})(c::Circle, n = 32) where T <: AbstractMesh
    newattribs = Dict{Symbol, Any}()
    VT = vertextype(T)
    verts = decompose(VT, c, n)
    N = length(verts)
    push!(verts, to_pointn(VT, origin(c))) # middle point
    middle_idx = length(verts)
    FT = facetype(T)
    faces = map(1:N) do i
        FT(i, middle_idx, i + 1)
    end
    return T(vertices = verts, faces = faces)
end

function (meshtype::Type{T})(
        c::Union{HyperCube{3,T}, HyperRectangle{3,HT}}
    ) where {T <: HMesh, HT}
    xdir = Vec{3, HT}(1, 0, 0)
    ydir = Vec{3, HT}(0, 1, 0)
    zdir = Vec{3, HT}(0, 0, 1)
    o0 = zero(Vec{3, HT})
    quads = [
        Quad(zdir, xdir, ydir), # Top
        Quad(o0,   ydir, xdir), # Bottom
        Quad(xdir, ydir, zdir), # Right
        Quad(o0,   zdir, ydir), # Left
        Quad(o0,   xdir, zdir), # Back
        Quad(ydir, zdir, xdir) #Front
    ]
    mesh = merge(map(meshtype, quads))
    v = vertices(mesh)
    w = widths(c)
    o = origin(c)
    map!(v, v) do v
        (v .* w) + o
    end
    return mesh
end


text:
##### Energy tendencies

#####
##### First order fluxes
#####

function flux(::Advect{Energy}, m, state, aux, t, ts, direction)
    return (state.ρu / state.ρ) * state.ρe
end

function flux(::Pressure{Energy}, m, state, aux, t, ts, direction)
    return state.ρu / state.ρ * air_pressure(ts)
end

#####
##### First order fluxes
#####

struct ViscousFlux{PV <: Energy} <: TendencyDef{Flux{SecondOrder}, PV} end
function flux(::ViscousFlux{Energy}, m, state, aux, t, ts, diffusive, hyperdiff)
    ν, D_t, τ = turbulence_tensors(m, state, diffusive, aux, t)
    return τ * state.ρu
end

struct DiffEnthalpyFlux{PV <: Energy} <: TendencyDef{Flux{SecondOrder}, PV} end
function flux(
    ::DiffEnthalpyFlux{Energy},
    m,
    state,
    aux,
    t,
    ts,
    diffusive,
    hyperdiff,
)
    ν, D_t, τ = turbulence_tensors(m, state, diffusive, aux, t)
    d_h_tot = -D_t .* diffusive.∇h_tot
    return d_h_tot * state.ρ
end

#####
##### Sources
#####

function source(s::Subsidence{Energy}, m, args)
    @unpack state, aux, diffusive = args
    z = altitude(m, aux)
    w_sub = subsidence_velocity(s, z)
    k̂ = vertical_unit_vector(m, aux)
    return -state.ρ * w_sub * dot(k̂, diffusive.∇h_tot)
end

function source(s::RemovePrecipitation{Energy}, m, args)
    @unpack state = args
    @unpack ts = args.precomputed
    if has_condensate(ts)
        nt = remove_precipitation_sources(s, m, args)
        return nt.S_ρ_e
    else
        FT = eltype(state)
        return FT(0)
    end
end

function source(s::WarmRain_1M{Energy}, m, args)
    nt = warm_rain_sources(m, args)
    return nt.S_ρ_e
end

function source(s::RainSnow_1M{Energy}, m, args)
    nt = rain_snow_sources(m, args)
    return nt.S_ρ_e
end


text:
# SPDX-License-Identifier: MIT

const PdfIndex = Union{Int,Nothing}
const Label = Union{AbstractString,Nothing}

mutable struct State{T<:Semifield}
    id::Int
    initweight::T
    finalweight::T
    pdfindex::PdfIndex
    label::Label
end

isinit(s::State{T}) where T = s.initweight ≠ zero(T)
isfinal(s::State{T}) where T = s.finalweight ≠ zero(T)
islabeled(s::State) = ! isnothing(s.label)
isemitting(s::State)  = ! isnothing(s.pdfindex)
setinit!(s::State{T}, weight::T = one(T)) where T = s.initweight = weight
setfinal!(s::State{T}, weight::T = one(T)) where T = s.finalweight = weight

mutable struct Link{T<:Semifield}
    dest::State
    weight::T
end

"""
    struct FSM{T<:Semifield}
        states # vector of states
        links # Dict state -> vector of links
    end

Probabilistic finite state machine.
"""
struct FSM{T<:Semifield}
    states::Vector{State{T}}
    links::Dict{State, Vector{Link{T}}}
end
FSM{T}() where T = FSM{T}(State{T}[], Dict{State, Vector{Link{T}}}())
FSM() = FSM{LogSemifield{Float64}}()
fsmtype(::FSM{T}) where T<:Semifield = T

states(fsm::FSM) = fsm.states
links(fsm::FSM{T}, state::State{T}) where T = get(fsm.links, state, Link{T}[])

function addstate!(fsm::FSM{T}; initweight = zero(T), finalweight = zero(T),
                   pdfindex = nothing, label = nothing) where T
    s = State(length(fsm.states)+1, initweight, finalweight, pdfindex, label)
    push!(fsm.states, s)
    s
end

function link!(fsm::FSM{T}, src::State{T}, dest::State{T}, weight::T = one(T)) where T
    list = get(fsm.links, src, Link{T}[])
    link = Link{T}(dest, weight)
    push!(list, link)
    fsm.links[src] = list
    link
end

function Base.show(io, ::MIME"image/svg+xml", fsm::FSM)
    dotpath, dotfile = mktemp()
    svgpath, svgfile = mktemp()

    write(dotfile, "Digraph {\n")
    write(dotfile, "rankdir=LR;")

    for s in states(fsm)
        name = "$(s.id)"
        label = islabeled(s) ? "$(s.label)" : "ϵ"
        label *= isemitting(s) ? ":$(s.pdfindex)" : ":ϵ"
        if s.initweight ≠ zero(typeof(s.initweight))
            weight = round(convert(Float64, s.initweight), digits = 3)
            label *= "/$(weight)"
        end
        if s.finalweight ≠ zero(typeof(s.finalweight))
            weight = round(convert(Float64, s.finalweight), digits = 3)
            label *= "/$(weight)"
        end
        attrs = "shape=" * (isfinal(s) ? "doublecircle" : "circle")
        attrs *= " penwidth=" * (isinit(s) ? "2" : "1")
        attrs *= " label=\"" * label * "\""
        attrs *= " style=filled fillcolor=" * (isemitting(s) ? "lightblue" : "none")
        write(dotfile, "$name [ $attrs ];\n")
    end

    for src in states(fsm)
        for link in links(fsm, src)
            weight = round(convert(Float64, link.weight), digits = 3)
            srcname = "$(src.id)"
            destname = "$(link.dest.id)"
            write(dotfile, "$srcname -> $destname [ label=\"$(weight)\" ];\n")
        end
    end
    write(dotfile, "}\n")
    close(dotfile)
    run(`dot -Tsvg $(dotpath) -o $(svgpath)`)

    xml = read(svgfile, String)
    write(io, xml)

    close(svgfile)

    rm(dotpath)
    rm(svgpath)
end

#======================================================================
FSM operations
======================================================================#

"""
    union(fsm1, fsm2, ...)

Merge all the fsms into a single one.
"""
function Base.union(fsm1::FSM{T}, fsm2::FSM{T}) where T
    allstates = union(states(fsm1), states(fsm2))
    newfsm = FSM{T}()

    smap = Dict()
    for state in allstates
        smap[state] = addstate!(newfsm, label = state.label,
                                pdfindex = state.pdfindex,
                                initweight = state.initweight,
                                finalweight = state.finalweight)
    end

    for src in states(fsm1)
        for link in links(fsm1, src)
            link!(newfsm, smap[src], smap[link.dest], link.weight)
        end
    end

    for src in states(fsm2)
        for link in links(fsm2, src)
            link!(newfsm, smap[src], smap[link.dest], link.weight)
        end
    end

    newfsm
end
Base.union(f::FSM{T}, o::FSM{T}...) where T = foldl(union, o, init = f)

"""
    renormalize!(fsm)

Ensure the that all the weights of all the outgoing arcs leaving a
state sum up to 1.
"""
function renormalize!(fsm::FSM{T}) where T
    total = zero(T)
    for s in filter(isinit, states(fsm)) total += s.initweight end
    for s in filter(isinit, states(fsm)) s.initweight /= total end

    for src in states(fsm)
        total = src.finalweight
        for link in links(fsm, src) total += link.weight end
        for link in links(fsm, src) link.weight /= total end
        src.finalweight /= total
    end

    fsm
end

"""
    replace(fsm, subfsms, delim = "!")

Replace the state in `fsm` wiht a sub-fsm from `subfsms`. The pairing
is done with the last tone of `label` of the state, i.e. the state
with label `a!b!c` will be replaced by `subfsms[c]`. States that don't
have matching labels are left untouched.
"""
function Base.replace(fsm::FSM{T}, subfsms::Dict, delim = "!") where T
    newfsm = FSM{T}()

    matchlabel = label -> isnothing(label) ? nothing : split(label, delim)[end]

    smap_in = Dict()
    smap_out = Dict()
    for s in states(fsm)
        if matchlabel(s.label) in keys(subfsms)
            smap = Dict()
            for cs in states(subfsms[matchlabel(s.label)])
                label = "$(s.label)$(delim)$(cs.label)"
                ns = addstate!(newfsm, pdfindex = cs.pdfindex, label = label,
                               initweight = s.initweight * cs.initweight,
                               finalweight = s.finalweight * cs.finalweight)
                smap[cs] = ns

                if isinit(cs) smap_in[s] = ns end
                if isfinal(cs) smap_out[s] = ns end
            end

            for cs in states(subfsms[matchlabel(s.label)])
                for link in links(subfsms[matchlabel(s.label)], cs)
                    link!(newfsm, smap[cs], smap[link.dest], link.weight)
                end
            end

        else
            ns = addstate!(newfsm, pdfindex = s.pdfindex, label = s.label,
                           initweight = s.initweight, finalweight = s.finalweight)
            smap_in[s] = ns
            smap_out[s] = ns
        end
    end

    for osrc in states(fsm)
        weight = one(T)
        if matchlabel(osrc.label) in keys(subfsms)
            finals = filter(isfinal, states(subfsms[matchlabel(osrc.label)]))
            weight = sum(map(s->s.finalweight, finals))
        end
        for link in links(fsm, osrc)
            src = smap_out[osrc]
            dest = smap_in[link.dest]
            link!(newfsm, src, dest, link.weight * weight)
        end
    end

    newfsm
end

function _unique_labels(statelist, T, step; init = true)
    labels = Dict()
    for (s, w) in statelist
        lstates, iw, fw, tw = get(labels, (s.label, step), (Set(), zero(T), zero(T), zero(T)))
        push!(lstates, s)
        labels[(s.label, step)] = (lstates, iw+s.initweight, fw+s.finalweight, tw+w)
    end

    # Inverse the map so that the set of states is the key.
    retval = Dict()
    for (key, value) in labels
        retval[value[1]] = (key[1], value[2], value[3], value[4], init)
    end
    retval
end

"""
    determinize(fsm)

Determinize the FSM w.r.t. the state labels.
"""
function determinize(fsm::FSM{T}) where T
    newfsm = FSM{T}()
    smap = Dict()
    newlinks = Dict()

    initstates = [(s, zero(T)) for s in filter(isinit, collect(states(fsm)))]
    queue = _unique_labels(initstates, T, 0, init = true)
    while ! isempty(queue)
        key, value = pop!(queue)
        lstates = key
        label, iw, fw, tw, init = value
        step = 0

        if key ∉ keys(smap)
            if init
                s = addstate!(newfsm, label = label, initweight = iw, finalweight = fw)
            else
                s = addstate!(newfsm, label = label, finalweight = fw)
            end
            smap[key] = s
        end

        nextstates = []
        for ls in lstates
            for link in links(fsm, ls)
                push!(nextstates, (link.dest, link.weight))
            end
        end

        nextlabels = _unique_labels(nextstates, T, step+1, init = false)
        for (key2, value2) in nextlabels
            w = get(newlinks, (key,key2), zero(T))
            newlinks[(key,key2)] = w+value2[end]
        end
        queue = merge(queue, nextlabels)
    end

    for (key, value) in newlinks
        src = smap[key[1]]
        dest = smap[key[2]]
        weight = value
        link!(newfsm, src, dest, weight)
    end

    newfsm
end

"""
    transpose(fsm)

Reverse the direction of the arcs.
"""
function Base.transpose(fsm::FSM{T}) where T
    newfsm = FSM{T}()
    smap = Dict()
    for s in states(fsm)
        ns = addstate!(newfsm, label = s.label, initweight = s.finalweight,
                       finalweight = s.initweight, pdfindex = s.pdfindex)
        smap[s] = ns
    end

    for src in states(fsm)
        for link in links(fsm, src)
            link!(newfsm, smap[link.dest], smap[src], link.weight)
        end
    end

    newfsm
end

"""
    minimize(fsm)

Return a minimal equivalent fsm.
"""
minimize(fsm::FSM{T}) where T = (transpose ∘ determinize ∘ transpose ∘ determinize)(fsm)

"""
	eps_closure!(fsm, state, closure; [weight=1], [visited=[]])

Find eps closure from `state` in `fsm`.
"""
function eps_closure!(
        fsm::FSM{T}, state::State, closure::Vector;
        weight::T=one(T), visited::Vector{State} = State[]
) where T <: Semifield

    if state in visited
        return closure
    end
	push!(visited, state)

    for l in links(fsm, state)
        if isemitting(l.dest)
            push!(closure, (l.dest, l.weight * weight))
        else
            eps_closure!(fsm, l.dest, closure; weight=l.weight * weight, visited=visited)
        end
    end
    return closure
end

"""
	remove_eps(fsm)

Removes non-emitting states from `fsm`.
"""
function remove_eps(fsm::FSM{T}) where T <: Semifield
    nfsm = FSM{T}()
    smap = Dict{State, State}()
    eps_states = []
    eps_closures = Dict{State, Vector}()
    for s in states(fsm)
        if isemitting(s)
            smap[s] = addstate!(nfsm;
                initweight=s.initweight, finalweight=s.finalweight,
                pdfindex=s.pdfindex, label=s.label)
        else
            push!(eps_states, s)
        end
    end

    for eps in eps_states
        closure = eps_closure!(fsm, eps, [])
        eps_closures[eps] = unique!(closure)
    end

    for s in states(fsm)
        for l in links(fsm, s)
            if isemitting(s) && isemitting(l.dest)
                link!(nfsm, smap[s], smap[l.dest], l.weight)
            elseif isemitting(s)
                for (ns, w) in eps_closures[l.dest]
                    link!(nfsm, smap[s], smap[ns], l.weight * w)
                end
            end
        end
    end
    return nfsm
end

max_stars_repo_name: MartinKocour/MarkovModels

max_issues_repo_name: MartinKocour/MarkovModels

max_forks_repo_name: MartinKocour/MarkovModels


text:
# AUTO GENERATED FILE - DO NOT EDIT

export 'feffery'_antdskeleton

"""
    'feffery'_antdskeleton(;kwargs...)
    'feffery'_antdskeleton(children::Any;kwargs...)
    'feffery'_antdskeleton(children_maker::Function;kwargs...)

An AntdSkeleton component.

Keyword arguments:
- `children` (a list of or a singular dash component, string or number; optional)
- `id` (String; optional)
- `active` (Bool; optional)
- `avatar` (optional): . avatar has the following type: Bool | lists containing elements 'active', 'shape', 'size'.
Those elements have the following types:
  - `active` (Bool; optional)
  - `shape` (a value equal to: 'circle', 'square'; optional)
  - `size` (Real | a value equal to: 'large', 'small', 'default'; optional)
- `className` (String; optional)
- `debug` (Bool; optional)
- `excludeProps` (Array of Strings; optional)
- `includeProps` (Array of Strings; optional)
- `listenPropsMode` (a value equal to: 'default', 'exclude', 'include'; optional)
- `loading` (Bool; optional)
- `loading_state` (optional): . loading_state has the following type: lists containing elements 'is_loading', 'prop_name', 'component_name'.
Those elements have the following types:
  - `is_loading` (Bool; optional): Determines if the component is loading or not
  - `prop_name` (String; optional): Holds which property is loading
  - `component_name` (String; optional): Holds the name of the component that is loading
- `paragraph` (optional): . paragraph has the following type: Bool | lists containing elements 'rows', 'width'.
Those elements have the following types:
  - `rows` (Real; optional)
  - `width` (Real | String | Array of Real | Strings; optional)
- `round` (Bool; optional)
- `style` (Dict; optional)
- `title` (optional): . title has the following type: Bool | lists containing elements 'width'.
Those elements have the following types:
  - `width` (Real | String; optional)
"""
function 'feffery'_antdskeleton(; kwargs...)
        available_props = Symbol[:children, :id, :active, :avatar, :className, :debug, :excludeProps, :includeProps, :listenPropsMode, :loading, :loading_state, :paragraph, :round, :style, :title]
        wild_props = Symbol[]
        return Component("'feffery'_antdskeleton", "AntdSkeleton", "feffery_antd_components", available_props, wild_props; kwargs...)
end

'feffery'_antdskeleton(children::Any; kwargs...) = 'feffery'_antdskeleton(;kwargs..., children = children)
'feffery'_antdskeleton(children_maker::Function; kwargs...) = 'feffery'_antdskeleton(children_maker(); kwargs...)

max_stars_repo_name: ReilYoung/feffery-antd-components

max_issues_repo_name: ReilYoung/feffery-antd-components

max_forks_repo_name: ReilYoung/feffery-antd-components


text:
using ArgParse
using EMISApproximateEquilibrium
const SR = EMISApproximateEquilibrium

function parse_commandline()
    s = ArgParseSettings()

    @add_arg_table! s begin
        "--build"
        help = "an flag to indicate is Simulation will be adding/building or removing devices to the system"
        action = :store_true
        "data_dir"
        help = "directory where the System data is stored"
        required = true
        "output_dir"
        help = "directory to store PowerSimulation Result"
        required = true
        "sample_dir"
        help = "directory where sample configuration infromation is stored"
        required = true
        "sample_no"
        help = "the sample or configuration number "
        required = true
        "build_no"
        help = "the build number "
        required = true
        "exp_name"
        help = "the experiment name "
        required = true
        "--load_growth"
        help = "mulitpler for load growth"
        arg_type = Float64
        default = 1.0
    end

    return parse_args(s)
end

function main()
    # parse command line args
    parsed_args = parse_commandline()
    args_dict = Dict()
    for (arg, val) in parsed_args
        args_dict[arg] = val
    end

    outdir = joinpath(
        args_dict["output_dir"],
        "sample_configuration_$(args_dict["exp_name"])_$(args_dict["build_no"])_$(args_dict["sample_no"])",
    )
    if !isdir(outdir)
        file_path = mkdir(outdir)
    end
    data_dir = args_dict["data_dir"]
    load_growth = args_dict["load_growth"]
    devices_added = []
    devices_deleted = []
    include(joinpath(
        args_dict["sample_dir"],
        "sample_configuration_$(args_dict["sample_no"]).jl",)
    )
    devices_added = Input.devices_added
    devices_deleted = Input.devices_deleted
    SR.create_continuous_simulation(
        outdir,
        364,
        devices_deleted,
        devices_added,
        "/tmp/scratch",
        data_dir,
        load_growth;
        simulation_name = "sample_$(args_dict["exp_name"])_$(args_dict["build_no"])_$(args_dict["sample_no"])",
    )
    training_dir =  joinpath("/home/sdalvi/work/scratch",
        "sample_configuration_$(args_dict["exp_name"])_$(args_dict["build_no"])_$(args_dict["sample_no"])/")
    if !isdir(training_dir)
        file_path = mkdir(training_dir)
    end
    src_dir = joinpath(outdir, "sample_$(args_dict["exp_name"])_$(args_dict["build_no"])_$(args_dict["sample_no"])/1/results/")
    run(`rsync -azp $src_dir $training_dir`)
end

main()


text:
# This file is part of the ValidatedNumerics.jl package; MIT licensed

## Promotion

## Promotion rules

# Avoid ambiguity with ForwardDiff:

promote_rule{T<:Real, N, R<:Real}(::Type{Interval{T}},
    ::Type{ForwardDiff.Dual{N,R}}) = ForwardDiff.Dual{N, Interval{promote_type(T,R)}}

promote_rule{T<:Real, S<:Real}(::Type{Interval{T}}, ::Type{Interval{S}}) =
    Interval{promote_type(T, S)}

promote_rule{T<:Real, S<:Real}(::Type{Interval{T}}, ::Type{S}) =
    Interval{promote_type(T, S)}

promote_rule{T<:Real}(::Type{BigFloat}, ::Type{Interval{T}}) =
    Interval{promote_type(T, BigFloat)}

## Conversion rules

# convert{T<:Real}(::Type{Interval}, x::T) = convert(Interval{Float64}, x)

doc"""`split_interval_string` deals with strings of the form
\"[3.5, 7.2]\""""

function split_interval_string(T, x::AbstractString)
    if !(contains(x, "["))  # string like "3.1"
        return @thin_round(T, parse(T, x))
    end

    m = match(r"\[(.*),(.*)\]", x)  # string like "[1, 2]"

    if m == nothing
        throw(ArgumentError("Unable to process string $x as interval"))
    end

    @round(T, parse(T, m.captures[1]), parse(T, m.captures[2]))
end

# Floating point intervals:

convert{T<:AbstractFloat}(::Type{Interval{T}}, x::AbstractString) =
    split_interval_string(T, x)

function convert{T<:AbstractFloat, S<:Real}(::Type{Interval{T}}, x::S)
    Interval{T}( T(x, RoundDown), T(x, RoundUp) )
    # the rounding up could be down as nextfloat of the rounded down one
end

function convert{T<:AbstractFloat}(::Type{Interval{T}}, x::Float64)
    II = convert(Interval{T}, rationalize(x))
    # This prevents that rationalize(x) returns a zero when x is very small
    if x != zero(x) && II == zero(Interval{T})
        II = Interval{T}(string(x))
    end
    II
end

function convert{T<:AbstractFloat}(::Type{Interval{T}}, x::Interval)
    Interval{T}( T(x.lo, RoundDown), T(x.hi, RoundUp) )
end

# Complex numbers:
convert{T<:AbstractFloat}(::Type{Interval{T}}, x::Complex{Bool}) = (x == im) ?
    one(T)*im : throw(ArgumentError("Complex{Bool} not equal to im"))

# Rational intervals
function convert(::Type{Interval{Rational{Int}}}, x::Irrational)
    a = float(convert(Interval{BigFloat}, x))
    convert(Interval{Rational{Int}}, a)
end

function convert(::Type{Interval{Rational{BigInt}}}, x::Irrational)
    a = convert(Interval{BigFloat}, x)
    convert(Interval{Rational{BigInt}}, a)
end

convert{T<:Integer, S<:Integer}(::Type{Interval{Rational{T}}}, x::S) =
    Interval(x*one(Rational{T}))

convert{T<:Integer, S<:Integer}(::Type{Interval{Rational{T}}}, x::Rational{S}) =
    Interval(x*one(Rational{T}))

convert{T<:Integer, S<:Float64}(::Type{Interval{Rational{T}}}, x::S) =
    Interval(rationalize(T, x))

convert{T<:Integer, S<:BigFloat}(::Type{Interval{Rational{T}}}, x::S) =
    Interval(rationalize(T, x))


text:
using Jack
using Test

@testset "Jack.jl" begin
    # Write your own tests here.
end


text:
#-------------------------------------------------------------------------------
# File: sat_encoding.jl
# Description: This file contains all the fuctions needed read sat results
# provided by minisat
# Date: December 10, 2019
# Author: Killian Fretaud, Rémi Garcia,
#         Boualem Lamraoui, Benoît Le Badezet, Benoit Loger
#-------------------------------------------------------------------------------

const g, p, w = 2,2,3

"""
    read_sat_results(file_name)

Read minisat results from "file_name" and print the corresponding SGP
solution in the REPL
"""
function read_sat_results(file_name::String)
    q = p*g
    nb_var = q*p*g*w
    results = Vector{Int}(undef,nb_var+1)
	open(file_name) do f
		lines = readlines(f)
		if lines[1] == "SAT" || lines[1] == "sat"
			lines = split(lines[2]," ")
			for i in 1:size(lines,1)
				results[i]=parse(Int,lines[i])
			end
        end
    end

    for week in 1:w
        println(" Week ", week)
        for group in 1:g
            print("Group ", group, " : ")
            for position in 1:p
                for player in 1:q
                    var = (week-1)*(q*p*g) + (group-1)*(q*p) + (position-1)*(q) + player
                    if results[var] > 0
                        print(Int(int_to_var(results[var])[4]) ," ")
                    end
                end
            end
            println()
        end
    end
end

function int_to_var(value::Int)
    q = p * g
    w_var = ceil(value/(q*p*g))
    g_var = ceil( (value-(w_var-1)*(q*p*g)) / (q*p) )
    p_var = ceil( ((value-(w_var-1)*(q*p*g))-(g_var-1)*(q*p)) / (q) )
    q_var = ((value-(w_var-1)*(q*p*g))-(g_var-1)*(q*p)) - (p_var-1)*q
    return w_var, g_var, p_var, q_var
end

read_sat_results("res.res")

max_stars_repo_name: remi-garcia/ProjetCSP

max_issues_repo_name: remi-garcia/ProjetCSP

max_forks_repo_name: remi-garcia/ProjetCSP


text:
using ImageView, FixedPointNumbers, Reactive, ColorTypes
using Test

@testset "contrast GUI" begin
    # test for the fix in #119
    imgbw = rand(N0f16, 100, 100)
    imgc = rand(RGB, 100, 100)
    for img in (imgbw, imgc)
        clim = ImageView.default_clim(img)
        imgsig = Signal(img)
        enabled, histsig, imgc = ImageView.prep_contrast(imgsig, clim)
        push!(enabled, true)
        yield()
        ret = ImageView.contrast_gui(enabled, histsig, clim)
        sleep(1.0)
        if isa(ret, Vector) #one gui dict per channel for color images
            for r in ret
                @test isa(r, Dict)
                Gtk.destroy(r["window"])
            end
        else
            @test isa(ret, Dict)
            Gtk.destroy(ret["window"])
        end
        # issue #168
        h = value(value(histsig)[1])
        fill!(h.weights, 0)
        push!(enabled, false)
        yield()
        h = value(value(histsig)[1])
        @test sum(h.weights) > 0
    end
end