Coverage for /usr/lib/python3/dist-packages/sympy/geometry/plane.py: 14%

293 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1"""Geometrical Planes. 

2 

3Contains 

4======== 

5Plane 

6 

7""" 

8 

9from sympy.core import Dummy, Rational, S, Symbol 

10from sympy.core.symbol import _symbol 

11from sympy.functions.elementary.trigonometric import cos, sin, acos, asin, sqrt 

12from .entity import GeometryEntity 

13from .line import (Line, Ray, Segment, Line3D, LinearEntity, LinearEntity3D, 

14 Ray3D, Segment3D) 

15from .point import Point, Point3D 

16from sympy.matrices import Matrix 

17from sympy.polys.polytools import cancel 

18from sympy.solvers import solve, linsolve 

19from sympy.utilities.iterables import uniq, is_sequence 

20from sympy.utilities.misc import filldedent, func_name, Undecidable 

21 

22from mpmath.libmp.libmpf import prec_to_dps 

23 

24import random 

25 

26 

27x, y, z, t = [Dummy('plane_dummy') for i in range(4)] 

28 

29 

30class Plane(GeometryEntity): 

31 """ 

32 A plane is a flat, two-dimensional surface. A plane is the two-dimensional 

33 analogue of a point (zero-dimensions), a line (one-dimension) and a solid 

34 (three-dimensions). A plane can generally be constructed by two types of 

35 inputs. They are three non-collinear points and a point and the plane's 

36 normal vector. 

37 

38 Attributes 

39 ========== 

40 

41 p1 

42 normal_vector 

43 

44 Examples 

45 ======== 

46 

47 >>> from sympy import Plane, Point3D 

48 >>> Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2)) 

49 Plane(Point3D(1, 1, 1), (-1, 2, -1)) 

50 >>> Plane((1, 1, 1), (2, 3, 4), (2, 2, 2)) 

51 Plane(Point3D(1, 1, 1), (-1, 2, -1)) 

52 >>> Plane(Point3D(1, 1, 1), normal_vector=(1,4,7)) 

53 Plane(Point3D(1, 1, 1), (1, 4, 7)) 

54 

55 """ 

56 def __new__(cls, p1, a=None, b=None, **kwargs): 

57 p1 = Point3D(p1, dim=3) 

58 if a and b: 

59 p2 = Point(a, dim=3) 

60 p3 = Point(b, dim=3) 

61 if Point3D.are_collinear(p1, p2, p3): 

62 raise ValueError('Enter three non-collinear points') 

63 a = p1.direction_ratio(p2) 

64 b = p1.direction_ratio(p3) 

65 normal_vector = tuple(Matrix(a).cross(Matrix(b))) 

66 else: 

67 a = kwargs.pop('normal_vector', a) 

68 evaluate = kwargs.get('evaluate', True) 

69 if is_sequence(a) and len(a) == 3: 

70 normal_vector = Point3D(a).args if evaluate else a 

71 else: 

72 raise ValueError(filldedent(''' 

73 Either provide 3 3D points or a point with a 

74 normal vector expressed as a sequence of length 3''')) 

75 if all(coord.is_zero for coord in normal_vector): 

76 raise ValueError('Normal vector cannot be zero vector') 

77 return GeometryEntity.__new__(cls, p1, normal_vector, **kwargs) 

78 

79 def __contains__(self, o): 

80 k = self.equation(x, y, z) 

81 if isinstance(o, (LinearEntity, LinearEntity3D)): 

82 d = Point3D(o.arbitrary_point(t)) 

83 e = k.subs([(x, d.x), (y, d.y), (z, d.z)]) 

84 return e.equals(0) 

85 try: 

86 o = Point(o, dim=3, strict=True) 

87 d = k.xreplace(dict(zip((x, y, z), o.args))) 

88 return d.equals(0) 

89 except TypeError: 

90 return False 

91 

92 def _eval_evalf(self, prec=15, **options): 

93 pt, tup = self.args 

94 dps = prec_to_dps(prec) 

