Skip to content

Commit 24d8797

Browse files
authored
#566: refactoring prior to supporting lb iterations (#568)
* #566: added infrastructure for parameters for lb iterations * #566: corrected incorrect handling of Bool/int * #566: eliminated "distributions" that are no longer needed * #566: no notion of QOI needed here any longer * #566: removed distributions * #566: fix test to reflect removal of stats/distr * #566: fixed failure of centralized test * #566: fixed bug in computation of Hamming distance to optimum * #566: clarified doc * #566: eliminated now unused notion of QOIs * #566: reduced computation of stats to what is needed: max load and total work * #566: make stat update message debug only * #566: removed QOIs from test harness * #566: removed forgotten QOIs
1 parent f9d76b3 commit 24d8797

16 files changed

+125
-264
lines changed

config/conf.yaml

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ check_schema: false
99
work_model:
1010
name: AffineCombination
1111
parameters:
12-
alpha: 0.0
13-
beta: 1.0
12+
alpha: 1.0
13+
beta: 0.0
1414
gamma: 0.0
1515

1616
# Specify algorithm
17+
brute_force_optimization: true
1718
algorithm:
1819
name: InformAndTransfer
1920
phase_id: 0
@@ -35,14 +36,4 @@ write_JSON:
3536
suffix: json
3637
communications: true
3738
offline_LB_compatible: false
38-
# visualization:
39-
# x_ranks: 2
40-
# y_ranks: 2
41-
# z_ranks: 1
42-
# object_jitter: 0.5
43-
# rank_qoi: load
44-
# object_qoi: load
45-
# save_meshes: true
46-
# force_continuous_object_qoi: true
47-
# output_visualization_dir: ../output
48-
# output_visualization_file_stem: output_file
39+
lb_iterations: true

config/synthetic-blocks.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ work_model:
1414
gamma: 0.0
1515

1616
# Specify algorithm
17+
brute_force_optimization: true
1718
algorithm:
1819
name: InformAndTransfer
1920
phase_id: 0
@@ -43,7 +44,7 @@ visualization:
4344
output_visualization_file_stem: output_file
4445

4546
write_JSON:
46-
compressed: False
47+
compressed: false
4748
suffix: json
48-
communications: True
49-
offline_LB_compatible: True
49+
communications: true
50+
offline_LB_compatible: true

src/lbaf/Applications/LBAF_app.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,12 @@ def init_parameters(self, config: dict, base_dir: str):
198198
raise SystemExit(1) from e
199199

200200
# Retrieve optional parameters
201-
self.json_params[
202-
"json_output_suffix"] = wrt_json.get("suffix", "json")
203-
self.json_params[
204-
"communications"] = wrt_json.get("communications", False)
205-
self.json_params[
206-
"offline_LB_compatible"] = wrt_json.get(
207-
"offline_LB_compatible", False)
201+
for k_out, k_wrt, v_def in [
202+
("json_output_suffix", "suffix", "json"),
203+
("communications", "communications", False),
204+
("offline_LB_compatible", "offline_LB_compatible", False),
205+
("lb_iterations", "lb_iterations", False)]:
206+
self.json_params[k_out] = wrt_json.get(k_wrt, v_def)
208207

209208
def check_parameters(self):
210209
"""Checks after initialization."""
@@ -412,7 +411,7 @@ def __print_statistics(self, phase: Phase, phase_name: str, work_model: WorkMode
412411
# Return rank load and work statistics
413412
return l_stats, w_stats
414413

415-
def __print_QOI(self) -> int: # pylint:disable=C0103:invalid-name
414+
def __print_qoi(self) -> int:
416415
"""Print list of implemented QOI based on the '-verbosity' command line argument."""
417416
verbosity = int(self.__args.verbose)
418417

@@ -463,7 +462,7 @@ def run(self, cfg=None, cfg_dir=None):
463462
self.__parse_args()
464463

465464
# Print list of implemented QOI (according to verbosity argument)
466-
self.__print_QOI()
465+
self.__print_qoi()
467466

468467
# Warn if default configuration is used because not set as argument
469468
if self.__args.configuration is None:
@@ -568,8 +567,7 @@ def run(self, cfg=None, cfg_dir=None):
568567
objects = initial_phase.get_objects()
569568
alpha, beta, gamma = [
570569
self.__parameters.work_model.get("parameters", {}).get(k)
571-
for k in ("alpha", "beta", "gamma")
572-
]
570+
for k in ("alpha", "beta", "gamma")]
573571
_n_a, _w_min_max, a_min_max = lbstats.compute_min_max_arrangements_work(
574572
objects, alpha, beta, gamma, n_ranks, logger=self.__logger)
575573
else:
@@ -581,16 +579,16 @@ def run(self, cfg=None, cfg_dir=None):
581579
self.__parameters.work_model,
582580
self.__parameters.algorithm,
583581
a_min_max,
584-
self.__logger,
585-
self.__parameters.rank_qoi if self.__parameters.rank_qoi is not None else '',
586-
self.__parameters.object_qoi if self.__parameters.object_qoi is not None else '')
582+
self.__logger)
587583

