Coverage for pygeodesy/rhumbsolve.py: 99%

86 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-04-23 16:38 -0400

1 

2# -*- coding: utf-8 -*- 

3 

4u'''Wrapper to invoke I{Karney}'s U{RhumbSolve 

5<https://GeographicLib.SourceForge.io/C++/doc/RhumbSolve.1.html>} utility 

6as an (exact) rhumb or rhumb line, but intended I{for testing purposes only}. 

7 

8Set env variable C{PYGEODESY_RHUMBSOLVE} to the (fully qualified) path 

9of the C{RhumbSolve} executable. 

10''' 

11# from pygeodesy.basics import _xinstanceof # from .karney 

12from pygeodesy.constants import _180_0, _N_180_0, _90_0 # PYCHOK used! 

13from pygeodesy.interns import NN, _azi12_, _lat2_, _lon2_, _s12_, _S12_, _UNDER_ 

14from pygeodesy.karney import GDict, _norm180, _sincos2d, _xinstanceof, wrap360 

15from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, _getenv, printf 

16from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple 

17from pygeodesy.props import deprecated_method, Property, Property_RO 

18from pygeodesy.rhumbx import Caps, RhumbError, Rhumb8Tuple # PYCHOK used! 

19from pygeodesy.solveBase import _LineSolveBase, _SolveBase 

20# from pygeodesy.utily import wrap360 # from .karney 

21 

22__all__ = _ALL_LAZY.rhumbsolve 

23__version__ = '23.04.11' 

24 

25_PYGEODESY_RHUMBSOLVE_ = 'PYGEODESY_RHUMBSOLVE' # PYCHOK used! 

26 

27 

28class _RhumbSolveBase(_SolveBase): 

29 '''(INTERNAL) Base class for L{RhumbSolve} and L{RhumbLineSolve}. 

30 ''' 

31 _Error = RhumbError 

32 _Names_Direct = _lat2_, _lon2_, _S12_ 

33 _Names_Inverse = _azi12_, _s12_, _S12_ 

34 _Solve_name = 'RhumbSolve' 

35 _Solve_path = _getenv(_PYGEODESY_RHUMBSOLVE_, _PYGEODESY_RHUMBSOLVE_) 

36 

37 @Property_RO 

38 def _cmdBasic(self): 

39 '''(INTERNAL) Get the basic C{RhumbSolve} cmd (C{tuple}). 

40 ''' 

41 return (self.RhumbSolve,) + self._e_option \ 

42 + self._p_option \ 

43 + self._s_option 

44 

45 @Property 

46 def RhumbSolve(self): 

47 '''Get the U{RhumbSolve<https://GeographicLib.SourceForge.io/C++/doc/RhumbSolve.1.html>} 

48 executable (C{filename}). 

49 ''' 

50 return self._Solve_path 

51 

52 @RhumbSolve.setter # PYCHOK setter! 

53 def RhumbSolve(self, path): 

54 '''Set the U{RhumbSolve<https://GeographicLib.SourceForge.io/C++/doc/RhumbSolve.1.html>} 

55 executable (C{filename}), the (fully qualified) path to the C{RhumbSolve} executable. 

56 

57 @raise RhumbError: Invalid B{C{path}}, B{C{path}} doesn't exist or isn't 

58 the C{RhumbSolve} executable. 

59 ''' 

60 self._setSolve(path) 

61 

62 @Property_RO 

63 def _s_option(self): # == not -E for GeodSolve 

64 return () if self.Exact else ('-s', ) 

65 

66 def toStr(self, **prec_sep): # PYCHOK signature 

67 '''Return this C{RhumbSolve} as string. 

68 

69 @kwarg prec_sep: Keyword argumens C{B{prec}=6} and C{B{sep}=', '} 

70 for the C{float} C{prec}ision, number of decimal digits 

71 (0..9) and the C{sep}arator string to join. Trailing 

72 zero decimals are stripped for B{C{prec}} values of 

73 1 and above, but kept for negative B{C{prec}} values. 

74 

75 @return: RhumbSolve items (C{str}). 

76 ''' 

77 return self._toStr(RhumbSolve=self.RhumbSolve, **prec_sep) 

