Coverage for src/pycse/beginner.py: 95.71%

70 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-23 16:23 -0400

1"""Beginner module. 

2 

3This module contains function definitions designed to minimize the need for 

4Python syntax. They are for beginners just learning, and they try to give 

5helpful error messages. 

6 

7There are a series of functions to access parts of a list, e.g. the first-fifth 

8and nth elements, the last element, all but the first, and all but the last 

9elements. There is also a cut function to avoid list slicing syntax. The point 

10of these is to delay introducing indexing syntax. 

11 

12""" 

13 

14import collections.abc 

15from scipy.optimize import fsolve as _fsolve 

16from scipy.integrate import quad 

17 

18 

19def first(x): 

20 """Return the first element of x if it is iterable, else return x.""" 

21 if not isinstance(x, collections.abc.Iterable): 

22 raise Exception("{} is not iterable.".format(x)) 

23 

24 return x[0] 

25 

26 

27def second(x): 

28 """Return the second element of x.""" 

29 if not isinstance(x, collections.abc.Iterable): 

30 raise Exception("{} is not iterable.".format(x)) 

31 

32 if not len(x) >= 2: 

33 raise Exception("{} does not have a second element.".format(x)) 

34 

35 return x[1] 

36 

37 

38def third(x): 

39 """Return the third element of x.""" 

40 if not isinstance(x, collections.abc.Iterable): 

41 raise Exception("{} is not iterable.".format(x)) 

42 

43 if not len(x) >= 3: 

44 raise Exception("{} does not have a third element.".format(x)) 

45 

46 return x[2] 

47 

48 

49def fourth(x): 

50 """Return the fourth element of x.""" 

51 if not isinstance(x, collections.abc.Iterable): 

52 raise Exception("{} is not iterable.".format(x)) 

53 

54 if not len(x) >= 4: 

55 raise Exception("{} does not have a fourth element.".format(x)) 

56 

57 return x[3] 

58 

59 

60def fifth(x): 

61 """Return the fifth element of x.""" 

62 if not isinstance(x, collections.abc.Iterable): 

63 raise Exception("{} is not iterable.".format(x)) 

64 

65 if not len(x) >= 5: 

66 raise Exception("{} does not have a fifth element.".format(x)) 

67 

68 return x[4] 

69 

70 

71def nth(x, n=0): 

72 """Return the nth value of x. 

73 Note that `n` starts at 0.""" 

74 if not isinstance(x, collections.abc.Iterable): 

75 raise Exception("{} is not iterable.".format(x)) 

76 

77 if not len(x) >= n: 

78 raise Exception("{} does not have an n={} element.".format(x, n)) 

79 

80 return x[n] 

81 

82 

83def cut(x, start=0, stop=None, step=None): 

84 """Alias for x[start:stop:step]. 

85 

86 This is to avoid having to introduce the slicing syntax. 

87 """ 

88 if not isinstance(x, collections.abc.Iterable): 

89 raise Exception("{} is not iterable.".format(x)) 

90 

91 return x[slice(start, stop, step)] 

92 

93 

94def last(x): 

95 """Return the last element of x if it is iterable.""" 

96 if not isinstance(x, collections.abc.Iterable): 

97 raise Exception("{} is not iterable.".format(x)) 

98 

99 return x[-1] 

100 

101 

102def rest(x): 

103 """Return everything after the first element of x.""" 

104 if not isinstance(x, collections.abc.Iterable): 

105 raise Exception("{} is not iterable.".format(x)) 

106 

107 return x[1:] 

108 

109 

110def butlast(x): 

111 """Return everything but the last element of x.""" 

112 if not isinstance(x, collections.abc.Iterable): 

113 raise Exception("{} is not iterable.".format(x)) 

114 

115 return x[0:-1] 

116 

117 

118# * Wrapped functions 

119 

120# These functions are wrapped to provide a simpler use for new students. Usually 

121# that means there are fewer confusing outputs. For example fsolve returns an 

122# array even for a single number which leads to the need to unpack it to get a 

123# simple number. The nsolve function does not do that. It returns a float if the 

124# result is a 1d array. It also is more explicit about checking for convergence. 

125 

126 

127def nsolve(objective, x0, *args, **kwargs): 

128 """Solve an objective function. 

129 

130 A Wrapped version of scipy.optimize.fsolve. 

131 

132 objective: a callable function f(x) = 0 

133 x0: the initial guess for the solution. 

134 

135 This version warns you if the call did not finish cleanly and prints the 

136 message. 

137 

138 Returns: If there is only one result it returns a float, otherwise it 

139 returns an array. 

140 

141 """ 

142 if "full_output" not in kwargs: 

143 kwargs["full_output"] = 1 

144 

145 ans, _, flag, msg = _fsolve(objective, x0, *args, **kwargs) 

146 

147 if flag != 1: 

148 raise Exception("nsolve did not finish cleanly: {}".format(msg)) 

149 

150 if len(ans) == 1: 

151 return float(ans) 

152 else: 

153 return ans 

154 

155 

156# The quad function returns the integral and error estimate. We rarely use the 

157# error estimate, so here we eliminate it from the output. 

158 

159 

160def integrate(f, a, b, *args, **kwargs): 

161 """Integrate the function f(x) from a to b. 

162 

163 This wraps scipy.integrate.quad to eliminate the error estimate and provide 

164 better debugging information. 

165 

166 If the error estimate is greater than the tolerance argument, an exception 

167 is raised. 

168 

169 """ 

170 if "full_output" not in kwargs: 

171 kwargs["full_output"] = 1 

172 results = quad(f, a, b, *args, **kwargs) 

173 

174 tolerance = kwargs.get("tolerance", 1e-6) 

175 

176 if second(results) > tolerance: 

177 raise Exception( 

178 "Your integral error {} is too large. ".format(second(results)) 

179 + "{} ".format(fourth(results)) 

180 + "See your instructor for help" 

181 ) 

182 return first(results)