| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587 |
- """
- Boolean algebra module for SymPy
- """
- from __future__ import annotations
- from typing import TYPE_CHECKING, overload, Any
- from collections.abc import Iterable, Mapping
- 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 true
- if e == False:
- return false
- if isinstance(e, Symbol):
- z = e.is_zero
- if z is None:
- return e
- return false if z else 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
- if TYPE_CHECKING:
- def __new__(cls, *args: Basic | complex) -> Boolean:
- ...
- @overload # type: ignore
- def subs(self, arg1: Mapping[Basic | complex, Boolean | complex], arg2: None=None) -> Boolean: ...
- @overload
- def subs(self, arg1: Iterable[tuple[Basic | complex, Boolean | complex]], arg2: None=None, **kwargs: Any) -> Boolean: ...
- @overload
- def subs(self, arg1: Boolean | complex, arg2: Boolean | complex) -> Boolean: ...
- @overload
- def subs(self, arg1: Mapping[Basic | complex, Basic | complex], arg2: None=None, **kwargs: Any) -> Basic: ...
- @overload
- def subs(self, arg1: Iterable[tuple[Basic | complex, Basic | complex]], arg2: None=None, **kwargs: Any) -> Basic: ...
- @overload
- def subs(self, arg1: Basic | complex, arg2: Basic | complex, **kwargs: Any) -> Basic: ...
- def subs(self, arg1: Mapping[Basic | complex, Basic | complex] | Basic | complex, # type: ignore
- arg2: Basic | complex | None = None, **kwargs: Any) -> Basic:
- ...
- def simplify(self, **kwargs) -> Boolean:
- ...
- @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
- 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:
- Do not 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 # doctest: +SKIP
- (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 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 # doctest: +SKIP
- (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 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: true if x else 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):
- return [as_Boolean(i) 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)
- new_args = []
- for arg in args:
- if deep:
- if not is_literal(arg) or isinstance(arg, Not):
- arg = arg.to_anf(deep=deep)
- new_args.append(arg)
- return cls(*new_args, 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 produce different results if
- ``a`` and ``b`` are integers.
- >>> And(x, y).subs(x, 1)
- y
- """
- zero = false
- identity = true
- nargs = None
- if TYPE_CHECKING:
- def __new__(cls, *args: Boolean | bool) -> Boolean: # type: ignore
- ...
- @property
- def args(self) -> tuple[Boolean, ...]:
- ...
- @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 [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 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(
- Add(e.lhs, -e.rhs, 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, 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
- if TYPE_CHECKING:
- def __new__(cls, *args: Boolean | bool) -> Boolean: # type: ignore
- ...
- @property
- def args(self) -> tuple[Boolean, ...]:
- ...
- @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 [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 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'], 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 = (to_anf(x, deep=deep) if deep else x for x in 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``.
- - As of Python 3.12, the bitwise not operator ``~`` used on a
- Python ``bool`` is deprecated and will emit a warning.
- >>> from sympy import true
- >>> ~True # doctest: +SKIP
- -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 = not 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])
- rv = rv.to_anf()
- 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 true
- elif A.is_Relational and B.is_Relational:
- if A.canonical == B.canonical:
- return 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 true:
- a = a.rhs
- elif a.rhs is true:
- a = a.lhs
- elif a.lhs is false:
- a = Not(a.rhs)
- elif a.rhs is false:
- a = Not(a.lhs)
- else:
- # binary can only equal True or False
- a = 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 true in a.args:
- a = a.lhs if a.rhs is true else a.rhs
- elif false in a.args:
- a = Not(a.lhs) if a.rhs is false else Not(a.rhs)
- else:
- _a = None
- if _a is not None and isinstance(_a, Ne):
- a = Not(a)
- if a is true:
- return b
- if a is 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 true and c is false:
- return a
- if b is false and c is 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.elementary.piecewise 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 :py:class:`~.And`, :py:class:`~.Or` and :py:class:`~.Not`,
- and :py:class:`~.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 the ``force`` flag must be set
- to ``True`` to simplify (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, the ``force`` flag must be set to
- ``True`` to simplify (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 :py:class:`~.And`, :py:class:`~.Or` and :py:class:`~.Not`,
- and :py:class:`~.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 :py:class:`~.Implies` and :py:class:`~.Equivalent` into
- :py:class:`~.And`, :py:class:`~.Or`, and :py:class:`~.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(zip(symbols, 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 its
- variable form (for ANF).
- Parameters
- ==========
- term : list of 1's and 0's (complementation pattern)
- 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 = {}
- 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 :py:class:`~.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)
- See also
- ========
- POSform
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Quine-McCluskey_algorithm
- .. [2] https://en.wikipedia.org/wiki/Don%27t-care_term
- """
- 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 :py:class:`~.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)
- See also
- ========
- SOPform
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Quine-McCluskey_algorithm
- .. [2] https://en.wikipedia.org/wiki/Don%27t-care_term
- """
- 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 :py:class:`~.And` function (i.e., the
- "Zhegalkin monomial") or logical :py:class:`~.Xor` function (i.e.,
- the "Zhegalkin polynomial"). When True and False
- are represented by 1 and 0, respectively, then
- :py:class:`~.And` is multiplication and :py:class:`~.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 pattern)
- 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 enumerate 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, dontcare=None):
- """
- This function simplifies a boolean function to its simplified version
- in SOP or POS form. The return type is an :py:class:`~.Or` or
- :py:class:`~.And` object in SymPy.
- Parameters
- ==========
- expr : Boolean
- 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.
- dontcare : Boolean
- Optimize expression under the assumption that inputs where this
- expression is true are don't care. This is useful in e.g. Piecewise
- conditions, where later conditions do not need to consider inputs that
- are converted by previous conditions. For example, if a previous
- condition is ``And(A, B)``, the simplification of expr can be made
- with don't cares for ``And(A, B)``.
- Examples
- ========
- >>> from sympy.logic import simplify_logic
- >>> from sympy.abc import x, y, z
- >>> b = (~x & ~y & ~z) | ( ~x & ~y & z)
- >>> simplify_logic(b)
- ~x & ~y
- >>> simplify_logic(x | y, dontcare=y)
- x
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Don%27t-care_term
- """
- 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 = {}
- undo = {}
- from sympy.core.symbol import Dummy
- variables = expr.atoms(Relational)
- if dontcare is not None:
- dontcare = sympify(dontcare)
- variables.update(dontcare.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)
- if dontcare is not None:
- dontcare = dontcare.xreplace(repl)
- # Get new variables after replacing
- variables = _find_predicates(expr)
- if not force and len(variables) > 8:
- return expr.xreplace(undo)
- if dontcare is not None:
- # Add variables from dontcare
- dcvariables = _find_predicates(dontcare)
- variables.update(dcvariables)
- # if too many restore to variables only
- if not force and len(variables) > 8:
- variables = _find_predicates(expr)
- dontcare = None
- # group into constants and variable values
- c, v = sift(ordered(variables), lambda x: x in (True, False), binary=True)
- variables = c + v
- # 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)
- if dontcare is not None:
- dctruthtable = _get_truthtable(v, dontcare, c)
- truthtable = [t for t in truthtable if t not in dctruthtable]
- else:
- dctruthtable = []
- big = len(truthtable) >= (2 ** (len(variables) - 1))
- if form == 'dnf' or form is None and big:
- return _sop_form(variables, truthtable, dctruthtable).xreplace(undo)
- return POSform(variables, truthtable, dctruthtable).xreplace(undo)
- def _get_truthtable(variables, expr, const):
- """ Return a list of all combinations leading to a True result for ``expr``.
- """
- _variables = variables.copy()
- 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
- res = [const + k[1] for k in _get_tt([[expr, []]]) if k[0]]
- if res == [[]]:
- return []
- else:
- return res
- 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) with
- two terms.
- 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``.
- threeterm_patterns : tuple, optional
- Tuple of tuples, with (pattern to simplify, simplified pattern) with
- three terms.
- """
- 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 = sorted(results,
- key=lambda pair: pair[0], reverse=True)
- # 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 = sorted(results,
- key=lambda pair: pair[0], reverse=True)
- # 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
- @cacheit
- 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)), 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)), 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, 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, false, And(Le(a, b), Ge(a, c))))),
- (Tuple(Lt(a, b), Lt(c, a)), ITE(b < c, false, And(Lt(a, b), Gt(a, c)))),
- (Tuple(Lt(c, a), Lt(a, b)), ITE(b < c, false, And(Lt(a, b), Gt(a, c)))),
- (Tuple(Le(a, b), Lt(c, a)), ITE(b <= c, false, And(Le(a, b), Gt(a, c)))),
- (Tuple(Le(c, a), Lt(a, b)), ITE(b <= c, false, And(Lt(a, b), Ge(a, c)))),
- (Tuple(Eq(a, b), Eq(a, c)), ITE(Eq(b, c), Eq(a, b), false)),
- (Tuple(Lt(a, b), Lt(-b, a)), ITE(b > 0, Lt(Abs(a), b), false)),
- (Tuple(Le(a, b), Le(-b, a)), ITE(b >= 0, Le(Abs(a), b), false)),
- )
- return _matchers_and
- @cacheit
- 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)), false),
- (Tuple(Ge(a, b), Gt(b, c), Gt(c, a)), false),
- (Tuple(Gt(a, b), Gt(b, c), Gt(c, a)), 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
- @cacheit
- 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)), true),
- #(Tuple(Le(b, a), Lt(a, b)), true),
- (Tuple(Le(b, a), Ne(a, b)), true),
- #(Tuple(Le(a, b), Lt(b, a)), true),
- #(Tuple(Le(a, b), Ne(a, b)), 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)),
- (Tuple(Eq(a, b), Ne(a, c)), ITE(Eq(b, c), true, Ne(a, c))),
- (Tuple(Ne(a, b), Ne(a, c)), ITE(Eq(b, c), Ne(a, b), true)),
- # 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, Lt(c, a), Le(b, a))),
- (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, true, Or(Le(a, b), Ge(a, c)))),
- (Tuple(Le(c, a), Le(a, b)), ITE(b >= c, true, Or(Le(a, b), Ge(a, c)))),
- (Tuple(Lt(a, b), Lt(c, a)), ITE(b > c, true, Or(Lt(a, b), Gt(a, c)))),
- (Tuple(Lt(c, a), Lt(a, b)), ITE(b > c, true, Or(Lt(a, b), Gt(a, c)))),
- (Tuple(Le(a, b), Lt(c, a)), ITE(b >= c, true, Or(Le(a, b), Gt(a, c)))),
- (Tuple(Le(c, a), Lt(a, b)), ITE(b >= c, true, Or(Lt(a, b), Ge(a, c)))),
- (Tuple(Lt(b, a), Lt(a, -b)), ITE(b >= 0, Gt(Abs(a), b), true)),
- (Tuple(Le(b, a), Le(a, -b)), ITE(b > 0, Ge(Abs(a), b), true)),
- )
- return _matchers_or
- @cacheit
- 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)), true),
- #(Tuple(Lt(b, a), Le(a, b)), 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 false
- args = []
- for a, b, _, _ in i:
- if a is S.NegativeInfinity:
- if b is S.Infinity:
- c = 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: :py:class:`~.And`,
- :py:class:`~.Or`, :py:class:`~.Xor`, :py:class:`~.Not`, and
- :py:class:`~.ITE` (multiplexer). :py:class:`~.Nand`, :py:class:`~.Nor`,
- and :py:class:`~.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
|