Source code for pyfeyn2.feynmandiagram

import logging
import warnings
from dataclasses import dataclass, field
from typing import List, Optional, Union

import cssutils
from cssselect import GenericTranslator, SelectorError
from lxml import etree
from particle import Particle
from xsdata.formats.converter import Converter, converter
from xsdata.formats.dataclass.parsers import XmlParser
from xsdata.formats.dataclass.serializers import XmlSerializer
from xsdata.formats.dataclass.serializers.config import SerializerConfig

from pyfeyn2.particles import get_either_particle
from pyfeyn2.types import get_default_sheet
from pyfeyn2.util import deprecated, withify

# We don't want to see the cssutils warnings, since we have custom properties
cssutils.log.setLevel(logging.CRITICAL)


# from pyfeyn2.propagator import Propagator
# from pyfeyn2.vertex import Vertex

# Global counter for unique ids
global_id = 0


[docs]@withify() @dataclass class Identifiable: id: Optional[str] = field( default=None, metadata={"name": "id", "namespace": "", "type": "Attribute"} ) # id2: Optional[str] = field(default=None, metadata={"name": "id2", "namespace": ""}) def __post_init__(self): global global_id if self.id is None: # use some global counter to generate unique id self.id = self.__class__.__name__ + str(global_id) global_id = global_id + 1
[docs]@withify() @dataclass class PDG(Identifiable): pdgid: Optional[int] = field( default=None, metadata={"name": "pdgid", "namespace": "", "type": "Attribute"} ) """PDG ID of the particle""" name: Optional[str] = field(default=None, metadata={"type": "Element"}) """Name of the particle""" type: Optional[str] = field( default=None, metadata={"xml_attribute": True, "type": "Attribute"} ) # TODO check SUSY particle: Optional[Particle] = field(default=None, metadata={"type": "Ignore"}) """Particle object from the particle package""" def _sync(self): """Sync the particle with the pdgid, name etc.""" if self.pdgid is not None: self.particle = Particle.from_pdgid(self.pdgid) self.name = self.particle.name elif self.name is not None: self.particle = get_either_particle( programmatic_name=self.name, name=self.name, evtgen_name=self.name, html_name=self.name, latex_name=self.name, ) if self.particle is None: raise ValueError(f"Particle {self.name} not found") self.pdgid = int(self.particle.pdgid) if self.pdgid is not None and self.type is None: # TODO infere type from pdgid if self.pdgid in range(1, 7): self.type = "fermion" elif self.pdgid == 22: self.type = "photon" elif self.pdgid == 21: self.type = "gluon" elif self.pdgid in range(11, 19): self.type = "fermion" elif abs(self.pdgid) == 24: self.type = "boson" elif self.pdgid == 23: self.type = "boson" elif self.pdgid == 25: self.type = "higgs" else: warnings.warn( f"Inferring type from pdgid not implemented for pdgid {self.pdgid} " ) self.type = "line" def __post_init__(self): super().__post_init__() self._sync() # def with_pdgid(self, pdgid): # self.pdgid = pdgid # self._sync() # return self # def with_name(self, name): # self.name = name # self._sync() # return self
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_pdgig(self, *args, **kwargs): return self.with_pdgid(*args, **kwargs)
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_type(self, *args, **kwargs): return self.with_type(*args, **kwargs)
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_name(self, *args, **kwargs): return self.with_name(*args, **kwargs)
[docs]@withify() @dataclass class Labeled: label: Optional[str] = field( default=None, metadata={"xml_attribute": True, "type": "Attribute"} ) """Label the object"""
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_label(self, *args, **kwargs): return self.with_label(*args, **kwargs)
[docs]@withify() @dataclass class Texted: text: Optional[str] = field( default="", metadata={"xml_attribute": True, "type": "Attribute"} ) """Text the object"""
[docs] @deprecated(version="2.0.7.1", reason='Use label=""') @deprecated(version="2.0.7.1", reason="Use with...().") def set_text(self, *args, **kwargs): return self.with_text(*args, **kwargs)
[docs]@dataclass class Point: x: Optional[float] = field( default=None, metadata={"xml_attribute": True, "type": "Attribute"} ) """x coordinate""" y: Optional[float] = field( default=None, metadata={"xml_attribute": True, "type": "Attribute"} ) """y coordinate""" z: Optional[float] = field( default=None, metadata={"xml_attribute": True, "type": "Attribute"} ) """z coordinate""" def with_point(self, p): self.x = float(p.x) self.y = float(p.y) return self def with_xy(self, x, y): self.x = float(x) self.y = float(y) return self def with_xyz(self, x, y, z): self.x = float(x) self.y = float(y) self.z = float(z) return self
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_point(self, *args, **kwargs): return self.with_point(*args, **kwargs)
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_xy(self, *args, **kwargs): return self.with_xy(*args, **kwargs)
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_xyz(self, *args, **kwargs): return self.with_xyz(*args, **kwargs)
CSSString = cssutils.css.CSSStyleDeclaration CSSSheet = cssutils.css.CSSStyleSheet
[docs]class CSSStringConverter(Converter):
[docs] @staticmethod def deserialize(value: str, **kwargs) -> CSSString: return cssutils.parseStyle(value)
[docs] @staticmethod def serialize(value: CSSString, **kwargs) -> str: return value.cssText.replace("\n", " ")
[docs]class CSSSheetConverter(Converter):
[docs] @staticmethod def deserialize(value: str, **kwargs) -> CSSSheet: return cssutils.parseString(value)
[docs] @staticmethod def serialize(value: CSSSheet, **kwargs) -> str: return value.cssText.decode("utf-8") # .replace("\n", " ")
converter.register_converter(CSSString, CSSStringConverter()) converter.register_converter(CSSSheet, CSSSheetConverter())
[docs]@dataclass class Styled: style: CSSString = field( default_factory=lambda: cssutils.parseStyle(""), metadata={"name": "style", "xml_attribute": True, "type": "Attribute"}, ) """CSS style string.""" clazz: Optional[str] = field( default=None, metadata={"name": "class", "xml_attribute": True, "type": "Attribute"}, ) """CSS class string.""" def raw_style(self): return self.style.cssText.replace("\n", " ") def put_style(self, key, value): if self.style is not None: self.style.setProperty(key, value) return self def with_style(self, style): if style is not None: self.style = cssutils.parseStyle(style) return self def with_class(self, clazz): self.clazz = clazz return self
[docs]@dataclass class Bending: bend: Optional[float] = field( default=None, metadata={"xml_attribute": True, "type": "Attribute"} )
[docs] @deprecated(version="2.0.7.1", reason='Use style="bend : true".') def with_bend(self, bend): self.bend = bend return self
[docs]@dataclass class Targeting: target: Optional[str] = field(default="", metadata={}) """Target of the object""" def with_target(self, target): if isinstance(target, str): self.target = target else: self.target = target.id return self
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_target(self, *args, **kwargs): return self.with_target(*args, **kwargs)
[docs]@dataclass class Sourcing: source: Optional[str] = field(default="", metadata={}) """Source of the object""" def with_source(self, source): if isinstance(source, str): self.source = source else: self.source = source.id return self
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_source(self, *args, **kwargs): return self.with_source(*args, **kwargs)
[docs]@dataclass class Line(Targeting, Sourcing): def connect(self, source, target): return self.with_source(source.id).with_target(target.id)
[docs]@withify() @dataclass class Vertex(Labeled, Point, Styled, Identifiable): pass
[docs]@withify() @dataclass class Connector(Labeled, Bending, Styled, PDG): momentum: Optional[str] = field(default=None, metadata={}) """Momentum of the connector""" tension: Optional[float] = field( default=None, metadata={"xml_attribute": True, "type": "Attribute"} ) """Tension of the connector""" length: Optional[float] = field( default=None, metadata={"xml_attribute": True, "type": "Attribute"} ) """Length of the connector"""
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_momentum(self, *args, **kwargs): return self.with_momentum(*args, **kwargs)
[docs] @deprecated(version="2.0.7.1", reason='Use style="tension=".') @deprecated(version="2.0.7.1", reason="Use with...().") def set_tension(self, *args, **kwargs): return self.with_tension(*args, **kwargs)
[docs] @deprecated(version="2.0.7.1", reason='Use style="tension=".') @deprecated(version="2.0.7.1", reason="Use with...().") def set_length(self, *args, **kwargs): return self.with_length(*args, **kwargs)
[docs]@withify() @dataclass class Leg(Point, Targeting, Connector): sense: str = field(default=None, metadata={}) """Sense of the leg, either 'incoming' or 'outgoing'""" external: Optional[str] = field( default=None, metadata={"xml_attribute": True, "type": "Attribute"} ) """External text for leg""" def with_incoming(self): self.sense = "incoming" return self def with_outgoing(self): self.sense = "outgoing" return self
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_external(self, *args, **kwargs): return self.with_external(*args, **kwargs)
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_incoming(self, *args, **kwargs): return self.with_incoming(*args, **kwargs)
[docs] @deprecated(version="2.0.7.1", reason="Use with...().") def set_outgoing(self, *args, **kwargs): return self.with_outgoing(*args, **kwargs)
[docs]@withify() @dataclass class Propagator(Line, Connector): pass
[docs]@withify() @dataclass class Label(Point, Texted, Identifiable):
[docs] @deprecated(version="2.0.7.1", reason="Use a orphaned Vertex with Label.") def __init__(self, *args, **kwargs): pass
[docs]@withify() @dataclass class FeynmanDiagram: class Meta: name = "diagram" default_style: Optional[bool] = field( default=True, metadata={"name": "default_style", "xml_attribute": True, "type": "Attribute"}, ) propagators: List[Propagator] = field( default_factory=list, metadata={"name": "propagator", "type": "Element", "namespace": ""}, ) vertices: List[Vertex] = field( default_factory=list, metadata={"name": "vertex", "type": "Element", "namespace": ""}, ) legs: List[Leg] = field( default_factory=list, metadata={"name": "leg", "type": "Element", "namespace": ""}, ) labels: List[Label] = field( default_factory=list, metadata={"name": "label", "type": "Element", "namespace": ""}, ) sheet: CSSSheet = field( default_factory=lambda: cssutils.parseString(""), metadata={ "name": "style", "xml_attribute": True, "type": "Attribute", "namespace": "", }, ) def add(self, *fd_all: List[Union[Propagator, Vertex, Leg, Label]]): for a in fd_all: if isinstance(a, Propagator): self.propagators.append(a) elif isinstance(a, Vertex): self.vertices.append(a) elif isinstance(a, Leg): self.legs.append(a) elif isinstance(a, Label): self.labels.append(a) else: raise Exception("Unknown type: " + str(type(a)) + " " + str(a)) return self def get_vertex(self, idd): for v in self.vertices: if v.id == idd: return v for leg in self.legs: if leg.id == idd: return leg return None def get_connections(self, vertex): return [ p for p in self.propagators if p.source == vertex.id or p.target == vertex.id ] + [leg for leg in self.legs if leg.target == vertex.id] def remove_propagator(self, propagator): self.propagators.remove(propagator) return self def get_bounding_box(self): min_x = 0 min_y = 0 max_x = 0 max_y = 0 for v in self.vertices: min_x = min(min_x, v.x) min_y = min(min_y, v.y) max_x = max(max_x, v.x) max_y = max(max_y, v.y) for leg in self.legs: min_x = min(min_x, leg.x) min_y = min(min_y, leg.y) max_x = max(max_x, leg.x) max_y = max(max_y, leg.y) return min_x, min_y, max_x, max_y def add_rule(self, rule: str): self.sheet.add(rule) return self def add_rules(self, rules: str): self.sheet = cssutils.parseString( self.sheet.cssText.decode("utf-8") + "\n" + rules ) return self def with_rule(self, rule: str): return self.with_rules(rule) def with_rules(self, rules: str): self.sheet = cssutils.parseString(rules) return self
[docs] def get_style(self, obj) -> cssutils.css.CSSStyleDeclaration: """Get the style of an object. This is prefered over accessing the style attribute directly, sicne it includes class and pdgid definitions. """ # selectorText is string css = [] # global style css += [self._get_obj_style(obj)] if isinstance(obj, str): css += [self._get_rule_style(obj)] if isinstance(obj, Styled): # specific attribute style css += [obj.style] return cssutils.css.CSSStyleDeclaration( cssText=";".join([c.cssText for c in css]) )
def _get_obj_style(self, obj: Identifiable) -> cssutils.css.CSSStyleDeclaration: document = etree.XML(self.to_xml().encode("ascii")) def lambdaselector(s, obj=obj, document=document): try: expression = GenericTranslator().css_to_xpath(s) except SelectorError: warnings.warn("Invalid selector: " + s) return False return obj.id in [e.get("id") for e in document.xpath(expression)] return self._get_style(lambdaselector) def _get_style(self, lambdaselector) -> cssutils.css.CSSStyleDeclaration: ret = [] if self.default_style: sheets = [get_default_sheet(), self.sheet] else: sheets = [self.sheet] for sheet in sheets: idd = [] cls = [] rest = [] glob = [] for rule in sheet: if rule.type == rule.STYLE_RULE: s = rule.selectorText if lambdaselector(s): if s.startswith("#"): idd.append(rule) elif s.startswith("["): rest.append(rule) elif s.startswith(":"): rest.append(rule) elif s.startswith("*"): glob.append(rule) elif s.contains("."): cls.append(rule) else: rest.append(rule) ret += reversed(idd + cls + rest + glob) # sort rules by priority return cssutils.css.CSSStyleDeclaration( cssText=";".join([r.style.cssText for r in ret]) )
[docs] def to_xml(self) -> str: """Return self as XML.""" config = SerializerConfig(pretty_print=True) serializer = XmlSerializer(config=config) return serializer.render(self)
[docs] @classmethod def from_xml(cls, xml: str): """Load self from XML.""" parser = XmlParser() return parser.from_string(xml, cls)
[docs]@dataclass class Meta: class Meta: name = "meta" name: Optional[str] = field( default="", metadata={"xml_attribute": True, "type": "Attribute"} ) value: Optional[str] = field( default="", metadata={"xml_attribute": True, "type": "Attribute"} )
alias_meta = Meta # TODO setup style ? # style: Optional[str] = field(default="", metadata={"type": "Element"})
[docs]@dataclass class FeynML: class Meta: name = "feynml" head: List[Head] = field( default_factory=list, metadata={"name": "head", "namespace": ""} ) diagrams: List[FeynmanDiagram] = field( default_factory=list, metadata={"name": "diagram", "type": "Element", "namespace": ""}, ) def get_diagram(self, idd): for d in self.diagrams: if d.id == idd: return d return None
[docs] def to_xml(self) -> str: """Return self as XML.""" config = SerializerConfig(pretty_print=True) serializer = XmlSerializer(config=config) return serializer.render(self)
[docs] @classmethod def from_xml(cls, xml: str): """Load self from XML.""" parser = XmlParser() return parser.from_string(xml, cls)