Harmony Search optimisation with dependent variable spaces and engineering domain catalogues
Project description
harmonix
Harmony Search optimisation with dependent variable spaces and engineering domain catalogues.
harmonix is a Python library for solving single- and multi-objective optimisation problems using the Harmony Search metaheuristic. Its key design principle is search-space first: instead of just minimising a function, you describe the domain of each variable precisely — including dependencies between variables, discrete grids, catalogue lookups, and domain-specific feasibility rules — and let the algorithm handle the rest.
from harmonix import DesignSpace, Continuous, Discrete, Minimization
space = DesignSpace()
space.add("h", Continuous(0.30, 1.20))
space.add("bf", Continuous(lo=lambda ctx: ctx["h"] * 0.5,
hi=lambda ctx: ctx["h"] * 2.0))
space.add("n", Discrete(4, 2, 20))
def objective(harmony):
h, bf, n = harmony["h"], harmony["bf"], harmony["n"]
cost = 1.1 * h * bf + 0.04 * n
penalty = max(0.0, h - 2 * bf)
return cost, penalty
result = Minimization(space, objective).optimize(
memory_size=20, hmcr=0.85, par=0.35, max_iter=5000
)
print(result)
Installation
pip install harmonix-opt
Requires Python 3.8+. No mandatory dependencies beyond the standard library.
For development:
pip install -r requirements-dev.txt
pip install -e .
Core concepts
Design variables
Every variable implements three methods that the algorithm calls internally:
| Method | Purpose |
|---|---|
sample(ctx) |
Draw a random feasible value |
filter(candidates, ctx) |
Keep only feasible values from harmony memory |
neighbor(value, ctx) |
Return an adjacent feasible value (pitch adjustment) |
The ctx argument is a dict of all variable values assigned earlier in the same harmony. This enables dependent bounds — the domain of a variable can depend on previously assigned variables.
Built-in variable types
| Type | Domain |
|---|---|
Continuous(lo, hi) |
ℝ ∩ [lo, hi] |
Discrete(lo, step, hi) |
{lo, lo+step, …, hi} |
Integer(lo, hi) |
{lo, lo+1, …, hi} |
Categorical(choices) |
finite label set |
All bounds accept callables for dependent domains:
space.add("d", Continuous(0.40, 1.20))
space.add("tw", Continuous(lo=lambda ctx: ctx["d"] / 50,
hi=lambda ctx: ctx["d"] / 10))
Domain-specific variable spaces
harmonix ships a catalogue of ready-made variable types for common engineering and mathematical domains:
from harmonix import ACIRebar, SteelSection, ConcreteGrade, PrimeVariable
# ACI 318 ductile bar arrangement — bounds depend on d and fc
space.add("rebar", ACIRebar(d_expr=lambda ctx: ctx["d"],
cc_expr=60.0,
fc=lambda ctx: ctx["grade"].fck_MPa,
fy=420.0))
# Standard steel I-section from built-in catalogue (IPE, HEA, HEB, W)
space.add("section", SteelSection(series=["IPE", "HEA"]))
# EN 206 concrete grade (C12/15 to C90/105)
space.add("concrete", ConcreteGrade(min_grade="C25/30", max_grade="C50/60"))
# Prime numbers
space.add("p", PrimeVariable(lo=2, hi=500))
Full catalogue:
| Category | Types |
|---|---|
| Mathematical | NaturalNumber, WholeNumber, NegativeInt, NegativeReal, PositiveReal, PrimeVariable, PowerOfTwo, Fibonacci |
| Structural | ACIRebar, ACIDoubleRebar, SteelSection, ConcreteGrade |
| Geotechnical | SoilSPT |
| Seismic | SeismicZoneTBDY |
All types are also accessible via the plugin registry:
from harmonix import create_variable, list_variable_types
print(list_variable_types())
var = create_variable("aci_rebar", d_expr=0.55, cc_expr=40.0)
Custom variables
Subclass Variable for full control:
from harmonix import Variable, register_variable
@register_variable("my_type")
class MyVariable(Variable):
def sample(self, ctx): ...
def filter(self, candidates, ctx): ...
def neighbor(self, value, ctx): ...
Factory function for quick prototyping:
from harmonix import make_variable
import random
EvenVar = make_variable(
sample = lambda ctx: random.choice(range(2, 101, 2)),
filter = lambda cands, ctx: [c for c in cands if c % 2 == 0],
neighbor = lambda val, ctx: val + random.choice([-2, 2]),
name = "even",
)
space.add("n", EvenVar())
Optimisers
Minimization
result = Minimization(space, objective).optimize(
memory_size = 20, # Harmony Memory Size (HMS)
hmcr = 0.85, # Harmony Memory Considering Rate
par = 0.35, # Pitch Adjusting Rate
max_iter = 5000,
bw_max = 0.05, # Initial bandwidth (5% of domain width)
bw_min = 0.001, # Final bandwidth (exponential decay)
resume = "auto", # "auto" | "new" | "resume"
checkpoint_path = "run.json",
checkpoint_every = 500,
use_cache = False, # Cache identical harmony evaluations
cache_maxsize = 4096,
log_init = False, # Write initial memory to CSV
log_history = False, # Write best-per-iteration to CSV
log_evaluations = False, # Write every evaluated harmony to CSV
history_every = 1,
verbose = True,
callback = my_callback,
)
print(result.best_harmony)
print(result.best_fitness)
Maximization
Same interface — negates internally, reports original sign.
result = Maximization(space, objective).optimize(...)
MultiObjective
def objective(harmony):
f1 = harmony["x"] ** 2
f2 = (harmony["x"] - 2) ** 2
return (f1, f2), 0.0 # tuple of objectives, penalty
result = MultiObjective(space, objective).optimize(
max_iter = 10_000,
archive_size = 100,
)
for entry in result.front:
print(entry.objectives, entry.harmony)
Callback and early stopping
def my_callback(iteration, partial_result):
print(iteration, partial_result.best_fitness)
if partial_result.best_fitness < 1e-4:
raise StopIteration # stops the loop cleanly
Advanced features
Dynamic bandwidth narrowing
The pitch adjustment step size decays exponentially from bw_max to bw_min over the run — wide exploration early, fine convergence late.
result = Minimization(space, objective).optimize(
bw_max=0.10, # 10% of domain width at iteration 0
bw_min=0.001, # 0.1% at final iteration
max_iter=5000,
)
Set bw_max == bw_min for constant bandwidth (original HS behaviour). Discrete and categorical variables are unaffected by bandwidth.
Resume control
# "auto" — continue if checkpoint exists, start fresh otherwise (safe default)
# "new" — always start fresh, overwrite any existing checkpoint
# "resume" — always continue; raises FileNotFoundError if checkpoint missing
result = optimizer.optimize(
max_iter = 50_000,
checkpoint_path = "run.json",
resume = "auto",
)
The initial harmony memory is saved immediately at startup — even a run interrupted in the first seconds can be resumed cleanly.
Evaluation cache
Identical harmonies are never re-evaluated when use_cache=True. Particularly valuable for expensive objectives (FEM, CFD, etc.).
result = optimizer.optimize(use_cache=True, cache_maxsize=4096)
print(optimizer._cache.stats())
# EvaluationCache: 412 hits / 1005 total (41.0% hit rate) size=593/4096
CSV logging
result = optimizer.optimize(
checkpoint_path = "run.json",
log_init = True, # → run_init.csv (initial memory)
log_history = True, # → run_history.csv (best per iteration)
log_evaluations = True, # → run_evals.csv (every evaluation)
history_every = 10, # write history every 10 iterations
)
All CSV files are readable directly in Excel or with pandas.read_csv().
Decoding engineering variables
Variables like ACIRebar and SteelSection store integer codes in the harmony. Use decode() to get full properties:
rebar_var = ACIRebar(d_expr=0.55, cc_expr=40.0)
code = result.best_harmony["rebar"]
diameter_mm, bar_count = rebar_var.decode(code)
print(rebar_var.describe(code)) # "8 bars of Ø19.00 mm"
section_var = SteelSection(series=["IPE"])
sec = section_var.decode(result.best_harmony["section"])
print(sec.name, sec.Iy_cm4, "cm4")
grade_var = ConcreteGrade()
grade = grade_var.decode(result.best_harmony["concrete"])
print(grade.name, grade.fck_MPa, "MPa", grade.Ecm_GPa, "GPa")
Steel section catalogue
The built-in catalogue covers IPE 80–600, HEA 100–500, HEB 100–500, and W-sections. Override with your own file:
var = SteelSection(catalogue="my_sections.json") # custom catalogue
var = SteelSection(series=["HEA", "HEB"]) # filter series
Algorithm background
harmonix implements Harmony Search with several enhancements:
Dynamic bandwidth narrowing — pitch adjustment step size decays exponentially. Early iterations explore broadly; late iterations converge precisely.
Intelligent pitch adjustment — neighbor() is called with the current dependency context so the perturbed value stays feasible. The common incorrect approach of calling sample() on PAR is avoided.
Dependent search spaces — variables are sampled in definition order; each receives a context dict of previously assigned values. Dependent bounds, catalogue filters, and feasibility checks can reference earlier variables without any special handling in the optimiser loop.
Deb constraint handling — feasible solutions always rank above infeasible ones; among infeasible solutions ranking is by total penalty.
References
- Geem, Z. W., Kim, J. H., & Loganathan, G. V. (2001). A new heuristic optimization algorithm: Harmony search. Simulation, 76(2), 60–68.
- Lee, K. S., & Geem, Z. W. (2005). A new meta-heuristic algorithm for continuous engineering optimization. Computer Methods in Applied Mechanics and Engineering, 194(36–38), 3902–3933.
- Deb, K. (2000). An efficient constraint handling method for genetic algorithms. Computer Methods in Applied Mechanics and Engineering, 186(2–4), 311–338.
- Ricart, J., Hüttemann, G., Lima, J., & Barán, B. (2011). Multiobjective harmony search algorithm proposals. Electronic Notes in Theoretical Computer Science, 281, 51–67.
Testing
pip install -r requirements-dev.txt
pytest tests/ -v
472 tests across 15 test files covering:
- All variable types —
sample,filter,neighbor, edge cases,lo > hivalidation - DesignSpace — dependency chains, empty space, 50-variable stress test
- Optimisers — Minimization, Maximization, MultiObjective
- New features — bandwidth decay, resume modes, evaluation cache, CSV logging
- Pareto archive — dominance, crowding distance, serialization
- Engineering physics — EC2 formulas, ACI 318 feasibility, steel section properties
- Determinism — same seed produces identical results
- Numerical correctness — Sphere, Rosenbrock, constrained minimization
Project structure
harmonix/
├── harmonix/
│ ├── variables.py # Continuous, Discrete, Integer, Categorical
│ ├── space.py # DesignSpace
│ ├── optimizer.py # Minimization, Maximization, MultiObjective
│ ├── pareto.py # Pareto archive, crowding distance
│ ├── registry.py # register_variable, make_variable
│ ├── logging.py # EvaluationCache, RunLogger
│ └── spaces/
│ ├── math.py # Mathematical search spaces
│ └── engineering.py # Engineering domain spaces
├── examples/
│ ├── 01_quickstart.py
│ ├── 02_welded_beam.py
│ ├── 03_rc_beam_design.py
│ ├── 04_custom_variable.py
│ ├── 05_multi_objective.py
│ ├── 06_steel_beam_design.py
│ └── 07_rc_section_full.py
├── tests/ # 472 tests across 15 files
├── requirements-dev.txt
├── ruff.toml
├── pyproject.toml
└── LICENSE
Citation
If you use harmonix-opt in your research, please cite it as follows:
APA:
Özcan, A. (2026). harmonix-opt: Harmony Search optimisation with dependent variable spaces (Version 1.0.1) [Computer software]. https://doi.org/10.5281/zenodo.19160019
BibTeX:
@software{ozcan_harmonix_2026,
author = {Özcan, Abdulkadir},
title = {harmonix-opt: Harmony Search optimisation with dependent variable spaces},
month = mar,
year = 2026,
publisher = {Zenodo},
version = {1.0.1},
doi = {10.5281/zenodo.19160019},
url = {[https://doi.org/10.5281/zenodo.19160019](https://doi.org/10.5281/zenodo.19160019)}
}
License
MIT © Abdulkadir Özcan
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file harmonix_opt-1.0.2.tar.gz.
File metadata
- Download URL: harmonix_opt-1.0.2.tar.gz
- Upload date:
- Size: 81.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b5c5a5c6888dc6c90541644399a09d34a23faff5455ff579b739019dc46ea89
|
|
| MD5 |
b0d688426f4677c174a40670dc5cdf0a
|
|
| BLAKE2b-256 |
390060e5e2eb35e9170983fe963822f95609d04e45baf780495f378ea4dc4f9d
|
Provenance
The following attestation bundles were made for harmonix_opt-1.0.2.tar.gz:
Publisher:
publish.yml on AutoPyloter/harmonix
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
harmonix_opt-1.0.2.tar.gz -
Subject digest:
9b5c5a5c6888dc6c90541644399a09d34a23faff5455ff579b739019dc46ea89 - Sigstore transparency entry: 1185502675
- Sigstore integration time:
-
Permalink:
AutoPyloter/harmonix@26b7cd6b5d28bed590b7b0f6ba5294615fabfe10 -
Branch / Tag:
refs/tags/v1.0.2 - Owner: https://github.com/AutoPyloter
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@26b7cd6b5d28bed590b7b0f6ba5294615fabfe10 -
Trigger Event:
release
-
Statement type:
File details
Details for the file harmonix_opt-1.0.2-py3-none-any.whl.
File metadata
- Download URL: harmonix_opt-1.0.2-py3-none-any.whl
- Upload date:
- Size: 48.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c69fe86eaf6ffe3c88a07b3afdf64ded1069f72d991f089934be728089eb9284
|
|
| MD5 |
7138aac5ced763196034c3989c32b735
|
|
| BLAKE2b-256 |
befbd18494072a346db1095b4a34115e421bdf91eb08e41312cbc8730e49c2c7
|
Provenance
The following attestation bundles were made for harmonix_opt-1.0.2-py3-none-any.whl:
Publisher:
publish.yml on AutoPyloter/harmonix
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
harmonix_opt-1.0.2-py3-none-any.whl -
Subject digest:
c69fe86eaf6ffe3c88a07b3afdf64ded1069f72d991f089934be728089eb9284 - Sigstore transparency entry: 1185502683
- Sigstore integration time:
-
Permalink:
AutoPyloter/harmonix@26b7cd6b5d28bed590b7b0f6ba5294615fabfe10 -
Branch / Tag:
refs/tags/v1.0.2 - Owner: https://github.com/AutoPyloter
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@26b7cd6b5d28bed590b7b0f6ba5294615fabfe10 -
Trigger Event:
release
-
Statement type: