# -*- coding: utf-8 -*-# noinspection PyUnresolvedReferencesr""".. _PDE-initialization:==============Initialization==============To initialize/construct a PDE instance, call the method ``ph.pde``, .. autofunction:: src.pde.pdeFor instance, if we want to solve the 2-dimensional linear port Hamitolian problem, i.e.,.. math:: \left\lbrace \begin{aligned} & \partial_t \tilde{\alpha} = \mathrm{d}\tilde{\beta} ,\\ & \partial_t \tilde{\beta} = - \mathrm{d}^\ast\tilde{\alpha}, \end{aligned} \right.for the outer 2-form :math:`\tilde{\alpha}` and the outer 1-form :math:`\tilde{\beta}`in the domain :math:`\mathcal{M}`,we make the following expression,>>> expression = [... 'da_dt = + d_b',... 'db_dt = - cd_a'... ]where we have string terms like ``'da_dt'``, ``'d_b'`` and so on connected by ``'+'``, ``'-'`` and ``'='``.Since these terms are just strings, the code needs to knowwhat forms they are representing. Thus, we need an interpreter,>>> interpreter = {... 'da_dt': da_dt,... 'd_b' : d_b,... 'db_dt': db_dt,... 'cd_a' : cd_a... }which links the strings, i.e. the keys of ``interpreter``, to the forms, ``da_dt``, ``d_b`` and so on.Note that because here strings that are same to the variable names are used,this interpreter is a subset of the local variable dictionarywhich can be returned by the built-in function ``locals``.Therefore, alternatively we can use>>> interpreter = locals()Sending ``expression`` and ``interpreter`` to ``ph.pde`` initializes a PDE instance,>>> pde = ph.pde(expression, interpreter)which is an instance of :class:`PartialDifferentialEquations`, .. autoclass:: src.pde.PartialDifferentialEquations :members: pr, test_with, unknowns, bc, deriveWe need to set the unknowns of the pde, which is done through setting the property ``unknowns``,i.e. :attr:`PartialDifferentialEquations.unknowns`,>>> pde.unknowns = [a, b]To visualize the PDE instnace just constructed, call the *print representation* method, see:meth:`PartialDifferentialEquations.pr`,>>> pde.pr()<Figure size ...It gives a figure of the PDE in differential forms. We can visualize the vector calculus versionif we pass the requirement to ``pr`` through keyword argument ``vc=True`` like>>> pde.pr(vc=True)<Figure size ...This is very handy, for example, when your reference PDE is given in vector calculus, and you want to check ifyou have input the correct differential form version of it, especially in 2-dimensions where the transformationbetween vector calculus and differential form suffers from extra minus signs here and there... _PDE-bc:===================Boundary conditions===================The boundary condition setting of a PDE can be accessed through property :attr:`PartialDifferentialEquations.bc`.To define boundary conditions for a PDE, we first need to identify boundary sections. We can define boundarysections by calling the ``partition`` method, for example,>>> pde.bc.partition(r"\Gamma_{\alpha}", r"\Gamma_{\beta}")This command defines two boundary sections whose symbolic representations are ``'\Gamma_{\alpha}'`` and``'\Gamma_{\beta}'``.Here they are in fact two 1-dimensional sub-manifolds (recall that in this case the computational domain isa 2-dimensional manifold).They are a partition of the boundary, i.e.,.. math:: \Gamma_{\alpha} \cup \Gamma_{\beta} = \partial \mathcal{M}\quad\text{and}\quad \Gamma_{\alpha} \cap \Gamma_{\beta} = \emptyset,where :math:`\partial \mathcal{M}` is the complete boundary of the computational domain (``manifold``).Change the amount (:math:`\geq 1`) of arguments for the ``partition`` method to define a partition ofdifferent amount of boundary sections. Since the ``manifold`` itself is abstract, the boundary sectionsare abstract as well; thus we can specify, for example,.. math:: \Gamma_{\alpha} = \partial \mathcal{M} \quad \text{and} \quad \Gamma_{\beta} = \emptyset,when we invoke a particular implementation for the simulation in the future.After we have defined boundary sections, we can specify boundary conditions on them by calling ``define_bc`` methodof :attr:`PartialDifferentialEquations.bc` property. For example,>>> pde.bc.define_bc(... {... r"\Gamma_{\alpha}": ph.trace(ph.Hodge(a)), # natural boundary condition... r"\Gamma_{\beta}": ph.trace(b), # essential boundary condition... }... )specifies- a natural boundary condition for the outer-oriented 2-form ``a`` on ``'\Gamma_{\alpha}'``,- an essential boundary condition for the outer-oriented 1-form ``b`` on ``'\Gamma_{\beta}'``... caution:: So far, only two types, **essential** and **natural**, of boundary conditions are implemented.Now, the ``pr`` method will also list the imposed boundary conditions,>>> pde.pr()<Figure size ..... _PDE-derivations:===========Derivations===========We can make changes to (for example, delete, replace or split a term in) the initialized PDE through property``pde.derive`` which gives an instance of :class:`PDEDerive`, a wrapper of all possible derivationsto a PDE instance. .. autoclass:: PDEDerive"""fromtools.frozenimportFrozenfromsrc.configimport_global_lin_repr_setting,_non_root_lin_sepfromsrc.form.mainimportForm,_global_root_forms_lin_dictfromsrc.configimport_global_operator_lin_repr_settingfromsrc.configimport_pde_test_form_lin_reprimportmatplotlib.pyplotaspltimportmatplotlibplt.rcParams.update({"text.usetex":True,"font.family":"DejaVu Sans","text.latex.preamble":r"\usepackage{amsmath, amssymb}",})matplotlib.use('TkAgg')fromsrc.wf.term.mainimportinnerfromsrc.wf.mainimportWeakFormulationfromsrc.bcimportBoundaryCondition
[docs]defpde(expression=None,interpreter=None,terms_and_signs_dict=None):"""A wrapper of the ``__init__`` method of :class:`PartialDifferentialEquations`. To make a PDE instance, you can either input - ``expression`` and ``interpreter`` or input - ``terms_and_signs_dict`` If you input ``expression`` and ``interpreter`` (recommended), the class will call a private method to parse ``expression`` according to ``interpreter`` and generates dictionaries of terms and signs. Parameters ---------- expression : List[str] The list of strings that represent a set of equations. interpreter : dict The dictionary of interpreters that explain the terms in the ``expression``. terms_and_signs_dict : dict The dictionary that represents the terms and signs of each equation directly (instead of through ``expression`` and ``interpreter``). Returns ------- pde : :class:`PartialDifferentialEquations` The output partial differential equations instance. """pde=PartialDifferentialEquations(expression=expression,interpreter=interpreter,terms_and_signs_dict=terms_and_signs_dict)returnpde
[docs]classPartialDifferentialEquations(Frozen):"""The Partial Differential Equations class."""def__init__(self,expression=None,interpreter=None,terms_and_signs_dict=None):ifterms_and_signs_dictisNone:# provided terms and signsexpression=self._check_expression(expression)interpreter=self._filter_interpreter(interpreter)self._parse_expression(expression,interpreter)else:assertexpressionisNoneandinterpreterisNoneself._parse_terms_and_signs(terms_and_signs_dict)self._check_equations()self._unknowns=Noneself._meshes,self._mesh=WeakFormulation._parse_meshes(self._term_dict)self._bc=Noneself._derive=PDEDerive(self)self._freeze()@staticmethoddef_check_expression(expression):""""""ifisinstance(expression,str):assertlen(expression)>0,"cannot be empty expression."expression=[expression,]else:assertisinstance(expression,(list,tuple)),f"pls put expression in a list or tuple."fori,expinenumerate(expression):assertisinstance(exp,str),f"expression[{i}] = {exp} is not a string."assertlen(exp)>0,f"expression[{i}] is empty."fori,equationinenumerate(expression):assertequation.count('=')==1,f"expression[{i}]={equation} is wrong, can only have one '='."returnexpression@staticmethoddef_filter_interpreter(interpreter):""""""new_interpreter=dict()forvar_nameininterpreter:ifinterpreter[var_name].__class__isForm:new_interpreter[var_name]=interpreter[var_name]else:passreturnnew_interpreterdef_parse_expression(self,expression,interpreter):"""Keep upgrading this method to let it understand more equations."""indi_dict=dict()sign_dict=dict()term_dict=dict()ind_dict=dict()indexing=dict()fori,equationinenumerate(expression):equation=equation.replace(' ','')# remove all spacesequation=equation.replace('-','+-')# let all terms be connected by +indi_dict[i]=([],[])# for left terms and right terms of ith equationsign_dict[i]=([],[])# for left terms and right terms of ith equationterm_dict[i]=([],[])# for left terms and right terms of ith equationind_dict[i]=([],[])# for left terms and right terms of ith equationk=0forj,lorinenumerate(equation.split('=')):local_terms=lor.split('+')forloc_terminlocal_terms:ifloc_term==''orloc_term=='-':# found empty terms, just ignore.passelse:ifloc_term=='0':passelse:ifloc_term[0]=='-':assertloc_term[1:]ininterpreter,f"found term {loc_term[1:]} not interpreted."indi=loc_term[1:]sign='-'term=interpreter[loc_term[1:]]else:assertloc_termininterpreter,f"found term {loc_term} not interpreted"indi=loc_termsign='+'term=interpreter[loc_term]indi_dict[i][j].append(indi)sign_dict[i][j].append(sign)term_dict[i][j].append(term)ifj==0:index=str(i)+'-'+str(k)elifj==1:index=str(i)+'-'+str(k)else:raiseException()k+=1indexing[index]=(indi,sign,term)ind_dict[i][j].append(index)self._indi_dict=indi_dict# a not very import attribute. Only for print representations.self._sign_dict=sign_dictself._term_dict=term_dict# can be form or (for example L2-inner-product- or duality-) termsself._ind_dict=ind_dictself._indexing=indexingefs=list()foriinself._term_dict:fortermsinself._term_dict[i]:forterminterms:ifterm==0:passelse:efs.extend(term.elementary_forms)self._efs=set(efs)def_parse_terms_and_signs(self,terms_and_signs_dict):"""We get an equation from terms and signs."""self._indi_dict=None# in this case, we will not have indi_dict; it is only used for print representationsterms_dict,signs_dict=terms_and_signs_dict_ind_dict=dict()_indexing=dict()num_eq=len(terms_dict)foriinrange(num_eq):assertiinterms_dictandiinsigns_dict,f"numbering of equations must be 0, 1, 2, ..."terms=terms_dict[i]signs=signs_dict[i]_ind_dict[i]=([],[])assertlen(terms)==len(signs)==2and \
len(terms[1])==len(signs[1])andlen(signs[0])==len(terms[0]), \
f"Pls put terms and signs of {i}th equation in ([], []) and ([], []) of same length."k=0forj,lr_termsinenumerate(terms):lr_signs=signs[j]form,terminenumerate(lr_terms):sign=lr_signs[m]index=str(i)+'-'+str(k)_ind_dict[i][j].append(index)_indexing[index]=('',sign,term)# the first entry is the indicator, it is ''.k+=1# ------ need to implement attributes below:self._sign_dict=signs_dictself._term_dict=terms_dictself._ind_dict=_ind_dictself._indexing=_indexingefs=list()foriinself._term_dict:fortermsinself._term_dict[i]:forterminterms:ifterm==0:passelse:efs.extend(term.elementary_forms)self._efs=set(efs)def_check_equations(self):"""Do a self-check after parsing terms."""foriinself._term_dict:left_terms,right_terms=self._term_dict[i]all_terms_of_equation_i=[]all_terms_of_equation_i.extend(left_terms)all_terms_of_equation_i.extend(right_terms)ifall([_.__class__isFormfor_inall_terms_of_equation_i]):term_i0=all_terms_of_equation_i[0]fork,term_ijinenumerate(all_terms_of_equation_i[1:]):assertterm_i0.space==term_ij.space, \
f"spaces in equation #{i} do not match each other: {k+1}th term."elifall([hasattr(_,'_is_able_to_be_a_weak_term')for_inall_terms_of_equation_i# has it, it must return True]):passelse:raiseException()def_pr_vc(self,figsize=(8,6),title=None):"""We print the pde but change all exterior derivatives to corresponding vector calculus operators."""fromsrc.configimportRANK,MASTER_RANKifRANK!=MASTER_RANK:returnelse:passfromsrc.spaces.operatorsimport_d_to_vc,_d_ast_to_vcfromsrc.configimport_global_operator_sym_repr_settingfromsrc.configimport_global_operator_lin_repr_settingfromsrc.configimport_non_root_lin_sepstart,end=_non_root_lin_sepd_sym_repr=_global_operator_sym_repr_setting['d']cd_sym_repr=_global_operator_sym_repr_setting['codifferential']d_lin_repr=_global_operator_lin_repr_setting['d']cd_lin_repr=_global_operator_lin_repr_setting['codifferential']fromsrc.form.othersimport_find_formnumber_equations=len(self._term_dict)symbolic=''foriinself._term_dict:fort,formsinenumerate(self._term_dict[i]):iflen(forms)==0:symbolic+='0'else:forj,forminenumerate(forms):sign=self._sign_dict[i][t][j]form_sym_repr=form._sym_reprform_lin_repr=form._lin_reprdo_it=False_ec_operator_type=''ifform_lin_repr.count(d_lin_repr)+form_lin_repr.count(cd_lin_repr)==1:# we do the vc pr when only one d or cd presents.do_it=Trueifd_lin_reprinform_lin_repr:_ec_operator_type='d'form_lin_repr=form_lin_repr.split(d_lin_repr)[1]elifcd_lin_reprinform_lin_repr:_ec_operator_type='cd'form_lin_repr=form_lin_repr.split(cd_lin_repr)[1]else:raiseException()elifform_lin_repr.count(cd_lin_repr)==1:# we do the vc pr when only one cd presents (may have multiple d).do_it=True_ec_operator_type='cd'form_lin_repr=form_lin_repr.split(cd_lin_repr)[1]else:passifdo_it:while1:ifform_lin_repr[:len(start)]==start:form_lin_repr=form_lin_repr[len(start):]else:breakwhile1:ifform_lin_repr[-len(end):]==end:form_lin_repr=form_lin_repr[:-len(end)]else:breakform=_find_form(form_lin_repr)space=form.spacespace_indicator=space.indicatorm,n,k=space.m,space.n,space.kori=space.orientationvc_operator_sym_dict={'derivative':r"\mathrm{d}",'gradient':r"\nabla",'curl':r"\nabla\times",'rot':r"\nabla\times",'divergence':r"\nabla\cdot",}if_ec_operator_type=='d':vc_operator=_d_to_vc(space_indicator,m,n,k,ori)vc_sign='+'vc_operator=vc_operator_sym_dict[vc_operator]form_sym_repr=form_sym_repr.replace(d_sym_repr,vc_operator+' ')elif_ec_operator_type=='cd':vc_sign,vc_operator=_d_ast_to_vc(space_indicator,m,n,k,ori)vc_operator=vc_operator_sym_dict[vc_operator]vc_operator=r"\widetilde{"+vc_operator+r"}"form_sym_repr=form_sym_repr.replace(cd_sym_repr,vc_operator)else:raiseException()ifvc_sign=='+':passelifvc_sign=='-':ifsign=='+':sign='-'else:sign='+'else:raiseException()ifj==0:ifsign=='+':symbolic+=form_sym_reprelifsign=='-':symbolic+='-'+form_sym_reprelse:raiseException()else:symbolic+=' '+sign+' '+form_sym_reprift==0:symbolic+=' &= 'ifi<number_equations-1:symbolic+=r' \\ 'else:passiflen(self)>1:symbolic=r"$\left\lbrace\begin{aligned}"+symbolic+r"\end{aligned}\right.$"else:symbolic=r"$\begin{aligned}"+symbolic+r"\end{aligned}$"ifself._unknownsisNone:ef_text=list()ef_text_space=list()forefinself._efs:ef_text.append(ef._sym_repr)ef_text_space.append(ef.space._sym_repr)ef_text_space=r"$\in\left("+r'\times '.join(ef_text_space)+r"\right)$"ef_text=r'for $'+r', '.join(ef_text)+r'$'+ef_text_space+','else:ef_text_unknowns=list()ef_text_unknown_spaces=list()ef_text_others=list()ef_text_others_spaces=list()forefinself._unknowns:ef_text_unknowns.append(ef._sym_repr)ef_text_unknown_spaces.append(ef.space._sym_repr)forefinself._efs:ifefinself._unknowns:passelse:ef_text_others.append(ef._sym_repr)ef_text_others_spaces.append(ef.space._sym_repr)ef_text_unknown_spaces=r"$\in\left("+r'\times '.join(ef_text_unknown_spaces)+r"\right)$"ef_text_others_spaces=r"$\in\left("+r'\times '.join(ef_text_others_spaces)+r"\right)$"iflen(ef_text_others)==0:ef_text=(r'seek unknowns: $'+r', '.join(ef_text_unknowns)+r'$'+ef_text_unknown_spaces+', such that')else:ef_text_others=r'for $'+r', '.join(ef_text_others)+r'$'+ef_text_others_spaces+', 'ef_text_unknowns=(r'seek $'+r', '.join(ef_text_unknowns)+r'$'+ef_text_unknown_spaces+', such that')ef_text=ef_text_others+"\n"+ef_text_unknownsef_text=self._mesh.manifold._manifold_text()+ef_textifself._bcisNoneorlen(self._bc._valid_bcs)==0:bc_text=''else:bc_text=self.bc._bc_text()fig=plt.figure(figsize=figsize)plt.axis((0,1,0,1))plt.axis('off')text=ef_text+'\n'+symbolic+bc_textplt.text(0.05,0.5,text,ha='left',va='center',size=15)iftitleisNone:passelse:plt.title(title)fromsrc.configimport_setting,_pr_cacheif_setting['pr_cache']:_pr_cache(fig,filename='pde_vc')else:plt.tight_layout()plt.show(block=_setting['block'])returnfig
[docs]defpr(self,indexing=True,figsize=(8,6),vc=False,title=None):"""Print the representation of the PDE. Parameters ---------- indexing : bool, optional Whether to show indices of my terms. The default value is ``True``. figsize : Tuple[float, int], optional The figure size. It has no effect when the figure is over-sized. A tight configuration will be applied when it is the case. The default value is ``(8, 6)``. vc : bool, optional Whether to show the vector calculus version of me. The default value is ``False``. title : {None, str}, optional The title of the figure. No title if it is ``None``. The default value is ``None``. See Also -------- :func:`src.config.set_pr_cache` """fromsrc.configimportRANK,MASTER_RANKifRANK!=MASTER_RANK:returnelse:passifvcisTrue:returnself._pr_vc(figsize=figsize,title=title)else:passnumber_equations=len(self._term_dict)indicator=''ifself._indi_dictisNone:passelse:foriinself._indi_dict:fort,termsinenumerate(self._indi_dict[i]):iflen(terms)==0:indicator+='0'else:forj,terminenumerate(terms):term=r'\text{\texttt{'+term+'}}'ifindexing:index=self._ind_dict[i][t][j].replace('-',r'\text{-}')term=r'\underbrace{'+term+r'}_{'+ \
rf"{index}"+'}'else:passsign=self._sign_dict[i][t][j]ifj==0:ifsign=='+':indicator+=termelifsign=='-':indicator+='-'+termelse:raiseException()else:indicator+=' '+sign+' '+termift==0:indicator+=' &= 'ifi<number_equations-1:indicator+=r' \\ 'else:passsymbolic=''foriinself._term_dict:fort,formsinenumerate(self._term_dict[i]):iflen(forms)==0:symbolic+='0'else:forj,forminenumerate(forms):sign=self._sign_dict[i][t][j]form_sym_repr=form._sym_reprifindexing:index=self._ind_dict[i][t][j].replace('-',r'\text{-}')form_sym_repr=r'\underbrace{'+form_sym_repr+r'}_{'+ \
rf"{index}"+'}'else:passifj==0:ifsign=='+':symbolic+=form_sym_reprelifsign=='-':symbolic+='-'+form_sym_reprelse:raiseException()else:symbolic+=' '+sign+' '+form_sym_reprift==0:symbolic+=' &= 'ifi<number_equations-1:symbolic+=r' \\ 'else:passifindicator!='':iflen(self)==1:indicator=r"$\begin{aligned}"+indicator+r"\end{aligned}$"else:indicator=r"$\left\lbrace\begin{aligned}"+indicator+r"\end{aligned}\right.$"iflen(self)>1:symbolic=r"$\left\lbrace\begin{aligned}"+symbolic+r"\end{aligned}\right.$"else:symbolic=r"$\begin{aligned}"+symbolic+r"\end{aligned}$"ifself._unknownsisNone:ef_text=list()ef_text_space=list()forefinself._efs:ef_text.append(ef._sym_repr)ef_text_space.append(ef.space._sym_repr)ef_text_space=r"$\in\left("+r'\times '.join(ef_text_space)+r"\right)$"ef_text=r'for $'+r', '.join(ef_text)+r'$'+ef_text_space+','else:ef_text_unknowns=list()ef_text_unknown_spaces=list()ef_text_others=list()ef_text_others_spaces=list()forefinself._unknowns:ef_text_unknowns.append(ef._sym_repr)ef_text_unknown_spaces.append(ef.space._sym_repr)forefinself._efs:ifefinself._unknowns:passelse:ef_text_others.append(ef._sym_repr)ef_text_others_spaces.append(ef.space._sym_repr)ef_text_unknown_spaces=r"$\in\left("+r'\times '.join(ef_text_unknown_spaces)+r"\right)$"ef_text_others_spaces=r"$\in\left("+r'\times '.join(ef_text_others_spaces)+r"\right)$"iflen(ef_text_others)==0:ef_text=(r'seek unknowns: $'+r', '.join(ef_text_unknowns)+r'$'+ef_text_unknown_spaces+', such that')else:ef_text_others=r'for $'+r', '.join(ef_text_others)+r'$'+ef_text_others_spaces+', 'ef_text_unknowns=(r'seek $'+r', '.join(ef_text_unknowns)+r'$'+ef_text_unknown_spaces+', such that')ef_text=ef_text_others+'\n'+ef_text_unknownsef_text=self._mesh.manifold._manifold_text()+ef_textifself._bcisNoneorlen(self._bc._valid_bcs)==0:bc_text=''else:bc_text=self.bc._bc_text()fig=plt.figure(figsize=figsize)plt.axis((0,1,0,1))plt.axis('off')ifindicator=='':text=ef_text+'\n'+symbolic+bc_textelse:text=indicator+'\n\n'+ef_text+'\n'+symbolic+bc_textplt.text(0.05,0.5,text,ha='left',va='center',size=15)iftitleisNone:passelse:plt.title(title)fromsrc.configimport_setting,_pr_cacheif_setting['pr_cache']:_pr_cache(fig,filename='pde')else:plt.tight_layout()plt.show(block=_setting['block'])returnfig
def__len__(self):"""How many equations we have?"""returnlen(self._term_dict)def__getitem__(self,index):returnself._indexing[index]def__iter__(self):""""""foriinself._ind_dict:forlriinself._ind_dict[i]:forindexinlri:yieldindex@propertydefmesh(self):returnself._mesh@propertydefelementary_forms(self):"""Return a set of root forms that this equation involves."""returnself._efs@propertydefunknowns(self):"""Unknowns of the PDE."""returnself._unknowns@unknowns.setterdefunknowns(self,unknowns):""""""ifself._unknownsisnotNone:f"unknowns exists; not allowed to change them."iflen(self)==1andnotisinstance(unknowns,(list,tuple)):unknowns=[unknowns,]assertisinstance(unknowns,(list,tuple)), \
f"please put unknowns in a list or tuple if there are more than 1 equations."assertlen(unknowns)==len(self), \
f"I have {len(self)} equations but receive {len(unknowns)} unknowns."fori,unknowninenumerate(unknowns):assertunknown.__class__isFormandunknown.is_root(), \
f"{i}th variable is not a root form."assertunknowninself._efs,f"{i}th variable = {unknown} is not an elementary form ({self._efs})."self._unknowns=unknowns
[docs]deftest_with(self,test_spaces,test_method='L2',sym_repr:list=None):"""Test the PDE with a set of spaces to obtain a weak formulation. Parameters ---------- test_spaces : list The list of the test spaces. test_method : {``'L2'``, }, optional The test method. Currently, it can only be ``'L2'`` representing the :math:`L^2`-inner product. The default value is ``'L2'``. sym_repr : {List[str], None}, optional The symbolic representations for the test variables. When it is ``None``, pre-set ones will be applied. The default value is ``None``. Returns ------- wf : :class:`src.wf.main.WeakFormulation` The weak formulation instance. """ifnotisinstance(test_spaces,(list,tuple)):test_spaces=[test_spaces,]else:pass# parse test spaces from forms if forms provided._test_spaces=list()fromsrc.form.mainimportFormfori,objinenumerate(test_spaces):ifisinstance(obj,Form):_test_spaces.append(obj.space)else:# noinspection PyUnresolvedReferencesassertobj._is_space(),f"test_spaces[{i}] is not a space."_test_spaces.append(obj)assertlen(_test_spaces)==len(self), \
f"pde has {len(self)} equations, so I need {len(self)} test spaces."assertself.unknownsisnotNone,f"Set unknowns before testing the pde."# -- below, we parse the test functions ----------------------------------------------test_spaces=_test_spacestfs=list()fori,tsinenumerate(test_spaces):unknown=None# in case not found an unknown, will raise Error.forunknowninself.unknowns:unknown_space=unknown.spaceifts==unknown_space:breakelse:unknown=Noneifsym_reprisNone:ifunknownisNone:# we do not find an unknown which is in the same space as the test form.sr=r"\underline{\tau}_"+str(i)else:assertunknown.is_root(),f"a trivial check."sr=unknown._sym_repr_base=sr.split('^')[0].split('_')[0]sr=sr.replace(_base,r'\underline{'+_base+'}')else:assertisinstance(sym_repr,(list,tuple))andlen(sym_repr)==len(test_spaces), \
f"We have {len(test_spaces)} test forms, so we need {len(test_spaces)} syb_repr. " \
f"Now we receive {len(sym_repr)}."sr=sym_repr[i]j=iform_lin_setting=_global_lin_repr_setting['form']_test_lin_repr=form_lin_setting[0]+f'{j}'+_pde_test_form_lin_repr+form_lin_setting[1]while_test_lin_reprin_global_root_forms_lin_dict:j+=1_test_lin_repr=form_lin_setting[0]+f'{j}'+_pde_test_form_lin_repr+form_lin_setting[1]tf=ts.make_form(sr,f'{j}'+_pde_test_form_lin_repr)tfs.append(tf)# -------- make weak form terms ---------------------------------------------------------term_dict=dict()foriinself._term_dict:# ith equationterm_dict[i]=([],[])forj,termsinenumerate(self._term_dict[i]):fork,terminenumerate(terms):ifterm.is_root():# we test a root-form with the test-form!raw_weak_term=inner(term,tfs[i],method=test_method)else:multiply_lin=_global_operator_lin_repr_setting['multiply']# check if we have multiplication in this pde term.ifmultiply_lininterm._lin_repr:ifterm._lin_repr.count(multiply_lin)==1:front_form_lin_repr,the_end_form=term._lin_repr.split(multiply_lin)# below we check if we have: a scalar_parameter multiply a formscalar_parameter_lin=_global_lin_repr_setting['scalar_parameter']scalar_parameter_front,scalar_parameter_end=scalar_parameter_linlen_scalar_parameter_front=len(scalar_parameter_front)iffront_form_lin_repr[:len_scalar_parameter_front]==scalar_parameter_front:sep0,sep1=_non_root_lin_sepifthe_end_form[:len(sep0)]==sep0andthe_end_form[-len(sep1):]==sep1:# - the form is not a root formroot_form_lin_repr=the_end_form[len(sep0):-len(sep1)]else:root_form_lin_repr=the_end_formfromsrc.form.othersimport_find_formthe_form=_find_form(root_form_lin_repr)sp_lin_repr=front_form_lin_reprfromsrc.form.parametersimport_find_root_scalar_parameterroot_factor=_find_root_scalar_parameter(sp_lin_repr)ifroot_factorisNone:# do not find a root factor.raiseNotImplementedError()else:raw_weak_term=inner(the_form,tfs[i],factor=root_factor,method=test_method)else:raiseNotImplementedError(front_form_lin_repr,the_end_form)else:raiseNotImplementedError()else:raw_weak_term=inner(term,tfs[i],method=test_method)term_dict[i][j].append(raw_weak_term)# ------ make the weak formulation ----------------------------------------------------wf=WeakFormulation(tfs,term_sign_dict=(term_dict,self._sign_dict))wf.unknowns=self.unknownswf._bc=self._bc# send the BC to the weak formulation.returnwf
@propertydefbc(self):"""The boundary condition of the PDE."""ifself._bcisNone:self._bc=BoundaryCondition(self._mesh)returnself._bc@propertydefderive(self):"""A wrapper all possible derivations to the PDE."""returnself._derive
[docs]classPDEDerive(Frozen):"""A wrapper all possible derivations to a PDE instance. .. todo:: To be implemented. So far, we recommend users to completely construct the PDE through initialization such that any further modification is avoided. """def__init__(self,pde):""""""self._pde=pdeself._freeze()