Skip to content

Commit

Permalink
Add "format string" support for tensors
Browse files Browse the repository at this point in the history
This comes in 2 forms:
1. Add a version of the pretty function that takes a format string as its input instead of a precision value. Also add a new "showHeader" argument to the original pretty function.
    - This new version of pretty let's you specify the format string that must be used to display each element. It also adds some new tensor-specific format string "tokens" that are used to control how tensors are displayed (beyond the format of each element).
2. Add a formatValue procedure that takes a tensor as its first input. This makes it possible to control how tensors are displayed in format strings, in the exact same way as if you were using the new pretty(specifier) procedure.

The special, tensor-specific tokens added by this change are:
- "[:]": Display the tensor as if it were a nim "array".
         This makes it easy to use the representation of a
         tensor in your own code. No header is shown.
- "[]": Same as "[:]" but displays the tensor in a single
        line. No header is shown.
- "<>": Combined with the 2 above (i.e. "<>[:]" or "<>[]")
        adds a header with basic tensor info (type and
        shape). "<:>" can be used as a shortcut for "<>[:]"
        while "<>" on its own is equivalent to "<>[]".
        Can also be combined with "<>||" (see below).
- "||": "Pretty-print" the tensor without a header. This
        can also be combined with "<>" (i.e. "<>||") to
        explicitly enable the default mode, which is pretty
        printing with a header.
- 'j': Formats complex values as (A+Bj) like in mathematics.
       Ignored for non Complex tensors

Note that these are only used to control how the tensors themselves are displayed as a whole, and are removed before displaying the individual elements.
  • Loading branch information
AngelEzquerra committed Jun 17, 2024
1 parent c95e1c7 commit 78647a4
Show file tree
Hide file tree
Showing 3 changed files with 644 additions and 39 deletions.
166 changes: 152 additions & 14 deletions src/arraymancer/tensor/display.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,160 @@
# limitations under the License.

import ./private/p_display,
./data_structure,
typetraits
./data_structure
import std / typetraits

proc pretty*[T](t: Tensor[T], precision = -1): string =
## Pretty-print a Tensor with the option to set a custom `precision`
## for float values.
var desc = t.type.name & " of shape \"" & $t.shape & "\" on backend \"" & "Cpu" & "\""
if t.storage.isNil: # return useful message for uninit'd tensors instead of crashing
return "Uninitialized " & $desc
if t.size() == 0:
return desc & "\n [] (empty)"
elif t.rank == 1: # for rank 1 we want an indentation, because we have no `|`
return desc & "\n " & t.prettyImpl(precision = precision)
else:
return desc & "\n" & t.prettyImpl(precision = precision)
proc pretty*[T](t: Tensor[T], precision: int = -1, showHeader: static bool = true): string =
## Pretty-print a Tensor as a "table" with a given precision and optional header
##
## Pretty-print a Tensor with options to set a custom `precision` for float
## values and to show or hide a header describing the tensor type and shape.
##
## Inputs:
## - Input Tensor.
## - precision: The number of decimals printed (for float tensors),
## _including_ the decimal point.
## - showHeader: If true (the default) show a dscription header
## indicating the tensor type and shape.
## Result:
## - A string containing a "pretty-print" representation of the Tensor.
##
## Examples:
## ```nim
## let t = arange(-2.0, 4.0).reshape(2, 3)
##
## echo t.pretty()
## # Tensor[system.float] of shape "[2, 3]" on backend "Cpu"
## # |-2 -1 0|
## # |1 2 3|
##
## # Note that the precision counts
## echo t.pretty(2)
## # Tensor[system.float] of shape "[2, 3]" on backend "Cpu"
## # |-2.0 -1.0 0.0|
## # |1.0 2.0 3.0|
## ```
const specifier = if showHeader: "" else: "||"
t.prettyImpl(precision = precision, specifier = specifier)