95 pt = pt.evalf(n=dps, **options) 

96 tup = tuple([i.evalf(n=dps, **options) for i in tup]) 

97 return self.func(pt, normal_vector=tup, evaluate=False) 

98 

99 def angle_between(self, o): 

100 """Angle between the plane and other geometric entity. 

101 

102 Parameters 

103 ========== 

104 

105 LinearEntity3D, Plane. 

106 

107 Returns 

108 ======= 

109 

110 angle : angle in radians 

111 

112 Notes 

113 ===== 

114 

115 This method accepts only 3D entities as it's parameter, but if you want 

116 to calculate the angle between a 2D entity and a plane you should 

117 first convert to a 3D entity by projecting onto a desired plane and 

118 then proceed to calculate the angle. 

119 

120 Examples 

121 ======== 

122 

123 >>> from sympy import Point3D, Line3D, Plane 

124 >>> a = Plane(Point3D(1, 2, 2), normal_vector=(1, 2, 3)) 

125 >>> b = Line3D(Point3D(1, 3, 4), Point3D(2, 2, 2)) 

126 >>> a.angle_between(b) 

127 -asin(sqrt(21)/6) 

128 

129 """ 

130 if isinstance(o, LinearEntity3D): 

131 a = Matrix(self.normal_vector) 

132 b = Matrix(o.direction_ratio) 

133 c = a.dot(b) 

134 d = sqrt(sum([i**2 for i in self.normal_vector])) 

135 e = sqrt(sum([i**2 for i in o.direction_ratio])) 

136 return asin(c/(d*e)) 

137 if isinstance(o, Plane): 

138 a = Matrix(self.normal_vector) 

139 b = Matrix(o.normal_vector) 

140 c = a.dot(b) 

141 d = sqrt(sum([i**2 for i in self.normal_vector])) 

142 e = sqrt(sum([i**2 for i in o.normal_vector])) 

143 return acos(c/(d*e)) 

144 

145 

146 def arbitrary_point(self, u=None, v=None): 

147 """ Returns an arbitrary point on the Plane. If given two 

148 parameters, the point ranges over the entire plane. If given 1 

149 or no parameters, returns a point with one parameter which, 

150 when varying from 0 to 2*pi, moves the point in a circle of 

151 radius 1 about p1 of the Plane. 

152 

153 Examples 

154 ======== 

155 

156 >>> from sympy import Plane, Ray 

157 >>> from sympy.abc import u, v, t, r 

158 >>> p = Plane((1, 1, 1), normal_vector=(1, 0, 0)) 

159 >>> p.arbitrary_point(u, v) 

160 Point3D(1, u + 1, v + 1) 

161 >>> p.arbitrary_point(t) 

162 Point3D(1, cos(t) + 1, sin(t) + 1) 

163 

164 While arbitrary values of u and v can move the point anywhere in 

165 the plane, the single-parameter point can be used to construct a 

166 ray whose arbitrary point can be located at angle t and radius 

167 r from p.p1: 

168 

169 >>> Ray(p.p1, _).arbitrary_point(r) 

170 Point3D(1, r*cos(t) + 1, r*sin(t) + 1) 

171 

172 Returns 

173 ======= 

174 

175 Point3D 

176 

177 """ 

178 circle = v is None 

179 if circle: 

180 u = _symbol(u or 't', real=True) 

181 else: 

182 u = _symbol(u or 'u', real=True) 

183 v = _symbol(v or 'v', real=True) 

184 x, y, z = self.normal_vector 

185 a, b, c = self.p1.args 

186 # x1, y1, z1 is a nonzero vector parallel to the plane 

187 if x.is_zero and y.is_zero: 

188 x1, y1, z1 = S.One, S.Zero, S.Zero 

189 else: 

190 x1, y1, z1 = -y, x, S.Zero 

191 # x2, y2, z2 is also parallel to the plane, and orthogonal to x1, y1, z1 

192 x2, y2, z2 = tuple(Matrix((x, y, z)).cross(Matrix((x1, y1, z1)))) 

