-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #339 from Point72/nodes-v-graph-docs
Add "Common Mistakes" page for new users
- Loading branch information
Showing
1 changed file
with
93 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
## Table of Contents | ||
|
||
- [Graphs vs. nodes](#graphs-vs-nodes) | ||
- [Always use engine time](#always-use-engine-time) | ||
|
||
## Graphs vs. nodes | ||
|
||
The most common challenge for new `csp` users is understanding the distinction between [graphs](CSP-Graph) and [nodes](CSP-Node). A few issues arise when graphs are treated like nodes and vice versa. | ||
|
||
1. *Nodes cannot call other nodes*. Nodes themselves can be considered *atomic* computation units; they perform a single transformation at runtime and do not invoke other nodes to execute. | ||
|
||
Example erroneous code | ||
|
||
```python | ||
import csp | ||
from csp import ts | ||
|
||
@csp.node | ||
def square(x: ts[int]) -> ts[int]: | ||
return x*x | ||
|
||
@csp.node | ||
def cube(x: ts[int]) -> ts[int]: | ||
return x*square(x) | ||
|
||
|
||
csp.run(cube(csp.const(1)), starttime=datetime(2020,1,1), endtime=timedelta()) | ||
# the run call above will fail at runtime with the error | ||
# ArgTypeMismatchError: In function square: Expected csp.impl.types.tstype.TsType[int] for argument 'x', got 1 (int) | ||
# square(x) accepts a time-series of int, but when cube is invoked we are passing it the VALUE of x (an int)! | ||
``` | ||
|
||
Corrected code - we simply make `cube` a graph, not a node. | ||
|
||
```python | ||
@csp.graph | ||
def cube(x: ts[int]) -> ts[int]: | ||
return x*square(x) | ||
``` | ||
|
||
2. *Graph code does not access runtime values*. This is the inverse of (1): graph code treats all time-series as Edges and simply "wires" together the application. | ||
|
||
Example erroneous code | ||
|
||
```python | ||
@csp.graph | ||
def clip(x: ts[int], low: int, high: int) -> ts[int]: | ||
return min(max(x, low), high) | ||
|
||
csp.run(clip(csp.const(3), 1, 4), starttime=datetime(2020,1,1), endtime=timedelta()) | ||
# the run fails with - ValueError: boolean evaluation of an edge is not supported | ||
# at graph-time, x is an Edge, not a value: so comparing it to an integer doesn't make sense | ||
# we really want to compare each value itself at runtime | ||
``` | ||
|
||
Corrected code - we apply the `min` and `max` functions to the values, not the Edge. | ||
|
||
```python | ||
@csp.graph | ||
def clip(x: ts[int], low: int, high: int) -> ts[int]: | ||
return csp.apply(x, lambda u: min(max(u, low), high), int) | ||
``` | ||
|
||
## Always use engine time | ||
|
||
Another common mistake is to use `datetime.now()` inside a `csp.node`. You should **always** use the engine time in a node by calling `csp.now()`, or else your application will not work correctly in historical mode. The *engine time* is the time that the current `csp` engine cycle started. In real-time it will be the wall clock at the time the cycle began, and in historical mode it will be the timestamp of the simulated event. The use of `csp.now()` allows the graph to be run in both execution contexts and still maintain a consistent view of the time. | ||
|
||
Example erroneous code | ||
|
||
```python | ||
from typing import List | ||
|
||
@csp.node | ||
def next_movie_showing(show_times: ts[List[datetime]]) -> ts[datetime]: | ||
next_showing = None | ||
for time in show_times: | ||
if time >= csp.now(): # list may include some shows today that have already past, so let's filter those out | ||
if next_showing is None or time < next_showing: | ||
next_showing = time | ||
|
||
return next_showing | ||
``` | ||
|
||
If you only run this node in real-time, you may not notice that it has a critical error. When we run on historical data, we will not get any valid show times! The show times in historical data will be on *past* days, but `datetime.now()` will be giving us the *current* time. | ||
|
||
Corrected code - we use `csp.now` instead of `datetime.now` so the node works on playback data. | ||
|
||
```python | ||
... | ||
for time in show_times: | ||
if time >= csp.now(): | ||
... | ||
``` |