Coverage for gpkit/nomials/core.py: 91%

93 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-07 22:15 -0500

1"The shared non-mathematical backbone of all Nomials" 

2from .data import NomialData 

3from ..small_classes import Numbers, FixedScalar 

4from ..repr_conventions import MUL, UNICODE_EXPONENTS 

5 

6 

7def nomial_latex_helper(c, pos_vars, neg_vars): 

8 """Combines (varlatex, exponent) tuples, 

9 separated by positive vs negative exponent, into a single latex string.""" 

10 pvarstrs = [f'{varl}^{{{x:.2g}}}' if f"{x:.2g}" != "1" else varl 

11 for (varl, x) in pos_vars] 

12 nvarstrs = [f'{varl}^{{{-x:.2g}}}' if f"{-x:.2g}" != "1" else varl 

13 for (varl, x) in neg_vars] 

14 pvarstr = " ".join(sorted(pvarstrs)) 

15 nvarstr = " ".join(sorted(nvarstrs)) 

16 cstr = f"{c:.2g}" 

17 if pos_vars and cstr in ["1", "-1"]: 

18 cstr = cstr[:-1] 

19 else: 

20 cstr = f"{c:.4g}" 

21 if "e" in cstr: # use exponential notation 

22 idx = cstr.index("e") 

23 cstr = f"{cstr[:idx]} \\times 10^{int(cstr[idx+1:])}" 

24 

25 if pos_vars and neg_vars: 

26 return f"{cstr}\\frac{{{pvarstr}}}{{{nvarstr}}}" 

27 if neg_vars and not pos_vars: 

28 return f"\\frac{{{cstr}}}{{{nvarstr}}}" 

29 if pos_vars: 

30 return f"{cstr}{pvarstr}" 

31 return f"{cstr}" 

32 

33 

34class Nomial(NomialData): 

35 "Shared non-mathematical properties of all nomials" 

36 sub = None 

37 

38 def str_without(self, excluded=()): # pylint: disable=too-many-branches 

39 "String representation, excluding fields ('units', varkey attributes)" 

40 units = "" if "units" in excluded else self.unitstr(" [%s]") 

41 if hasattr(self, "key"): 

42 return self.key.str_without(excluded) + units # pylint: disable=no-member 

43 if "ast" not in excluded and self.ast: 

44 return self.parse_ast(excluded) + units 

45 mstrs = [] 

46 for exp, c in self.hmap.items(): 

47 pvarstrs, nvarstrs = [], [] 

48 for (var, x) in sorted(exp.items(), 

49 key=lambda vx: (vx[1], str(vx[0]))): 

50 if not x: 

51 continue 

52 if x > 0: 

53 varstrlist = pvarstrs 

54 else: 

55 x = -x 

56 varstrlist = nvarstrs 

57 varstr = var.str_without(excluded) 

58 if UNICODE_EXPONENTS and int(x) == x and 2 <= x <= 9: 

59 x = int(x) 

60 if x in (2, 3): 

61 varstr += chr(176+x) 

62 elif x in (4, 5, 6, 7, 8, 9): 

63 varstr += chr(8304+x) 

64 elif x != 1: 

65 varstr += f"^{x:.2g}" 

66 varstrlist.append(varstr) 

67 numerator_strings = pvarstrs 

68 cstr = f"{c:.3g}" 

69 if cstr == "-1": 

70 cstr = "-" 

71 if numerator_strings and cstr == "1": 

72 mstr = MUL.join(pvarstrs) 

73 else: 

74 mstr = MUL.join([cstr] + pvarstrs) 

75 if nvarstrs: 

76 mstr = mstr + "/" + "/".join(nvarstrs) 

77 mstrs.append(mstr) 

78 return " + ".join(sorted(mstrs)) + units 

79 

80 def latex(self, excluded=()): # TODO: add ast parsing here 

81 "Latex representation, parsing `excluded` just as .str_without does" 

82 mstrs = [] 

83 for exp, c in self.hmap.items(): 

84 pos_vars, neg_vars = [], [] 

85 for var, x in exp.items(): 

86 if x > 0: 

87 pos_vars.append((var.latex(excluded), x)) 

88 elif x < 0: 

89 neg_vars.append((var.latex(excluded), x)) 

90 mstrs.append(nomial_latex_helper(c, pos_vars, neg_vars)) 

91 

92 if "units" in excluded: 

93 return " + ".join(sorted(mstrs)) 

94 units = self.unitstr(r"\mathrm{~\left[ %s \right]}", ":L~") 

95 units_tf = units.replace("frac", "tfrac").replace(r"\cdot", r"\cdot ") 

96 return " + ".join(sorted(mstrs)) + units_tf 

97 

98 @property 

99 def value(self): 

100 """Self, with values substituted for variables that have values 

101 

102 Returns 

103 ------- 

104 float, if no symbolic variables remain after substitution 

105 (Monomial, Posynomial, or Nomial), otherwise. 

106 """ 

107 if isinstance(self, FixedScalar): 

108 return self.cs[0] 

109 p = self.sub({k: k.value for k in self.vks if "value" in k.descr}) # pylint: disable=not-callable 

110 return p.cs[0] if isinstance(p, FixedScalar) else p 

111 

112 def __eq__(self, other): 

113 "True if self and other are algebraically identical." 

114 if isinstance(other, Numbers): 

115 return isinstance(self, FixedScalar) and self.value == other 

116 return super().__eq__(other) 

117 

118 __hash__ = NomialData.__hash__ 

119 # pylint: disable=multiple-statements 

120 def __ne__(self, other): return not Nomial.__eq__(self, other) 

121 def __radd__(self, other): return self.__add__(other, rev=True) # pylint: disable=no-member 

122 def __rmul__(self, other): return self.__mul__(other, rev=True) # pylint: disable=no-member 

123 

124 def prod(self): 

125 "Return self for compatibility with NomialArray" 

126 return self 

127 

128 sum = prod