SkySpark by SkyFoundry

15. Axon Usage

Str Examples

Common functions for working with strings: capitalize, contains, decapitalize, endsWith, get, index, indexr, isEmpty, isTagName, lower, replace, size, startsWith,`toTagName`, trim, trimStart, trimEnd, upper.

123.toStr                      >>  "123"    // convert any object to string
"num=" + 3                     >>  "num=3"  // use '+' for concat
"hi world".isEmpty             >>  false
"hi world".size                >>  8
"hi world"[5]                  >>  114      // unicode char for 'w'
"hi world"[3..-2]              >>  "worl"   // get with range is slice
"root toot".index("oo")        >>  1
"root toot".indexr("oo")       >>  6
"hi world".contains("hi")      >>  true
"Abc".upper                    >>  "ABC"
"Abc".lower                    >>  "abc"
"a,b,c".split(",")             >>  ["a", "b", "c"]
"fooBar".capitalize            >>  "FooBar"
"FooBar".decapitalize          >>  "fooBar"
" xyz ".trim                   >>  "xyz"
"abcd".startsWith("ab")        >>  true
"abcd".endsWith("cd")          >>  true
"foo bar".isTagName            >>  false
"foo bar".toTagName            >>  "fooBar"
"root toot".replace("oo", "a") >> "rat tat"

Uri Examples

Common functions for working with URIs: uriBasename, uriDecode, uriEncode, uriExt, uriHost, uriIsDir, uriName, uriPath, uriPathStr, uriPort, uriScheme

`/a/b/file.txt`.uriName                   >>  "file.txt"
`/a/b/file.txt`.uriBasename               >>  "file"
`/a/b/file.txt`.uriExt                    >>  "txt"
`http://host:81/a/b/file.txt`.uriScheme   >>  "http"
`http://host:81/a/b/file.txt`.uriHost     >>  "host"
`http://host:81/a/b/file.txt`.uriPort     >>  81
`http://host:81/a/b/file.txt`.uriPathStr  >>  "/a/b/file.txt"
`http://host:81/a/b/file.txt`.uriPath     >>  ["a", "b", "file.txt"]
`/a/b`.uriIsDir                           >>  false
`/a/b/`.uriIsDir                          >>  true
`file name.html`.uriEncode                >>  "file%20name.html"
"file%20name.html".uriDecode              >>  `file name.html`

List Examples

Common functions for working with lists: add, addAll, all, any, concat, contains, get, each, find, findAll, first, fold, index, indexr, insert, insertAll, isEmpty, last, map, moveTo, remove, set, size, sort, sortr.

x: [10, 20, 30]
y: ["chat", "apple", "bee"]
x.isEmpty          >>  false
x.size             >>  3
x[2]               >>  30
x[1..-1]           >>  [20, 30]  // get with range is slice
x.first            >>  10
x.index(30)        >>  2
x.index(40)        >>  null
x.contains(20)     >>  true
x.fold(sum)        >>  60
x.any v => v < 20  >>  true
x.all v => v < 20  >>  false
x.concat(";")      >>  "10;20;30"

Functions which modify a list always return a new list (the original is immutable):

x.add(40)                      >>  [10, 20, 30, 40]
x.addAll([40, 50])             >>  [10, 20, 30, 40, 50]
x.set(2, 99)                   >>  [10, 20, 99]
x.insert(0, 99)                >>  [99, 10, 20, 30]
x.insertAll(0, [88,99])        >>  [88, 99, 10, 20, 30]
x.remove(1)                    >>  [10, 30]
y.sort                         >>  ["apple", "bee", "chart"]
y.sortr                        >>  ["chart", "bee", "apple"]
y.sort((a,b)=>a.size<=>b.size) >>  ["bee", "chat", "apple"]
y.each s => echo(s)            >>  iterator
y.map s => s.size              >>  [4, 5, 3]
y.find s => s.size == 3        >>  "bee"
y.findAll s => s.size <= 4     >>  ["chat", "bee"]
y.moveTo("chat", -1)           >>  ["apple", "bee", "chat"]

Dict Examples

Common functions for working with dicts: all, any, get, each, find, findAll, has, isEmpty, map, missing, names, remove, set, dis, trap, vals.

d: {dis:"Bob", bday:1980-06-01}
d.isEmpty            >>  false
d["bday"]            >>  1980-06-01
d["foo"]             >>  null
d->dis               >>  "Bob"
d->foo               >>  UnknownNameErr exception
d.has("bday")        >>  true
d.missing("bday")    >>  false
d.names              >>  ["dis", "bday"]
d.vals               >>  ["Bob", 1980-06-01]
d.dis                >>  "Bob"
d.dis("bday")        >>  "1-Jun-1980"
d.any v => v.isDate  >>  true
d.all(isDate)        >>  false

