# Getting started: data science Turn a Python experiment into a flow with recorded parameters, training curves and files you can find again. Goodbye, `results_final_v3.csv`. This example uses a toy training curve, so you can try the whole workflow without a dataset or GPU. You need Python 3.12 or newer on Linux or macOS. Install Git too if you want flow changes recorded in version history. ## 1. Start your instance Create a project directory and install Fluksio in a virtual environment: ```sh mkdir research-flow cd research-flow python3 -m venv .venv . .venv/bin/activate pip install fluksio fluksio serve ``` Fluksio creates `.fluksio/` for this project's flows, run history and artifacts. It prints the initial admin password once and saves a token for local CLI commands. Keep the password somewhere safe. At a terminal, `serve` opens a dashboard. Leave it running, then open a second terminal in `research-flow` and activate the same environment: ```sh . .venv/bin/activate ``` For the browser dashboard, sign up at [fluksio.com](https://fluksio.com), add an instance, and enter the code it gives you: ```sh fluksio enroll ``` Select the instance in the portal. The CLI also works without enrolling. !!! tip "Use your existing research environment" You can install Fluksio in the virtual environment that already holds your research packages. Nodes use that environment by default. The startup banner shows the interpreter; see [Packages](../code/nodes.md#packages) for other setups. ## 2. Write a small flow Save this as `study.py` in `research-flow`: ```python import json import math from pathlib import Path import fluksio from fluksio import Flow, Port, node @node( requires=[Port("lr", "float"), Port("epochs", "int")], provides=[ Port("loss", "float", stream=True), Port("weights", "artifact"), Port("final_loss", "float"), ], ) def fit(lr, epochs): loss = 1.0 for epoch in range(epochs): loss = math.exp(-lr * (epoch + 1)) yield {"loss": loss} checkpoint = json.dumps({"loss": loss}).encode() return { "weights": fluksio.save_artifact( checkpoint, "weights.json", "application/json" ), "final_loss": loss, } @node(requires=["weights"], provides=Port("score", "float")) def evaluate(weights): checkpoint = json.loads(Path(fluksio.load_artifact(weights)).read_text()) return 1.0 - checkpoint["loss"] train = Flow( "train", nodes=[fit, evaluate], inputs=[Port("lr", "float", initial=0.05), Port("epochs", "int", initial=10)], outputs=["score", "final_loss", "weights"], ) ``` `@node` declares inputs and outputs. Matching message names connect the nodes: `fit` produces `weights`, and `evaluate` reads it. `Flow` lists the nodes, run parameters and final results. Each `yield` records a loss value. The final `return` sends the checkpoint to the next node. Artifact ports carry a small reference; the file stays in the artifact store. ## 3. Sync and run ```sh fluksio sync study fluksio run train --lr 0.1 --epochs 20 --follow ``` `sync` uploads the flow and generates node code that imports your functions. Keep the project files available to the engine. Later `run` commands sync edits before submitting; `--follow` waits and prints metrics as they arrive. This run should finish with `final_loss` around `0.1353` and `score` around `0.8647`. In the portal, open **Flows → train** to see the graph, or **Runs** to inspect the parameters, metrics and results. From the CLI: ```sh fluksio runs --flow train fluksio artifacts fluksio artifacts train.weights -o weights.json ``` Use the ID printed by `run`. Artifact names are flow-qualified; the list command shows the exact names to download. ## 4. Try a few learning rates ```sh fluksio sweep train --param lr=0.01,0.05,0.1 --wait fluksio export metrics --flow train -o curves.csv fluksio export runs --flow train --status ok -o results.csv ``` The sweep creates three runs in one group. Select them on **Runs** to compare their curves. The exports give you a row per metric point and a row per run, ready for your analysis. Python can read the same tables: ```python from fluksio.sdk.client import Client client = Client() results = client.export_runs(flow="train", status="ok") curves = client.export_metrics(flow="train", name="train.loss") ``` Pass either list to `pandas.DataFrame` if you use pandas. ## Bring your own experiment Replace the toy calculation in `fit` with your training code. Keep ordinary Python helpers separate from artifact storage so you can still test them without an engine: `save_artifact` and `load_artifact` work only inside nodes. The public repository's `examples/myresearch/` package shows this pattern across several files. - Defaulted function arguments that are not input ports become node settings. Use flow inputs for values you want to vary between runs. - Use `fluksio.emit(loss=value)` when metrics arrive through a framework callback instead of a loop you can `yield` from. - Runs can reuse unchanged stages. Use `--no-cache` when you want every node to execute; see [Stage caching](../concepts/runs.md#stage-caching). - `--seed` records a seed and fills an input named `seed` when declared. Your code still needs to pass that value to its random number generator. ### Declaring what a node needs For heavy computation, declare resources on the node: ```python @node(requires=["dataset"], provides=Port("score", "float"), resources={"cpus": 2, "ram": "2G"}) def score_dataset(dataset): return {"score": calculate_score(dataset)} ``` The engine accounts for these resources and sets worker thread limits. GPU nodes can request `resources={"gpus": 1}`; check that the Workers screen reports your GPU, or set `fluksio serve --gpus 1` when automatic detection does not cover it. Resource accounting does not impose a hard memory limit. To run on another machine, attach a [remote worker](../code/workers.md) and install your project and its dependencies there. ## Next steps - [Writing node code](../code/nodes.md) — ports, streaming, artifacts and packages. - [Runs](../concepts/runs.md) — caching, comparisons and retries. - [CLI reference](../code/cli.md) — command options and instance settings.