78 

79# @Property_RO 

80# def _u_option(self): 

81# return '-u' if self.unroll else () 

82 

83 

84class RhumbSolve(_RhumbSolveBase): 

85 '''Wrapper to invoke I{Karney}'s U{RhumbSolve<https://GeographicLib.SourceForge.io/C++/doc/RhumbSolve.1.html>}, 

86 the C{Exact} version of class L{pygeodesy.Rhumb}. 

87 

88 @note: Use property C{RhumbSolve} or env variable C{PYGEODESY_RHUMBSOLVE} to specify the (fully 

89 qualified) path to the C{RhumbSolve} executable. 

90 

91 @note: This C{rhumb} is intended I{for testing purposes only}, it invokes the C{RhumbSolve} 

92 executable for I{every} method call. 

93 ''' 

94# def Area(self, polyline=False, name=NN): 

95# '''Set up a L{RhumbArea} to compute area and 

96# perimeter of a polygon. 

97# 

98# @kwarg polyline: If C{True} perimeter only, otherwise 

99# area and perimeter (C{bool}). 

100# @kwarg name: Optional name (C{str}). 

101# 

102# @return: A L{RhumbArea} instance. 

103# 

104# @note: The B{C{debug}} setting is passed as C{verbose} 

105# to the returned L{RhumbAreaExact} instance. 

106# ''' 

107# rA = _MODS.rhumbx.RhumbArea(self, polyline=polyline, 

108# name=name or self.name) 

109# if self.verbose or self.debug: # PYCHOK no cover 

110# rA.verbose = True 

111# return rA 

112 

113# Polygon = Area # for C{geographiclib} compatibility 

114 

115 def _azimuth_reverse(self, azimuth): 

116 '''(INTERNAL) Reverse final azimuth C{azimuth}. 

117 ''' 

118 z = _norm180(float(azimuth)) 

119 if self.reverse2: # like .utils.atan2d 

120 z += _180_0 if z < 0 else _N_180_0 

121 return z 

122 

123 def Direct3(self, lat1, lon1, azi1, s12): # PYCHOK outmask 

124 '''Return the destination lat, lon and reverse azimuth 

125 (final bearing) in C{degrees}. 

126 

127 @return: L{Destination3Tuple}C{(lat, lon, final)}. 

128 ''' 

129 r = self._GDictDirect(lat1, lon1, azi1, False, s12, floats=False) 

130 z = self._azimuth_reverse(r.azi12) 

131 return Destination3Tuple(float(r.lat2), float(r.lon2), wrap360(z), 

132 iteration=r._iteration) 

133 

134 def _GDictDirect(self, lat, lon, azi1, arcmode, s12_a12, *unused, **floats): 

135 '''(INTERNAL) Get C{_GenDirect}-like result as an 8-item C{GDict}. 

136 ''' 

137 d = _RhumbSolveBase._GDictDirect(self, lat, lon, azi1, arcmode, s12_a12, **floats) 

138 r = GDict(lat1=lat, lon1=lon, azi12=azi1, s12=s12_a12) # a12=s12_a12 / self.ellipsoid._L_90 

139 r.update(d) 

140 return r 

141 

142 def _GDictInverse(self, lat1, lon1, lat2, lon2, *unused, **floats): 

143 '''(INTERNAL) Get C{_GenInverse}-like result as an 8-item C{GDict}, but 

144 I{without} C{_SALPs_CALPs_}. 

145 ''' 

146 i = _RhumbSolveBase._GDictInverse(self, lat1, lon1, lat2, lon2, **floats) 

147 a = float(i.s12) / self.ellipsoid._L_90 # for .Inverse1 

148 r = GDict(lat1=lat1, lon1=lon1, lat2=lat2, lon2=lon2, a12=a) 

149 r.update(i) 

150 return r 

151 

152 def Inverse3(self, lat1, lon1, lat2, lon2): # PYCHOK outmask 

153 '''Return the distance in C{meter} and the forward and 

154 reverse azimuths (initial and final bearing) in C{degrees}. 

155 

156 @return: L{Distance3Tuple}C{(distance, initial, final)}. 

157 ''' 