// iterate keys, vals
d.each((v, k) => echo(k + ": " + v))

For a detailed discussion on using [] versus -> see Axon Language.

Functions which modify a dict always return a new dict (the original is immutable):

d.set("person", marker())  >>  {dis:"Bob", bday:1980-06-01, person}
d.remove("bday")           >>  {dis:"Bob"}
d.map v => v + "!"         >>  {dis:"Bob!", bday:"1980-06-01!"}
d.find v => v.isDate       >>  1980-06-01
d.findAll v => v.isDate    >>  {bday:1980-06-01}

Grid Examples

Common functions for working with grids: addCol, addColMeta, addMeta, addRow, addRows, all, any, col, cols, colNames, each, find, findAll, first, foldCol, foldCols, get, gridRowsToDict, gridColsToDict, has, isEmpty, join, joinAll, keepCols, last, map, meta, missing, removeCol, removeCols, renameCol, reorderCols, size, sort, sortr, toGrid.

// create grid from list of dicts
g: [{dis:"Site-A", area:2300ft²},
    {dis:"Site-B", area:3100ft²},
    {dis:"Site-C", area:1950ft²}].toGrid

g.isEmpty                     >>  false
g.size                        >>  3
g.has("area")                 >>  true
g.missing("foo")              >>  true
g.meta                        >>  grid level meta data
g.cols                        >>  [ Col("dis"), Col("area") }
g.colNames                    >>  ["dis", "area"]
g.col("dis").name             >>  "dis"
g.col("dis").meta             >>  meta data for column "dis"
g.col("foo")                  >>  throws UnknownNameErr
g.col("foo", false)           >>  null
g.first                       >>  {dis:"Site-A", area:2300ft²}
g.last                        >>  {dis:"Site-C", area:1950ft²}
g[1]                          >>  {dis:"Site-B", area:3100²}
g[-2]                         >>  {dis:"Site-B", area:3100²}
g[0..1]                       >>  slice to new grid of Site-A, Site-B
g.each(row=>...)              >>  iterate each row as a dict
g.foldCol("area", sum)        >>  7350ft²
g.any r => r->area > 2000ft²  >> true
g.all r => r->area > 2000ft²  >> false

Functions which modify a grid always return a new grid (the original is immutable):

g.sort("area")                             >> sort by area column
g.sortr("area")                            >> reverse sort by area column
g.sort((a,b)=>...)                         >> sort with function
g.map r => r.set("area", r->area.to(1m²))  >> area ft² -> m²
g.find r => r->dis == "Site-A"             >> find row where dis == "Site-A"
g.findAll r => r->area < 2000              >> grid with rows where area < 2000
rowToName: (r) => r->dis[-1..-1].lower     >> func to map "Site-A" -> "a"
g.gridRowsToDict(rowToName, r=>r->area)    >> {a:2300ft², b:3100², c: 1950ft²}
g.gridColsToDict(c=>c.name,c=>c.name.size) >> {dis:3, area:4}
g.addMeta({title:"Sites"})                 >> adds grid level meta data
g.addColMeta("area", {dis:"Sq Footage"})   >> add column level meta data
g.addCol("areaM2") r => r->area.to(1m²)    >> add new column which is area in m²
g.renameCol("area", "sqFt")                >> rename column area -> sqFt
g.reorderCols(["dis", "area"])             >> force specific column ordering
g.removeCol("area")                        >> remove a column
g.removeCols(["area"])                     >> remove a list of columns
g.keepCols(["dis"])                        >> remove all cols except given list
g.addRow({dis:"Site-D", area: 4000ft²})    >> add new row to end of grid
g.addRows([{dis:"Site-D"},{dis:"Site-E"}]) >> add list of new rows to grid

Joins between two grids may be done by column name using join or joinAll:

// create another grid
h: [{dis:"Site-A", tz:"Chicago"},
    {dis:"Site-B", tz:"Denver"},
    {dis:"Site-C", tz:"New_York"}].toGrid

// join g and h by the dis column
g.join(h, "dis")

// resulting join grid
dis     area      tz
------  --------  --------
Site-A  2,300ft²  Chicago
Site-B  3100ft²   Denver
Site-C  1,950ft²  New_York

Regex Examples

Regular expressions can be used via the reMatches, reFind, and reGroups functions. Typically you will want to use raw string literals r"" to avoid escaping the backslash:

// check match for entire string
reMatches(r"AHU-(\d+)", "AHU")     // false
reMatches(r"AHU-(\d+)", "AHU-10")  // true