193 if circle: 

194 x1, y1, z1 = (w/sqrt(x1**2 + y1**2 + z1**2) for w in (x1, y1, z1)) 

195 x2, y2, z2 = (w/sqrt(x2**2 + y2**2 + z2**2) for w in (x2, y2, z2)) 

196 p = Point3D(a + x1*cos(u) + x2*sin(u), \ 

197 b + y1*cos(u) + y2*sin(u), \ 

198 c + z1*cos(u) + z2*sin(u)) 

199 else: 

200 p = Point3D(a + x1*u + x2*v, b + y1*u + y2*v, c + z1*u + z2*v) 

201 return p 

202 

203 

204 @staticmethod 

205 def are_concurrent(*planes): 

206 """Is a sequence of Planes concurrent? 

207 

208 Two or more Planes are concurrent if their intersections 

209 are a common line. 

210 

211 Parameters 

212 ========== 

213 

214 planes: list 

215 

216 Returns 

217 ======= 

218 

219 Boolean 

220 

221 Examples 

222 ======== 

223 

224 >>> from sympy import Plane, Point3D 

225 >>> a = Plane(Point3D(5, 0, 0), normal_vector=(1, -1, 1)) 

226 >>> b = Plane(Point3D(0, -2, 0), normal_vector=(3, 1, 1)) 

227 >>> c = Plane(Point3D(0, -1, 0), normal_vector=(5, -1, 9)) 

228 >>> Plane.are_concurrent(a, b) 

229 True 

230 >>> Plane.are_concurrent(a, b, c) 

231 False 

232 

233 """ 

234 planes = list(uniq(planes)) 

235 for i in planes: 

236 if not isinstance(i, Plane): 

237 raise ValueError('All objects should be Planes but got %s' % i.func) 

238 if len(planes) < 2: 

239 return False 

240 planes = list(planes) 

241 first = planes.pop(0) 

242 sol = first.intersection(planes[0]) 

243 if sol == []: 

244 return False 

245 else: 

246 line = sol[0] 

247 for i in planes[1:]: 

248 l = first.intersection(i) 

249 if not l or l[0] not in line: 

250 return False 

251 return True 

252 

253 

254 def distance(self, o): 

255 """Distance between the plane and another geometric entity. 

256 

257 Parameters 

258 ========== 

259 

260 Point3D, LinearEntity3D, Plane. 

261 

262 Returns 

263 ======= 

264 

265 distance 

266 

267 Notes 

268 ===== 

269 

270 This method accepts only 3D entities as it's parameter, but if you want 

271 to calculate the distance between a 2D entity and a plane you should 

272 first convert to a 3D entity by projecting onto a desired plane and 

273 then proceed to calculate the distance. 

274 

275 Examples 

276 ======== 

277 

278 >>> from sympy import Point3D, Line3D, Plane 

279 >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 1, 1)) 

280 >>> b = Point3D(1, 2, 3) 

281 >>> a.distance(b) 

282 sqrt(3) 

283 >>> c = Line3D(Point3D(2, 3, 1), Point3D(1, 2, 2)) 

284 >>> a.distance(c) 

285 0 

286 

287 """ 

288 if self.intersection(o) != []: 

289 return S.Zero 

290 

291 if isinstance(o, (Segment3D, Ray3D)): 

292 a, b = o.p1, o.p2 

293 pi, = self.intersection(Line3D(a, b)) 

294 if pi in o: 

295 return self.distance(pi) 

296 elif a in Segment3D(pi, b): 

297 return self.distance(a) 

298 else: 

299 assert isinstance(o, Segment3D) is True 

300 return self.distance(b) 

301 

302 # following code handles `Point3D`, `LinearEntity3D`, `Plane` 

303 a = o if isinstance(o, Point3D) else o.p1 

304 n = Point3D(self.normal_vector).unit 

305 d = (a - self.p1).dot(n) 

306 return abs(d) 

307 

308 

