Artificial Muscle Network: physics-inspired optimization for scheduling, routing, and allocation
Project description
AMN (Artificial Muscle Network)
A physics-inspired optimization library for scheduling, routing, and allocation problems.
AMN treats optimization like a physical system: objectives create "forces" that push solutions toward better states, while constraints act as boundaries. The result is an intuitive, fast, and explainable optimizer that works out of the box.
pip install artificial-muscle-network
Why AMN?
The Problem
You have a real-world optimization problem:
- "How do I assign 50 tasks to 10 workers fairly?"
- "Which truck should deliver which packages?"
- "How should I allocate my budget across departments?"
Your options today:
| Approach | Problem |
|---|---|
| OR-Tools / CPLEX / Gurobi | Steep learning curve, complex APIs, heavy dependencies, expensive licenses |
| Genetic Algorithms | Slow convergence, unpredictable results, lots of hyperparameters |
| Greedy Heuristics | Fast but often miss better solutions |
| Write it yourself | Time-consuming, error-prone |
The AMN Solution
AMN gives you scikit-learn simplicity for operations research problems:
from amn import Scheduler, Resource, Task, Constraint
scheduler = Scheduler(resources, tasks, constraints)
solution = scheduler.optimize()
That's it. No PhD required.
Key Features
🚀 Zero Dependencies
Pure Python. No NumPy, no SciPy, no compiled extensions. Install it anywhere Python runs—your laptop, a Raspberry Pi, AWS Lambda, or a restricted corporate environment.
🎯 Multi-Objective by Design
Real problems have competing goals: minimize cost AND maximize fairness AND respect constraints. AMN handles this naturally by combining weighted objectives into a single energy function.
🔍 Explainable Results
Every solution comes with an energy trace showing how the optimizer converged. You can inspect the "forces" (gradients) to understand why the optimizer made specific choices.
⚡ Fast Convergence
Typically converges in 100-500 iterations. Most problems solve in under a second.
🧩 Extensible
Build custom objectives and constraints for your specific domain. The core optimizer is problem-agnostic.
When Should You Use AMN?
✅ Great For
| Use Case | Example |
|---|---|
| Staff Scheduling | Assign nurses to shifts respecting skills and availability |
| Task Assignment | Distribute work orders to technicians fairly |
| Delivery Routing | Assign packages to drivers minimizing distance |
| Resource Allocation | Split budget across projects maximizing ROI |
| Portfolio Optimization | Balance investments across assets |
| Load Balancing | Distribute requests across servers evenly |
| Classroom Scheduling | Assign courses to rooms and time slots |
| Crew Rostering | Build airline crew schedules |
⚠️ Consider Alternatives For
| Use Case | Better Tool |
|---|---|
| Exact optimal solutions required | CPLEX, Gurobi (commercial MIP solvers) |
| Millions of variables | Specialized large-scale solvers |
| Real-time (< 10ms) decisions | Pre-computed lookup tables |
| Traveling Salesman (exact) | Concorde TSP solver |
🎯 Sweet Spot
AMN shines when you need a good solution fast for problems with:
- 10-1000 tasks/items
- 2-100 resources/vehicles/assets
- Multiple competing objectives
- Soft constraints that can be traded off
What to Expect
Performance
| Problem Size | Typical Time | Iterations |
|---|---|---|
| 10 tasks, 3 workers | ~50ms | 100-200 |
| 50 tasks, 10 workers | ~200ms | 200-400 |
| 200 tasks, 20 workers | ~1s | 300-500 |
| 500 tasks, 50 workers | ~5s | 400-600 |
Benchmarked on a 2020 MacBook Pro. Your mileage may vary.
Solution Quality
AMN finds good solutions, not necessarily optimal solutions. For most practical problems:
- Solutions are within 5-15% of optimal
- Trade-offs between objectives are well-balanced
- Constraints are respected (hard) or minimally violated (soft)
If you need provably optimal solutions, use a MIP solver. If you need a good solution in seconds with minimal setup, use AMN.
Reproducibility
Set a seed for deterministic results:
scheduler = Scheduler(resources, tasks, constraints, seed=42)
Three Problem Domains
AMN provides high-level solvers for three common optimization domains.
1. 📅 Scheduling
Problem: Assign tasks to resources respecting skills, capacity, and fairness.
Real-world examples:
- Nurse shift scheduling
- Construction crew assignment
- Call center staffing
- Manufacturing job shop scheduling
from amn import Scheduler, Resource, Task, Constraint
# Define your workforce
resources = [
Resource("Alice", capacity=40, skills={"python", "sql"}),
Resource("Bob", capacity=40, skills={"javascript", "sql"}),
Resource("Carol", capacity=32, skills={"python", "javascript"}), # Part-time
]
# Define the work
tasks = [
Task("Backend API", duration=16, required_skills={"python"}),
Task("Database Migration", duration=8, required_skills={"sql"}),
Task("Frontend Update", duration=12, required_skills={"javascript"}),
Task("Data Pipeline", duration=10, required_skills={"python", "sql"}),
]
# Define constraints
constraints = [
Constraint.skill_match(), # Workers must have required skills
Constraint.capacity(max_hours=40), # Respect weekly hours
Constraint.no_overlap(), # One worker per task
]
# Solve
scheduler = Scheduler(resources, tasks, constraints, seed=42)
solution = scheduler.optimize(max_iterations=300)
# Results
print(f"Solved in {solution.iterations} iterations")
print(f"Converged: {solution.converged}")
print(f"\nAssignments:")
for task, resource in solution.assignments:
print(f" {task.name} → {resource.name}")
Output:
Solved in 156 iterations
Converged: True
Assignments:
Backend API → Alice
Database Migration → Bob
Frontend Update → Carol
Data Pipeline → Alice
2. 🚚 Vehicle Routing
Problem: Assign deliveries to vehicles minimizing distance while respecting capacity.
Real-world examples:
- Last-mile delivery optimization
- Field service technician routing
- School bus routing
- Waste collection routes
from amn import Router, Location, Vehicle, Delivery
# Define locations (x, y coordinates)
locations = [
Location("Warehouse", 0, 0), # Depot
Location("Downtown", 2, 3),
Location("Suburbs", 5, 1),
Location("Mall", 3, 4),
Location("Airport", 7, 2),
]
# Define fleet
vehicles = [
Vehicle("Van 1", capacity=100, depot_index=0),
Vehicle("Van 2", capacity=80, depot_index=0),
]
# Define deliveries
deliveries = [
Delivery("Package A", location_index=1, demand=30),
Delivery("Package B", location_index=2, demand=50),
Delivery("Package C", location_index=3, demand=25),
Delivery("Package D", location_index=4, demand=45),
]
# Solve
router = Router(
locations, vehicles, deliveries,
distance_weight=1.0, # Minimize total distance
fairness_weight=0.5, # Balance load across vehicles
seed=42
)
solution = router.optimize()
# Get organized routes
routes = router.get_routes(solution)
for vehicle_name, stops in routes.items():
if stops:
stop_names = [d.name for d in stops]
total_demand = sum(d.demand for d in stops)
print(f"{vehicle_name}: {stop_names} (load: {total_demand})")
Output:
Van 1: ['Package A', 'Package C'] (load: 55)
Van 2: ['Package B', 'Package D'] (load: 95)
3. 💰 Portfolio Allocation
Problem: Allocate budget across assets balancing risk and return.
Real-world examples:
- Investment portfolio optimization
- Marketing budget allocation
- R&D project funding
- Inventory stocking decisions
from amn import Allocator, Asset
# Define investment options
assets = [
Asset("US Stocks", expected_return=0.10, risk=0.18),
Asset("Int'l Stocks", expected_return=0.08, risk=0.22),
Asset("Bonds", expected_return=0.04, risk=0.05),
Asset("Real Estate", expected_return=0.07, risk=0.12),
Asset("Cash", expected_return=0.02, risk=0.01),
]
# Solve with different risk tolerances
for risk_tolerance in [0.2, 0.5, 0.8]:
allocator = Allocator(
assets,
risk_tolerance=risk_tolerance, # 0 = min risk, 1 = max return
budget=1.0,
seed=42
)
solution = allocator.optimize()
weights = allocator.get_weights(solution)
print(f"\nRisk Tolerance: {risk_tolerance}")
print(f"Expected Return: {allocator.expected_return(solution):.1%}")
print(f"Expected Risk: {allocator.expected_risk(solution):.1%}")
print("Allocation:")
for name, weight in sorted(weights.items(), key=lambda x: -x[1]):
if weight > 0.01:
print(f" {name}: {weight:.1%}")
Output:
Risk Tolerance: 0.2
Expected Return: 3.8%
Expected Risk: 2.1%
Allocation:
Cash: 45.2%
Bonds: 38.1%
Real Estate: 16.7%
Risk Tolerance: 0.5
Expected Return: 6.2%
Expected Risk: 8.4%
Allocation:
Bonds: 31.2%
Real Estate: 28.5%
US Stocks: 24.1%
Int'l Stocks: 16.2%
Risk Tolerance: 0.8
Expected Return: 8.1%
Expected Risk: 14.2%
Allocation:
US Stocks: 42.3%
Int'l Stocks: 28.7%
Real Estate: 21.5%
Bonds: 7.5%
How It Works
AMN uses a physics-inspired approach called gradient descent with momentum and constraint projection.
The Intuition
Imagine your solution as a ball on a hilly landscape:
- Energy = height on the landscape (lower is better)
- Forces = slopes pushing the ball downhill
- Momentum = the ball's velocity (helps escape local minima)
- Damping = friction (prevents oscillation)
- Constraints = walls that keep the ball in valid regions
The Algorithm
1. Start with random assignment strengths (0-1 values)
2. Repeat until converged:
a. Compute forces = -∇Energy - damping × velocity
b. Update velocity += force × dt
c. Update position += velocity × dt
d. Project position onto constraints (clip, normalize, etc.)
3. Decode continuous solution to discrete assignments
Why "Artificial Muscle"?
The name comes from the analogy to biological muscles:
- Multiple "fibers" (objectives) pull in different directions
- The system finds equilibrium balancing all forces
- Constraints act like tendons limiting range of motion
Advanced Usage
Custom Objectives
from amn import Optimizer
from amn.core.objectives import Objective
class MyObjective(Objective):
def evaluate(self, y):
# Return scalar energy (lower is better)
return sum(v ** 2 for row in y for v in row)
def gradient(self, y):
# Return matrix of partial derivatives
return [[2 * v for v in row] for row in y]
# Use with low-level optimizer
opt = Optimizer(
variables=[[0.5, 0.5], [0.5, 0.5]],
constraints=[],
objectives=[MyObjective(weight=1.0)]
)
solution = opt.optimize()
Tuning Parameters
scheduler = Scheduler(
resources, tasks, constraints,
# Objective weights (higher = more important)
coverage_weight=2.0, # Assign all tasks
fairness_weight=1.0, # Balance workload
sparsity_weight=0.5, # Encourage 0/1 values
# Optimizer dynamics
dt=0.1, # Time step (smaller = more stable)
damping=0.1, # Friction (higher = less oscillation)
seed=42 # Reproducibility
)
Inspecting Convergence
solution = scheduler.optimize(max_iterations=500)
import matplotlib.pyplot as plt
plt.plot(solution.energies)
plt.xlabel('Iteration')
plt.ylabel('Energy')
plt.title('Convergence')
plt.show()
FAQ
Q: How does AMN compare to linear programming?
Linear programming (LP) finds provably optimal solutions to problems with linear objectives and constraints. AMN finds good solutions to more general problems (non-linear objectives, soft constraints) much faster. Use LP when you need optimality guarantees; use AMN when you need speed and flexibility.
Q: Can AMN handle hard constraints?
Yes, through constraint projection. After each optimization step, solutions are projected back onto the feasible region. Some constraints (like capacity) are enforced in the decoder as well.
Q: What if my problem doesn't fit the three domains?
Use the low-level Optimizer class directly with custom objectives and constraints. The high-level classes (Scheduler, Router, Allocator) are convenience wrappers.
Q: How do I choose objective weights?
Start with equal weights, observe which objectives dominate, then adjust. Higher weight = that objective matters more. It's often useful to normalize objectives to similar scales.
Q: Is AMN deterministic?
Yes, if you set a seed. Without a seed, initialization is random.
Installation
pip install artificial-muscle-network
Or install from source:
git clone https://github.com/eldm-ethanmoore/artificial-muscle-network.git
cd artificial-muscle-network
pip install -e .
Requires Python 3.9+. No dependencies.
Documentation
- Quickstart Guide
- API Reference
- How It Works
- Tutorials
- Benchmarks vs OR-Tools, SciPy ← Real performance data
Contributing
Contributions welcome! See CONTRIBUTING.md.
License
MIT © AMN Contributors
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 artificial_muscle_network-2.0.0.tar.gz.
File metadata
- Download URL: artificial_muscle_network-2.0.0.tar.gz
- Upload date:
- Size: 29.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9114e77d1cbc3bae9aae7d4749f32dacbc864fe9b2c2ae22fda9d4458a635442
|
|
| MD5 |
bd6eba4b084a09b26669aef3f9698586
|
|
| BLAKE2b-256 |
e5d1c60f04233f13685077f35e8cde685b7302b4a83968d9b614bc704a14cc26
|
File details
Details for the file artificial_muscle_network-2.0.0-py3-none-any.whl.
File metadata
- Download URL: artificial_muscle_network-2.0.0-py3-none-any.whl
- Upload date:
- Size: 25.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
163f9143fa6860f1583c6dca29ee9b6fdbc8417ba7f9cf1ee56d8deafc52639c
|
|
| MD5 |
7e45bc64f4f6b3a41ffe6f2fd90a2b41
|
|
| BLAKE2b-256 |
020a9864bb9ab15e10bb814244a72d66b14b1ba9d47ab9658f80f14a6b4e40cd
|