mirror of
https://github.com/blackboxprogramming/universal-computer.git
synced 2026-03-20 04:51:13 -05:00
Add CI pipeline, 15-test suite, enhanced README
- GitHub Actions CI: Python 3.10/3.11/3.12, pytest, smoke tests - Tests: tape representation, incrementer, even/odd, halt detection, step limits, missing transitions, stay direction - README with badges, theory section, machine creation docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> RoadChain-SHA2048: d4d129eaf6941a81 RoadChain-Identity: alexa@sovereign RoadChain-Full: d4d129eaf6941a815dd5b84bad71115b294f7425d4f184f52ff6770ec641cad192fd361ccea269edefdb8330de6c61b50063d8c103e231bd138fd73fb956d152054336332321ec9e6d1dffa3f3d81cb6295d46d2722116bb9a8239a18d21c6d6d3145e98ff8aff3d5026645db418c5fd0a049f109f8b9e2ee29518a927aade77f315e411b2d0cdfec578f764dcec90da4a0686a39ad1ebcb0bdb9511488efb7f3e14155fe77a550d3b7d07838006a43a3847f239fb2487ca55b22c019ce4ffabc42d0e8e617180ca1aa1ace8fdfcc3ca0f0f24dc7e4be8ed510efcb8f18745217907aa3d5ee94c33b18ff21638ff0b5c0936e5a090f8ad50a1c31ec93787b9fd
This commit is contained in:
34
.github/workflows/ci.yml
vendored
Normal file
34
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test (Python ${{ matrix.python-version }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install pytest pytest-cov
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: pytest tests/ -v --cov=utm --cov-report=term-missing
|
||||||
|
|
||||||
|
- name: Smoke test — run included machines
|
||||||
|
run: |
|
||||||
|
python utm.py machines/incrementer.json --tape "1101"
|
||||||
|
python utm.py machines/even_odd.json --tape "1111"
|
||||||
83
README.md
83
README.md
@@ -1,56 +1,67 @@
|
|||||||
> ⚗️ **Research Repository**
|
|
||||||
>
|
|
||||||
> This is an experimental/research repository. Code here is exploratory and not production-ready.
|
|
||||||
> For production systems, see [BlackRoad-OS](https://github.com/BlackRoad-OS).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Universal Computer
|
# Universal Computer
|
||||||
|
|
||||||
This repository contains an implementation of a **universal Turing machine** in Python. A universal Turing machine is a theoretical device capable of simulating any other Turing machine. In other words, it can compute anything that is computable. The implementation here is simple and educational; it demonstrates the principles of universality and emulation in a compact form.
|
[](https://github.com/blackboxprogramming/universal-computer/actions/workflows/ci.yml)
|
||||||
|
[](https://python.org)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
## Overview
|
A universal Turing machine simulator in Python. Demonstrates the foundational concept of computability: a single machine that can simulate any other Turing machine.
|
||||||
|
|
||||||
The core of the project is a Turing machine simulator that reads a description of another machine and an input tape, then executes that machine's transition function step by step. The simulator supports tapes of unbounded length in both directions and maintains a set of states, including a halting state. The universal machine itself accepts programs encoded as tables of transitions.
|
## How It Works
|
||||||
|
|
||||||
### Features
|
A Turing machine has a tape (infinite in both directions), a read/write head, a set of states, and a transition function. Given a state and the symbol under the head, the machine writes a new symbol, moves left/right/stay, and transitions to a new state. It halts when it reaches the halt state.
|
||||||
|
|
||||||
- **Tape representation:** The tape is implemented as a Python dictionary mapping integer positions to symbols. Positions not present in the dictionary are assumed to hold a blank symbol (`'_'`).
|
This implementation uses:
|
||||||
- **Transition function:** Each transition is a mapping from `(current_state, current_symbol)` to `(next_state, write_symbol, move_direction)`, where `move_direction` is `'L'`, `'R'`, or `'S'` (stay).
|
- **Dictionary-based tape** -- positions map to symbols, missing positions are blank
|
||||||
- **Machine description format:** Machine descriptions are loaded from JSON files. A description includes the set of states, the input alphabet, the blank symbol, the transition function, the start state, and the halting state.
|
- **JSON machine descriptions** -- portable, human-readable definitions
|
||||||
- **Simulation:** The simulator runs the machine until it reaches the halting state or exceeds a configurable step limit. It yields the final tape contents and the number of steps executed.
|
- **Configurable step limit** -- prevents infinite loops
|
||||||
|
|
||||||
### Running the simulator
|
## Usage
|
||||||
|
|
||||||
To use the universal Turing machine, first prepare a JSON file describing the machine you want to simulate (see `machines/` for examples), then run:
|
```bash
|
||||||
|
# Increment binary number: 1101 (13) -> 1110 (14)
|
||||||
```
|
|
||||||
python3 utm.py machines/your_machine.json --tape "your input tape here"
|
|
||||||
```
|
|
||||||
|
|
||||||
For example, to run a binary incrementer:
|
|
||||||
|
|
||||||
```
|
|
||||||
python3 utm.py machines/incrementer.json --tape "1101"
|
python3 utm.py machines/incrementer.json --tape "1101"
|
||||||
|
|
||||||
|
# Check parity
|
||||||
|
python3 utm.py machines/even_odd.json --tape "1111"
|
||||||
```
|
```
|
||||||
|
|
||||||
This will increment the binary number `1101` (13) to `1110` (14).
|
## Included Machines
|
||||||
|
|
||||||
## Directory structure
|
| Machine | File | Description |
|
||||||
|
|---------|------|-------------|
|
||||||
|
| Binary Incrementer | `incrementer.json` | Adds 1 to a binary number |
|
||||||
|
| Even/Odd | `even_odd.json` | Determines parity of a unary number |
|
||||||
|
|
||||||
- `utm.py` – the universal Turing machine simulator.
|
## Creating Your Own Machine
|
||||||
- `machines/` – sample machine descriptions in JSON format.
|
|
||||||
- `README.md` – this file.
|
|
||||||
|
|
||||||
## Sample machines
|
```json
|
||||||
|
{
|
||||||
|
"states": ["q0", "q1", "halt"],
|
||||||
|
"alphabet": ["0", "1"],
|
||||||
|
"blank": "_",
|
||||||
|
"transitions": {
|
||||||
|
"q0:0": ["q0", "0", "R"],
|
||||||
|
"q0:1": ["q1", "1", "R"],
|
||||||
|
"q0:_": ["halt", "_", "S"]
|
||||||
|
},
|
||||||
|
"start": "q0",
|
||||||
|
"halt": "halt"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
The repository includes a few sample machine descriptions:
|
Each transition key is `"state:symbol"` mapping to `[next_state, write_symbol, direction]` where direction is `L` (left), `R` (right), or `S` (stay).
|
||||||
|
|
||||||
- `incrementer.json` – a machine that increments a binary number.
|
## Development
|
||||||
- `even_odd.json` – a machine that decides whether a unary number has an even or odd number of symbols.
|
|
||||||
|
|
||||||
Feel free to add more machines to the `machines/` directory to explore the power of Turing machines!
|
```bash
|
||||||
|
pip install pytest
|
||||||
|
pytest tests/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theory
|
||||||
|
|
||||||
|
Alan Turing proved in 1936 that a universal Turing machine can compute anything that any Turing machine can compute. Every computer is a physical realization of this idea.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is released under the MIT License. See `LICENSE` for details.
|
Proprietary -- BlackRoad OS, Inc.
|
||||||
|
|||||||
BIN
tests/__pycache__/test_utm.cpython-314-pytest-9.0.2.pyc
Normal file
BIN
tests/__pycache__/test_utm.cpython-314-pytest-9.0.2.pyc
Normal file
Binary file not shown.
150
tests/test_utm.py
Normal file
150
tests/test_utm.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
"""Tests for the Universal Turing Machine simulator."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
|
from utm import TuringMachine, parse_tape, tape_to_string
|
||||||
|
|
||||||
|
MACHINES_DIR = os.path.join(os.path.dirname(__file__), '..', 'machines')
|
||||||
|
|
||||||
|
|
||||||
|
def load_machine(name: str) -> TuringMachine:
|
||||||
|
path = os.path.join(MACHINES_DIR, name)
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
return TuringMachine(json.load(f))
|
||||||
|
|
||||||
|
|
||||||
|
# ── Tape representation ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class TestTape:
|
||||||
|
def test_parse_tape_basic(self):
|
||||||
|
tape = parse_tape("1101", "_")
|
||||||
|
assert tape == {0: "1", 1: "1", 2: "0", 3: "1"}
|
||||||
|
|
||||||
|
def test_parse_tape_empty(self):
|
||||||
|
tape = parse_tape("", "_")
|
||||||
|
assert tape == {}
|
||||||
|
|
||||||
|
def test_parse_tape_with_blanks(self):
|
||||||
|
tape = parse_tape("1_0", "_")
|
||||||
|
assert tape == {0: "1", 2: "0"}
|
||||||
|
|
||||||
|
def test_tape_to_string(self):
|
||||||
|
tape = {0: "1", 1: "1", 2: "1", 3: "0"}
|
||||||
|
assert tape_to_string(tape, "_") == "1110"
|
||||||
|
|
||||||
|
def test_tape_to_string_empty(self):
|
||||||
|
assert tape_to_string({}, "_") == ""
|
||||||
|
|
||||||
|
def test_tape_to_string_with_gaps(self):
|
||||||
|
tape = {0: "1", 2: "1"}
|
||||||
|
result = tape_to_string(tape, "_")
|
||||||
|
assert result == "1_1"
|
||||||
|
|
||||||
|
|
||||||
|
# ── Incrementer machine ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class TestIncrementer:
|
||||||
|
def test_increment_unary_3(self):
|
||||||
|
"""111 (unary 3) should become 1111 (unary 4)."""
|
||||||
|
tm = load_machine("incrementer.json")
|
||||||
|
tape = parse_tape("111", tm.blank)
|
||||||
|
final_tape, steps, state = tm.run(tape)
|
||||||
|
result = tape_to_string(final_tape, tm.blank)
|
||||||
|
assert result == "1111"
|
||||||
|
assert state == tm.halt
|
||||||
|
|
||||||
|
def test_increment_unary_1(self):
|
||||||
|
"""1 (unary 1) should become 11 (unary 2)."""
|
||||||
|
tm = load_machine("incrementer.json")
|
||||||
|
tape = parse_tape("1", tm.blank)
|
||||||
|
final_tape, steps, state = tm.run(tape)
|
||||||
|
result = tape_to_string(final_tape, tm.blank)
|
||||||
|
assert result == "11"
|
||||||
|
|
||||||
|
def test_increment_empty(self):
|
||||||
|
"""Empty tape should become 1 (unary 1)."""
|
||||||
|
tm = load_machine("incrementer.json")
|
||||||
|
tape = parse_tape("", tm.blank)
|
||||||
|
final_tape, steps, state = tm.run(tape)
|
||||||
|
result = tape_to_string(final_tape, tm.blank)
|
||||||
|
assert result == "1"
|
||||||
|
|
||||||
|
|
||||||
|
# ── Even/odd machine ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class TestEvenOdd:
|
||||||
|
def test_even_length(self):
|
||||||
|
tm = load_machine("even_odd.json")
|
||||||
|
tape = parse_tape("1111", tm.blank)
|
||||||
|
final_tape, steps, state = tm.run(tape)
|
||||||
|
assert state == tm.halt
|
||||||
|
|
||||||
|
def test_odd_length(self):
|
||||||
|
tm = load_machine("even_odd.json")
|
||||||
|
tape = parse_tape("111", tm.blank)
|
||||||
|
final_tape, steps, state = tm.run(tape)
|
||||||
|
assert state == tm.halt
|
||||||
|
|
||||||
|
|
||||||
|
# ── Machine behavior ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class TestMachineBehavior:
|
||||||
|
def test_halts_within_step_limit(self):
|
||||||
|
tm = load_machine("incrementer.json")
|
||||||
|
tape = parse_tape("1", tm.blank)
|
||||||
|
_, steps, _ = tm.run(tape, max_steps=100)
|
||||||
|
assert steps < 100
|
||||||
|
|
||||||
|
def test_step_limit_prevents_infinite_loop(self):
|
||||||
|
"""A machine that never halts should stop at max_steps."""
|
||||||
|
desc = {
|
||||||
|
"states": ["q0"],
|
||||||
|
"alphabet": ["0", "1"],
|
||||||
|
"blank": "_",
|
||||||
|
"transitions": {"q0:0": ["q0", "1", "R"], "q0:1": ["q0", "0", "R"], "q0:_": ["q0", "0", "R"]},
|
||||||
|
"start": "q0",
|
||||||
|
"halt": "halt"
|
||||||
|
}
|
||||||
|
tm = TuringMachine(desc)
|
||||||
|
tape = parse_tape("0", tm.blank)
|
||||||
|
_, steps, state = tm.run(tape, max_steps=50)
|
||||||
|
assert steps == 50
|
||||||
|
assert state != tm.halt
|
||||||
|
|
||||||
|
def test_missing_transition_stops(self):
|
||||||
|
"""Machine should stop when no transition exists."""
|
||||||
|
desc = {
|
||||||
|
"states": ["q0", "halt"],
|
||||||
|
"alphabet": ["a"],
|
||||||
|
"blank": "_",
|
||||||
|
"transitions": {},
|
||||||
|
"start": "q0",
|
||||||
|
"halt": "halt"
|
||||||
|
}
|
||||||
|
tm = TuringMachine(desc)
|
||||||
|
tape = parse_tape("a", tm.blank)
|
||||||
|
_, steps, state = tm.run(tape)
|
||||||
|
assert steps == 0
|
||||||
|
assert state == "q0"
|
||||||
|
|
||||||
|
def test_stay_direction(self):
|
||||||
|
"""S direction should not move the head."""
|
||||||
|
desc = {
|
||||||
|
"states": ["q0", "halt"],
|
||||||
|
"alphabet": ["a"],
|
||||||
|
"blank": "_",
|
||||||
|
"transitions": {"q0:a": ["halt", "b", "S"]},
|
||||||
|
"start": "q0",
|
||||||
|
"halt": "halt"
|
||||||
|
}
|
||||||
|
tm = TuringMachine(desc)
|
||||||
|
tape = parse_tape("a", "_")
|
||||||
|
final_tape, steps, state = tm.run(tape)
|
||||||
|
assert state == "halt"
|
||||||
|
assert final_tape[0] == "b"
|
||||||
|
assert steps == 1
|
||||||
Reference in New Issue
Block a user