158 r = self._GDictInverse(lat1, lon1, lat2, lon2, floats=False) 

159 z = self._azimuth_reverse(r.azi12) 

160 return Distance3Tuple(float(r.s12), wrap360(r.azi12), wrap360(z), 

161 iteration=r._iteration) 

162 

163 def Line(self, lat1, lon1, azi1, caps=Caps.ALL): 

164 '''Set up a L{RhumbLineSolve} to compute several points 

165 on a single rhumb line. 

166 

167 @arg lat1: Latitude of the first point (C{degrees}). 

168 @arg lon1: Longitude of the first point (C{degrees}). 

169 @arg azi1: Azimuth at the first point (compass C{degrees}). 

170 @kwarg caps: Bit-or'ed combination of L{Caps} values specifying 

171 the capabilities the L{RhumbLineSolve} instance 

172 should possess, always C{Caps.ALL}. 

173 

174 @return: A L{RhumbLineSolve} instance. 

175 

176 @note: If the point is at a pole, the azimuth is defined by keeping 

177 B{C{lon1}} fixed, writing C{B{lat1} = ±(90 − ε)}, and taking 

178 the limit C{ε → 0+}. 

179 

180 @see: C++ U{RhumbExact.Line 

181 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1RhumbExact.html>} 

182 and Python U{Rhumb.Line<https://GeographicLib.SourceForge.io/Python/doc/code.html>}. 

183 ''' 

184 return RhumbLineSolve(self, lat1, lon1, azi1, caps=caps, name=self.name) 

185 

186 

187class RhumbLineSolve(_RhumbSolveBase, _LineSolveBase): 

188 '''Wrapper to invoke I{Karney}'s U{RhumbSolve<https://GeographicLib.SourceForge.io/C++/doc/RhumbSolve.1.html>}, 

189 the C{Exact} version of class L{pygeodesy.RhumbLine}. 

190 

191 @note: Use property C{RhumbSolve} or env variable C{PYGEODESY_RHUMBSOLVE} to specify the (fully 

192 qualified) path to the C{RhumbSolve} executable. 

193 

194 @note: This C{rhumb line} is intended I{for testing purposes only}, it invokes the C{RhumbSolve} 

195 executable for I{every} method call. 

196 ''' 

197 def __init__(self, rhumb, lat1, lon1, azi12, caps=Caps.ALL, name=NN): 

198 '''New L{RhumbLineSolve} instance, allowing points to be found along 

199 a rhumb starting at C{(B{lat1}, B{lon1})} with azimuth B{C{azi12}}. 

200 

201 @arg rhumb: The rhumb to use (L{RhumbSolve}). 

202 @arg lat1: Latitude of the first point (C{degrees90}). 

203 @arg lon1: Longitude of the first point (C{degrees180}). 

204 @arg azi12: Azimuth of the rhumb line (compass C{degrees180}). 

205 @kwarg caps: Bit-or'ed combination of L{Caps} values specifying 

206 the capabilities the L{RhumbLineSolve} instance should 

207 possess, always C{Caps.ALL}. Use C{Caps.LINE_OFF} 

208 if updates to the B{C{rhumb}} should I{not} be 

209 reflected in this L{RhumbLineSolve} instance. 

210 

211 @kwarg name: Optional name (C{str}). 

212 

213 @raise RhumbError: Invalid path for C{RhumbSolve} executable or 

214 isn't the C{RhumbSolve} executable, see 

215 property C{B{rhumb}.RhumbSolve}. 

216 

217 @raise TypeError: Invalid B{C{rhumb}}. 

218 ''' 

219 _xinstanceof(RhumbSolve, rhumb=rhumb) 

220 if (caps & Caps.LINE_OFF): # copy to avoid updates 

221 rhumb = rhumb.copy(deep=False, name=NN(_UNDER_, rhumb.name)) 

222 _LineSolveBase.__init__(self, rhumb, lat1, lon1, caps, name, azi12=azi12) 

223 try: 

224 self.RhumbSolve = rhumb.RhumbSolve # rhumb or copy of rhumb 

225 except RhumbError: 

226 pass 

227 