588584
# Execute runtime for specified phases
589-
offline_LB_compatible = self.__parameters.json_params.get( # pylint:disable=C0103:invalid-name;not lowercase
585+
offline_LB_compatible = self.__parameters.json_params.get(
590586
"offline_LB_compatible", False)
587+
lb_iterations = self.__parameters.json_params.get(
588+
"lb_iterations", False)
591589
rebalanced_phase = runtime.execute(
592590
self.__parameters.algorithm.get("phase_id", 0),
593-
offline_LB_compatible)
591+
1 if offline_LB_compatible else 0)
594592

595593
# Instantiate phase to VT file writer when requested
596594
if self.__json_writer:
@@ -619,7 +617,8 @@ def run(self, cfg=None, cfg_dir=None):
619617
f"Writing all ({len(phases)}) phases for offline load-balancing")
620618
self.__json_writer.write(phases)
621619
else:
622-
self.__logger.info(f"Writing single phase {phase_id} to JSON files")
620+
# Add new phase when load balancing when offline mode not selected
621+
self.__logger.info(f"Creating rebalanced phase {phase_id}")
623622
self.__json_writer.write(
624623
{phase_id: rebalanced_phase})
625624

src/lbaf/Execution/lbsAlgorithmBase.py

Lines changed: 14 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,11 @@ class AlgorithmBase:
5858
_work_model: WorkModelBase
5959
_logger: Logger
6060

61-
def __init__(self, work_model: WorkModelBase, parameters: dict, logger: Logger, rank_qoi: str, object_qoi: str):
61+
def __init__(self, work_model: WorkModelBase, parameters: dict, logger: Logger):
6262
"""Class constructor.
6363
6464
:param work_model: a WorkModelBase instance
6565
:param parameters: a dictionary of parameters
66-
:param rank_qoi: rank QOI to track
67-
:param object_qoi: object QOI to track.
6866
"""
6967
# Assert that a logger instance was passed
7068
if not isinstance(logger, Logger):
@@ -83,40 +81,18 @@ def __init__(self, work_model: WorkModelBase, parameters: dict, logger: Logger,
8381
self._logger.error("Could not create an algorithm without a dictionary of parameters")
8482
raise SystemExit(1)
8583

86-
# Assert that quantity of interest names are string
87-
if rank_qoi and not isinstance(rank_qoi, str):
88-
self._logger.error("Could not create an algorithm with non-string rank QOI name")
89-
raise SystemExit(1)
90-
self.__rank_qoi = rank_qoi
91-
if object_qoi and not isinstance(object_qoi, str):
92-
self._logger.error("Could not create an algorithm with non-string object QOI name")
93-
raise SystemExit(1)
94-
self.__object_qoi = object_qoi
95-
self._logger.info(
96-
f"Created base algorithm tracking rank {rank_qoi} and object {object_qoi}")
97-
9884
# Initially no phase is assigned for processing
9985
self._rebalanced_phase = None
10086

10187
# Save the initial communications data
10288
self._initial_communications = {}
10389

104-
# Map global statistical QOIs to their computation methods
90+
# Map rank statistics to their respective computation methods
10591
self.__statistics = {
10692
("ranks", lambda x: x.get_load()): {
107-
"minimum load": "minimum",
108-
"maximum load": "maximum",
109-
"load variance": "variance",
110-
"load imbalance": "imbalance"},
111-
("largest_volumes", lambda x: x): {
112-
"number of communication edges": "cardinality",
113-
"maximum largest directed volume": "maximum",
114-
"total largest directed volume": "sum"},
115-
("ranks", lambda x: self._work_model.compute(x)): { #pylint:disable=W0108
116-
"minimum work": "minimum",
117-
"maximum work": "maximum",
118-
"total work": "sum",
119-
"work variance": "variance"}}
93+
"maximum load": "maximum"},
94+
("ranks", lambda x: self._work_model.compute(x)): {
95+
"total work": "sum"}}
12096