proc pretty*[T](t: Tensor[T], specifier: static string = ""): string =
## Pretty-print a Tensor with the option to set a custom format `specifier`
##
## The "format specifier" is similar to those used in format strings, with
## the addition of a few, tensor specific modifiers (shown below).
##
## Inputs:
## - Input Tensor
## - specifier: A format specifier similar to those used in format strings,
## with the addition of a few, tensor specific modifiers which
## can be combined to achieve different results:
## - "[:]": Display the tensor as if it were a nim "array".
## This makes it easy to use the representation of a
## tensor in your own code. No header is shown.
## - "[]": Same as "[:]" but displays the tensor in a single
## line. No header is shown.
## - "<>": Combined with the 2 above (i.e. "<>[:]" or "<>[]")
## adds a header with basic tensor info (type and
## shape). "<:>" can be used as a shortcut for "<>[:]"
## while "<>" on its own is equivalent to "<>[]".
## Can also be combined with "<>||" (see below).
## - "||": "Pretty-print" the tensor _without_ a header. This
## can also be combined with "<>" (i.e. "<>||") to
## explicitly enable the default mode, which is pretty
## printing with a header.
## Notes:
## - Note that in addition to these we support all of the standard format
## specifiers, such as "f", "g", "+", etc (and including, in nim 2.2 and
## above, the 'j' specifier for complex tensors). For a list of supported
## format specifiers please check the documentation of nim's `strformat`
## module.
## - This version of this function does not have a `showHeader` argument
## because to disable the header you must add a "n" to the format
## specifier.
##
## Examples:
## ```nim
## let t_int = arange(-2, 22, 4).reshape(2, 3)
##
## # You can specify a format for the elements in the tensor
## # Note that the default is "pretty-printing" the tensor
## # _and_ showing a header describing its type and shape
## echo t_int.pretty("+05X")
## # Tensor[system.int] of shape "[2, 3]" on backend "Cpu"
## # |-0002 +0002 +0006|
## # |+000a +000e +0012|
##
## # The header can be disabled by using "||"
## echo t_int.pretty("+05X||")
## # |-0002 +0002 +0006|
## # |+000a +000e +0012|
##
## # Use the "[:]" format specifier to print the tensor as a
## # "multi-line array" _without_ a header
## echo t_int.pretty("[:]")
## # [[-2, 2, 6],
## # [10, 14, 18]]
##
## # Enable the header adding "<>" (i.e. "<>[:]") or the shorter "<:>"
## echo t_int.pretty("<:>")
## # Tensor[int]<2,3>:
## # [[-2, 2, 6],
## # [10, 14, 18]]
##
## # The "[]" specifier is similar to "[:]" but prints on a single line
## echo t_int.pretty("[]")
## # Tensor[float]<2,3>:[[-2.00, -1.00, +0.00], [+1.00, +2.00, +3.00]]
##
## # You can also enable the header using "<>" or "<>[]"
## echo t_int.pretty("<>")
## # [[-2.00, -1.00, +0.00], [+1.00, +2.00, +3.00]]
##
## # You can combine "[]", "[:]", "<>" and "<:>" with a regular format spec:
## let t_float = arange(-2.0, 22.0, 4.0).reshape(2, 3)
##
## echo t_float.pretty("6.2f<:>")
## # Tensor[int]<2,3>:
## # [[ -2.00, 2.00, 6.00],
## # [ 10.00, 14.00, 18.00]]
## ```
t.prettyImpl(precision = -1, specifier = specifier)

proc `$`*[T](t: Tensor[T]): string =
## Pretty-print a tensor (when using ``echo`` for example)
t.pretty()

proc `$$`*[T](t: Tensor[T]): string =
## Print the "elements" of a tensor as a multi-line array
t.pretty(specifier = "<:>")

proc `$<>`*[T](t: Tensor[T]): string =
## Print the "elements" of a tensor as a single-line array
t.pretty(specifier = "<>")

proc formatValue*[T](result: var string, t: Tensor[T], specifier: static string) =
## Standard format implementation for `Tensor`. It makes little
## sense to call this directly, but it is required to exist
## by the `&` macro.
##
## For Tensors, we add some additional specifiers which can be combined to
## achieve different results:
## - "[:]": Display the tensor as if it were a nim "array".
## This makes it easy to use the representation of a
## tensor in your own code. No header is shown.
## - "[]": Same as "[:]" but displays the tensor in a single
## line. No header is shown.
## - "<>": Combined with the 2 above (i.e. "<>[:]" or "<>[]")
## adds a header with basic tensor info (type and
## shape). "<:>" can be used as a shortcut for "<>[:]"
## while "<>" on its own is equivalent to "<>[]".
## Can also be combined with "<>||" (see below).
## - "||": "Pretty-print" the tensor _without_ a header. This
## can also be combined with "<>" (i.e. "<>||") to
## explicitly enable the default mode, which is pretty
## printing with a header.
## - 'j': Formats complex values as (A+Bj) like in mathematics.
## Ignored for non Complex tensors
if specifier.len == 0:
result.add $t
else:
result.add t.pretty(specifier = specifier)
Loading

0 comments on commit 78647a4

Please sign in to comment.