Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ jobs:
with:
python-version: "3.x"

- name: Sync version to sonar-project.properties
run: |
# Ensure version is synced before building
python scripts/update_version.py

- name: Build release distributions
run: |
# Build the hyperway package
Expand Down Expand Up @@ -92,6 +97,11 @@ jobs:
with:
python-version: '3.12'

- name: Sync version to sonar-project.properties
run: |
# Ensure version is synced before SonarCloud analysis
python scripts/update_version.py

- name: Install Graphviz
run: |
wget https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/14.0.1/ubuntu_25.04_graphviz-14.0.1-cmake.deb -O /tmp/graphviz-14.0.1.deb
Expand Down
129 changes: 129 additions & 0 deletions docs/future/knuckles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
## Knuckles and Connection Decisions

> A knuckle is a connection selection mechanism that filters available edges at execution time. It transforms static graph topology into dynamic routing based on runtime state, values, or conditions.

### Core Concept

When a `Unit` has multiple outgoing connections, the stepper normally follows all of them. A knuckle intercepts this point and returns a filtered subset:

```python
# Pseudocode
def select_connections(connections, stepper, akw):
# connections: tuple of available Connection objects
# stepper: StepperC instance (for state access)
# akw: ArgsPack with current values
return filtered_subset_of_connections
```

### Implementation Approaches

**1. Method Override** - Subclass `Unit` and override `select_connections()`:

```python
class ConditionalUnit(Unit):
def select_connections(self, connections, stepper, akw):
if condition_met(akw):
return connections # follow all
return connections[:1] # follow only first
```

**2. Pluggable Knuckle** - Attach a callable to `unit.knuckle`:

```python
unit = as_unit(func)
unit.knuckle = lambda conns, s, akw: filter_logic(conns, akw)
```

The `Unit.select_connections()` method checks for an attached knuckle first, then falls back to default behavior (return all connections).

### Integration Point

The stepper invokes knuckle logic in `call_one_unit()` after resolving available connections but before iteration:

```python
# Pseudocode in StepperC.call_one_unit
connections = get_connections(graph, unit)
selected = unit.select_connections(connections, stepper, akw)
if not selected:
return unit.leaf(...) # treat as end-node
for conn in selected:
# execute connection...
```

### Use Cases

- **Conditional branching**: Route based on value thresholds, types, or predicates
- **State machine transitions**: Implement NFA/epsilon transitions by selecting connections without consuming input
- **Round-robin distribution**: Stateful knuckles can track invocation count and alternate paths
- **Circuit breaking**: Dynamically disable connections based on errors or stepper state
- **A/B testing**: Probabilistic routing between connection sets

### Event Emission and State Machines

Knuckles enable independent state machine behavior:

- A knuckle can emit events to external observers when selecting connections
- Epsilon transitions occur when `akw` values don't change but connection selection does
- Non-deterministic routing happens when multiple valid connections exist and selection varies per invocation
- State machines can track history via `stepper.stash` or custom knuckle state

# Example

An example of a knuckle selecting connections given their name and a key in `akw`:

```python
def name_based_knuckle(connections, stepper, akw):
key = akw.get('route_key')
return [conn for conn in connections if conn.name == key]

unit = as_unit(some_function)
unit.knuckle = name_based_knuckle


# 4 connections, two called dave:
g = Graph()

for name in ['alice', 'bob', 'dave', 'dave']:
g.connect(unit, another_unit, name=name)
stepper = StepperC(g)

akw1 = ArgsPack(route_key='dave')
selected_conns = unit.select_connections(g.get_connections(unit), stepper, akw1)
assert len(selected_conns) == 2 # both 'dave' connections selected
```

```python
akw2 = ArgsPack(route_key='alice')
selected_conns = unit.select_connections(g.get_connections(unit), stepper, akw2)
assert len(selected_conns) == 1 # only 'alice' connection selected
```

## Value Splitting Knuckles

A knuckle can perform value splitting. Each connection can receive a modified `ArgsPack`:

```python
def splitting_knuckle(connections, stepper, akw):
result = []
for conn in connections:
modified_akw = akw.copy()
modified_akw['selected'] = conn.name
result.append((conn, modified_akw))
return result

unit.knuckle = splitting_knuckle
```

In an example where the value is numerical, the knuckle can provide each edge with a divided value:

```python
def dividing_knuckle(connections, stepper, akw):
value = akw.get('value', 1)
num_conns = len(connections)
for conn in connections:
modified_akw = akw.copy()
modified_akw['value'] = value / num_conns
yield (conn, modified_akw)

unit.knuckle = dividing_knuckle
```
61 changes: 61 additions & 0 deletions docs/future.md → docs/future/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,31 @@
HyperWay interacts with state machines using non-deterministic finite automatons (NFA). This allows changing condition sets as they traverse paths or nodes. Non-deterministic functionality lets us select which steps to take going forward. This is coupled with lambda functions along the edges of nodes. When a connection is chosen or a series of connections are selected, the lambda function can serve as a state transitioning tool, allowing a parallel or sibling system to manage a state independently of the graph and its nodes.

