{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n# Defining ArraySchema and AnnotatedArray's\nArraySchema are dictionary objects that describe the requirements of\nan array structure. AnnotatedArray are xarray.DataArray's that are expected\nto conform to a given ArraySchema - subclasses of xarray.DataArray.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import rmellipse as rme" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Defining AnnotatedArrays\n\nThere are 2 required fields in a schema. The first is a shape and the second\nis a dimension specification.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Array2x2(rme.AnnotatedArray):\n schema = rme.ArraySchema(\n shape=(2, 2),\n dims=('row', 'column'),\n )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dimension and shape specifiers can also be alphabetical letters (lower or\nupper case) that indicate arbitrary dimensionality. If two specifiers\nare the same letter, that indicates they are the same shape. For example,\na stack of square matrices might be defined as\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class ArrayMxNxN(rme.AnnotatedArray):\n schema = rme.ArraySchema(\n shape=('M', 'N', 'N'),\n dims=('page', 'row', 'column'),\n )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It's possible to define arbitary dimensionality or shape with ellipses.\nFor example, the following schema is an array of arbitary leading dimensions\nending in an NxN shape of rows and columns. If the shape is arbitary, then\nso are the dimension names in the same position. There can be only a single\narbitrary specifier. So (...,'N') is okay but (...,'N',...) is not.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class ArrayLeadingNxN(rme.AnnotatedArray):\n schema = rme.ArraySchema(\n shape=(..., 'N', 'N'),\n dims=(..., 'row', 'column'),\n )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data Types\n\nBy default, an ArraySchema has no dtype specifier (None) but one can be\nprovided to identity what kind of data is expected in the values of the\nDataArray. An array is considered valid to a schema it it can be casted\ninto that schema's dtype using the numpy.can_cast() function.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Array2x2Float(rme.AnnotatedArray):\n schema = rme.ArraySchema(shape=(2, 2), dims=('row', 'column'), dtype=float)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Coordinates\n\nYou can specify the expected coordinates of an AnnotatedArray\nby including a coords field with a CoordinateSchema.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class TimeDomainArray(rme.AnnotatedArray):\n schema = rme.ArraySchema(\n shape=('N',), dims=('time',), coords={'time': rme.CoordinateSchema(dtype=float)}\n )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If an array has a fixed dimension shape and coordinates, you can\ndefine the coordinate values by supplying values to the coordinate field.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class TimeDomainArray2x2(rme.AnnotatedArray):\n schema = rme.ArraySchema(\n shape=('N', 2, 2),\n dims=('time', 'row', 'col'),\n coords={\n 'time': rme.CoordinateSchema(dtype=float),\n 'row': rme.CoordinateSchema(dtype=int, values=[0, 1]),\n 'col': rme.CoordinateSchema(dtype=int, values=[0, 1]),\n },\n )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Units\n\nBy default the units field is None (which means no specified units, not\nunitless). If your array contains values of a physical unit, it can be\nsupplied as a string in the units field. This can be provided to both\nthe array values themselves, and to individual coordinates.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class TimeDomainVoltage(rme.AnnotatedArray):\n schema = rme.ArraySchema(\n shape=('N',),\n dims=('time',),\n units='V',\n coords={'time': rme.CoordinateSchema(dtype=float, units='s')},\n )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Metadata\n\nMetadata on xarray.DataArrays (and consequently AnnotatedArrays) are stored\nin the attrs attribute as a dictionary. If your data model is expecting\nspecific structures of metadata, those can be defined using Pydantic datamodels.\nFor example, you may be making a system that records DC measurments, and want\nto require that operator, temperature, and source current metadata\nfields are always present. It's strongly recommended that extra metadata\nfields be allowed as well.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from pydantic import BaseModel, ConfigDict\n\n\nclass DCMeasurementMetadata(BaseModel):\n # Enable extra fields\n model_config = ConfigDict(extra='allow')\n operator: str\n temperature_celcius: float\n source_current_amps: float\n\n\nclass TimeDomainVoltageWithMetadata(rme.AnnotatedArray):\n schema = rme.ArraySchema(\n shape=('N',),\n dims=('time',),\n units='V',\n coords={'time': rme.CoordinateSchema(dtype=float, units='s')},\n attrs=DCMeasurementMetadata,\n )" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.4" } }, "nbformat": 4, "nbformat_minor": 0 }