5050class LineParameters (Identifiable , JsonMixin , CatalogueMixin [pd .DataFrame ]):
5151 """Parameters that define electrical models of lines."""
5252
53- _type_re = "|" .join (x .code () for x in LineType )
54- _material_re = "|" .join (x .code () for x in Material )
55- _insulator_re = "|" .join (x .code () for x in Insulator )
56- _section_re = r"[1-9][0-9]*"
57- _REGEXP_LINE_TYPE_NAME = re .compile (
58- rf"^({ _type_re } )_({ _material_re } )_({ _insulator_re } _)?{ _section_re } $" , flags = re .IGNORECASE
59- )
60-
6153 @ureg_wraps (None , (None , None , "ohm/km" , "S/km" , "A" , None , None , None , "mm²" ))
6254 def __init__ (
6355 self ,
@@ -142,11 +134,11 @@ def __init__(
142134 def __repr__ (self ) -> str :
143135 s = f"<{ type (self ).__name__ } : id={ self .id !r} "
144136 if self ._line_type is not None :
145- s += f", line_type={ str ( self ._line_type )!r } "
137+ s += f", line_type=' { self ._line_type !s } ' "
146138 if self ._insulators is not None :
147- s += f", insulators={ self ._insulators } "
139+ s += f", insulators=' { self ._insulators !s } ' "
148140 if self ._materials is not None :
149- s += f", materials={ self ._materials } "
141+ s += f", materials=' { self ._materials !s } ' "
150142 if self ._sections is not None :
151143 s += f", sections={ self ._sections } "
152144 if self ._ampacities is not None :
@@ -471,11 +463,11 @@ def from_geometry(
471463 cls ,
472464 id : Id ,
473465 * ,
474- line_type : LineType ,
475- material : Material | None = None ,
476- material_neutral : Material | None = None ,
477- insulator : Insulator | None = None ,
478- insulator_neutral : Insulator | None = None ,
466+ line_type : LineType | str ,
467+ material : Material | str | None = None ,
468+ material_neutral : Material | str | None = None ,
469+ insulator : Insulator | str | None = None ,
470+ insulator_neutral : Insulator | str | None = None ,
479471 section : float | Q_ [float ],
480472 section_neutral : float | Q_ [float ] | None = None ,
481473 height : float | Q_ [float ],
@@ -565,11 +557,11 @@ def from_geometry(
565557 def _from_geometry (
566558 cls ,
567559 id : Id ,
568- line_type : LineType ,
569- material : Material | None ,
570- material_neutral : Material | None ,
571- insulator : Insulator | None ,
572- insulator_neutral : Insulator | None ,
560+ line_type : LineType | str ,
561+ material : Material | str | None ,
562+ material_neutral : Material | str | None ,
563+ insulator : Insulator | str | None ,
564+ insulator_neutral : Insulator | str | None ,
573565 section : float ,
574566 section_neutral : float | None ,
575567 height : float ,
@@ -623,23 +615,14 @@ def _from_geometry(
623615 # dpn = data["dpn"] # Distance phase-to-neutral (m)
624616 # dsh = data["dsh"] # Diameter of the sheath (mm)
625617
618+ # Normalize enumerations and fill optional values
626619 line_type = LineType (line_type )
627- if material is None :
628- material = _DEFAULT_MATERIAL [line_type ]
629- if insulator is None :
630- insulator = _DEFAULT_INSULATOR [line_type ]
631- if material_neutral is None :
632- material_neutral = material
633- if insulator_neutral is None :
634- insulator_neutral = insulator
620+ material = _DEFAULT_MATERIAL [line_type ] if material is None else Material (material )
621+ insulator = _DEFAULT_INSULATOR [line_type ] if insulator is None else Insulator (insulator )
622+ material_neutral = material if material_neutral is None else Material (material_neutral )
623+ insulator_neutral = insulator if insulator_neutral is None else Insulator (insulator_neutral )
635624 if section_neutral is None :
636625 section_neutral = section
637- material = Material (material )
638- material_neutral = Material (material_neutral )
639- if insulator is not None :
640- insulator = Insulator (insulator )
641- if insulator_neutral is not None :
642- insulator_neutral = Insulator (insulator_neutral )
643626
644627 # Geometric configuration
645628 coord , coord_prim , epsilon , epsilon_neutral = cls ._get_geometric_configuration (
@@ -728,8 +711,8 @@ def _from_geometry(
728711 @staticmethod
729712 def _get_geometric_configuration (
730713 line_type : LineType ,
731- insulator : Insulator | None ,
732- insulator_neutral : Insulator | None ,
714+ insulator : Insulator ,
715+ insulator_neutral : Insulator ,
733716 height : float ,
734717 external_diameter : float ,
735718 ) -> tuple [FloatArray , FloatArray , float , float ]:
@@ -762,7 +745,7 @@ def _get_geometric_configuration(
762745 if line_type in (LineType .OVERHEAD , LineType .TWISTED ):
763746 # TODO This configuration is for twisted lines... Create a overhead configuration.
764747 if height <= 0 :
765- msg = f"The height of a '{ line_type } ' line must be a positive number."
748+ msg = f"The height of '{ line_type } ' line must be a positive number."
766749 logger .error (msg )
767750 raise RoseauLoadFlowException (msg = msg , code = RoseauLoadFlowExceptionCode .BAD_LINE_MODEL )
768751 x = SQRT3 * external_diameter / 8
@@ -786,7 +769,7 @@ def _get_geometric_configuration(
786769 epsilon_neutral = EPSILON_0 .m # TODO assume no insulator. Maybe valid for overhead but not for twisted...
787770 elif line_type == LineType .UNDERGROUND :
788771 if height >= 0 :
789- msg = f"The height of a '{ line_type } ' line must be a negative number."
772+ msg = f"The height of '{ line_type } ' line must be a negative number."
790773 logger .error (msg )
791774 raise RoseauLoadFlowException (msg = msg , code = RoseauLoadFlowExceptionCode .BAD_LINE_MODEL )
792775 x = np .sqrt (2 ) * external_diameter / 8
@@ -796,9 +779,7 @@ def _get_geometric_configuration(
796779 epsilon = (EPSILON_0 * EPSILON_R [insulator ]).m
797780 epsilon_neutral = (EPSILON_0 * EPSILON_R [insulator_neutral ]).m
798781 else :
799- msg = f"The line type { line_type !r} of the line { id !r} is unknown."
800- logger .error (msg )
801- raise RoseauLoadFlowException (msg = msg , code = RoseauLoadFlowExceptionCode .BAD_LINE_TYPE )
782+ raise NotImplementedError (line_type ) # unreachable
802783
803784 return coord , coord_prim , epsilon , epsilon_neutral
804785
@@ -809,7 +790,8 @@ def from_coiffier_model(cls, name: str, nb_phases: int = 3, id: Id | None = None
809790 Args:
810791 name:
811792 The canonical name of the line parameters. It must be in the format
812- `LineType_Material_CrossSection`. E.g. "U_AL_150".
793+ `LineType_Material_CrossSection` (e.g. "S_AL_150") or
794+ `LineType_Material_Insulator_CrossSection` (e.g. "S_AL_PE_150").
813795
814796 nb_phases:
815797 The number of phases of the line between 1 and 4, defaults to 3. It represents the
@@ -823,30 +805,44 @@ def from_coiffier_model(cls, name: str, nb_phases: int = 3, id: Id | None = None
823805 The corresponding line parameters.
824806 """
825807 # Check the user input and retrieve enumerated types
808+ m = re .match (
809+ r"^(?P<line_type>[a-z]+)_(?P<material>[a-z]+)_(?:(?P<insulator>[a-z]+)_)?(?P<section>[1-9][0-9]*)$" ,
810+ name ,
811+ flags = re .IGNORECASE ,
812+ )
826813 try :
827- if cls . _REGEXP_LINE_TYPE_NAME . fullmatch ( string = name ) is None :
814+ if m is None :
828815 raise AssertionError
829- line_type_s , material_s , section_s = name .split ("_" )
830- line_type = LineType (line_type_s )
831- material = Material (material_s )
832- section = Q_ (float (section_s ), "mm**2" )
833- except Exception :
816+ matches = m .groupdict ()
817+ line_type = LineType (matches ["line_type" ])
818+ material = Material (matches ["material" ])
819+ insulator = Insulator (matches ["insulator" ]) if matches ["insulator" ] is not None else None
820+ section = Q_ (float (matches ["section" ]), "mm**2" )
821+ except Exception as e :
834822 msg = (
835823 f"The Coiffier line parameter name { name !r} is not valid, expected format is "
836- "'LineType_Material_CrossSection'. "
824+ "'LineType_Material_CrossSection' or 'LineType_Material_Insulator_CrossSection' "
837825 )
826+ if m is not None :
827+ msg += f": { e } "
838828 logger .error (msg )
839829 raise RoseauLoadFlowException (msg = msg , code = RoseauLoadFlowExceptionCode .BAD_TYPE_NAME_SYNTAX ) from None
840-
830+ if insulator is not None :
831+ # TODO: add insulator support
832+ warnings .warn (
833+ f"The insulator is currently ignored in the Coiffier model, got '{ insulator .upper ()} '." ,
834+ category = UserWarning ,
835+ stacklevel = find_stack_level (),
836+ )
841837 r = RHO [material ] / section
842838 if line_type == LineType .OVERHEAD :
843839 c_b1 = Q_ (50 , "µF/km" )
844840 c_b2 = Q_ (0 , "µF/(km*mm**2)" )
845841 x = Q_ (0.35 , "ohm/km" )
846842 if material == Material .AA :
847- if section <= 50 :
843+ if section <= Q_ ( 50 , "mm**2" ) :
848844 c_imax = 14.20
849- elif 50 < section <= 100 :
845+ elif section <= Q_ ( 100 , "mm**2" ) :
850846 c_imax = 12.10
851847 else :
852848 c_imax = 15.70
@@ -855,14 +851,14 @@ def from_coiffier_model(cls, name: str, nb_phases: int = 3, id: Id | None = None
855851 elif material == Material .CU :
856852 c_imax = 21
857853 elif material == Material .LA :
858- if section <= 50 :
854+ if section <= Q_ ( 50 , "mm**2" ) :
859855 c_imax = 13.60
860- elif 50 < section <= 100 :
856+ elif section <= Q_ ( 100 , "mm**2" ) :
861857 c_imax = 12.10
862858 else :
863859 c_imax = 15.60
864860 else :
865- c_imax = 15.90
861+ c_imax = 15.90 # pragma: no-cover # unreachable
866862 elif line_type == LineType .TWISTED :
867863 c_b1 = Q_ (1750 , "µF/km" )
868864 c_b2 = Q_ (5 , "µF/(km*mm**2)" )
@@ -883,9 +879,7 @@ def from_coiffier_model(cls, name: str, nb_phases: int = 3, id: Id | None = None
883879 c_imax = 16.5
884880 x = Q_ (0.1 , "ohm/km" )
885881 else :
886- msg = f"The line type { line_type !r} of the line { name !r} is unknown."
887- logger .error (msg )
888- raise RoseauLoadFlowException (msg = msg , code = RoseauLoadFlowExceptionCode .BAD_LINE_TYPE )
882+ raise NotImplementedError (line_type ) # unreachable
889883 b = (c_b1 + c_b2 * section ) * 1e-4 * OMEGA
890884 b = b .to ("S/km" )
891885
@@ -1329,7 +1323,7 @@ def _get_catalogue(
13291323 )
13301324 try :
13311325 mask = enum_series == enum_class (value )
1332- except RoseauLoadFlowException :
1326+ except ValueError :
13331327 mask = pd .Series (data = False , index = catalogue_data .index )
13341328 if raise_if_not_found and mask .sum () == 0 :
13351329 cls ._raise_not_found_in_catalogue (
@@ -1669,14 +1663,23 @@ def _check_matrix(self) -> None:
16691663
16701664 @staticmethod
16711665 def _check_enum_array (
1672- value : _StrEnumType | Sequence [_StrEnumType ] | None ,
1673- enum_class : type [_StrEnumType ],
1666+ value : str | Sequence [str ] | NDArray | None ,
1667+ enum_class : type [StrEnum ],
16741668 name : Literal ["insulators" , "materials" ],
16751669 size : int ,
1676- ) -> NDArray [_StrEnumType ] | None :
1670+ ) -> NDArray [np . object_ ] | None :
16771671 value_isna = pd .isna (value )
1672+
1673+ def convert (v ):
1674+ try :
1675+ return enum_class (v )
1676+ except ValueError as e :
1677+ raise RoseauLoadFlowException (
1678+ msg = str (e ), code = RoseauLoadFlowExceptionCode [f"BAD_{ enum_class .__name__ .upper ()} " ]
1679+ ) from None
1680+
16781681 if np .isscalar (value_isna ):
1679- return None if value_isna else np .array ([enum_class (value ) for _ in range (size )], dtype = np .object_ )
1682+ return None if value_isna else np .array ([convert (value ) for _ in range (size )], dtype = np .object_ )
16801683 elif np .all (value_isna ):
16811684 return None
16821685 else :
@@ -1686,9 +1689,9 @@ def _check_enum_array(
16861689 raise RoseauLoadFlowException (msg = msg , code = RoseauLoadFlowExceptionCode [f"BAD_{ name .upper ()} _VALUE" ])
16871690
16881691 # Build the numpy array fails with pd.NA inside
1689- values = np .array ([enum_class (v ) for v in value ], dtype = np .object_ )
1690- if len (value ) != size :
1691- msg = f"Incorrect number of { name } : { len (value )} instead of { size } ."
1692+ values = np .array ([convert (v ) for v in value ], dtype = np .object_ )
1693+ if len (values ) != size :
1694+ msg = f"Incorrect number of { name } : { len (values )} instead of { size } ."
16921695 logger .error (msg )
16931696 raise RoseauLoadFlowException (msg = msg , code = RoseauLoadFlowExceptionCode [f"BAD_{ name .upper ()} _SIZE" ])
16941697 return values
0 commit comments