Skip to content

Instantly share code, notes, and snippets.

@odashi
Created December 5, 2025 04:15
Show Gist options
  • Select an option

  • Save odashi/077234b7adb8fc30340fd59397074e13 to your computer and use it in GitHub Desktop.

Select an option

Save odashi/077234b7adb8fc30340fd59397074e13 to your computer and use it in GitHub Desktop.
Config generator for fractional factorial design
# Copyright (c) 2025 Yusuke Oda.
# License: MIT
#
# Usage:
#
# $ python generate_fracfact_config.py --num-total-factors 8 --resolution 4 --print-binary
# INFO:root:Determined number of base factors: 4
# 00000000
# 10001110
# 01001101
# 11000011
# 00101011
# 10100101
# 01100110
# 11101000
# 00010111
# 10011001
# 01011010
# 11010100
# 00111100
# 10110010
# 01110001
# 11111111
import argparse
import heapq
import logging
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser()
p.add_argument("--num-total-factors", type=int, required=True, help="Number of total factors")
p.add_argument("--resolution", type=int, required=True, help="Resolution of experiment")
p.add_argument("--experiment-id", type=int, default=None, help="Experiment ID of fractal factorization")
p.add_argument("--num-base-factors", type=int, default=None, help="Number of independent factors. If not set, it will be automatically determined.")
p.add_argument("--print-binary", action="store_true", help="Print factors in binary format")
args = p.parse_args()
if args.num_base_factors is not None and args.num_base_factors < 1:
raise ValueError("Number of base factors must be at least 1.")
if args.resolution < 3:
raise ValueError("Resolution must be at least 3.")
if args.num_base_factors is not None and args.num_total_factors >= 2 ** args.num_base_factors:
raise ValueError(f"Too many total factors for the given number of base factors: {args.num_total_factors} >= 2^{args.num_base_factors}")
if (
args.experiment_id is not None
and args.num_base_factors is not None
and args.experiment_id >= 2 ** args.num_base_factors
):
raise ValueError(f"Experiment ID out of range for the given number of base factors: {args.experiment_id} >= 2^{args.num_base_factors}")
return args
def generate_factors(num_base_factors: int, num_total_factors: int, resolution: int):
factors = []
distances = [99999999] * (2 ** num_base_factors)
distances[0] = -1 # No factor experiment: not used
max_distance = resolution - 2
for f in range(num_base_factors):
factor = 1 << f
factors.append(factor)
distances[factor] = 0
logging.debug(f"New factor: {factor:0{num_base_factors}b}")
if len(factors) >= num_total_factors:
return factors
queue = [(0, a) for a in factors]
while queue:
cur_dist, cur_factor = heapq.heappop(queue)
if cur_dist > distances[cur_factor]:
continue
if cur_dist >= max_distance:
factors.append(cur_factor)
cur_dist = distances[cur_factor] = 0
logging.debug(f"New factor: {cur_factor:0{num_base_factors}b}")
if len(factors) >= num_total_factors:
return factors
for factor in factors:
new_factor = cur_factor ^ factor
new_dist = cur_dist + 1
if new_dist < distances[new_factor]:
distances[new_factor] = new_dist
heapq.heappush(queue, (new_dist, new_factor))
raise ValueError("Could not generate enough factors within the given resolution.")
def main():
args = parse_args()
if args.num_base_factors is not None:
num_base_factors = args.num_base_factors
factors = generate_factors(num_base_factors, args.num_total_factors, args.resolution)
else:
# Automatically determine the number of base factors
for num_base_factors in range(1, 21):
try:
factors = generate_factors(num_base_factors, args.num_total_factors, args.resolution)
logging.info(f"Determined number of base factors: {num_base_factors}")
break
except ValueError:
logging.debug(f"Failed to generate factors with {num_base_factors} base factors.")
for i, f in enumerate(factors):
logging.debug(f"Factor {i}: {f:0{num_base_factors}b}")
if args.experiment_id is not None:
exp_conf = [(f & args.experiment_id).bit_count() % 2 for f in factors]
if args.print_binary:
print("".join(str(x) for x in exp_conf))
else:
print(" ".join(str(i) for i, x in enumerate(exp_conf) if x == 1))
else:
for exp_id in range(2 ** num_base_factors):
exp_conf = [(f & exp_id).bit_count() % 2 for f in factors]
if args.print_binary:
print("".join(str(x) for x in exp_conf))
else:
print(" ".join(str(i) for i, x in enumerate(exp_conf) if x == 1))
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment