Skip to content

Commit a0fd39a

Browse files
committed
improving things
1 parent 228276b commit a0fd39a

2 files changed

Lines changed: 85 additions & 53 deletions

File tree

petsctools/options.py

Lines changed: 41 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ def get_default_options(default_options_set: DefaultOptionSet,
280280
return default_options
281281

282282

283+
# TODO: Add note about how we 'freeze' options at instantiation
283284
class OptionsManager:
284285
"""Class that helps with managing setting PETSc options.
285286
@@ -405,76 +406,62 @@ class OptionsManager:
405406
AppContextManager
406407
"""
407408

408-
count = itertools.count()
409+
count = 0
409410

410411
def __init__(self, parameters: dict,
411412
options_prefix: str | None = None,
412413
default_prefix: str | None = None,
413414
default_options_set: DefaultOptionSet | None = None,
414415
appmngr: AppContextManager | None = None):
415-
super().__init__()
416416
if parameters is None:
417417
parameters = {}
418418
else:
419419
# Convert nested dicts
420420
parameters = flatten_parameters(parameters)
421421

422422
# If no prefix is provided generate a default prefix
423-
# and ignore any command line options
424423
if options_prefix is None:
425424
default_prefix = default_prefix or "petsctools_"
426425
default_prefix = _validate_prefix(default_prefix)
427-
self.options_prefix = f"{default_prefix}{next(self.count)}_"
428-
self.parameters = parameters
429-
self.to_delete = set(parameters)
430-
426+
options_prefix = f"{default_prefix}{self.count}_"
427+
self.count += 1
428+
unsafe_prefix = True
431429
else:
432430
options_prefix = _validate_prefix(options_prefix)
433-
self.options_prefix = options_prefix
434-
435-
# Are we part of a solver set sharing defaults?
436-
if default_options_set:
437-
if options_prefix not in default_options_set.custom_prefixes:
438-
raise ValueError(
439-
f"The options_prefix {options_prefix} must be one"
440-
f" of the custom_prefixes of the DefaultOptionSet"
441-
f" {default_options_set.custom_prefixes}")
442-
default_options = get_default_options(
443-
default_options_set, self.options_object)
444-
else:
445-
default_options = {}
446-
447-
# Note: we need to know which parameters to_delete
448-
# so we need to exclude the relevant command line
449-
# options when combining the parameters from the
450-
# defaults and the source code.
451-
452-
# Start building parameters from the defaults so
453-
# that they will overwritten by any other source.
454-
self.parameters = {
455-
k: v
456-
for k, v in default_options.items()
457-
if options_prefix + k not in get_commandline_options()
458-
}
459-
460-
# Update using the parameters passed in the code but
461-
# exclude those options from the dict that were passed
462-
# on the commandline because those have global scope and are
463-
# not under the control of the options manager.
464-
self.parameters.update({
465-
k: v
466-
for k, v in parameters.items()
467-
if options_prefix + k not in get_commandline_options()
468-
})
469-
self.to_delete = set(self.parameters)
470-
471-
# Now update parameters from options, so that they're
472-
# available to solver setup (for, e.g., matrix-free).
473-
# Can't ask for the prefixed guy in the options object,
474-
# since that does not DTRT for flag options.
475-
for k, v in self.options_object.getAll().items():
476-
if k.startswith(self.options_prefix):
477-
self.parameters[k[len(self.options_prefix):]] = v
431+
unsafe_prefix = False
432+
433+
# Are we part of a solver set sharing defaults?
434+
if default_options_set:
435+
if options_prefix not in default_options_set.custom_prefixes:
436+
raise ValueError(
437+
f"The options_prefix {options_prefix} must be one"
438+
f" of the custom_prefixes of the DefaultOptionSet"
439+
f" {default_options_set.custom_prefixes}")
440+
default_options = get_default_options(
441+
default_options_set, self.options_object)
442+
else:
443+
default_options = {}
444+
445+
# Start building parameters from the defaults so
446+
# that they will overwritten by any other source.
447+
parameters = default_options | parameters
448+
449+
# The parameters to drop from the global options when we leave the
450+
# inserted_options context. This is everything except for options
451+
# passed on the command line.
452+
to_delete = set(parameters.keys())
453+
for k, v in self.options_object.getAll().items():
454+
if k.startswith(options_prefix):
455+
if unsafe_prefix:
456+
print("WARN")
457+
parameters[k[len(options_prefix):]] = v
458+
if k in to_delete:
459+
# option is set globally, don't drop when we leave the context
460+
to_delete.delete(k)
461+
462+
self.parameters = parameters
463+
self.to_delete = to_delete
464+
self.options_prefix = options_prefix
478465

479466
self._setfromoptions = False
480467

@@ -562,12 +549,13 @@ def inserted_options(self):
562549
for k in self.to_delete:
563550
if self.options_object.used(self.options_prefix + k):
564551
self._used_options.add(k)
565-
del self.options_object[self.options_prefix + k]
552+
del self.options_object[k]
566553

567554
@functools.cached_property
568555
def options_object(self):
569556
from petsc4py import PETSc
570557

558+
# We can't pass the prefix here because that doesn't DTRT for flag options
571559
return PETSc.Options()
572560

573561

tests/test_options.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,47 @@ def test_default_options():
157157
assert options2.parameters["opt2"] == "2"
158158
assert options2.parameters["opt3"] == "3"
159159
assert options2.parameters["opt4"] == "6"
160+
161+
162+
@pytest.mark.parametrize("options_prefix", (None, "", "custom_"))
163+
def test_commandline_options(options_prefix):
164+
from petsc4py import PETSc
165+
166+
if options_prefix is None:
167+
true_prefix = f"petsctools_{petsctools.OptionsManager.count}_"
168+
else:
169+
true_prefix = options_prefix
170+
171+
# Put some options in the database as though they were passed by a user on
172+
# the command line
173+
options = PETSc.Options()
174+
options["opt1"] = "unused"
175+
options[f"{true_prefix}opt2"] = "will_overwrite"
176+
options[f"{true_prefix}opt3"] = "extra"
177+
178+
default_params = {
179+
# this will get ignored because we pass something on the command line instead
180+
"opt2": "default_opt2",
181+
# this will be inserted and popped from the database
182+
"opt4": "default_opt4",
183+
}
184+
om = petsctools.OptionsManager(default_params, options_prefix=options_prefix)
185+
186+
assert om.options_prefix == true_prefix
187+
188+
with om.inserted_options():
189+
assert options["opt1"] == "unused"
190+
assert options[f"{om.options_prefix}opt2"] == "will_overwrite"
191+
assert options[f"{om.options_prefix}opt3"] == "extra"
192+
assert options[f"{om.options_prefix}opt4"] == "default_opt4"
193+
194+
# make sure the command line options are persistent
195+
assert options["opt1"] == "unused"
196+
assert options[f"{om.options_prefix}opt2"] == "will_overwrite"
197+
assert options[f"{om.options_prefix}opt3"] == "extra"
198+
assert f"{om.options_prefix}opt4" not in options
199+
200+
# TODO
201+
# make sure we warn on usage if prefix is None
202+
# and the appctx too
203+

0 commit comments

Comments
 (0)