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
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
groups:
actions:
patterns: ["*"]
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Tests
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
pytest:
runs-on: ubuntu-latest
timeout-minutes: 40
env:
MPLBACKEND: Agg
CI: "true"
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.3.0
with:
enable-cache: true
- name: Run pytest
run: uv run --python 3.13 --group dev pytest -q
1 change: 1 addition & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ jobs:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.3.0
- run: uvx ruff@0.17.1 check
- run: uvx ruff@0.17.1 format --check
8 changes: 8 additions & 0 deletions .github/workflows/readme.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: readme
on:
push:
branches: [main]
pull_request:
jobs:
readme:
uses: NaNIsaac/.github/.github/workflows/readme.yml@main
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ Each folder under `runs/` is one self-contained experiment (config, simulation,

## Install

Uses [uv](https://docs.astral.sh/uv/) and Python 3.13. `torch` is pulled from the PyTorch CUDA 12.8 index.
Uses [uv](https://docs.astral.sh/uv/) and Python 3.13. `torch` is pulled from the PyTorch CUDA 12.8 index (PyPI on macOS).

```bash
git clone https://github.com/NaNIsaac/ReflectoExp.git
cd ReflectoExp
```

Install the dependencies and the `reflecto_exp` package:

<!-- test -->
```bash
uv sync
uv pip install -e . # installs the reflecto_exp package
uv pip install -e .
```

The refnx-based experiments (exp02, exp03) also need `uv pip install refnx`, which is not a declared dependency.
Expand Down
9 changes: 6 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def get_venv_python() -> Path:

return venv_python


def find_exp_folder(exp_num: int, runs_dir_path: Path = Path("./runs")) -> Path:
"""
runs 폴더 내에서 exp{num} 또는 exp0{num}으로 시작하는 폴더를 찾습니다.
Expand Down Expand Up @@ -52,6 +53,7 @@ def find_exp_folder(exp_num: int, runs_dir_path: Path = Path("./runs")) -> Path:

return found_folder


def run_main(target_folder: Path):
"""
찾은 폴더 내부의 main.py를 가상환경 Python으로 실행합니다.
Expand All @@ -72,15 +74,16 @@ def run_main(target_folder: Path):
subprocess.run(
[str(venv_python), "main.py"],
cwd=target_folder, # 작업 디렉토리를 해당 실험 폴더로 변경
check=True
check=True,
)
except subprocess.CalledProcessError as e:
print("-" * 50)
print(f"💥 [Fail] 실행 중 오류 발생 (Exit code: {e.returncode})")


if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Run experiment based on ID')
parser.add_argument('exp_num', type=int, help='Experiment number (e.g., 5)')
parser = argparse.ArgumentParser(description="Run experiment based on ID")
parser.add_argument("exp_num", type=int, help="Experiment number (e.g., 5)")

args = parser.parse_args()

Expand Down
30 changes: 15 additions & 15 deletions notebooks/function_test.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"from reflecto.simulate.simulator import ParamSet, build_structure, compute_refl\n",
"\n",
"wavelen: float = 1.54 # (nm)\n",
"tth_min: float = 0.1 # degree\n",
"tth_min: float = 0.1 # degree\n",
"tth_max: float = 6.0\n",
"q_n: int = 200\n",
"tths: np.ndarray = np.linspace(tth_min, tth_max, q_n)\n",
Expand Down Expand Up @@ -54,7 +54,7 @@
"fig, ax = plt.subplots(1, 1, figsize=(8, 5))\n",
"ax.plot(qs, refl)\n",
"ax.plot(qs, refl2)\n",
"ax.set_yscale('log')\n",
"ax.set_yscale(\"log\")\n",
"\n",
"plt.show()"
]
Expand Down Expand Up @@ -84,7 +84,7 @@
"slds = [f.sld for f in films]\n",
"fig, ax = plt.subplots(1, 1, figsize=(8, 5))\n",
"ax.plot(qs, refl)\n",
"ax.set_yscale('log')"
"ax.set_yscale(\"log\")"
]
},
{
Expand All @@ -101,8 +101,8 @@
"# Manual: 평균 30분 (1800초) ~ 60분 범위\n",
"# Hybrid: 평균 2초 ~ 4초 범위\n",
"means = [1800, 3]\n",
"labels = ['Manual Fitting\\n(Expert)', 'Hybrid Pipeline\\n(Ours)']\n",
"colors = ['#bdbdbd', '#2e7d32'] # 회색 vs 초록색\n",
"labels = [\"Manual Fitting\\n(Expert)\", \"Hybrid Pipeline\\n(Ours)\"]\n",
"colors = [\"#bdbdbd\", \"#2e7d32\"] # 회색 vs 초록색\n",
"\n",
"fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(5, 6))\n",
"fig.subplots_adjust(hspace=0.1) # 위아래 그래프 간격 조절\n",
Expand All @@ -113,32 +113,32 @@
"\n",
"# 축 범위 설정 (Broken Axis 효과)\n",
"ax1.set_ylim(1500, 2000) # 위쪽 그래프: 1500~2000초 구간\n",
"ax2.set_ylim(0, 100) # 아래쪽 그래프: 0~100초 구간\n",
"ax2.set_ylim(0, 100) # 아래쪽 그래프: 0~100초 구간\n",
"\n",
"# 위쪽 그래프의 X축 눈금 제거\n",
"ax1.spines['bottom'].set_visible(False)\n",
"ax2.spines['top'].set_visible(False)\n",
"ax1.spines[\"bottom\"].set_visible(False)\n",
"ax2.spines[\"top\"].set_visible(False)\n",
"ax1.xaxis.tick_top()\n",
"ax1.tick_params(labeltop=False) # 위쪽 라벨 제거\n",
"ax2.xaxis.tick_bottom()\n",
"\n",
"# 빗금(Diagonal lines)으로 잘린 부분 표현\n",
"d = .015\n",
"kwargs = {'transform': ax1.transAxes, 'color': 'k', 'clip_on': False}\n",
"ax1.plot((-d, +d), (-d, +d), **kwargs) # top-left diagonal\n",
"d = 0.015\n",
"kwargs = {\"transform\": ax1.transAxes, \"color\": \"k\", \"clip_on\": False}\n",
"ax1.plot((-d, +d), (-d, +d), **kwargs) # top-left diagonal\n",
"ax1.plot((1 - d, 1 + d), (-d, +d), **kwargs) # top-right diagonal\n",
"\n",
"kwargs.update(transform=ax2.transAxes) # switch to the bottom axes\n",
"ax2.plot((-d, +d), (1 - d, 1 + d), **kwargs) # bottom-left diagonal\n",
"ax2.plot((1 - d, 1 + d), (1 - d, 1 + d), **kwargs) # bottom-right diagonal\n",
"\n",
"# 텍스트 라벨 추가 (가장 중요!)\n",
"ax1.text(0, 1850, \"~ 30 mins\", ha='center', va='bottom', fontsize=14, fontweight='bold')\n",
"ax2.text(1, 10, \"< 3 sec\", ha='center', va='bottom', fontsize=14, fontweight='bold', color='#2e7d32')\n",
"ax1.text(0, 1850, \"~ 30 mins\", ha=\"center\", va=\"bottom\", fontsize=14, fontweight=\"bold\")\n",
"ax2.text(1, 10, \"< 3 sec\", ha=\"center\", va=\"bottom\", fontsize=14, fontweight=\"bold\", color=\"#2e7d32\")\n",
"\n",
"# 제목 및 Y축 라벨\n",
"ax1.set_title(\"Processing Time per Scan\", fontsize=14, fontweight='bold', pad=20)\n",
"fig.text(0.02, 0.5, 'Time (seconds)', va='center', rotation='vertical', fontsize=12)\n",
"ax1.set_title(\"Processing Time per Scan\", fontsize=14, fontweight=\"bold\", pad=20)\n",
"fig.text(0.02, 0.5, \"Time (seconds)\", va=\"center\", rotation=\"vertical\", fontsize=12)\n",
"\n",
"plt.show()"
]
Expand Down
16 changes: 13 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,18 @@ url = "https://download.pytorch.org/whl/cu128"
explicit = true