309 def equals(self, o): 

310 """ 

311 Returns True if self and o are the same mathematical entities. 

312 

313 Examples 

314 ======== 

315 

316 >>> from sympy import Plane, Point3D 

317 >>> a = Plane(Point3D(1, 2, 3), normal_vector=(1, 1, 1)) 

318 >>> b = Plane(Point3D(1, 2, 3), normal_vector=(2, 2, 2)) 

319 >>> c = Plane(Point3D(1, 2, 3), normal_vector=(-1, 4, 6)) 

320 >>> a.equals(a) 

321 True 

322 >>> a.equals(b) 

323 True 

324 >>> a.equals(c) 

325 False 

326 """ 

327 if isinstance(o, Plane): 

328 a = self.equation() 

329 b = o.equation() 

330 return cancel(a/b).is_constant() 

331 else: 

332 return False 

333 

334 

335 def equation(self, x=None, y=None, z=None): 

336 """The equation of the Plane. 

337 

338 Examples 

339 ======== 

340 

341 >>> from sympy import Point3D, Plane 

342 >>> a = Plane(Point3D(1, 1, 2), Point3D(2, 4, 7), Point3D(3, 5, 1)) 

343 >>> a.equation() 

344 -23*x + 11*y - 2*z + 16 

345 >>> a = Plane(Point3D(1, 4, 2), normal_vector=(6, 6, 6)) 

346 >>> a.equation() 

347 6*x + 6*y + 6*z - 42 

348 

349 """ 

350 x, y, z = [i if i else Symbol(j, real=True) for i, j in zip((x, y, z), 'xyz')] 

351 a = Point3D(x, y, z) 

352 b = self.p1.direction_ratio(a) 

353 c = self.normal_vector 

354 return (sum(i*j for i, j in zip(b, c))) 

355 

356 

357 def intersection(self, o): 

358 """ The intersection with other geometrical entity. 

359 

360 Parameters 

361 ========== 

362 

363 Point, Point3D, LinearEntity, LinearEntity3D, Plane 

364 

365 Returns 

366 ======= 

367 

368 List 

369 

370 Examples 

371 ======== 

372 

373 >>> from sympy import Point3D, Line3D, Plane 

374 >>> a = Plane(Point3D(1, 2, 3), normal_vector=(1, 1, 1)) 

375 >>> b = Point3D(1, 2, 3) 

376 >>> a.intersection(b) 

377 [Point3D(1, 2, 3)] 

378 >>> c = Line3D(Point3D(1, 4, 7), Point3D(2, 2, 2)) 

379 >>> a.intersection(c) 

380 [Point3D(2, 2, 2)] 

381 >>> d = Plane(Point3D(6, 0, 0), normal_vector=(2, -5, 3)) 

382 >>> e = Plane(Point3D(2, 0, 0), normal_vector=(3, 4, -3)) 

383 >>> d.intersection(e) 

384 [Line3D(Point3D(78/23, -24/23, 0), Point3D(147/23, 321/23, 23))] 

385 

386 """ 

387 if not isinstance(o, GeometryEntity): 

388 o = Point(o, dim=3) 

389 if isinstance(o, Point): 

390 if o in self: 

391 return [o] 

392 else: 

393 return [] 

394 if isinstance(o, (LinearEntity, LinearEntity3D)): 

395 # recast to 3D 

396 p1, p2 = o.p1, o.p2 

397 if isinstance(o, Segment): 

398 o = Segment3D(p1, p2) 

399 elif isinstance(o, Ray): 

400 o = Ray3D(p1, p2) 

401 elif isinstance(o, Line): 

402 o = Line3D(p1, p2) 

403 else: 

404 raise ValueError('unhandled linear entity: %s' % o.func) 

405 if o in self: 

406 return [o] 

407 else: 

408 a = Point3D(o.arbitrary_point(t)) 

409 p1, n = self.p1, Point3D(self.normal_vector) 

410 

411 # TODO: Replace solve with solveset, when this line is tested 

412 c = solve((a - p1).dot(n), t) 

413 if not c: 

414 return [] 

415 else: 

416 c = [i for i in c if i.is_real is not False] 

417 if len(c) > 1: 

418 c = [i for i in c if i.is_real] 

419 if len(c) != 1: 

420 raise Undecidable("not sure which point is real") 

421 p = a.subs(t, c[0]) 

422 if p not in o: 

423 return [] # e.g. a segment might not intersect a plane 

424 return [p] 

425 if isinstance(o, Plane): 

426 if self.equals(o): 

427 return [self] 

428 if self.is_parallel(o): 

429 return [] 

430 else: 

431 x, y, z = map(Dummy, 'xyz') 

432 a, b = Matrix([self.normal_vector]), Matrix([o.normal_vector]) 

433 c = list(a.cross(b)) 

434 d = self.equation(x, y, z) 

435 e = o.equation(x, y, z) 

436 result = list(linsolve([d, e], x, y, z))[0] 

437 for i in (x, y, z): result = result.subs(i, 0) 

438 return [Line3D(Point3D(result), direction_ratio=c)] 

439 

440 

441 def is_coplanar(self, o): 

442 """ Returns True if `o` is coplanar with self, else False. 

443 

444 Examples 

445 ======== 

446 

447 >>> from sympy import Plane 

448 >>> o = (0, 0, 0) 

449 >>> p = Plane(o, (1, 1, 1)) 

450 >>> p2 = Plane(o, (2, 2, 2)) 

451 >>> p == p2 

452 False 

453 >>> p.is_coplanar(p2) 

454 True 

455 """ 

456 if isinstance(o, Plane): 

457 return not cancel(self.equation(x, y, z)/o.equation(x, y, z)).has(x, y, z) 

458 if isinstance(o, Point3D): 

459 return o in self 

460 elif isinstance(o, LinearEntity3D): 

461 return all(i in self for i in self) 

462 elif isinstance(o, GeometryEntity): # XXX should only be handling 2D objects now 

463 return all(i == 0 for i in self.normal_vector[:2]) 

464 

465 

466 def is_parallel(self, l): 

467 """Is the given geometric entity parallel to the plane? 

468 

469 Parameters 

470 ========== 

471 

472 LinearEntity3D or Plane 

473 

474 Returns 

475 ======= 

476 

477 Boolean 

478 

479 Examples 

480 ======== 

481 

482 >>> from sympy import Plane, Point3D 

483 >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6)) 

484 >>> b = Plane(Point3D(3,1,3), normal_vector=(4, 8, 12)) 

485 >>> a.is_parallel(b) 

486 True 

487 

488 """ 

489 if isinstance(l, LinearEntity3D): 

490 a = l.direction_ratio 

491 b = self.normal_vector 

492 c = sum([i*j for i, j in zip(a, b)]) 

493 if c == 0: 

494 return True 

495 else: 

496 return False 

497 elif isinstance(l, Plane): 

498 a = Matrix(l.normal_vector) 

499 b = Matrix(self.normal_vector) 

500 if a.cross(b).is_zero_matrix: 

501 return True 

502 else: 

503 return False 

504 

505 

506 def is_perpendicular(self, l): 

507 """Is the given geometric entity perpendicualar to the given plane? 

508 

509 Parameters 

510 ========== 

511 

512 LinearEntity3D or Plane 

513 

514 Returns 

515 ======= 

516 

517 Boolean 

518 

519 Examples 

520 ======== 

521 

522 >>> from sympy import Plane, Point3D 

523 >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6)) 

524 >>> b = Plane(Point3D(2, 2, 2), normal_vector=(-1, 2, -1)) 

525 >>> a.is_perpendicular(b) 

526 True 

527 

528 """ 

529 if isinstance(l, LinearEntity3D): 

530 a = Matrix(l.direction_ratio) 

531 b = Matrix(self.normal_vector) 

532 if a.cross(b).is_zero_matrix: 

533 return True 

534 else: 

535 return False 