// find substring in a regex
reFind(r"AHU-(\d+)",  "Store-2")        // null
reFind(r"AHU-(\d+)",  "Store-2 AHU-3")  // "AHU-3"

// find all substring groups in a regex
reGroups(r"(Clg|Hgt)-(\d+)", "foo")     // null
reGroups(r"(Clg|Hgt)-(\d+)", "Hgt-7")   // ["Hgt-7", "Hgt", "7"]
reGroups(r"(Clg|Hgt)-(\d+)", "<Hgt-7>") // ["Hgt-7", "Hgt", "7"]

Note that reGroups returns a list of strings for each group defined by (). The first item in the list is always the entire match.

Axon uses the Java regular expression engine - see java.util.regex.Pattern for full specification.

Read Examples

You can do simple queries to slice and dice your data:

site                          // find everything with site tag
site and geoCity=="Richmond"  // find all the sites in Richmond
equip and siteRef==xxxx       // find all the equip within a site with rec id xxx
equip and siteRef->dis=="Foo" // find all the equip within a site with dis tag "Foo"
ahu and siteRef==xxxx         // find all the AHUs within a site
point and equipRef==xxx       // find all the points within a piece of equipment

The above queries are shorthand for readAll(filter). In general most queries will start will some filter to select a set of recs from the database.

His Examples

To perform time-series analysis you'll typically pipe one or more records to the hisRead function:

// get history data for the points within a given piece of equipment
readAll(point and equipRef==xxx).hisRead(2009-10-03) // for a single day
readAll(point and equipRef==xxx).hisRead(2009-10)    // for a month
readAll(point and equipRef==xxx).hisRead(2009)       // for an entire year
readAll(point and equipRef==xxx).hisRead(pastWeek)   // for last 7 days
readAll(point and equipRef==xxx).hisRead(today)      // for today
readAll(point and equipRef==xxx).hisRead(2009-10-01..2009-10-07)

Any of the time range functions can be used with hisRead including today, yesterday, thisWeek, pastWeek, thisMonth, pastMonth.

In general once you get above a days worth of data, you will need to use a rollup to condense the data into a more manageable volume. Rollups are easy:

// find daily max of electrical demand for month of March 2010
readAll(kw).hisRead(2010-03).hisRollup(max, 1day)

As you start to perform time-series analysis, you'll start to to use functions to map, filter, and fold your data:

// find all the days where daily max exceeded 200 KW
readAll(kw).hisRead(2010-03).hisRollup(max, 1day).hisFindAll(v => v > 200)

// find all the periods of time where zone temp was above 75
readAll(zoneTemp).hisRead(pastMonth).hisFindPeriods(v => v > 75)

As you start to write functions that mine the data for certain patterns, you'll want to start combining them. A common technique is to use hisFindPeriods to find different conditions, then compute the intersection:

(ahu, dates) => do

  // find periods in cooling mode for this AHU
  coolPeriods: read(cool and equipRef==ahu->id)
       .hisRead(dates).hisFindPeriods(v => v)

  // find periods in heating mode for this AHU
  heatPeriods: read(heat and equipRef==ahu->id)
        .hisRead(dates).hisFindPeriods(v => v)

  // compute when AHU running in both heating and cooling modes
  hisPeriodIntersection([coolPeriods, heatPeriods])
end

Energy Examples

Most energy queries start life using a read/readAll piped to the hisRead function:

// daily consumption data for all sites in Mar 2011
readAll(kwh and siteMeter).hisRead(2011-03).hisRollup(sum, 1day)

// monthly consumption data for all sites in 2010
readAll(kwh and siteMeter).hisRead(2010).hisRollup(sum, 1mo)

// raw demand data for all sites on 1 Apr 2011
readAll(kw and siteMeter).hisRead(2011-04-01)

// demand data normalized by area (square footage/meters)
readAll(kw and siteMeter).hisRead(2011-04-01)
  .energyNormByArea

// demand data normalized by degree-day
readAll(kw and siteMeter).hisRead(2011-04-01)
  .energyNormByDegreeDay

// demand data normalized by area and degree-day
readAll(kw and siteMeter).hisRead(2011-04-01)
  .energyNormByArea
  .energyNormByDegreeDay

// table of site dis and total kWh consumption for March sorted by kWh
readAll(kwh and siteMeter).hisRead(2011-03)
  .hisRollup(sum, 1mo)
  .hisFlatten((kw,ts,his) => { dis:his->siteRef->dis, kw:kw })
  .sort("kw")

// average daily profile of hourly demand peak across last month
readAll(kw and siteMeter).hisRead(lastMonth)
  .hisRollup(max, 1hr)
  .hisDailyProfile(avg)