Files
PSO/tests/test_public_api.py
jung-geun 813433000a feat: modernize PSO and add convergence research
Migrate the package and examples to the tensor-native PyTorch implementation, add benchmark evidence, and add the guarded post-training convergence protocol with TensorBoard progress monitoring and hash-verified recovery.

Constraint: Preserve one-shot official-test sealing and auditable research artifacts

Rejected: Commit local .omc runs and downloaded datasets | multi-gigabyte runtime state is machine-local

Confidence: high

Scope-risk: broad

Not-tested: Production CUDA run on pieroot-server
2026-09-07 22:03:25 +09:00

181 lines
5.3 KiB
Python

import inspect
import subprocess
import sys
import pytest
import torch
import pso
from pso import Optimizer, Particle, __version__
def test_canonical_exports_and_all():
"""Verify pso exports Optimizer, Particle, __version__, stage plugins and defines __all__ correctly."""
expected_all = [
"Optimizer",
"Particle",
"__version__",
"BasePlugin",
"InitializationPlugin",
"EvaluationPlugin",
"MovementPlugin",
"ConvergencePlugin",
"RefinementPlugin",
"PluginMetadata",
"SwarmState",
"available_plugins",
]
assert pso.__all__ == expected_all
assert pso.Optimizer is Optimizer
assert pso.Particle is Particle
assert pso.__version__ == "4.0.0"
assert __version__ == "4.0.0"
from pso.plugins import (
BasePlugin,
InitializationPlugin,
EvaluationPlugin,
MovementPlugin,
ConvergencePlugin,
RefinementPlugin,
PluginMetadata,
SwarmState,
available_plugins,
)
assert pso.BasePlugin is BasePlugin
assert pso.InitializationPlugin is InitializationPlugin
assert pso.EvaluationPlugin is EvaluationPlugin
assert pso.MovementPlugin is MovementPlugin
assert pso.ConvergencePlugin is ConvergencePlugin
assert pso.RefinementPlugin is RefinementPlugin
assert pso.PluginMetadata is PluginMetadata
assert pso.SwarmState is SwarmState
assert pso.available_plugins is available_plugins
def test_lowercase_aliases_and_legacy_api_absent():
"""Verify lowercase names and legacy get_best_weights are excluded/absent."""
assert "optimizer" not in pso.__all__
assert "particle" not in pso.__all__
assert not hasattr(Optimizer, "get_best_weights")
assert hasattr(Optimizer, "get_best_state_dict")
if hasattr(pso, "optimizer"):
obj = getattr(pso, "optimizer")
assert not isinstance(obj, type)
if hasattr(pso, "particle"):
obj = getattr(pso, "particle")
assert not isinstance(obj, type)
def test_optimizer_init_signature_and_kwonly():
"""Verify Optimizer.__init__ parameter names and keyword-only positions."""
sig = inspect.signature(Optimizer.__init__)
params = sig.parameters
assert "model" in params
assert "loss" in params
# Positional parameters (excluding self)
assert params["model"].kind in (
inspect.Parameter.POSITIONAL_OR_KEYWORD,
inspect.Parameter.POSITIONAL_ONLY,
)
assert params["loss"].kind in (
inspect.Parameter.POSITIONAL_OR_KEYWORD,
inspect.Parameter.POSITIONAL_ONLY,
)
kwonly_expected = [
"method",
"initialization",
"evaluation",
"convergence",
"refinement",
"method_options",
"n_particles",
"c0",
"c1",
"w_min",
"w_max",
"negative_swarm",
"mutation_swarm",
"particle_min",
"particle_max",
"velocity_limit_ratio",
"boundary_strategy",
"initial_position_noise",
"seed",
"device",
"fitness_size",
"convergence_patience",
"convergence_min_delta",
"convergence_monitor",
"refinement_epochs",
"refinement_lr",
"moment_blend",
"moment_beta1",
"moment_beta2",
"moment_step_size",
"moment_epsilon",
]
for name in kwonly_expected:
assert name in params, f"Missing parameter {name} in Optimizer.__init__"
assert params[name].kind == inspect.Parameter.KEYWORD_ONLY, (
f"Parameter {name} must be KEYWORD_ONLY"
)
def test_optimizer_fit_signature_and_kwonly():
"""Verify Optimizer.fit parameter names and keyword-only positions."""
sig = inspect.signature(Optimizer.fit)
params = sig.parameters
assert "x" in params
assert "y" in params
kwonly_expected = [
"epochs",
"batch_size",
"fitness_size",
"renewal",
"validation_data",
"validation_split",
"output_dir",
"log_format",
"checkpoint_interval",
"save_info",
]
for name in kwonly_expected:
assert name in params, f"Missing parameter {name} in Optimizer.fit"
assert params[name].kind == inspect.Parameter.KEYWORD_ONLY, (
f"Parameter {name} must be KEYWORD_ONLY"
)
def test_kwonly_positional_and_unknown_kwargs(model_factory, xor_data):
"""Verify passing keyword-only arguments positionally or unknown kwargs raises TypeError."""
x, y = xor_data
model = model_factory()
loss = torch.nn.BCEWithLogitsLoss()
with pytest.raises(TypeError):
Optimizer(model, loss, "binary") # type: ignore[call-arg]
opt = Optimizer(model, loss, task="binary")
with pytest.raises(TypeError):
opt.fit(x, y, invalid_unknown_arg=123) # type: ignore[call-arg]
def test_subprocess_import_quiet_stdout():
"""Verify importing pso in a fresh subprocess produces exit code 0 and empty stdout."""
res = subprocess.run(
[sys.executable, "-c", "import pso"],
capture_output=True,
text=True,
check=False,
)
assert res.returncode == 0, f"Import failed with stderr: {res.stderr}"
assert res.stdout == "", f"Expected empty stdout from import pso, got: {res.stdout!r}"