## Knuckles and Lambda Functions

see also: [Knuckles](./knuckles.md)

To integrate additional features into HyperWay, we extend the concept of a knuckle. A knuckle allows selecting from a finite set of connections. When visualized, this will allow using a knuckle function (or lambda) for a list of connections. The lambda function not only connects to a list of nodes, reducing the list of connections upon request, but also enables event emission for an independent state machine. This state machine can have non-deterministic or epsilon transitions, where a state can transition without consuming input symbols.

## Tagged Nodes and Edges
HyperWay can utilize tagged nodes or edges in a hypergraph methodology. During a step, rather than deterministic nodes and connections, it can request or infer a new set of nodes or conditions. The request for nodes is built into the graph as a tag, enabling it to find all nodes with a particular tag. Similarly, tagged connections can be requested upon exiting a node, retrieving a list of connections defined by a tag.

## [TODO: Rename] Fiber Connections

A _fiber_ is a "thinner" connection between nodes, or the knuckles on those nodes. A fiber can bridge the graph to reference nodes in the graph, of which are not directly connected (on the path).

For example we have a ring of 6 nodes, but the output of node three, alters node one output value. A fiber connection can be made between these nodes, allowing node three to influence node one without a direct connection in the path.

The _Graph_ path and nodes don't see this connection. The stepper doesn't traverse this edge. Instead, the fiber connection is a side-channel that allows nodes to influence each other without being on the main path.

The fiber may perform a range of interactions with the destinations. The receiver node may choose to listen to the fiber connection, or ignore it. The fiber connection may also perform transformations on the data being sent, or buffer it for later use.

---

Conceptually I feel this is important for the purpose of "back talk", where the tip of a connection (the outbound node or knuckle) can send information back to an earlier point in the graph without being on the main path.

Consider for example electronic circuits, where feedback loops are common. A fiber connection could represent a feedback loop in a graph, without affecting the main flow of data.


## Hyperedges
Hyperedges allow multiple input and exit nodes on a single connection, treating many nodes grouped under one hyperedge as a single definition. This extends to executing a sub-graph of connections that the hyperedge is connected to. Implementing a boundary around a group of nodes creates a hyperedge that can connect to another node, graph, or hyperedge.

Expand All @@ -18,6 +38,47 @@ The value from a hyperedge yields the same count of results as the nodes inside
### Unique Configurations
Integrating unique functionality directly into the hyperedge allows for specific manipulations, requests, or configurations unique to the edge itself. For example, a hyperedge might require all nodes on the edge to activate simultaneously or stash the results of particular nodes as the stepper walks through them. When a hyperedge is "full," it emits a result down its connections. The default method requires all nodes to be activated simultaneously, while optional methods, such as the fill method, provide flexibility in how and when results are emitted.


## Edge | Hyperedge | Fiber | Knuckle

An edge:

+ Standard connection
+ Single input and output node
+ Deterministic path
+ Has Wire function
+ Does not contain a sub-graph

A hyperedge:

+ Multiple input and output nodes
+ Non-deterministic path
+ Can contain sub-graphs
+ Has Recombobulation function
+ multiple inputs [may] fire simultaneously for an edge reaction

A fiber:

+ Side-channel connection
+ Connects non-adjacent nodes
+ Independent of main graph path
+ Can influence nodes and knuckles
+ Does not affect stepper traversal
+ Does not contain a sub-graph

A knuckle (aka connection selection):

+ Connection selector
+ Finite set of edges
+ Can use fiber
+ Has api for connection selection







### Directing a Stepper
Assign directions to a stepper, applying a preferred path to follow. For example, when on node `ID1`, follow connections `{4,6}`:

Expand Down
18 changes: 18 additions & 0 deletions docs/future/vector-directional-execitions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Vector Directional Executions

Vector directional executions refer to selecting nodes through directional vectors, such as PI/2. Allowing the developer to build computational graphs through directional relationships.

> In my thoughts, I call this a "ghost edge", where we assume a (default) connection, if the directional vector plots within the radius of another node (a function with an {x,y})


## Simple Example


A node is sitting at the origin (0,0) and has a directional vector of PI/2 (90 degrees). This means that the node is pointing straight up along the positive Y-axis.

For a a higher selection count in a more dense space, we can consider a node at (0,0) with a radius of 5 units. Pi/2 will plot a point nearest a node at (0,5).

