-
Notifications
You must be signed in to change notification settings - Fork 185
image
SummaryThis proposal adds a first end-to-end path that translates Python source into an ordinary Texera workflow of Python UDF operators. It reuses the existing compiler kernel and its working source model, dependency analysis, checking, realization, and rendering algorithms. The implementation is a smaller modular composition of that compiler, not a second compiler and not a rewrite from scratch. The initial boundary is about where the compiler may split a program, not which normal Python statements a user may write. What the compiler doesThe user-facing pipeline has four stable stages: An evaluation atom is simply a set of program operations that cannot be separated safely. The initial composition closes those atoms under each complete top-level statement. Therefore the smallest placement unit visible to the grouping strategy is one complete statement. Statement-level does not mean straight-line-onlyControl flow, functions, and classes are not rejected merely because of their syntax. A complete top-level What is deferred is decomposing the interior of those statements across multiple operators. Future modules may add the dependency and execution protocols required to split loops, calls, exceptions, or recursion without changing the grouping interface, checker, renderer, or workflow builder. Invalid Python still fails during parsing, and a proposed operator boundary still fails verification when no registered realization can implement it. ExampleGiven: data = load("a.csv") for row in data: clean(row) model = train(data) save(model) the initial placement units are: A grouping strategy may propose: Verification then proves whether the value required downstream can cross from A to B. If it can, the selected boundary realization emits explicit export/import actions. If it cannot, that cut is illegal and a coarser legal grouping must be selected. The compiler never splits Conceptually, the generated workflow is: Deterministic grouping strategiesThe same analyzed program can be compiled with either strategy:
Neither strategy may override dependency, transport, or realizability constraints. One authoritative checker certifies the final proposal. Internal architectureThe forest retains exact Python source and structural information. The dependence graph records which operations produce and consume semantic values. The statement placement view is the only solver-facing projection. The checker owns final legality. Realizations explain how certified local code and boundaries are materialized. The renderer only composes already-certified projections. Boundary transport and Amber integration
MOSAIC owns source semantics, dependency analysis, grouping, and realization selection. Amber/PyTexera owns generic execution and transport primitives; it remains unaware of MOSAIC-specific atoms, carriers, colors, or solver rules. Extensibility ruleOptional capabilities are installed as modules with explicit dependencies and contributions. A provider and the projector that interprets its facts are owned together. Adding support for distributed loops, calls, exceptions, files, consoles, or resources must add analysis evidence and/or a boundary method while preserving the same four-stage pipeline. Feature-specific conditionals must not be scattered through dependence-graph construction, checking, or rendering, and no module may introduce a second authoritative graph, checker, or renderer. End-to-end result: Wine classificationThe current MVP was exercised with a realistic scikit-learn Wine classification program. This is the actual compiler input and the generated Python UDF code below is copied from the resulting Texera workflow JSON.
Wine input programimport numpy as np import pandas as pd from sklearn.datasets import load_wine from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix from sklearn.model_selection import train_test_split wine = load_wine(as_frame=True) frame = wine.frame.copy() features = frame.drop(columns="target") target = frame["target"].astype(np.int64) class_profile = frame.groupby("target").mean(numeric_only=True) lower_limits = features.quantile(0.01) upper_limits = features.quantile(0.99) curated = features.clip(lower=lower_limits, upper=upper_limits, axis=1) curated["phenol_balance"] = curated["total_phenols"] / curated["nonflavanoid_phenols"].clip(lower=0.01) curated["color_hue_ratio"] = curated["color_intensity"] / curated["hue"].clip(lower=0.01) curated["alcohol_malic_interaction"] = curated["alcohol"] * curated["malic_acid"] X_train, X_test, y_train, y_test = train_test_split( curated, target, test_size=0.25, random_state=42, stratify=target, ) training_mean = X_train.mean(axis=0) training_variance = X_train.var(axis=0, ddof=0) training_scale = np.sqrt(training_variance).replace(0.0, 1.0) X_train_scaled = (X_train - training_mean) / training_scale X_test_scaled = (X_test - training_mean) / training_scale model = RandomForestClassifier( n_estimators=300, max_depth=7, min_samples_leaf=2, random_state=42, n_jobs=-1, ) model.fit(X_train_scaled, y_train) prediction = model.predict(X_test_scaled) class_probability = model.predict_proba(X_test_scaled) quality_report = classification_report(y_test, prediction, output_dict=True, zero_division=0) confusion = confusion_matrix(y_test, prediction) accuracy = float(quality_report["accuracy"]) macro_f1 = float(quality_report["macro avg"]["f1-score"]) mean_confidence = float(np.mean(np.max(class_probability, axis=1))) importance = pd.Series(model.feature_importances_, index=X_train_scaled.columns, name="importance") top_features = importance.nlargest(8) summary = pd.DataFrame({ "metric": ["accuracy", "macro_f1", "mean_confidence"], "value": [accuracy, macro_f1, mean_confidence], }) print(summary.to_string(index=False)) print(top_features.to_string()) print(confusion) The generated DAG exposes parallel branches where the statement dependencies permit them and joins those branches through ordinary Texera input ports. Port labels use the producer identifier (for example, Representative generated UDFsThese are complete UDF 001 — imports and dataset loadingimport pytexera.workflow as _mosaic_workflow_module_0 _mosaic_runtime_0 = _mosaic_workflow_module_0.Runtime( input_ports=(), outgoing=('boundary_0000', 'boundary_0001', 'boundary_0002', 'boundary_0003', 'boundary_0004', 'boundary_0005'), ) @_mosaic_runtime_0.driver def _mosaic_driver_0(_mosaic_heap_0): import numpy as np import pandas as pd from sklearn.datasets import load_wine from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix from sklearn.model_selection import train_test_split wine = load_wine(as_frame=True) _mosaic_boundary_0 = 'boundary_0000' _mosaic_runtime_0.export_boundary(_mosaic_heap_0, _mosaic_boundary_0, ()) _mosaic_boundary_0 = 'boundary_0001' _mosaic_runtime_0.export_boundary(_mosaic_heap_0, _mosaic_boundary_0, ('wine',), locals()) _mosaic_boundary_0 = 'boundary_0002' _mosaic_runtime_0.export_boundary(_mosaic_heap_0, _mosaic_boundary_0, ()) _mosaic_boundary_0 = 'boundary_0003' _mosaic_runtime_0.export_boundary(_mosaic_heap_0, _mosaic_boundary_0, ()) _mosaic_boundary_0 = 'boundary_0004' _mosaic_runtime_0.export_boundary(_mosaic_heap_0, _mosaic_boundary_0, ()) _mosaic_boundary_0 = 'boundary_0005' _mosaic_runtime_0.export_boundary(_mosaic_heap_0, _mosaic_boundary_0, ()) return _mosaic_heap_0 class _mosaic_operator_0(_mosaic_workflow_module_0.TupleOperator): runtime = _mosaic_runtime_0 UDF 004 — dependency join, train/test split, and exportimport pytexera.workflow as _mosaic_workflow_module_0 _mosaic_runtime_0 = _mosaic_workflow_module_0.Runtime( input_ports=(_mosaic_workflow_module_0.InputPort(boundaries=('boundary_0003',)), _mosaic_workflow_module_0.InputPort(boundaries=('boundary_0008', 'boundary_0009')),), outgoing=('boundary_0010', 'boundary_0011', 'boundary_0012'), ) @_mosaic_runtime_0.driver def _mosaic_driver_0(_mosaic_heap_0): _mosaic_boundary_0 = 'boundary_0003' _mosaic_runtime_0.import_boundary(_mosaic_heap_0, _mosaic_boundary_0, ()) from sklearn.model_selection import train_test_split _mosaic_heap_0.train_test_split = train_test_split _mosaic_boundary_0 = 'boundary_0008' _mosaic_runtime_0.import_boundary(_mosaic_heap_0, _mosaic_boundary_0, ('curated', 'target')) _mosaic_boundary_0 = 'boundary_0009' _mosaic_runtime_0.import_boundary(_mosaic_heap_0, _mosaic_boundary_0, ()) X_train, X_test, y_train, y_test = _mosaic_heap_0.train_test_split( _mosaic_heap_0.curated, _mosaic_heap_0.target, test_size=0.25, random_state=42, stratify=_mosaic_heap_0.target, ) training_mean = X_train.mean(axis=0) _mosaic_boundary_0 = 'boundary_0010' _mosaic_runtime_0.export_boundary(_mosaic_heap_0, _mosaic_boundary_0, ('X_train', 'training_mean'), locals()) _mosaic_boundary_0 = 'boundary_0011' _mosaic_runtime_0.export_boundary(_mosaic_heap_0, _mosaic_boundary_0, ()) _mosaic_boundary_0 = 'boundary_0012' _mosaic_runtime_0.export_boundary(_mosaic_heap_0, _mosaic_boundary_0, ('X_test', 'y_test', 'y_train'), locals()) return _mosaic_heap_0 class _mosaic_operator_0(_mosaic_workflow_module_0.TupleOperator): runtime = _mosaic_runtime_0 UDF 008 — terminal reportingimport pytexera.workflow as _mosaic_workflow_module_0 _mosaic_runtime_0 = _mosaic_workflow_module_0.Runtime( input_ports=(_mosaic_workflow_module_0.InputPort(boundaries=('boundary_0017', 'boundary_0018')),), outgoing=(), ) @_mosaic_runtime_0.driver def _mosaic_driver_0(_mosaic_heap_0): _mosaic_boundary_0 = 'boundary_0017' _mosaic_runtime_0.import_boundary(_mosaic_heap_0, _mosaic_boundary_0, ('confusion', 'summary', 'top_features')) _mosaic_boundary_0 = 'boundary_0018' _mosaic_runtime_0.import_boundary(_mosaic_heap_0, _mosaic_boundary_0, ()) print(_mosaic_heap_0.summary.to_string(index=False)) print(_mosaic_heap_0.top_features.to_string()) print(_mosaic_heap_0.confusion) return _mosaic_heap_0 class _mosaic_operator_0(_mosaic_workflow_module_0.TupleOperator): runtime = _mosaic_runtime_0 This example demonstrates compilation and workflow construction. Runtime performance claims require separate controlled execution measurements and are not inferred from the graph shape. Validation planThe initial implementation will cover:
Non-goalsThis first integration does not distribute the interior of control-flow statements, functions, classes, recursion, exceptions, or individual expressions. It also excludes ML-based grouping, whole-namespace transport, and an Amber engine redesign. The existing complex-case compiler work remains the reference for later modules. It is not being discarded or reimplemented. Questions for review
|
All reactions
Replies: 1 comment
Per an offline discussions between @carloea2 and me, I support this design. Here are my answers to the questions:
- Yes.
- Either way is fine. We can choose one based on the quality of generated workflows. Deciding a good strategy requires more future effort.
- Yes.
- We can select some sample Python programs, e.g., those from GitHub.