139 lines
4.0 KiB
Python
139 lines
4.0 KiB
Python
"""
|
|
Generate parameter combinations from parsed parameter lists.
|
|
|
|
Two modes:
|
|
- cartesian: All combinations (every permutation)
|
|
- linear_zip: Paired values, cycling shorter lists to match the longest
|
|
|
|
Each combination is a list of tuples:
|
|
[(param_name, param_value, target_info), ...]
|
|
|
|
where target_info is a dict with keys:
|
|
target_node_title, target_widget_name
|
|
"""
|
|
|
|
import itertools
|
|
|
|
|
|
def cartesian(parsed_params: list[dict]) -> list[list[tuple]]:
|
|
"""
|
|
Cartesian product of all parameter value lists.
|
|
|
|
Args:
|
|
parsed_params: List of dicts, each with keys:
|
|
- name: str (parameter display name)
|
|
- parsed_values: list (the values to iterate)
|
|
- target_node_title: str
|
|
- target_widget_name: str
|
|
|
|
Returns:
|
|
List of combinations. Each combination is a list of
|
|
(param_name, param_value, target_info) tuples.
|
|
|
|
Example:
|
|
params = [
|
|
{"name": "steps", "parsed_values": [10, 20],
|
|
"target_node_title": "KSampler", "target_widget_name": "steps"},
|
|
{"name": "cfg", "parsed_values": [5, 7, 9],
|
|
"target_node_title": "KSampler", "target_widget_name": "cfg"},
|
|
]
|
|
Result: 6 combinations (2 x 3)
|
|
"""
|
|
if not parsed_params:
|
|
return []
|
|
|
|
# Filter out params with empty value lists
|
|
active_params = [p for p in parsed_params if p.get("parsed_values")]
|
|
if not active_params:
|
|
return []
|
|
|
|
value_lists = [p["parsed_values"] for p in active_params]
|
|
target_infos = [
|
|
{
|
|
"target_node_title": p["target_node_title"],
|
|
"target_widget_name": p["target_widget_name"],
|
|
}
|
|
for p in active_params
|
|
]
|
|
names = [p["name"] for p in active_params]
|
|
|
|
combinations = []
|
|
for combo_values in itertools.product(*value_lists):
|
|
combination = [
|
|
(names[i], combo_values[i], target_infos[i])
|
|
for i in range(len(active_params))
|
|
]
|
|
combinations.append(combination)
|
|
|
|
return combinations
|
|
|
|
|
|
def linear_zip(parsed_params: list[dict]) -> list[list[tuple]]:
|
|
"""
|
|
Zip parameters together, cycling shorter lists to match the longest.
|
|
|
|
Args:
|
|
parsed_params: Same format as cartesian()
|
|
|
|
Returns:
|
|
List of combinations (same format as cartesian).
|
|
Length equals the longest parameter value list.
|
|
Shorter lists are cycled.
|
|
|
|
Example:
|
|
steps: [10, 20, 30]
|
|
cfg: [5, 7]
|
|
Result: 3 combinations: (10,5), (20,7), (30,5)
|
|
"""
|
|
if not parsed_params:
|
|
return []
|
|
|
|
active_params = [p for p in parsed_params if p.get("parsed_values")]
|
|
if not active_params:
|
|
return []
|
|
|
|
max_len = max(len(p["parsed_values"]) for p in active_params)
|
|
if max_len == 0:
|
|
return []
|
|
|
|
target_infos = [
|
|
{
|
|
"target_node_title": p["target_node_title"],
|
|
"target_widget_name": p["target_widget_name"],
|
|
}
|
|
for p in active_params
|
|
]
|
|
names = [p["name"] for p in active_params]
|
|
|
|
combinations = []
|
|
for i in range(max_len):
|
|
combination = []
|
|
for j, param in enumerate(active_params):
|
|
values = param["parsed_values"]
|
|
# Cycle: use modulo to wrap around
|
|
value = values[i % len(values)]
|
|
combination.append((names[j], value, target_infos[j]))
|
|
combinations.append(combination)
|
|
|
|
return combinations
|
|
|
|
|
|
def get_axis_labels(combinations: list[list[tuple]], param_names: list[str]) -> dict:
|
|
"""
|
|
Extract unique values for each parameter from the combinations.
|
|
Useful for grid labeling.
|
|
|
|
Returns:
|
|
Dict mapping param_name -> list of unique values in order.
|
|
"""
|
|
labels = {name: [] for name in param_names}
|
|
seen = {name: set() for name in param_names}
|
|
|
|
for combo in combinations:
|
|
for name, value, _ in combo:
|
|
if name in labels and value not in seen[name]:
|
|
seen[name].add(value)
|
|
labels[name].append(value)
|
|
|
|
return labels
|