536 elif isinstance(l, Plane): 

537 a = Matrix(l.normal_vector) 

538 b = Matrix(self.normal_vector) 

539 if a.dot(b) == 0: 

540 return True 

541 else: 

542 return False 

543 else: 

544 return False 

545 

546 @property 

547 def normal_vector(self): 

548 """Normal vector of the given plane. 

549 

550 Examples 

551 ======== 

552 

553 >>> from sympy import Point3D, Plane 

554 >>> a = Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2)) 

555 >>> a.normal_vector 

556 (-1, 2, -1) 

557 >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 4, 7)) 

558 >>> a.normal_vector 

559 (1, 4, 7) 

560 

561 """ 

562 return self.args[1] 

563 

564 @property 

565 def p1(self): 

566 """The only defining point of the plane. Others can be obtained from the 

567 arbitrary_point method. 

568 

569 See Also 

570 ======== 

571 

572 sympy.geometry.point.Point3D 

573 

574 Examples 

575 ======== 

576 

577 >>> from sympy import Point3D, Plane 

578 >>> a = Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2)) 

579 >>> a.p1 

580 Point3D(1, 1, 1) 

581 

582 """ 

583 return self.args[0] 

584 

585 def parallel_plane(self, pt): 

586 """ 

587 Plane parallel to the given plane and passing through the point pt. 

588 

589 Parameters 

590 ========== 

591 

592 pt: Point3D 

593 

594 Returns 

595 ======= 

596 

597 Plane 

598 

599 Examples 

600 ======== 

601 

602 >>> from sympy import Plane, Point3D 

603 >>> a = Plane(Point3D(1, 4, 6), normal_vector=(2, 4, 6)) 

604 >>> a.parallel_plane(Point3D(2, 3, 5)) 

605 Plane(Point3D(2, 3, 5), (2, 4, 6)) 

606 

607 """ 

608 a = self.normal_vector 

609 return Plane(pt, normal_vector=a) 

610 

611 def perpendicular_line(self, pt): 

612 """A line perpendicular to the given plane. 

613 

614 Parameters 

615 ========== 

616 

617 pt: Point3D 

618 

619 Returns 

620 ======= 

621 

622 Line3D 

623 

624 Examples 

625 ======== 

626 

627 >>> from sympy import Plane, Point3D 

628 >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6)) 

629 >>> a.perpendicular_line(Point3D(9, 8, 7)) 

630 Line3D(Point3D(9, 8, 7), Point3D(11, 12, 13)) 

631 

632 """ 

633 a = self.normal_vector 

634 return Line3D(pt, direction_ratio=a) 

635 

636 def perpendicular_plane(self, *pts): 

637 """ 

638 Return a perpendicular passing through the given points. If the 

639 direction ratio between the points is the same as the Plane's normal 

640 vector then, to select from the infinite number of possible planes, 

641 a third point will be chosen on the z-axis (or the y-axis 

642 if the normal vector is already parallel to the z-axis). If less than 

643 two points are given they will be supplied as follows: if no point is 

644 given then pt1 will be self.p1; if a second point is not given it will 

645 be a point through pt1 on a line parallel to the z-axis (if the normal 

646 is not already the z-axis, otherwise on the line parallel to the 

647 y-axis). 

648 

649 Parameters 

650 ========== 

651 

652 pts: 0, 1 or 2 Point3D 

653 

654 Returns 

655 ======= 

656 

657 Plane 

658 

659 Examples 

660 ======== 

661 

662 >>> from sympy import Plane, Point3D 

663 >>> a, b = Point3D(0, 0, 0), Point3D(0, 1, 0) 

664 >>> Z = (0, 0, 1) 

665 >>> p = Plane(a, normal_vector=Z) 

666 >>> p.perpendicular_plane(a, b) 

667 Plane(Point3D(0, 0, 0), (1, 0, 0)) 

668 """ 

669 if len(pts) > 2: 

670 raise ValueError('No more than 2 pts should be provided.') 

671 

672 pts = list(pts) 

