Skip to main content

SwapN

Move Type: Advanced Complexity: O(N! × I) where N = concurrent objects, I = iterations Primary Use: Satisfy exact-limit constraints by swapping N objects simultaneously

Swap N objects simultaneously to satisfy complex exact-limit constraints that simpler move types cannot handle.

Overview

SwapN (also known as SWAP_N) is a highly specialized move type designed for problems with exact-limit constraints where you need to move N objects together to maintain balance. It was originally created for the Capacity Request Portal (CRP) problem.

Instead of moving objects one at a time, SwapN:

  1. Selects N objects from a source set
  2. Tries placing them in N different destination containers
  3. Swaps them with N objects already in those containers
  4. Evaluates if this simultaneous N-way swap improves the objective

This allows satisfying constraints like "exactly 3 requests per pod" that are impossible with single-object moves.

Use when:

  • Have exact-limit constraints (MIN + MAX with same value)
  • Simple moves get stuck due to constraints
  • Need to move N objects simultaneously
  • Know which objects and destinations to consider
  • Very specific capacity allocation problems

Avoid when:

  • No exact-limit constraints
  • Simple moves work fine
  • Don't have specific source objects
  • Generic optimization (very expensive)
  • N is large (>5)

Quick Example

# Swap 3 objects simultaneously to satisfy exact-limit constraints
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapNMoveTypeSpec(
swapNConcurrentObjects=3,
swapNSourceObjects=[
"req0",
"req1",
"req2",
"req3",
"req4",
"req5",
],
swapNDestinationScope=[
["rack0", "rack1", "rack2"], # Pod 0 racks
["rack3", "rack4", "rack5"], # Pod 1 racks
],
swapNIterations=1000,
),
]
)
)
)

Parameters

ParameterTypeRequiredDefaultDescription
swapNConcurrentObjectsintNo1Number of objects to swap simultaneously
swapNSourceObjectslist<string>YesnullSource objects to consider
swapNDestinationScopelist<list<string>>YesnullGrouped destination containers
swapNIterationsintNo1000000Max random iterations to try

Parameter Details

swapNConcurrentObjects:

  • Number of objects to swap simultaneously (N)
  • Example: 3 means swap 3 objects at once
  • Higher N = exponentially more expensive
  • Typically 2-5

swapNSourceObjects:

  • List of object names to consider as sources
  • Only these objects will be moved
  • Example: ["request0", "request1", "request2", "request3"]
  • Typically unassigned or pending objects

swapNDestinationScope:

  • Nested list of container groups
  • Outer list: groups (e.g., pods)
  • Inner lists: containers in each group (e.g., racks per pod)
  • Example: [["rack0", "rack1"], ["rack2", "rack3"]]
  • Each group represents mutually exclusive destinations

swapNIterations:

  • Maximum random sampling iterations
  • Algorithm tries random combinations up to this limit
  • Higher = better quality but slower
  • Default 1M is usually sufficient

How It Works

For each iteration (up to swapNIterations):

  1. Random selection: Pick N random objects from swapNSourceObjects
  2. Random destinations: Pick N random destination containers from swapNDestinationScope (one from each group if possible)
  3. Generate swap: Create N-way swap moving selected objects to destinations
  4. Evaluate: Test if this swap improves objective
  5. Track best: Keep the best swap found so far
  6. Repeat: Try different random combinations

Visual Example

Problem: Allocate 6 requests to 2 pods, exactly 3 requests per pod

Source: unallocated = [req0, req1, req2, req3, req4, req5]

Destinations:
Pod0: [rack0, rack1, rack2, ...] (10 racks)
Pod1: [rack10, rack11, rack12, ...] (10 racks)

swapNConcurrentObjects = 3

Iteration 1:
Select: [req0, req2, req4]
Destinations: [rack1 (pod0), rack3 (pod0), rack11 (pod1)] ✗ Invalid (2 in pod0, 1 in pod1)

Iteration 2:
Select: [req1, req3, req5]
Destinations: [rack0 (pod0), rack2 (pod0), rack4 (pod0)] ✗ Invalid (all in pod0)

Iteration 3:
Select: [req0, req1, req2]
Destinations: [rack1 (pod0), rack11 (pod1), rack12 (pod1)] ✗ Invalid (1 in pod0, 2 in pod1)

Iteration 100:
Select: [req0, req2, req4]
Destinations: [rack0 (pod0), rack2 (pod0), rack4 (pod0)] ✓ Valid! (3 in pod0)

Then continue to place remaining 3 in pod1...

Complexity

Per iteration: O(N!)

Where:

  • N = swapNConcurrentObjects

Total: O(N! × I)

Where:

  • I = iterations attempted (up to swapNIterations)

Examples:

  • N=2, I=1000: ~2,000 combinations
  • N=3, I=1000: ~6,000 combinations
  • N=4, I=1000: ~24,000 combinations
  • N=5, I=10000: ~1,200,000 combinations

⚠️ Factorial growth: N=6 is 720x more expensive than N=2!

Usage Patterns

Exact-Limit Allocation

Allocate requests with exact pod limits:

# Exactly 3 requests per pod using SwapN
solver.addConstraint(
ConstraintSpecs(
groupCountSpec=GroupCountSpec(
scope="pod",
dimension="request_count",
partitionName="request_group",
bound=GroupCountSpecBound.EXACT,
limit=GroupLimitSpec(defaultLimit=3),
)
)
)

solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapNMoveTypeSpec(
swapNConcurrentObjects=3,
swapNSourceObjects=[
"request0",
"request1",
"request2",
"request3",
"request4",
"request5",
],
swapNDestinationScope=[
["rack0", "rack1", "rack2"], # Pod 0
["rack3", "rack4", "rack5"], # Pod 1
],
),
]
)
)
)

Capacity Request Portal

Original use case - allocate N hosts per request:

# Capacity Request Portal: allocate hosts with exact pod constraints
solver = ProblemSolver(service_name="example", service_scope="test")
solver.setObjectName("host")
solver.setContainerName("rack")

# Define pod scope
solver.addScope(
"pod",
{
"rack0": "pod0",
"rack1": "pod0",
"rack2": "pod1",
"rack3": "pod1",
},
)

solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapNMoveTypeSpec(
swapNConcurrentObjects=3, # 3 hosts per allocation
swapNSourceObjects=[
"host0",
"host1",
"host2",
"host3",
"host4",
"host5",
],
swapNDestinationScope=[
["rack0", "rack1"], # Pod 0 racks
["rack2", "rack3"], # Pod 1 racks
],
swapNIterations=1000000, # 1M iterations
),
]
)
)
)

Limited Iterations

Control sampling for performance:

# Use fewer iterations for faster (but lower quality) results
solver = ProblemSolver(service_name="example", service_scope="test")

solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapNMoveTypeSpec(
swapNConcurrentObjects=2,
swapNSourceObjects=["obj0", "obj1", "obj2", "obj3"],
swapNDestinationScope=[
["container0", "container1"],
["container2", "container3"],
],
swapNIterations=100, # Reduced from default 1M
),
]
)
)
)

Performance Characteristics

When Does It Help?

SwapN helps when:

  • Exact-limit constraints: Have MIN + MAX with same value
  • Stuck simple moves: Single/Swap move types can't make progress
  • Known sources: Have specific objects to allocate
  • Small N: Need to move 2-5 objects simultaneously
  • Specific problem structure: Like CRP problem

SwapN does NOT help when:

  • No exact limits: Using ranges (MIN ≠ MAX)
  • Simple moves work: Other move types making progress
  • Large N: N > 5 (factorial explosion)
  • No source specification: Don't know which objects
  • General optimization: Too specialized and expensive

Complexity Explosion

NFactorialIterationsTotal OpsPractical?
221,0002K✓ Fast
361,0006K✓ Good
4241,00024K✓ OK
512010,0001.2M⚠ Slow
672010,0007.2M✗ Very slow
103,628,800--✗ Impractical

Comparison with Alternatives

Move TypeSimultaneousConstraint TypeUse Case
Single1 objectGeneralStandard moves
Swap2 objectsPairwiseObject swaps
SwapNN objectsExact-limitCRP-like problems

Troubleshooting

Problem: No improving moves found

Diagnosis: Random sampling not finding valid combinations

Solutions:

  • Increase swapNIterations (try 10M)
  • Check if problem is actually solvable
  • Verify source objects and destinations are correct
  • May need different N value

Problem: Too slow

Diagnosis: N too large or too many iterations

Solutions:

  • Reduce swapNConcurrentObjects if possible
  • Reduce swapNIterations (try 1K or 10K)
  • Check if simpler move types can work
  • Consider if problem structure allows smaller N

Problem: Constraint violations

Diagnosis: Swaps violate constraints

Solutions:

  • Verify exact-limit constraints are set correctly
  • Check destination scope grouping is correct
  • Review capacity constraints
  • Ensure source objects are valid

Problem: Wrong destinations

Diagnosis: swapNDestinationScope structure incorrect

Solutions:

  • Verify outer list groups destinations correctly
  • Check inner lists contain right containers
  • Example: [["pod0_racks"], ["pod1_racks"]]
  • Each inner list should represent mutually exclusive group

When to Use SwapN

DO use when:

  • Have exact-limit constraints (MIN = MAX)
  • Simple moves stuck
  • Know source objects
  • N is small (2-5)
  • Have CRP-like problem structure

DO NOT use when:

  • Using constraint ranges (MIN ≠ MAX)
  • Simple moves working
  • N is large (>5)
  • Generic optimization needed
  • Don't have specific sources/destinations

Alternatives:

  • Single - For general moves
  • Swap - For 2-object swaps
  • KLSearch - For escaping local optima differently

When to try alternatives first:

  • Always try Single and Swap first
  • Only use SwapN when stuck on exact-limit constraints

Source Code

Next Steps

  • Try KLSearch for different local optima escape
  • Review Move Types Overview for choosing move types
  • Learn about exact-limit constraints in capacity specs

Notes

⚠️ Highly Specialized: SwapN is designed for very specific problems with exact-limit constraints. It's expensive and should only be used when simpler move types fail.

⚠️ Factorial Complexity: Keep N ≤ 5. N=6 is 720x slower than N=2. N=10 is completely impractical.

💡 Original Use Case: Created for Capacity Request Portal (CRP) problem requiring "exactly N hosts per pod" allocation.