Matr1x Matr1x
  • User Guide
  • Reference
  • Changelog

Skills

A skill is a package of structured files that teaches an AI coding agent how to work with a specific tool or framework. This project ships multiple skills — use the switcher below to browse each one. Install a skill in your agent and it will be able to run commands, edit configuration, write content, and troubleshoot problems without step-by-step guidance from you.

Any agent — install all with npx:

npx skills add https://andythomas.github.io/matr1x/

CLI — install all skills in a project:

great-docs skill install matr1x-measurements

Codex / OpenCode

Tell the agent to fetch these skill files:
https://andythomas.github.io/matr1x/.well-known/agent-skills/matr1x-install/SKILL.md
https://andythomas.github.io/matr1x/.well-known/agent-skills/matr1x-migration/SKILL.md
https://andythomas.github.io/matr1x/.well-known/agent-skills/dependabot/SKILL.md

Or browse the skill files below.

SKILL.md

---
name: matr1x-install
description: >
  Use to install and setup the matr1x package.
license: GNU General Public License v3 or later (GPLv3+)
compatibility: Requires Python >=3.10.
---

# Matr1x

Python tools for data recording, instrument control and visualization.

## Installation

1. On a Linux system, install the required qt6 library via `sudo apt install qt6-base-dev`
2. Follow the [uv installation](https://docs.astral.sh/uv/getting-started/installation/) procedure if `uv` is not already installed.
3. Clone the gitub repository `git clone https://github.com/andythomas/matr1x.git` in the desired location (called pkg-root from now on).
4. Execute `uv sync` in pkg-root.
5. Activate the virtual environment in pkg-root according to the OS, i.e. `source .venv/bin/activate` on Linux/MacOS, `.\.venv\Scripts\activate.bat` on Windows.
6. Ensure that `~/.matr1x.toml` exists and includes the following content. If the file does not exist, create it with this content. If it exists, add this content only if it is not already present:

```toml
[matr1x.install]
controlguis = ["control-dummy"]
```

7. If not, either add the content to the existing file or generate the file with the content.
8. Run the disktop integration in the pkg-root folder via `matrix-di` 
9. Launch `matrix-script` in the pkg-root folder. This will take a minute or two, because the editor-assets will be downloaded.

In case there are any errors in the last two steps, please inspect the newest files in `~/logs/` for the underlying cause.

SKILL LAYOUT

migration/
├── SKILL.md
└── references/
    ├── migration-v8.3.md
    ├── migration-v8.4.md
    ├── migration-v8.5i.md
    ├── migration-v8.5ii.md
    ├── migration-v8.6i.md
    └── migration-v8.6ii.md

SKILL.md

---
name: matr1x-migration
description: >
  Perform the required steps for an update of the matr1x package.
license: GNU General Public License v3 or later (GPLv3+)
compatibility: Requires Python >=3.10.
---

Please refer to the migration files in the reference subdirectory and perform the required steps. Start in ascending order with the lowest version number.

references/migration-v8.3.md

# Upgrade to v8.3

1. Check if there is a `~/.matr1x.toml` file 
2. If yes, check if the is a `[matr1x.install]` section.
3. If yes, delete the `options` and `pipoptions` keys (if present).

references/migration-v8.4.md

# Upgrade to v8.4

1. Check if there is a `~/.matr1x.toml` file 
2. If yes, check if the is a `[matr1x.scripts.matrix-script]` section.
3. If yes, check if there are entries for the `duplicate_output_to_logfile` and/or `print_to_comment` keys
4. Move the existing entries to the `[matr1x]` section of the file. If that section does not exist, create it.

references/migration-v8.5i.md

# Upgrade to v8.5

## Enforce unique systems classes

1. Please inspect the files in the `systems` subdirectory of the repository.
2. In every file there should be an import of the `System` class.
3. In every file there should be one and only one `system =` line in scope of the module.
4. The right side of this assignment should be a subclass of the imported `System` class. If it only instantiates `System`, e.g. `system = System()`, subclass `System` as in the following example.

```python
class MeasSystem(System):
    """Measurement system for dummy feature demonstration."""
```

A good name might be the filename of the module. 
If this name starts with `system_` strip this part from the class name. 

5. CLASS NAME UNIQUENESS CHECK (applies to all files, independent of steps 1-4):
   a. Search ALL .py files in this directory for any shared subclass name
      (especially `MeasSystem`).
   b. Every file must have a UNIQUE class name — no two files may use the same one.
   c. Rename all occurrences. Derive names from the filename (minus `system_` prefix)
      or from the hardware described in dcdata.
   d. Files already using unique names do NOT need changes.

6. Make sure you did not touch the comments or docstrings. The only exception is the replacement of an referral of an old name to the new name of the class.

## Control Command Migration

This skill migrates legacy patterns in matr1x files:

**`cmds` dict entries** — raw lists or `Command.from_deprecated_list()` calls → `Command`, `Get`, or `Set` instantiation

### Legacy formats to look for

**Raw list values** inside a dict:
```python
cmds = {
    ":key": [dtype, setfunc, setargs, getfunc, getargs],  # length 5
    ":key": [dtype, setfunc, setargs, getfunc, getargs, polling_cmd],  # length 6
}
```

### Conversion rules

Apply these rules to each entry (prefer `Get`/`Set` over `Command` when possible):

| setfunc | getfunc | Use |
|---------|---------|-----|
| not `None` | not `None` | `Command(dtype, setfunc, getfunc, ...)` |
| `None` | not `None` | `Get(dtype, getfunc, ...)` |
| not `None` | `None` | `Set(dtype, setfunc, ...)` |

Optional keyword arguments — **only include if non-empty / not `None`**:
- `setargs` — omit if `None`, `()`, or `[]`
- `getargs` — omit if `None`, `()`, or `[]`
- `polling_cmd` — omit if `None`

#### Before / After example

**Before:**
```python
from matr1x.util import Command

cmds = {
    ":temp": [float, "setTemp", (), "getTemp", ()],
    ":field": [float, None, (), "getField", ()],
    ":press": [float, "setPress", (1,), None, (), ":pressrd"],
    ":v2": [float, ("dummy", "p2"), None, "V2", None, ":v2rd"],
    ":info": Command.from_deprecated_list([str, None, None, "getInfo", None]),
}
```

**After:**
```python
from matr1x.util import Command, Get, Set

cmds = {
    ":temp": Command(float, "setTemp", "getTemp"),
    ":field": Get(float, "getField"),
    ":press": Set(float, "setPress", setargs=(1,), polling_cmd=":pressrd"),
    ":v2": Command(float, ("dummy", "p2"), "V2", polling_cmd=":v2rd"),
    ":info": Get(str, "getInfo"),
}
```

---

### Steps to perform the migration

1. **Identify files** — scan for files in the control subdirectories containing `cmds = {`,`common_commands = {`, `cmd_list = {` or similar patterns with list values.

2. **Read each file** fully before making changes.

3. **Apply changes** convert the dict entries that are still a list 

4. **Fix imports** — ensure the file imports exactly the classes it uses:
   - Add `Get` and/or `Set` and/or `Command` to the import if they are now used.
   - Remove `Get`, `Set`, or `Command` from the import if they are no longer used.
   - The import comes from `matr1x.util`.

references/migration-v8.5ii.md

# Upgrade to v8.5

## Move from System Instance to System Class

Define system setup on exactly one local `System` subclass. `System.from_file` discovers and
instantiates that class, so no module-level `system` variable is required.
Initialized `system` and `sys` exports remain temporarily supported with a deprecation warning.

## Migration

1. Read the whole system file.
2. There must be exactly one class defined in the system file that is a subclass of `System`.
3. If there is no class and `System` or a subclass of it is only instantiated (e.g. `MySystem = System()`), create a subclass with an appropriate name. A good name could be the instance name (e.g. `MySystem`). This class is called "SystemClass" from here on.
4. If no `__init__` exists for the SystemClass, add one and call `super().__init__()` first. Please double-check that no `__init__` exists before adding one.
5. Now identify module-level mutations of `system`: `add_dev`, `add_param`, as well as `dcdata` assignments, and `load_config`. These instance mutation need to be moved into the SystemClass, which at least requires a change of `system.` to `self.`.
6. Preserve the imports, it is not required to add or remove imports.
7. Preserve comments as source: move each comment or section heading with the adjacent setup block into `__init__`. Keep its wording unless changing `system` to `self` is needed for accuracy; do not drop instructional comments merely because their code moves.
8. Preserve order: configuration and metadata must be initialized before any setup that relies on them, and device/parameter registrations should retain their former order.
9. Now, inspect the remaining items in the module for any items that need to be moved into the SystemClass. In particular, this includes functions that use the former `system` instance and need to become methods of SystemClass, which again at least requires a change of `system.` to `self.`.

## Examples

Before:

```python
from matr1x.devices.dummy import dummy
from matr1x.system import System


class Example(System):
    pass


system = Example()
system.dcdata["source"] = "example"
system.add_dev("device", dummy, args=("TCPIP::localhost::10007::SOCKET",))
system.add_param("voltage", "V", getter=["device", "voltage"])
```

After:

```python
from matr1x.devices.dummy import dummy
from matr1x.system import System


class Example(System):
    def __init__(self):
        super().__init__()
        self.dcdata["source"] = "example"
        self.add_dev("device", dummy, args=("TCPIP::localhost::10007::SOCKET",))
        self.add_param("voltage", "V", getter=["device", "voltage"])
```

## Import check

Use a focused import check after the change for every file:

```python
from pathlib import Path
from matr1x.error_handling import Success
from matr1x.system import System

result = System.from_file(Path("path/to/system_file.py"))
assert isinstance(result, Success)
```

## Steps to Perform the Migration

1. **Identify files** — scan for files in the system subdirectories. Most likely the filenames have a `system_`-prefix.

2. **Read each file** fully before making changes.

3. **Apply changes** as described in the "Migration" step by step instructions.

4. **Validate changes** using the following two checks:
   - `ty check`: This should catch any remaining items that need to be moved in the SystemClass.
   - Import check (as described above): This should catch the additional errors.

5. **Perform additonal changes** as required by the previous step and repeat validation until all errors are resolved.

references/migration-v8.6i.md

# Upgrade to v8.6

## Validate system configuration defaults

`System.load_config()` needs to be switched from `BaseModel` classes to
`SystemConfigModel` to enable validation of their default values. For migration
perform the following steps:

1. Find every Pydantic model passed to `load_config(...)` in a system file.
   Do not change unrelated `BaseModel` classes.
2. Replace `BaseModel` with `SystemConfigModel` for each system configuration model:

```python
# Before
from pydantic import BaseModel, Field


class DeviceConfig(BaseModel):
    mode: Literal["CURR", "VOLT"] = Field("VOLT")
```

```python
# After
from pydantic import Field

from matr1x.models import SystemConfigModel


class DeviceConfig(SystemConfigModel):
    mode: Literal["CURR", "VOLT"] = Field("VOLT")
```

3. Preserve other Pydantic model configuration options. Pydantic merges them with the
   inherited `validate_default=True` setting:

```python
class DeviceConfig(SystemConfigModel):
    model_config = ConfigDict(extra="forbid")
```

4. Validate the model after migration. Instantiate it without arguments when it has no
   required fields; otherwise supply representative values for the required fields. Defaults
   must satisfy the same `Literal`, numeric, string, and custom constraints as configured
   values. Fix an invalid default or make that field required with `Field(...)`; do not
   disable default validation.
5. If a configuration model already uses a custom Pydantic base class, do not replace it
   blindly. Make that base inherit `SystemConfigModel`, or add
   `ConfigDict(validate_default=True)` while preserving its existing behavior.

references/migration-v8.6ii.md

# Upgrade to v8.6

## Give Control GUI Systems Unique Names

Every `System` used by a `GuiDict` in the same `ControlWindow` must have a
unique name. The name is used to expose the system through the merged system,
for example as `window.S.temperature`.

The name must:

- be unique among all `GuiDict` systems in the `ControlWindow`; and
- be a valid Python identifier according to `str.isidentifier()`; and
- not be a Python keyword such as `class` or `return`.

For example, `temperature`, `magnet_2`, and `cryostat` are valid names.
Names containing spaces, hyphens, or dots, such as `temperature control`,
`magnet-2`, or `lab.cryostat`, are not valid.

## Migration

1. Find the `GuiDict` classes used by each control GUI.
2. Check whether each class explicitly defines an `S` attribute.
3. If a `GuiDict` does not define `S`, no change is required. `GuiDict`
   automatically creates an empty system named after the `GuiDict` class.
4. If a `GuiDict` explicitly defines `S`, ensure that its `System` has a name
   that meets both requirements above.
5. Check all `GuiDict` systems passed to the same `ControlWindow` together and
   resolve any duplicate names.

Prefer supplying the name when the system is created:

```python
from matr1x.control import GuiDict
from matr1x.system import System


class TemperatureGui(GuiDict):
    S = System(name="temperature")
```

If a custom `System` subclass does not accept `name` as an initializer
argument, set the name explicitly after creating it:

```python
class TemperatureGui(GuiDict):
    S = TemperatureSystem()
    S.name = "temperature"
```

A separate `System` subclass is not required merely to give each control GUI
system a unique name.

## Validation

Start each migrated control GUI and verify that it opens without a missing,
invalid, or duplicate system-name error. If the merged system is accessed
directly, also verify that every subsystem is available under its configured
name, for example:

```python
control_window.S.temperature
```

SKILL.md

---
name: dependabot
description: >
  Address the dependabot alerts.
license: GNU General Public License v3 or later (GPLv3+)
compatibility: Requires Python >=3.10.
---

# Dependabot

Take the dependabot alerts and address them.

## Address alerts

1. Read the [dependabot alerts](https://github.com/andythomas/IFW_software/security/dependabot) for this repository.
2. Follow the links to the affected packages one by one.
3. For every affected package, read the page.
4. On top of the page there will be a recommendation for the version of the package to upgrade to.
5. Look into the local `pyproject.toml` file in the project's root directory.
6. If the affected package is listed there, upgrade it to the recommended version.
7. If the affected package is not listed there, add it to the `tool.uv` override-dependencies key in `pyproject.toml`.

## Example

```toml
[tool.uv]
override-dependencies = [
    "urllib3>=2.7.0", # CVE-2026-44432, CVE-2026-44431
    "idna>=3.15", # CVE-2026-45409
    "starlette>=1.0.1", # CVE-2026-48710
    "fastapi>=0.136.1", # forced by CVE-2026-48710
]
```

## Steps to perform the migration

1. **address alerts** - perform the steps outlined above.

2. **update repository** - run `uv sync --all-extras --all-groups` to update the repository and lock file.

3. **query user** - Ask the user to perform the tests, do not perform the tests automatically.

Site created with Great Docs.