673 if len(pts) == 0: 

674 pts.append(self.p1) 

675 if len(pts) == 1: 

676 x, y, z = self.normal_vector 

677 if x == y == 0: 

678 dir = (0, 1, 0) 

679 else: 

680 dir = (0, 0, 1) 

681 pts.append(pts[0] + Point3D(*dir)) 

682 

683 p1, p2 = [Point(i, dim=3) for i in pts] 

684 l = Line3D(p1, p2) 

685 n = Line3D(p1, direction_ratio=self.normal_vector) 

686 if l in n: # XXX should an error be raised instead? 

687 # there are infinitely many perpendicular planes; 

688 x, y, z = self.normal_vector 

689 if x == y == 0: 

690 # the z axis is the normal so pick a pt on the y-axis 

691 p3 = Point3D(0, 1, 0) # case 1 

692 else: 

693 # else pick a pt on the z axis 

694 p3 = Point3D(0, 0, 1) # case 2 

695 # in case that point is already given, move it a bit 

696 if p3 in l: 

697 p3 *= 2 # case 3 

698 else: 

699 p3 = p1 + Point3D(*self.normal_vector) # case 4 

700 return Plane(p1, p2, p3) 

701 

702 def projection_line(self, line): 

703 """Project the given line onto the plane through the normal plane 

704 containing the line. 

705 

706 Parameters 

707 ========== 

708 

709 LinearEntity or LinearEntity3D 

710 

711 Returns 

712 ======= 

713 

714 Point3D, Line3D, Ray3D or Segment3D 

715 

716 Notes 

717 ===== 

718 

719 For the interaction between 2D and 3D lines(segments, rays), you should 

720 convert the line to 3D by using this method. For example for finding the 

721 intersection between a 2D and a 3D line, convert the 2D line to a 3D line 

722 by projecting it on a required plane and then proceed to find the 

723 intersection between those lines. 

724 

725 Examples 

726 ======== 

727 

728 >>> from sympy import Plane, Line, Line3D, Point3D 

729 >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 1, 1)) 

730 >>> b = Line(Point3D(1, 1), Point3D(2, 2)) 

731 >>> a.projection_line(b) 

732 Line3D(Point3D(4/3, 4/3, 1/3), Point3D(5/3, 5/3, -1/3)) 

733 >>> c = Line3D(Point3D(1, 1, 1), Point3D(2, 2, 2)) 

734 >>> a.projection_line(c) 

735 Point3D(1, 1, 1) 

736 

737 """ 

738 if not isinstance(line, (LinearEntity, LinearEntity3D)): 

739 raise NotImplementedError('Enter a linear entity only') 

740 a, b = self.projection(line.p1), self.projection(line.p2) 

741 if a == b: 

742 # projection does not imply intersection so for 

743 # this case (line parallel to plane's normal) we 

744 # return the projection point 

745 return a 

746 if isinstance(line, (Line, Line3D)): 

747 return Line3D(a, b) 

748 if isinstance(line, (Ray, Ray3D)): 

749 return Ray3D(a, b) 

750 if isinstance(line, (Segment, Segment3D)): 

751 return Segment3D(a, b) 

752 

753 def projection(self, pt): 

754 """Project the given point onto the plane along the plane normal. 

755 

756 Parameters 

757 ========== 

758 

759 Point or Point3D 

760 

761 Returns 

762 ======= 

763 

764 Point3D 

765 

766 Examples 

767 ======== 

768 

769 >>> from sympy import Plane, Point3D 

770 >>> A = Plane(Point3D(1, 1, 2), normal_vector=(1, 1, 1)) 

771 

772 The projection is along the normal vector direction, not the z 

773 axis, so (1, 1) does not project to (1, 1, 2) on the plane A: 

774 

775 >>> b = Point3D(1, 1) 

776 >>> A.projection(b) 

777 Point3D(5/3, 5/3, 2/3) 

778 >>> _ in A 

779 True 

780 

781 But the point (1, 1, 2) projects to (1, 1) on the XY-plane: 

782 

783 >>> XY = Plane((0, 0, 0), (0, 0, 1)) 

784 >>> XY.projection((1, 1, 2)) 

785 Point3D(1, 1, 0) 

786 """ 