228# def ArcPosition(self, a12, *unused): 

229# '''Find the position on the line given B{C{a12}}. 

230# 

231# @arg a12: Spherical arc length from the first point to the 

232# second point (C{degrees}). 

233# 

234# @return: A C{dict} with 8 items C{lat1, lon1, lat2, lon2, 

235# azi12, a12, s12, S12}. 

236# ''' 

237# s = a12 * self.ellipsoid._L_90 

238# a = self._GDictInvoke(self._cmdArc, True, self._Names_Direct, s) 

239# r = GDict(a12=a12, s12=s, **self._lla1) 

240# r.updated(a) 

241# return r 

242 

243 @Property_RO 

244 def azi12(self): 

245 '''Get this rhumb line's azimuth (compass C{degrees}). 

246 ''' 

247 return self._lla1.azi12 

248 

249 azi1 = azi12 # like GeodesicLineSolve 

250 

251 @Property_RO 

252 def azi12_sincos2(self): # PYCHOK no cover 

253 '''Get the sine and cosine of this rhumb line's azimuth (2-tuple C{(sin, cos)}). 

254 ''' 

255 return _sincos2d(self.azi12) 

256 

257 azi1_sincos2 = azi12_sincos2 

258 

259# @Property_RO 

260# def _cmdArc(self): 

261# '''(INTERNAL) Get the C{RhumbSolve} I{-a -L} cmd (C{tuple}). 

262# ''' 

263# return self._cmdDistance + ('-a',) 

264 

265 def Position(self, s12, *unused): 

266 '''Find the position on the line given B{C{s12}}. 

267 

268 @arg s12: Distance from the first point to the second (C{meter}). 

269 

270 @return: A L{GDict} with 7 items C{lat1, lon1, lat2, lon2, 

271 azi12, s12, S12}. 

272 ''' 

273 d = self._GDictInvoke(self._cmdDistance, True, self._Names_Direct, s12) 

274 r = GDict(s12=s12, **self._lla1) # a12=s12 / self.ellipsoid._L_90 

275 r.update(d) 

276 return r 

277 

278 def toStr(self, **prec_sep): # PYCHOK signature 

279 '''Return this C{RhumbLineSolve} as string. 

280 

281 @kwarg prec_sep: Keyword argumens C{B{prec}=6} and C{B{sep}=', '} 

282 for the C{float} C{prec}ision, number of decimal digits 

283 (0..9) and the C{sep}arator string to join. Trailing 

284 zero decimals are stripped for B{C{prec}} values of 

285 1 and above, but kept for negative B{C{prec}} values. 

286 

287 @return: RhumbLineSolve items (C{str}). 

288 ''' 

289 return _LineSolveBase._toStr(self, azi12=self.azi12, rhumb=self._solve, 

290 RhumbSolve=self.RhumbSolve, **prec_sep) 

291 

292 

293class RhumbSolve7Tuple(Rhumb8Tuple): 

294 '''7-Tuple C{(lat1, lon1, lat2, lon2, azi12, s12, S12)} with lat- C{lat1}, 

295 C{lat2} and longitudes C{lon1}, C{lon2} of both points, the azimuth of the 

296 rhumb line C{azi12}, the distance C{s12} and the area C{S12} under the 

297 rhumb line between both points. 

298 ''' 

299 assert Rhumb8Tuple._Names_.index(_MODS.interns._a12_) == 7 

300 _Names_ = Rhumb8Tuple._Names_[:7] # drop a12 

301 _Units_ = Rhumb8Tuple._Units_[:7] 

302 

303 @deprecated_method 

304 def _to7Tuple(self): # PYCHOK no cover 

305 '''DEPRECATED, I{don't use!} 

306 ''' 

307 return _MODS.deprecated.Rhumb7Tuple(self[:7]) 

308 

309 

310__all__ += _ALL_DOCS(_RhumbSolveBase) 

311 

312if __name__ == '__main__': 

313 

314 from sys import argv 

315 

316 rS = RhumbSolve(name='Test') 

317 rS.verbose = '--verbose' in argv # or '-v' in argv 

318 

319 if rS.RhumbSolve in (_PYGEODESY_RHUMBSOLVE_, None): # not set 