12197
def get_rebalanced_phase(self):
12298
"""Return phased assigned for processing by algoritm."""
@@ -131,9 +107,7 @@ def factory(
131107
algorithm_name:str,
132108
parameters: dict,
133109
work_model: WorkModelBase,
134-
logger: Logger,
135-
rank_qoi: str,
136-
object_qoi:str):
110+
logger: Logger):
137111
"""Instantiate the necessary concrete algorithm."""
138112
# Load up available algorithms
139113
# pylint:disable=W0641:possibly-unused-variable,C0415:import-outside-toplevel
@@ -148,90 +122,22 @@ def factory(
148122
try:
149123
# Instantiate and return object
150124
algorithm = locals()[algorithm_name + "Algorithm"]
151-
return algorithm(work_model, parameters, logger, rank_qoi, object_qoi)
125+
return algorithm(work_model, parameters, logger)
152126
except Exception as e:
153127
# Otherwise, error out
154128
logger.error(f"Could not create an algorithm with name {algorithm_name}")
155129
raise SystemExit(1) from e
156130

157-
def _update_distributions_and_statistics(self, distributions: dict, statistics: dict):
158-
"""Compute and update run distributions and statistics."""
159-
# Create or update distributions of object quantities of interest
160-
for object_qoi_name in tuple({"load", self.__object_qoi}):
161-
if not object_qoi_name:
162-
continue
163-
try:
164-
distributions.setdefault(f"object {object_qoi_name}", []).append(
165-
{o.get_id(): getattr(o, f"get_{object_qoi_name}")()
166-
for o in self._rebalanced_phase.get_objects()})
167-
except AttributeError as err:
168-
self.__print_QOI("obj")
169-
self._logger.error(f"Invalid object_qoi name '{object_qoi_name}'")
170-
raise SystemExit(1) from err
171-
172-
# Create or update distributions of rank quantities of interest
173-
for rank_qoi_name in tuple({"objects", "load", self.__rank_qoi}):
174-
if not rank_qoi_name or rank_qoi_name == "work":
175-
continue
176-
try:
177-
distributions.setdefault(f"rank {rank_qoi_name}", []).append(
178-
[getattr(p, f"get_{rank_qoi_name}")()
179-
for p in self._rebalanced_phase.get_ranks()])
180-
except AttributeError as err:
181-
self.__print_QOI("rank")
182-
self._logger.error(f"Invalid rank_qoi name '{rank_qoi_name}'")
183-
raise SystemExit(1) from err
184-
distributions.setdefault("rank work", []).append(
185-
[self._work_model.compute(p) for p in self._rebalanced_phase.get_ranks()])
186-
187-
# Create or update distributions of edge quantities of interest
188-
distributions.setdefault("sent", []).append(dict(
189-
self._rebalanced_phase.get_edge_maxima().items()))
190-
131+
def _update_statistics(self, statistics: dict):
132+
"""Compute and update run statistics."""
191133
# Create or update statistics dictionary entries
192134
for (support, getter), stat_names in self.__statistics.items():
193135
for k, v in stat_names.items():
136+
self._logger.debug(f"Updating {k} statistics for {support}")
194137
stats = compute_function_statistics(
195138
getattr(self._rebalanced_phase, f"get_{support}")(), getter)
196139
statistics.setdefault(k, []).append(getattr(stats, f"get_{v}")())
197140

198-
def __print_QOI(self,rank_or_obj): # pylint:disable=invalid-name
199-
"""Print list of implemented QOI when invalid QOI is given."""
200-
# Initialize file paths
201-
current_path = os.path.abspath(__file__)
202-
target_dir = os.path.join(
203-
os.path.dirname(os.path.dirname(current_path)), "Model")
204-
rank_script_name = "lbsRank.py"
205-
object_script_name = "lbsObject.py"
206-
207-
if rank_or_obj == "rank":
208-
# Create list of all Rank QOI (lbsRank.get_*)
209-
r_qoi_list = ["work"]
210-
with open(os.path.join(target_dir, rank_script_name), 'r', encoding="utf-8") as f:
211-
lines = f.readlines()
212-
for line in lines:
213-
if line[8:12] == "get_":
214-
r_qoi_list.append(line[12:line.find("(")])
215-
216-
# Print QOI based on verbosity level
217-
self._logger.error("List of all possible Rank QOI:")
218-
for r_qoi in r_qoi_list:
219-
self._logger.error("\t" + r_qoi)
220-
221-
if rank_or_obj == "obj":
222-
# Create list of all Object QOI (lbsObject.get_*)
223-
o_qoi_list = []
224-
with open(os.path.join(target_dir, object_script_name), 'r', encoding="utf-8") as f:
225-
lines = f.readlines()
226-
for line in lines:
227-
if line[8:12] == "get_":
228-
o_qoi_list.append(line[12:line.find("(")])
229-
230-
# Print QOI based on verbosity level
231-
self._logger.error("List of all possible Object QOI:")
232-
for o_qoi in o_qoi_list:
233-
self._logger.error("\t" + o_qoi)
234-
235141
def _report_final_mapping(self, logger):
236142
"""Report final rank object mapping in debug mode."""
237143
for rank in self._rebalanced_phase.get_ranks():
@@ -253,7 +159,7 @@ def _report_final_mapping(self, logger):
253159
logger.debug(
254160
f"object {k.get_id()} on rank {k.get_rank_id()}: {v}")
255161

256-
def _initialize(self, p_id, phases, distributions, statistics):
162+
def _initialize(self, p_id, phases, statistics):
257163
"""Factor out pre-execution checks and initalizations."""
258164
# Ensure that a list with at least one phase was provided
259165
if not isinstance(phases, dict) or not all(
@@ -286,16 +192,15 @@ def _initialize(self, p_id, phases, distributions, statistics):
286192
f"across {self._rebalanced_phase.get_number_of_ranks()} ranks "
287193
f"into phase {self._rebalanced_phase.get_id()}")
288194

289-
# Initialize run distributions and statistics
290-
self._update_distributions_and_statistics(distributions, statistics)
195+
# Initialize run statistics
196+
self._update_statistics(statistics)
291197

292198
@abc.abstractmethod
293-
def execute(self, p_id, phases, distributions, statistics, a_min_max):
199+
def execute(self, p_id, phases, statistics, a_min_max):
294200
"""Execute balancing algorithm on Phase instance.
295201
296202
:param: p_id: index of phase to be rebalanced (all if equal to _)
297203
:param: phases: list of Phase instances
298-
:param: distributions: dictionary of load-varying variables
299204
:param: statistics: dictionary of statistics
300205
:param: a_min_max: possibly empty list of optimal arrangements.
301206
"""

src/lbaf/Execution/lbsBruteForceAlgorithm.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,24 @@
5151
class BruteForceAlgorithm(AlgorithmBase):
5252
"""A concrete class for the brute force optimization algorithm"""
5353

54-
def __init__(self, work_model, parameters: dict, lgr: Logger, rank_qoi: str, object_qoi: str):
54+
def __init__(self, work_model, parameters: dict, lgr: Logger):
5555
"""Class constructor.
5656
5757
:param work_model: a WorkModelBase instance
5858
:param parameters: a dictionary of parameters
59-
:param rank_qoi: rank QOI to track
60-
:param object_qoi: object QOI to track.
6159
"""
6260
# Call superclass init
63-
super().__init__(work_model, parameters, lgr, rank_qoi, object_qoi)
61+
super().__init__(work_model, parameters, lgr)
6462

6563
# Assign optional parameters
6664
self.__skip_transfer = parameters.get("skip_transfer", False)
6765
self._logger.info(
6866
f"Instantiated {'with' if self.__skip_transfer else 'without'} transfer stage skipping")
6967

70-
def execute(self, p_id: int, phases: list, distributions: dict, statistics: dict, _):
68+
def execute(self, p_id: int, phases: list, statistics: dict, _):
7169
"""Execute brute force optimization algorithm on phase with index p_id."""
7270
# Perform pre-execution checks and initializations
73-
self._initialize(p_id, phases, distributions, statistics)
71+
self._initialize(p_id, phases, statistics)
7472
self._logger.info("Starting brute force optimization")
7573
initial_phase = phases[min(phases.keys())]
7674
phase_ranks = initial_phase.get_ranks()
@@ -113,8 +111,8 @@ def execute(self, p_id: int, phases: list, distributions: dict, statistics: dict
113111
# Report on object transfers
114112
self._logger.info(f"{n_transfers} transfers occurred")
115113

116-
# Update run distributions and statistics
117-
self._update_distributions_and_statistics(distributions, statistics)
114+
# Update run statistics
115+
self._update_statistics(statistics)
118116

119117
# Report final mapping in debug mode
120118
self._report_final_mapping(self._logger)

0 commit comments

Comments
 (0)