SwapSampled
Move Type: Swap Complexity: O(sample²) instead of O(objects²)
Sample a subset of objects for swapping instead of trying all combinations. Essential for large-scale capacity-constrained problems.
Overview
SwapSampled uses SwapMoveTypeSpec with the sampleSize parameter to limit the number of swap combinations evaluated. Instead of trying all possible object pairs, it samples objects from both source and destination containers.
Use when:
- Problem is large (>10K objects)
- Full Swap is too slow
- Capacity constraints require swaps but exhaustive search is impractical
Avoid when:
- Problem is small enough for full Swap
- Need to explore all possible swaps
- Sample size might miss important swaps
Quick Example
- Python
- C++
# Use SwapMoveTypeSpec with sampling for large problems
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleMoveTypeSpec(),
SwapMoveTypeSpec(
sampleSize=SampleSize(
defaultSampleSize=100, # Sample 100 objects on each side
),
),
]
)
)
)
void quickExample() {
// Use SwapMoveTypeSpec with sampling for large problems
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
// Add Single
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleMoveTypeSpec() = SingleMoveTypeSpec{};
// Add SwapSampled
SwapMoveTypeSpec swapSpec;
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 100;
swapSpec.sampleSize() = sampleSize;
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Parameters
SwapSampled uses SwapMoveTypeSpec with these key parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sampleSize | SampleSize | Yes | null | Controls sampling on both src and dst |
partitionNameToExploreSwapsWithinObjectGroup | string | No | null | Only swap within same group |
greedyOnSrc | bool | No | false | Exit early when trying src objects |
greedyOnDst | bool | No | false | Exit early when trying dst objects |
destinationsToExplore | DestinationsToExploreOptions | No | null | Restrict destination containers |
SampleSize Structure
| Field | Type | Description |
|---|---|---|
defaultSampleSize | int32 | Default sample size for all objects |
objectToSampleSize | map<string, int32> | Per-object sample size override |
How It Works
Given a hot container and cold container:
- Sample source objects: Select up to
sampleSizeobjects from hot container - Sample destination objects: Select up to
sampleSizeobjects from cold container - Evaluate swaps: Test swapping each sampled source object with each sampled destination object
- Apply best: Apply the best swap that improves the objective
Visual Example
Full Swap (100 × 100 = 10,000 swaps):
Hot Container (100 objects) × Cold Container (100 objects)
= 10,000 swap combinations to evaluate
SwapSampled with sampleSize=10 (10 × 10 = 100 swaps):
Hot Container (sample 10) × Cold Container (sample 10)
= 100 swap combinations to evaluate (100x speedup!)
Comparison with Full Swap
| Aspect | Swap | SwapSampled |
|---|---|---|
| Combinations | N × M | sample × sample |
| Speed | Slow for large N,M | Much faster |
| Quality | Best swap guaranteed | Good swap likely |
| Use case | Small/medium problems | Large problems |
Complexity
Moves evaluated per iteration: O(S² × C)
Where:
- S = sample size (from
sampleSizeparameter) - C = number of cold containers
- Compare to full Swap: O(N × M × C) where N,M = object counts
Example - Large problem:
- Hot container: 10,000 objects
- Cold containers: 1,000 containers with ~10,000 objects each
- Full Swap: 10,000 × 10,000 × 1,000 = 100 billion evaluations
- SwapSampled (sample=100): 100 × 100 × 1,000 = 10 million evaluations (10,000x speedup!)
Usage Patterns
Basic Sampling
Default usage with reasonable sample size:
- Python
- C++
# Reasonable sample size for most large problems
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapMoveTypeSpec(
sampleSize=SampleSize(
defaultSampleSize=100,
),
),
]
)
)
)
void basicSampling() {
// Reasonable sample size for most large problems
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
SwapMoveTypeSpec swapSpec;
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 100;
swapSpec.sampleSize() = std::move(sampleSize);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Per-Object Sample Size
Different sample sizes for different objects:
- Python
- C++
# Higher sample size for important objects
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapMoveTypeSpec(
sampleSize=SampleSize(
defaultSampleSize=50, # Default for most objects
objectToSampleSize={
"critical_obj1": 200, # More sampling for critical objects
"critical_obj2": 200,
},
),
),
]
)
)
)
void perObjectSampling() {
// Higher sample size for important objects
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
SwapMoveTypeSpec swapSpec;
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 50;
sampleSize.objectToSampleSize() = {
{"critical_obj1", 200},
{"critical_obj2", 200},
};
swapSpec.sampleSize() = std::move(sampleSize);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Large Problem Configuration
Aggressive sampling for very large problems:
- Python
- C++
# Small sample for problems with 100K+ objects
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapMoveTypeSpec(
sampleSize=SampleSize(
defaultSampleSize=50, # Aggressive sampling for speed
),
),
]
)
)
)
void largeProblem() {
// Small sample for problems with 100K+ objects
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
SwapMoveTypeSpec swapSpec;
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 50;
swapSpec.sampleSize() = std::move(sampleSize);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Combined with Greedy Flags
Sample + early exit for maximum speed:
- Python
- C++
# Sample + early exit = maximum speed
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapMoveTypeSpec(
sampleSize=SampleSize(
defaultSampleSize=50,
),
greedyOnSrc=True, # Exit early when trying source objects
greedyOnDst=True, # Exit early when trying destination objects
),
]
)
)
)
void greedySampling() {
// Sample + early exit = maximum speed
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
SwapMoveTypeSpec swapSpec;
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 50;
swapSpec.sampleSize() = std::move(sampleSize);
swapSpec.greedyOnSrc() = true;
swapSpec.greedyOnDst() = true;
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Adaptive Sampling Strategy
Start with small sample, increase if needed:
- Python
- C++
# Start with small sample, increase in later stages
# Note: This would require LocalSearchStageSolverSpec
# Showing concept with single-stage for simplicity
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleMoveTypeSpec(),
SwapMoveTypeSpec(
sampleSize=SampleSize(
defaultSampleSize=100, # Start moderate
),
),
SwapMoveTypeSpec(
sampleSize=SampleSize(
defaultSampleSize=500, # Increase for refinement
),
),
]
)
)
)
void adaptiveSampling() {
// Start with small sample, increase in later stages
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
// Add Single
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleMoveTypeSpec() = SingleMoveTypeSpec{};
// Add SwapSampled with moderate sample
SwapMoveTypeSpec swapSpec1;
SampleSize sampleSize1;
sampleSize1.defaultSampleSize() = 100;
swapSpec1.sampleSize() = std::move(sampleSize1);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = swapSpec1;
// Add SwapSampled with larger sample for refinement
SwapMoveTypeSpec swapSpec2;
SampleSize sampleSize2;
sampleSize2.defaultSampleSize() = 500;
swapSpec2.sampleSize() = std::move(sampleSize2);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = swapSpec2;
solver.addSolver(solverSpec);
}
Performance Characteristics
Recommended Sample Sizes
| Problem Size | Objects per Container | Recommended Sample Size | Speedup vs Full |
|---|---|---|---|
| Medium | 100-1K | 50-100 | 10-100x |
| Large | 1K-10K | 100-500 | 100-1000x |
| Very Large | >10K | 500-1000 | 1000-10000x |
Sampling Strategy
Conservative (higher quality, slower):
- Sample size = sqrt(object count)
- Example: 10,000 objects → sample 100
Balanced (good trade-off):
- Sample size = 100-200 (fixed)
- Works well for most problems
Aggressive (maximum speed):
- Sample size = 50 or less
- Accept lower quality for speed
Comparison with Variants
| Move Type | Speed | Completeness | Use Case |
|---|---|---|---|
| Swap | Slow | Complete | Small/medium (<10K objects) |
| SwapSampled | Fast | Sampled | Large (>10K objects) |
| SwapFullContainers | Medium | Container-level | Full container swaps |
| SwapFullWithEmpty | Fast | Empty targets only | Consolidation |
Troubleshooting
Problem: SwapSampled not finding good moves
Diagnosis: Sample size too small, missing good swaps
Solutions:
- Increase
defaultSampleSize(try doubling it) - Use per-object sampling for important objects
- Try multiple runs with different random seeds
- Check if full Swap finds better moves (on smaller subset)
Problem: Still too slow
Diagnosis: Sample size too large or problem extremely large
Solutions:
- Reduce sample size (try 50 or less)
- Enable
greedyOnSrcandgreedyOnDstfor early exit - Restrict destinations with
destinationsToExplore - Consider if swaps are necessary (try other move types)
Problem: Solution quality much worse than expected
Diagnosis: Sampling missing critical swaps
Solutions:
- Increase sample size for better coverage
- Use stratified sampling (sample from different object groups)
- Combine with full Swap in multi-stage approach
- Try SingleEndChain as alternative
Problem: Non-deterministic results
Diagnosis: Random sampling produces different results each run
Solutions:
- Set
randomSeedin LocalSearchSolverSpec for reproducibility - Use larger sample size for more stable results
- Run multiple times and pick best result
- Accept variance as trade-off for speed
Choosing Sample Size
Rule of thumb: Sample size² should be > number of containers
Example calculation:
- 1000 containers
- sqrt(1000) ≈ 32
- Recommended sample size: 50-100
Validation:
- Run with sample size S
- Run with sample size 2×S
- If results similar → S is sufficient
- If results much better with 2×S → increase S
Related Move Types
Variants:
- Swap - Full exhaustive swap (no sampling)
- SwapFullContainers - Swap entire containers
- SwapFullWithEmpty - Move to empty containers
Alternatives:
- SingleEndChain - 2-move sequences (alternative to swaps)
- SingleFast - Faster single moves with early exit
Complementary:
- Single - Try before SwapSampled (simpler)
- Greedy flags - Combine for even faster convergence
Source Code
- Thrift definition:
interface/thrift/SolverSpecs.thrift:537(SwapMoveTypeSpec) - Sample size struct:
interface/thrift/SolverSpecs.thrift:458 - Implementation:
solver/moves/SwapMoveType.h - Tests:
solver/moves/tests/SwapMoveTypeTest.cpp
Next Steps
- Learn about Swap for full exhaustive search
- Try SwapFullContainers for container-level swaps
- Review Performance Guide for tuning sample sizes
- See Move Types Overview for choosing move types