320 rS.RhumbSolve = '/opt/local/bin/RhumbSolve' # '/opt/local/Cellar/geographiclib/1.51/bin/RhumbSolve' # HomeBrew 

321 printf('version: %s', rS.version) 

322 

323 r = rS.Direct(40.6, -73.8, 51, 5.5e6) 

324 printf('Direct: %r', r, nl=1) 

325 printf('Direct3: %r', rS.Direct3(40.6, -73.8, 51, 5.5e6)) 

326 

327 printf('Inverse: %r', rS.Inverse( 40.6, -73.8, 51.6, -0.5), nl=1) 

328 printf('Inverse1: %r', rS.Inverse1(40.6, -73.8, 51.6, -0.5)) 

329 printf('Inverse3: %r', rS.Inverse3(40.6, -73.8, 51.6, -0.5)) 

330 

331 printf('Inverse: %r', rS.Inverse( 40.6, -73.8, 35.8, 140.3), nl=1) 

332 printf('Inverse1: %r', rS.Inverse1(40.6, -73.8, 35.8, 140.3)) 

333 printf('Inverse3: %r', rS.Inverse3(40.6, -73.8, 35.8, 140.3)) 

334 

335 rlS = RhumbLineSolve(rS, 40.6, -73.8, 51, name='LineTest') 

336 p = rlS.Position(5.5e6) 

337 printf('Position: %s %r', p == r, p, nl=1) 

338# p = rlS.ArcPosition(49.475527) 

339# printf('ArcPosition: %s %r', p == r, p) 

340 

341# % python3 -m pygeodesy.rhumbsolve 

342 

343# version: /opt/local/bin/RhumbSolve: GeographicLib version 1.51 

344# 

345# Direct: GDict(S12=44095641862956.148438, azi12=51, lat1=40.6, lat2=71.6889, lon1=-73.8, lon2=0.25552, s12=5500000.0) 

346# Direct3: Destination3Tuple(lat=71.6889, lon=0.25552, final=51.0) 

347# 

348# Inverse: GDict(S12=37395209100030.367188, a12=51.929543, azi12=77.76839, lat1=40.6, lat2=51.6, lon1=-73.8, lon2=-0.5, s12=5771083.383328) 

349# Inverse1: 51.92954250756195 

350# Inverse3: Distance3Tuple(distance=5771083.383328, initial=77.76839, final=77.76839) 

351# 

352# Inverse: GDict(S12=-63760642939072.492188, a12=115.02062, azi12=-92.388888, lat1=40.6, lat2=35.8, lon1=-73.8, lon2=140.3, s12=12782581.067684) 

353# Inverse1: 115.02061966879258 

354# Inverse3: Distance3Tuple(distance=12782581.067684, initial=267.611112, final=267.611112) 

355# 

356# Position: True GDict(S12=44095641862956.148438, azi12=51, lat1=40.6, lat2=71.6889, lon1=-73.8, lon2=0.25552, s12=5500000.0) 

357 

358 

359# % python3 -m pygeodesy.rhumbsolve --verbose 

360 

361# RhumbSolve 'Test' 1: /opt/local/bin/RhumbSolve --version (invoke) 

362# RhumbSolve 'Test' 1: /opt/local/bin/RhumbSolve: GeographicLib version 1.51 (0) 

363# version: /opt/local/bin/RhumbSolve: GeographicLib version 1.51 

364# RhumbSolve 'Test' 2: /opt/local/bin/RhumbSolve -p 10 \ 40.600000000000001 -73.799999999999997 51.0 5500000.0 (Direct) 

365# RhumbSolve 'Test' 2: lat2=71.688899882813047, lon2=0.255519824423445, S12=44095641862956.148 (0) 

366 

367# Direct: GDict(S12=44095641862956.148438, azi12=51, lat1=40.6, lat2=71.6889, lon1=-73.8, lon2=0.25552, s12=5500000.0) 

368# RhumbSolve 'Test' 3: /opt/local/bin/RhumbSolve -p 10 \ 40.600000000000001 -73.799999999999997 51.0 5500000.0 (Direct3) 