This can also be combined with other directional vectors to create more complex selection patterns. For example, using PI/4 (45 degrees) and 3*PI/4 (135 degrees) will select nodes that are diagonally positioned relative to the origin node.

Another example is an arc spread selecting nodes in an connic projection.

2 changes: 1 addition & 1 deletion docs/sonarqube/QUICK_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```bash
# Generate coverage locally
./generate_coverage.sh py312
./scripts/generate_coverage.sh py312

# Or with tox directly
tox -e py312
Expand Down
26 changes: 14 additions & 12 deletions docs/sonarqube/WORKFLOW_COMPLETE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Comparison document including:

- `tox.ini` - Generates coverage.xml
- `sonar-project.properties` - SonarQube/SonarCloud configuration
- `generate_coverage.sh` - Helper script for local testing
- `scripts/generate_coverage.sh` - Helper script for local testing

---

Expand Down Expand Up @@ -216,10 +216,10 @@ Current Project Coverage (as of last run):
The workflows integrate seamlessly with:

```
tox.ini # Already generates coverage.xml
sonar-project.properties # Already configured
generate_coverage.sh # Can test locally
pyproject.toml # Tox dependency already added
tox.ini # Already generates coverage.xml
sonar-project.properties # Already configured
scripts/generate_coverage.sh # Can test locally
pyproject.toml # Tox dependency already added
```

### No Changes Needed To
Expand Down Expand Up @@ -282,7 +282,7 @@ on:
#### 1. Coverage.xml Not Found
```bash
# Verify locally
./generate_coverage.sh py312
./scripts/generate_coverage.sh py312
ls -lh coverage.xml
```

Expand Down Expand Up @@ -360,11 +360,13 @@ Add to `README.md`:
└── WORKFLOW_COMPLETE.md # This file

Project Root:
├── tox.ini # Tox configuration (updated)
├── sonar-project.properties # Sonar configuration (updated)
├── generate_coverage.sh # Coverage helper script
├── SONARQUBE_SETUP.md # Original setup doc (updated)
└── coverage.xml # Generated by tox (not committed)
├── scripts/
│ ├── generate_coverage.sh # Coverage helper script
│ └── update_version.py # Version sync script
├── tox.ini # Tox configuration (updated)
├── sonar-project.properties # Sonar configuration (updated)
├── SONARQUBE_SETUP.md # Original setup doc (updated)
└── coverage.xml # Generated by tox (not committed)
```

---
Expand All @@ -391,7 +393,7 @@ Project Root:
6. ✅ Comparison guide for choosing platform
7. ✅ Troubleshooting guide included
8. ✅ Badge examples provided
9. ✅ Local testing verified with generate_coverage.sh
9. ✅ Local testing verified with scripts/generate_coverage.sh
10. ✅ Works with existing project structure

**Ready to deploy!** Choose your platform and follow the Quick Start Guide above.
73 changes: 73 additions & 0 deletions docs/version-management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Version Management

The version is defined in **`src/hyperway/_version.py`**:

```python
__version__ = "1.0.5"
```

The `pyproject.toml` uses dynamic versioning to read from the `_version.py` file:

```toml
[project]
dynamic = ["version"]

[tool.setuptools.dynamic]
version = {attr = "hyperway._version.__version__"}
```

`sonar-project.properties` is a plain text config file, we use a Python script to sync it:

```bash
python scripts/update_version.py
```

This script:
1. Reads the version from `src/hyperway/_version.py`
2. Updates the `sonar.projectVersion` line in `sonar-project.properties`

## Updating the Version

**To update the version for a new release:**

1. Edit **only** the version in `src/hyperway/_version.py`:
```python
__version__ = "1.0.6"
```

2. (Optional) Run the update script to sync SonarQube config:
```bash
python scripts/update_version.py
```
> **Note:** The GitHub Actions workflow automatically runs this script during release builds, so you don't have to remember to do it manually.

3. Commit the version file:
```bash
git add src/hyperway/_version.py
# Optionally include sonar-project.properties if you ran scripts/update_version.py
git commit -m "Bump version to 1.0.6"
```

### Automated Workflow

The GitHub Actions workflow (`.github/workflows/python-publish.yml`) automatically syncs the version before:
- **Building releases** - Ensures the package has the correct version
- **SonarCloud analysis** - Ensures the analysis reports the correct version

This means even if you forget to run `scripts/update_version.py` locally, the CI/CD pipeline will ensure everything stays in sync.

## Verification

Check that the version is correctly set:

```bash
# Check package version
python -c "import sys; sys.path.insert(0, 'src'); from hyperway import __version__; print(__version__)"

# Check SonarQube version
grep "sonar.projectVersion" sonar-project.properties

# Or run the sync script to verify
python scripts/update_version.py
```

Loading