1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502 |
- """
- Boolean algebra module for SymPy
- """
- from collections import defaultdict
- from itertools import chain, combinations, product, permutations
- from sympy.core.add import Add
- from sympy.core.basic import Basic
- from sympy.core.cache import cacheit
- from sympy.core.containers import Tuple
- from sympy.core.decorators import sympify_method_args, sympify_return
- from sympy.core.function import Application, Derivative
- from sympy.core.kind import BooleanKind, NumberKind
- from sympy.core.numbers import Number
- from sympy.core.operations import LatticeOp
- from sympy.core.singleton import Singleton, S
- from sympy.core.sorting import ordered
- from sympy.core.sympify import _sympy_converter, _sympify, sympify
- from sympy.utilities.iterables import sift, ibin
- from sympy.utilities.misc import filldedent
- def as_Boolean(e):
- """Like ``bool``, return the Boolean value of an expression, e,
- which can be any instance of :py:class:`~.Boolean` or ``bool``.
- Examples
- ========
- >>> from sympy import true, false, nan
- >>> from sympy.logic.boolalg import as_Boolean
- >>> from sympy.abc import x
- >>> as_Boolean(0) is false
- True
- >>> as_Boolean(1) is true
- True
- >>> as_Boolean(x)
- x
- >>> as_Boolean(2)
- Traceback (most recent call last):
- ...
- TypeError: expecting bool or Boolean, not `2`.
- >>> as_Boolean(nan)
- Traceback (most recent call last):
- ...
- TypeError: expecting bool or Boolean, not `nan`.
- """
- from sympy.core.symbol import Symbol
- if e == True:
- return S.true
- if e == False:
- return S.false
- if isinstance(e, Symbol):
- z = e.is_zero
- if z is None:
- return e
- return S.false if z else S.true
- if isinstance(e, Boolean):
- return e
- raise TypeError('expecting bool or Boolean, not `%s`.' % e)
- @sympify_method_args
- class Boolean(Basic):
- """A Boolean object is an object for which logic operations make sense."""
- __slots__ = ()
- kind = BooleanKind
- @sympify_return([('other', 'Boolean')], NotImplemented)
- def __and__(self, other):
- return And(self, other)
- __rand__ = __and__
- @sympify_return([('other', 'Boolean')], NotImplemented)
- def __or__(self, other):
- return Or(self, other)
- __ror__ = __or__
- def __invert__(self):
- """Overloading for ~"""
- return Not(self)
- @sympify_return([('other', 'Boolean')], NotImplemented)
- def __rshift__(self, other):
- return Implies(self, other)
- @sympify_return([('other', 'Boolean')], NotImplemented)
- def __lshift__(self, other):
- return Implies(other, self)
- __rrshift__ = __lshift__
- __rlshift__ = __rshift__
- @sympify_return([('other', 'Boolean')], NotImplemented)
- def __xor__(self, other):
- return Xor(self, other)
- __rxor__ = __xor__
- def equals(self, other):
- """
- Returns ``True`` if the given formulas have the same truth table.
- For two formulas to be equal they must have the same literals.
- Examples
- ========
- >>> from sympy.abc import A, B, C
- >>> from sympy import And, Or, Not
- >>> (A >> B).equals(~B >> ~A)
- True
- >>> Not(And(A, B, C)).equals(And(Not(A), Not(B), Not(C)))
- False
- >>> Not(And(A, Not(A))).equals(Or(B, Not(B)))
- False
- """
- from sympy.logic.inference import satisfiable
- from sympy.core.relational import Relational
- if self.has(Relational) or other.has(Relational):
- raise NotImplementedError('handling of relationals')
- return self.atoms() == other.atoms() and \
- not satisfiable(Not(Equivalent(self, other)))
- def to_nnf(self, simplify=True):
- # override where necessary
- return self
- def as_set(self):
- """
- Rewrites Boolean expression in terms of real sets.
- Examples
- ========
- >>> from sympy import Symbol, Eq, Or, And
- >>> x = Symbol('x', real=True)
- >>> Eq(x, 0).as_set()
- {0}
- >>> (x > 0).as_set()
- Interval.open(0, oo)
- >>> And(-2 < x, x < 2).as_set()
- Interval.open(-2, 2)
- >>> Or(x < -2, 2 < x).as_set()
- Union(Interval.open(-oo, -2), Interval.open(2, oo))
- """
- from sympy.calculus.util import periodicity
- from sympy.core.relational import Relational
- free = self.free_symbols
- if len(free) == 1:
- x = free.pop()
- if x.kind is NumberKind:
- reps = {}
- for r in self.atoms(Relational):
- if periodicity(r, x) not in (0, None):
- s = r._eval_as_set()
- if s in (S.EmptySet, S.UniversalSet, S.Reals):
- reps[r] = s.as_relational(x)
- continue
- raise NotImplementedError(filldedent('''
- as_set is not implemented for relationals
- with periodic solutions
- '''))
- new = self.subs(reps)
- if new.func != self.func:
- return new.as_set() # restart with new obj
- else:
- return new._eval_as_set()
- return self._eval_as_set()
- else:
- raise NotImplementedError("Sorry, as_set has not yet been"
- " implemented for multivariate"
- " expressions")
- @property
- def binary_symbols(self):
- from sympy.core.relational import Eq, Ne
- return set().union(*[i.binary_symbols for i in self.args
- if i.is_Boolean or i.is_Symbol
- or isinstance(i, (Eq, Ne))])
- def _eval_refine(self, assumptions):
- from sympy.assumptions import ask
- ret = ask(self, assumptions)
- if ret is True:
- return true
- elif ret is False:
- return false
- return None
- class BooleanAtom(Boolean):
- """
- Base class of :py:class:`~.BooleanTrue` and :py:class:`~.BooleanFalse`.
- """
- is_Boolean = True
- is_Atom = True
- _op_priority = 11 # higher than Expr
- def simplify(self, *a, **kw):
- return self
- def expand(self, *a, **kw):
- return self
- @property
- def canonical(self):
- return self
- def _noop(self, other=None):
- raise TypeError('BooleanAtom not allowed in this context.')
- __add__ = _noop
- __radd__ = _noop
- __sub__ = _noop
- __rsub__ = _noop
- __mul__ = _noop
- __rmul__ = _noop
- __pow__ = _noop
- __rpow__ = _noop
- __truediv__ = _noop
- __rtruediv__ = _noop
- __mod__ = _noop
- __rmod__ = _noop
- _eval_power = _noop
- # /// drop when Py2 is no longer supported
- def __lt__(self, other):
- raise TypeError(filldedent('''
- A Boolean argument can only be used in
- Eq and Ne; all other relationals expect
- real expressions.
- '''))
- __le__ = __lt__
- __gt__ = __lt__
- __ge__ = __lt__
- # \\\
- def _eval_simplify(self, **kwargs):
- return self
- class BooleanTrue(BooleanAtom, metaclass=Singleton):
- """
- SymPy version of ``True``, a singleton that can be accessed via ``S.true``.
- This is the SymPy version of ``True``, for use in the logic module. The
- primary advantage of using ``true`` instead of ``True`` is that shorthand Boolean
- operations like ``~`` and ``>>`` will work as expected on this class, whereas with
- True they act bitwise on 1. Functions in the logic module will return this
- class when they evaluate to true.
- Notes
- =====
- There is liable to be some confusion as to when ``True`` should
- be used and when ``S.true`` should be used in various contexts
- throughout SymPy. An important thing to remember is that
- ``sympify(True)`` returns ``S.true``. This means that for the most
- part, you can just use ``True`` and it will automatically be converted
- to ``S.true`` when necessary, similar to how you can generally use 1
- instead of ``S.One``.
- The rule of thumb is:
- "If the boolean in question can be replaced by an arbitrary symbolic
- ``Boolean``, like ``Or(x, y)`` or ``x > 1``, use ``S.true``.
- Otherwise, use ``True``"
- In other words, use ``S.true`` only on those contexts where the
- boolean is being used as a symbolic representation of truth.
- For example, if the object ends up in the ``.args`` of any expression,
- then it must necessarily be ``S.true`` instead of ``True``, as
- elements of ``.args`` must be ``Basic``. On the other hand,
- ``==`` is not a symbolic operation in SymPy, since it always returns
- ``True`` or ``False``, and does so in terms of structural equality
- rather than mathematical, so it should return ``True``. The assumptions
- system should use ``True`` and ``False``. Aside from not satisfying
- the above rule of thumb, the assumptions system uses a three-valued logic
- (``True``, ``False``, ``None``), whereas ``S.true`` and ``S.false``
- represent a two-valued logic. When in doubt, use ``True``.
- "``S.true == True is True``."
- While "``S.true is True``" is ``False``, "``S.true == True``"
- is ``True``, so if there is any doubt over whether a function or
- expression will return ``S.true`` or ``True``, just use ``==``
- instead of ``is`` to do the comparison, and it will work in either
- case. Finally, for boolean flags, it's better to just use ``if x``
- instead of ``if x is True``. To quote PEP 8:
- Don't compare boolean values to ``True`` or ``False``
- using ``==``.
- * Yes: ``if greeting:``
- * No: ``if greeting == True:``
- * Worse: ``if greeting is True:``
- Examples
- ========
- >>> from sympy import sympify, true, false, Or
- >>> sympify(True)
- True
- >>> _ is True, _ is true
- (False, True)
- >>> Or(true, false)
- True
- >>> _ is true
- True
- Python operators give a boolean result for true but a
- bitwise result for True
- >>> ~true, ~True
- (False, -2)
- >>> true >> true, True >> True
- (True, 0)
- Python operators give a boolean result for true but a
- bitwise result for True
- >>> ~true, ~True
- (False, -2)
- >>> true >> true, True >> True
- (True, 0)
- See Also
- ========
- sympy.logic.boolalg.BooleanFalse
- """
- def __bool__(self):
- return True
- def __hash__(self):
- return hash(True)
- def __eq__(self, other):
- if other is True:
- return True
- if other is False:
- return False
- return super().__eq__(other)
- @property
- def negated(self):
- return S.false
- def as_set(self):
- """
- Rewrite logic operators and relationals in terms of real sets.
- Examples
- ========
- >>> from sympy import true
- >>> true.as_set()
- UniversalSet
- """
- return S.UniversalSet
- class BooleanFalse(BooleanAtom, metaclass=Singleton):
- """
- SymPy version of ``False``, a singleton that can be accessed via ``S.false``.
- This is the SymPy version of ``False``, for use in the logic module. The
- primary advantage of using ``false`` instead of ``False`` is that shorthand
- Boolean operations like ``~`` and ``>>`` will work as expected on this class,
- whereas with ``False`` they act bitwise on 0. Functions in the logic module
- will return this class when they evaluate to false.
- Notes
- ======
- See the notes section in :py:class:`sympy.logic.boolalg.BooleanTrue`
- Examples
- ========
- >>> from sympy import sympify, true, false, Or
- >>> sympify(False)
- False
- >>> _ is False, _ is false
- (False, True)
- >>> Or(true, false)
- True
- >>> _ is true
- True
- Python operators give a boolean result for false but a
- bitwise result for False
- >>> ~false, ~False
- (True, -1)
- >>> false >> false, False >> False
- (True, 0)
- See Also
- ========
- sympy.logic.boolalg.BooleanTrue
- """
- def __bool__(self):
- return False
- def __hash__(self):
- return hash(False)
- def __eq__(self, other):
- if other is True:
- return False
- if other is False:
- return True
- return super().__eq__(other)
- @property
- def negated(self):
- return S.true
- def as_set(self):
- """
- Rewrite logic operators and relationals in terms of real sets.
- Examples
- ========
- >>> from sympy import false
- >>> false.as_set()
- EmptySet
- """
- return S.EmptySet
- true = BooleanTrue()
- false = BooleanFalse()
- # We want S.true and S.false to work, rather than S.BooleanTrue and
- # S.BooleanFalse, but making the class and instance names the same causes some
- # major issues (like the inability to import the class directly from this
- # file).
- S.true = true
- S.false = false
- _sympy_converter[bool] = lambda x: S.true if x else S.false
- class BooleanFunction(Application, Boolean):
- """Boolean function is a function that lives in a boolean space
- It is used as base class for :py:class:`~.And`, :py:class:`~.Or`,
- :py:class:`~.Not`, etc.
- """
- is_Boolean = True
- def _eval_simplify(self, **kwargs):
- rv = simplify_univariate(self)
- if not isinstance(rv, BooleanFunction):
- return rv.simplify(**kwargs)
- rv = rv.func(*[a.simplify(**kwargs) for a in rv.args])
- return simplify_logic(rv)
- def simplify(self, **kwargs):
- from sympy.simplify.simplify import simplify
- return simplify(self, **kwargs)
- def __lt__(self, other):
- raise TypeError(filldedent('''
- A Boolean argument can only be used in
- Eq and Ne; all other relationals expect
- real expressions.
- '''))
- __le__ = __lt__
- __ge__ = __lt__
- __gt__ = __lt__
- @classmethod
- def binary_check_and_simplify(self, *args):
- from sympy.core.relational import Relational, Eq, Ne
- args = [as_Boolean(i) for i in args]
- bin_syms = set().union(*[i.binary_symbols for i in args])
- rel = set().union(*[i.atoms(Relational) for i in args])
- reps = {}
- for x in bin_syms:
- for r in rel:
- if x in bin_syms and x in r.free_symbols:
- if isinstance(r, (Eq, Ne)):
- if not (
- S.true in r.args or
- S.false in r.args):
- reps[r] = S.false
- else:
- raise TypeError(filldedent('''
- Incompatible use of binary symbol `%s` as a
- real variable in `%s`
- ''' % (x, r)))
- return [i.subs(reps) for i in args]
- def to_nnf(self, simplify=True):
- return self._to_nnf(*self.args, simplify=simplify)
- def to_anf(self, deep=True):
- return self._to_anf(*self.args, deep=deep)
- @classmethod
- def _to_nnf(cls, *args, **kwargs):
- simplify = kwargs.get('simplify', True)
- argset = set()
- for arg in args:
- if not is_literal(arg):
- arg = arg.to_nnf(simplify)
- if simplify:
- if isinstance(arg, cls):
- arg = arg.args
- else:
- arg = (arg,)
- for a in arg:
- if Not(a) in argset:
- return cls.zero
- argset.add(a)
- else:
- argset.add(arg)
- return cls(*argset)
- @classmethod
- def _to_anf(cls, *args, **kwargs):
- deep = kwargs.get('deep', True)
- argset = set()
- for arg in args:
- if deep:
- if not is_literal(arg) or isinstance(arg, Not):
- arg = arg.to_anf(deep=deep)
- argset.add(arg)
- else:
- argset.add(arg)
- return cls(*argset, remove_true=False)
- # the diff method below is copied from Expr class
- def diff(self, *symbols, **assumptions):
- assumptions.setdefault("evaluate", True)
- return Derivative(self, *symbols, **assumptions)
- def _eval_derivative(self, x):
- if x in self.binary_symbols:
- from sympy.core.relational import Eq
- from sympy.functions.elementary.piecewise import Piecewise
- return Piecewise(
- (0, Eq(self.subs(x, 0), self.subs(x, 1))),
- (1, True))
- elif x in self.free_symbols:
- # not implemented, see https://www.encyclopediaofmath.org/
- # index.php/Boolean_differential_calculus
- pass
- else:
- return S.Zero
- class And(LatticeOp, BooleanFunction):
- """
- Logical AND function.
- It evaluates its arguments in order, returning false immediately
- when an argument is false and true if they are all true.
- Examples
- ========
- >>> from sympy.abc import x, y
- >>> from sympy import And
- >>> x & y
- x & y
- Notes
- =====
- The ``&`` operator is provided as a convenience, but note that its use
- here is different from its normal use in Python, which is bitwise
- and. Hence, ``And(a, b)`` and ``a & b`` will return different things if
- ``a`` and ``b`` are integers.
- >>> And(x, y).subs(x, 1)
- y
- """
- zero = false
- identity = true
- nargs = None
- @classmethod
- def _new_args_filter(cls, args):
- args = BooleanFunction.binary_check_and_simplify(*args)
- args = LatticeOp._new_args_filter(args, And)
- newargs = []
- rel = set()
- for x in ordered(args):
- if x.is_Relational:
- c = x.canonical
- if c in rel:
- continue
- elif c.negated.canonical in rel:
- return [S.false]
- else:
- rel.add(c)
- newargs.append(x)
- return newargs
- def _eval_subs(self, old, new):
- args = []
- bad = None
- for i in self.args:
- try:
- i = i.subs(old, new)
- except TypeError:
- # store TypeError
- if bad is None:
- bad = i
- continue
- if i == False:
- return S.false
- elif i != True:
- args.append(i)
- if bad is not None:
- # let it raise
- bad.subs(old, new)
- # If old is And, replace the parts of the arguments with new if all
- # are there
- if isinstance(old, And):
- old_set = set(old.args)
- if old_set.issubset(args):
- args = set(args) - old_set
- args.add(new)
- return self.func(*args)
- def _eval_simplify(self, **kwargs):
- from sympy.core.relational import Equality, Relational
- from sympy.solvers.solveset import linear_coeffs
- # standard simplify
- rv = super()._eval_simplify(**kwargs)
- if not isinstance(rv, And):
- return rv
- # simplify args that are equalities involving
- # symbols so x == 0 & x == y -> x==0 & y == 0
- Rel, nonRel = sift(rv.args, lambda i: isinstance(i, Relational),
- binary=True)
- if not Rel:
- return rv
- eqs, other = sift(Rel, lambda i: isinstance(i, Equality), binary=True)
- measure = kwargs['measure']
- if eqs:
- ratio = kwargs['ratio']
- reps = {}
- sifted = {}
- # group by length of free symbols
- sifted = sift(ordered([
- (i.free_symbols, i) for i in eqs]),
- lambda x: len(x[0]))
- eqs = []
- nonlineqs = []
- while 1 in sifted:
- for free, e in sifted.pop(1):
- x = free.pop()
- if (e.lhs != x or x in e.rhs.free_symbols) and x not in reps:
- try:
- m, b = linear_coeffs(
- e.rewrite(Add, evaluate=False), x)
- enew = e.func(x, -b/m)
- if measure(enew) <= ratio*measure(e):
- e = enew
- else:
- eqs.append(e)
- continue
- except ValueError:
- pass
- if x in reps:
- eqs.append(e.subs(x, reps[x]))
- elif e.lhs == x and x not in e.rhs.free_symbols:
- reps[x] = e.rhs
- eqs.append(e)
- else:
- # x is not yet identified, but may be later
- nonlineqs.append(e)
- resifted = defaultdict(list)
- for k in sifted:
- for f, e in sifted[k]:
- e = e.xreplace(reps)
- f = e.free_symbols
- resifted[len(f)].append((f, e))
- sifted = resifted
- for k in sifted:
- eqs.extend([e for f, e in sifted[k]])
- nonlineqs = [ei.subs(reps) for ei in nonlineqs]
- other = [ei.subs(reps) for ei in other]
- rv = rv.func(*([i.canonical for i in (eqs + nonlineqs + other)] + nonRel))
- patterns = _simplify_patterns_and()
- threeterm_patterns = _simplify_patterns_and3()
- return _apply_patternbased_simplification(rv, patterns,
- measure, S.false,
- threeterm_patterns=threeterm_patterns)
- def _eval_as_set(self):
- from sympy.sets.sets import Intersection
- return Intersection(*[arg.as_set() for arg in self.args])
- def _eval_rewrite_as_Nor(self, *args, **kwargs):
- return Nor(*[Not(arg) for arg in self.args])
- def to_anf(self, deep=True):
- if deep:
- result = And._to_anf(*self.args, deep=deep)
- return distribute_xor_over_and(result)
- return self
- class Or(LatticeOp, BooleanFunction):
- """
- Logical OR function
- It evaluates its arguments in order, returning true immediately
- when an argument is true, and false if they are all false.
- Examples
- ========
- >>> from sympy.abc import x, y
- >>> from sympy import Or
- >>> x | y
- x | y
- Notes
- =====
- The ``|`` operator is provided as a convenience, but note that its use
- here is different from its normal use in Python, which is bitwise
- or. Hence, ``Or(a, b)`` and ``a | b`` will return different things if
- ``a`` and ``b`` are integers.
- >>> Or(x, y).subs(x, 0)
- y
- """
- zero = true
- identity = false
- @classmethod
- def _new_args_filter(cls, args):
- newargs = []
- rel = []
- args = BooleanFunction.binary_check_and_simplify(*args)
- for x in args:
- if x.is_Relational:
- c = x.canonical
- if c in rel:
- continue
- nc = c.negated.canonical
- if any(r == nc for r in rel):
- return [S.true]
- rel.append(c)
- newargs.append(x)
- return LatticeOp._new_args_filter(newargs, Or)
- def _eval_subs(self, old, new):
- args = []
- bad = None
- for i in self.args:
- try:
- i = i.subs(old, new)
- except TypeError:
- # store TypeError
- if bad is None:
- bad = i
- continue
- if i == True:
- return S.true
- elif i != False:
- args.append(i)
- if bad is not None:
- # let it raise
- bad.subs(old, new)
- # If old is Or, replace the parts of the arguments with new if all
- # are there
- if isinstance(old, Or):
- old_set = set(old.args)
- if old_set.issubset(args):
- args = set(args) - old_set
- args.add(new)
- return self.func(*args)
- def _eval_as_set(self):
- from sympy.sets.sets import Union
- return Union(*[arg.as_set() for arg in self.args])
- def _eval_rewrite_as_Nand(self, *args, **kwargs):
- return Nand(*[Not(arg) for arg in self.args])
- def _eval_simplify(self, **kwargs):
- from sympy.core.relational import Le, Ge, Eq
- lege = self.atoms(Le, Ge)
- if lege:
- reps = {i: self.func(
- Eq(i.lhs, i.rhs), i.strict) for i in lege}
- return self.xreplace(reps)._eval_simplify(**kwargs)
- # standard simplify
- rv = super()._eval_simplify(**kwargs)
- if not isinstance(rv, Or):
- return rv
- patterns = _simplify_patterns_or()
- return _apply_patternbased_simplification(rv, patterns,
- kwargs['measure'], S.true)
- def to_anf(self, deep=True):
- args = range(1, len(self.args) + 1)
- args = (combinations(self.args, j) for j in args)
- args = chain.from_iterable(args) # powerset
- args = (And(*arg) for arg in args)
- args = map(lambda x: to_anf(x, deep=deep) if deep else x, args)
- return Xor(*list(args), remove_true=False)
- class Not(BooleanFunction):
- """
- Logical Not function (negation)
- Returns ``true`` if the statement is ``false`` or ``False``.
- Returns ``false`` if the statement is ``true`` or ``True``.
- Examples
- ========
- >>> from sympy import Not, And, Or
- >>> from sympy.abc import x, A, B
- >>> Not(True)
- False
- >>> Not(False)
- True
- >>> Not(And(True, False))
- True
- >>> Not(Or(True, False))
- False
- >>> Not(And(And(True, x), Or(x, False)))
- ~x
- >>> ~x
- ~x
- >>> Not(And(Or(A, B), Or(~A, ~B)))
- ~((A | B) & (~A | ~B))
- Notes
- =====
- - The ``~`` operator is provided as a convenience, but note that its use
- here is different from its normal use in Python, which is bitwise
- not. In particular, ``~a`` and ``Not(a)`` will be different if ``a`` is
- an integer. Furthermore, since bools in Python subclass from ``int``,
- ``~True`` is the same as ``~1`` which is ``-2``, which has a boolean
- value of True. To avoid this issue, use the SymPy boolean types
- ``true`` and ``false``.
- >>> from sympy import true
- >>> ~True
- -2
- >>> ~true
- False
- """
- is_Not = True
- @classmethod
- def eval(cls, arg):
- if isinstance(arg, Number) or arg in (True, False):
- return false if arg else true
- if arg.is_Not:
- return arg.args[0]
- # Simplify Relational objects.
- if arg.is_Relational:
- return arg.negated
- def _eval_as_set(self):
- """
- Rewrite logic operators and relationals in terms of real sets.
- Examples
- ========
- >>> from sympy import Not, Symbol
- >>> x = Symbol('x')
- >>> Not(x > 0).as_set()
- Interval(-oo, 0)
- """
- return self.args[0].as_set().complement(S.Reals)
- def to_nnf(self, simplify=True):
- if is_literal(self):
- return self
- expr = self.args[0]
- func, args = expr.func, expr.args
- if func == And:
- return Or._to_nnf(*[Not(arg) for arg in args], simplify=simplify)
- if func == Or:
- return And._to_nnf(*[Not(arg) for arg in args], simplify=simplify)
- if func == Implies:
- a, b = args
- return And._to_nnf(a, Not(b), simplify=simplify)
- if func == Equivalent:
- return And._to_nnf(Or(*args), Or(*[Not(arg) for arg in args]),
- simplify=simplify)
- if func == Xor:
- result = []
- for i in range(1, len(args)+1, 2):
- for neg in combinations(args, i):
- clause = [Not(s) if s in neg else s for s in args]
- result.append(Or(*clause))
- return And._to_nnf(*result, simplify=simplify)
- if func == ITE:
- a, b, c = args
- return And._to_nnf(Or(a, Not(c)), Or(Not(a), Not(b)), simplify=simplify)
- raise ValueError("Illegal operator %s in expression" % func)
- def to_anf(self, deep=True):
- return Xor._to_anf(true, self.args[0], deep=deep)
- class Xor(BooleanFunction):
- """
- Logical XOR (exclusive OR) function.
- Returns True if an odd number of the arguments are True and the rest are
- False.
- Returns False if an even number of the arguments are True and the rest are
- False.
- Examples
- ========
- >>> from sympy.logic.boolalg import Xor
- >>> from sympy import symbols
- >>> x, y = symbols('x y')
- >>> Xor(True, False)
- True
- >>> Xor(True, True)
- False
- >>> Xor(True, False, True, True, False)
- True
- >>> Xor(True, False, True, False)
- False
- >>> x ^ y
- x ^ y
- Notes
- =====
- The ``^`` operator is provided as a convenience, but note that its use
- here is different from its normal use in Python, which is bitwise xor. In
- particular, ``a ^ b`` and ``Xor(a, b)`` will be different if ``a`` and
- ``b`` are integers.
- >>> Xor(x, y).subs(y, 0)
- x
- """
- def __new__(cls, *args, remove_true=True, **kwargs):
- argset = set()
- obj = super().__new__(cls, *args, **kwargs)
- for arg in obj._args:
- if isinstance(arg, Number) or arg in (True, False):
- if arg:
- arg = true
- else:
- continue
- if isinstance(arg, Xor):
- for a in arg.args:
- argset.remove(a) if a in argset else argset.add(a)
- elif arg in argset:
- argset.remove(arg)
- else:
- argset.add(arg)
- rel = [(r, r.canonical, r.negated.canonical)
- for r in argset if r.is_Relational]
- odd = False # is number of complimentary pairs odd? start 0 -> False
- remove = []
- for i, (r, c, nc) in enumerate(rel):
- for j in range(i + 1, len(rel)):
- rj, cj = rel[j][:2]
- if cj == nc:
- odd = ~odd
- break
- elif cj == c:
- break
- else:
- continue
- remove.append((r, rj))
- if odd:
- argset.remove(true) if true in argset else argset.add(true)
- for a, b in remove:
- argset.remove(a)
- argset.remove(b)
- if len(argset) == 0:
- return false
- elif len(argset) == 1:
- return argset.pop()
- elif True in argset and remove_true:
- argset.remove(True)
- return Not(Xor(*argset))
- else:
- obj._args = tuple(ordered(argset))
- obj._argset = frozenset(argset)
- return obj
- # XXX: This should be cached on the object rather than using cacheit
- # Maybe it can be computed in __new__?
- @property # type: ignore
- @cacheit
- def args(self):
- return tuple(ordered(self._argset))
- def to_nnf(self, simplify=True):
- args = []
- for i in range(0, len(self.args)+1, 2):
- for neg in combinations(self.args, i):
- clause = [Not(s) if s in neg else s for s in self.args]
- args.append(Or(*clause))
- return And._to_nnf(*args, simplify=simplify)
- def _eval_rewrite_as_Or(self, *args, **kwargs):
- a = self.args
- return Or(*[_convert_to_varsSOP(x, self.args)
- for x in _get_odd_parity_terms(len(a))])
- def _eval_rewrite_as_And(self, *args, **kwargs):
- a = self.args
- return And(*[_convert_to_varsPOS(x, self.args)
- for x in _get_even_parity_terms(len(a))])
- def _eval_simplify(self, **kwargs):
- # as standard simplify uses simplify_logic which writes things as
- # And and Or, we only simplify the partial expressions before using
- # patterns
- rv = self.func(*[a.simplify(**kwargs) for a in self.args])
- if not isinstance(rv, Xor): # This shouldn't really happen here
- return rv
- patterns = _simplify_patterns_xor()
- return _apply_patternbased_simplification(rv, patterns,
- kwargs['measure'], None)
- def _eval_subs(self, old, new):
- # If old is Xor, replace the parts of the arguments with new if all
- # are there
- if isinstance(old, Xor):
- old_set = set(old.args)
- if old_set.issubset(self.args):
- args = set(self.args) - old_set
- args.add(new)
- return self.func(*args)
- class Nand(BooleanFunction):
- """
- Logical NAND function.
- It evaluates its arguments in order, giving True immediately if any
- of them are False, and False if they are all True.
- Returns True if any of the arguments are False
- Returns False if all arguments are True
- Examples
- ========
- >>> from sympy.logic.boolalg import Nand
- >>> from sympy import symbols
- >>> x, y = symbols('x y')
- >>> Nand(False, True)
- True
- >>> Nand(True, True)
- False
- >>> Nand(x, y)
- ~(x & y)
- """
- @classmethod
- def eval(cls, *args):
- return Not(And(*args))
- class Nor(BooleanFunction):
- """
- Logical NOR function.
- It evaluates its arguments in order, giving False immediately if any
- of them are True, and True if they are all False.
- Returns False if any argument is True
- Returns True if all arguments are False
- Examples
- ========
- >>> from sympy.logic.boolalg import Nor
- >>> from sympy import symbols
- >>> x, y = symbols('x y')
- >>> Nor(True, False)
- False
- >>> Nor(True, True)
- False
- >>> Nor(False, True)
- False
- >>> Nor(False, False)
- True
- >>> Nor(x, y)
- ~(x | y)
- """
- @classmethod
- def eval(cls, *args):
- return Not(Or(*args))
- class Xnor(BooleanFunction):
- """
- Logical XNOR function.
- Returns False if an odd number of the arguments are True and the rest are
- False.
- Returns True if an even number of the arguments are True and the rest are
- False.
- Examples
- ========
- >>> from sympy.logic.boolalg import Xnor
- >>> from sympy import symbols
- >>> x, y = symbols('x y')
- >>> Xnor(True, False)
- False
- >>> Xnor(True, True)
- True
- >>> Xnor(True, False, True, True, False)
- False
- >>> Xnor(True, False, True, False)
- True
- """
- @classmethod
- def eval(cls, *args):
- return Not(Xor(*args))
- class Implies(BooleanFunction):
- r"""
- Logical implication.
- A implies B is equivalent to if A then B. Mathematically, it is written
- as `A \Rightarrow B` and is equivalent to `\neg A \vee B` or ``~A | B``.
- Accepts two Boolean arguments; A and B.
- Returns False if A is True and B is False
- Returns True otherwise.
- Examples
- ========
- >>> from sympy.logic.boolalg import Implies
- >>> from sympy import symbols
- >>> x, y = symbols('x y')
- >>> Implies(True, False)
- False
- >>> Implies(False, False)
- True
- >>> Implies(True, True)
- True
- >>> Implies(False, True)
- True
- >>> x >> y
- Implies(x, y)
- >>> y << x
- Implies(x, y)
- Notes
- =====
- The ``>>`` and ``<<`` operators are provided as a convenience, but note
- that their use here is different from their normal use in Python, which is
- bit shifts. Hence, ``Implies(a, b)`` and ``a >> b`` will return different
- things if ``a`` and ``b`` are integers. In particular, since Python
- considers ``True`` and ``False`` to be integers, ``True >> True`` will be
- the same as ``1 >> 1``, i.e., 0, which has a truth value of False. To
- avoid this issue, use the SymPy objects ``true`` and ``false``.
- >>> from sympy import true, false
- >>> True >> False
- 1
- >>> true >> false
- False
- """
- @classmethod
- def eval(cls, *args):
- try:
- newargs = []
- for x in args:
- if isinstance(x, Number) or x in (0, 1):
- newargs.append(bool(x))
- else:
- newargs.append(x)
- A, B = newargs
- except ValueError:
- raise ValueError(
- "%d operand(s) used for an Implies "
- "(pairs are required): %s" % (len(args), str(args)))
- if A in (True, False) or B in (True, False):
- return Or(Not(A), B)
- elif A == B:
- return S.true
- elif A.is_Relational and B.is_Relational:
- if A.canonical == B.canonical:
- return S.true
- if A.negated.canonical == B.canonical:
- return B
- else:
- return Basic.__new__(cls, *args)
- def to_nnf(self, simplify=True):
- a, b = self.args
- return Or._to_nnf(Not(a), b, simplify=simplify)
- def to_anf(self, deep=True):
- a, b = self.args
- return Xor._to_anf(true, a, And(a, b), deep=deep)
- class Equivalent(BooleanFunction):
- """
- Equivalence relation.
- ``Equivalent(A, B)`` is True iff A and B are both True or both False.
- Returns True if all of the arguments are logically equivalent.
- Returns False otherwise.
- For two arguments, this is equivalent to :py:class:`~.Xnor`.
- Examples
- ========
- >>> from sympy.logic.boolalg import Equivalent, And
- >>> from sympy.abc import x
- >>> Equivalent(False, False, False)
- True
- >>> Equivalent(True, False, False)
- False
- >>> Equivalent(x, And(x, True))
- True
- """
- def __new__(cls, *args, **options):
- from sympy.core.relational import Relational
- args = [_sympify(arg) for arg in args]
- argset = set(args)
- for x in args:
- if isinstance(x, Number) or x in [True, False]: # Includes 0, 1
- argset.discard(x)
- argset.add(bool(x))
- rel = []
- for r in argset:
- if isinstance(r, Relational):
- rel.append((r, r.canonical, r.negated.canonical))
- remove = []
- for i, (r, c, nc) in enumerate(rel):
- for j in range(i + 1, len(rel)):
- rj, cj = rel[j][:2]
- if cj == nc:
- return false
- elif cj == c:
- remove.append((r, rj))
- break
- for a, b in remove:
- argset.remove(a)
- argset.remove(b)
- argset.add(True)
- if len(argset) <= 1:
- return true
- if True in argset:
- argset.discard(True)
- return And(*argset)
- if False in argset:
- argset.discard(False)
- return And(*[Not(arg) for arg in argset])
- _args = frozenset(argset)
- obj = super().__new__(cls, _args)
- obj._argset = _args
- return obj
- # XXX: This should be cached on the object rather than using cacheit
- # Maybe it can be computed in __new__?
- @property # type: ignore
- @cacheit
- def args(self):
- return tuple(ordered(self._argset))
- def to_nnf(self, simplify=True):
- args = []
- for a, b in zip(self.args, self.args[1:]):
- args.append(Or(Not(a), b))
- args.append(Or(Not(self.args[-1]), self.args[0]))
- return And._to_nnf(*args, simplify=simplify)
- def to_anf(self, deep=True):
- a = And(*self.args)
- b = And(*[to_anf(Not(arg), deep=False) for arg in self.args])
- b = distribute_xor_over_and(b)
- return Xor._to_anf(a, b, deep=deep)
- class ITE(BooleanFunction):
- """
- If-then-else clause.
- ``ITE(A, B, C)`` evaluates and returns the result of B if A is true
- else it returns the result of C. All args must be Booleans.
- From a logic gate perspective, ITE corresponds to a 2-to-1 multiplexer,
- where A is the select signal.
- Examples
- ========
- >>> from sympy.logic.boolalg import ITE, And, Xor, Or
- >>> from sympy.abc import x, y, z
- >>> ITE(True, False, True)
- False
- >>> ITE(Or(True, False), And(True, True), Xor(True, True))
- True
- >>> ITE(x, y, z)
- ITE(x, y, z)
- >>> ITE(True, x, y)
- x
- >>> ITE(False, x, y)
- y
- >>> ITE(x, y, y)
- y
- Trying to use non-Boolean args will generate a TypeError:
- >>> ITE(True, [], ())
- Traceback (most recent call last):
- ...
- TypeError: expecting bool, Boolean or ITE, not `[]`
- """
- def __new__(cls, *args, **kwargs):
- from sympy.core.relational import Eq, Ne
- if len(args) != 3:
- raise ValueError('expecting exactly 3 args')
- a, b, c = args
- # check use of binary symbols
- if isinstance(a, (Eq, Ne)):
- # in this context, we can evaluate the Eq/Ne
- # if one arg is a binary symbol and the other
- # is true/false
- b, c = map(as_Boolean, (b, c))
- bin_syms = set().union(*[i.binary_symbols for i in (b, c)])
- if len(set(a.args) - bin_syms) == 1:
- # one arg is a binary_symbols
- _a = a
- if a.lhs is S.true:
- a = a.rhs
- elif a.rhs is S.true:
- a = a.lhs
- elif a.lhs is S.false:
- a = Not(a.rhs)
- elif a.rhs is S.false:
- a = Not(a.lhs)
- else:
- # binary can only equal True or False
- a = S.false
- if isinstance(_a, Ne):
- a = Not(a)
- else:
- a, b, c = BooleanFunction.binary_check_and_simplify(
- a, b, c)
- rv = None
- if kwargs.get('evaluate', True):
- rv = cls.eval(a, b, c)
- if rv is None:
- rv = BooleanFunction.__new__(cls, a, b, c, evaluate=False)
- return rv
- @classmethod
- def eval(cls, *args):
- from sympy.core.relational import Eq, Ne
- # do the args give a singular result?
- a, b, c = args
- if isinstance(a, (Ne, Eq)):
- _a = a
- if S.true in a.args:
- a = a.lhs if a.rhs is S.true else a.rhs
- elif S.false in a.args:
- a = Not(a.lhs) if a.rhs is S.false else Not(a.rhs)
- else:
- _a = None
- if _a is not None and isinstance(_a, Ne):
- a = Not(a)
- if a is S.true:
- return b
- if a is S.false:
- return c
- if b == c:
- return b
- else:
- # or maybe the results allow the answer to be expressed
- # in terms of the condition
- if b is S.true and c is S.false:
- return a
- if b is S.false and c is S.true:
- return Not(a)
- if [a, b, c] != args:
- return cls(a, b, c, evaluate=False)
- def to_nnf(self, simplify=True):
- a, b, c = self.args
- return And._to_nnf(Or(Not(a), b), Or(a, c), simplify=simplify)
- def _eval_as_set(self):
- return self.to_nnf().as_set()
- def _eval_rewrite_as_Piecewise(self, *args, **kwargs):
- from sympy.functions import Piecewise
- return Piecewise((args[1], args[0]), (args[2], True))
- class Exclusive(BooleanFunction):
- """
- True if only one or no argument is true.
- ``Exclusive(A, B, C)`` is equivalent to ``~(A & B) & ~(A & C) & ~(B & C)``.
- For two arguments, this is equivalent to :py:class:`~.Xor`.
- Examples
- ========
- >>> from sympy.logic.boolalg import Exclusive
- >>> Exclusive(False, False, False)
- True
- >>> Exclusive(False, True, False)
- True
- >>> Exclusive(False, True, True)
- False
- """
- @classmethod
- def eval(cls, *args):
- and_args = []
- for a, b in combinations(args, 2):
- and_args.append(Not(And(a, b)))
- return And(*and_args)
- # end class definitions. Some useful methods
- def conjuncts(expr):
- """Return a list of the conjuncts in ``expr``.
- Examples
- ========
- >>> from sympy.logic.boolalg import conjuncts
- >>> from sympy.abc import A, B
- >>> conjuncts(A & B)
- frozenset({A, B})
- >>> conjuncts(A | B)
- frozenset({A | B})
- """
- return And.make_args(expr)
- def disjuncts(expr):
- """Return a list of the disjuncts in ``expr``.
- Examples
- ========
- >>> from sympy.logic.boolalg import disjuncts
- >>> from sympy.abc import A, B
- >>> disjuncts(A | B)
- frozenset({A, B})
- >>> disjuncts(A & B)
- frozenset({A & B})
- """
- return Or.make_args(expr)
- def distribute_and_over_or(expr):
- """
- Given a sentence ``expr`` consisting of conjunctions and disjunctions
- of literals, return an equivalent sentence in CNF.
- Examples
- ========
- >>> from sympy.logic.boolalg import distribute_and_over_or, And, Or, Not
- >>> from sympy.abc import A, B, C
- >>> distribute_and_over_or(Or(A, And(Not(B), Not(C))))
- (A | ~B) & (A | ~C)
- """
- return _distribute((expr, And, Or))
- def distribute_or_over_and(expr):
- """
- Given a sentence ``expr`` consisting of conjunctions and disjunctions
- of literals, return an equivalent sentence in DNF.
- Note that the output is NOT simplified.
- Examples
- ========
- >>> from sympy.logic.boolalg import distribute_or_over_and, And, Or, Not
- >>> from sympy.abc import A, B, C
- >>> distribute_or_over_and(And(Or(Not(A), B), C))
- (B & C) | (C & ~A)
- """
- return _distribute((expr, Or, And))
- def distribute_xor_over_and(expr):
- """
- Given a sentence ``expr`` consisting of conjunction and
- exclusive disjunctions of literals, return an
- equivalent exclusive disjunction.
- Note that the output is NOT simplified.
- Examples
- ========
- >>> from sympy.logic.boolalg import distribute_xor_over_and, And, Xor, Not
- >>> from sympy.abc import A, B, C
- >>> distribute_xor_over_and(And(Xor(Not(A), B), C))
- (B & C) ^ (C & ~A)
- """
- return _distribute((expr, Xor, And))
- def _distribute(info):
- """
- Distributes ``info[1]`` over ``info[2]`` with respect to ``info[0]``.
- """
- if isinstance(info[0], info[2]):
- for arg in info[0].args:
- if isinstance(arg, info[1]):
- conj = arg
- break
- else:
- return info[0]
- rest = info[2](*[a for a in info[0].args if a is not conj])
- return info[1](*list(map(_distribute,
- [(info[2](c, rest), info[1], info[2])
- for c in conj.args])), remove_true=False)
- elif isinstance(info[0], info[1]):
- return info[1](*list(map(_distribute,
- [(x, info[1], info[2])
- for x in info[0].args])),
- remove_true=False)
- else:
- return info[0]
- def to_anf(expr, deep=True):
- r"""
- Converts expr to Algebraic Normal Form (ANF).
- ANF is a canonical normal form, which means that two
- equivalent formulas will convert to the same ANF.
- A logical expression is in ANF if it has the form
- .. math:: 1 \oplus a \oplus b \oplus ab \oplus abc
- i.e. it can be:
- - purely true,
- - purely false,
- - conjunction of variables,
- - exclusive disjunction.
- The exclusive disjunction can only contain true, variables
- or conjunction of variables. No negations are permitted.
- If ``deep`` is ``False``, arguments of the boolean
- expression are considered variables, i.e. only the
- top-level expression is converted to ANF.
- Examples
- ========
- >>> from sympy.logic.boolalg import And, Or, Not, Implies, Equivalent
- >>> from sympy.logic.boolalg import to_anf
- >>> from sympy.abc import A, B, C
- >>> to_anf(Not(A))
- A ^ True
- >>> to_anf(And(Or(A, B), Not(C)))
- A ^ B ^ (A & B) ^ (A & C) ^ (B & C) ^ (A & B & C)
- >>> to_anf(Implies(Not(A), Equivalent(B, C)), deep=False)
- True ^ ~A ^ (~A & (Equivalent(B, C)))
- """
- expr = sympify(expr)
- if is_anf(expr):
- return expr
- return expr.to_anf(deep=deep)
- def to_nnf(expr, simplify=True):
- """
- Converts ``expr`` to Negation Normal Form (NNF).
- A logical expression is in NNF if it
- contains only And, Or and Not, and Not is applied only to literals.
- If ``simplify`` is ``True``, the result contains no redundant clauses.
- Examples
- ========
- >>> from sympy.abc import A, B, C, D
- >>> from sympy.logic.boolalg import Not, Equivalent, to_nnf
- >>> to_nnf(Not((~A & ~B) | (C & D)))
- (A | B) & (~C | ~D)
- >>> to_nnf(Equivalent(A >> B, B >> A))
- (A | ~B | (A & ~B)) & (B | ~A | (B & ~A))
- """
- if is_nnf(expr, simplify):
- return expr
- return expr.to_nnf(simplify)
- def to_cnf(expr, simplify=False, force=False):
- """
- Convert a propositional logical sentence ``expr`` to conjunctive normal
- form: ``((A | ~B | ...) & (B | C | ...) & ...)``.
- If ``simplify`` is ``True``, ``expr`` is evaluated to its simplest CNF
- form using the Quine-McCluskey algorithm; this may take a long
- time if there are more than 8 variables and requires that the
- ``force`` flag be set to ``True`` (default is ``False``).
- Examples
- ========
- >>> from sympy.logic.boolalg import to_cnf
- >>> from sympy.abc import A, B, D
- >>> to_cnf(~(A | B) | D)
- (D | ~A) & (D | ~B)
- >>> to_cnf((A | B) & (A | ~A), True)
- A | B
- """
- expr = sympify(expr)
- if not isinstance(expr, BooleanFunction):
- return expr
- if simplify:
- if not force and len(_find_predicates(expr)) > 8:
- raise ValueError(filldedent('''
- To simplify a logical expression with more
- than 8 variables may take a long time and requires
- the use of `force=True`.'''))
- return simplify_logic(expr, 'cnf', True, force=force)
- # Don't convert unless we have to
- if is_cnf(expr):
- return expr
- expr = eliminate_implications(expr)
- res = distribute_and_over_or(expr)
- return res
- def to_dnf(expr, simplify=False, force=False):
- """
- Convert a propositional logical sentence ``expr`` to disjunctive normal
- form: ``((A & ~B & ...) | (B & C & ...) | ...)``.
- If ``simplify`` is ``True``, ``expr`` is evaluated to its simplest DNF form using
- the Quine-McCluskey algorithm; this may take a long
- time if there are more than 8 variables and requires that the
- ``force`` flag be set to ``True`` (default is ``False``).
- Examples
- ========
- >>> from sympy.logic.boolalg import to_dnf
- >>> from sympy.abc import A, B, C
- >>> to_dnf(B & (A | C))
- (A & B) | (B & C)
- >>> to_dnf((A & B) | (A & ~B) | (B & C) | (~B & C), True)
- A | C
- """
- expr = sympify(expr)
- if not isinstance(expr, BooleanFunction):
- return expr
- if simplify:
- if not force and len(_find_predicates(expr)) > 8:
- raise ValueError(filldedent('''
- To simplify a logical expression with more
- than 8 variables may take a long time and requires
- the use of `force=True`.'''))
- return simplify_logic(expr, 'dnf', True, force=force)
- # Don't convert unless we have to
- if is_dnf(expr):
- return expr
- expr = eliminate_implications(expr)
- return distribute_or_over_and(expr)
- def is_anf(expr):
- r"""
- Checks if ``expr`` is in Algebraic Normal Form (ANF).
- A logical expression is in ANF if it has the form
- .. math:: 1 \oplus a \oplus b \oplus ab \oplus abc
- i.e. it is purely true, purely false, conjunction of
- variables or exclusive disjunction. The exclusive
- disjunction can only contain true, variables or
- conjunction of variables. No negations are permitted.
- Examples
- ========
- >>> from sympy.logic.boolalg import And, Not, Xor, true, is_anf
- >>> from sympy.abc import A, B, C
- >>> is_anf(true)
- True
- >>> is_anf(A)
- True
- >>> is_anf(And(A, B, C))
- True
- >>> is_anf(Xor(A, Not(B)))
- False
- """
- expr = sympify(expr)
- if is_literal(expr) and not isinstance(expr, Not):
- return True
- if isinstance(expr, And):
- for arg in expr.args:
- if not arg.is_Symbol:
- return False
- return True
- elif isinstance(expr, Xor):
- for arg in expr.args:
- if isinstance(arg, And):
- for a in arg.args:
- if not a.is_Symbol:
- return False
- elif is_literal(arg):
- if isinstance(arg, Not):
- return False
- else:
- return False
- return True
- else:
- return False
- def is_nnf(expr, simplified=True):
- """
- Checks if ``expr`` is in Negation Normal Form (NNF).
- A logical expression is in NNF if it
- contains only And, Or and Not, and Not is applied only to literals.
- If ``simplified`` is ``True``, checks if result contains no redundant clauses.
- Examples
- ========
- >>> from sympy.abc import A, B, C
- >>> from sympy.logic.boolalg import Not, is_nnf
- >>> is_nnf(A & B | ~C)
- True
- >>> is_nnf((A | ~A) & (B | C))
- False
- >>> is_nnf((A | ~A) & (B | C), False)
- True
- >>> is_nnf(Not(A & B) | C)
- False
- >>> is_nnf((A >> B) & (B >> A))
- False
- """
- expr = sympify(expr)
- if is_literal(expr):
- return True
- stack = [expr]
- while stack:
- expr = stack.pop()
- if expr.func in (And, Or):
- if simplified:
- args = expr.args
- for arg in args:
- if Not(arg) in args:
- return False
- stack.extend(expr.args)
- elif not is_literal(expr):
- return False
- return True
- def is_cnf(expr):
- """
- Test whether or not an expression is in conjunctive normal form.
- Examples
- ========
- >>> from sympy.logic.boolalg import is_cnf
- >>> from sympy.abc import A, B, C
- >>> is_cnf(A | B | C)
- True
- >>> is_cnf(A & B & C)
- True
- >>> is_cnf((A & B) | C)
- False
- """
- return _is_form(expr, And, Or)
- def is_dnf(expr):
- """
- Test whether or not an expression is in disjunctive normal form.
- Examples
- ========
- >>> from sympy.logic.boolalg import is_dnf
- >>> from sympy.abc import A, B, C
- >>> is_dnf(A | B | C)
- True
- >>> is_dnf(A & B & C)
- True
- >>> is_dnf((A & B) | C)
- True
- >>> is_dnf(A & (B | C))
- False
- """
- return _is_form(expr, Or, And)
- def _is_form(expr, function1, function2):
- """
- Test whether or not an expression is of the required form.
- """
- expr = sympify(expr)
- vals = function1.make_args(expr) if isinstance(expr, function1) else [expr]
- for lit in vals:
- if isinstance(lit, function2):
- vals2 = function2.make_args(lit) if isinstance(lit, function2) else [lit]
- for l in vals2:
- if is_literal(l) is False:
- return False
- elif is_literal(lit) is False:
- return False
- return True
- def eliminate_implications(expr):
- """
- Change ``Implies`` and ``Equivalent`` into ``And``, ``Or``, and ``Not``.
- That is, return an expression that is equivalent to ``expr``, but has only
- ``&``, ``|``, and ``~`` as logical
- operators.
- Examples
- ========
- >>> from sympy.logic.boolalg import Implies, Equivalent, \
- eliminate_implications
- >>> from sympy.abc import A, B, C
- >>> eliminate_implications(Implies(A, B))
- B | ~A
- >>> eliminate_implications(Equivalent(A, B))
- (A | ~B) & (B | ~A)
- >>> eliminate_implications(Equivalent(A, B, C))
- (A | ~C) & (B | ~A) & (C | ~B)
- """
- return to_nnf(expr, simplify=False)
- def is_literal(expr):
- """
- Returns True if expr is a literal, else False.
- Examples
- ========
- >>> from sympy import Or, Q
- >>> from sympy.abc import A, B
- >>> from sympy.logic.boolalg import is_literal
- >>> is_literal(A)
- True
- >>> is_literal(~A)
- True
- >>> is_literal(Q.zero(A))
- True
- >>> is_literal(A + B)
- True
- >>> is_literal(Or(A, B))
- False
- """
- from sympy.assumptions import AppliedPredicate
- if isinstance(expr, Not):
- return is_literal(expr.args[0])
- elif expr in (True, False) or isinstance(expr, AppliedPredicate) or expr.is_Atom:
- return True
- elif not isinstance(expr, BooleanFunction) and all(
- (isinstance(expr, AppliedPredicate) or a.is_Atom) for a in expr.args):
- return True
- return False
- def to_int_repr(clauses, symbols):
- """
- Takes clauses in CNF format and puts them into an integer representation.
- Examples
- ========
- >>> from sympy.logic.boolalg import to_int_repr
- >>> from sympy.abc import x, y
- >>> to_int_repr([x | y, y], [x, y]) == [{1, 2}, {2}]
- True
- """
- # Convert the symbol list into a dict
- symbols = dict(list(zip(symbols, list(range(1, len(symbols) + 1)))))
- def append_symbol(arg, symbols):
- if isinstance(arg, Not):
- return -symbols[arg.args[0]]
- else:
- return symbols[arg]
- return [{append_symbol(arg, symbols) for arg in Or.make_args(c)}
- for c in clauses]
- def term_to_integer(term):
- """
- Return an integer corresponding to the base-2 digits given by ``term``.
- Parameters
- ==========
- term : a string or list of ones and zeros
- Examples
- ========
- >>> from sympy.logic.boolalg import term_to_integer
- >>> term_to_integer([1, 0, 0])
- 4
- >>> term_to_integer('100')
- 4
- """
- return int(''.join(list(map(str, list(term)))), 2)
- integer_to_term = ibin # XXX could delete?
- def truth_table(expr, variables, input=True):
- """
- Return a generator of all possible configurations of the input variables,
- and the result of the boolean expression for those values.
- Parameters
- ==========
- expr : Boolean expression
- variables : list of variables
- input : bool (default ``True``)
- Indicates whether to return the input combinations.
- Examples
- ========
- >>> from sympy.logic.boolalg import truth_table
- >>> from sympy.abc import x,y
- >>> table = truth_table(x >> y, [x, y])
- >>> for t in table:
- ... print('{0} -> {1}'.format(*t))
- [0, 0] -> True
- [0, 1] -> True
- [1, 0] -> False
- [1, 1] -> True
- >>> table = truth_table(x | y, [x, y])
- >>> list(table)
- [([0, 0], False), ([0, 1], True), ([1, 0], True), ([1, 1], True)]
- If ``input`` is ``False``, ``truth_table`` returns only a list of truth values.
- In this case, the corresponding input values of variables can be
- deduced from the index of a given output.
- >>> from sympy.utilities.iterables import ibin
- >>> vars = [y, x]
- >>> values = truth_table(x >> y, vars, input=False)
- >>> values = list(values)
- >>> values
- [True, False, True, True]
- >>> for i, value in enumerate(values):
- ... print('{0} -> {1}'.format(list(zip(
- ... vars, ibin(i, len(vars)))), value))
- [(y, 0), (x, 0)] -> True
- [(y, 0), (x, 1)] -> False
- [(y, 1), (x, 0)] -> True
- [(y, 1), (x, 1)] -> True
- """
- variables = [sympify(v) for v in variables]
- expr = sympify(expr)
- if not isinstance(expr, BooleanFunction) and not is_literal(expr):
- return
- table = product((0, 1), repeat=len(variables))
- for term in table:
- value = expr.xreplace(dict(zip(variables, term)))
- if input:
- yield list(term), value
- else:
- yield value
- def _check_pair(minterm1, minterm2):
- """
- Checks if a pair of minterms differs by only one bit. If yes, returns
- index, else returns `-1`.
- """
- # Early termination seems to be faster than list comprehension,
- # at least for large examples.
- index = -1
- for x, i in enumerate(minterm1): # zip(minterm1, minterm2) is slower
- if i != minterm2[x]:
- if index == -1:
- index = x
- else:
- return -1
- return index
- def _convert_to_varsSOP(minterm, variables):
- """
- Converts a term in the expansion of a function from binary to its
- variable form (for SOP).
- """
- temp = [variables[n] if val == 1 else Not(variables[n])
- for n, val in enumerate(minterm) if val != 3]
- return And(*temp)
- def _convert_to_varsPOS(maxterm, variables):
- """
- Converts a term in the expansion of a function from binary to its
- variable form (for POS).
- """
- temp = [variables[n] if val == 0 else Not(variables[n])
- for n, val in enumerate(maxterm) if val != 3]
- return Or(*temp)
- def _convert_to_varsANF(term, variables):
- """
- Converts a term in the expansion of a function from binary to it's
- variable form (for ANF).
- Parameters
- ==========
- term : list of 1's and 0's (complementation patter)
- variables : list of variables
- """
- temp = [variables[n] for n, t in enumerate(term) if t == 1]
- if not temp:
- return true
- return And(*temp)
- def _get_odd_parity_terms(n):
- """
- Returns a list of lists, with all possible combinations of n zeros and ones
- with an odd number of ones.
- """
- return [e for e in [ibin(i, n) for i in range(2**n)] if sum(e) % 2 == 1]
- def _get_even_parity_terms(n):
- """
- Returns a list of lists, with all possible combinations of n zeros and ones
- with an even number of ones.
- """
- return [e for e in [ibin(i, n) for i in range(2**n)] if sum(e) % 2 == 0]
- def _simplified_pairs(terms):
- """
- Reduces a set of minterms, if possible, to a simplified set of minterms
- with one less variable in the terms using QM method.
- """
- if not terms:
- return []
- simplified_terms = []
- todo = list(range(len(terms)))
- # Count number of ones as _check_pair can only potentially match if there
- # is at most a difference of a single one
- termdict = defaultdict(list)
- for n, term in enumerate(terms):
- ones = sum([1 for t in term if t == 1])
- termdict[ones].append(n)
- variables = len(terms[0])
- for k in range(variables):
- for i in termdict[k]:
- for j in termdict[k+1]:
- index = _check_pair(terms[i], terms[j])
- if index != -1:
- # Mark terms handled
- todo[i] = todo[j] = None
- # Copy old term
- newterm = terms[i][:]
- # Set differing position to don't care
- newterm[index] = 3
- # Add if not already there
- if newterm not in simplified_terms:
- simplified_terms.append(newterm)
- if simplified_terms:
- # Further simplifications only among the new terms
- simplified_terms = _simplified_pairs(simplified_terms)
- # Add remaining, non-simplified, terms
- simplified_terms.extend([terms[i] for i in todo if i is not None])
- return simplified_terms
- def _rem_redundancy(l1, terms):
- """
- After the truth table has been sufficiently simplified, use the prime
- implicant table method to recognize and eliminate redundant pairs,
- and return the essential arguments.
- """
- if not terms:
- return []
- nterms = len(terms)
- nl1 = len(l1)
- # Create dominating matrix
- dommatrix = [[0]*nl1 for n in range(nterms)]
- colcount = [0]*nl1
- rowcount = [0]*nterms
- for primei, prime in enumerate(l1):
- for termi, term in enumerate(terms):
- # Check prime implicant covering term
- if all(t == 3 or t == mt for t, mt in zip(prime, term)):
- dommatrix[termi][primei] = 1
- colcount[primei] += 1
- rowcount[termi] += 1
- # Keep track if anything changed
- anythingchanged = True
- # Then, go again
- while anythingchanged:
- anythingchanged = False
- for rowi in range(nterms):
- # Still non-dominated?
- if rowcount[rowi]:
- row = dommatrix[rowi]
- for row2i in range(nterms):
- # Still non-dominated?
- if rowi != row2i and rowcount[rowi] and (rowcount[rowi] <= rowcount[row2i]):
- row2 = dommatrix[row2i]
- if all(row2[n] >= row[n] for n in range(nl1)):
- # row2 dominating row, remove row2
- rowcount[row2i] = 0
- anythingchanged = True
- for primei, prime in enumerate(row2):
- if prime:
- # Make corresponding entry 0
- dommatrix[row2i][primei] = 0
- colcount[primei] -= 1
- colcache = dict()
- for coli in range(nl1):
- # Still non-dominated?
- if colcount[coli]:
- if coli in colcache:
- col = colcache[coli]
- else:
- col = [dommatrix[i][coli] for i in range(nterms)]
- colcache[coli] = col
- for col2i in range(nl1):
- # Still non-dominated?
- if coli != col2i and colcount[col2i] and (colcount[coli] >= colcount[col2i]):
- if col2i in colcache:
- col2 = colcache[col2i]
- else:
- col2 = [dommatrix[i][col2i] for i in range(nterms)]
- colcache[col2i] = col2
- if all(col[n] >= col2[n] for n in range(nterms)):
- # col dominating col2, remove col2
- colcount[col2i] = 0
- anythingchanged = True
- for termi, term in enumerate(col2):
- if term and dommatrix[termi][col2i]:
- # Make corresponding entry 0
- dommatrix[termi][col2i] = 0
- rowcount[termi] -= 1
- if not anythingchanged:
- # Heuristically select the prime implicant covering most terms
- maxterms = 0
- bestcolidx = -1
- for coli in range(nl1):
- s = colcount[coli]
- if s > maxterms:
- bestcolidx = coli
- maxterms = s
- # In case we found a prime implicant covering at least two terms
- if bestcolidx != -1 and maxterms > 1:
- for primei, prime in enumerate(l1):
- if primei != bestcolidx:
- for termi, term in enumerate(colcache[bestcolidx]):
- if term and dommatrix[termi][primei]:
- # Make corresponding entry 0
- dommatrix[termi][primei] = 0
- anythingchanged = True
- rowcount[termi] -= 1
- colcount[primei] -= 1
- return [l1[i] for i in range(nl1) if colcount[i]]
- def _input_to_binlist(inputlist, variables):
- binlist = []
- bits = len(variables)
- for val in inputlist:
- if isinstance(val, int):
- binlist.append(ibin(val, bits))
- elif isinstance(val, dict):
- nonspecvars = list(variables)
- for key in val.keys():
- nonspecvars.remove(key)
- for t in product((0, 1), repeat=len(nonspecvars)):
- d = dict(zip(nonspecvars, t))
- d.update(val)
- binlist.append([d[v] for v in variables])
- elif isinstance(val, (list, tuple)):
- if len(val) != bits:
- raise ValueError("Each term must contain {bits} bits as there are"
- "\n{bits} variables (or be an integer)."
- "".format(bits=bits))
- binlist.append(list(val))
- else:
- raise TypeError("A term list can only contain lists,"
- " ints or dicts.")
- return binlist
- def SOPform(variables, minterms, dontcares=None):
- """
- The SOPform function uses simplified_pairs and a redundant group-
- eliminating algorithm to convert the list of all input combos that
- generate '1' (the minterms) into the smallest Sum of Products form.
- The variables must be given as the first argument.
- Return a logical Or function (i.e., the "sum of products" or "SOP"
- form) that gives the desired outcome. If there are inputs that can
- be ignored, pass them as a list, too.
- The result will be one of the (perhaps many) functions that satisfy
- the conditions.
- Examples
- ========
- >>> from sympy.logic import SOPform
- >>> from sympy import symbols
- >>> w, x, y, z = symbols('w x y z')
- >>> minterms = [[0, 0, 0, 1], [0, 0, 1, 1],
- ... [0, 1, 1, 1], [1, 0, 1, 1], [1, 1, 1, 1]]
- >>> dontcares = [[0, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 1]]
- >>> SOPform([w, x, y, z], minterms, dontcares)
- (y & z) | (~w & ~x)
- The terms can also be represented as integers:
- >>> minterms = [1, 3, 7, 11, 15]
- >>> dontcares = [0, 2, 5]
- >>> SOPform([w, x, y, z], minterms, dontcares)
- (y & z) | (~w & ~x)
- They can also be specified using dicts, which does not have to be fully
- specified:
- >>> minterms = [{w: 0, x: 1}, {y: 1, z: 1, x: 0}]
- >>> SOPform([w, x, y, z], minterms)
- (x & ~w) | (y & z & ~x)
- Or a combination:
- >>> minterms = [4, 7, 11, [1, 1, 1, 1]]
- >>> dontcares = [{w : 0, x : 0, y: 0}, 5]
- >>> SOPform([w, x, y, z], minterms, dontcares)
- (w & y & z) | (~w & ~y) | (x & z & ~w)
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Quine-McCluskey_algorithm
- """
- if not minterms:
- return false
- variables = tuple(map(sympify, variables))
- minterms = _input_to_binlist(minterms, variables)
- dontcares = _input_to_binlist((dontcares or []), variables)
- for d in dontcares:
- if d in minterms:
- raise ValueError('%s in minterms is also in dontcares' % d)
- return _sop_form(variables, minterms, dontcares)
- def _sop_form(variables, minterms, dontcares):
- new = _simplified_pairs(minterms + dontcares)
- essential = _rem_redundancy(new, minterms)
- return Or(*[_convert_to_varsSOP(x, variables) for x in essential])
- def POSform(variables, minterms, dontcares=None):
- """
- The POSform function uses simplified_pairs and a redundant-group
- eliminating algorithm to convert the list of all input combinations
- that generate '1' (the minterms) into the smallest Product of Sums form.
- The variables must be given as the first argument.
- Return a logical And function (i.e., the "product of sums" or "POS"
- form) that gives the desired outcome. If there are inputs that can
- be ignored, pass them as a list, too.
- The result will be one of the (perhaps many) functions that satisfy
- the conditions.
- Examples
- ========
- >>> from sympy.logic import POSform
- >>> from sympy import symbols
- >>> w, x, y, z = symbols('w x y z')
- >>> minterms = [[0, 0, 0, 1], [0, 0, 1, 1], [0, 1, 1, 1],
- ... [1, 0, 1, 1], [1, 1, 1, 1]]
- >>> dontcares = [[0, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 1]]
- >>> POSform([w, x, y, z], minterms, dontcares)
- z & (y | ~w)
- The terms can also be represented as integers:
- >>> minterms = [1, 3, 7, 11, 15]
- >>> dontcares = [0, 2, 5]
- >>> POSform([w, x, y, z], minterms, dontcares)
- z & (y | ~w)
- They can also be specified using dicts, which does not have to be fully
- specified:
- >>> minterms = [{w: 0, x: 1}, {y: 1, z: 1, x: 0}]
- >>> POSform([w, x, y, z], minterms)
- (x | y) & (x | z) & (~w | ~x)
- Or a combination:
- >>> minterms = [4, 7, 11, [1, 1, 1, 1]]
- >>> dontcares = [{w : 0, x : 0, y: 0}, 5]
- >>> POSform([w, x, y, z], minterms, dontcares)
- (w | x) & (y | ~w) & (z | ~y)
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Quine-McCluskey_algorithm
- """
- if not minterms:
- return false
- variables = tuple(map(sympify, variables))
- minterms = _input_to_binlist(minterms, variables)
- dontcares = _input_to_binlist((dontcares or []), variables)
- for d in dontcares:
- if d in minterms:
- raise ValueError('%s in minterms is also in dontcares' % d)
- maxterms = []
- for t in product((0, 1), repeat=len(variables)):
- t = list(t)
- if (t not in minterms) and (t not in dontcares):
- maxterms.append(t)
- new = _simplified_pairs(maxterms + dontcares)
- essential = _rem_redundancy(new, maxterms)
- return And(*[_convert_to_varsPOS(x, variables) for x in essential])
- def ANFform(variables, truthvalues):
- """
- The ANFform function converts the list of truth values to
- Algebraic Normal Form (ANF).
- The variables must be given as the first argument.
- Return True, False, logical And funciton (i.e., the
- "Zhegalkin monomial") or logical Xor function (i.e.,
- the "Zhegalkin polynomial"). When True and False
- are represented by 1 and 0, respectively, then
- And is multiplication and Xor is addition.
- Formally a "Zhegalkin monomial" is the product (logical
- And) of a finite set of distinct variables, including
- the empty set whose product is denoted 1 (True).
- A "Zhegalkin polynomial" is the sum (logical Xor) of a
- set of Zhegalkin monomials, with the empty set denoted
- by 0 (False).
- Parameters
- ==========
- variables : list of variables
- truthvalues : list of 1's and 0's (result column of truth table)
- Examples
- ========
- >>> from sympy.logic.boolalg import ANFform
- >>> from sympy.abc import x, y
- >>> ANFform([x], [1, 0])
- x ^ True
- >>> ANFform([x, y], [0, 1, 1, 1])
- x ^ y ^ (x & y)
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Zhegalkin_polynomial
- """
- n_vars = len(variables)
- n_values = len(truthvalues)
- if n_values != 2 ** n_vars:
- raise ValueError("The number of truth values must be equal to 2^%d, "
- "got %d" % (n_vars, n_values))
- variables = tuple(map(sympify, variables))
- coeffs = anf_coeffs(truthvalues)
- terms = []
- for i, t in enumerate(product((0, 1), repeat=n_vars)):
- if coeffs[i] == 1:
- terms.append(t)
- return Xor(*[_convert_to_varsANF(x, variables) for x in terms],
- remove_true=False)
- def anf_coeffs(truthvalues):
- """
- Convert a list of truth values of some boolean expression
- to the list of coefficients of the polynomial mod 2 (exclusive
- disjunction) representing the boolean expression in ANF
- (i.e., the "Zhegalkin polynomial").
- There are `2^n` possible Zhegalkin monomials in `n` variables, since
- each monomial is fully specified by the presence or absence of
- each variable.
- We can enumerate all the monomials. For example, boolean
- function with four variables ``(a, b, c, d)`` can contain
- up to `2^4 = 16` monomials. The 13-th monomial is the
- product ``a & b & d``, because 13 in binary is 1, 1, 0, 1.
- A given monomial's presence or absence in a polynomial corresponds
- to that monomial's coefficient being 1 or 0 respectively.
- Examples
- ========
- >>> from sympy.logic.boolalg import anf_coeffs, bool_monomial, Xor
- >>> from sympy.abc import a, b, c
- >>> truthvalues = [0, 1, 1, 0, 0, 1, 0, 1]
- >>> coeffs = anf_coeffs(truthvalues)
- >>> coeffs
- [0, 1, 1, 0, 0, 0, 1, 0]
- >>> polynomial = Xor(*[
- ... bool_monomial(k, [a, b, c])
- ... for k, coeff in enumerate(coeffs) if coeff == 1
- ... ])
- >>> polynomial
- b ^ c ^ (a & b)
- """
- s = '{:b}'.format(len(truthvalues))
- n = len(s) - 1
- if len(truthvalues) != 2**n:
- raise ValueError("The number of truth values must be a power of two, "
- "got %d" % len(truthvalues))
- coeffs = [[v] for v in truthvalues]
- for i in range(n):
- tmp = []
- for j in range(2 ** (n-i-1)):
- tmp.append(coeffs[2*j] +
- list(map(lambda x, y: x^y, coeffs[2*j], coeffs[2*j+1])))
- coeffs = tmp
- return coeffs[0]
- def bool_minterm(k, variables):
- """
- Return the k-th minterm.
- Minterms are numbered by a binary encoding of the complementation
- pattern of the variables. This convention assigns the value 1 to
- the direct form and 0 to the complemented form.
- Parameters
- ==========
- k : int or list of 1's and 0's (complementation patter)
- variables : list of variables
- Examples
- ========
- >>> from sympy.logic.boolalg import bool_minterm
- >>> from sympy.abc import x, y, z
- >>> bool_minterm([1, 0, 1], [x, y, z])
- x & z & ~y
- >>> bool_minterm(6, [x, y, z])
- x & y & ~z
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Canonical_normal_form#Indexing_minterms
- """
- if isinstance(k, int):
- k = ibin(k, len(variables))
- variables = tuple(map(sympify, variables))
- return _convert_to_varsSOP(k, variables)
- def bool_maxterm(k, variables):
- """
- Return the k-th maxterm.
- Each maxterm is assigned an index based on the opposite
- conventional binary encoding used for minterms. The maxterm
- convention assigns the value 0 to the direct form and 1 to
- the complemented form.
- Parameters
- ==========
- k : int or list of 1's and 0's (complementation pattern)
- variables : list of variables
- Examples
- ========
- >>> from sympy.logic.boolalg import bool_maxterm
- >>> from sympy.abc import x, y, z
- >>> bool_maxterm([1, 0, 1], [x, y, z])
- y | ~x | ~z
- >>> bool_maxterm(6, [x, y, z])
- z | ~x | ~y
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Canonical_normal_form#Indexing_maxterms
- """
- if isinstance(k, int):
- k = ibin(k, len(variables))
- variables = tuple(map(sympify, variables))
- return _convert_to_varsPOS(k, variables)
- def bool_monomial(k, variables):
- """
- Return the k-th monomial.
- Monomials are numbered by a binary encoding of the presence and
- absences of the variables. This convention assigns the value
- 1 to the presence of variable and 0 to the absence of variable.
- Each boolean function can be uniquely represented by a
- Zhegalkin Polynomial (Algebraic Normal Form). The Zhegalkin
- Polynomial of the boolean function with `n` variables can contain
- up to `2^n` monomials. We can enumarate all the monomials.
- Each monomial is fully specified by the presence or absence
- of each variable.
- For example, boolean function with four variables ``(a, b, c, d)``
- can contain up to `2^4 = 16` monomials. The 13-th monomial is the
- product ``a & b & d``, because 13 in binary is 1, 1, 0, 1.
- Parameters
- ==========
- k : int or list of 1's and 0's
- variables : list of variables
- Examples
- ========
- >>> from sympy.logic.boolalg import bool_monomial
- >>> from sympy.abc import x, y, z
- >>> bool_monomial([1, 0, 1], [x, y, z])
- x & z
- >>> bool_monomial(6, [x, y, z])
- x & y
- """
- if isinstance(k, int):
- k = ibin(k, len(variables))
- variables = tuple(map(sympify, variables))
- return _convert_to_varsANF(k, variables)
- def _find_predicates(expr):
- """Helper to find logical predicates in BooleanFunctions.
- A logical predicate is defined here as anything within a BooleanFunction
- that is not a BooleanFunction itself.
- """
- if not isinstance(expr, BooleanFunction):
- return {expr}
- return set().union(*(map(_find_predicates, expr.args)))
- def simplify_logic(expr, form=None, deep=True, force=False):
- """
- This function simplifies a boolean function to its simplified version
- in SOP or POS form. The return type is an Or or And object in SymPy.
- Parameters
- ==========
- expr : Boolean expression
- form : string (``'cnf'`` or ``'dnf'``) or ``None`` (default).
- If ``'cnf'`` or ``'dnf'``, the simplest expression in the corresponding
- normal form is returned; if ``None``, the answer is returned
- according to the form with fewest args (in CNF by default).
- deep : bool (default ``True``)
- Indicates whether to recursively simplify any
- non-boolean functions contained within the input.
- force : bool (default ``False``)
- As the simplifications require exponential time in the number
- of variables, there is by default a limit on expressions with
- 8 variables. When the expression has more than 8 variables
- only symbolical simplification (controlled by ``deep``) is
- made. By setting ``force`` to ``True``, this limit is removed. Be
- aware that this can lead to very long simplification times.
- Examples
- ========
- >>> from sympy.logic import simplify_logic
- >>> from sympy.abc import x, y, z
- >>> from sympy import S
- >>> b = (~x & ~y & ~z) | ( ~x & ~y & z)
- >>> simplify_logic(b)
- ~x & ~y
- >>> S(b)
- (z & ~x & ~y) | (~x & ~y & ~z)
- >>> simplify_logic(_)
- ~x & ~y
- """
- if form not in (None, 'cnf', 'dnf'):
- raise ValueError("form can be cnf or dnf only")
- expr = sympify(expr)
- # check for quick exit if form is given: right form and all args are
- # literal and do not involve Not
- if form:
- form_ok = False
- if form == 'cnf':
- form_ok = is_cnf(expr)
- elif form == 'dnf':
- form_ok = is_dnf(expr)
- if form_ok and all(is_literal(a)
- for a in expr.args):
- return expr
- from sympy.core.relational import Relational
- if deep:
- variables = expr.atoms(Relational)
- from sympy.simplify.simplify import simplify
- s = tuple(map(simplify, variables))
- expr = expr.xreplace(dict(zip(variables, s)))
- if not isinstance(expr, BooleanFunction):
- return expr
- # Replace Relationals with Dummys to possibly
- # reduce the number of variables
- repl = dict()
- undo = dict()
- from sympy.core.symbol import Dummy
- variables = expr.atoms(Relational)
- while variables:
- var = variables.pop()
- if var.is_Relational:
- d = Dummy()
- undo[d] = var
- repl[var] = d
- nvar = var.negated
- if nvar in variables:
- repl[nvar] = Not(d)
- variables.remove(nvar)
- expr = expr.xreplace(repl)
- # Get new variables after replacing
- variables = _find_predicates(expr)
- if not force and len(variables) > 8:
- return expr.xreplace(undo)
- # group into constants and variable values
- c, v = sift(ordered(variables), lambda x: x in (True, False), binary=True)
- variables = c + v
- truthtable = []
- # standardize constants to be 1 or 0 in keeping with truthtable
- c = [1 if i == True else 0 for i in c]
- truthtable = _get_truthtable(v, expr, c)
- big = len(truthtable) >= (2 ** (len(variables) - 1))
- if form == 'dnf' or form is None and big:
- return _sop_form(variables, truthtable, []).xreplace(undo)
- return POSform(variables, truthtable).xreplace(undo)
- def _get_truthtable(variables, expr, const):
- """ Return a list of all combinations leading to a True result for ``expr``.
- """
- def _get_tt(inputs):
- if variables:
- v = variables.pop()
- tab = [[i[0].xreplace({v: false}), [0] + i[1]] for i in inputs if i[0] is not false]
- tab.extend([[i[0].xreplace({v: true}), [1] + i[1]] for i in inputs if i[0] is not false])
- return _get_tt(tab)
- return inputs
- return [const + k[1] for k in _get_tt([[expr, []]]) if k[0]]
- def _finger(eq):
- """
- Assign a 5-item fingerprint to each symbol in the equation:
- [
- # of times it appeared as a Symbol;
- # of times it appeared as a Not(symbol);
- # of times it appeared as a Symbol in an And or Or;
- # of times it appeared as a Not(Symbol) in an And or Or;
- a sorted tuple of tuples, (i, j, k), where i is the number of arguments
- in an And or Or with which it appeared as a Symbol, and j is
- the number of arguments that were Not(Symbol); k is the number
- of times that (i, j) was seen.
- ]
- Examples
- ========
- >>> from sympy.logic.boolalg import _finger as finger
- >>> from sympy import And, Or, Not, Xor, to_cnf, symbols
- >>> from sympy.abc import a, b, x, y
- >>> eq = Or(And(Not(y), a), And(Not(y), b), And(x, y))
- >>> dict(finger(eq))
- {(0, 0, 1, 0, ((2, 0, 1),)): [x],
- (0, 0, 1, 0, ((2, 1, 1),)): [a, b],
- (0, 0, 1, 2, ((2, 0, 1),)): [y]}
- >>> dict(finger(x & ~y))
- {(0, 1, 0, 0, ()): [y], (1, 0, 0, 0, ()): [x]}
- In the following, the (5, 2, 6) means that there were 6 Or
- functions in which a symbol appeared as itself amongst 5 arguments in
- which there were also 2 negated symbols, e.g. ``(a0 | a1 | a2 | ~a3 | ~a4)``
- is counted once for a0, a1 and a2.
- >>> dict(finger(to_cnf(Xor(*symbols('a:5')))))
- {(0, 0, 8, 8, ((5, 0, 1), (5, 2, 6), (5, 4, 1))): [a0, a1, a2, a3, a4]}
- The equation must not have more than one level of nesting:
- >>> dict(finger(And(Or(x, y), y)))
- {(0, 0, 1, 0, ((2, 0, 1),)): [x], (1, 0, 1, 0, ((2, 0, 1),)): [y]}
- >>> dict(finger(And(Or(x, And(a, x)), y)))
- Traceback (most recent call last):
- ...
- NotImplementedError: unexpected level of nesting
- So y and x have unique fingerprints, but a and b do not.
- """
- f = eq.free_symbols
- d = dict(list(zip(f, [[0]*4 + [defaultdict(int)] for fi in f])))
- for a in eq.args:
- if a.is_Symbol:
- d[a][0] += 1
- elif a.is_Not:
- d[a.args[0]][1] += 1
- else:
- o = len(a.args), sum(isinstance(ai, Not) for ai in a.args)
- for ai in a.args:
- if ai.is_Symbol:
- d[ai][2] += 1
- d[ai][-1][o] += 1
- elif ai.is_Not:
- d[ai.args[0]][3] += 1
- else:
- raise NotImplementedError('unexpected level of nesting')
- inv = defaultdict(list)
- for k, v in ordered(iter(d.items())):
- v[-1] = tuple(sorted([i + (j,) for i, j in v[-1].items()]))
- inv[tuple(v)].append(k)
- return inv
- def bool_map(bool1, bool2):
- """
- Return the simplified version of *bool1*, and the mapping of variables
- that makes the two expressions *bool1* and *bool2* represent the same
- logical behaviour for some correspondence between the variables
- of each.
- If more than one mappings of this sort exist, one of them
- is returned.
- For example, ``And(x, y)`` is logically equivalent to ``And(a, b)`` for
- the mapping ``{x: a, y: b}`` or ``{x: b, y: a}``.
- If no such mapping exists, return ``False``.
- Examples
- ========
- >>> from sympy import SOPform, bool_map, Or, And, Not, Xor
- >>> from sympy.abc import w, x, y, z, a, b, c, d
- >>> function1 = SOPform([x, z, y],[[1, 0, 1], [0, 0, 1]])
- >>> function2 = SOPform([a, b, c],[[1, 0, 1], [1, 0, 0]])
- >>> bool_map(function1, function2)
- (y & ~z, {y: a, z: b})
- The results are not necessarily unique, but they are canonical. Here,
- ``(w, z)`` could be ``(a, d)`` or ``(d, a)``:
- >>> eq = Or(And(Not(y), w), And(Not(y), z), And(x, y))
- >>> eq2 = Or(And(Not(c), a), And(Not(c), d), And(b, c))
- >>> bool_map(eq, eq2)
- ((x & y) | (w & ~y) | (z & ~y), {w: a, x: b, y: c, z: d})
- >>> eq = And(Xor(a, b), c, And(c,d))
- >>> bool_map(eq, eq.subs(c, x))
- (c & d & (a | b) & (~a | ~b), {a: a, b: b, c: d, d: x})
- """
- def match(function1, function2):
- """Return the mapping that equates variables between two
- simplified boolean expressions if possible.
- By "simplified" we mean that a function has been denested
- and is either an And (or an Or) whose arguments are either
- symbols (x), negated symbols (Not(x)), or Or (or an And) whose
- arguments are only symbols or negated symbols. For example,
- ``And(x, Not(y), Or(w, Not(z)))``.
- Basic.match is not robust enough (see issue 4835) so this is
- a workaround that is valid for simplified boolean expressions
- """
- # do some quick checks
- if function1.__class__ != function2.__class__:
- return None # maybe simplification makes them the same?
- if len(function1.args) != len(function2.args):
- return None # maybe simplification makes them the same?
- if function1.is_Symbol:
- return {function1: function2}
- # get the fingerprint dictionaries
- f1 = _finger(function1)
- f2 = _finger(function2)
- # more quick checks
- if len(f1) != len(f2):
- return False
- # assemble the match dictionary if possible
- matchdict = {}
- for k in f1.keys():
- if k not in f2:
- return False
- if len(f1[k]) != len(f2[k]):
- return False
- for i, x in enumerate(f1[k]):
- matchdict[x] = f2[k][i]
- return matchdict
- a = simplify_logic(bool1)
- b = simplify_logic(bool2)
- m = match(a, b)
- if m:
- return a, m
- return m
- def _apply_patternbased_simplification(rv, patterns, measure,
- dominatingvalue,
- replacementvalue=None,
- threeterm_patterns=None):
- """
- Replace patterns of Relational
- Parameters
- ==========
- rv : Expr
- Boolean expression
- patterns : tuple
- Tuple of tuples, with (pattern to simplify, simplified pattern).
- measure : function
- Simplification measure.
- dominatingvalue : Boolean or ``None``
- The dominating value for the function of consideration.
- For example, for :py:class:`~.And` ``S.false`` is dominating.
- As soon as one expression is ``S.false`` in :py:class:`~.And`,
- the whole expression is ``S.false``.
- replacementvalue : Boolean or ``None``, optional
- The resulting value for the whole expression if one argument
- evaluates to ``dominatingvalue``.
- For example, for :py:class:`~.Nand` ``S.false`` is dominating, but
- in this case the resulting value is ``S.true``. Default is ``None``.
- If ``replacementvalue`` is ``None`` and ``dominatingvalue`` is not
- ``None``, ``replacementvalue = dominatingvalue``.
- """
- from sympy.core.relational import Relational, _canonical
- if replacementvalue is None and dominatingvalue is not None:
- replacementvalue = dominatingvalue
- # Use replacement patterns for Relationals
- Rel, nonRel = sift(rv.args, lambda i: isinstance(i, Relational),
- binary=True)
- if len(Rel) <= 1:
- return rv
- Rel, nonRealRel = sift(Rel, lambda i: not any(s.is_real is False
- for s in i.free_symbols),
- binary=True)
- Rel = [i.canonical for i in Rel]
- if threeterm_patterns and len(Rel) >= 3:
- Rel = _apply_patternbased_threeterm_simplification(Rel,
- threeterm_patterns, rv.func, dominatingvalue,
- replacementvalue, measure)
- Rel = _apply_patternbased_twoterm_simplification(Rel, patterns,
- rv.func, dominatingvalue, replacementvalue, measure)
- rv = rv.func(*([_canonical(i) for i in ordered(Rel)]
- + nonRel + nonRealRel))
- return rv
- def _apply_patternbased_twoterm_simplification(Rel, patterns, func,
- dominatingvalue,
- replacementvalue,
- measure):
- """ Apply pattern-based two-term simplification."""
- from sympy.functions.elementary.miscellaneous import Min, Max
- from sympy.core.relational import Ge, Gt, _Inequality
- changed = True
- while changed and len(Rel) >= 2:
- changed = False
- # Use only < or <=
- Rel = [r.reversed if isinstance(r, (Ge, Gt)) else r for r in Rel]
- # Sort based on ordered
- Rel = list(ordered(Rel))
- # Eq and Ne must be tested reversed as well
- rtmp = [(r, ) if isinstance(r, _Inequality) else (r, r.reversed) for r in Rel]
- # Create a list of possible replacements
- results = []
- # Try all combinations of possibly reversed relational
- for ((i, pi), (j, pj)) in combinations(enumerate(rtmp), 2):
- for pattern, simp in patterns:
- res = []
- for p1, p2 in product(pi, pj):
- # use SymPy matching
- oldexpr = Tuple(p1, p2)
- tmpres = oldexpr.match(pattern)
- if tmpres:
- res.append((tmpres, oldexpr))
- if res:
- for tmpres, oldexpr in res:
- # we have a matching, compute replacement
- np = simp.xreplace(tmpres)
- if np == dominatingvalue:
- # if dominatingvalue, the whole expression
- # will be replacementvalue
- return [replacementvalue]
- # add replacement
- if not isinstance(np, ITE) and not np.has(Min, Max):
- # We only want to use ITE and Min/Max replacements if
- # they simplify to a relational
- costsaving = measure(func(*oldexpr.args)) - measure(np)
- if costsaving > 0:
- results.append((costsaving, ([i, j], np)))
- if results:
- # Sort results based on complexity
- results = list(reversed(sorted(results,
- key=lambda pair: pair[0])))
- # Replace the one providing most simplification
- replacement = results[0][1]
- idx, newrel = replacement
- idx.sort()
- # Remove the old relationals
- for index in reversed(idx):
- del Rel[index]
- if dominatingvalue is None or newrel != Not(dominatingvalue):
- # Insert the new one (no need to insert a value that will
- # not affect the result)
- if newrel.func == func:
- for a in newrel.args:
- Rel.append(a)
- else:
- Rel.append(newrel)
- # We did change something so try again
- changed = True
- return Rel
- def _apply_patternbased_threeterm_simplification(Rel, patterns, func,
- dominatingvalue,
- replacementvalue,
- measure):
- """ Apply pattern-based three-term simplification."""
- from sympy.functions.elementary.miscellaneous import Min, Max
- from sympy.core.relational import Le, Lt, _Inequality
- changed = True
- while changed and len(Rel) >= 3:
- changed = False
- # Use only > or >=
- Rel = [r.reversed if isinstance(r, (Le, Lt)) else r for r in Rel]
- # Sort based on ordered
- Rel = list(ordered(Rel))
- # Create a list of possible replacements
- results = []
- # Eq and Ne must be tested reversed as well
- rtmp = [(r, ) if isinstance(r, _Inequality) else (r, r.reversed) for r in Rel]
- # Try all combinations of possibly reversed relational
- for ((i, pi), (j, pj), (k, pk)) in permutations(enumerate(rtmp), 3):
- for pattern, simp in patterns:
- res = []
- for p1, p2, p3 in product(pi, pj, pk):
- # use SymPy matching
- oldexpr = Tuple(p1, p2, p3)
- tmpres = oldexpr.match(pattern)
- if tmpres:
- res.append((tmpres, oldexpr))
- if res:
- for tmpres, oldexpr in res:
- # we have a matching, compute replacement
- np = simp.xreplace(tmpres)
- if np == dominatingvalue:
- # if dominatingvalue, the whole expression
- # will be replacementvalue
- return [replacementvalue]
- # add replacement
- if not isinstance(np, ITE) and not np.has(Min, Max):
- # We only want to use ITE and Min/Max replacements if
- # they simplify to a relational
- costsaving = measure(func(*oldexpr.args)) - measure(np)
- if costsaving > 0:
- results.append((costsaving, ([i, j, k], np)))
- if results:
- # Sort results based on complexity
- results = list(reversed(sorted(results,
- key=lambda pair: pair[0])))
- # Replace the one providing most simplification
- replacement = results[0][1]
- idx, newrel = replacement
- idx.sort()
- # Remove the old relationals
- for index in reversed(idx):
- del Rel[index]
- if dominatingvalue is None or newrel != Not(dominatingvalue):
- # Insert the new one (no need to insert a value that will
- # not affect the result)
- if newrel.func == func:
- for a in newrel.args:
- Rel.append(a)
- else:
- Rel.append(newrel)
- # We did change something so try again
- changed = True
- return Rel
- def _simplify_patterns_and():
- """ Two-term patterns for And."""
- from sympy.core import Wild
- from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt
- from sympy.functions.elementary.complexes import Abs
- from sympy.functions.elementary.miscellaneous import Min, Max
- a = Wild('a')
- b = Wild('b')
- c = Wild('c')
- # Relationals patterns should be in alphabetical order
- # (pattern1, pattern2, simplified)
- # Do not use Ge, Gt
- _matchers_and = ((Tuple(Eq(a, b), Lt(a, b)), S.false),
- #(Tuple(Eq(a, b), Lt(b, a)), S.false),
- #(Tuple(Le(b, a), Lt(a, b)), S.false),
- #(Tuple(Lt(b, a), Le(a, b)), S.false),
- (Tuple(Lt(b, a), Lt(a, b)), S.false),
- (Tuple(Eq(a, b), Le(b, a)), Eq(a, b)),
- #(Tuple(Eq(a, b), Le(a, b)), Eq(a, b)),
- #(Tuple(Le(b, a), Lt(b, a)), Gt(a, b)),
- (Tuple(Le(b, a), Le(a, b)), Eq(a, b)),
- #(Tuple(Le(b, a), Ne(a, b)), Gt(a, b)),
- #(Tuple(Lt(b, a), Ne(a, b)), Gt(a, b)),
- (Tuple(Le(a, b), Lt(a, b)), Lt(a, b)),
- (Tuple(Le(a, b), Ne(a, b)), Lt(a, b)),
- (Tuple(Lt(a, b), Ne(a, b)), Lt(a, b)),
- # Sign
- (Tuple(Eq(a, b), Eq(a, -b)), And(Eq(a, S.Zero), Eq(b, S.Zero))),
- # Min/Max/ITE
- (Tuple(Le(b, a), Le(c, a)), Ge(a, Max(b, c))),
- (Tuple(Le(b, a), Lt(c, a)), ITE(b > c, Ge(a, b), Gt(a, c))),
- (Tuple(Lt(b, a), Lt(c, a)), Gt(a, Max(b, c))),
- (Tuple(Le(a, b), Le(a, c)), Le(a, Min(b, c))),
- (Tuple(Le(a, b), Lt(a, c)), ITE(b < c, Le(a, b), Lt(a, c))),
- (Tuple(Lt(a, b), Lt(a, c)), Lt(a, Min(b, c))),
- (Tuple(Le(a, b), Le(c, a)), ITE(Eq(b, c), Eq(a, b), ITE(b < c, S.false, And(Le(a, b), Ge(a, c))))),
- (Tuple(Le(c, a), Le(a, b)), ITE(Eq(b, c), Eq(a, b), ITE(b < c, S.false, And(Le(a, b), Ge(a, c))))),
- (Tuple(Lt(a, b), Lt(c, a)), ITE(b < c, S.false, And(Lt(a, b), Gt(a, c)))),
- (Tuple(Lt(c, a), Lt(a, b)), ITE(b < c, S.false, And(Lt(a, b), Gt(a, c)))),
- (Tuple(Le(a, b), Lt(c, a)), ITE(b <= c, S.false, And(Le(a, b), Gt(a, c)))),
- (Tuple(Le(c, a), Lt(a, b)), ITE(b <= c, S.false, And(Lt(a, b), Ge(a, c)))),
- (Tuple(Eq(a, b), Eq(a, c)), ITE(Eq(b, c), Eq(a, b), S.false)),
- (Tuple(Lt(a, b), Lt(-b, a)), ITE(b > 0, Lt(Abs(a), b), S.false)),
- (Tuple(Le(a, b), Le(-b, a)), ITE(b >= 0, Le(Abs(a), b), S.false)),
- )
- return _matchers_and
- def _simplify_patterns_and3():
- """ Three-term patterns for And."""
- from sympy.core import Wild
- from sympy.core.relational import Eq, Ge, Gt
- a = Wild('a')
- b = Wild('b')
- c = Wild('c')
- # Relationals patterns should be in alphabetical order
- # (pattern1, pattern2, pattern3, simplified)
- # Do not use Le, Lt
- _matchers_and = ((Tuple(Ge(a, b), Ge(b, c), Gt(c, a)), S.false),
- (Tuple(Ge(a, b), Gt(b, c), Gt(c, a)), S.false),
- (Tuple(Gt(a, b), Gt(b, c), Gt(c, a)), S.false),
- # (Tuple(Ge(c, a), Gt(a, b), Gt(b, c)), S.false),
- # Lower bound relations
- # Commented out combinations that does not simplify
- (Tuple(Ge(a, b), Ge(a, c), Ge(b, c)), And(Ge(a, b), Ge(b, c))),
- (Tuple(Ge(a, b), Ge(a, c), Gt(b, c)), And(Ge(a, b), Gt(b, c))),
- # (Tuple(Ge(a, b), Gt(a, c), Ge(b, c)), And(Ge(a, b), Ge(b, c))),
- (Tuple(Ge(a, b), Gt(a, c), Gt(b, c)), And(Ge(a, b), Gt(b, c))),
- # (Tuple(Gt(a, b), Ge(a, c), Ge(b, c)), And(Gt(a, b), Ge(b, c))),
- (Tuple(Ge(a, c), Gt(a, b), Gt(b, c)), And(Gt(a, b), Gt(b, c))),
- (Tuple(Ge(b, c), Gt(a, b), Gt(a, c)), And(Gt(a, b), Ge(b, c))),
- (Tuple(Gt(a, b), Gt(a, c), Gt(b, c)), And(Gt(a, b), Gt(b, c))),
- # Upper bound relations
- # Commented out combinations that does not simplify
- (Tuple(Ge(b, a), Ge(c, a), Ge(b, c)), And(Ge(c, a), Ge(b, c))),
- (Tuple(Ge(b, a), Ge(c, a), Gt(b, c)), And(Ge(c, a), Gt(b, c))),
- # (Tuple(Ge(b, a), Gt(c, a), Ge(b, c)), And(Gt(c, a), Ge(b, c))),
- (Tuple(Ge(b, a), Gt(c, a), Gt(b, c)), And(Gt(c, a), Gt(b, c))),
- # (Tuple(Gt(b, a), Ge(c, a), Ge(b, c)), And(Ge(c, a), Ge(b, c))),
- (Tuple(Ge(c, a), Gt(b, a), Gt(b, c)), And(Ge(c, a), Gt(b, c))),
- (Tuple(Ge(b, c), Gt(b, a), Gt(c, a)), And(Gt(c, a), Ge(b, c))),
- (Tuple(Gt(b, a), Gt(c, a), Gt(b, c)), And(Gt(c, a), Gt(b, c))),
- # Circular relation
- (Tuple(Ge(a, b), Ge(b, c), Ge(c, a)), And(Eq(a, b), Eq(b, c))),
- )
- return _matchers_and
- def _simplify_patterns_or():
- """ Two-term patterns for Or."""
- from sympy.core import Wild
- from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt
- from sympy.functions.elementary.complexes import Abs
- from sympy.functions.elementary.miscellaneous import Min, Max
- a = Wild('a')
- b = Wild('b')
- c = Wild('c')
- # Relationals patterns should be in alphabetical order
- # (pattern1, pattern2, simplified)
- # Do not use Ge, Gt
- _matchers_or = ((Tuple(Le(b, a), Le(a, b)), S.true),
- #(Tuple(Le(b, a), Lt(a, b)), S.true),
- (Tuple(Le(b, a), Ne(a, b)), S.true),
- #(Tuple(Le(a, b), Lt(b, a)), S.true),
- #(Tuple(Le(a, b), Ne(a, b)), S.true),
- #(Tuple(Eq(a, b), Le(b, a)), Ge(a, b)),
- #(Tuple(Eq(a, b), Lt(b, a)), Ge(a, b)),
- (Tuple(Eq(a, b), Le(a, b)), Le(a, b)),
- (Tuple(Eq(a, b), Lt(a, b)), Le(a, b)),
- #(Tuple(Le(b, a), Lt(b, a)), Ge(a, b)),
- (Tuple(Lt(b, a), Lt(a, b)), Ne(a, b)),
- (Tuple(Lt(b, a), Ne(a, b)), Ne(a, b)),
- (Tuple(Le(a, b), Lt(a, b)), Le(a, b)),
- #(Tuple(Lt(a, b), Ne(a, b)), Ne(a, b)),
- # Min/Max/ITE
- (Tuple(Le(b, a), Le(c, a)), Ge(a, Min(b, c))),
- #(Tuple(Ge(b, a), Ge(c, a)), Ge(Min(b, c), a)),
- (Tuple(Le(b, a), Lt(c, a)), ITE(b > c, Gt(a, c), Ge(a, b))),
- (Tuple(Lt(b, a), Lt(c, a)), Gt(a, Min(b, c))),
- #(Tuple(Gt(b, a), Gt(c, a)), Gt(Min(b, c), a)),
- (Tuple(Le(a, b), Le(a, c)), Le(a, Max(b, c))),
- #(Tuple(Le(b, a), Le(c, a)), Le(Max(b, c), a)),
- (Tuple(Le(a, b), Lt(a, c)), ITE(b >= c, Le(a, b), Lt(a, c))),
- (Tuple(Lt(a, b), Lt(a, c)), Lt(a, Max(b, c))),
- #(Tuple(Lt(b, a), Lt(c, a)), Lt(Max(b, c), a)),
- (Tuple(Le(a, b), Le(c, a)), ITE(b >= c, S.true, Or(Le(a, b), Ge(a, c)))),
- (Tuple(Le(c, a), Le(a, b)), ITE(b >= c, S.true, Or(Le(a, b), Ge(a, c)))),
- (Tuple(Lt(a, b), Lt(c, a)), ITE(b > c, S.true, Or(Lt(a, b), Gt(a, c)))),
- (Tuple(Lt(c, a), Lt(a, b)), ITE(b > c, S.true, Or(Lt(a, b), Gt(a, c)))),
- (Tuple(Le(a, b), Lt(c, a)), ITE(b >= c, S.true, Or(Le(a, b), Gt(a, c)))),
- (Tuple(Le(c, a), Lt(a, b)), ITE(b >= c, S.true, Or(Lt(a, b), Ge(a, c)))),
- (Tuple(Lt(b, a), Lt(a, -b)), ITE(b >= 0, Gt(Abs(a), b), S.true)),
- (Tuple(Le(b, a), Le(a, -b)), ITE(b > 0, Ge(Abs(a), b), S.true)),
- )
- return _matchers_or
- def _simplify_patterns_xor():
- """ Two-term patterns for Xor."""
- from sympy.functions.elementary.miscellaneous import Min, Max
- from sympy.core import Wild
- from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt
- a = Wild('a')
- b = Wild('b')
- c = Wild('c')
- # Relationals patterns should be in alphabetical order
- # (pattern1, pattern2, simplified)
- # Do not use Ge, Gt
- _matchers_xor = (#(Tuple(Le(b, a), Lt(a, b)), S.true),
- #(Tuple(Lt(b, a), Le(a, b)), S.true),
- #(Tuple(Eq(a, b), Le(b, a)), Gt(a, b)),
- #(Tuple(Eq(a, b), Lt(b, a)), Ge(a, b)),
- (Tuple(Eq(a, b), Le(a, b)), Lt(a, b)),
- (Tuple(Eq(a, b), Lt(a, b)), Le(a, b)),
- (Tuple(Le(a, b), Lt(a, b)), Eq(a, b)),
- (Tuple(Le(a, b), Le(b, a)), Ne(a, b)),
- (Tuple(Le(b, a), Ne(a, b)), Le(a, b)),
- # (Tuple(Lt(b, a), Lt(a, b)), Ne(a, b)),
- (Tuple(Lt(b, a), Ne(a, b)), Lt(a, b)),
- # (Tuple(Le(a, b), Lt(a, b)), Eq(a, b)),
- # (Tuple(Le(a, b), Ne(a, b)), Ge(a, b)),
- # (Tuple(Lt(a, b), Ne(a, b)), Gt(a, b)),
- # Min/Max/ITE
- (Tuple(Le(b, a), Le(c, a)),
- And(Ge(a, Min(b, c)), Lt(a, Max(b, c)))),
- (Tuple(Le(b, a), Lt(c, a)),
- ITE(b > c, And(Gt(a, c), Lt(a, b)),
- And(Ge(a, b), Le(a, c)))),
- (Tuple(Lt(b, a), Lt(c, a)),
- And(Gt(a, Min(b, c)), Le(a, Max(b, c)))),
- (Tuple(Le(a, b), Le(a, c)),
- And(Le(a, Max(b, c)), Gt(a, Min(b, c)))),
- (Tuple(Le(a, b), Lt(a, c)),
- ITE(b < c, And(Lt(a, c), Gt(a, b)),
- And(Le(a, b), Ge(a, c)))),
- (Tuple(Lt(a, b), Lt(a, c)),
- And(Lt(a, Max(b, c)), Ge(a, Min(b, c)))),
- )
- return _matchers_xor
- def simplify_univariate(expr):
- """return a simplified version of univariate boolean expression, else ``expr``"""
- from sympy.functions.elementary.piecewise import Piecewise
- from sympy.core.relational import Eq, Ne
- if not isinstance(expr, BooleanFunction):
- return expr
- if expr.atoms(Eq, Ne):
- return expr
- c = expr
- free = c.free_symbols
- if len(free) != 1:
- return c
- x = free.pop()
- ok, i = Piecewise((0, c), evaluate=False
- )._intervals(x, err_on_Eq=True)
- if not ok:
- return c
- if not i:
- return S.false
- args = []
- for a, b, _, _ in i:
- if a is S.NegativeInfinity:
- if b is S.Infinity:
- c = S.true
- else:
- if c.subs(x, b) == True:
- c = (x <= b)
- else:
- c = (x < b)
- else:
- incl_a = (c.subs(x, a) == True)
- incl_b = (c.subs(x, b) == True)
- if incl_a and incl_b:
- if b.is_infinite:
- c = (x >= a)
- else:
- c = And(a <= x, x <= b)
- elif incl_a:
- c = And(a <= x, x < b)
- elif incl_b:
- if b.is_infinite:
- c = (x > a)
- else:
- c = And(a < x, x <= b)
- else:
- c = And(a < x, x < b)
- args.append(c)
- return Or(*args)
- # Classes corresponding to logic gates
- # Used in gateinputcount method
- BooleanGates = (And, Or, Xor, Nand, Nor, Not, Xnor, ITE)
- def gateinputcount(expr):
- """
- Return the total number of inputs for the logic gates realizing the
- Boolean expression.
- Returns
- =======
- int
- Number of gate inputs
- Note
- ====
- Not all Boolean functions count as gate here, only those that are
- considered to be standard gates. These are: ``And``, ``Or``, ``Xor``,
- ``Not``, and ``ITE`` (multiplexer). ``Nand``, ``Nor``, and ``Xnor`` will
- be evaluated to ``Not(And())`` etc.
- Examples
- ========
- >>> from sympy.logic import And, Or, Nand, Not, gateinputcount
- >>> from sympy.abc import x, y, z
- >>> expr = And(x, y)
- >>> gateinputcount(expr)
- 2
- >>> gateinputcount(Or(expr, z))
- 4
- Note that ``Nand`` is automatically evaluated to ``Not(And())`` so
- >>> gateinputcount(Nand(x, y, z))
- 4
- >>> gateinputcount(Not(And(x, y, z)))
- 4
- Although this can be avoided by using ``evaluate=False``
- >>> gateinputcount(Nand(x, y, z, evaluate=False))
- 3
- Also note that a comparison will count as a Boolean variable:
- >>> gateinputcount(And(x > z, y >= 2))
- 2
- As will a symbol:
- >>> gateinputcount(x)
- 0
- """
- if not isinstance(expr, Boolean):
- raise TypeError("Expression must be Boolean")
- if isinstance(expr, BooleanGates):
- return len(expr.args) + sum(gateinputcount(x) for x in expr.args)
- return 0
|