369# RhumbSolve 'Test' 3: lat2=71.688899882813047, lon2=0.255519824423445, S12=44095641862956.148 (0) 

370# Direct3: Destination3Tuple(lat=71.6889, lon=0.25552, final=51.0) 

371# RhumbSolve 'Test' 4: /opt/local/bin/RhumbSolve -p 10 -i \ 40.600000000000001 -73.799999999999997 51.600000000000001 -0.5 (Inverse) 

372# RhumbSolve 'Test' 4: azi12=77.768389710255661, s12=5771083.3833280317, S12=37395209100030.367 (0) 

373 

374# Inverse: GDict(S12=37395209100030.367188, a12=51.929543, azi12=77.76839, lat1=40.6, lat2=51.6, lon1=-73.8, lon2=-0.5, s12=5771083.383328) 

375# RhumbSolve 'Test' 5: /opt/local/bin/RhumbSolve -p 10 -i \ 40.600000000000001 -73.799999999999997 51.600000000000001 -0.5 (Inverse1) 

376# RhumbSolve 'Test' 5: azi12=77.768389710255661, s12=5771083.3833280317, S12=37395209100030.367 (0) 

377# Inverse1: 51.92954250756195 

378# RhumbSolve 'Test' 6: /opt/local/bin/RhumbSolve -p 10 -i \ 40.600000000000001 -73.799999999999997 51.600000000000001 -0.5 (Inverse3) 

379# RhumbSolve 'Test' 6: azi12=77.768389710255661, s12=5771083.3833280317, S12=37395209100030.367 (0) 

380# Inverse3: Distance3Tuple(distance=5771083.383328, initial=77.76839, final=77.76839) 

381# RhumbSolve 'Test' 7: /opt/local/bin/RhumbSolve -p 10 -i \ 40.600000000000001 -73.799999999999997 35.799999999999997 140.300000000000011 (Inverse) 

382# RhumbSolve 'Test' 7: azi12=-92.388887981699639, s12=12782581.0676841792, S12=-63760642939072.492 (0) 

383 

384# Inverse: GDict(S12=-63760642939072.492188, a12=115.02062, azi12=-92.388888, lat1=40.6, lat2=35.8, lon1=-73.8, lon2=140.3, s12=12782581.067684) 

385# RhumbSolve 'Test' 8: /opt/local/bin/RhumbSolve -p 10 -i \ 40.600000000000001 -73.799999999999997 35.799999999999997 140.300000000000011 (Inverse1) 

386# RhumbSolve 'Test' 8: azi12=-92.388887981699639, s12=12782581.0676841792, S12=-63760642939072.492 (0) 

387# Inverse1: 115.02061966879258 

388# RhumbSolve 'Test' 9: /opt/local/bin/RhumbSolve -p 10 -i \ 40.600000000000001 -73.799999999999997 35.799999999999997 140.300000000000011 (Inverse3) 

389# RhumbSolve 'Test' 9: azi12=-92.388887981699639, s12=12782581.0676841792, S12=-63760642939072.492 (0) 

390# Inverse3: Distance3Tuple(distance=12782581.067684, initial=267.611112, final=267.611112) 

391 

392# Position: True GDict(S12=44095641862956.148438, azi12=51, lat1=40.6, lat2=71.6889, lon1=-73.8, lon2=0.25552, s12=5500000.0) 

393 

394 

395# **) MIT License 

396# 

397# Copyright (C) 2022-2023 -- mrJean1 at Gmail -- All Rights Reserved. 

398# 

399# Permission is hereby granted, free of charge, to any person obtaining a 

400# copy of this software and associated documentation files (the "Software"), 

401# to deal in the Software without restriction, including without limitation 

402# the rights to use, copy, modify, merge, publish, distribute, sublicense, 

403# and/or sell copies of the Software, and to permit persons to whom the 

404# Software is furnished to do so, subject to the following conditions: 

405# 

406# The above copyright notice and this permission notice shall be included 

407# in all copies or substantial portions of the Software. 

408# 

409# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 

410# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

411# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 

412# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 

413# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 

414# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 

415# OTHER DEALINGS IN THE SOFTWARE.