787 rv = Point(pt, dim=3) 

788 if rv in self: 

789 return rv 

790 return self.intersection(Line3D(rv, rv + Point3D(self.normal_vector)))[0] 

791 

792 def random_point(self, seed=None): 

793 """ Returns a random point on the Plane. 

794 

795 Returns 

796 ======= 

797 

798 Point3D 

799 

800 Examples 

801 ======== 

802 

803 >>> from sympy import Plane 

804 >>> p = Plane((1, 0, 0), normal_vector=(0, 1, 0)) 

805 >>> r = p.random_point(seed=42) # seed value is optional 

806 >>> r.n(3) 

807 Point3D(2.29, 0, -1.35) 

808 

809 The random point can be moved to lie on the circle of radius 

810 1 centered on p1: 

811 

812 >>> c = p.p1 + (r - p.p1).unit 

813 >>> c.distance(p.p1).equals(1) 

814 True 

815 """ 

816 if seed is not None: 

817 rng = random.Random(seed) 

818 else: 

819 rng = random 

820 params = { 

821 x: 2*Rational(rng.gauss(0, 1)) - 1, 

822 y: 2*Rational(rng.gauss(0, 1)) - 1} 

823 return self.arbitrary_point(x, y).subs(params) 

824 

825 def parameter_value(self, other, u, v=None): 

826 """Return the parameter(s) corresponding to the given point. 

827 

828 Examples 

829 ======== 

830 

831 >>> from sympy import pi, Plane 

832 >>> from sympy.abc import t, u, v 

833 >>> p = Plane((2, 0, 0), (0, 0, 1), (0, 1, 0)) 

834 

835 By default, the parameter value returned defines a point 

836 that is a distance of 1 from the Plane's p1 value and 

837 in line with the given point: 

838 

839 >>> on_circle = p.arbitrary_point(t).subs(t, pi/4) 

840 >>> on_circle.distance(p.p1) 

841 1 

842 >>> p.parameter_value(on_circle, t) 

843 {t: pi/4} 

844 

845 Moving the point twice as far from p1 does not change 

846 the parameter value: 

847 

848 >>> off_circle = p.p1 + (on_circle - p.p1)*2 

849 >>> off_circle.distance(p.p1) 

850 2 

851 >>> p.parameter_value(off_circle, t) 

852 {t: pi/4} 

853 

854 If the 2-value parameter is desired, supply the two 

855 parameter symbols and a replacement dictionary will 

856 be returned: 

857 

858 >>> p.parameter_value(on_circle, u, v) 

859 {u: sqrt(10)/10, v: sqrt(10)/30} 

860 >>> p.parameter_value(off_circle, u, v) 

861 {u: sqrt(10)/5, v: sqrt(10)/15} 

862 """ 

863 if not isinstance(other, GeometryEntity): 

864 other = Point(other, dim=self.ambient_dimension) 

865 if not isinstance(other, Point): 

866 raise ValueError("other must be a point") 

867 if other == self.p1: 

868 return other 

869 if isinstance(u, Symbol) and v is None: 

870 delta = self.arbitrary_point(u) - self.p1 

871 eq = delta - (other - self.p1).unit 

872 sol = solve(eq, u, dict=True) 

873 elif isinstance(u, Symbol) and isinstance(v, Symbol): 

874 pt = self.arbitrary_point(u, v) 

875 sol = solve(pt - other, (u, v), dict=True) 

876 else: 

877 raise ValueError('expecting 1 or 2 symbols') 

878 if not sol: 

879 raise ValueError("Given point is not on %s" % func_name(self)) 

880 return sol[0] # {t: tval} or {u: uval, v: vval} 

881 

882 @property 

883 def ambient_dimension(self): 

884 return self.p1.ambient_dimension