mirror of
https://github.com/jung-geun/PSO.git
synced 2026-09-20 14:11:48 +09:00
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
181 lines
5.3 KiB
Python
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}"
|