1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136 |
- """
- Second quantization operators and states for bosons.
- This follow the formulation of Fetter and Welecka, "Quantum Theory
- of Many-Particle Systems."
- """
- from collections import defaultdict
- 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.expr import Expr
- from sympy.core.function import Function
- from sympy.core.mul import Mul
- from sympy.core.numbers import I
- from sympy.core.power import Pow
- from sympy.core.singleton import S
- from sympy.core.sorting import default_sort_key
- from sympy.core.symbol import Dummy, Symbol
- from sympy.core.sympify import sympify
- from sympy.functions.elementary.miscellaneous import sqrt
- from sympy.functions.special.tensor_functions import KroneckerDelta
- from sympy.matrices.dense import zeros
- from sympy.printing.str import StrPrinter
- from sympy.utilities.iterables import has_dups
- __all__ = [
- 'Dagger',
- 'KroneckerDelta',
- 'BosonicOperator',
- 'AnnihilateBoson',
- 'CreateBoson',
- 'AnnihilateFermion',
- 'CreateFermion',
- 'FockState',
- 'FockStateBra',
- 'FockStateKet',
- 'FockStateBosonKet',
- 'FockStateBosonBra',
- 'FockStateFermionKet',
- 'FockStateFermionBra',
- 'BBra',
- 'BKet',
- 'FBra',
- 'FKet',
- 'F',
- 'Fd',
- 'B',
- 'Bd',
- 'apply_operators',
- 'InnerProduct',
- 'BosonicBasis',
- 'VarBosonicBasis',
- 'FixedBosonicBasis',
- 'Commutator',
- 'matrix_rep',
- 'contraction',
- 'wicks',
- 'NO',
- 'evaluate_deltas',
- 'AntiSymmetricTensor',
- 'substitute_dummies',
- 'PermutationOperator',
- 'simplify_index_permutations',
- ]
- class SecondQuantizationError(Exception):
- pass
- class AppliesOnlyToSymbolicIndex(SecondQuantizationError):
- pass
- class ContractionAppliesOnlyToFermions(SecondQuantizationError):
- pass
- class ViolationOfPauliPrinciple(SecondQuantizationError):
- pass
- class SubstitutionOfAmbigousOperatorFailed(SecondQuantizationError):
- pass
- class WicksTheoremDoesNotApply(SecondQuantizationError):
- pass
- class Dagger(Expr):
- """
- Hermitian conjugate of creation/annihilation operators.
- Examples
- ========
- >>> from sympy import I
- >>> from sympy.physics.secondquant import Dagger, B, Bd
- >>> Dagger(2*I)
- -2*I
- >>> Dagger(B(0))
- CreateBoson(0)
- >>> Dagger(Bd(0))
- AnnihilateBoson(0)
- """
- def __new__(cls, arg):
- arg = sympify(arg)
- r = cls.eval(arg)
- if isinstance(r, Basic):
- return r
- obj = Basic.__new__(cls, arg)
- return obj
- @classmethod
- def eval(cls, arg):
- """
- Evaluates the Dagger instance.
- Examples
- ========
- >>> from sympy import I
- >>> from sympy.physics.secondquant import Dagger, B, Bd
- >>> Dagger(2*I)
- -2*I
- >>> Dagger(B(0))
- CreateBoson(0)
- >>> Dagger(Bd(0))
- AnnihilateBoson(0)
- The eval() method is called automatically.
- """
- dagger = getattr(arg, '_dagger_', None)
- if dagger is not None:
- return dagger()
- if isinstance(arg, Basic):
- if arg.is_Add:
- return Add(*tuple(map(Dagger, arg.args)))
- if arg.is_Mul:
- return Mul(*tuple(map(Dagger, reversed(arg.args))))
- if arg.is_Number:
- return arg
- if arg.is_Pow:
- return Pow(Dagger(arg.args[0]), arg.args[1])
- if arg == I:
- return -arg
- else:
- return None
- def _dagger_(self):
- return self.args[0]
- class TensorSymbol(Expr):
- is_commutative = True
- class AntiSymmetricTensor(TensorSymbol):
- """Stores upper and lower indices in separate Tuple's.
- Each group of indices is assumed to be antisymmetric.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.physics.secondquant import AntiSymmetricTensor
- >>> i, j = symbols('i j', below_fermi=True)
- >>> a, b = symbols('a b', above_fermi=True)
- >>> AntiSymmetricTensor('v', (a, i), (b, j))
- AntiSymmetricTensor(v, (a, i), (b, j))
- >>> AntiSymmetricTensor('v', (i, a), (b, j))
- -AntiSymmetricTensor(v, (a, i), (b, j))
- As you can see, the indices are automatically sorted to a canonical form.
- """
- def __new__(cls, symbol, upper, lower):
- try:
- upper, signu = _sort_anticommuting_fermions(
- upper, key=cls._sortkey)
- lower, signl = _sort_anticommuting_fermions(
- lower, key=cls._sortkey)
- except ViolationOfPauliPrinciple:
- return S.Zero
- symbol = sympify(symbol)
- upper = Tuple(*upper)
- lower = Tuple(*lower)
- if (signu + signl) % 2:
- return -TensorSymbol.__new__(cls, symbol, upper, lower)
- else:
- return TensorSymbol.__new__(cls, symbol, upper, lower)
- @classmethod
- def _sortkey(cls, index):
- """Key for sorting of indices.
- particle < hole < general
- FIXME: This is a bottle-neck, can we do it faster?
- """
- h = hash(index)
- label = str(index)
- if isinstance(index, Dummy):
- if index.assumptions0.get('above_fermi'):
- return (20, label, h)
- elif index.assumptions0.get('below_fermi'):
- return (21, label, h)
- else:
- return (22, label, h)
- if index.assumptions0.get('above_fermi'):
- return (10, label, h)
- elif index.assumptions0.get('below_fermi'):
- return (11, label, h)
- else:
- return (12, label, h)
- def _latex(self, printer):
- return "{%s^{%s}_{%s}}" % (
- self.symbol,
- "".join([ i.name for i in self.args[1]]),
- "".join([ i.name for i in self.args[2]])
- )
- @property
- def symbol(self):
- """
- Returns the symbol of the tensor.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.physics.secondquant import AntiSymmetricTensor
- >>> i, j = symbols('i,j', below_fermi=True)
- >>> a, b = symbols('a,b', above_fermi=True)
- >>> AntiSymmetricTensor('v', (a, i), (b, j))
- AntiSymmetricTensor(v, (a, i), (b, j))
- >>> AntiSymmetricTensor('v', (a, i), (b, j)).symbol
- v
- """
- return self.args[0]
- @property
- def upper(self):
- """
- Returns the upper indices.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.physics.secondquant import AntiSymmetricTensor
- >>> i, j = symbols('i,j', below_fermi=True)
- >>> a, b = symbols('a,b', above_fermi=True)
- >>> AntiSymmetricTensor('v', (a, i), (b, j))
- AntiSymmetricTensor(v, (a, i), (b, j))
- >>> AntiSymmetricTensor('v', (a, i), (b, j)).upper
- (a, i)
- """
- return self.args[1]
- @property
- def lower(self):
- """
- Returns the lower indices.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.physics.secondquant import AntiSymmetricTensor
- >>> i, j = symbols('i,j', below_fermi=True)
- >>> a, b = symbols('a,b', above_fermi=True)
- >>> AntiSymmetricTensor('v', (a, i), (b, j))
- AntiSymmetricTensor(v, (a, i), (b, j))
- >>> AntiSymmetricTensor('v', (a, i), (b, j)).lower
- (b, j)
- """
- return self.args[2]
- def __str__(self):
- return "%s(%s,%s)" % self.args
- def doit(self, **kw_args):
- """
- Returns self.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.physics.secondquant import AntiSymmetricTensor
- >>> i, j = symbols('i,j', below_fermi=True)
- >>> a, b = symbols('a,b', above_fermi=True)
- >>> AntiSymmetricTensor('v', (a, i), (b, j)).doit()
- AntiSymmetricTensor(v, (a, i), (b, j))
- """
- return self
- class SqOperator(Expr):
- """
- Base class for Second Quantization operators.
- """
- op_symbol = 'sq'
- is_commutative = False
- def __new__(cls, k):
- obj = Basic.__new__(cls, sympify(k))
- return obj
- @property
- def state(self):
- """
- Returns the state index related to this operator.
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import F, Fd, B, Bd
- >>> p = Symbol('p')
- >>> F(p).state
- p
- >>> Fd(p).state
- p
- >>> B(p).state
- p
- >>> Bd(p).state
- p
- """
- return self.args[0]
- @property
- def is_symbolic(self):
- """
- Returns True if the state is a symbol (as opposed to a number).
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import F
- >>> p = Symbol('p')
- >>> F(p).is_symbolic
- True
- >>> F(1).is_symbolic
- False
- """
- if self.state.is_Integer:
- return False
- else:
- return True
- def doit(self, **kw_args):
- """
- FIXME: hack to prevent crash further up...
- """
- return self
- def __repr__(self):
- return NotImplemented
- def __str__(self):
- return "%s(%r)" % (self.op_symbol, self.state)
- def apply_operator(self, state):
- """
- Applies an operator to itself.
- """
- raise NotImplementedError('implement apply_operator in a subclass')
- class BosonicOperator(SqOperator):
- pass
- class Annihilator(SqOperator):
- pass
- class Creator(SqOperator):
- pass
- class AnnihilateBoson(BosonicOperator, Annihilator):
- """
- Bosonic annihilation operator.
- Examples
- ========
- >>> from sympy.physics.secondquant import B
- >>> from sympy.abc import x
- >>> B(x)
- AnnihilateBoson(x)
- """
- op_symbol = 'b'
- def _dagger_(self):
- return CreateBoson(self.state)
- def apply_operator(self, state):
- """
- Apply state to self if self is not symbolic and state is a FockStateKet, else
- multiply self by state.
- Examples
- ========
- >>> from sympy.physics.secondquant import B, BKet
- >>> from sympy.abc import x, y, n
- >>> B(x).apply_operator(y)
- y*AnnihilateBoson(x)
- >>> B(0).apply_operator(BKet((n,)))
- sqrt(n)*FockStateBosonKet((n - 1,))
- """
- if not self.is_symbolic and isinstance(state, FockStateKet):
- element = self.state
- amp = sqrt(state[element])
- return amp*state.down(element)
- else:
- return Mul(self, state)
- def __repr__(self):
- return "AnnihilateBoson(%s)" % self.state
- def _latex(self, printer):
- if self.state is S.Zero:
- return "b_{0}"
- else:
- return "b_{%s}" % self.state.name
- class CreateBoson(BosonicOperator, Creator):
- """
- Bosonic creation operator.
- """
- op_symbol = 'b+'
- def _dagger_(self):
- return AnnihilateBoson(self.state)
- def apply_operator(self, state):
- """
- Apply state to self if self is not symbolic and state is a FockStateKet, else
- multiply self by state.
- Examples
- ========
- >>> from sympy.physics.secondquant import B, Dagger, BKet
- >>> from sympy.abc import x, y, n
- >>> Dagger(B(x)).apply_operator(y)
- y*CreateBoson(x)
- >>> B(0).apply_operator(BKet((n,)))
- sqrt(n)*FockStateBosonKet((n - 1,))
- """
- if not self.is_symbolic and isinstance(state, FockStateKet):
- element = self.state
- amp = sqrt(state[element] + 1)
- return amp*state.up(element)
- else:
- return Mul(self, state)
- def __repr__(self):
- return "CreateBoson(%s)" % self.state
- def _latex(self, printer):
- if self.state is S.Zero:
- return "{b^\\dagger_{0}}"
- else:
- return "{b^\\dagger_{%s}}" % self.state.name
- B = AnnihilateBoson
- Bd = CreateBoson
- class FermionicOperator(SqOperator):
- @property
- def is_restricted(self):
- """
- Is this FermionicOperator restricted with respect to fermi level?
- Returns
- =======
- 1 : restricted to orbits above fermi
- 0 : no restriction
- -1 : restricted to orbits below fermi
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import F, Fd
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> F(a).is_restricted
- 1
- >>> Fd(a).is_restricted
- 1
- >>> F(i).is_restricted
- -1
- >>> Fd(i).is_restricted
- -1
- >>> F(p).is_restricted
- 0
- >>> Fd(p).is_restricted
- 0
- """
- ass = self.args[0].assumptions0
- if ass.get("below_fermi"):
- return -1
- if ass.get("above_fermi"):
- return 1
- return 0
- @property
- def is_above_fermi(self):
- """
- Does the index of this FermionicOperator allow values above fermi?
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import F
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> F(a).is_above_fermi
- True
- >>> F(i).is_above_fermi
- False
- >>> F(p).is_above_fermi
- True
- Note
- ====
- The same applies to creation operators Fd
- """
- return not self.args[0].assumptions0.get("below_fermi")
- @property
- def is_below_fermi(self):
- """
- Does the index of this FermionicOperator allow values below fermi?
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import F
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> F(a).is_below_fermi
- False
- >>> F(i).is_below_fermi
- True
- >>> F(p).is_below_fermi
- True
- The same applies to creation operators Fd
- """
- return not self.args[0].assumptions0.get("above_fermi")
- @property
- def is_only_below_fermi(self):
- """
- Is the index of this FermionicOperator restricted to values below fermi?
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import F
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> F(a).is_only_below_fermi
- False
- >>> F(i).is_only_below_fermi
- True
- >>> F(p).is_only_below_fermi
- False
- The same applies to creation operators Fd
- """
- return self.is_below_fermi and not self.is_above_fermi
- @property
- def is_only_above_fermi(self):
- """
- Is the index of this FermionicOperator restricted to values above fermi?
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import F
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> F(a).is_only_above_fermi
- True
- >>> F(i).is_only_above_fermi
- False
- >>> F(p).is_only_above_fermi
- False
- The same applies to creation operators Fd
- """
- return self.is_above_fermi and not self.is_below_fermi
- def _sortkey(self):
- h = hash(self)
- label = str(self.args[0])
- if self.is_only_q_creator:
- return 1, label, h
- if self.is_only_q_annihilator:
- return 4, label, h
- if isinstance(self, Annihilator):
- return 3, label, h
- if isinstance(self, Creator):
- return 2, label, h
- class AnnihilateFermion(FermionicOperator, Annihilator):
- """
- Fermionic annihilation operator.
- """
- op_symbol = 'f'
- def _dagger_(self):
- return CreateFermion(self.state)
- def apply_operator(self, state):
- """
- Apply state to self if self is not symbolic and state is a FockStateKet, else
- multiply self by state.
- Examples
- ========
- >>> from sympy.physics.secondquant import B, Dagger, BKet
- >>> from sympy.abc import x, y, n
- >>> Dagger(B(x)).apply_operator(y)
- y*CreateBoson(x)
- >>> B(0).apply_operator(BKet((n,)))
- sqrt(n)*FockStateBosonKet((n - 1,))
- """
- if isinstance(state, FockStateFermionKet):
- element = self.state
- return state.down(element)
- elif isinstance(state, Mul):
- c_part, nc_part = state.args_cnc()
- if isinstance(nc_part[0], FockStateFermionKet):
- element = self.state
- return Mul(*(c_part + [nc_part[0].down(element)] + nc_part[1:]))
- else:
- return Mul(self, state)
- else:
- return Mul(self, state)
- @property
- def is_q_creator(self):
- """
- Can we create a quasi-particle? (create hole or create particle)
- If so, would that be above or below the fermi surface?
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import F
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> F(a).is_q_creator
- 0
- >>> F(i).is_q_creator
- -1
- >>> F(p).is_q_creator
- -1
- """
- if self.is_below_fermi:
- return -1
- return 0
- @property
- def is_q_annihilator(self):
- """
- Can we destroy a quasi-particle? (annihilate hole or annihilate particle)
- If so, would that be above or below the fermi surface?
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import F
- >>> a = Symbol('a', above_fermi=1)
- >>> i = Symbol('i', below_fermi=1)
- >>> p = Symbol('p')
- >>> F(a).is_q_annihilator
- 1
- >>> F(i).is_q_annihilator
- 0
- >>> F(p).is_q_annihilator
- 1
- """
- if self.is_above_fermi:
- return 1
- return 0
- @property
- def is_only_q_creator(self):
- """
- Always create a quasi-particle? (create hole or create particle)
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import F
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> F(a).is_only_q_creator
- False
- >>> F(i).is_only_q_creator
- True
- >>> F(p).is_only_q_creator
- False
- """
- return self.is_only_below_fermi
- @property
- def is_only_q_annihilator(self):
- """
- Always destroy a quasi-particle? (annihilate hole or annihilate particle)
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import F
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> F(a).is_only_q_annihilator
- True
- >>> F(i).is_only_q_annihilator
- False
- >>> F(p).is_only_q_annihilator
- False
- """
- return self.is_only_above_fermi
- def __repr__(self):
- return "AnnihilateFermion(%s)" % self.state
- def _latex(self, printer):
- if self.state is S.Zero:
- return "a_{0}"
- else:
- return "a_{%s}" % self.state.name
- class CreateFermion(FermionicOperator, Creator):
- """
- Fermionic creation operator.
- """
- op_symbol = 'f+'
- def _dagger_(self):
- return AnnihilateFermion(self.state)
- def apply_operator(self, state):
- """
- Apply state to self if self is not symbolic and state is a FockStateKet, else
- multiply self by state.
- Examples
- ========
- >>> from sympy.physics.secondquant import B, Dagger, BKet
- >>> from sympy.abc import x, y, n
- >>> Dagger(B(x)).apply_operator(y)
- y*CreateBoson(x)
- >>> B(0).apply_operator(BKet((n,)))
- sqrt(n)*FockStateBosonKet((n - 1,))
- """
- if isinstance(state, FockStateFermionKet):
- element = self.state
- return state.up(element)
- elif isinstance(state, Mul):
- c_part, nc_part = state.args_cnc()
- if isinstance(nc_part[0], FockStateFermionKet):
- element = self.state
- return Mul(*(c_part + [nc_part[0].up(element)] + nc_part[1:]))
- return Mul(self, state)
- @property
- def is_q_creator(self):
- """
- Can we create a quasi-particle? (create hole or create particle)
- If so, would that be above or below the fermi surface?
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import Fd
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> Fd(a).is_q_creator
- 1
- >>> Fd(i).is_q_creator
- 0
- >>> Fd(p).is_q_creator
- 1
- """
- if self.is_above_fermi:
- return 1
- return 0
- @property
- def is_q_annihilator(self):
- """
- Can we destroy a quasi-particle? (annihilate hole or annihilate particle)
- If so, would that be above or below the fermi surface?
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import Fd
- >>> a = Symbol('a', above_fermi=1)
- >>> i = Symbol('i', below_fermi=1)
- >>> p = Symbol('p')
- >>> Fd(a).is_q_annihilator
- 0
- >>> Fd(i).is_q_annihilator
- -1
- >>> Fd(p).is_q_annihilator
- -1
- """
- if self.is_below_fermi:
- return -1
- return 0
- @property
- def is_only_q_creator(self):
- """
- Always create a quasi-particle? (create hole or create particle)
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import Fd
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> Fd(a).is_only_q_creator
- True
- >>> Fd(i).is_only_q_creator
- False
- >>> Fd(p).is_only_q_creator
- False
- """
- return self.is_only_above_fermi
- @property
- def is_only_q_annihilator(self):
- """
- Always destroy a quasi-particle? (annihilate hole or annihilate particle)
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import Fd
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> Fd(a).is_only_q_annihilator
- False
- >>> Fd(i).is_only_q_annihilator
- True
- >>> Fd(p).is_only_q_annihilator
- False
- """
- return self.is_only_below_fermi
- def __repr__(self):
- return "CreateFermion(%s)" % self.state
- def _latex(self, printer):
- if self.state is S.Zero:
- return "{a^\\dagger_{0}}"
- else:
- return "{a^\\dagger_{%s}}" % self.state.name
- Fd = CreateFermion
- F = AnnihilateFermion
- class FockState(Expr):
- """
- Many particle Fock state with a sequence of occupation numbers.
- Anywhere you can have a FockState, you can also have S.Zero.
- All code must check for this!
- Base class to represent FockStates.
- """
- is_commutative = False
- def __new__(cls, occupations):
- """
- occupations is a list with two possible meanings:
- - For bosons it is a list of occupation numbers.
- Element i is the number of particles in state i.
- - For fermions it is a list of occupied orbits.
- Element 0 is the state that was occupied first, element i
- is the i'th occupied state.
- """
- occupations = list(map(sympify, occupations))
- obj = Basic.__new__(cls, Tuple(*occupations))
- return obj
- def __getitem__(self, i):
- i = int(i)
- return self.args[0][i]
- def __repr__(self):
- return ("FockState(%r)") % (self.args)
- def __str__(self):
- return "%s%r%s" % (getattr(self, 'lbracket', ""), self._labels(), getattr(self, 'rbracket', ""))
- def _labels(self):
- return self.args[0]
- def __len__(self):
- return len(self.args[0])
- def _latex(self, printer):
- return "%s%s%s" % (getattr(self, 'lbracket_latex', ""), printer._print(self._labels()), getattr(self, 'rbracket_latex', ""))
- class BosonState(FockState):
- """
- Base class for FockStateBoson(Ket/Bra).
- """
- def up(self, i):
- """
- Performs the action of a creation operator.
- Examples
- ========
- >>> from sympy.physics.secondquant import BBra
- >>> b = BBra([1, 2])
- >>> b
- FockStateBosonBra((1, 2))
- >>> b.up(1)
- FockStateBosonBra((1, 3))
- """
- i = int(i)
- new_occs = list(self.args[0])
- new_occs[i] = new_occs[i] + S.One
- return self.__class__(new_occs)
- def down(self, i):
- """
- Performs the action of an annihilation operator.
- Examples
- ========
- >>> from sympy.physics.secondquant import BBra
- >>> b = BBra([1, 2])
- >>> b
- FockStateBosonBra((1, 2))
- >>> b.down(1)
- FockStateBosonBra((1, 1))
- """
- i = int(i)
- new_occs = list(self.args[0])
- if new_occs[i] == S.Zero:
- return S.Zero
- else:
- new_occs[i] = new_occs[i] - S.One
- return self.__class__(new_occs)
- class FermionState(FockState):
- """
- Base class for FockStateFermion(Ket/Bra).
- """
- fermi_level = 0
- def __new__(cls, occupations, fermi_level=0):
- occupations = list(map(sympify, occupations))
- if len(occupations) > 1:
- try:
- (occupations, sign) = _sort_anticommuting_fermions(
- occupations, key=hash)
- except ViolationOfPauliPrinciple:
- return S.Zero
- else:
- sign = 0
- cls.fermi_level = fermi_level
- if cls._count_holes(occupations) > fermi_level:
- return S.Zero
- if sign % 2:
- return S.NegativeOne*FockState.__new__(cls, occupations)
- else:
- return FockState.__new__(cls, occupations)
- def up(self, i):
- """
- Performs the action of a creation operator.
- Explanation
- ===========
- If below fermi we try to remove a hole,
- if above fermi we try to create a particle.
- If general index p we return ``Kronecker(p,i)*self``
- where ``i`` is a new symbol with restriction above or below.
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import FKet
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- >>> FKet([]).up(a)
- FockStateFermionKet((a,))
- A creator acting on vacuum below fermi vanishes
- >>> FKet([]).up(i)
- 0
- """
- present = i in self.args[0]
- if self._only_above_fermi(i):
- if present:
- return S.Zero
- else:
- return self._add_orbit(i)
- elif self._only_below_fermi(i):
- if present:
- return self._remove_orbit(i)
- else:
- return S.Zero
- else:
- if present:
- hole = Dummy("i", below_fermi=True)
- return KroneckerDelta(i, hole)*self._remove_orbit(i)
- else:
- particle = Dummy("a", above_fermi=True)
- return KroneckerDelta(i, particle)*self._add_orbit(i)
- def down(self, i):
- """
- Performs the action of an annihilation operator.
- Explanation
- ===========
- If below fermi we try to create a hole,
- If above fermi we try to remove a particle.
- If general index p we return ``Kronecker(p,i)*self``
- where ``i`` is a new symbol with restriction above or below.
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy.physics.secondquant import FKet
- >>> a = Symbol('a', above_fermi=True)
- >>> i = Symbol('i', below_fermi=True)
- >>> p = Symbol('p')
- An annihilator acting on vacuum above fermi vanishes
- >>> FKet([]).down(a)
- 0
- Also below fermi, it vanishes, unless we specify a fermi level > 0
- >>> FKet([]).down(i)
- 0
- >>> FKet([],4).down(i)
- FockStateFermionKet((i,))
- """
- present = i in self.args[0]
- if self._only_above_fermi(i):
- if present:
- return self._remove_orbit(i)
- else:
- return S.Zero
- elif self._only_below_fermi(i):
- if present:
- return S.Zero
- else:
- return self._add_orbit(i)
- else:
- if present:
- hole = Dummy("i", below_fermi=True)
- return KroneckerDelta(i, hole)*self._add_orbit(i)
- else:
- particle = Dummy("a", above_fermi=True)
- return KroneckerDelta(i, particle)*self._remove_orbit(i)
- @classmethod
- def _only_below_fermi(cls, i):
- """
- Tests if given orbit is only below fermi surface.
- If nothing can be concluded we return a conservative False.
- """
- if i.is_number:
- return i <= cls.fermi_level
- if i.assumptions0.get('below_fermi'):
- return True
- return False
- @classmethod
- def _only_above_fermi(cls, i):
- """
- Tests if given orbit is only above fermi surface.
- If fermi level has not been set we return True.
- If nothing can be concluded we return a conservative False.
- """
- if i.is_number:
- return i > cls.fermi_level
- if i.assumptions0.get('above_fermi'):
- return True
- return not cls.fermi_level
- def _remove_orbit(self, i):
- """
- Removes particle/fills hole in orbit i. No input tests performed here.
- """
- new_occs = list(self.args[0])
- pos = new_occs.index(i)
- del new_occs[pos]
- if (pos) % 2:
- return S.NegativeOne*self.__class__(new_occs, self.fermi_level)
- else:
- return self.__class__(new_occs, self.fermi_level)
- def _add_orbit(self, i):
- """
- Adds particle/creates hole in orbit i. No input tests performed here.
- """
- return self.__class__((i,) + self.args[0], self.fermi_level)
- @classmethod
- def _count_holes(cls, list):
- """
- Returns the number of identified hole states in list.
- """
- return len([i for i in list if cls._only_below_fermi(i)])
- def _negate_holes(self, list):
- return tuple([-i if i <= self.fermi_level else i for i in list])
- def __repr__(self):
- if self.fermi_level:
- return "FockStateKet(%r, fermi_level=%s)" % (self.args[0], self.fermi_level)
- else:
- return "FockStateKet(%r)" % (self.args[0],)
- def _labels(self):
- return self._negate_holes(self.args[0])
- class FockStateKet(FockState):
- """
- Representation of a ket.
- """
- lbracket = '|'
- rbracket = '>'
- lbracket_latex = r'\left|'
- rbracket_latex = r'\right\rangle'
- class FockStateBra(FockState):
- """
- Representation of a bra.
- """
- lbracket = '<'
- rbracket = '|'
- lbracket_latex = r'\left\langle'
- rbracket_latex = r'\right|'
- def __mul__(self, other):
- if isinstance(other, FockStateKet):
- return InnerProduct(self, other)
- else:
- return Expr.__mul__(self, other)
- class FockStateBosonKet(BosonState, FockStateKet):
- """
- Many particle Fock state with a sequence of occupation numbers.
- Occupation numbers can be any integer >= 0.
- Examples
- ========
- >>> from sympy.physics.secondquant import BKet
- >>> BKet([1, 2])
- FockStateBosonKet((1, 2))
- """
- def _dagger_(self):
- return FockStateBosonBra(*self.args)
- class FockStateBosonBra(BosonState, FockStateBra):
- """
- Describes a collection of BosonBra particles.
- Examples
- ========
- >>> from sympy.physics.secondquant import BBra
- >>> BBra([1, 2])
- FockStateBosonBra((1, 2))
- """
- def _dagger_(self):
- return FockStateBosonKet(*self.args)
- class FockStateFermionKet(FermionState, FockStateKet):
- """
- Many-particle Fock state with a sequence of occupied orbits.
- Explanation
- ===========
- Each state can only have one particle, so we choose to store a list of
- occupied orbits rather than a tuple with occupation numbers (zeros and ones).
- states below fermi level are holes, and are represented by negative labels
- in the occupation list.
- For symbolic state labels, the fermi_level caps the number of allowed hole-
- states.
- Examples
- ========
- >>> from sympy.physics.secondquant import FKet
- >>> FKet([1, 2])
- FockStateFermionKet((1, 2))
- """
- def _dagger_(self):
- return FockStateFermionBra(*self.args)
- class FockStateFermionBra(FermionState, FockStateBra):
- """
- See Also
- ========
- FockStateFermionKet
- Examples
- ========
- >>> from sympy.physics.secondquant import FBra
- >>> FBra([1, 2])
- FockStateFermionBra((1, 2))
- """
- def _dagger_(self):
- return FockStateFermionKet(*self.args)
- BBra = FockStateBosonBra
- BKet = FockStateBosonKet
- FBra = FockStateFermionBra
- FKet = FockStateFermionKet
- def _apply_Mul(m):
- """
- Take a Mul instance with operators and apply them to states.
- Explanation
- ===========
- This method applies all operators with integer state labels
- to the actual states. For symbolic state labels, nothing is done.
- When inner products of FockStates are encountered (like <a|b>),
- they are converted to instances of InnerProduct.
- This does not currently work on double inner products like,
- <a|b><c|d>.
- If the argument is not a Mul, it is simply returned as is.
- """
- if not isinstance(m, Mul):
- return m
- c_part, nc_part = m.args_cnc()
- n_nc = len(nc_part)
- if n_nc in (0, 1):
- return m
- else:
- last = nc_part[-1]
- next_to_last = nc_part[-2]
- if isinstance(last, FockStateKet):
- if isinstance(next_to_last, SqOperator):
- if next_to_last.is_symbolic:
- return m
- else:
- result = next_to_last.apply_operator(last)
- if result == 0:
- return S.Zero
- else:
- return _apply_Mul(Mul(*(c_part + nc_part[:-2] + [result])))
- elif isinstance(next_to_last, Pow):
- if isinstance(next_to_last.base, SqOperator) and \
- next_to_last.exp.is_Integer:
- if next_to_last.base.is_symbolic:
- return m
- else:
- result = last
- for i in range(next_to_last.exp):
- result = next_to_last.base.apply_operator(result)
- if result == 0:
- break
- if result == 0:
- return S.Zero
- else:
- return _apply_Mul(Mul(*(c_part + nc_part[:-2] + [result])))
- else:
- return m
- elif isinstance(next_to_last, FockStateBra):
- result = InnerProduct(next_to_last, last)
- if result == 0:
- return S.Zero
- else:
- return _apply_Mul(Mul(*(c_part + nc_part[:-2] + [result])))
- else:
- return m
- else:
- return m
- def apply_operators(e):
- """
- Take a SymPy expression with operators and states and apply the operators.
- Examples
- ========
- >>> from sympy.physics.secondquant import apply_operators
- >>> from sympy import sympify
- >>> apply_operators(sympify(3)+4)
- 7
- """
- e = e.expand()
- muls = e.atoms(Mul)
- subs_list = [(m, _apply_Mul(m)) for m in iter(muls)]
- return e.subs(subs_list)
- class InnerProduct(Basic):
- """
- An unevaluated inner product between a bra and ket.
- Explanation
- ===========
- Currently this class just reduces things to a product of
- Kronecker Deltas. In the future, we could introduce abstract
- states like ``|a>`` and ``|b>``, and leave the inner product unevaluated as
- ``<a|b>``.
- """
- is_commutative = True
- def __new__(cls, bra, ket):
- if not isinstance(bra, FockStateBra):
- raise TypeError("must be a bra")
- if not isinstance(ket, FockStateKet):
- raise TypeError("must be a key")
- return cls.eval(bra, ket)
- @classmethod
- def eval(cls, bra, ket):
- result = S.One
- for i, j in zip(bra.args[0], ket.args[0]):
- result *= KroneckerDelta(i, j)
- if result == 0:
- break
- return result
- @property
- def bra(self):
- """Returns the bra part of the state"""
- return self.args[0]
- @property
- def ket(self):
- """Returns the ket part of the state"""
- return self.args[1]
- def __repr__(self):
- sbra = repr(self.bra)
- sket = repr(self.ket)
- return "%s|%s" % (sbra[:-1], sket[1:])
- def __str__(self):
- return self.__repr__()
- def matrix_rep(op, basis):
- """
- Find the representation of an operator in a basis.
- Examples
- ========
- >>> from sympy.physics.secondquant import VarBosonicBasis, B, matrix_rep
- >>> b = VarBosonicBasis(5)
- >>> o = B(0)
- >>> matrix_rep(o, b)
- Matrix([
- [0, 1, 0, 0, 0],
- [0, 0, sqrt(2), 0, 0],
- [0, 0, 0, sqrt(3), 0],
- [0, 0, 0, 0, 2],
- [0, 0, 0, 0, 0]])
- """
- a = zeros(len(basis))
- for i in range(len(basis)):
- for j in range(len(basis)):
- a[i, j] = apply_operators(Dagger(basis[i])*op*basis[j])
- return a
- class BosonicBasis:
- """
- Base class for a basis set of bosonic Fock states.
- """
- pass
- class VarBosonicBasis:
- """
- A single state, variable particle number basis set.
- Examples
- ========
- >>> from sympy.physics.secondquant import VarBosonicBasis
- >>> b = VarBosonicBasis(5)
- >>> b
- [FockState((0,)), FockState((1,)), FockState((2,)),
- FockState((3,)), FockState((4,))]
- """
- def __init__(self, n_max):
- self.n_max = n_max
- self._build_states()
- def _build_states(self):
- self.basis = []
- for i in range(self.n_max):
- self.basis.append(FockStateBosonKet([i]))
- self.n_basis = len(self.basis)
- def index(self, state):
- """
- Returns the index of state in basis.
- Examples
- ========
- >>> from sympy.physics.secondquant import VarBosonicBasis
- >>> b = VarBosonicBasis(3)
- >>> state = b.state(1)
- >>> b
- [FockState((0,)), FockState((1,)), FockState((2,))]
- >>> state
- FockStateBosonKet((1,))
- >>> b.index(state)
- 1
- """
- return self.basis.index(state)
- def state(self, i):
- """
- The state of a single basis.
- Examples
- ========
- >>> from sympy.physics.secondquant import VarBosonicBasis
- >>> b = VarBosonicBasis(5)
- >>> b.state(3)
- FockStateBosonKet((3,))
- """
- return self.basis[i]
- def __getitem__(self, i):
- return self.state(i)
- def __len__(self):
- return len(self.basis)
- def __repr__(self):
- return repr(self.basis)
- class FixedBosonicBasis(BosonicBasis):
- """
- Fixed particle number basis set.
- Examples
- ========
- >>> from sympy.physics.secondquant import FixedBosonicBasis
- >>> b = FixedBosonicBasis(2, 2)
- >>> state = b.state(1)
- >>> b
- [FockState((2, 0)), FockState((1, 1)), FockState((0, 2))]
- >>> state
- FockStateBosonKet((1, 1))
- >>> b.index(state)
- 1
- """
- def __init__(self, n_particles, n_levels):
- self.n_particles = n_particles
- self.n_levels = n_levels
- self._build_particle_locations()
- self._build_states()
- def _build_particle_locations(self):
- tup = ["i%i" % i for i in range(self.n_particles)]
- first_loop = "for i0 in range(%i)" % self.n_levels
- other_loops = ''
- for cur, prev in zip(tup[1:], tup):
- temp = "for %s in range(%s + 1) " % (cur, prev)
- other_loops = other_loops + temp
- tup_string = "(%s)" % ", ".join(tup)
- list_comp = "[%s %s %s]" % (tup_string, first_loop, other_loops)
- result = eval(list_comp)
- if self.n_particles == 1:
- result = [(item,) for item in result]
- self.particle_locations = result
- def _build_states(self):
- self.basis = []
- for tuple_of_indices in self.particle_locations:
- occ_numbers = self.n_levels*[0]
- for level in tuple_of_indices:
- occ_numbers[level] += 1
- self.basis.append(FockStateBosonKet(occ_numbers))
- self.n_basis = len(self.basis)
- def index(self, state):
- """Returns the index of state in basis.
- Examples
- ========
- >>> from sympy.physics.secondquant import FixedBosonicBasis
- >>> b = FixedBosonicBasis(2, 3)
- >>> b.index(b.state(3))
- 3
- """
- return self.basis.index(state)
- def state(self, i):
- """Returns the state that lies at index i of the basis
- Examples
- ========
- >>> from sympy.physics.secondquant import FixedBosonicBasis
- >>> b = FixedBosonicBasis(2, 3)
- >>> b.state(3)
- FockStateBosonKet((1, 0, 1))
- """
- return self.basis[i]
- def __getitem__(self, i):
- return self.state(i)
- def __len__(self):
- return len(self.basis)
- def __repr__(self):
- return repr(self.basis)
- class Commutator(Function):
- """
- The Commutator: [A, B] = A*B - B*A
- The arguments are ordered according to .__cmp__()
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.physics.secondquant import Commutator
- >>> A, B = symbols('A,B', commutative=False)
- >>> Commutator(B, A)
- -Commutator(A, B)
- Evaluate the commutator with .doit()
- >>> comm = Commutator(A,B); comm
- Commutator(A, B)
- >>> comm.doit()
- A*B - B*A
- For two second quantization operators the commutator is evaluated
- immediately:
- >>> from sympy.physics.secondquant import Fd, F
- >>> a = symbols('a', above_fermi=True)
- >>> i = symbols('i', below_fermi=True)
- >>> p,q = symbols('p,q')
- >>> Commutator(Fd(a),Fd(i))
- 2*NO(CreateFermion(a)*CreateFermion(i))
- But for more complicated expressions, the evaluation is triggered by
- a call to .doit()
- >>> comm = Commutator(Fd(p)*Fd(q),F(i)); comm
- Commutator(CreateFermion(p)*CreateFermion(q), AnnihilateFermion(i))
- >>> comm.doit(wicks=True)
- -KroneckerDelta(i, p)*CreateFermion(q) +
- KroneckerDelta(i, q)*CreateFermion(p)
- """
- is_commutative = False
- @classmethod
- def eval(cls, a, b):
- """
- The Commutator [A,B] is on canonical form if A < B.
- Examples
- ========
- >>> from sympy.physics.secondquant import Commutator, F, Fd
- >>> from sympy.abc import x
- >>> c1 = Commutator(F(x), Fd(x))
- >>> c2 = Commutator(Fd(x), F(x))
- >>> Commutator.eval(c1, c2)
- 0
- """
- if not (a and b):
- return S.Zero
- if a == b:
- return S.Zero
- if a.is_commutative or b.is_commutative:
- return S.Zero
- #
- # [A+B,C] -> [A,C] + [B,C]
- #
- a = a.expand()
- if isinstance(a, Add):
- return Add(*[cls(term, b) for term in a.args])
- b = b.expand()
- if isinstance(b, Add):
- return Add(*[cls(a, term) for term in b.args])
- #
- # [xA,yB] -> xy*[A,B]
- #
- ca, nca = a.args_cnc()
- cb, ncb = b.args_cnc()
- c_part = list(ca) + list(cb)
- if c_part:
- return Mul(Mul(*c_part), cls(Mul._from_args(nca), Mul._from_args(ncb)))
- #
- # single second quantization operators
- #
- if isinstance(a, BosonicOperator) and isinstance(b, BosonicOperator):
- if isinstance(b, CreateBoson) and isinstance(a, AnnihilateBoson):
- return KroneckerDelta(a.state, b.state)
- if isinstance(a, CreateBoson) and isinstance(b, AnnihilateBoson):
- return S.NegativeOne*KroneckerDelta(a.state, b.state)
- else:
- return S.Zero
- if isinstance(a, FermionicOperator) and isinstance(b, FermionicOperator):
- return wicks(a*b) - wicks(b*a)
- #
- # Canonical ordering of arguments
- #
- if a.sort_key() > b.sort_key():
- return S.NegativeOne*cls(b, a)
- def doit(self, **hints):
- """
- Enables the computation of complex expressions.
- Examples
- ========
- >>> from sympy.physics.secondquant import Commutator, F, Fd
- >>> from sympy import symbols
- >>> i, j = symbols('i,j', below_fermi=True)
- >>> a, b = symbols('a,b', above_fermi=True)
- >>> c = Commutator(Fd(a)*F(i),Fd(b)*F(j))
- >>> c.doit(wicks=True)
- 0
- """
- a = self.args[0]
- b = self.args[1]
- if hints.get("wicks"):
- a = a.doit(**hints)
- b = b.doit(**hints)
- try:
- return wicks(a*b) - wicks(b*a)
- except ContractionAppliesOnlyToFermions:
- pass
- except WicksTheoremDoesNotApply:
- pass
- return (a*b - b*a).doit(**hints)
- def __repr__(self):
- return "Commutator(%s,%s)" % (self.args[0], self.args[1])
- def __str__(self):
- return "[%s,%s]" % (self.args[0], self.args[1])
- def _latex(self, printer):
- return "\\left[%s,%s\\right]" % tuple([
- printer._print(arg) for arg in self.args])
- class NO(Expr):
- """
- This Object is used to represent normal ordering brackets.
- i.e. {abcd} sometimes written :abcd:
- Explanation
- ===========
- Applying the function NO(arg) to an argument means that all operators in
- the argument will be assumed to anticommute, and have vanishing
- contractions. This allows an immediate reordering to canonical form
- upon object creation.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.physics.secondquant import NO, F, Fd
- >>> p,q = symbols('p,q')
- >>> NO(Fd(p)*F(q))
- NO(CreateFermion(p)*AnnihilateFermion(q))
- >>> NO(F(q)*Fd(p))
- -NO(CreateFermion(p)*AnnihilateFermion(q))
- Note
- ====
- If you want to generate a normal ordered equivalent of an expression, you
- should use the function wicks(). This class only indicates that all
- operators inside the brackets anticommute, and have vanishing contractions.
- Nothing more, nothing less.
- """
- is_commutative = False
- def __new__(cls, arg):
- """
- Use anticommutation to get canonical form of operators.
- Explanation
- ===========
- Employ associativity of normal ordered product: {ab{cd}} = {abcd}
- but note that {ab}{cd} /= {abcd}.
- We also employ distributivity: {ab + cd} = {ab} + {cd}.
- Canonical form also implies expand() {ab(c+d)} = {abc} + {abd}.
- """
- # {ab + cd} = {ab} + {cd}
- arg = sympify(arg)
- arg = arg.expand()
- if arg.is_Add:
- return Add(*[ cls(term) for term in arg.args])
- if arg.is_Mul:
- # take coefficient outside of normal ordering brackets
- c_part, seq = arg.args_cnc()
- if c_part:
- coeff = Mul(*c_part)
- if not seq:
- return coeff
- else:
- coeff = S.One
- # {ab{cd}} = {abcd}
- newseq = []
- foundit = False
- for fac in seq:
- if isinstance(fac, NO):
- newseq.extend(fac.args)
- foundit = True
- else:
- newseq.append(fac)
- if foundit:
- return coeff*cls(Mul(*newseq))
- # We assume that the user don't mix B and F operators
- if isinstance(seq[0], BosonicOperator):
- raise NotImplementedError
- try:
- newseq, sign = _sort_anticommuting_fermions(seq)
- except ViolationOfPauliPrinciple:
- return S.Zero
- if sign % 2:
- return (S.NegativeOne*coeff)*cls(Mul(*newseq))
- elif sign:
- return coeff*cls(Mul(*newseq))
- else:
- pass # since sign==0, no permutations was necessary
- # if we couldn't do anything with Mul object, we just
- # mark it as normal ordered
- if coeff != S.One:
- return coeff*cls(Mul(*newseq))
- return Expr.__new__(cls, Mul(*newseq))
- if isinstance(arg, NO):
- return arg
- # if object was not Mul or Add, normal ordering does not apply
- return arg
- @property
- def has_q_creators(self):
- """
- Return 0 if the leftmost argument of the first argument is a not a
- q_creator, else 1 if it is above fermi or -1 if it is below fermi.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.physics.secondquant import NO, F, Fd
- >>> a = symbols('a', above_fermi=True)
- >>> i = symbols('i', below_fermi=True)
- >>> NO(Fd(a)*Fd(i)).has_q_creators
- 1
- >>> NO(F(i)*F(a)).has_q_creators
- -1
- >>> NO(Fd(i)*F(a)).has_q_creators #doctest: +SKIP
- 0
- """
- return self.args[0].args[0].is_q_creator
- @property
- def has_q_annihilators(self):
- """
- Return 0 if the rightmost argument of the first argument is a not a
- q_annihilator, else 1 if it is above fermi or -1 if it is below fermi.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.physics.secondquant import NO, F, Fd
- >>> a = symbols('a', above_fermi=True)
- >>> i = symbols('i', below_fermi=True)
- >>> NO(Fd(a)*Fd(i)).has_q_annihilators
- -1
- >>> NO(F(i)*F(a)).has_q_annihilators
- 1
- >>> NO(Fd(a)*F(i)).has_q_annihilators
- 0
- """
- return self.args[0].args[-1].is_q_annihilator
- def doit(self, **kw_args):
- """
- Either removes the brackets or enables complex computations
- in its arguments.
- Examples
- ========
- >>> from sympy.physics.secondquant import NO, Fd, F
- >>> from textwrap import fill
- >>> from sympy import symbols, Dummy
- >>> p,q = symbols('p,q', cls=Dummy)
- >>> print(fill(str(NO(Fd(p)*F(q)).doit())))
- KroneckerDelta(_a, _p)*KroneckerDelta(_a,
- _q)*CreateFermion(_a)*AnnihilateFermion(_a) + KroneckerDelta(_a,
- _p)*KroneckerDelta(_i, _q)*CreateFermion(_a)*AnnihilateFermion(_i) -
- KroneckerDelta(_a, _q)*KroneckerDelta(_i,
- _p)*AnnihilateFermion(_a)*CreateFermion(_i) - KroneckerDelta(_i,
- _p)*KroneckerDelta(_i, _q)*AnnihilateFermion(_i)*CreateFermion(_i)
- """
- if kw_args.get("remove_brackets", True):
- return self._remove_brackets()
- else:
- return self.__new__(type(self), self.args[0].doit(**kw_args))
- def _remove_brackets(self):
- """
- Returns the sorted string without normal order brackets.
- The returned string have the property that no nonzero
- contractions exist.
- """
- # check if any creator is also an annihilator
- subslist = []
- for i in self.iter_q_creators():
- if self[i].is_q_annihilator:
- assume = self[i].state.assumptions0
- # only operators with a dummy index can be split in two terms
- if isinstance(self[i].state, Dummy):
- # create indices with fermi restriction
- assume.pop("above_fermi", None)
- assume["below_fermi"] = True
- below = Dummy('i', **assume)
- assume.pop("below_fermi", None)
- assume["above_fermi"] = True
- above = Dummy('a', **assume)
- cls = type(self[i])
- split = (
- self[i].__new__(cls, below)
- * KroneckerDelta(below, self[i].state)
- + self[i].__new__(cls, above)
- * KroneckerDelta(above, self[i].state)
- )
- subslist.append((self[i], split))
- else:
- raise SubstitutionOfAmbigousOperatorFailed(self[i])
- if subslist:
- result = NO(self.subs(subslist))
- if isinstance(result, Add):
- return Add(*[term.doit() for term in result.args])
- else:
- return self.args[0]
- def _expand_operators(self):
- """
- Returns a sum of NO objects that contain no ambiguous q-operators.
- Explanation
- ===========
- If an index q has range both above and below fermi, the operator F(q)
- is ambiguous in the sense that it can be both a q-creator and a q-annihilator.
- If q is dummy, it is assumed to be a summation variable and this method
- rewrites it into a sum of NO terms with unambiguous operators:
- {Fd(p)*F(q)} = {Fd(a)*F(b)} + {Fd(a)*F(i)} + {Fd(j)*F(b)} -{F(i)*Fd(j)}
- where a,b are above and i,j are below fermi level.
- """
- return NO(self._remove_brackets)
- def __getitem__(self, i):
- if isinstance(i, slice):
- indices = i.indices(len(self))
- return [self.args[0].args[i] for i in range(*indices)]
- else:
- return self.args[0].args[i]
- def __len__(self):
- return len(self.args[0].args)
- def iter_q_annihilators(self):
- """
- Iterates over the annihilation operators.
- Examples
- ========
- >>> from sympy import symbols
- >>> i, j = symbols('i j', below_fermi=True)
- >>> a, b = symbols('a b', above_fermi=True)
- >>> from sympy.physics.secondquant import NO, F, Fd
- >>> no = NO(Fd(a)*F(i)*F(b)*Fd(j))
- >>> no.iter_q_creators()
- <generator object... at 0x...>
- >>> list(no.iter_q_creators())
- [0, 1]
- >>> list(no.iter_q_annihilators())
- [3, 2]
- """
- ops = self.args[0].args
- iter = range(len(ops) - 1, -1, -1)
- for i in iter:
- if ops[i].is_q_annihilator:
- yield i
- else:
- break
- def iter_q_creators(self):
- """
- Iterates over the creation operators.
- Examples
- ========
- >>> from sympy import symbols
- >>> i, j = symbols('i j', below_fermi=True)
- >>> a, b = symbols('a b', above_fermi=True)
- >>> from sympy.physics.secondquant import NO, F, Fd
- >>> no = NO(Fd(a)*F(i)*F(b)*Fd(j))
- >>> no.iter_q_creators()
- <generator object... at 0x...>
- >>> list(no.iter_q_creators())
- [0, 1]
- >>> list(no.iter_q_annihilators())
- [3, 2]
- """
- ops = self.args[0].args
- iter = range(0, len(ops))
- for i in iter:
- if ops[i].is_q_creator:
- yield i
- else:
- break
- def get_subNO(self, i):
- """
- Returns a NO() without FermionicOperator at index i.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.physics.secondquant import F, NO
- >>> p, q, r = symbols('p,q,r')
- >>> NO(F(p)*F(q)*F(r)).get_subNO(1)
- NO(AnnihilateFermion(p)*AnnihilateFermion(r))
- """
- arg0 = self.args[0] # it's a Mul by definition of how it's created
- mul = arg0._new_rawargs(*(arg0.args[:i] + arg0.args[i + 1:]))
- return NO(mul)
- def _latex(self, printer):
- return "\\left\\{%s\\right\\}" % printer._print(self.args[0])
- def __repr__(self):
- return "NO(%s)" % self.args[0]
- def __str__(self):
- return ":%s:" % self.args[0]
- def contraction(a, b):
- """
- Calculates contraction of Fermionic operators a and b.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.physics.secondquant import F, Fd, contraction
- >>> p, q = symbols('p,q')
- >>> a, b = symbols('a,b', above_fermi=True)
- >>> i, j = symbols('i,j', below_fermi=True)
- A contraction is non-zero only if a quasi-creator is to the right of a
- quasi-annihilator:
- >>> contraction(F(a),Fd(b))
- KroneckerDelta(a, b)
- >>> contraction(Fd(i),F(j))
- KroneckerDelta(i, j)
- For general indices a non-zero result restricts the indices to below/above
- the fermi surface:
- >>> contraction(Fd(p),F(q))
- KroneckerDelta(_i, q)*KroneckerDelta(p, q)
- >>> contraction(F(p),Fd(q))
- KroneckerDelta(_a, q)*KroneckerDelta(p, q)
- Two creators or two annihilators always vanishes:
- >>> contraction(F(p),F(q))
- 0
- >>> contraction(Fd(p),Fd(q))
- 0
- """
- if isinstance(b, FermionicOperator) and isinstance(a, FermionicOperator):
- if isinstance(a, AnnihilateFermion) and isinstance(b, CreateFermion):
- if b.state.assumptions0.get("below_fermi"):
- return S.Zero
- if a.state.assumptions0.get("below_fermi"):
- return S.Zero
- if b.state.assumptions0.get("above_fermi"):
- return KroneckerDelta(a.state, b.state)
- if a.state.assumptions0.get("above_fermi"):
- return KroneckerDelta(a.state, b.state)
- return (KroneckerDelta(a.state, b.state)*
- KroneckerDelta(b.state, Dummy('a', above_fermi=True)))
- if isinstance(b, AnnihilateFermion) and isinstance(a, CreateFermion):
- if b.state.assumptions0.get("above_fermi"):
- return S.Zero
- if a.state.assumptions0.get("above_fermi"):
- return S.Zero
- if b.state.assumptions0.get("below_fermi"):
- return KroneckerDelta(a.state, b.state)
- if a.state.assumptions0.get("below_fermi"):
- return KroneckerDelta(a.state, b.state)
- return (KroneckerDelta(a.state, b.state)*
- KroneckerDelta(b.state, Dummy('i', below_fermi=True)))
- # vanish if 2xAnnihilator or 2xCreator
- return S.Zero
- else:
- #not fermion operators
- t = ( isinstance(i, FermionicOperator) for i in (a, b) )
- raise ContractionAppliesOnlyToFermions(*t)
- def _sqkey(sq_operator):
- """Generates key for canonical sorting of SQ operators."""
- return sq_operator._sortkey()
- def _sort_anticommuting_fermions(string1, key=_sqkey):
- """Sort fermionic operators to canonical order, assuming all pairs anticommute.
- Explanation
- ===========
- Uses a bidirectional bubble sort. Items in string1 are not referenced
- so in principle they may be any comparable objects. The sorting depends on the
- operators '>' and '=='.
- If the Pauli principle is violated, an exception is raised.
- Returns
- =======
- tuple (sorted_str, sign)
- sorted_str: list containing the sorted operators
- sign: int telling how many times the sign should be changed
- (if sign==0 the string was already sorted)
- """
- verified = False
- sign = 0
- rng = list(range(len(string1) - 1))
- rev = list(range(len(string1) - 3, -1, -1))
- keys = list(map(key, string1))
- key_val = dict(list(zip(keys, string1)))
- while not verified:
- verified = True
- for i in rng:
- left = keys[i]
- right = keys[i + 1]
- if left == right:
- raise ViolationOfPauliPrinciple([left, right])
- if left > right:
- verified = False
- keys[i:i + 2] = [right, left]
- sign = sign + 1
- if verified:
- break
- for i in rev:
- left = keys[i]
- right = keys[i + 1]
- if left == right:
- raise ViolationOfPauliPrinciple([left, right])
- if left > right:
- verified = False
- keys[i:i + 2] = [right, left]
- sign = sign + 1
- string1 = [ key_val[k] for k in keys ]
- return (string1, sign)
- def evaluate_deltas(e):
- """
- We evaluate KroneckerDelta symbols in the expression assuming Einstein summation.
- Explanation
- ===========
- If one index is repeated it is summed over and in effect substituted with
- the other one. If both indices are repeated we substitute according to what
- is the preferred index. this is determined by
- KroneckerDelta.preferred_index and KroneckerDelta.killable_index.
- In case there are no possible substitutions or if a substitution would
- imply a loss of information, nothing is done.
- In case an index appears in more than one KroneckerDelta, the resulting
- substitution depends on the order of the factors. Since the ordering is platform
- dependent, the literal expression resulting from this function may be hard to
- predict.
- Examples
- ========
- We assume the following:
- >>> from sympy import symbols, Function, Dummy, KroneckerDelta
- >>> from sympy.physics.secondquant import evaluate_deltas
- >>> i,j = symbols('i j', below_fermi=True, cls=Dummy)
- >>> a,b = symbols('a b', above_fermi=True, cls=Dummy)
- >>> p,q = symbols('p q', cls=Dummy)
- >>> f = Function('f')
- >>> t = Function('t')
- The order of preference for these indices according to KroneckerDelta is
- (a, b, i, j, p, q).
- Trivial cases:
- >>> evaluate_deltas(KroneckerDelta(i,j)*f(i)) # d_ij f(i) -> f(j)
- f(_j)
- >>> evaluate_deltas(KroneckerDelta(i,j)*f(j)) # d_ij f(j) -> f(i)
- f(_i)
- >>> evaluate_deltas(KroneckerDelta(i,p)*f(p)) # d_ip f(p) -> f(i)
- f(_i)
- >>> evaluate_deltas(KroneckerDelta(q,p)*f(p)) # d_qp f(p) -> f(q)
- f(_q)
- >>> evaluate_deltas(KroneckerDelta(q,p)*f(q)) # d_qp f(q) -> f(p)
- f(_p)
- More interesting cases:
- >>> evaluate_deltas(KroneckerDelta(i,p)*t(a,i)*f(p,q))
- f(_i, _q)*t(_a, _i)
- >>> evaluate_deltas(KroneckerDelta(a,p)*t(a,i)*f(p,q))
- f(_a, _q)*t(_a, _i)
- >>> evaluate_deltas(KroneckerDelta(p,q)*f(p,q))
- f(_p, _p)
- Finally, here are some cases where nothing is done, because that would
- imply a loss of information:
- >>> evaluate_deltas(KroneckerDelta(i,p)*f(q))
- f(_q)*KroneckerDelta(_i, _p)
- >>> evaluate_deltas(KroneckerDelta(i,p)*f(i))
- f(_i)*KroneckerDelta(_i, _p)
- """
- # We treat Deltas only in mul objects
- # for general function objects we don't evaluate KroneckerDeltas in arguments,
- # but here we hard code exceptions to this rule
- accepted_functions = (
- Add,
- )
- if isinstance(e, accepted_functions):
- return e.func(*[evaluate_deltas(arg) for arg in e.args])
- elif isinstance(e, Mul):
- # find all occurrences of delta function and count each index present in
- # expression.
- deltas = []
- indices = {}
- for i in e.args:
- for s in i.free_symbols:
- if s in indices:
- indices[s] += 1
- else:
- indices[s] = 0 # geek counting simplifies logic below
- if isinstance(i, KroneckerDelta):
- deltas.append(i)
- for d in deltas:
- # If we do something, and there are more deltas, we should recurse
- # to treat the resulting expression properly
- if d.killable_index.is_Symbol and indices[d.killable_index]:
- e = e.subs(d.killable_index, d.preferred_index)
- if len(deltas) > 1:
- return evaluate_deltas(e)
- elif (d.preferred_index.is_Symbol and indices[d.preferred_index]
- and d.indices_contain_equal_information):
- e = e.subs(d.preferred_index, d.killable_index)
- if len(deltas) > 1:
- return evaluate_deltas(e)
- else:
- pass
- return e
- # nothing to do, maybe we hit a Symbol or a number
- else:
- return e
- def substitute_dummies(expr, new_indices=False, pretty_indices={}):
- """
- Collect terms by substitution of dummy variables.
- Explanation
- ===========
- This routine allows simplification of Add expressions containing terms
- which differ only due to dummy variables.
- The idea is to substitute all dummy variables consistently depending on
- the structure of the term. For each term, we obtain a sequence of all
- dummy variables, where the order is determined by the index range, what
- factors the index belongs to and its position in each factor. See
- _get_ordered_dummies() for more information about the sorting of dummies.
- The index sequence is then substituted consistently in each term.
- Examples
- ========
- >>> from sympy import symbols, Function, Dummy
- >>> from sympy.physics.secondquant import substitute_dummies
- >>> a,b,c,d = symbols('a b c d', above_fermi=True, cls=Dummy)
- >>> i,j = symbols('i j', below_fermi=True, cls=Dummy)
- >>> f = Function('f')
- >>> expr = f(a,b) + f(c,d); expr
- f(_a, _b) + f(_c, _d)
- Since a, b, c and d are equivalent summation indices, the expression can be
- simplified to a single term (for which the dummy indices are still summed over)
- >>> substitute_dummies(expr)
- 2*f(_a, _b)
- Controlling output:
- By default the dummy symbols that are already present in the expression
- will be reused in a different permutation. However, if new_indices=True,
- new dummies will be generated and inserted. The keyword 'pretty_indices'
- can be used to control this generation of new symbols.
- By default the new dummies will be generated on the form i_1, i_2, a_1,
- etc. If you supply a dictionary with key:value pairs in the form:
- { index_group: string_of_letters }
- The letters will be used as labels for the new dummy symbols. The
- index_groups must be one of 'above', 'below' or 'general'.
- >>> expr = f(a,b,i,j)
- >>> my_dummies = { 'above':'st', 'below':'uv' }
- >>> substitute_dummies(expr, new_indices=True, pretty_indices=my_dummies)
- f(_s, _t, _u, _v)
- If we run out of letters, or if there is no keyword for some index_group
- the default dummy generator will be used as a fallback:
- >>> p,q = symbols('p q', cls=Dummy) # general indices
- >>> expr = f(p,q)
- >>> substitute_dummies(expr, new_indices=True, pretty_indices=my_dummies)
- f(_p_0, _p_1)
- """
- # setup the replacing dummies
- if new_indices:
- letters_above = pretty_indices.get('above', "")
- letters_below = pretty_indices.get('below', "")
- letters_general = pretty_indices.get('general', "")
- len_above = len(letters_above)
- len_below = len(letters_below)
- len_general = len(letters_general)
- def _i(number):
- try:
- return letters_below[number]
- except IndexError:
- return 'i_' + str(number - len_below)
- def _a(number):
- try:
- return letters_above[number]
- except IndexError:
- return 'a_' + str(number - len_above)
- def _p(number):
- try:
- return letters_general[number]
- except IndexError:
- return 'p_' + str(number - len_general)
- aboves = []
- belows = []
- generals = []
- dummies = expr.atoms(Dummy)
- if not new_indices:
- dummies = sorted(dummies, key=default_sort_key)
- # generate lists with the dummies we will insert
- a = i = p = 0
- for d in dummies:
- assum = d.assumptions0
- if assum.get("above_fermi"):
- if new_indices:
- sym = _a(a)
- a += 1
- l1 = aboves
- elif assum.get("below_fermi"):
- if new_indices:
- sym = _i(i)
- i += 1
- l1 = belows
- else:
- if new_indices:
- sym = _p(p)
- p += 1
- l1 = generals
- if new_indices:
- l1.append(Dummy(sym, **assum))
- else:
- l1.append(d)
- expr = expr.expand()
- terms = Add.make_args(expr)
- new_terms = []
- for term in terms:
- i = iter(belows)
- a = iter(aboves)
- p = iter(generals)
- ordered = _get_ordered_dummies(term)
- subsdict = {}
- for d in ordered:
- if d.assumptions0.get('below_fermi'):
- subsdict[d] = next(i)
- elif d.assumptions0.get('above_fermi'):
- subsdict[d] = next(a)
- else:
- subsdict[d] = next(p)
- subslist = []
- final_subs = []
- for k, v in subsdict.items():
- if k == v:
- continue
- if v in subsdict:
- # We check if the sequence of substitutions end quickly. In
- # that case, we can avoid temporary symbols if we ensure the
- # correct substitution order.
- if subsdict[v] in subsdict:
- # (x, y) -> (y, x), we need a temporary variable
- x = Dummy('x')
- subslist.append((k, x))
- final_subs.append((x, v))
- else:
- # (x, y) -> (y, a), x->y must be done last
- # but before temporary variables are resolved
- final_subs.insert(0, (k, v))
- else:
- subslist.append((k, v))
- subslist.extend(final_subs)
- new_terms.append(term.subs(subslist))
- return Add(*new_terms)
- class KeyPrinter(StrPrinter):
- """Printer for which only equal objects are equal in print"""
- def _print_Dummy(self, expr):
- return "(%s_%i)" % (expr.name, expr.dummy_index)
- def __kprint(expr):
- p = KeyPrinter()
- return p.doprint(expr)
- def _get_ordered_dummies(mul, verbose=False):
- """Returns all dummies in the mul sorted in canonical order.
- Explanation
- ===========
- The purpose of the canonical ordering is that dummies can be substituted
- consistently across terms with the result that equivalent terms can be
- simplified.
- It is not possible to determine if two terms are equivalent based solely on
- the dummy order. However, a consistent substitution guided by the ordered
- dummies should lead to trivially (non-)equivalent terms, thereby revealing
- the equivalence. This also means that if two terms have identical sequences of
- dummies, the (non-)equivalence should already be apparent.
- Strategy
- --------
- The canoncial order is given by an arbitrary sorting rule. A sort key
- is determined for each dummy as a tuple that depends on all factors where
- the index is present. The dummies are thereby sorted according to the
- contraction structure of the term, instead of sorting based solely on the
- dummy symbol itself.
- After all dummies in the term has been assigned a key, we check for identical
- keys, i.e. unorderable dummies. If any are found, we call a specialized
- method, _determine_ambiguous(), that will determine a unique order based
- on recursive calls to _get_ordered_dummies().
- Key description
- ---------------
- A high level description of the sort key:
- 1. Range of the dummy index
- 2. Relation to external (non-dummy) indices
- 3. Position of the index in the first factor
- 4. Position of the index in the second factor
- The sort key is a tuple with the following components:
- 1. A single character indicating the range of the dummy (above, below
- or general.)
- 2. A list of strings with fully masked string representations of all
- factors where the dummy is present. By masked, we mean that dummies
- are represented by a symbol to indicate either below fermi, above or
- general. No other information is displayed about the dummies at
- this point. The list is sorted stringwise.
- 3. An integer number indicating the position of the index, in the first
- factor as sorted in 2.
- 4. An integer number indicating the position of the index, in the second
- factor as sorted in 2.
- If a factor is either of type AntiSymmetricTensor or SqOperator, the index
- position in items 3 and 4 is indicated as 'upper' or 'lower' only.
- (Creation operators are considered upper and annihilation operators lower.)
- If the masked factors are identical, the two factors cannot be ordered
- unambiguously in item 2. In this case, items 3, 4 are left out. If several
- indices are contracted between the unorderable factors, it will be handled by
- _determine_ambiguous()
- """
- # setup dicts to avoid repeated calculations in key()
- args = Mul.make_args(mul)
- fac_dum = { fac: fac.atoms(Dummy) for fac in args }
- fac_repr = { fac: __kprint(fac) for fac in args }
- all_dums = set().union(*fac_dum.values())
- mask = {}
- for d in all_dums:
- if d.assumptions0.get('below_fermi'):
- mask[d] = '0'
- elif d.assumptions0.get('above_fermi'):
- mask[d] = '1'
- else:
- mask[d] = '2'
- dum_repr = {d: __kprint(d) for d in all_dums}
- def _key(d):
- dumstruct = [ fac for fac in fac_dum if d in fac_dum[fac] ]
- other_dums = set().union(*[fac_dum[fac] for fac in dumstruct])
- fac = dumstruct[-1]
- if other_dums is fac_dum[fac]:
- other_dums = fac_dum[fac].copy()
- other_dums.remove(d)
- masked_facs = [ fac_repr[fac] for fac in dumstruct ]
- for d2 in other_dums:
- masked_facs = [ fac.replace(dum_repr[d2], mask[d2])
- for fac in masked_facs ]
- all_masked = [ fac.replace(dum_repr[d], mask[d])
- for fac in masked_facs ]
- masked_facs = dict(list(zip(dumstruct, masked_facs)))
- # dummies for which the ordering cannot be determined
- if has_dups(all_masked):
- all_masked.sort()
- return mask[d], tuple(all_masked) # positions are ambiguous
- # sort factors according to fully masked strings
- keydict = dict(list(zip(dumstruct, all_masked)))
- dumstruct.sort(key=lambda x: keydict[x])
- all_masked.sort()
- pos_val = []
- for fac in dumstruct:
- if isinstance(fac, AntiSymmetricTensor):
- if d in fac.upper:
- pos_val.append('u')
- if d in fac.lower:
- pos_val.append('l')
- elif isinstance(fac, Creator):
- pos_val.append('u')
- elif isinstance(fac, Annihilator):
- pos_val.append('l')
- elif isinstance(fac, NO):
- ops = [ op for op in fac if op.has(d) ]
- for op in ops:
- if isinstance(op, Creator):
- pos_val.append('u')
- else:
- pos_val.append('l')
- else:
- # fallback to position in string representation
- facpos = -1
- while 1:
- facpos = masked_facs[fac].find(dum_repr[d], facpos + 1)
- if facpos == -1:
- break
- pos_val.append(facpos)
- return (mask[d], tuple(all_masked), pos_val[0], pos_val[-1])
- dumkey = dict(list(zip(all_dums, list(map(_key, all_dums)))))
- result = sorted(all_dums, key=lambda x: dumkey[x])
- if has_dups(iter(dumkey.values())):
- # We have ambiguities
- unordered = defaultdict(set)
- for d, k in dumkey.items():
- unordered[k].add(d)
- for k in [ k for k in unordered if len(unordered[k]) < 2 ]:
- del unordered[k]
- unordered = [ unordered[k] for k in sorted(unordered) ]
- result = _determine_ambiguous(mul, result, unordered)
- return result
- def _determine_ambiguous(term, ordered, ambiguous_groups):
- # We encountered a term for which the dummy substitution is ambiguous.
- # This happens for terms with 2 or more contractions between factors that
- # cannot be uniquely ordered independent of summation indices. For
- # example:
- #
- # Sum(p, q) v^{p, .}_{q, .}v^{q, .}_{p, .}
- #
- # Assuming that the indices represented by . are dummies with the
- # same range, the factors cannot be ordered, and there is no
- # way to determine a consistent ordering of p and q.
- #
- # The strategy employed here, is to relabel all unambiguous dummies with
- # non-dummy symbols and call _get_ordered_dummies again. This procedure is
- # applied to the entire term so there is a possibility that
- # _determine_ambiguous() is called again from a deeper recursion level.
- # break recursion if there are no ordered dummies
- all_ambiguous = set()
- for dummies in ambiguous_groups:
- all_ambiguous |= dummies
- all_ordered = set(ordered) - all_ambiguous
- if not all_ordered:
- # FIXME: If we arrive here, there are no ordered dummies. A method to
- # handle this needs to be implemented. In order to return something
- # useful nevertheless, we choose arbitrarily the first dummy and
- # determine the rest from this one. This method is dependent on the
- # actual dummy labels which violates an assumption for the
- # canonicalization procedure. A better implementation is needed.
- group = [ d for d in ordered if d in ambiguous_groups[0] ]
- d = group[0]
- all_ordered.add(d)
- ambiguous_groups[0].remove(d)
- stored_counter = _symbol_factory._counter
- subslist = []
- for d in [ d for d in ordered if d in all_ordered ]:
- nondum = _symbol_factory._next()
- subslist.append((d, nondum))
- newterm = term.subs(subslist)
- neworder = _get_ordered_dummies(newterm)
- _symbol_factory._set_counter(stored_counter)
- # update ordered list with new information
- for group in ambiguous_groups:
- ordered_group = [ d for d in neworder if d in group ]
- ordered_group.reverse()
- result = []
- for d in ordered:
- if d in group:
- result.append(ordered_group.pop())
- else:
- result.append(d)
- ordered = result
- return ordered
- class _SymbolFactory:
- def __init__(self, label):
- self._counterVar = 0
- self._label = label
- def _set_counter(self, value):
- """
- Sets counter to value.
- """
- self._counterVar = value
- @property
- def _counter(self):
- """
- What counter is currently at.
- """
- return self._counterVar
- def _next(self):
- """
- Generates the next symbols and increments counter by 1.
- """
- s = Symbol("%s%i" % (self._label, self._counterVar))
- self._counterVar += 1
- return s
- _symbol_factory = _SymbolFactory('_]"]_') # most certainly a unique label
- @cacheit
- def _get_contractions(string1, keep_only_fully_contracted=False):
- """
- Returns Add-object with contracted terms.
- Uses recursion to find all contractions. -- Internal helper function --
- Will find nonzero contractions in string1 between indices given in
- leftrange and rightrange.
- """
- # Should we store current level of contraction?
- if keep_only_fully_contracted and string1:
- result = []
- else:
- result = [NO(Mul(*string1))]
- for i in range(len(string1) - 1):
- for j in range(i + 1, len(string1)):
- c = contraction(string1[i], string1[j])
- if c:
- sign = (j - i + 1) % 2
- if sign:
- coeff = S.NegativeOne*c
- else:
- coeff = c
- #
- # Call next level of recursion
- # ============================
- #
- # We now need to find more contractions among operators
- #
- # oplist = string1[:i]+ string1[i+1:j] + string1[j+1:]
- #
- # To prevent overcounting, we don't allow contractions
- # we have already encountered. i.e. contractions between
- # string1[:i] <---> string1[i+1:j]
- # and string1[:i] <---> string1[j+1:].
- #
- # This leaves the case:
- oplist = string1[i + 1:j] + string1[j + 1:]
- if oplist:
- result.append(coeff*NO(
- Mul(*string1[:i])*_get_contractions( oplist,
- keep_only_fully_contracted=keep_only_fully_contracted)))
- else:
- result.append(coeff*NO( Mul(*string1[:i])))
- if keep_only_fully_contracted:
- break # next iteration over i leaves leftmost operator string1[0] uncontracted
- return Add(*result)
- def wicks(e, **kw_args):
- """
- Returns the normal ordered equivalent of an expression using Wicks Theorem.
- Examples
- ========
- >>> from sympy import symbols, Dummy
- >>> from sympy.physics.secondquant import wicks, F, Fd
- >>> p, q, r = symbols('p,q,r')
- >>> wicks(Fd(p)*F(q))
- KroneckerDelta(_i, q)*KroneckerDelta(p, q) + NO(CreateFermion(p)*AnnihilateFermion(q))
- By default, the expression is expanded:
- >>> wicks(F(p)*(F(q)+F(r)))
- NO(AnnihilateFermion(p)*AnnihilateFermion(q)) + NO(AnnihilateFermion(p)*AnnihilateFermion(r))
- With the keyword 'keep_only_fully_contracted=True', only fully contracted
- terms are returned.
- By request, the result can be simplified in the following order:
- -- KroneckerDelta functions are evaluated
- -- Dummy variables are substituted consistently across terms
- >>> p, q, r = symbols('p q r', cls=Dummy)
- >>> wicks(Fd(p)*(F(q)+F(r)), keep_only_fully_contracted=True)
- KroneckerDelta(_i, _q)*KroneckerDelta(_p, _q) + KroneckerDelta(_i, _r)*KroneckerDelta(_p, _r)
- """
- if not e:
- return S.Zero
- opts = {
- 'simplify_kronecker_deltas': False,
- 'expand': True,
- 'simplify_dummies': False,
- 'keep_only_fully_contracted': False
- }
- opts.update(kw_args)
- # check if we are already normally ordered
- if isinstance(e, NO):
- if opts['keep_only_fully_contracted']:
- return S.Zero
- else:
- return e
- elif isinstance(e, FermionicOperator):
- if opts['keep_only_fully_contracted']:
- return S.Zero
- else:
- return e
- # break up any NO-objects, and evaluate commutators
- e = e.doit(wicks=True)
- # make sure we have only one term to consider
- e = e.expand()
- if isinstance(e, Add):
- if opts['simplify_dummies']:
- return substitute_dummies(Add(*[ wicks(term, **kw_args) for term in e.args]))
- else:
- return Add(*[ wicks(term, **kw_args) for term in e.args])
- # For Mul-objects we can actually do something
- if isinstance(e, Mul):
- # we don't want to mess around with commuting part of Mul
- # so we factorize it out before starting recursion
- c_part = []
- string1 = []
- for factor in e.args:
- if factor.is_commutative:
- c_part.append(factor)
- else:
- string1.append(factor)
- n = len(string1)
- # catch trivial cases
- if n == 0:
- result = e
- elif n == 1:
- if opts['keep_only_fully_contracted']:
- return S.Zero
- else:
- result = e
- else: # non-trivial
- if isinstance(string1[0], BosonicOperator):
- raise NotImplementedError
- string1 = tuple(string1)
- # recursion over higher order contractions
- result = _get_contractions(string1,
- keep_only_fully_contracted=opts['keep_only_fully_contracted'] )
- result = Mul(*c_part)*result
- if opts['expand']:
- result = result.expand()
- if opts['simplify_kronecker_deltas']:
- result = evaluate_deltas(result)
- return result
- # there was nothing to do
- return e
- class PermutationOperator(Expr):
- """
- Represents the index permutation operator P(ij).
- P(ij)*f(i)*g(j) = f(i)*g(j) - f(j)*g(i)
- """
- is_commutative = True
- def __new__(cls, i, j):
- i, j = sorted(map(sympify, (i, j)), key=default_sort_key)
- obj = Basic.__new__(cls, i, j)
- return obj
- def get_permuted(self, expr):
- """
- Returns -expr with permuted indices.
- Explanation
- ===========
- >>> from sympy import symbols, Function
- >>> from sympy.physics.secondquant import PermutationOperator
- >>> p,q = symbols('p,q')
- >>> f = Function('f')
- >>> PermutationOperator(p,q).get_permuted(f(p,q))
- -f(q, p)
- """
- i = self.args[0]
- j = self.args[1]
- if expr.has(i) and expr.has(j):
- tmp = Dummy()
- expr = expr.subs(i, tmp)
- expr = expr.subs(j, i)
- expr = expr.subs(tmp, j)
- return S.NegativeOne*expr
- else:
- return expr
- def _latex(self, printer):
- return "P(%s%s)" % self.args
- def simplify_index_permutations(expr, permutation_operators):
- """
- Performs simplification by introducing PermutationOperators where appropriate.
- Explanation
- ===========
- Schematically:
- [abij] - [abji] - [baij] + [baji] -> P(ab)*P(ij)*[abij]
- permutation_operators is a list of PermutationOperators to consider.
- If permutation_operators=[P(ab),P(ij)] we will try to introduce the
- permutation operators P(ij) and P(ab) in the expression. If there are other
- possible simplifications, we ignore them.
- >>> from sympy import symbols, Function
- >>> from sympy.physics.secondquant import simplify_index_permutations
- >>> from sympy.physics.secondquant import PermutationOperator
- >>> p,q,r,s = symbols('p,q,r,s')
- >>> f = Function('f')
- >>> g = Function('g')
- >>> expr = f(p)*g(q) - f(q)*g(p); expr
- f(p)*g(q) - f(q)*g(p)
- >>> simplify_index_permutations(expr,[PermutationOperator(p,q)])
- f(p)*g(q)*PermutationOperator(p, q)
- >>> PermutList = [PermutationOperator(p,q),PermutationOperator(r,s)]
- >>> expr = f(p,r)*g(q,s) - f(q,r)*g(p,s) + f(q,s)*g(p,r) - f(p,s)*g(q,r)
- >>> simplify_index_permutations(expr,PermutList)
- f(p, r)*g(q, s)*PermutationOperator(p, q)*PermutationOperator(r, s)
- """
- def _get_indices(expr, ind):
- """
- Collects indices recursively in predictable order.
- """
- result = []
- for arg in expr.args:
- if arg in ind:
- result.append(arg)
- else:
- if arg.args:
- result.extend(_get_indices(arg, ind))
- return result
- def _choose_one_to_keep(a, b, ind):
- # we keep the one where indices in ind are in order ind[0] < ind[1]
- return min(a, b, key=lambda x: default_sort_key(_get_indices(x, ind)))
- expr = expr.expand()
- if isinstance(expr, Add):
- terms = set(expr.args)
- for P in permutation_operators:
- new_terms = set()
- on_hold = set()
- while terms:
- term = terms.pop()
- permuted = P.get_permuted(term)
- if permuted in terms | on_hold:
- try:
- terms.remove(permuted)
- except KeyError:
- on_hold.remove(permuted)
- keep = _choose_one_to_keep(term, permuted, P.args)
- new_terms.add(P*keep)
- else:
- # Some terms must get a second chance because the permuted
- # term may already have canonical dummy ordering. Then
- # substitute_dummies() does nothing. However, the other
- # term, if it exists, will be able to match with us.
- permuted1 = permuted
- permuted = substitute_dummies(permuted)
- if permuted1 == permuted:
- on_hold.add(term)
- elif permuted in terms | on_hold:
- try:
- terms.remove(permuted)
- except KeyError:
- on_hold.remove(permuted)
- keep = _choose_one_to_keep(term, permuted, P.args)
- new_terms.add(P*keep)
- else:
- new_terms.add(term)
- terms = new_terms | on_hold
- return Add(*terms)
- return expr
|