[tool.uv.sources]
torch = [{ index = "pytorch-cu128" }]
torchvision = [{ index = "pytorch-cu128" }]
torchaudio = [{ index = "pytorch-cu128" }]
torch = [{ index = "pytorch-cu128", marker = "sys_platform != 'darwin'" }]
torchvision = [{ index = "pytorch-cu128", marker = "sys_platform != 'darwin'" }]
torchaudio = [{ index = "pytorch-cu128", marker = "sys_platform != 'darwin'" }]

# GenX lists wxpython for its GUI, which this project never starts. PyPI has no Linux wheel for it,
# so on Linux uv would compile it and fail without GTK headers.
[tool.uv]
override-dependencies = ["wxpython; sys_platform != 'linux'"]

[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["tests"]

[tool.ruff]
line-length = 150
Expand All @@ -110,5 +119,6 @@ combine-as-imports = true

[dependency-groups]
dev = [
"pytest>=9.0.2",
"ruff>=0.14.3",
]
22 changes: 10 additions & 12 deletions runs/exp01_quan_class/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,7 @@ class ParamQuantizer:
q.quantize(param) -> np.ndarray([t_idx, r_idx, s_idx])
"""

def __init__(
self,
thickness_bins: np.ndarray | None = None,
roughness_bins: np.ndarray | None = None,
sld_bins: np.ndarray | None = None
):
def __init__(self, thickness_bins: np.ndarray | None = None, roughness_bins: np.ndarray | None = None, sld_bins: np.ndarray | None = None):
# sensible defaults (units: nm for thickness, Å for roughness, 1e-6 Å^-2 or user convention)
self.thickness_bins = np.linspace(0.0, 200.0, 51) if thickness_bins is None else np.asarray(thickness_bins)
self.roughness_bins = np.linspace(0.0, 10.0, 21) if roughness_bins is None else np.asarray(roughness_bins)
Expand Down Expand Up @@ -103,11 +98,14 @@ def quantize(self, param: ParamSet) -> np.ndarray:
t = float(param.thickness)
r = float(param.roughness)
s = float(param.sld)
return np.array([
self._quantize_array(np.asarray([t]), self.thickness_bins)[0],
self._quantize_array(np.asarray([r]), self.roughness_bins)[0],
self._quantize_array(np.asarray([s]), self.sld_bins)[0],
], dtype=np.int64)
return np.array(
[
self._quantize_array(np.asarray([t]), self.thickness_bins)[0],
self._quantize_array(np.asarray([r]), self.roughness_bins)[0],
self._quantize_array(np.asarray([s]), self.sld_bins)[0],
],
dtype=np.int64,
)

def quantize_multi(self, thickness_arr: np.ndarray, roughness_arr: np.ndarray, sld_arr: np.ndarray) -> np.ndarray:
"""Vectorized quantize for arrays per-layer. Returns shape (n_layers, 3)."""
Expand Down Expand Up @@ -214,7 +212,7 @@ def __getitem__(self, idx):
self._ensure_open()
hf = self._hf

refl = hf["R"][idx] # (q_len,) or (q_len, ...)
refl = hf["R"][idx] # (q_len,) or (q_len, ...)
thick = hf["thickness"][idx] # (n_layers,)
rough = hf["roughness"][idx]
sld = hf["sld"][idx]
Expand Down
Loading
Loading