Spaces:
Running
Running
"""Options manager for :class:`~.Poly` and public API functions. """ | |
from __future__ import annotations | |
__all__ = ["Options"] | |
from sympy.core import Basic, sympify | |
from sympy.polys.polyerrors import GeneratorsError, OptionError, FlagError | |
from sympy.utilities import numbered_symbols, topological_sort, public | |
from sympy.utilities.iterables import has_dups, is_sequence | |
import sympy.polys | |
import re | |
class Option: | |
"""Base class for all kinds of options. """ | |
option: str | None = None | |
is_Flag = False | |
requires: list[str] = [] | |
excludes: list[str] = [] | |
after: list[str] = [] | |
before: list[str] = [] | |
def default(cls): | |
return None | |
def preprocess(cls, option): | |
return None | |
def postprocess(cls, options): | |
pass | |
class Flag(Option): | |
"""Base class for all kinds of flags. """ | |
is_Flag = True | |
class BooleanOption(Option): | |
"""An option that must have a boolean value or equivalent assigned. """ | |
def preprocess(cls, value): | |
if value in [True, False]: | |
return bool(value) | |
else: | |
raise OptionError("'%s' must have a boolean value assigned, got %s" % (cls.option, value)) | |
class OptionType(type): | |
"""Base type for all options that does registers options. """ | |
def __init__(cls, *args, **kwargs): | |
def getter(self): | |
try: | |
return self[cls.option] | |
except KeyError: | |
return cls.default() | |
setattr(Options, cls.option, getter) | |
Options.__options__[cls.option] = cls | |
class Options(dict): | |
""" | |
Options manager for polynomial manipulation module. | |
Examples | |
======== | |
>>> from sympy.polys.polyoptions import Options | |
>>> from sympy.polys.polyoptions import build_options | |
>>> from sympy.abc import x, y, z | |
>>> Options((x, y, z), {'domain': 'ZZ'}) | |
{'auto': False, 'domain': ZZ, 'gens': (x, y, z)} | |
>>> build_options((x, y, z), {'domain': 'ZZ'}) | |
{'auto': False, 'domain': ZZ, 'gens': (x, y, z)} | |
**Options** | |
* Expand --- boolean option | |
* Gens --- option | |
* Wrt --- option | |
* Sort --- option | |
* Order --- option | |
* Field --- boolean option | |
* Greedy --- boolean option | |
* Domain --- option | |
* Split --- boolean option | |
* Gaussian --- boolean option | |
* Extension --- option | |
* Modulus --- option | |
* Symmetric --- boolean option | |
* Strict --- boolean option | |
**Flags** | |
* Auto --- boolean flag | |
* Frac --- boolean flag | |
* Formal --- boolean flag | |
* Polys --- boolean flag | |
* Include --- boolean flag | |
* All --- boolean flag | |
* Gen --- flag | |
* Series --- boolean flag | |
""" | |
__order__ = None | |
__options__: dict[str, type[Option]] = {} | |
def __init__(self, gens, args, flags=None, strict=False): | |
dict.__init__(self) | |
if gens and args.get('gens', ()): | |
raise OptionError( | |
"both '*gens' and keyword argument 'gens' supplied") | |
elif gens: | |
args = dict(args) | |
args['gens'] = gens | |
defaults = args.pop('defaults', {}) | |
def preprocess_options(args): | |
for option, value in args.items(): | |
try: | |
cls = self.__options__[option] | |
except KeyError: | |
raise OptionError("'%s' is not a valid option" % option) | |
if issubclass(cls, Flag): | |
if flags is None or option not in flags: | |
if strict: | |
raise OptionError("'%s' flag is not allowed in this context" % option) | |
if value is not None: | |
self[option] = cls.preprocess(value) | |
preprocess_options(args) | |
for key in dict(defaults): | |
if key in self: | |
del defaults[key] | |
else: | |
for option in self.keys(): | |
cls = self.__options__[option] | |
if key in cls.excludes: | |
del defaults[key] | |
break | |
preprocess_options(defaults) | |
for option in self.keys(): | |
cls = self.__options__[option] | |
for require_option in cls.requires: | |
if self.get(require_option) is None: | |
raise OptionError("'%s' option is only allowed together with '%s'" % (option, require_option)) | |
for exclude_option in cls.excludes: | |
if self.get(exclude_option) is not None: | |
raise OptionError("'%s' option is not allowed together with '%s'" % (option, exclude_option)) | |
for option in self.__order__: | |
self.__options__[option].postprocess(self) | |
def _init_dependencies_order(cls): | |
"""Resolve the order of options' processing. """ | |
if cls.__order__ is None: | |
vertices, edges = [], set() | |
for name, option in cls.__options__.items(): | |
vertices.append(name) | |
edges.update((_name, name) for _name in option.after) | |
edges.update((name, _name) for _name in option.before) | |
try: | |
cls.__order__ = topological_sort((vertices, list(edges))) | |
except ValueError: | |
raise RuntimeError( | |
"cycle detected in sympy.polys options framework") | |
def clone(self, updates={}): | |
"""Clone ``self`` and update specified options. """ | |
obj = dict.__new__(self.__class__) | |
for option, value in self.items(): | |
obj[option] = value | |
for option, value in updates.items(): | |
obj[option] = value | |
return obj | |
def __setattr__(self, attr, value): | |
if attr in self.__options__: | |
self[attr] = value | |
else: | |
super().__setattr__(attr, value) | |
def args(self): | |
args = {} | |
for option, value in self.items(): | |
if value is not None and option != 'gens': | |
cls = self.__options__[option] | |
if not issubclass(cls, Flag): | |
args[option] = value | |
return args | |
def options(self): | |
options = {} | |
for option, cls in self.__options__.items(): | |
if not issubclass(cls, Flag): | |
options[option] = getattr(self, option) | |
return options | |
def flags(self): | |
flags = {} | |
for option, cls in self.__options__.items(): | |
if issubclass(cls, Flag): | |
flags[option] = getattr(self, option) | |
return flags | |
class Expand(BooleanOption, metaclass=OptionType): | |
"""``expand`` option to polynomial manipulation functions. """ | |
option = 'expand' | |
requires: list[str] = [] | |
excludes: list[str] = [] | |
def default(cls): | |
return True | |
class Gens(Option, metaclass=OptionType): | |
"""``gens`` option to polynomial manipulation functions. """ | |
option = 'gens' | |
requires: list[str] = [] | |
excludes: list[str] = [] | |
def default(cls): | |
return () | |
def preprocess(cls, gens): | |
if isinstance(gens, Basic): | |
gens = (gens,) | |
elif len(gens) == 1 and is_sequence(gens[0]): | |
gens = gens[0] | |
if gens == (None,): | |
gens = () | |
elif has_dups(gens): | |
raise GeneratorsError("duplicated generators: %s" % str(gens)) | |
elif any(gen.is_commutative is False for gen in gens): | |
raise GeneratorsError("non-commutative generators: %s" % str(gens)) | |
return tuple(gens) | |
class Wrt(Option, metaclass=OptionType): | |
"""``wrt`` option to polynomial manipulation functions. """ | |
option = 'wrt' | |
requires: list[str] = [] | |
excludes: list[str] = [] | |
_re_split = re.compile(r"\s*,\s*|\s+") | |
def preprocess(cls, wrt): | |
if isinstance(wrt, Basic): | |
return [str(wrt)] | |
elif isinstance(wrt, str): | |
wrt = wrt.strip() | |
if wrt.endswith(','): | |
raise OptionError('Bad input: missing parameter.') | |
if not wrt: | |
return [] | |
return list(cls._re_split.split(wrt)) | |
elif hasattr(wrt, '__getitem__'): | |
return list(map(str, wrt)) | |
else: | |
raise OptionError("invalid argument for 'wrt' option") | |
class Sort(Option, metaclass=OptionType): | |
"""``sort`` option to polynomial manipulation functions. """ | |
option = 'sort' | |
requires: list[str] = [] | |
excludes: list[str] = [] | |
def default(cls): | |
return [] | |
def preprocess(cls, sort): | |
if isinstance(sort, str): | |
return [ gen.strip() for gen in sort.split('>') ] | |
elif hasattr(sort, '__getitem__'): | |
return list(map(str, sort)) | |
else: | |
raise OptionError("invalid argument for 'sort' option") | |
class Order(Option, metaclass=OptionType): | |
"""``order`` option to polynomial manipulation functions. """ | |
option = 'order' | |
requires: list[str] = [] | |
excludes: list[str] = [] | |
def default(cls): | |
return sympy.polys.orderings.lex | |
def preprocess(cls, order): | |
return sympy.polys.orderings.monomial_key(order) | |
class Field(BooleanOption, metaclass=OptionType): | |
"""``field`` option to polynomial manipulation functions. """ | |
option = 'field' | |
requires: list[str] = [] | |
excludes = ['domain', 'split', 'gaussian'] | |
class Greedy(BooleanOption, metaclass=OptionType): | |
"""``greedy`` option to polynomial manipulation functions. """ | |
option = 'greedy' | |
requires: list[str] = [] | |
excludes = ['domain', 'split', 'gaussian', 'extension', 'modulus', 'symmetric'] | |
class Composite(BooleanOption, metaclass=OptionType): | |
"""``composite`` option to polynomial manipulation functions. """ | |
option = 'composite' | |
def default(cls): | |
return None | |
requires: list[str] = [] | |
excludes = ['domain', 'split', 'gaussian', 'extension', 'modulus', 'symmetric'] | |
class Domain(Option, metaclass=OptionType): | |
"""``domain`` option to polynomial manipulation functions. """ | |
option = 'domain' | |
requires: list[str] = [] | |
excludes = ['field', 'greedy', 'split', 'gaussian', 'extension'] | |
after = ['gens'] | |
_re_realfield = re.compile(r"^(R|RR)(_(\d+))?$") | |
_re_complexfield = re.compile(r"^(C|CC)(_(\d+))?$") | |
_re_finitefield = re.compile(r"^(FF|GF)\((\d+)\)$") | |
_re_polynomial = re.compile(r"^(Z|ZZ|Q|QQ|ZZ_I|QQ_I|R|RR|C|CC)\[(.+)\]$") | |
_re_fraction = re.compile(r"^(Z|ZZ|Q|QQ)\((.+)\)$") | |
_re_algebraic = re.compile(r"^(Q|QQ)\<(.+)\>$") | |
def preprocess(cls, domain): | |
if isinstance(domain, sympy.polys.domains.Domain): | |
return domain | |
elif hasattr(domain, 'to_domain'): | |
return domain.to_domain() | |
elif isinstance(domain, str): | |
if domain in ['Z', 'ZZ']: | |
return sympy.polys.domains.ZZ | |
if domain in ['Q', 'QQ']: | |
return sympy.polys.domains.QQ | |
if domain == 'ZZ_I': | |
return sympy.polys.domains.ZZ_I | |
if domain == 'QQ_I': | |
return sympy.polys.domains.QQ_I | |
if domain == 'EX': | |
return sympy.polys.domains.EX | |
r = cls._re_realfield.match(domain) | |
if r is not None: | |
_, _, prec = r.groups() | |
if prec is None: | |
return sympy.polys.domains.RR | |
else: | |
return sympy.polys.domains.RealField(int(prec)) | |
r = cls._re_complexfield.match(domain) | |
if r is not None: | |
_, _, prec = r.groups() | |
if prec is None: | |
return sympy.polys.domains.CC | |
else: | |
return sympy.polys.domains.ComplexField(int(prec)) | |
r = cls._re_finitefield.match(domain) | |
if r is not None: | |
return sympy.polys.domains.FF(int(r.groups()[1])) | |
r = cls._re_polynomial.match(domain) | |
if r is not None: | |
ground, gens = r.groups() | |
gens = list(map(sympify, gens.split(','))) | |
if ground in ['Z', 'ZZ']: | |
return sympy.polys.domains.ZZ.poly_ring(*gens) | |
elif ground in ['Q', 'QQ']: | |
return sympy.polys.domains.QQ.poly_ring(*gens) | |
elif ground in ['R', 'RR']: | |
return sympy.polys.domains.RR.poly_ring(*gens) | |
elif ground == 'ZZ_I': | |
return sympy.polys.domains.ZZ_I.poly_ring(*gens) | |
elif ground == 'QQ_I': | |
return sympy.polys.domains.QQ_I.poly_ring(*gens) | |
else: | |
return sympy.polys.domains.CC.poly_ring(*gens) | |
r = cls._re_fraction.match(domain) | |
if r is not None: | |
ground, gens = r.groups() | |
gens = list(map(sympify, gens.split(','))) | |
if ground in ['Z', 'ZZ']: | |
return sympy.polys.domains.ZZ.frac_field(*gens) | |
else: | |
return sympy.polys.domains.QQ.frac_field(*gens) | |
r = cls._re_algebraic.match(domain) | |
if r is not None: | |
gens = list(map(sympify, r.groups()[1].split(','))) | |
return sympy.polys.domains.QQ.algebraic_field(*gens) | |
raise OptionError('expected a valid domain specification, got %s' % domain) | |
def postprocess(cls, options): | |
if 'gens' in options and 'domain' in options and options['domain'].is_Composite and \ | |
(set(options['domain'].symbols) & set(options['gens'])): | |
raise GeneratorsError( | |
"ground domain and generators interfere together") | |
elif ('gens' not in options or not options['gens']) and \ | |
'domain' in options and options['domain'] == sympy.polys.domains.EX: | |
raise GeneratorsError("you have to provide generators because EX domain was requested") | |
class Split(BooleanOption, metaclass=OptionType): | |
"""``split`` option to polynomial manipulation functions. """ | |
option = 'split' | |
requires: list[str] = [] | |
excludes = ['field', 'greedy', 'domain', 'gaussian', 'extension', | |
'modulus', 'symmetric'] | |
def postprocess(cls, options): | |
if 'split' in options: | |
raise NotImplementedError("'split' option is not implemented yet") | |
class Gaussian(BooleanOption, metaclass=OptionType): | |
"""``gaussian`` option to polynomial manipulation functions. """ | |
option = 'gaussian' | |
requires: list[str] = [] | |
excludes = ['field', 'greedy', 'domain', 'split', 'extension', | |
'modulus', 'symmetric'] | |
def postprocess(cls, options): | |
if 'gaussian' in options and options['gaussian'] is True: | |
options['domain'] = sympy.polys.domains.QQ_I | |
Extension.postprocess(options) | |
class Extension(Option, metaclass=OptionType): | |
"""``extension`` option to polynomial manipulation functions. """ | |
option = 'extension' | |
requires: list[str] = [] | |
excludes = ['greedy', 'domain', 'split', 'gaussian', 'modulus', | |
'symmetric'] | |
def preprocess(cls, extension): | |
if extension == 1: | |
return bool(extension) | |
elif extension == 0: | |
raise OptionError("'False' is an invalid argument for 'extension'") | |
else: | |
if not hasattr(extension, '__iter__'): | |
extension = {extension} | |
else: | |
if not extension: | |
extension = None | |
else: | |
extension = set(extension) | |
return extension | |
def postprocess(cls, options): | |
if 'extension' in options and options['extension'] is not True: | |
options['domain'] = sympy.polys.domains.QQ.algebraic_field( | |
*options['extension']) | |
class Modulus(Option, metaclass=OptionType): | |
"""``modulus`` option to polynomial manipulation functions. """ | |
option = 'modulus' | |
requires: list[str] = [] | |
excludes = ['greedy', 'split', 'domain', 'gaussian', 'extension'] | |
def preprocess(cls, modulus): | |
modulus = sympify(modulus) | |
if modulus.is_Integer and modulus > 0: | |
return int(modulus) | |
else: | |
raise OptionError( | |
"'modulus' must a positive integer, got %s" % modulus) | |
def postprocess(cls, options): | |
if 'modulus' in options: | |
modulus = options['modulus'] | |
symmetric = options.get('symmetric', True) | |
options['domain'] = sympy.polys.domains.FF(modulus, symmetric) | |
class Symmetric(BooleanOption, metaclass=OptionType): | |
"""``symmetric`` option to polynomial manipulation functions. """ | |
option = 'symmetric' | |
requires = ['modulus'] | |
excludes = ['greedy', 'domain', 'split', 'gaussian', 'extension'] | |
class Strict(BooleanOption, metaclass=OptionType): | |
"""``strict`` option to polynomial manipulation functions. """ | |
option = 'strict' | |
def default(cls): | |
return True | |
class Auto(BooleanOption, Flag, metaclass=OptionType): | |
"""``auto`` flag to polynomial manipulation functions. """ | |
option = 'auto' | |
after = ['field', 'domain', 'extension', 'gaussian'] | |
def default(cls): | |
return True | |
def postprocess(cls, options): | |
if ('domain' in options or 'field' in options) and 'auto' not in options: | |
options['auto'] = False | |
class Frac(BooleanOption, Flag, metaclass=OptionType): | |
"""``auto`` option to polynomial manipulation functions. """ | |
option = 'frac' | |
def default(cls): | |
return False | |
class Formal(BooleanOption, Flag, metaclass=OptionType): | |
"""``formal`` flag to polynomial manipulation functions. """ | |
option = 'formal' | |
def default(cls): | |
return False | |
class Polys(BooleanOption, Flag, metaclass=OptionType): | |
"""``polys`` flag to polynomial manipulation functions. """ | |
option = 'polys' | |
class Include(BooleanOption, Flag, metaclass=OptionType): | |
"""``include`` flag to polynomial manipulation functions. """ | |
option = 'include' | |
def default(cls): | |
return False | |
class All(BooleanOption, Flag, metaclass=OptionType): | |
"""``all`` flag to polynomial manipulation functions. """ | |
option = 'all' | |
def default(cls): | |
return False | |
class Gen(Flag, metaclass=OptionType): | |
"""``gen`` flag to polynomial manipulation functions. """ | |
option = 'gen' | |
def default(cls): | |
return 0 | |
def preprocess(cls, gen): | |
if isinstance(gen, (Basic, int)): | |
return gen | |
else: | |
raise OptionError("invalid argument for 'gen' option") | |
class Series(BooleanOption, Flag, metaclass=OptionType): | |
"""``series`` flag to polynomial manipulation functions. """ | |
option = 'series' | |
def default(cls): | |
return False | |
class Symbols(Flag, metaclass=OptionType): | |
"""``symbols`` flag to polynomial manipulation functions. """ | |
option = 'symbols' | |
def default(cls): | |
return numbered_symbols('s', start=1) | |
def preprocess(cls, symbols): | |
if hasattr(symbols, '__iter__'): | |
return iter(symbols) | |
else: | |
raise OptionError("expected an iterator or iterable container, got %s" % symbols) | |
class Method(Flag, metaclass=OptionType): | |
"""``method`` flag to polynomial manipulation functions. """ | |
option = 'method' | |
def preprocess(cls, method): | |
if isinstance(method, str): | |
return method.lower() | |
else: | |
raise OptionError("expected a string, got %s" % method) | |
def build_options(gens, args=None): | |
"""Construct options from keyword arguments or ... options. """ | |
if args is None: | |
gens, args = (), gens | |
if len(args) != 1 or 'opt' not in args or gens: | |
return Options(gens, args) | |
else: | |
return args['opt'] | |
def allowed_flags(args, flags): | |
""" | |
Allow specified flags to be used in the given context. | |
Examples | |
======== | |
>>> from sympy.polys.polyoptions import allowed_flags | |
>>> from sympy.polys.domains import ZZ | |
>>> allowed_flags({'domain': ZZ}, []) | |
>>> allowed_flags({'domain': ZZ, 'frac': True}, []) | |
Traceback (most recent call last): | |
... | |
FlagError: 'frac' flag is not allowed in this context | |
>>> allowed_flags({'domain': ZZ, 'frac': True}, ['frac']) | |
""" | |
flags = set(flags) | |
for arg in args.keys(): | |
try: | |
if Options.__options__[arg].is_Flag and arg not in flags: | |
raise FlagError( | |
"'%s' flag is not allowed in this context" % arg) | |
except KeyError: | |
raise OptionError("'%s' is not a valid option" % arg) | |
def set_defaults(options, **defaults): | |
"""Update options with default values. """ | |
if 'defaults' not in options: | |
options = dict(options) | |
options['defaults'] = defaults | |
return options | |
Options._init_dependencies_order() | |