Seamless is a reactive framework that consists of cells. It's a bit like a spreadsheet like Microsoft Excel. But a spreadsheet is only for simple formulas and plotting.
Seamless is more powerful and can be used also for serious programming: algorithm development, scientific workflows, 3D shaders, and prototype graphical user interfaces.
For now, seamless is also less convenient than a spreadsheet, which provides out-of-the-box a table of cells that can access each other. In seamless, you have to define and connect the cells yourself.
Future versions of seamless will contain a GUI for this. But for now, you must use seamless from IPython.
In [1]:
import seamless
from seamless import context, cell
ctx = context()
This defines a main context for our cells. Let's create some:
In [2]:
ctx.a = cell(int).set(2)
ctx.b = cell(int).set(3)
ctx.result = cell(int)
Let's define a formula for the result cell: a + b
In a spreadsheet, you would write the formula inside the result cell.
But in seamless, you create a separate code cell. Usually, code cells contain Python code:
In [3]:
from seamless import pythoncell
ctx.formula = pythoncell().set("return a + b")
To perform computations, we need to define a transformer.
Our transformer has two **int** inputs, called a and b, and one **int** output, called result.
Each of them is declared as a pin of the transformer:
In [4]:
from seamless import transformer
t = ctx.transform = transformer({
"a": {"pin": "input", "dtype": "int"},
"b": {"pin": "input", "dtype": "int"},
"result": {"pin": "output", "dtype": "int"}
})
In [5]:
print(t.a)
print(t.b)
print(t.result)
Every transformer has an additional (implicitly declared) input pin, called code, for Python code:
In [6]:
print(t.code)
To activate the transformer, we connect cells to the input pins:
In [7]:
ctx.a.connect(t.a)
ctx.b.connect(t.b)
... and the output pin to a cell:
In [8]:
t.result.connect(ctx.result)
Normally, the result cell will contain the computed value:
In [9]:
print(ctx.result.value)
Something went wrong... what is the status?
In [10]:
ctx.status()
Out[10]:
result is undefined, we knew that already... but ah, we forgot to connect the formula:
In [11]:
ctx.formula.connect(t.code)
The following step is only necessary if you run the entire IPython script at once. When typing the commands one-by-one, you can omit it.
In [12]:
await ctx.computation() # The transformer runs asynchronously, it needs few milliseconds to complete
# Typically, time.sleep(0.01) will work too
Out[12]:
In [13]:
ctx.status()
Out[13]:
In [14]:
print(ctx.result.value)
And there we have it.
The transformer now responds to updates of any connected input cell:
In [15]:
ctx.a.set(5)
await ctx.computation()
print(ctx.result.value)
In [16]:
ctx.b.set(7)
await ctx.computation()
print(ctx.result.value)
In the same way, it responds to changes in the formula. Seamless considers a code cell as just another input cell.
In [17]:
ctx.formula.set("return b - a")
await ctx.computation()
print(ctx.result.value)
Admittedly, setting up a seamless context is a bit clunky. In future versions of seamless, there will be a GUI that can take care of setting up things like cells, connections and transformers.
In the meantime, if you want, you could write some convenience functions. For example:
In [18]:
def make_transformer(*, code, output, **kwargs):
from seamless import transformer, cell, pythoncell
ctx = seamless.core.context.get_active_context()
assert output in kwargs # The "output" parameter says which of the pins is output
# Construct transformer parameter dict
transformer_params = {}
for k in kwargs:
dtype = kwargs[k]
if isinstance(dtype, type):
dtype = dtype.__name__
pin = "output" if k == output else "input"
transformer_params[k] = {"pin": pin, "dtype": dtype}
# Create the transformer
t = transformer(transformer_params)
# Connect a code cell, whose name is in the variable "code"
# If the code cell does not exist, create it
try:
cell_code = getattr(ctx, code)
except AttributeError:
cell_code = pythoncell()
setattr(ctx, code, cell_code)
cell_code.connect(t.code)
# For every pin, connect it to the cell of the same name
# If that cell does not exist, create it
for k in kwargs:
dtype = kwargs[k]
try:
cell_k = getattr(ctx, k)
except AttributeError:
cell_k = cell(dtype)
setattr(ctx, k, cell_k)
pin = getattr(t, k)
if k == output:
pin.connect(cell_k)
else:
cell_k.connect(pin)
return t
With this, we can set up a transformer in just a few lines of code:
In [19]:
ctx.result2 = cell(int)
ctx.transformer2 = make_transformer(a=int, b=int, result2=int, output="result2", code="formula2")
ctx.formula2.set("return a * b") # ctx.a.value * ctx.b.value = 5 * 7 = 35
Out[19]:
In [20]:
await ctx.computation()
print(ctx.result2.value)
In addition to transformers, seamless has another construct to do computations in a reactive way: reactors
Reactors work just like transformers, except that they have three code input pins: code_start, code_update and code_stop. The code in the connected cells is all executed in the same Python namespace. For example:
In [21]:
from seamless import reactor
ctx.code_start, ctx.code_update, ctx.code_stop = pythoncell(), pythoncell(), pythoncell()
ctx.reactor = reactor({"a": {"pin": "input", "dtype": "int"}})
ctx.a.connect(ctx.reactor.a)
ctx.code_start.connect(ctx.reactor.code_start)
ctx.code_update.connect(ctx.reactor.code_update)
ctx.code_stop.connect(ctx.reactor.code_stop)
ctx.code_start.set("print('START'); somevalue = 42")
ctx.code_update.set("print('UPDATE', somevalue)")
ctx.code_stop.set("")
await ctx.computation()
Out[21]:
A reactor has access to its pins via the PINS object:
In [22]:
ctx.code_start.set("value = PINS.a.get()")
ctx.code_update.set("""
new_value = PINS.a.get()
if new_value == value:
print("Setting value: {0}".format(new_value))
else:
print("Updating value: {0} (was {1})".format(new_value, value))
value = new_value
""")
await ctx.computation()
ctx.status()
Out[22]:
As you can see, seamless prints an error message, but it is harmless.
Right after you change code_start, the reactor is re-evaluated with the new code_start and the old code_update. But as soon as you enter the new code_update, everything is OK again. This glitchy behavior is also like a spreadsheet.
The reactor now tracks the value of a, reporting any changes:
In [23]:
ctx.a.set(8)
ctx.a.set(5)
await ctx.computation()
Out[23]:
In [24]:
from seamless.lib import edit, display
ctx.gui = context() # Create a subcontext to organize our cells better
ctx.gui.a = edit(ctx.a, "Input a")
ctx.gui.b = edit(ctx.b, "Input b")
ctx.gui.result = display(ctx.result, "Result")
and the same for "Input b" and "Result"
Changing the input values will immediately update the result.
We can do the same for the code cell, this creates a text editor. The code is updated as soon as you press Ctrl+S or click "Save".
In [25]:
ctx.gui.formula = edit(ctx.formula, "Transformer code")
Execute the following command, or copy-paste the code into the "Transformer code" window:
In [26]:
ctx.formula.set("""
def fibonacci(n):
def fib(n):
if n <= 1:
return [1]
elif n == 2:
return [1, 1]
else:
fib0 = fib(n-1)
return fib0 + [ fib0[-1] + fib0[-2] ]
fib0 = fib(n)
return fib0[-1]
return fibonacci(a) + fibonacci(b)
""")
await ctx.computation()
Out[26]:
In [27]:
ctx.a.set(10)
ctx.b.set(20)
await ctx.computation()
print(ctx.result.value)
The seamless library itself consists of seamless cells:
In [28]:
print(ctx.gui.formula)
print(ctx.gui.formula.rc)
print(ctx.gui.formula.rc.code_start)
print(ctx.gui.formula.rc.code_start.cell())
In [29]:
text_editor_code = ctx.gui.formula.rc.code_start.cell()
ctx.gui.text_editor = edit(text_editor_code, "Text editor source code")
This displays the code of the seamless library text editor itself: https://github.com/sjdv1982/seamless/blob/stable/seamless/lib/gui/cell-basic_editor_text.py
Editing Text editor source code immediately changes the other window!
For example, add b.setTextColor(QColor(255,0,0)) at the end, and press Ctrl+S:
You can also inspect and manipulate the Text editor by hooking up an additional IPython shell to the reactor's namespace.
In [30]:
from seamless.gui import shell
shell(ctx.gui.formula) #or: shell(ctx.gui.formula.rc)
Out[30]:
This will open a new IPython QtConsole shell, in which you can type, for example, b.setText("test")
The way Qt works, it will not be immediately responsive to all modifications.
Integrating the following code snippets will enable Python syntax highlighting:
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
css = HtmlFormatter().get_style_defs('.highlight')
def highlight_python():
txt = b.toPlainText()
html = highlight(txt, PythonLexer(), HtmlFormatter())
b.setHtml("<style>" + css + "</style>"+ html
In this way, you can live-edit any cell in the seamless library. It is currently extremely basic, so please save all your improvements and make pull requests on GitHub!
Or, you can just link the cell to a file, and use your favorite text editor or Python IDE.
In [31]:
import tempfile
from seamless.lib import link
link(ctx.formula, tempfile.gettempdir(), "formula.py")
Out[31]:
The link() reactor writes all future changes in the cell to the file, and vice versa.
In [ ]: