123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790 |
- """Options manager for :class:`~.Poly` and public API functions. """
- __all__ = ["Options"]
- from typing import Dict as tDict, Type
- from typing import List, Optional
- 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 = None # type: Optional[str]
- is_Flag = False
- requires = [] # type: List[str]
- excludes = [] # type: List[str]
- after = [] # type: List[str]
- before = [] # type: List[str]
- @classmethod
- def default(cls):
- return None
- @classmethod
- def preprocess(cls, option):
- return None
- @classmethod
- 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. """
- @classmethod
- 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):
- @property
- def getter(self):
- try:
- return self[cls.option]
- except KeyError:
- return cls.default()
- setattr(Options, cls.option, getter)
- Options.__options__[cls.option] = cls
- @public
- 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__ = {} # type: tDict[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, value in dict(defaults).items():
- 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)
- @classmethod
- 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)
- for _name in option.after:
- edges.add((_name, name))
- for _name in option.before:
- edges.add((name, _name))
- 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)
- @property
- 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
- @property
- def options(self):
- options = {}
- for option, cls in self.__options__.items():
- if not issubclass(cls, Flag):
- options[option] = getattr(self, option)
- return options
- @property
- 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 = [] # type: List[str]
- excludes = [] # type: List[str]
- @classmethod
- def default(cls):
- return True
- class Gens(Option, metaclass=OptionType):
- """``gens`` option to polynomial manipulation functions. """
- option = 'gens'
- requires = [] # type: List[str]
- excludes = [] # type: List[str]
- @classmethod
- def default(cls):
- return ()
- @classmethod
- 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 = [] # type: List[str]
- excludes = [] # type: List[str]
- _re_split = re.compile(r"\s*,\s*|\s+")
- @classmethod
- 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 [ gen for gen in 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 = [] # type: List[str]
- excludes = [] # type: List[str]
- @classmethod
- def default(cls):
- return []
- @classmethod
- 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 = [] # type: List[str]
- excludes = [] # type: List[str]
- @classmethod
- def default(cls):
- return sympy.polys.orderings.lex
- @classmethod
- 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 = [] # type: List[str]
- excludes = ['domain', 'split', 'gaussian']
- class Greedy(BooleanOption, metaclass=OptionType):
- """``greedy`` option to polynomial manipulation functions. """
- option = 'greedy'
- requires = [] # type: List[str]
- excludes = ['domain', 'split', 'gaussian', 'extension', 'modulus', 'symmetric']
- class Composite(BooleanOption, metaclass=OptionType):
- """``composite`` option to polynomial manipulation functions. """
- option = 'composite'
- @classmethod
- def default(cls):
- return None
- requires = [] # type: List[str]
- excludes = ['domain', 'split', 'gaussian', 'extension', 'modulus', 'symmetric']
- class Domain(Option, metaclass=OptionType):
- """``domain`` option to polynomial manipulation functions. """
- option = 'domain'
- requires = [] # type: 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)\<(.+)\>$")
- @classmethod
- 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)
- @classmethod
- 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 = [] # type: List[str]
- excludes = ['field', 'greedy', 'domain', 'gaussian', 'extension',
- 'modulus', 'symmetric']
- @classmethod
- 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 = [] # type: List[str]
- excludes = ['field', 'greedy', 'domain', 'split', 'extension',
- 'modulus', 'symmetric']
- @classmethod
- 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 = [] # type: List[str]
- excludes = ['greedy', 'domain', 'split', 'gaussian', 'modulus',
- 'symmetric']
- @classmethod
- 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
- @classmethod
- 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 = [] # type: List[str]
- excludes = ['greedy', 'split', 'domain', 'gaussian', 'extension']
- @classmethod
- 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)
- @classmethod
- 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'
- @classmethod
- def default(cls):
- return True
- class Auto(BooleanOption, Flag, metaclass=OptionType):
- """``auto`` flag to polynomial manipulation functions. """
- option = 'auto'
- after = ['field', 'domain', 'extension', 'gaussian']
- @classmethod
- def default(cls):
- return True
- @classmethod
- 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'
- @classmethod
- def default(cls):
- return False
- class Formal(BooleanOption, Flag, metaclass=OptionType):
- """``formal`` flag to polynomial manipulation functions. """
- option = 'formal'
- @classmethod
- 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'
- @classmethod
- def default(cls):
- return False
- class All(BooleanOption, Flag, metaclass=OptionType):
- """``all`` flag to polynomial manipulation functions. """
- option = 'all'
- @classmethod
- def default(cls):
- return False
- class Gen(Flag, metaclass=OptionType):
- """``gen`` flag to polynomial manipulation functions. """
- option = 'gen'
- @classmethod
- def default(cls):
- return 0
- @classmethod
- 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'
- @classmethod
- def default(cls):
- return False
- class Symbols(Flag, metaclass=OptionType):
- """``symbols`` flag to polynomial manipulation functions. """
- option = 'symbols'
- @classmethod
- def default(cls):
- return numbered_symbols('s', start=1)
- @classmethod
- 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'
- @classmethod
- 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()
|