Python API

LogQbit 的核心入口是 LogFolder。一个 LogFolder 对应磁盘上的一个实验记录目录, 目录里通常包含三类文件:

  • data.feather: 表格数据。
  • metadata.json: 与 LogBrowser 交互的轻量元数据。
  • const.yaml: 实验常量和配置参数。

LogFolder

LogFolder 负责创建记录目录、追加数据行、管理元数据和常量,并在需要时把缓冲数据写入磁盘。

创建新记录

from logqbit.logfolder import LogFolder

with LogFolder.new("./runs", title="cooldown") as log:
    log.add_row(time=0.0, temperature=300.0)
    log.add_row(time=1.0, temperature=295.2)

    log.add_const(operator="alice", sample="device-a")
    log.meta.plot_axes = ["time"]

LogFolder.new(parent) 会在 parent 下创建下一个数字目录,例如 0/1/2/。 推荐使用 with 语句;退出代码块时会自动 close(),确保末段数据完成写入并关闭后台线程。

打开已有记录

log = LogFolder("./runs/0", create=False)
print(log.df)
log.close()

如果路径不存在并且 create=False,会抛出 FileNotFoundError

追加数据

标量参数会追加一行:

log.add_row(x=1.0, y=2.0)

列表、数组或其他有长度的参数会一次追加多行:

log.add_row(
    x=[0.0, 1.0, 2.0],
    y=[1.2, 1.8, 2.1],
)

多行写入会交给 pandas 检查列长度是否一致。

读取和保存

df = log.df
log.flush()
log.close()
  • log.df 返回当前完整 dataframe,包括还没有写入磁盘的缓冲行。
  • log.flush() 立即同步写入 data.feather,调用会阻塞直到写入完成。
  • log.close() 会先 flush,再停止后台 autosave 线程。它是幂等的,可以重复调用。

如果只需要读取已经写好的数据文件,最简单的方式是直接用 pandas:

import pandas as pd

df = pd.read_feather("./runs/0/data.feather")

这适合做只读分析、导出脚本或不需要创建 LogFolder 对象的场景。

普通脚本自然退出时,LogQbit 也会通过 atexit 尝试关闭仍然活跃的 LogFolder。 对象被垃圾回收时还有 weakref.finalize 兜底。不过需要强保证时,仍然推荐使用 with 或显式调用 close()

常量和元数据

log.add_const(temperature="300 K", bias=0.1)
log.add_const_to_head(run_group="calibration")

log.const["instrument/name"] = "scope-a"
log.meta.star = True
log.meta.plot_axes = ["time"]
  • log.constlog.reg 的别名,类型是 Registry,对应 const.yaml
  • add_const() 会把键值追加到 YAML 文件并立即保存。
  • add_const_to_head() 会把键值插入到 YAML 顶部,适合放最重要的运行参数。
  • log.meta 对应 metadata.json,主要用于和 LogBrowser 交互,例如标题、收藏、 回收站状态、绘图轴等 GUI 展示相关的轻量状态。

Registry

Registry 是基于 YAML 文件的轻量键值注册表。它支持使用 / 分隔路径访问嵌套字段:

from logqbit.registry import Registry

reg = Registry("const.yaml")
reg["device/name"] = "sample-a"
print(reg["device/name"])

直接通过 get()set()[] 操作时,Registry 会在读写前后和文件同步。 如果要做一批本地修改,可以操作 root,最后手动保存:

reg.root["operator"] = "alice"
reg.root["temperature"] = "300 K"
reg.save()

reload() 会在文件变化后重新读取磁盘内容。本地未保存的修改会被新的磁盘内容覆盖。 undo()redo() 可以撤销或重做最近的保存快照。

DataFrameBuffer

DataFrameBufferLogFolder 内部使用的低层组件,负责把追加进来的 dataframe 片段缓冲在内存里,并后台 autosave 到 feather 文件。普通用户通常不需要直接使用它; 优先使用 LogFolder.add_row()LogFolder.flush()LogFolder.close()

如果需要单独使用 dataframe 缓冲,可以这样写:

import pandas as pd

from logqbit.dataframe import DataFrameBuffer

buffer = DataFrameBuffer("data.feather")
buffer.add_one_row({"x": 1.0, "y": 2.0})
buffer.add_multi_rows(pd.DataFrame({"x": [2.0, 3.0], "y": [4.0, 6.0]}))
buffer.flush()
buffer.close()

后台线程的状态机很小:等待数据变 dirty,等待当前 autosave interval 合并连续追加, 如果仍然 dirty 就写盘。flush() 会跳过等待,在调用线程同步写入。

API Reference

LogFolder

LogFolder(
    path: str | Path,
    title: str = "untitled",
    create: bool = True,
)

Directory-backed experiment log with data, metadata, and constants.

A LogFolder manages three files under one directory:

  • data.feather for tabular records
  • metadata.json for lightweight metadata
  • const.yaml for constant parameters and configuration

const property

const: Registry

Alias for reg. Access the const.yaml registry.

df property

df: DataFrame

Get the full dataframe, flushing all data rows.

df_path property

df_path: Path

Path to the backing data.feather file.

add_const

add_const(meta: dict = None, /, **kwargs)

Append constant values to const.yaml and save immediately.

add_const_to_head

add_const_to_head(meta: dict = None, /, **kwargs)

Insert constant values at the top of const.yaml and save.

add_row

add_row(**kwargs) -> None

Add a new row or multiple rows to the dataframe. Supports both scalar and vector input. For vector input, pandas will check length consistency.

capture

capture(
    func: Callable[[float], dict[str, float | list[float]]],
    axes: list[float | list[float]]
    | dict[str, float | list[float]],
)

Run a parameter sweep and append returned rows to this log.

close

close() -> None

Flush pending data and stop the background autosave thread.

flush

flush() -> None

Flash the pending data immediately, block until done.

new classmethod

new(
    parent_path: Path, title: str = "untitled"
) -> LogFolder

Create the next numeric log directory under parent_path.

LogMetadata

LogMetadata(
    path: str | Path,
    title: str = "untitled",
    create: bool = True,
)

JSON-backed metadata helper for a log folder.

Common fields such as title, star, and plot_axes are exposed as descriptors and synchronized to metadata.json on assignment.

Registry

Registry(
    path: str | Path,
    create: bool = True,
    history_size: int = 2,
)

A simple registry based on YAML file.

get/set values synchronized with the file unless explicitly calling get_local/set_local.

reg = Registry('config.yaml')
reg['new_key/sub_key'] = 123  # synced with file

Operations on root and subitems are local and needs to be saved manually. e.g.

reg.reload()
reg.root['another_key'] = 456  # local change, not synced until save.
reg.save()

Local changes will be discarded when reload().

NOTE: Local operations is useful for batch update without frequent file I/O.

print_local

print_local()

Print the local content to stdout.

reload

reload()

Reloads the file if it has changed since the last load.

DataFrameBuffer

DataFrameBuffer(
    path: str | Path, autosave_interval: float = 0.2
)

Buffer appended dataframe rows and persist them to a feather file.

The background thread has a small state machine: wait until data becomes dirty, wait the current autosave interval to batch nearby appends, then write if the buffer is still dirty. flush() skips that delay and writes synchronously on the caller's thread.

close

close() -> None

Flush pending rows and stop the autosave thread.

flush

flush() -> pd.DataFrame

Flush pending rows immediately, blocking until the save finishes.