{"id":352507,"date":"2024-05-20T22:09:59","date_gmt":"2024-05-20T22:09:59","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=352507"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-05-20T22:09:59","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=352507","title":{"rendered":"<span>\u0426\u0438\u0440\u043a\u0443\u043b\u044c \u0438 \u043b\u0438\u043d\u0435\u0439\u043a\u0430. \u0427\u0430\u0441\u0442\u044c 1<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! <\/p>\n<p>\u041a\u0430\u043a-\u0442\u043e \u0440\u0430\u0437 \u043c\u043d\u0435 \u0437\u0430\u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u0446\u0438\u0440\u043a\u0443\u043b\u0435\u043c \u0438 \u043b\u0438\u043d\u0435\u0439\u043a\u043e\u0439. \u041d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0433\u0443\u0433\u043b\u0438\u0432, \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u043b, \u0447\u0442\u043e \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c <a href=\"https:\/\/en.wikipedia.org\/wiki\/Compass_(drawing_tool)\" rel=\"noopener noreferrer nofollow\"><em>compass<\/em> <\/a>\u044d\u0442\u043e<em> <\/em>\u0435\u0449\u0451 \u0438 \u0446\u0438\u0440\u043a\u0443\u043b\u044c, \u0438 \u0447\u0442\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0433\u043e\u0442\u043e\u0432\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0435\u0442. <\/p>\n<p>\u0412\u0441\u0451 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0435 \u0432\u044b\u043b\u0438\u043b\u043e\u0441\u044c \u0432 \u044d\u0442\u0443 \u0441\u0442\u0430\u0442\u044c\u044e.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/553\/56e\/032\/55356e032781c60391a199270401133d.gif\" alt=\"\u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438\" title=\"\u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438\" width=\"640\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/553\/56e\/032\/55356e032781c60391a199270401133d.gif\"\/><\/p>\n<div><figcaption>\u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438<\/figcaption><\/div>\n<\/figure>\n<h2>\u041a\u043e\u0440\u043e\u0442\u043a\u043e\u0435 \u043f\u0440\u0435\u0434\u0438\u0441\u043b\u043e\u0432\u0438\u0435 <\/h2>\n<p>\u0421\u0442\u0430\u0442\u044c\u044e \u044f \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u043b \u043d\u0430 \u0434\u0432\u0435 \u0447\u0430\u0441\u0442\u0438. \u0412\u00a0\u043f\u0435\u0440\u0432\u043e\u0439\u00a0\u2014 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0438 \u043b\u0438\u043d\u0435\u0439\u043a\u0438, \u0430 \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u0439 \u0443\u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0445 \u043a\u043b\u0430\u0441\u0441\u043e\u0432.<\/p>\n<p>\u0421\u0440\u0430\u0437\u0443 \u043f\u0440\u043e\u044f\u0441\u043d\u044e \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b. \u041f\u0438\u0441\u0430\u0442\u044c \u044f \u0431\u0443\u0434\u0443 \u043d\u0430\u00a0python, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e\u00a0\u043d\u0443\u0436\u043d\u0430\u044f \u043c\u043d\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f\u00a0\u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 (\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430\u00a0\u043d\u0435\u0451 \u0432\u00a0\u043a\u043e\u043d\u0446\u0435) \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430 \u043d\u0430\u00a0\u043d\u0451\u043c. \u0415\u0449\u0451 \u044f \u043d\u0435\u00a0\u0441\u0442\u0430\u043b \u0438\u0434\u0442\u0438 \u043f\u043e\u00a0\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u043c\u0443 \u0448\u0430\u0431\u043b\u043e\u043d\u0443, \u043a\u043e\u0433\u0434\u0430 \u0434\u043b\u044f\u00a0\u0442\u043e\u0447\u0435\u043a \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430. \u041e\u043f\u044f\u0442\u044c \u0442\u0430\u043a\u0438 \u0438\u0437\u2011\u0437\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f\u00a0\u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438, \u0432\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432\u00a0\u043c\u0430\u0441\u0441\u0438\u0432\u0435 numpy.<\/p>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043f\u043e\u043a\u0430 \u0447\u0442\u043e \u043c\u043d\u0435 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e python \u0438 numpy.<\/p>\n<h2>\u0426\u0438\u0440\u043a\u0443\u043b\u044c<\/h2>\n<p>\u041f\u043e\u0434 \u0446\u0438\u0440\u043a\u0443\u043b\u0435\u043c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c \u0442\u0440\u0438 \u0442\u043e\u0447\u043a\u0438: <code>left<\/code> , <code>right<\/code> \u0438 <code>center<\/code>. \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0446\u0438\u0440\u043a\u0443\u043b\u044c \u043c\u043e\u0436\u043d\u043e \u0438 \u043f\u043e-\u0434\u0440\u0443\u0433\u043e\u043c\u0443, \u043d\u043e \u044d\u0442\u043e\u0442 \u0441\u043f\u043e\u0441\u043e\u0431 \u043c\u043d\u0435 \u043a\u0430\u0436\u0435\u0442\u0441\u044f \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u043c. <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/998\/360\/ddf\/998360ddfc55726a8acc1f352401b7fa.gif\" width=\"640\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/998\/360\/ddf\/998360ddfc55726a8acc1f352401b7fa.gif\"\/><\/figure>\n<p>\u0422\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code> \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0440\u0430\u0432\u043d\u043e\u0443\u0434\u0430\u043b\u0435\u043d\u044b \u043e\u0442 \u0442\u043e\u0447\u043a\u0438 <code>center<\/code> \u0438 \u0432\u0441\u0435 \u0442\u0440\u0438 \u0442\u043e\u0447\u043a\u0438 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u043b\u0435\u0436\u0430\u0442\u044c \u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u043f\u0440\u044f\u043c\u043e\u0439. \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u044d\u0442\u043e \u0443\u0441\u043b\u043e\u0432\u0438\u0435, \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0434\u043b\u0438\u043d\u044b \u043e\u0442\u0440\u0435\u0437\u043a\u043e\u0432 <code>left<\/code>, <code>center<\/code> \u0438 <code>right<\/code>, <code>center<\/code>, \u0430 \u0442\u0430\u043a\u0436\u0435 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%9F%D1%81%D0%B5%D0%B2%D0%B4%D0%BE%D1%81%D0%BA%D0%B0%D0%BB%D1%8F%D1%80%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5\" rel=\"noopener noreferrer nofollow\">\u043f\u0441\u0435\u0432\u0434\u043e\u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/a> \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0445 \u043f\u043e \u0442\u043e\u0447\u043a\u0430\u043c <code>left<\/code>, <code>right<\/code> \u0438 <code>left<\/code>, <code>center<\/code>.<\/p>\n<p>\u041a\u043e\u0440\u043e\u0442\u043a\u043e \u043d\u0430\u043f\u043e\u043c\u043d\u044e, \u043f\u0441\u0435\u0432\u0434\u043e\u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u0430\u043a<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"a \\vee b = \\begin{vmatrix} a_1 &amp; a_2 \\\\ b_1  &amp; b_2\\end{vmatrix} = a_1b_2-a_2b_2,\" alt=\"a \\vee b = \\begin{vmatrix} a_1 &amp; a_2 \\\\ b_1  &amp; b_2\\end{vmatrix} = a_1b_2-a_2b_2,\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/053\/bfc\/fd1\/053bfcfd1c732eef385e5127e4d7b4e2.svg\" width=\"266\" height=\"49\"\/><\/p>\n<p>\u0438 \u043e\u043d\u043e \u0440\u0430\u0432\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0443\u043b\u044e \u0442\u043e\u0433\u0434\u0430 \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u044b \u043a\u043e\u043b\u043b\u0438\u043d\u0435\u0430\u0440\u043d\u044b. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043f\u043e \u0437\u043d\u0430\u043a\u0443 \u043c\u043e\u0436\u043d\u043e \u0441\u0443\u0434\u0438\u0442\u044c \u043e \u0432\u0437\u0430\u0438\u043c\u043d\u043e\u043c \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7dd\/bd5\/b34\/7ddbd5b345e49dee004af9f10e693c24.gif\" alt=\"\u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438 a \u0438 b \u043c\u0435\u043d\u044c\u0448\u0435 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432, \u0442\u043e \u043f\u0441\u0435\u0432\u0434\u043e\u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435, \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435, \u0435\u0441\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u0435, \u0442\u043e \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \" title=\"\u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438 a \u0438 b \u043c\u0435\u043d\u044c\u0448\u0435 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432, \u0442\u043e \u043f\u0441\u0435\u0432\u0434\u043e\u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435, \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435, \u0435\u0441\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u0435, \u0442\u043e \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \" width=\"640\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7dd\/bd5\/b34\/7ddbd5b345e49dee004af9f10e693c24.gif\"\/><\/p>\n<div><figcaption>\u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438 a \u0438 b \u043c\u0435\u043d\u044c\u0448\u0435 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432, \u0442\u043e \u043f\u0441\u0435\u0432\u0434\u043e\u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435, \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435, \u0435\u0441\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u0435, \u0442\u043e \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 <\/figcaption><\/div>\n<\/figure>\n<p>\u041f\u043e \u0438\u0442\u043e\u0433\u0443, \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043a\u043b\u0430\u0441\u0441\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a.<\/p>\n<pre><code class=\"python\">class Compass:     def __init__(         self,         left: np.ndarray,         right: np.ndarray,         center: np.ndarray     ):         det = np.linalg.det(np.vstack((center - left, center - right)))                  if np.isclose(det, 0):             raise ValueError(\"collinear points\")         if np.isclose(                 np.linalg.norm(center - left), np.linalg.norm(center - right)             ):                 raise ValueError(\"invalid center\")                  self.left = left         self.right = right         self.center = center<\/code><\/pre>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0435\u0449\u0451 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430:<\/p>\n<ul>\n<li>\n<p><code>radius<\/code>\u00a0\u2014 \u0440\u0430\u0434\u0438\u0443\u0441 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0446\u0438\u0440\u043a\u0443\u043b\u044f.<\/p>\n<\/li>\n<li>\n<p><code>length<\/code>\u00a0\u2014 \u0434\u043b\u0438\u043d\u0430 \u043d\u043e\u0436\u043a\u0438 \u0446\u0438\u0440\u043a\u0443\u043b\u044f.<\/p>\n<\/li>\n<li>\n<p><code>outer_center<\/code> \u2014 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442, \u0433\u0434\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f <code>center<\/code> \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>. \u0415\u0441\u043b\u0438 <code>center<\/code> \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0441\u043b\u0435\u0432\u0430 \u043e\u0442 \u043f\u0440\u044f\u043c\u043e\u0439 \u0432 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043e\u0442 <code>left<\/code> \u043a <code>right<\/code>, \u0442\u043e <code>outer_center = True<\/code>, \u0438\u043d\u0430\u0447\u0435 <code>outer_center = False<\/code>. \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u044d\u0442\u043e \u0442\u0430\u043a\u0436\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0441\u0435\u0432\u0434\u043e\u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">@property def outer_center(self):     det = np.linalg.det(np.vstack((self.right - self.left, self.center - self.left)))     return True if det > 0 else False  @property def length(self):     return np.linalg.norm(self.center - self.left)  @property def radius(self):     return np.linalg.norm(self.right - self.left)<\/code><\/pre>\n<h3>\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b<\/h3>\n<p>\u041f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442\u044c \u043a \u043c\u0435\u0442\u043e\u0434\u0430\u043c, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u043c \u0437\u0430 \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0446\u0438\u0438 \u0441 \u0446\u0438\u0440\u043a\u0443\u043b\u0435\u043c, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b.<\/p>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439\u00a0\u2014 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043a \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0437\u044c\u043c\u0451\u043c \u0432\u0435\u043a\u0442\u043e\u0440, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u043f\u043e \u0442\u043e\u0447\u043a\u0430\u043c <code>left<\/code>, <code>right<\/code>. \u041d\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e \u0438 \u043f\u043e\u043c\u0435\u043d\u044f\u0435\u043c \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u043c\u0435\u0441\u0442\u0430\u043c\u0438, \u0443\u043c\u043d\u043e\u0436\u0438\u0432 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043e\u0434\u043d\u0443 \u0438\u0437 \u043d\u0438\u0445 \u043d\u0430 \u043c\u0438\u043d\u0443\u0441 \u0435\u0434\u0438\u043d\u0438\u0446\u0443.    <\/p>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442<\/summary>\n<div class=\"spoiler__content\">\n<p>\u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u0435\u0441\u043b\u0438 \u0443 \u0435\u0434\u0438\u043d\u0438\u0447\u043d\u043e\u0433\u043e \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u043c\u0435\u0441\u0442\u0430\u043c\u0438 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0438 \u0443\u043c\u043d\u043e\u0436\u0438\u0442\u044c \u043e\u0434\u043d\u0443 \u0438\u0437 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442 \u043d\u0430 \u043c\u0438\u043d\u0443\u0441 \u0435\u0434\u0438\u043d\u0438\u0446\u0443, \u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440 \u0431\u0443\u0434\u0435\u0442 \u0435\u0434\u0438\u043d\u0438\u0447\u043d\u044b\u043c, \u0447\u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e \u0444\u043e\u0440\u043c\u0443\u043b\u0435 \u0434\u043b\u0438\u043d\u044b \u0432\u0435\u043a\u0442\u043e\u0440\u0430.<\/p>\n<p>\u0412\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440 \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u043f\u0435\u043d\u0434\u0438\u043a\u0443\u043b\u044f\u0440\u0435\u043d \u043a \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u043c\u0443, \u0442\u0430\u043a \u043a\u0430\u043a \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0440\u0430\u0432\u043d\u043e \u043d\u0443\u043b\u044e, \u0447\u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435\u043c \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f. <\/p>\n<p>\u0415\u0441\u0442\u044c \u0435\u0449\u0451 \u0438 \u0441\u043e\u0432\u0441\u0435\u043c \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0435 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u0435, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u044b\u043c\u0438 \u0447\u0438\u0441\u043b\u0430\u043c\u0438 \u0438 \u0438\u0445 \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c. \u041f\u043e \u0441\u0443\u0442\u0438, \u043c\u044b \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0432\u0435\u043a\u0442\u043e\u0440\u0443 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u0438 \u0443\u043c\u043d\u043e\u0436\u0430\u0435\u043c \u0435\u0433\u043e \u043d\u0430 \u043c\u043d\u0438\u043c\u0443\u044e \u0435\u0434\u0438\u043d\u0438\u0446\u0443, \u0442\u0435\u043c \u0441\u0430\u043c\u044b\u043c \u043f\u043e\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0435\u0433\u043e \u043d\u0430 90 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432.<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>outer<\/code>, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u0439 \u0437\u0430 \u0432\u044b\u0431\u043e\u0440 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0435\u043a\u0442\u043e\u0440\u0430. \u0415\u0441\u043b\u0438 <code>outer = True<\/code>, \u0442\u043e \u0432\u0435\u043a\u0442\u043e\u0440 \u043d\u043e\u0440\u043c\u0430\u043b\u0438 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d \u0432\u043b\u0435\u0432\u043e \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>, \u0432 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u043f\u0440\u0430\u0432\u043e. <\/p>\n<pre><code class=\"python\">def _normal_vec(self, outer: bool):     vec = (self.right - self.left) \/ self.radius     normal_vec = np.array([-vec[1], vec[0]])     det = np.linalg.det(np.vstack((vec, normal_vec)))          if outer:         return np.sign(det) * normal_vec          return -np.sign(det) * normal_vec<\/code><\/pre>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439\u00a0\u2014 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u0443\u0433\u043b\u0430 \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438. \u0412\u0430\u0436\u043d\u043e, \u0447\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u043d\u0443\u0436\u0435\u043d \u043d\u0435 \u043d\u0430\u0438\u043c\u0435\u043d\u044c\u0448\u0438\u0439 \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438, \u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439. \u0422\u043e \u0435\u0441\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0443\u0433\u043e\u043b, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0432 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u043c \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0438 (\u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0438) \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u0441\u043e\u0432\u043f\u0430\u043b \u0441\u043e \u0432\u0442\u043e\u0440\u044b\u043c.  <\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f <a href=\"https:\/\/numpy.org\/doc\/stable\/reference\/generated\/numpy.arctan2\" rel=\"noopener noreferrer nofollow\">arctan2<\/a>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u043e\u0441\u044c\u044e<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"x\" alt=\"x\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/758\/630\/b9a\/758630b9ab588c448320de8f194a4f16.svg\" width=\"11\" height=\"12\"\/>\u0438 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u043c \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u043e\u0442 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"-180^\\circ\" alt=\"-180^\\circ\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5ab\/194\/522\/5ab1945226052c2c48f563b6409e8541.svg\" width=\"53\" height=\"19\"\/>\u0434\u043e <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"180^\\circ\" alt=\"180^\\circ\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b3d\/343\/049\/b3d34304941a5114a4ac648eddfb7cbc.svg\" width=\"38\" height=\"18\"\/>. <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 \u0438\u0441\u043a\u043e\u043c\u044b\u0439 \u0443\u0433\u043e\u043b, \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0434\u043b\u044f \u0434\u0432\u0443\u0445 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 \u0443\u0433\u043b\u044b \u0441 \u043e\u0441\u044c\u044e <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"x\" alt=\"x\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/002\/e97\/a55\/002e97a55de5f598fae044a8d55bc25e.svg\" width=\"11\" height=\"12\"\/>\u0438 \u0432\u0437\u044f\u0442\u044c \u0438\u0445 \u0440\u0430\u0437\u043d\u043e\u0441\u0442\u044c. \u0417\u0434\u0435\u0441\u044c \u0432\u0430\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c, \u043a\u0430\u043a\u043e\u0439 \u0443\u0433\u043e\u043b \u0438\u0437 \u043a\u0430\u043a\u043e\u0433\u043e \u0432\u044b\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u043c \u0441\u0447\u0438\u0442\u0430\u0442\u044c, \u0447\u0442\u043e \u043f\u043e\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u0435\u043a\u0442\u043e\u0440 <code>vec1<\/code> \u043a \u0432\u0435\u043a\u0442\u043e\u0440\u0443 <code>vec2<\/code>, \u0442\u043e \u0435\u0441\u0442\u044c \u0438\u0437 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0443\u0433\u043b\u0430 \u0432\u044b\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u044b\u0439.<\/p>\n<pre><code class=\"python\">def _angle_vec(self, vec1: np.ndarray, vec2: np.ndarray):     return np.arctan2(vec2[1], vec2[0]) - np.arctan2(vec1[1], vec1[0])<\/code><\/pre>\n<h3>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0446\u0435\u043d\u0442\u0440\u0430<\/h3>\n<p>\u0427\u0430\u0441\u0442\u043e \u043d\u0435\u0443\u0434\u043e\u0431\u043d\u043e \u0438 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0442\u043e\u0447\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c, \u0433\u0434\u0435  \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0442\u043e\u0447\u043a\u0430 <code>center<\/code>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0435\u0451 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u043f\u043e \u0442\u043e\u0447\u043a\u0430\u043c <code>left<\/code> \u0438 <code>right<\/code>. \u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>height<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0442\u043e\u0447\u043a\u0438 <code>center<\/code> \u0434\u043e \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>, \u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>outer<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0442\u043e, \u0433\u0434\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f <code>center<\/code> \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u044d\u0442\u043e\u0439 \u043f\u0440\u044f\u043c\u043e\u0439. <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ecd\/9d9\/c86\/ecd9d9c86b895feb64ef9c3fd145bb36.gif\" width=\"640\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ecd\/9d9\/c86\/ecd9d9c86b895feb64ef9c3fd145bb36.gif\"\/><\/figure>\n<p>\u0412\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0442\u0435\u043c, \u0447\u0442\u043e \u043d\u0430 \u0442\u0440\u0435\u0445 \u0442\u043e\u0447\u043a\u0430\u0445 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0440\u0430\u0432\u043d\u043e\u0431\u0435\u0434\u0440\u0435\u043d\u043d\u044b\u0439 \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0430 \u0432 \u0440\u0430\u0432\u043d\u043e\u0431\u0435\u0434\u0440\u0435\u043d\u043d\u043e\u043c \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0435 \u0432\u044b\u0441\u043e\u0442\u0430, \u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u043d\u0430\u044f \u043a \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u044e, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438 \u043c\u0435\u0434\u0438\u0430\u043d\u043e\u0439. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0441\u0435\u0440\u0435\u0434\u0438\u043d\u0443 \u043e\u0442\u0440\u0435\u0437\u043a\u0430 <code>left<\/code>, <code>right<\/code> \u0438 \u043e\u0442\u043b\u043e\u0436\u0438\u0442\u044c \u043e\u0442 \u043d\u0435\u0451 \u0432\u0435\u043a\u0442\u043e\u0440 \u043d\u043e\u0440\u043c\u0430\u043b\u0438, \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u043d\u044b\u0439 \u043d\u0430 <code>height<\/code>. \u041a\u043e\u043d\u0435\u0446 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u0442\u043e\u0447\u043a\u043e\u0439 <code>center<\/code>.<\/p>\n<pre><code class=\"python\">def init_center(self, height: float, outer: bool):     self.center = 0.5 * (self.right + self.left) + height * self._normal_vec(outer)<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043a\u043b\u0430\u0441\u0441\u0430.<\/p>\n<pre><code class=\"python\">class Compass:     def __init__(         self,         left: np.ndarray,         right: np.ndarray,         center: np.ndarray = None,         outer_center: bool = True,     ):         self.left = left         self.right = right         self.center = center          if center is None:             self.init_center(self.radius, outer_center)         else:             det = np.linalg.det(np.vstack((center - left, center - right)))              if np.isclose(                 np.linalg.norm(center - left), np.linalg.norm(center - right)             ):                 raise ValueError(\"invalid center\")             if np.isclose(det, 0):                 raise ValueError(\"collinear points\")<\/code><\/pre>\n<h3>\u041f\u0435\u0440\u0435\u0432\u043e\u0440\u043e\u0442 <\/h3>\n<p>\u0414\u043b\u044f \u043f\u0435\u0440\u0435\u0432\u043e\u0440\u043e\u0442\u0430 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u0440\u0430\u0437\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0443 <code>center<\/code> \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>. \u0421\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u0445\u043e\u0434\u044f \u0438\u0437 \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0441\u043e\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439. \u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c, \u0447\u0442\u043e \u0446\u0438\u0440\u043a\u0443\u043b\u044c \u0443\u0436\u0435 \u043f\u0435\u0440\u0435\u0432\u0451\u0440\u043d\u0443\u0442. \u0422\u043e\u0433\u0434\u0430 \u043d\u0430 \u0442\u0440\u0435\u0445 \u0442\u043e\u0447\u043a\u0430\u0445 <code>left<\/code>, <code>right<\/code>, <code>center<\/code> \u0438 \u0442\u043e\u0447\u043a\u0435 <code>new_center<\/code>, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0439 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0432\u043e\u0440\u043e\u0442\u0430, \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0440\u043e\u043c\u0431. \u042d\u0442\u043e \u0434\u0430\u0451\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435. <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/89b\/27c\/63c\/89b27c63ce35b881a111bcb56158b9c2.gif\" width=\"640\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/89b\/27c\/63c\/89b27c63ce35b881a111bcb56158b9c2.gif\"\/><\/figure>\n<p>\u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u043f\u0440\u043e\u0442\u0438\u0432\u043e\u043f\u043e\u043b\u043e\u0436\u043d\u044b\u0435 \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u0440\u0430\u0432\u043d\u044b \u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b, \u0442\u043e \u0435\u0441\u0442\u044c \u0432\u0435\u043a\u0442\u043e\u0440, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u043f\u043e \u0442\u043e\u0447\u043a\u0430\u043c <code>left <\/code>\u0438 <code>new_center<\/code>, \u0440\u0430\u0432\u0435\u043d \u0432\u0435\u043a\u0442\u043e\u0440\u0443, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u043c\u0443 \u043f\u043e \u0442\u043e\u0447\u043a\u0430\u043c <code>center<\/code> \u0438 <code>right<\/code>. <\/p>\n<p>\u0412\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u0434\u0438\u0430\u0433\u043e\u043d\u0430\u043b\u0438 \u0440\u043e\u043c\u0431\u0430 \u043f\u0435\u0440\u043f\u0435\u043d\u0434\u0438\u043a\u0443\u043b\u044f\u0440\u043d\u044b \u0438 \u0434\u0435\u043b\u044f\u0442\u0441\u044f \u043f\u043e\u043f\u043e\u043b\u0430\u043c \u0432 \u0442\u043e\u0447\u043a\u0435 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u044f, \u0442\u043e \u0435\u0441\u0442\u044c \u0442\u043e\u0447\u043a\u0430 <code>new_center<\/code> \u0441\u0438\u043c\u043c\u0435\u0442\u0440\u0438\u0447\u043d\u0430 \u0442\u043e\u0447\u043a\u0435 <code>center<\/code> \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 <code>new_center<\/code>, \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u043b\u043e\u0436\u0438\u0442\u044c \u0432\u0435\u043a\u0442\u043e\u0440 <code>center<\/code>, <code>right<\/code> \u043e\u0442 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code>, \u0438 \u043a\u043e\u043d\u0435\u0446 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u0442\u043e\u0447\u043a\u043e\u0439 <code>new_center<\/code>.<\/p>\n<pre><code class=\"python\">def flip(self):     self.center = self.right + self.left - self.center<\/code><\/pre>\n<h3>\u041f\u043e\u0432\u043e\u0440\u043e\u0442\u044b<\/h3>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u043f\u0438\u0448\u0435\u043c \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 <code>point<\/code>.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/937\/64a\/a07\/93764aa07616be106a88af095790efe7.gif\" alt=\"\u041f\u043e\u0432\u043e\u0440\u043e\u0442 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0442\u043e\u0447\u043a\u0438 center\" title=\"\u041f\u043e\u0432\u043e\u0440\u043e\u0442 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0442\u043e\u0447\u043a\u0438 center\" width=\"640\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/937\/64a\/a07\/93764aa07616be106a88af095790efe7.gif\"\/><\/p>\n<div><figcaption>\u041f\u043e\u0432\u043e\u0440\u043e\u0442 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0442\u043e\u0447\u043a\u0438 center<\/figcaption><\/div>\n<\/figure>\n<p>\u0414\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u044d\u0442\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c. \u041d\u0443\u0436\u043d\u043e \u0441\u0434\u0432\u0438\u043d\u0443\u0442\u044c \u0432\u0441\u0435 \u0442\u043e\u0447\u043a\u0438 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043d\u0430 <code>-point<\/code>, \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0432\u0448\u0438\u0435\u0441\u044f \u0442\u043e\u0447\u043a\u0438 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0430\u0447\u0430\u043b\u0430 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442, \u0442\u043e \u0435\u0441\u0442\u044c \u0443\u043c\u043d\u043e\u0436\u0438\u0442\u044c \u0441\u043b\u0435\u0432\u0430 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"M =\\begin{pmatrix}   \\cos{\\alpha} &amp; -\\sin{\\alpha} \\\\   \\sin{\\alpha} &amp; \\cos{\\alpha} \\end{pmatrix},\" alt=\"M =\\begin{pmatrix}   \\cos{\\alpha} &amp; -\\sin{\\alpha} \\\\   \\sin{\\alpha} &amp; \\cos{\\alpha} \\end{pmatrix},\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/26c\/94a\/68b\/26c94a68b7978d17945be173427433c6.svg\" width=\"207\" height=\"49\"\/><\/p>\n<p>\u0438 \u0441\u0434\u0432\u0438\u043d\u0443\u0442\u044c \u0438\u0445 \u043e\u0431\u0440\u0430\u0442\u043d\u043e. \u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0432\u0441\u0435 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u0432\u0438\u0434\u0435 \u0444\u043e\u0440\u043c\u0443\u043b\u044b, \u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043c<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"\\begin{pmatrix}x' \\\\ y' \\end{pmatrix} = \\begin{pmatrix}   \\cos{\\alpha} &amp; -\\sin{\\alpha} \\\\   \\sin{\\alpha} &amp; \\cos{\\alpha} \\end{pmatrix} \\left( \\begin{pmatrix}x\\\\ y \\end{pmatrix} - \\begin{pmatrix}p_x \\\\ p_y \\end{pmatrix}\\right) + \\begin{pmatrix}p_x \\\\ p_y \\end{pmatrix}.\" alt=\"\\begin{pmatrix}x' \\\\ y' \\end{pmatrix} = \\begin{pmatrix}   \\cos{\\alpha} &amp; -\\sin{\\alpha} \\\\   \\sin{\\alpha} &amp; \\cos{\\alpha} \\end{pmatrix} \\left( \\begin{pmatrix}x\\\\ y \\end{pmatrix} - \\begin{pmatrix}p_x \\\\ p_y \\end{pmatrix}\\right) + \\begin{pmatrix}p_x \\\\ p_y \\end{pmatrix}.\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e42\/17b\/e5b\/e4217be5b85b674668c6e2389d7310d8.svg\" width=\"470\" height=\"51\"\/><\/p>\n<pre><code class=\"python\">def rotate_about_point(self, angle: float, point: np.ndarray):     rot_mat = np.array([         [np.cos(angle), -np.sin(angle)],          [np.sin(angle), np.cos(angle)]     ])     self.left = np.matmul(rot_mat, self.left - point) + point     self.right = np.matmul(rot_mat, self.right - point) + point     self.center = np.matmul(rot_mat, self.center - point) + point<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f. <\/p>\n<pre><code class=\"python\"> def rotate_about_center(self, angle: float):     self.rotate_about_point(angle, self.center)  def rotate_about_left(self, angle: float):     self.rotate_about_point(angle, self.left)  def rotate_about_right(self, angle: float):     self.rotate_about_point(angle, self.right)<\/code><\/pre>\n<p>\u041d\u043e \u0435\u0441\u0442\u044c \u0438 \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u0442\u0438\u043f \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u043e\u0432, \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043e\u0434\u043d\u0443 \u0438\u0437 \u043d\u043e\u0436\u0435\u043a \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0430 \u043f\u043e\u043f\u0430\u043b\u0430 \u0432 \u0442\u043e\u0447\u043a\u0443 <code>point<\/code>.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8bb\/dea\/1c9\/8bbdea1c9966f214d97cd96af7e1ba3b.gif\" alt=\"\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 \u043f\u0435\u0440\u0432\u043e\u0439 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438, \u0433\u0434\u0435 \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f\" title=\"\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 \u043f\u0435\u0440\u0432\u043e\u0439 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438, \u0433\u0434\u0435 \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f\" width=\"640\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8bb\/dea\/1c9\/8bbdea1c9966f214d97cd96af7e1ba3b.gif\"\/><\/p>\n<div><figcaption>\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 \u043f\u0435\u0440\u0432\u043e\u0439 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438, \u0433\u0434\u0435 \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f<\/figcaption><\/div>\n<\/figure>\n<p>\u0412\u0430\u0436\u043d\u043e, \u0447\u0442\u043e \u044d\u0442\u043e \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u043d\u043e\u0436\u043a\u0430 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u043f\u0430\u043b\u0430 \u0432 \u0442\u043e\u0447\u043a\u0443 <code>point<\/code>.<\/p>\n<pre><code class=\"python\">def rotate_left_to(self, point: np.ndarray):     angle = self._angle_vec(self.left - self.right, point - self.right)     self.rotate_about_right(angle)      if not np.allclose(self.left, point):         raise ValueError(\"can't rotate to chosen point\")  def rotate_right_to(self, point: np.ndarray):     angle = self._angle_vec(self.right - self.left, point - self.left)     self.rotate_about_left(angle)      if not np.allclose(self.right, point):         raise ValueError(\"can't rotate to chosen point\")<\/code><\/pre>\n<h3>\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0434\u0438\u0443\u0441\u0430<\/h3>\n<p>\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0440\u0430\u0434\u0438\u0443\u0441 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043c\u043e\u0436\u043d\u043e \u0434\u0432\u0443\u043c\u044f \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c\u0438. \u041f\u0435\u0440\u0432\u044b\u0439, \u043a\u043e\u0433\u0434\u0430 \u043e\u0434\u043d\u0430 \u0438\u0437 \u043d\u043e\u0436\u0435\u043a \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u0442\u0441\u044f, \u0430 \u0434\u0440\u0443\u0433\u0430\u044f \u0441\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u0434\u043e \u043d\u0443\u0436\u043d\u043e\u0433\u043e \u0440\u0430\u0434\u0438\u0443\u0441\u0430. \u0412\u0442\u043e\u0440\u043e\u0439, \u043a\u043e\u0433\u0434\u0430 \u0434\u0432\u0435 \u043d\u043e\u0436\u043a\u0438 \u0441\u0434\u0432\u0438\u0433\u0430\u044e\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e. \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e4a\/aad\/470\/e4aaad470b3ae22038be27df3ca7d877.gif\" alt=\"\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0434\u0438\u0443\u0441\u0430 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0441 \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043b\u0435\u0432\u043e\u0439 \u043d\u043e\u0436\u043a\u043e\u0439\" title=\"\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0434\u0438\u0443\u0441\u0430 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0441 \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043b\u0435\u0432\u043e\u0439 \u043d\u043e\u0436\u043a\u043e\u0439\" width=\"640\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e4a\/aad\/470\/e4aaad470b3ae22038be27df3ca7d877.gif\"\/><\/p>\n<div><figcaption>\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0434\u0438\u0443\u0441\u0430 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0441 \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043b\u0435\u0432\u043e\u0439 \u043d\u043e\u0436\u043a\u043e\u0439<\/figcaption><\/div>\n<\/figure>\n<p>\u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u043d\u0443\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0430 \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0438 \u0432 \u043a\u0430\u043a\u043e\u043c \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u0441\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u043e\u0434\u043d\u0430 \u0438\u0437 \u0442\u043e\u0447\u0435\u043a. \u0414\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u0441\u0442\u0438 \u0431\u0443\u0434\u0435\u043c \u0441\u0447\u0438\u0442\u0430\u0442\u044c, \u0447\u0442\u043e \u0441\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u0447\u043a\u0430 <code>right<\/code>. \u0422\u043e\u0433\u0434\u0430 \u043e\u043d\u0430 \u0441\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u043a \u0442\u043e\u0447\u043a\u0435 <code>left<\/code>, \u0435\u0441\u043b\u0438 \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0430\u0434\u0438\u0443\u0441 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u043e\u0432\u043e\u0433\u043e. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u043d\u0430 \u0441\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code>. \u0412 \u043e\u0431\u043e\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u0442\u043e\u0447\u043a\u0430 <code>right<\/code> \u0441\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043c\u043e\u0434\u0443\u043b\u044c \u0440\u0430\u0437\u043d\u0438\u0446\u044b \u0440\u0430\u0434\u0438\u0443\u0441\u043e\u0432. \u0417\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0442\u0430\u043a<\/p>\n<pre><code class=\"python\">self.right += (new_radius \/ self.radius - 1) * (self.right - self.left)<\/code><\/pre>\n<p>\u0412\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u043d\u0443\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c, \u0433\u0434\u0435 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0442\u043e\u0447\u043a\u0430 <code>center<\/code> \u043f\u043e\u0441\u043b\u0435 \u0441\u0434\u0432\u0438\u0433\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0437\u043d\u0430\u0442\u044c, \u043d\u0430 \u043a\u0430\u043a\u043e\u043c \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u043e\u0442 \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>, \u043e\u043d\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c. \u0422\u0430\u043a \u043a\u0430\u043a \u043f\u0440\u0438 \u0441\u0434\u0432\u0438\u0433\u0435 \u0434\u043b\u0438\u043d\u0430 \u043d\u043e\u0436\u0435\u043a \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f, \u043e\u043f\u044f\u0442\u044c \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0442\u0435\u043c, \u0447\u0442\u043e \u043d\u0430 \u0442\u0440\u0435\u0445 \u0442\u043e\u0447\u043a\u0430\u0445 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0440\u0430\u0432\u043d\u043e\u0431\u0435\u0434\u0440\u0435\u043d\u043d\u044b\u0439 \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0438 \u0432\u044b\u0441\u043e\u0442\u0430 \u043a \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u044e \u0432 \u043d\u0435\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043c\u0435\u0434\u0438\u0430\u043d\u043e\u0439. \u0422\u043e\u0433\u0434\u0430 \u043f\u043e \u0442\u0435\u043e\u0440\u0435\u043c\u0435 \u041f\u0438\u0444\u0430\u0433\u043e\u0440\u0430 \u0432\u044b\u0441\u043e\u0442\u0430 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u0430\u043a<\/p>\n<pre><code class=\"python\">height = (self.length**2 - 0.25 * new_radius**2) ** 0.5<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0442\u043e\u0447\u043a\u0438 <code>center<\/code> \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430 <code>init_center<\/code>. <\/p>\n<p>\u041e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043b\u0438\u0448\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0432\u0438\u043d\u0443\u0442\u044c \u043d\u043e\u0436\u043a\u0443 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0440\u0430\u0434\u0438\u0443\u0441. \u041f\u0440\u0435\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 <code>center<\/code> \u043b\u0435\u0436\u0438\u0442 \u043d\u0430 \u0441\u0435\u0440\u0435\u0434\u0438\u043d\u0435 \u043e\u0442\u0440\u0435\u0437\u043a\u0430 <code>left<\/code>, <code>right<\/code>, \u0442\u043e \u0435\u0441\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0440\u0430\u0434\u0438\u0443\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043c\u0435\u043d\u044c\u0448\u0435 \u0443\u0434\u0432\u043e\u0435\u043d\u043d\u043e\u0439 \u0434\u043b\u0438\u043d\u044b \u043d\u043e\u0436\u043a\u0438 \u0446\u0438\u0440\u043a\u0443\u043b\u044f.<\/p>\n<pre><code class=\"python\">def change_radius_to_right(self, new_radius: float):     if new_radius >= 2 * self.length or new_radius &lt;= 0:         raise ValueError(\"invalid radius\")      height = (self.length**2 - 0.25 * new_radius**2) ** 0.5     self.right += (new_radius \/ self.radius - 1) * (self.right - self.left)     self.init_center(height, self.outer_center)  def change_radius_to_left(self, new_radius: float):     if new_radius >= 2 * self.length or new_radius &lt;= 0:         raise ValueError(\"invalid radius\")      height = (self.length**2 - 0.25 * new_radius**2) ** 0.5     self.left += (new_radius \/ self.radius - 1) * (self.left - self.right)     self.init_center(height, self.outer_center)<\/code><\/pre>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0441\u0434\u0432\u0438\u0433\u0430\u044e\u0442\u0441\u044f \u0434\u0432\u0435 \u043d\u043e\u0436\u043a\u0438 \u0446\u0438\u0440\u043a\u0443\u043b\u044f, \u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0432\u0441\u0451 \u0442\u043e\u0436\u0435 \u0441\u0430\u043c\u043e\u0435, \u043d\u043e \u0441\u0434\u0432\u0438\u0433\u0438 \u0431\u0443\u0434\u0443\u0442 \u043d\u0435 \u043d\u0430 \u043c\u043e\u0434\u0443\u043b\u044c \u0440\u0430\u0437\u043d\u043e\u0441\u0442\u0438 \u0440\u0430\u0434\u0438\u0443\u0441\u043e\u0432, \u0430 \u043d\u0430 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0443 \u0442\u043e\u0433\u043e \u0436\u0435 \u043c\u043e\u0434\u0443\u043b\u044f. <\/p>\n<pre><code class=\"python\">def change_radius(self, new_radius: float):     if new_radius >= 2 * self.length or new_radius &lt;= 0:         raise ValueError(\"invalid radius\")          height = (self.length**2 - 0.25 * new_radius**2) ** 0.5     radius = self.radius     vec = self.right - self.left     self.left -= 0.5 * (new_radius \/ radius - 1) * vec     self.right += 0.5 * (new_radius \/ radius - 1) * vec     self.init_center(height, self.outer_center)<\/code><\/pre>\n<h3>\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0443\u0433\u043b\u0430<\/h3>\n<p>\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0443\u0433\u043e\u043b \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043c\u043e\u0436\u043d\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0440\u0430\u0434\u0438\u0443\u0441\u0430 \u0438 \u0441\u0434\u0432\u0438\u0433\u0430 \u0446\u0438\u0440\u043a\u0443\u043b\u044f. \u041d\u043e \u044f \u0440\u0435\u0448\u0438\u043b \u043f\u043e\u0439\u0442\u0438 \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u043f\u0443\u0442\u0451\u043c.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/10c\/470\/385\/10c470385a61048fe4b85ede31d06336.gif\" width=\"640\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/10c\/470\/385\/10c470385a61048fe4b85ede31d06336.gif\"\/><\/figure>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u043c \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u043d\u043e\u0436\u043a\u0430\u043c\u0438 \u0446\u0438\u0440\u043a\u0443\u043b\u044f. \u0417\u0430\u043c\u0435\u0447\u0443, \u0447\u0442\u043e \u043e\u043d \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0442 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"0^\\circ\" alt=\"0^\\circ\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/111\/041\/ad8\/111041ad82938a7dae3135b04128318a.svg\" width=\"18\" height=\"18\"\/>\u0434\u043e <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"180^\\circ\" alt=\"180^\\circ\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/3f2\/b9f\/ff1\/3f2b9fff11c4b8ff3f6d9b7d79b056d0.svg\" width=\"38\" height=\"18\"\/>. \u0412 \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0439 \u0440\u0430\u0437 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0442\u0435\u043c, \u0447\u0442\u043e \u043d\u0430 \u0442\u043e\u0447\u043a\u0430\u0445 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0440\u0430\u0432\u043d\u043e\u0431\u0435\u0434\u0440\u0435\u043d\u043d\u044b\u0439 \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0438 \u0432\u044b\u0441\u043e\u0442\u0430 \u043a \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u044e \u0432 \u043d\u0451\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0431\u0438\u0441\u0441\u0435\u043a\u0442\u0440\u0438\u0441\u043e\u0439. \u041f\u043e\u044d\u0442\u043e\u043c\u0443, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043d\u0443\u0436\u043d\u044b\u0439 \u0443\u0433\u043e\u043b, \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043a\u0430\u0436\u0434\u0443\u044e \u0438\u0437 \u043d\u043e\u0436\u0435\u043a \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0442\u043e\u0447\u043a\u0438 <code>center<\/code> \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0443\u0433\u043e\u043b:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"\\gamma = \\frac{1}{2}(\\theta - \\alpha).\" alt=\"\\gamma = \\frac{1}{2}(\\theta - \\alpha).\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/3f4\/1ba\/413\/3f41ba4135f349bc0a548c63699c2af4.svg\" width=\"118\" height=\"42\"\/><\/p>\n<p>\u0417\u0434\u0435\u0441\u044c <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"\\theta\" alt=\"\\theta\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5a0\/d9e\/25e\/5a0d9e25e9280ed0cedc1aef22a85989.svg\" width=\"9\" height=\"17\"\/>&#8212; \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u043d\u043e\u0436\u043a\u0430\u043c\u0438 \u0446\u0438\u0440\u043a\u0443\u043b\u044f, \u0430 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"\\alpha\" alt=\"\\alpha\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/256\/0af\/659\/2560af659b473f826a9dc2772e6f3f73.svg\" width=\"12\" height=\"12\"\/>&#8212; \u0443\u0433\u043e\u043b, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0445\u043e\u0442\u0438\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c. \u0414\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0443\u0436\u043d\u044b \u0434\u0432\u0435 \u043c\u0430\u0442\u0440\u0438\u0446\u044b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u044e\u0442 \u043d\u0430 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0439 \u0443\u0433\u043e\u043b, \u043d\u043e \u0432 \u043f\u0440\u043e\u0442\u0438\u0432\u043e\u043f\u043e\u043b\u043e\u0436\u043d\u044b\u0445 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u0445. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0435 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u043d\u043e\u0436\u043a\u0438, \u043d\u0443\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0442\u043e\u0447\u043a\u0438 <code>center<\/code> \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>.<\/p>\n<p>\u0418 \u043d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0435\u043c \u043f\u0440\u043e \u0442\u043e, \u0447\u0442\u043e \u043d\u0435\u043b\u044c\u0437\u044f \u0432\u044b\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0438\u043b\u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043b\u043e\u0436\u0438\u0442\u044c \u0446\u0438\u0440\u043a\u0443\u043b\u044c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u043e\u0432\u044b\u0439 \u0443\u0433\u043e\u043b \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u0442 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"0^\\circ\" alt=\"0^\\circ\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4e6\/bde\/eca\/4e6bdeecaca4ef8c695659c258c70b25.svg\" width=\"18\" height=\"18\"\/>\u0434\u043e <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"180^\\circ.\" alt=\"180^\\circ.\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/074\/3fa\/311\/0743fa311805c9de82c948f3cfbfab03.svg\" width=\"43\" height=\"18\"\/><\/p>\n<pre><code class=\"python\">def change_angle(self, angle: float):     if angle >= np.pi or angle &lt;= 0:         raise ValueError(\"invalid angle\")          vec_left = self.left - self.center     vec_right = self.right - self.center     current_angle = self._angle_vec(vec_left, vec_right)     new_angle = 0.5 * (current_angle - angle)     rot_mat = np.array([         [np.cos(new_angle), -np.sin(new_angle)],         [np.sin(new_angle), np.cos(new_angle)],     ])          if self.outer_center:         self.left = np.matmul(rot_mat, vec_left) + self.center         self.right = np.matmul(rot_mat.transpose(), vec_right) + self.center     else:         self.left = np.matmul(rot_mat.transpose(), vec_left) + self.center         self.right = np.matmul(rot_mat, vec_right) + self.center<\/code><\/pre>\n<h3>\u0421\u0434\u0432\u0438\u0433 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0414\u043b\u044f \u0441\u0434\u0432\u0438\u0433\u0430 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0432\u0438\u043d\u0443\u0442\u044c \u043a\u0430\u0436\u0434\u0443\u044e \u0442\u043e\u0447\u043a\u0443 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043d\u0430 \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u0432\u0435\u043a\u0442\u043e\u0440 <code>vec<\/code>.<\/p>\n<pre><code class=\"python\">def shift(self, vec: np.ndarray):     self.left += vec     self.right += vec     self.center += vec<\/code><\/pre>\n<p>\u041d\u043e \u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0446\u0438\u0440\u043a\u0443\u043b\u044c \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0432\u0438\u043d\u0443\u0442\u044c \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043e\u0434\u043d\u0430 \u0438\u0437 \u0435\u0433\u043e \u0442\u043e\u0447\u0435\u043a \u043f\u043e\u043f\u0430\u043b\u0430 \u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u0443\u044e \u0442\u043e\u0447\u043a\u0443 <code>point<\/code>, \u0447\u0442\u043e \u043b\u0435\u0433\u043a\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e \u0441\u0434\u0432\u0438\u0433\u0430. <\/p>\n<pre><code class=\"python\">def move_to(self, point: np.ndarray):     self.shift(point - (self.left + self.right + self.center) \/ 3)  def move_left_to(self, point: np.ndarray):     self.shift(point - self.left)  def move_right_to(self, point: np.ndarray):     self.shift(point - self.right)  def move_center_to(self, point: np.ndarray):     self.shift(point - self.center)<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0446\u0438\u0440\u043a\u0443\u043b\u044f<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import numpy as np   class Compass:     def __init__(         self,         left: np.ndarray,         right: np.ndarray,         center: np.ndarray = None,         outer_center: bool = True,     ):         self.left = left         self.right = right         self.center = center          if center is None:             self.init_center(self.radius, outer_center)         else:             det = np.linalg.det(np.vstack((center - left, center - right)))              if np.isclose(                 np.linalg.norm(center - left), np.linalg.norm(center - right)             ):                 raise ValueError(\"invalid center\")             if np.isclose(det, 0):                 raise ValueError(\"collinear points\")      @property     def outer_center(self):         det = np.linalg.det(             np.vstack((self.right - self.left, self.center - self.left))         )         return True if det > 0 else False      @property     def length(self):         return np.linalg.norm(self.center - self.left)      @property     def radius(self):         return np.linalg.norm(self.right - self.left)      def _normal_vec(self, outer: bool):         vec = (self.right - self.left) \/ self.radius         normal_vec = np.array([-vec[1], vec[0]])         det = np.linalg.det(np.vstack((vec, normal_vec)))          if outer:             return np.sign(det) * normal_vec          return -np.sign(det) * normal_vec      def _angle_vec(self, vec1, vec2):         return np.arctan2(vec2[1], vec2[0]) - np.arctan2(vec1[1], vec1[0])      def init_center(self, height: float, outer: bool):         self.center = 0.5 * (self.right + self.left) + height * self._normal_vec(outer)      def flip(self):         self.center = self.right + self.left - self.center      def rotate_about_point(self, angle: float, point: np.ndarray):         rot_mat = np.array([             [np.cos(angle), -np.sin(angle)],              [np.sin(angle), np.cos(angle)]         ])         self.left = np.matmul(rot_mat, self.left - point) + point         self.right = np.matmul(rot_mat, self.right - point) + point         self.center = np.matmul(rot_mat, self.center - point) + point      def rotate_about_center(self, angle: float):         self.rotate_about_point(angle, self.center)      def rotate_about_left(self, angle: float):         self.rotate_about_point(angle, self.left)      def rotate_about_right(self, angle: float):         self.rotate_about_point(angle, self.right)      def rotate_left_to(self, point: np.ndarray):         angle = self._angle_vec(self.left - self.right, point - self.right)         self.rotate_about_right(angle)          if not np.allclose(self.left, point):             raise ValueError(\"can't rotate to chosen point\")      def rotate_right_to(self, point: np.ndarray):         angle = self._angle_vec(self.right - self.left, point - self.left)         self.rotate_about_left(angle)          if not np.allclose(self.right, point):             raise ValueError(\"can't rotate to chosen point\")      def change_angle(self, angle: float):         if angle >= np.pi or angle &lt;= 0:             raise ValueError(\"invalid angle\")          vec_left = self.left - self.center         vec_right = self.right - self.center         current_angle = self._angle_vec(vec_left, vec_right)         new_angle = 0.5 * (current_angle - angle)         rot_mat = np.array([             [np.cos(new_angle), -np.sin(new_angle)],             [np.sin(new_angle), np.cos(new_angle)],         ])          if self.outer_center:             self.left = np.matmul(rot_mat, vec_left) + self.center             self.right = np.matmul(rot_mat.transpose(), vec_right) + self.center         else:             self.left = np.matmul(rot_mat.transpose(), vec_left) + self.center             self.right = np.matmul(rot_mat, vec_right) + self.center      def change_radius_to_right(self, new_radius: float):         if new_radius >= 2 * self.length or new_radius &lt;= 0:             raise ValueError(\"invalid radius\")          height = (self.length**2 - 0.25 * new_radius**2) ** 0.5         self.right += (new_radius \/ self.radius - 1) * (self.right - self.left)         self.init_center(height, self.outer_center)      def change_radius_to_left(self, new_radius: float):         if new_radius >= 2 * self.length or new_radius &lt;= 0:             raise ValueError(\"invalid radius\")          height = (self.length**2 - 0.25 * new_radius**2) ** 0.5         self.left += (new_radius \/ self.radius - 1) * (self.left - self.right)         self.init_center(height, self.outer_center)      def change_radius(self, new_radius: float):         if new_radius >= 2 * self.length or new_radius &lt;= 0:             raise ValueError(\"invalid radius\")          height = (self.length**2 - 0.25 * new_radius**2) ** 0.5         radius = self.radius         vec = self.right - self.left         self.left -= 0.5 * (new_radius \/ radius - 1) * vec         self.right += 0.5 * (new_radius \/ radius - 1) * vec         self.init_center(height, self.outer_center)      def shift(self, vec: np.ndarray):         self.left += vec         self.right += vec         self.center += vec      def move_to(self, point: np.ndarray):         self.shift(point - (self.left + self.right + self.center) \/ 3)      def move_left_to(self, point: np.ndarray):         self.shift(point - self.left)      def move_right_to(self, point: np.ndarray):         self.shift(point - self.right)      def move_center_to(self, point: np.ndarray):         self.shift(point - self.center)<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h2>\u041b\u0438\u043d\u0435\u0439\u043a\u0430<\/h2>\n<p>\u0421 \u043b\u0438\u043d\u0435\u0439\u043a\u043e\u0439 \u043f\u043e\u0447\u0442\u0438 \u0432\u0441\u0451 \u0442\u043e\u0436\u0435 \u0441\u0430\u043c\u043e\u0435, \u0447\u0442\u043e \u0438 \u0441 \u0446\u0438\u0440\u043a\u0443\u043b\u0435\u043c. \u0427\u0442\u043e\u0431\u044b \u0435\u0451 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c, \u043d\u0443\u0436\u043d\u043e \u0437\u0430\u0434\u0430\u0442\u044c \u0434\u043b\u0438\u043d\u0443, \u0448\u0438\u0440\u0438\u043d\u0443 \u0438 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u0438 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0445 \u0434\u0435\u043b\u0435\u043d\u0438\u0439. \u041d\u043e \u0432 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0446\u0438\u0440\u043a\u0443\u043b\u044f, \u043f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u0442\u043e\u0447\u0435\u043a.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8e8\/3bd\/a35\/8e83bda35bb06b9c089a9375772f69bc.gif\" alt=\"\u0422\u043e\u0447\u043a\u0438 \u043b\u0438\u043d\u0435\u0439\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0443\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c\" title=\"\u0422\u043e\u0447\u043a\u0438 \u043b\u0438\u043d\u0435\u0439\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0443\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c\" width=\"640\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8e8\/3bd\/a35\/8e83bda35bb06b9c089a9375772f69bc.gif\"\/><\/p>\n<div><figcaption>\u0422\u043e\u0447\u043a\u0438 \u043b\u0438\u043d\u0435\u0439\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0443\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c<\/figcaption><\/div>\n<\/figure>\n<p>\u0412\u0441\u0435 \u044d\u0442\u0438 \u0442\u043e\u0447\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0430 \u0442\u0440\u0438 \u0433\u0440\u0443\u043f\u043f\u044b: <code>shape<\/code>, <code>division<\/code> \u0438 <code>subdivision<\/code>. <\/p>\n<ul>\n<li>\n<p><code>shape<\/code> \u2014 \u0442\u043e\u0447\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0437\u0430\u0434\u0430\u044e\u0442 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0442\u043e \u0435\u0441\u0442\u044c \u0441\u0430\u043c\u0443 \u043b\u0438\u043d\u0435\u0439\u043a\u0443. <\/p>\n<\/li>\n<li>\n<p><code>division<\/code> \u0438 <code>subdivision<\/code> \u2014 \u0442\u043e\u0447\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0437\u0430\u0434\u0430\u044e\u0442 \u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0438 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0435 \u0434\u0435\u043b\u0435\u043d\u0438\u044f. \u041d\u043e \u043d\u0443\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e\u0447\u043a\u0438 \u043d\u0430\u0447\u0430\u043b\u0430 \u0434\u0435\u043b\u0435\u043d\u0438\u0439, \u043d\u043e \u0438 \u0442\u043e\u0447\u043a\u0438 \u043a\u043e\u043d\u0446\u0430 \u0434\u0435\u043b\u0435\u043d\u0438\u0439, \u0442\u043e \u0435\u0441\u0442\u044c, \u043f\u043e \u0441\u0443\u0442\u0438, \u043e\u0442\u0440\u0435\u0437\u043a\u0438. \u042f \u0438\u0445 \u0431\u0443\u0434\u0443 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043f\u043e\u0434\u0440\u044f\u0434, \u0442\u043e \u0435\u0441\u0442\u044c \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435: \u043d\u0430\u0447\u0430\u043b\u043e \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0434\u0435\u043b\u0435\u043d\u0438\u044f, \u043a\u043e\u043d\u0435\u0446 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0434\u0435\u043b\u0435\u043d\u0438\u044f, \u043d\u0430\u0447\u0430\u043b\u043e \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0434\u0435\u043b\u0435\u043d\u0438\u044f, \u043a\u043e\u043d\u0435\u0446 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0438 \u0442\u0430\u043a \u0434\u0430\u043b\u0435\u0435 \u0434\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0434\u0435\u043b\u0435\u043d\u0438\u044f. <\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7b8\/842\/6a4\/7b88426a4aff0146ddf72b45249cbc8e.gif\" alt=\"\u041f\u0440\u0438\u043c\u0435\u0440 \u043b\u0438\u043d\u0435\u0439\u043a\u0438 \u0441 \u0434\u0432\u0443\u043c\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043c\u0438 \u0438 \u0447\u0435\u0442\u044b\u0440\u044c\u043c\u044f \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u043c\u0438 \u0434\u0435\u043b\u0435\u043d\u0438\u044f\u043c\u0438\" title=\"\u041f\u0440\u0438\u043c\u0435\u0440 \u043b\u0438\u043d\u0435\u0439\u043a\u0438 \u0441 \u0434\u0432\u0443\u043c\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043c\u0438 \u0438 \u0447\u0435\u0442\u044b\u0440\u044c\u043c\u044f \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u043c\u0438 \u0434\u0435\u043b\u0435\u043d\u0438\u044f\u043c\u0438\" width=\"640\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7b8\/842\/6a4\/7b88426a4aff0146ddf72b45249cbc8e.gif\"\/><\/p>\n<div><figcaption>\u041f\u0440\u0438\u043c\u0435\u0440 \u043b\u0438\u043d\u0435\u0439\u043a\u0438 \u0441 \u0434\u0432\u0443\u043c\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043c\u0438 \u0438 \u0447\u0435\u0442\u044b\u0440\u044c\u043c\u044f \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u043c\u0438 \u0434\u0435\u043b\u0435\u043d\u0438\u044f\u043c\u0438<\/figcaption><\/div>\n<\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0443 \u043a\u043b\u0430\u0441\u0441\u0430. \u041d\u0430 \u0432\u0445\u043e\u0434 \u043e\u043d \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b:<\/p>\n<ul>\n<li>\n<p><code>width<\/code>, <code>height<\/code> \u2014 \u0448\u0438\u0440\u0438\u043d\u0430 \u0438 \u0434\u043b\u0438\u043d\u0430 \u043b\u0438\u043d\u0435\u0439\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p><code>division<\/code>, <code>subdivision<\/code> \u2014 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u0438 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0445 \u0434\u0435\u043b\u0435\u043d\u0438\u0439.<\/p>\n<\/li>\n<li>\n<p><code>padding_width<\/code>\u00a0\u2014 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043e\u0442\u0441\u0442\u0443\u043f \u0434\u0435\u043b\u0435\u043d\u0438\u0439 \u043f\u043e \u0448\u0438\u0440\u0438\u043d\u0435. <\/p>\n<\/li>\n<li>\n<p><code>height_division,<\/code> <code>height_subdivision<\/code> \u2014 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0432\u044b\u0441\u043e\u0442\u0430 \u0434\u0435\u043b\u0435\u043d\u0438\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c, \u0447\u0442\u043e \u043b\u0438\u043d\u0435\u0439\u043a\u0430 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0430 \u0442\u0430\u043a, \u0447\u0442\u043e \u0435\u0451 \u0432\u0435\u0440\u0445\u043d\u044f\u044f \u043b\u0435\u0432\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442. \u0412 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 <code>shape<\/code> \u0441\u043e\u0432\u0441\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e.<\/p>\n<pre><code class=\"python\">self.shape = np.array([[0, 0], [0, -height], [width, -height], [width, 0]])<\/code><\/pre>\n<p>\u041f\u0435\u0440\u0435\u0439\u0434\u0451\u043c \u043e\u0442 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0432\u0435\u043b\u0438\u0447\u0438\u043d \u043a \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u044b\u043c \u0438 \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u0448\u0438\u0440\u0438\u043d\u0443 \u0441 \u0443\u0447\u0451\u0442\u043e\u043c \u043e\u0442\u0441\u0442\u0443\u043f\u0430.<\/p>\n<pre><code class=\"python\">padding_width *= width width -= padding_width height_division *= height height_subdivision *= height<\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043d\u0443\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 <code>division<\/code> \u0438 <code>subdivision<\/code>, \u043d\u0430\u0447\u043d\u0451\u043c \u0441 \u043f\u0435\u0440\u0432\u044b\u0445. <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u043f\u043e<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"x\" alt=\"x\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f5d\/ae0\/689\/f5dae068902f43ed06c9a35f12634903.svg\" width=\"11\" height=\"12\"\/>, \u0440\u0430\u0432\u043d\u043e\u043c\u0435\u0440\u043d\u043e \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0442\u043e\u0447\u043a\u0438 \u043d\u0430 \u043e\u0442\u0440\u0435\u0437\u043a\u0435 \u043e\u0442 <code>0<\/code> \u0434\u043e <code>width<\/code> \u0438 \u0441\u0434\u0432\u0438\u043d\u0435\u043c \u0438\u0445 \u0432\u043f\u0440\u0430\u0432\u043e \u043d\u0430 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0443 \u043e\u0442\u0441\u0442\u0443\u043f\u0430 \u043f\u043e \u0448\u0438\u0440\u0438\u043d\u0435. \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043d\u0443\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u0430\u043a \u043d\u0430\u0447\u0430\u043b\u043e \u0434\u0435\u043b\u0435\u043d\u0438\u044f, \u0442\u0430\u043a \u0438 \u043a\u043e\u043d\u0435\u0446 \u0434\u0435\u043b\u0435\u043d\u0438\u044f, \u0442\u043e \u043a\u0430\u0436\u0434\u0443\u044e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0443 \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c. <\/p>\n<p>\u0414\u043b\u044f \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442 \u043f\u043e<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"y\" alt=\"y\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f0e\/998\/61f\/f0e99861f698d0e55898f3c8bc14a1ea.svg\" width=\"10\" height=\"15\"\/>\u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c <code>0, -height_division<\/code> \u0441\u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u0434\u0435\u043b\u0435\u043d\u0438\u0439, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0430\u0447\u0430\u043b\u043e \u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043b\u0435\u0436\u0438\u0442 \u043d\u0430 \u0441\u0430\u043c\u043e\u0439 \u043b\u0438\u043d\u0435\u0439\u043a\u0438 (\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430 \u043f\u043e<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"y\" alt=\"y\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/369\/d92\/762\/369d927627c0439ee5de5e2530f7e25c.svg\" width=\"10\" height=\"15\"\/>\u0440\u0430\u0432\u043d\u0430 \u043d\u0443\u043b\u044e), \u0430 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430 \u043f\u043e<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"y\" alt=\"y\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/45f\/f19\/f71\/45ff19f71c60b4e6c10283cea4e5e43f.svg\" width=\"10\" height=\"15\"\/>\u043a\u043e\u043d\u0446\u0430 \u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0440\u0430\u0432\u043d\u0430 \u0434\u043b\u0438\u043d\u0435 \u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0441\u043e \u0437\u043d\u0430\u043a\u043e\u043c \u043c\u0438\u043d\u0443\u0441. <\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0438 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u043c\u0430\u0441\u0441\u0438\u0432, \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430. <\/p>\n<pre><code class=\"python\">div_x = np.linspace(0, width, division) div_x += 0.5 * padding_width div_x = np.repeat(div_x, 2) div_y = np.full((division, 2), [0, -height_division]) self.division = np.vstack((div_x, div_y.reshape(-1))).transpose()<\/code><\/pre>\n<p>\u0414\u043b\u044f \u0442\u043e\u0447\u0435\u043a <code>subdivision<\/code> \u0434\u0435\u043b\u0430\u0435\u043c \u0442\u043e\u0436\u0435 \u0441\u0430\u043c\u043e\u0435, \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442 \u043f\u043e<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"x\" alt=\"x\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c67\/92b\/d9e\/c6792bd9ec3a8ca27eced51bd22ad135.svg\" width=\"11\" height=\"12\"\/>\u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0442\u043e\u0447\u043a\u0438 \u043c\u0435\u0436\u0434\u0443 \u0431\u043e\u043b\u044c\u0448\u0438\u043c\u0438 \u0434\u0435\u043b\u0435\u043d\u0438\u044f\u043c\u0438.<\/p>\n<pre><code class=\"python\">subdiv_x = np.array([     np.linspace(div_x[2 * i], div_x[2 * i + 2], subdivision + 2)[1:-1]     for i in range(division - 1) ]) subdiv_x = np.repeat(subdiv_x.reshape(-1), 2) subdiv_y = np.full(     ((division - 1) * subdivision, 2), [0, -height_subdivision] ) self.subdivision = np.vstack((subdiv_x, subdiv_y.reshape(-1))).transpose()<\/code><\/pre>\n<p>\u0412 \u043a\u043e\u043d\u0446\u0435 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043b\u0438\u043d\u0435\u0439\u043a\u0438 \u0432 \u043d\u0430\u0447\u0430\u043b\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u0434\u0435\u043b\u0435\u043d\u0438\u0439 \u0431\u043e\u043b\u044c\u0448\u0435 \u0434\u0432\u0443\u0445. <\/p>\n<p>\u041f\u043e \u0438\u0442\u043e\u0433\u0443, \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043a\u043b\u0430\u0441\u0441\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c.<\/p>\n<pre><code class=\"python\">import numpy as np   class Ruler:     def __init__(         self,         width: float,         height: float,         division: int,         subdivision: int = 0,         padding_width: float = 0.1,         height_division: float = 0.45,         height_subdivision: float = 0.25,     ):         if division &lt; 2:             raise ValueError(\"invalid division\")          self.shape = np.array([[0, 0], [0, -height], [width, -height], [width, 0]])         padding_width *= width         width -= padding_width         height_division *= height         height_subdivision *= height          div_x = np.linspace(0, width, division)         div_x += 0.5 * padding_width         div_x = np.repeat(div_x, 2)         div_y = np.full((division, 2), [0, -height_division])         self.division = np.vstack((div_x, div_y.reshape(-1))).transpose()          self.subdivision = None         if subdivision > 0:             subdiv_x = np.array([                 np.linspace(div_x[2 * i], div_x[2 * i + 2], subdivision + 2)[1:-1]                 for i in range(division - 1)             ])             subdiv_x = np.repeat(subdiv_x.reshape(-1), 2)             subdiv_y = np.full(                 ((division - 1) * subdivision, 2), [0, -height_subdivision]             )             self.subdivision = np.vstack((subdiv_x, subdiv_y.reshape(-1))).transpose()          self.move_to(np.array([0, 0]))<\/code><\/pre>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 <code>start<\/code> \u0438 <code>end<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044e\u0442 \u043d\u0430\u0447\u0430\u043b\u043e \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u0434\u0435\u043b\u0435\u043d\u0438\u044f.<\/p>\n<pre><code class=\"python\">@property def start(self):     return self.division[0]  @property def end(self):     return self.division[-2]<\/code><\/pre>\n<p>\u0414\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0438\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0442 \u043c\u0435\u0442\u043e\u0434\u044b \u0446\u0438\u0440\u043a\u0443\u043b\u044f, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043d\u0435 \u0431\u0443\u0434\u0443 \u0438\u0445 \u0442\u0430\u043a \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c, \u0430 \u0431\u043e\u043b\u044c\u0448\u0435 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u044e\u0441\u044c \u043d\u0430 \u0442\u043e\u043c, \u0447\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c. <\/p>\n<h3>\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b<\/h3>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439\u00a0\u2014 \u0432\u0441\u0451 \u0442\u043e\u0442 \u0436\u0435 \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438.<\/p>\n<pre><code class=\"python\">def _angle_vec(self, vec1: np.ndarray, vec2: np.ndarray):     return np.arctan2(vec2[1], vec2[0]) - np.arctan2(vec1[1], vec1[0])<\/code><\/pre>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439\u00a0\u2014 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0442\u043e\u0447\u043a\u0438 \u043d\u0430\u0447\u0430\u043b\u0430 \u0434\u0435\u043b\u0435\u043d\u0438\u044f. \u041d\u0430 \u0432\u0445\u043e\u0434 \u043e\u043d \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u0438 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u043e\u0433\u043e \u0434\u0435\u043b\u0435\u043d\u0438\u044f. \u0417\u0434\u0435\u0441\u044c \u0432\u0430\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u0442\u043e\u0447\u043a\u0438 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u043f\u043e\u0434\u0440\u044f\u0434 (\u043d\u0430\u0447\u0430\u043b\u043e \u0434\u0435\u043b\u0435\u043d\u0438\u044f, \u043a\u043e\u043d\u0435\u0446 \u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0438 \u0442.\u0434.), \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u043d\u0443\u0436\u043d\u043e \u0443\u043c\u043d\u043e\u0436\u0430\u0442\u044c \u043d\u0430 \u0434\u0432\u0430.<\/p>\n<pre><code class=\"python\">def _get_division(self, div: int, subdiv: int = None):     div, subdiv = 2 * div, 2 * subdiv if subdiv is not None else None     if div >= len(self.division):         raise IndexError(\"division index out of range\")     if subdiv is None:         return self.division[div]     if self.subdivision is None:         raise ValueError(\"subdivision is not defined\")      count_subdiv = len(self.subdivision) \/\/ (len(self.division) - 2)      if subdiv >= 2 * count_subdiv:         raise IndexError(\"subdivision index out of range\")      return self.subdivision[count_subdiv * div + subdiv]<\/code><\/pre>\n<h3>\u041f\u043e\u0432\u043e\u0440\u043e\u0442\u044b<\/h3>\n<p>\u041a\u0430\u043a \u0438 \u0441 \u0446\u0438\u0440\u043a\u0443\u043b\u0435\u043c, \u043e\u043f\u0438\u0448\u0435\u043c \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u043b\u0438\u043d\u0435\u0439\u043a\u0438 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 <code>point<\/code>.<\/p>\n<pre><code class=\"python\">def rotate_about_point(self, angle: float, point: np.ndarray):     rot_mat = np.array([         [np.cos(angle), np.sin(angle)],          [-np.sin(angle), np.cos(angle)]     ])     self.shape = np.matmul(self.shape - point, rot_mat) + point     self.division = np.matmul(self.division - point, rot_mat) + point     if self.subdivision is not None:         self.subdivision = np.matmul(self.subdivision - point, rot_mat) + point<\/code><\/pre>\n<p>\u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0446\u0438\u0440\u043a\u0443\u043b\u044f, \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043a\u0430\u0436\u0434\u0443\u044e \u0442\u043e\u0447\u043a\u0443 \u0438\u0437 \u043c\u0430\u0441\u0441\u0438\u0432\u0430 \u0442\u043e\u0447\u0435\u043a. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0432\u0441\u044e \u0442\u0443 \u0436\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044e <a href=\"https:\/\/numpy.org\/doc\/stable\/reference\/generated\/numpy.matmul\" rel=\"noopener noreferrer nofollow\">numpy.matmul<\/a>, \u043d\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u0443\u043c\u043d\u043e\u0436\u0430\u0435\u043c \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043d\u0435 \u0441\u043b\u0435\u0432\u0430, \u0430 \u0441\u043f\u0440\u0430\u0432\u0430. \u0412\u0441\u0451 \u043f\u043e\u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u043c\u0430\u0441\u0441\u0438\u0432 \u0442\u043e\u0447\u0435\u043a \u0438\u043c\u0435\u0435\u0442 \u0440\u0430\u0437\u043c\u0435\u0440 <code>(k, 2)<\/code>, \u0433\u0434\u0435 <code>k<\/code>  &#8212; \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u043e\u0447\u0435\u043a, \u0438 \u0435\u0433\u043e \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u043c\u0430\u0442\u0440\u0438\u0447\u043d\u043e \u0443\u043c\u043d\u043e\u0436\u0438\u0442\u044c \u0441\u043b\u0435\u0432\u0430 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 <code>(2, 2)<\/code>. <\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0435\u0441\u043b\u0438 \u0440\u0430\u043d\u044c\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0430\u0441\u044c \u0442\u0430\u043a\u0430\u044f \u043c\u0430\u0442\u0440\u0438\u0446\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"M =\\begin{pmatrix}   \\cos{\\alpha} &amp; -\\sin{\\alpha} \\\\   \\sin{\\alpha} &amp; \\cos{\\alpha} \\end{pmatrix},\" alt=\"M =\\begin{pmatrix}   \\cos{\\alpha} &amp; -\\sin{\\alpha} \\\\   \\sin{\\alpha} &amp; \\cos{\\alpha} \\end{pmatrix},\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b01\/24a\/8a0\/b0124a8a0c6e95a0d373f8d3dcc8d238.svg\" width=\"207\" height=\"49\"\/><\/p>\n<p>\u0442\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0443\u0436\u043d\u043e \u0431\u0440\u0430\u0442\u044c \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0443\u044e <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"M^t = \\begin{pmatrix}   \\cos{\\alpha} &amp; \\sin{\\alpha} \\\\   -\\sin{\\alpha} &amp; \\cos{\\alpha} \\end{pmatrix}.\" alt=\"M^t = \\begin{pmatrix}   \\cos{\\alpha} &amp; \\sin{\\alpha} \\\\   -\\sin{\\alpha} &amp; \\cos{\\alpha} \\end{pmatrix}.\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/3d9\/d9b\/27c\/3d9d9b27c89b740395f9e4fee35df7b5.svg\" width=\"215\" height=\"49\"\/><\/p>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u0442\u0430\u043a<\/summary>\n<div class=\"spoiler__content\">\n<p>\u041f\u0440\u0438 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0435 \u0442\u043e\u0447\u0435\u043a \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043e\u043d\u0438 \u0443\u043c\u043d\u043e\u0436\u0430\u043b\u0438\u0441\u044c \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0441\u043b\u0435\u0432\u0430, \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0441\u043b\u043e\u0432\u0430\u043c\u0438, \u0442\u043e\u0447\u043a\u0438 \u0432\u043e\u0441\u043f\u0440\u0438\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u044c \u043a\u0430\u043a \u0432\u0435\u043a\u0442\u043e\u0440-\u0441\u0442\u043e\u043b\u0431\u0435\u0446. \u0422\u0435\u043f\u0435\u0440\u044c \u0436\u0435 \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043f\u0440\u0430\u0432\u0430, \u0442\u043e \u0435\u0441\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u0432\u043e\u0441\u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044e\u0442\u0441\u044f \u043a\u0430\u043a \u0432\u0435\u043a\u0442\u043e\u0440-\u0441\u0442\u0440\u043e\u043a\u0430. <\/p>\n<p>\u041f\u0443\u0441\u0442\u044c<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"x\" alt=\"x\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/dee\/d89\/59d\/deed8959d5eb141c6d1ae48edfe71f0a.svg\" width=\"11\" height=\"12\"\/>&#8212; \u0442\u043e\u0447\u043a\u0430, \u0432\u043e\u0441\u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c\u0430\u044f \u043a\u0430\u043a \u0432\u0435\u043a\u0442\u043e\u0440-\u0441\u0442\u0440\u043e\u043a\u0430, <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"\\widetilde{x}\" alt=\"\\widetilde{x}\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0bc\/bd9\/dd5\/0bcbd9dd55ed11ae27ab14ba75a863b8.svg\" width=\"11\" height=\"16\"\/> &#8212; \u044d\u0442\u0430 \u0436\u0435 \u0442\u043e\u0447\u043a\u0430 \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430. <\/p>\n<p>\u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438 \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0438 \u0441\u043b\u0435\u0432\u0430 \u0442\u043e\u0447\u043a\u0438<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"x\" alt=\"x\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5bc\/2f2\/bbc\/5bc2f2bbcf37eb2e2404477592650f2e.svg\" width=\"11\" height=\"12\"\/>\u0438 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"\\widetilde{x}\" alt=\"\\widetilde{x}\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9ab\/8f9\/679\/9ab8f96792a8178c1b19d3f5c4c25bd3.svg\" width=\"11\" height=\"16\"\/> \u0441\u0432\u044f\u0437\u0430\u043d\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0440\u0430\u0432\u0435\u043d\u0441\u0442\u0432\u043e\u043c<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"\\widetilde{x}^t = M x^t.\" alt=\"\\widetilde{x}^t = M x^t.\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a47\/6e6\/d76\/a476e6d761f761bb657d7746c86709cd.svg\" width=\"87\" height=\"20\"\/><\/p>\n<p>\u0412\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u043c <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"\\widetilde{x} = (\\widetilde{x}^t)^t= (Mx^t)^t= xM^t.\" alt=\"\\widetilde{x} = (\\widetilde{x}^t)^t= (Mx^t)^t= xM^t.\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/dc9\/6f4\/d51\/dc96f4d5150e1f9269876edf6ec6d2aa.svg\" width=\"232\" height=\"24\"\/><\/p>\n<\/div>\n<\/details>\n<p>\u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u044b \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0442\u0430\u043a<\/p>\n<pre><code class=\"python\">def rotate_about_start(self, angle: float):     self.rotate_about_point(angle, self.start)  def rotate_about_end(self, angle: float):     self.rotate_about_point(angle, self.end)  def rotate_about_division(self, angle: float, division: int, subdivision: int = None):     self.rotate_about_point(angle, self._get_division(division, subdivision))  def rotate_start_to(self, point: np.ndarray):     angle = self._angle_vec(self.start - self.end, point - self.end)     self.rotate_about_end(angle)     product = np.dot(self.start - point, self.end - point)      if not np.isclose(product, 0) and product > 0:         raise ValueError(\"can't rotate to chosen point\")  def rotate_end_to(self, point: np.ndarray):     angle = self._angle_vec(self.end - self.start, point - self.start)     self.rotate_about_start(angle)     product = np.dot(self.start - point, self.end - point)      if not np.isclose(product, 0) and product > 0:         raise ValueError(\"can't rotate to chosen point\")<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0434\u043b\u044f \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043d\u0443\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u043e\u0434\u043d\u0430 \u0438\u0437 \u043d\u043e\u0436\u0435\u043a \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043f\u043e\u043f\u0430\u043b\u0430 \u0432 \u043d\u0443\u0436\u043d\u0443\u044e \u0442\u043e\u0447\u043a\u0443, \u0442\u043e \u0434\u043b\u044f \u043b\u0438\u043d\u0435\u0439\u043a\u0438 \u0443\u0441\u043b\u043e\u0432\u0438\u0435 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0434\u0440\u0443\u0433\u043e\u0435. \u041d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u0442\u043e\u0447\u043a\u0430 \u043b\u0435\u0436\u0438\u0442 \u043d\u0430 \u043b\u0438\u043d\u0435\u0439\u043a\u0438, \u0442\u043e \u0435\u0441\u0442\u044c \u043d\u0430 \u043e\u0442\u0440\u0435\u0437\u043a\u0435. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 <code>point<\/code>, <code>self.start<\/code> \u0438 <code>point<\/code>, <code>self.end<\/code> \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u043e\u043d\u043e \u043c\u0435\u043d\u044c\u0448\u0435 \u0438\u043b\u0438 \u0440\u0430\u0432\u043d\u043e \u043d\u0443\u043b\u044e.<\/p>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u0442\u0430\u043a<\/summary>\n<div class=\"spoiler__content\">\n<p>\u0414\u043b\u044f \u0435\u0432\u043a\u043b\u0438\u0434\u043e\u0432\u044b\u0445 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432 \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u0432\u0438\u0434\u0435<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"(a,b)=|a||b|\\cos{\\alpha},\" alt=\"(a,b)=|a||b|\\cos{\\alpha},\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8b5\/b10\/f7b\/8b5b10f7b5feeb8d2e6f5db41811ee05.svg\" width=\"158\" height=\"22\"\/><\/p>\n<p>\u0433\u0434\u0435 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"\\alpha\" alt=\"\\alpha\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/08d\/015\/265\/08d015265f5b918e5d44653259edbbc1.svg\" width=\"12\" height=\"12\"\/>&#8212; \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"a\" alt=\"a\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/079\/ef9\/4ec\/079ef94ecc5dc735cfe3b385c30fcbc8.svg\" width=\"10\" height=\"12\"\/>\u0438<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"b\" alt=\"b\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7da\/654\/9c3\/7da6549c31fe95cdc0645548f91a3641.svg\" width=\"8\" height=\"17\"\/>, <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"|a|,|b|\" alt=\"|a|,|b|\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/16a\/fbb\/d02\/16afbbd02b1be19397ec769e2e94c657.svg\" width=\"49\" height=\"22\"\/>&#8212; \u0434\u043b\u0438\u043d\u044b \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"a\" alt=\"a\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8ef\/0c9\/47a\/8ef0c947a321eb0090239f9a65cfb6e1.svg\" width=\"10\" height=\"12\"\/>\u0438 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"b\" alt=\"b\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5aa\/97c\/fa1\/5aa97cfa1460cc3bd753be52a43f23d7.svg\" width=\"8\" height=\"17\"\/>.<\/p>\n<p>\u041f\u0443\u0441\u0442\u044c <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"v_1, v_2\" alt=\"v_1, v_2\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/956\/8de\/3bf\/9568de3bf4160aa1842f13bebb7b6368.svg\" width=\"45\" height=\"15\"\/>&#8212; \u043f\u0435\u0440\u0432\u044b\u0439 \u0438 \u0432\u0442\u043e\u0440\u043e\u0439 \u0432\u0435\u043a\u0442\u043e\u0440, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0432\u044b\u0448\u0435. \u0412\u0430\u0436\u043d\u043e \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u044d\u0442\u0438 \u0432\u0435\u043a\u0442\u043e\u0440\u044b \u043a\u043e\u043b\u043b\u0438\u043d\u0435\u0430\u0440\u043d\u044b, \u0442\u0430\u043a \u043a\u0430\u043a \u043b\u0438\u043d\u0435\u0439\u043a\u0430 \u0443\u0436\u0435 \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0430 \u043a \u0442\u043e\u0447\u043a\u0435. <\/p>\n<p>\u0422\u043e\u0433\u0434\u0430, \u0435\u0441\u043b\u0438 \u0442\u043e\u0447\u043a\u0430 \u043b\u0435\u0436\u0438\u0442 \u043d\u0430 \u043e\u0442\u0440\u0435\u0437\u043a\u0435, \u0442\u043e \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0440\u0430\u0432\u0435\u043d <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"180^\\circ\" alt=\"180^\\circ\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/490\/4a5\/c5b\/4904a5c5b1aeec007a885e6586f46c1a.svg\" width=\"38\" height=\"18\"\/>, \u0442\u043e \u0435\u0441\u0442\u044c \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435, \u0442\u0430\u043a \u043a\u0430\u043a <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"\\cos{180^\\circ}=-1.\" alt=\"\\cos{180^\\circ}=-1.\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a14\/2ac\/518\/a142ac5187456e3db62f6aaf0b3dbf1e.svg\" width=\"122\" height=\"19\"\/><\/p>\n<p>\u0415\u0441\u043b\u0438 \u0436\u0435 \u0442\u043e\u0447\u043a\u0430 \u043b\u0435\u0436\u0438\u0442 \u043d\u0430 \u043a\u043e\u043d\u0446\u0435 \u043e\u0442\u0440\u0435\u0437\u043a\u0430, \u0442\u043e \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0440\u0430\u0432\u043d\u043e \u043d\u0443\u043b\u044e, \u0442\u0430\u043a \u043a\u0430\u043a \u043e\u0434\u0438\u043d \u0432\u0435\u043a\u0442\u043e\u0440 \u043d\u0443\u043b\u0435\u0432\u043e\u0439.<\/p>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u0435\u0441\u043b\u0438 \u0442\u043e\u0447\u043a\u0430 \u043b\u0435\u0436\u0438\u0442 \u043d\u0435 \u043d\u0430 \u043e\u0442\u0440\u0435\u0437\u043a\u0435, \u0442\u043e \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438 \u0440\u0430\u0432\u0435\u043d <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"0^\\circ\" alt=\"0^\\circ\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b43\/9b9\/57d\/b439b957db391e8c505add0a6af061c8.svg\" width=\"18\" height=\"18\"\/>, \u0442\u043e \u0435\u0441\u0442\u044c \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435, \u0442\u0430\u043a \u043a\u0430\u043a<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"\\cos{0^\\circ} = 1.\" alt=\"\\cos{0^\\circ} = 1.\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d45\/11d\/138\/d4511d1389a100493e42e65f02b0acdb.svg\" width=\"88\" height=\"18\"\/><\/p>\n<\/div>\n<\/details>\n<h3>\u0421\u0434\u0432\u0438\u0433 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f<\/h3>\n<p>\u0421\u0434\u0432\u0438\u0433 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0442 \u0441\u0434\u0432\u0438\u0433 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u0446\u0438\u0440\u043a\u0443\u043b\u044f.<\/p>\n<pre><code class=\"python\">def shift(self, vec: np.ndarray):     self.shape += vec     self.division += vec      if self.subdivision is not None:         self.subdivision += vec  def move_to(self, point: np.ndarray):     vec = 0.25 * np.sum(self.shape, axis=0)     self.shift(point - vec)  def move_start_to(self, point: np.ndarray):     self.shift(point - self.start)  def move_end_to(self, point: np.ndarray):     self.shift(point - self.end)  def move_division_to(self, point: np.ndarray, division: int, subdivision: int = None):     self.shift(point - self._get_division(division, subdivision))<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u043b\u0438\u043d\u0435\u0439\u043a\u0438<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import numpy as np   class Ruler:     def __init__(         self,         width: float,         height: float,         division: int,         subdivision: int = 0,         padding_width: float = 0.1,         height_division: float = 0.45,         height_subdivision: float = 0.25,     ):         if division &lt; 2:             raise ValueError(\"invalid division\")          self.shape = np.array([[0, 0], [0, -height], [width, -height], [width, 0]])         padding_width *= width         width -= padding_width         height_division *= height         height_subdivision *= height          div_x = np.linspace(0, width, division)         div_x += 0.5 * padding_width         div_x = np.repeat(div_x, 2)         div_y = np.full((division, 2), [0, -height_division])         self.division = np.vstack((div_x, div_y.reshape(-1))).transpose()          self.subdivision = None         if subdivision > 0:             subdiv_x = np.array([                 np.linspace(div_x[2 * i], div_x[2 * i + 2], subdivision + 2)[1:-1]                 for i in range(division - 1)             ])             subdiv_x = np.repeat(subdiv_x.reshape(-1), 2)             subdiv_y = np.full(                 ((division - 1) * subdivision, 2), [0, -height_subdivision]             )             self.subdivision = np.vstack((subdiv_x, subdiv_y.reshape(-1))).transpose()          self.move_to(np.array([0, 0]))      @property     def start(self):         return self.division[0]      @property     def end(self):         return self.division[-2]      def _angle_vec(self, vec1: np.ndarray, vec2: np.ndarray):         return np.arctan2(vec2[1], vec2[0]) - np.arctan2(vec1[1], vec1[0])      def _get_division(self, div: int, subdiv: int = None):         div, subdiv = 2 * div, 2 * subdiv if subdiv is not None else None         if div >= len(self.division):             raise IndexError(\"division index out of range\")         if subdiv is None:             return self.division[div]         if self.subdivision is None:             raise ValueError(\"subdivision is not defined\")          count_subdiv = len(self.subdivision) \/\/ (len(self.division) - 2)          if subdiv >= 2 * count_subdiv:             raise IndexError(\"subdivision index out of range\")          return self.subdivision[count_subdiv * div + subdiv]      def rotate_about_point(self, angle: float, point: np.ndarray):         rot_mat = np.array([             [np.cos(angle), np.sin(angle)],              [-np.sin(angle), np.cos(angle)]         ])         self.shape = np.matmul(self.shape - point, rot_mat) + point         self.division = np.matmul(self.division - point, rot_mat) + point         if self.subdivision is not None:             self.subdivision = np.matmul(self.subdivision - point, rot_mat) + point      def rotate_about_start(self, angle: float):         self.rotate_about_point(angle, self.start)      def rotate_about_end(self, angle: float):         self.rotate_about_point(angle, self.end)      def rotate_about_division(         self, angle: float, division: int, subdivision: int = None     ):         self.rotate_about_point(angle, self._get_division(division, subdivision))      def rotate_start_to(self, point: np.ndarray):         angle = self._angle_vec(self.start - self.end, point - self.end)         self.rotate_about_end(angle)         product = np.dot(self.start - point, self.end - point)          if not np.isclose(product, 0) and product > 0:             raise ValueError(\"can't rotate to chosen point\")      def rotate_end_to(self, point: np.ndarray):         angle = self._angle_vec(self.end - self.start, point - self.start)         self.rotate_about_start(angle)         product = np.dot(self.start - point, self.end - point)          if not np.isclose(product, 0) and product > 0:             raise ValueError(\"can't rotate to chosen point\")      def shift(self, vec: np.ndarray):         self.shape += vec         self.division += vec          if self.subdivision is not None:             self.subdivision += vec      def move_to(self, point: np.ndarray):         vec = 0.25 * np.sum(self.shape, axis=0)         self.shift(point - vec)      def move_start_to(self, point: np.ndarray):         self.shift(point - self.start)      def move_end_to(self, point: np.ndarray):         self.shift(point - self.end)      def move_division_to(         self, point: np.ndarray, division: int, subdivision: int = None     ):         self.shift(point - self._get_division(division, subdivision)) <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h2>\u0424\u0443\u043d\u043a\u0446\u0438\u0438 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0439<\/h2>\n<p>\u041f\u0440\u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0438 \u0446\u0438\u0440\u043a\u0443\u043b\u0435\u043c \u0438 \u043b\u0438\u043d\u0435\u0439\u043a\u043e\u0439 \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0439 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0435\u0439 \u0438 \u043f\u0440\u044f\u043c\u044b\u0445, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0442\u0440\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u044e\u0442.<\/p>\n<p>\u041f\u0435\u0440\u0432\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u00a0\u2014 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u044f\u043c\u044b\u0445. \u041d\u0430 \u0432\u0445\u043e\u0434 \u043e\u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0434\u0432\u0435 \u043f\u0440\u044f\u043c\u044b\u0435 \u0432 \u0432\u0438\u0434\u0435 \u0434\u0432\u0443\u0445 \u0442\u043e\u0447\u0435\u043a, \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u043f\u0440\u044f\u043c\u0430\u044f. \u041d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u0436\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u043e\u0447\u043a\u0443 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u044f \u0438\u043b\u0438 <code>None<\/code>, \u0435\u0441\u043b\u0438 \u043f\u0440\u044f\u043c\u044b\u0435 \u043d\u0435 \u043f\u0435\u0440\u0435\u0441\u0435\u043a\u0430\u044e\u0442\u0441\u044f.<\/p>\n<p>\u042d\u0442\u0443 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043f\u043e-\u0440\u0430\u0437\u043d\u043e\u043c\u0443. \u0421\u0430\u043c\u044b\u0439 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 &#8212; \u0432 \u043b\u043e\u0431 \u0440\u0435\u0448\u0438\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0443\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0439, \u043d\u043e \u044f <a href=\"https:\/\/stackoverflow.com\/a\/42727584\" rel=\"noopener noreferrer nofollow\">\u043d\u0430\u0448\u0451\u043b<\/a> \u0431\u043e\u043b\u0435\u0435 \u043a\u0440\u0430\u0441\u0438\u0432\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431. \u042f \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u0445\u043e\u0447\u0443 \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0442\u044c, \u043f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u0442\u0430\u043a, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u043b\u0435\u0437\u0442\u044c \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0432\u043d\u0443\u044e \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u044e, \u0438 \u043a \u0442\u043e\u043c\u0443 \u0436\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u0435\u0441\u0442\u044c \u0445\u043e\u0440\u043e\u0448\u0435\u0435 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u0435 \u044d\u0442\u043e\u0433\u043e.<\/p>\n<pre><code class=\"python\">def line_intersection(     line1: tuple[np.ndarray, np.ndarray], line2: tuple[np.ndarray, np.ndarray] ):     s = np.vstack((line1, line2))     h = np.hstack((s, np.ones((4, 1))))     line1 = np.cross(h[0], h[1])     line2 = np.cross(h[2], h[3])     x, y, z = np.cross(line1, line2)      if np.isclose(z, 0):         return None      return np.array([x \/ z, y \/ z])<\/code><\/pre>\n<p>\u0412\u0442\u043e\u0440\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u2014 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u044f\u043c\u043e\u0439 \u0438 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438. \u041d\u0430 \u0432\u0445\u043e\u0434 \u043e\u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043f\u0440\u044f\u043c\u0443\u044e \u0432 \u0432\u0438\u0434\u0443 \u0434\u0432\u0443\u0445 \u0442\u043e\u0447\u0435\u043a \u0438 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u044c \u0432 \u0432\u0438\u0434\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442 \u0446\u0435\u043d\u0442\u0440\u0430 \u0438 \u0440\u0430\u0434\u0438\u0443\u0441\u0430. \u041d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u0436\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u043e\u0447\u043a\u0438 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u044f \u0438\u043b\u0438 <code>None<\/code>, \u0435\u0441\u043b\u0438 \u0438\u0445 \u043d\u0435\u0442.<\/p>\n<p>\u0422\u0443\u0442 \u043f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u0440\u0435\u0448\u0430\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0443\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0439, \u043d\u043e \u044f <a href=\"https:\/\/mathworld.wolfram.com\/Circle-LineIntersection.html\" rel=\"noopener noreferrer nofollow\">\u043d\u0430\u0448\u0451\u043b<\/a> \u0443\u0436\u0435 \u0433\u043e\u0442\u043e\u0432\u044b\u0435 \u0444\u043e\u0440\u043c\u0443\u043b\u044b. \u041c\u043e\u0436\u043d\u043e \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u043e\u043d\u0438 \u0432\u0435\u0440\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0441\u043b\u0443\u0447\u0430\u044f, \u043a\u043e\u0433\u0434\u0430 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u043e\u0431\u0449\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u043d\u0443\u0436\u043d\u043e \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0434\u0432\u0438\u043d\u0443\u0442\u044c \u043f\u0440\u044f\u043c\u0443\u044e \u0438 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u044c \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0446\u0435\u043d\u0442\u0440 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u043e\u043f\u0430\u043b \u0432 \u043d\u0430\u0447\u0430\u043b\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442, \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0444\u043e\u0440\u043c\u0443\u043b\u0430\u043c\u0438 \u0438 \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0441\u0434\u0432\u0438\u043d\u0443\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u043e\u0447\u043a\u0438 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u044f.<\/p>\n<pre><code class=\"python\">def circle_line_intersection(     line: tuple[np.ndarray, np.ndarray], circle: tuple[np.ndarray, float] ):     center, radius = circle     p1, p2 = line - center     dx = p2[0] - p1[0]     dy = p2[1] - p1[1]     dr = dx**2 + dy**2     det = p1[0] * p2[1] - p1[1] * p2[0]     d = dr * radius**2 - det**2      if np.isclose(d, 0):         return np.array([det * dy \/ dr, -det * dx \/ dr]) + center     elif d > 0:         x1 = (det * dy + dx * np.sqrt(d)) \/ dr         y1 = (-det * dx + dy * np.sqrt(d)) \/ dr         x2 = (det * dy - dx * np.sqrt(d)) \/ dr         y2 = (-det * dx - dy * np.sqrt(d)) \/ dr         return np.array([x1, y1]) + center, np.array([x2, y2]) + center            return None<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u00a0\u2014 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0435 \u0434\u0432\u0443\u0445 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0435\u0439. \u041d\u0430 \u0432\u0445\u043e\u0434 \u043e\u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0434\u0432\u0435 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438 \u0432 \u0432\u0438\u0434\u0435 \u0446\u0435\u043d\u0442\u0440\u0430 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442 \u0438 \u0440\u0430\u0434\u0438\u0443\u0441\u0430. \u041d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u0436\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u043e\u0447\u043a\u0438 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u044f \u0438\u043b\u0438 <code>None<\/code>, \u0435\u0441\u043b\u0438 \u0438\u0445 \u043d\u0435\u0442.<\/p>\n<p>\u0418 \u043e\u043f\u044f\u0442\u044c \u0442\u0430\u043a\u0438 \u044f <a href=\"https:\/\/e-maxx.ru\/algo\/circles_intersection\" rel=\"noopener noreferrer nofollow\">\u043d\u0430\u0448\u0451\u043b<\/a> \u0443\u0436\u0435 \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435. \u0421\u0443\u0442\u044c \u0435\u0433\u043e \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 \u0442\u043e\u0447\u043a\u0438 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u044f \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0435\u0439, \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0448\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"\\left \\{\\begin{array}{l} (x-x_0)^2 + (y-y_0)^2 = r^2, \\\\  (x-\\widetilde{x}_0)^2 + (y-\\widetilde{y}_0)^2 = \\widetilde{r}^2. \\end{array} \\right.\" alt=\"\\left \\{\\begin{array}{l} (x-x_0)^2 + (y-y_0)^2 = r^2, \\\\  (x-\\widetilde{x}_0)^2 + (y-\\widetilde{y}_0)^2 = \\widetilde{r}^2. \\end{array} \\right.\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8c0\/38c\/f1d\/8c038cf1deaf24cb49c8cb17d4be9589.svg\" width=\"249\" height=\"53\"\/><\/p>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0432\u0430\u044f \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u044c \u043d\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442, \u0442\u043e \u0441\u0434\u0432\u0438\u043d\u0435\u043c \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0446\u0435\u043d\u0442\u0440 \u043f\u043e\u043f\u0430\u043b \u0432 \u043d\u0430\u0447\u0430\u043b\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"\\left \\{\\begin{array}{l} x^2 + y^2 = r^2, \\\\  (x + (x_0-\\widetilde{x}_0))^2 + (y- (y_0 - \\widetilde{y}_0))^2 = \\widetilde{r}^2. \\end{array} \\right.\" alt=\"\\left \\{\\begin{array}{l} x^2 + y^2 = r^2, \\\\  (x + (x_0-\\widetilde{x}_0))^2 + (y- (y_0 - \\widetilde{y}_0))^2 = \\widetilde{r}^2. \\end{array} \\right.\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/85a\/f1f\/d67\/85af1fd676b9cf0b3197555aa4a5d680.svg\" width=\"364\" height=\"52\"\/><\/p>\n<p>\u0412\u044b\u0447\u0442\u0435\u043c \u043f\u0435\u0440\u0432\u043e\u0435 \u0443\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435 \u0438\u0437 \u0432\u0442\u043e\u0440\u043e\u0433\u043e <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"\\left \\{\\begin{array}{l} x^2 + y^2 = r^2, \\\\  2x(x_0-\\widetilde{x}_0) + 2y(y_0 - \\widetilde{y}_0) - (x_0 - \\widetilde{x}_0)^2 - (y_0 - \\widetilde{y}_0)^2 - \\widetilde{r}^2 = 0 .\\end{array} \\right.\" alt=\"\\left \\{\\begin{array}{l} x^2 + y^2 = r^2, \\\\  2x(x_0-\\widetilde{x}_0) + 2y(y_0 - \\widetilde{y}_0) - (x_0 - \\widetilde{x}_0)^2 - (y_0 - \\widetilde{y}_0)^2 - \\widetilde{r}^2 = 0 .\\end{array} \\right.\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ad3\/4eb\/1c6\/ad34eb1c6c20580d3217d1fdaaab81ea.svg\" width=\"541\" height=\"52\"\/><\/p>\n<p>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0432\u0442\u043e\u0440\u043e\u0435 \u0443\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435\u00a0\u2014 \u044d\u0442\u043e \u0443\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u044f\u043c\u043e\u0439. \u0422\u043e \u0435\u0441\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u0442\u043e\u0447\u0435\u043a \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0439 \u0434\u0432\u0443\u0445 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043a \u043f\u043e\u0438\u0441\u043a\u0443 \u0442\u043e\u0447\u0435\u043a \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0439 \u043f\u0440\u044f\u043c\u043e\u0439 \u0438 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438.<\/p>\n<pre><code class=\"python\">def circle_intersection(     circle1: tuple[np.ndarray, float], circle2: tuple[np.ndarray, float] ):     center1, radius1 = circle1     center2, radius2 = circle2     new_center = center1 - center2     a = 2 * new_center[0]     b = 2 * new_center[1]     c = new_center[0] ** 2 + new_center[1] ** 2 + radius1**2 - radius2**2      if np.isclose(a, 0):         line = np.array([0, -c \/ b]), np.array([1, -c \/ b])     elif np.isclose(b, 0):         line = np.array([-c \/ a, 0]), np.array([-c \/ a, 1])     else:         line = np.array([-1, (a - c) \/ b]), np.array([0, -c \/ b])      points = circle_line_intersection(line, (np.array([0, 0]), radius1))     return points + center1 if points is not None else None<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0439<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import numpy as np   def line_intersection(     line1: tuple[np.ndarray, np.ndarray], line2: tuple[np.ndarray, np.ndarray] ):     s = np.vstack((line1, line2))     h = np.hstack((s, np.ones((4, 1))))     line1 = np.cross(h[0], h[1])     line2 = np.cross(h[2], h[3])     x, y, z = np.cross(line1, line2)      if np.isclose(z, 0):         return None      return np.array([x \/ z, y \/ z])   def circle_intersection(     circle1: tuple[np.ndarray, float], circle2: tuple[np.ndarray, float] ):     center1, radius1 = circle1     center2, radius2 = circle2     new_center = center1 - center2     a = 2 * new_center[0]     b = 2 * new_center[1]     c = new_center[0] ** 2 + new_center[1] ** 2 + radius1**2 - radius2**2      if np.isclose(a, 0):         line = np.array([0, -c \/ b]), np.array([1, -c \/ b])     elif np.isclose(b, 0):         line = np.array([-c \/ a, 0]), np.array([-c \/ a, 1])     else:         line = np.array([-1, (a - c) \/ b]), np.array([0, -c \/ b])      points = circle_line_intersection(line, (np.array([0, 0]), radius1))      return points + center1 if points is not None else None   def circle_line_intersection(     line: tuple[np.ndarray, np.ndarray], circle: tuple[np.ndarray, float] ):     center, radius = circle     p1, p2 = line - center     dx = p2[0] - p1[0]     dy = p2[1] - p1[1]     dr = dx**2 + dy**2     det = p1[0] * p2[1] - p1[1] * p2[0]     d = dr * radius**2 - det**2      if np.isclose(d, 0):         return np.array([det * dy \/ dr, -det * dx \/ dr]) + center     elif d > 0:         x1 = (det * dy + dx * np.sqrt(d)) \/ dr         y1 = (-det * dx + dy * np.sqrt(d)) \/ dr         x2 = (det * dy - dx * np.sqrt(d)) \/ dr         y2 = (-det * dx - dy * np.sqrt(d)) \/ dr          return np.array([x1, y1]) + center, np.array([x2, y2]) + center      return None <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u043f\u0435\u0440\u0432\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0437\u0430\u043a\u043e\u043d\u0447\u0435\u043d\u0430. \u041d\u043e \u0443\u0436\u0435 \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e, \u043a\u0430\u043a \u043e\u0442 \u0432\u0441\u0435\u0433\u043e \u044d\u0442\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u043d\u0438\u0431\u0443\u0434\u044c \u0438\u0434\u0435\u0438, \u0447\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0435\u0449\u0451 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c, \u0438\u043b\u0438 \u044f \u0447\u0442\u043e-\u0442\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u043f\u043b\u043e\u0445\u043e, \u0442\u043e \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0438\u0448\u0438\u0442\u0435, \u044f \u044d\u0442\u043e \u0432\u0441\u0451 \u0443\u0447\u0442\u0443.  <\/p>\n<h2>\u0420\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438<\/h2>\n<ul>\n<li>\n<p><a href=\"https:\/\/stackoverflow.com\/a\/3252222\" rel=\"noopener noreferrer nofollow\"><u>\u041f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u044f\u043c\u044b\u0445<\/u><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/mathworld.wolfram.com\/Circle-LineIntersection.html\" rel=\"noopener noreferrer nofollow\"><u>\u041f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0435 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u044f\u043c\u043e\u0439<\/u><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/e-maxx.ru\/algo\/circles_intersection\" rel=\"noopener noreferrer nofollow\"><u>\u041f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0435 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0435\u0439<\/u><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/ManimCommunity\/manim\" rel=\"noopener noreferrer nofollow\"><u>\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438<\/u><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/Monotirg\/Math-animation\/tree\/main\/MathAnimation\/Other\" rel=\"noopener noreferrer nofollow\"><u>\u041a\u043e\u0434 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438<\/u><\/a><\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/755364\/\"> https:\/\/habr.com\/ru\/articles\/755364\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! <\/p>\n<p>\u041a\u0430\u043a-\u0442\u043e \u0440\u0430\u0437 \u043c\u043d\u0435 \u0437\u0430\u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u0446\u0438\u0440\u043a\u0443\u043b\u0435\u043c \u0438 \u043b\u0438\u043d\u0435\u0439\u043a\u043e\u0439. \u041d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0433\u0443\u0433\u043b\u0438\u0432, \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u043b, \u0447\u0442\u043e \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c <a href=\"https:\/\/en.wikipedia.org\/wiki\/Compass_(drawing_tool)\" rel=\"noopener noreferrer nofollow\"><em>compass<\/em> <\/a>\u044d\u0442\u043e<em> <\/em>\u0435\u0449\u0451 \u0438 \u0446\u0438\u0440\u043a\u0443\u043b\u044c, \u0438 \u0447\u0442\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0433\u043e\u0442\u043e\u0432\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0435\u0442. <\/p>\n<p>\u0412\u0441\u0451 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0435 \u0432\u044b\u043b\u0438\u043b\u043e\u0441\u044c \u0432 \u044d\u0442\u0443 \u0441\u0442\u0430\u0442\u044c\u044e.<\/p>\n<figure class=\"full-width\">\n<div><figcaption>\u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438<\/figcaption><\/div>\n<\/figure>\n<h2>\u041a\u043e\u0440\u043e\u0442\u043a\u043e\u0435 \u043f\u0440\u0435\u0434\u0438\u0441\u043b\u043e\u0432\u0438\u0435 <\/h2>\n<p>\u0421\u0442\u0430\u0442\u044c\u044e \u044f \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u043b \u043d\u0430 \u0434\u0432\u0435 \u0447\u0430\u0441\u0442\u0438. \u0412\u00a0\u043f\u0435\u0440\u0432\u043e\u0439\u00a0\u2014 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0438 \u043b\u0438\u043d\u0435\u0439\u043a\u0438, \u0430 \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u0439 \u0443\u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0445 \u043a\u043b\u0430\u0441\u0441\u043e\u0432.<\/p>\n<p>\u0421\u0440\u0430\u0437\u0443 \u043f\u0440\u043e\u044f\u0441\u043d\u044e \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b. \u041f\u0438\u0441\u0430\u0442\u044c \u044f \u0431\u0443\u0434\u0443 \u043d\u0430\u00a0python, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e\u00a0\u043d\u0443\u0436\u043d\u0430\u044f \u043c\u043d\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f\u00a0\u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 (\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430\u00a0\u043d\u0435\u0451 \u0432\u00a0\u043a\u043e\u043d\u0446\u0435) \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430 \u043d\u0430\u00a0\u043d\u0451\u043c. \u0415\u0449\u0451 \u044f \u043d\u0435\u00a0\u0441\u0442\u0430\u043b \u0438\u0434\u0442\u0438 \u043f\u043e\u00a0\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u043c\u0443 \u0448\u0430\u0431\u043b\u043e\u043d\u0443, \u043a\u043e\u0433\u0434\u0430 \u0434\u043b\u044f\u00a0\u0442\u043e\u0447\u0435\u043a \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430. \u041e\u043f\u044f\u0442\u044c \u0442\u0430\u043a\u0438 \u0438\u0437\u2011\u0437\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f\u00a0\u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438, \u0432\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432\u00a0\u043c\u0430\u0441\u0441\u0438\u0432\u0435 numpy.<\/p>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043f\u043e\u043a\u0430 \u0447\u0442\u043e \u043c\u043d\u0435 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e python \u0438 numpy.<\/p>\n<h2>\u0426\u0438\u0440\u043a\u0443\u043b\u044c<\/h2>\n<p>\u041f\u043e\u0434 \u0446\u0438\u0440\u043a\u0443\u043b\u0435\u043c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c \u0442\u0440\u0438 \u0442\u043e\u0447\u043a\u0438: <code>left<\/code> , <code>right<\/code> \u0438 <code>center<\/code>. \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0446\u0438\u0440\u043a\u0443\u043b\u044c \u043c\u043e\u0436\u043d\u043e \u0438 \u043f\u043e-\u0434\u0440\u0443\u0433\u043e\u043c\u0443, \u043d\u043e \u044d\u0442\u043e\u0442 \u0441\u043f\u043e\u0441\u043e\u0431 \u043c\u043d\u0435 \u043a\u0430\u0436\u0435\u0442\u0441\u044f \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u043c. <\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0422\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code> \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0440\u0430\u0432\u043d\u043e\u0443\u0434\u0430\u043b\u0435\u043d\u044b \u043e\u0442 \u0442\u043e\u0447\u043a\u0438 <code>center<\/code> \u0438 \u0432\u0441\u0435 \u0442\u0440\u0438 \u0442\u043e\u0447\u043a\u0438 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u043b\u0435\u0436\u0430\u0442\u044c \u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u043f\u0440\u044f\u043c\u043e\u0439. \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u044d\u0442\u043e \u0443\u0441\u043b\u043e\u0432\u0438\u0435, \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0434\u043b\u0438\u043d\u044b \u043e\u0442\u0440\u0435\u0437\u043a\u043e\u0432 <code>left<\/code>, <code>center<\/code> \u0438 <code>right<\/code>, <code>center<\/code>, \u0430 \u0442\u0430\u043a\u0436\u0435 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%9F%D1%81%D0%B5%D0%B2%D0%B4%D0%BE%D1%81%D0%BA%D0%B0%D0%BB%D1%8F%D1%80%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5\" rel=\"noopener noreferrer nofollow\">\u043f\u0441\u0435\u0432\u0434\u043e\u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/a> \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0445 \u043f\u043e \u0442\u043e\u0447\u043a\u0430\u043c <code>left<\/code>, <code>right<\/code> \u0438 <code>left<\/code>, <code>center<\/code>.<\/p>\n<p>\u041a\u043e\u0440\u043e\u0442\u043a\u043e \u043d\u0430\u043f\u043e\u043c\u043d\u044e, \u043f\u0441\u0435\u0432\u0434\u043e\u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u0430\u043a<\/p>\n<p>\u0438 \u043e\u043d\u043e \u0440\u0430\u0432\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0443\u043b\u044e \u0442\u043e\u0433\u0434\u0430 \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u044b \u043a\u043e\u043b\u043b\u0438\u043d\u0435\u0430\u0440\u043d\u044b. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043f\u043e \u0437\u043d\u0430\u043a\u0443 \u043c\u043e\u0436\u043d\u043e \u0441\u0443\u0434\u0438\u0442\u044c \u043e \u0432\u0437\u0430\u0438\u043c\u043d\u043e\u043c \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432.  <\/p>\n<figure class=\"full-width\">\n<div><figcaption>\u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438 a \u0438 b \u043c\u0435\u043d\u044c\u0448\u0435 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432, \u0442\u043e \u043f\u0441\u0435\u0432\u0434\u043e\u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435, \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435, \u0435\u0441\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u0435, \u0442\u043e \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 <\/figcaption><\/div>\n<\/figure>\n<p>\u041f\u043e \u0438\u0442\u043e\u0433\u0443, \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043a\u043b\u0430\u0441\u0441\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a.<\/p>\n<pre><code class=\"python\">class Compass:     def __init__(         self,         left: np.ndarray,         right: np.ndarray,         center: np.ndarray     ):         det = np.linalg.det(np.vstack((center - left, center - right)))                  if np.isclose(det, 0):             raise ValueError(\"collinear points\")         if np.isclose(                 np.linalg.norm(center - left), np.linalg.norm(center - right)             ):                 raise ValueError(\"invalid center\")                  self.left = left         self.right = right         self.center = center<\/code><\/pre>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0435\u0449\u0451 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430:<\/p>\n<ul>\n<li>\n<p><code>radius<\/code>\u00a0\u2014 \u0440\u0430\u0434\u0438\u0443\u0441 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0446\u0438\u0440\u043a\u0443\u043b\u044f.<\/p>\n<\/li>\n<li>\n<p><code>length<\/code>\u00a0\u2014 \u0434\u043b\u0438\u043d\u0430 \u043d\u043e\u0436\u043a\u0438 \u0446\u0438\u0440\u043a\u0443\u043b\u044f.<\/p>\n<\/li>\n<li>\n<p><code>outer_center<\/code> \u2014 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442, \u0433\u0434\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f <code>center<\/code> \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>. \u0415\u0441\u043b\u0438 <code>center<\/code> \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0441\u043b\u0435\u0432\u0430 \u043e\u0442 \u043f\u0440\u044f\u043c\u043e\u0439 \u0432 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043e\u0442 <code>left<\/code> \u043a <code>right<\/code>, \u0442\u043e <code>outer_center = True<\/code>, \u0438\u043d\u0430\u0447\u0435 <code>outer_center = False<\/code>. \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u044d\u0442\u043e \u0442\u0430\u043a\u0436\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0441\u0435\u0432\u0434\u043e\u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">@property def outer_center(self):     det = np.linalg.det(np.vstack((self.right - self.left, self.center - self.left)))     return True if det > 0 else False  @property def length(self):     return np.linalg.norm(self.center - self.left)  @property def radius(self):     return np.linalg.norm(self.right - self.left)<\/code><\/pre>\n<h3>\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b<\/h3>\n<p>\u041f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442\u044c \u043a \u043c\u0435\u0442\u043e\u0434\u0430\u043c, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u043c \u0437\u0430 \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0446\u0438\u0438 \u0441 \u0446\u0438\u0440\u043a\u0443\u043b\u0435\u043c, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b.<\/p>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439\u00a0\u2014 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043a \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0437\u044c\u043c\u0451\u043c \u0432\u0435\u043a\u0442\u043e\u0440, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u043f\u043e \u0442\u043e\u0447\u043a\u0430\u043c <code>left<\/code>, <code>right<\/code>. \u041d\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e \u0438 \u043f\u043e\u043c\u0435\u043d\u044f\u0435\u043c \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u043c\u0435\u0441\u0442\u0430\u043c\u0438, \u0443\u043c\u043d\u043e\u0436\u0438\u0432 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043e\u0434\u043d\u0443 \u0438\u0437 \u043d\u0438\u0445 \u043d\u0430 \u043c\u0438\u043d\u0443\u0441 \u0435\u0434\u0438\u043d\u0438\u0446\u0443.    <\/p>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442<\/summary>\n<div class=\"spoiler__content\">\n<p>\u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u0435\u0441\u043b\u0438 \u0443 \u0435\u0434\u0438\u043d\u0438\u0447\u043d\u043e\u0433\u043e \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u043c\u0435\u0441\u0442\u0430\u043c\u0438 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0438 \u0443\u043c\u043d\u043e\u0436\u0438\u0442\u044c \u043e\u0434\u043d\u0443 \u0438\u0437 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442 \u043d\u0430 \u043c\u0438\u043d\u0443\u0441 \u0435\u0434\u0438\u043d\u0438\u0446\u0443, \u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440 \u0431\u0443\u0434\u0435\u0442 \u0435\u0434\u0438\u043d\u0438\u0447\u043d\u044b\u043c, \u0447\u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e \u0444\u043e\u0440\u043c\u0443\u043b\u0435 \u0434\u043b\u0438\u043d\u044b \u0432\u0435\u043a\u0442\u043e\u0440\u0430.<\/p>\n<p>\u0412\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440 \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u043f\u0435\u043d\u0434\u0438\u043a\u0443\u043b\u044f\u0440\u0435\u043d \u043a \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u043c\u0443, \u0442\u0430\u043a \u043a\u0430\u043a \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0440\u0430\u0432\u043d\u043e \u043d\u0443\u043b\u044e, \u0447\u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435\u043c \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f. <\/p>\n<p>\u0415\u0441\u0442\u044c \u0435\u0449\u0451 \u0438 \u0441\u043e\u0432\u0441\u0435\u043c \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0435 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u0435, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u044b\u043c\u0438 \u0447\u0438\u0441\u043b\u0430\u043c\u0438 \u0438 \u0438\u0445 \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c. \u041f\u043e \u0441\u0443\u0442\u0438, \u043c\u044b \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0432\u0435\u043a\u0442\u043e\u0440\u0443 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u0438 \u0443\u043c\u043d\u043e\u0436\u0430\u0435\u043c \u0435\u0433\u043e \u043d\u0430 \u043c\u043d\u0438\u043c\u0443\u044e \u0435\u0434\u0438\u043d\u0438\u0446\u0443, \u0442\u0435\u043c \u0441\u0430\u043c\u044b\u043c \u043f\u043e\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0435\u0433\u043e \u043d\u0430 90 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432.<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>outer<\/code>, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u0439 \u0437\u0430 \u0432\u044b\u0431\u043e\u0440 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0435\u043a\u0442\u043e\u0440\u0430. \u0415\u0441\u043b\u0438 <code>outer = True<\/code>, \u0442\u043e \u0432\u0435\u043a\u0442\u043e\u0440 \u043d\u043e\u0440\u043c\u0430\u043b\u0438 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d \u0432\u043b\u0435\u0432\u043e \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>, \u0432 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u043f\u0440\u0430\u0432\u043e. <\/p>\n<pre><code class=\"python\">def _normal_vec(self, outer: bool):     vec = (self.right - self.left) \/ self.radius     normal_vec = np.array([-vec[1], vec[0]])     det = np.linalg.det(np.vstack((vec, normal_vec)))          if outer:         return np.sign(det) * normal_vec          return -np.sign(det) * normal_vec<\/code><\/pre>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439\u00a0\u2014 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u0443\u0433\u043b\u0430 \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438. \u0412\u0430\u0436\u043d\u043e, \u0447\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u043d\u0443\u0436\u0435\u043d \u043d\u0435 \u043d\u0430\u0438\u043c\u0435\u043d\u044c\u0448\u0438\u0439 \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438, \u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439. \u0422\u043e \u0435\u0441\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0443\u0433\u043e\u043b, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0432 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u043c \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0438 (\u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0438) \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u0441\u043e\u0432\u043f\u0430\u043b \u0441\u043e \u0432\u0442\u043e\u0440\u044b\u043c.  <\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f <a href=\"https:\/\/numpy.org\/doc\/stable\/reference\/generated\/numpy.arctan2\" rel=\"noopener noreferrer nofollow\">arctan2<\/a>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u043e\u0441\u044c\u044e\u0438 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u043c \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u043e\u0442 \u0434\u043e . <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 \u0438\u0441\u043a\u043e\u043c\u044b\u0439 \u0443\u0433\u043e\u043b, \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0434\u043b\u044f \u0434\u0432\u0443\u0445 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 \u0443\u0433\u043b\u044b \u0441 \u043e\u0441\u044c\u044e \u0438 \u0432\u0437\u044f\u0442\u044c \u0438\u0445 \u0440\u0430\u0437\u043d\u043e\u0441\u0442\u044c. \u0417\u0434\u0435\u0441\u044c \u0432\u0430\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c, \u043a\u0430\u043a\u043e\u0439 \u0443\u0433\u043e\u043b \u0438\u0437 \u043a\u0430\u043a\u043e\u0433\u043e \u0432\u044b\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u043c \u0441\u0447\u0438\u0442\u0430\u0442\u044c, \u0447\u0442\u043e \u043f\u043e\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u0435\u043a\u0442\u043e\u0440 <code>vec1<\/code> \u043a \u0432\u0435\u043a\u0442\u043e\u0440\u0443 <code>vec2<\/code>, \u0442\u043e \u0435\u0441\u0442\u044c \u0438\u0437 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0443\u0433\u043b\u0430 \u0432\u044b\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u044b\u0439.<\/p>\n<pre><code class=\"python\">def _angle_vec(self, vec1: np.ndarray, vec2: np.ndarray):     return np.arctan2(vec2[1], vec2[0]) - np.arctan2(vec1[1], vec1[0])<\/code><\/pre>\n<h3>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0446\u0435\u043d\u0442\u0440\u0430<\/h3>\n<p>\u0427\u0430\u0441\u0442\u043e \u043d\u0435\u0443\u0434\u043e\u0431\u043d\u043e \u0438 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0442\u043e\u0447\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c, \u0433\u0434\u0435  \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0442\u043e\u0447\u043a\u0430 <code>center<\/code>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0435\u0451 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u043f\u043e \u0442\u043e\u0447\u043a\u0430\u043c <code>left<\/code> \u0438 <code>right<\/code>. \u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>height<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0442\u043e\u0447\u043a\u0438 <code>center<\/code> \u0434\u043e \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>, \u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>outer<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0442\u043e, \u0433\u0434\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f <code>center<\/code> \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u044d\u0442\u043e\u0439 \u043f\u0440\u044f\u043c\u043e\u0439. <\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0412\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0442\u0435\u043c, \u0447\u0442\u043e \u043d\u0430 \u0442\u0440\u0435\u0445 \u0442\u043e\u0447\u043a\u0430\u0445 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0440\u0430\u0432\u043d\u043e\u0431\u0435\u0434\u0440\u0435\u043d\u043d\u044b\u0439 \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0430 \u0432 \u0440\u0430\u0432\u043d\u043e\u0431\u0435\u0434\u0440\u0435\u043d\u043d\u043e\u043c \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0435 \u0432\u044b\u0441\u043e\u0442\u0430, \u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u043d\u0430\u044f \u043a \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u044e, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438 \u043c\u0435\u0434\u0438\u0430\u043d\u043e\u0439. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0441\u0435\u0440\u0435\u0434\u0438\u043d\u0443 \u043e\u0442\u0440\u0435\u0437\u043a\u0430 <code>left<\/code>, <code>right<\/code> \u0438 \u043e\u0442\u043b\u043e\u0436\u0438\u0442\u044c \u043e\u0442 \u043d\u0435\u0451 \u0432\u0435\u043a\u0442\u043e\u0440 \u043d\u043e\u0440\u043c\u0430\u043b\u0438, \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u043d\u044b\u0439 \u043d\u0430 <code>height<\/code>. \u041a\u043e\u043d\u0435\u0446 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u0442\u043e\u0447\u043a\u043e\u0439 <code>center<\/code>.<\/p>\n<pre><code class=\"python\">def init_center(self, height: float, outer: bool):     self.center = 0.5 * (self.right + self.left) + height * self._normal_vec(outer)<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043a\u043b\u0430\u0441\u0441\u0430.<\/p>\n<pre><code class=\"python\">class Compass:     def __init__(         self,         left: np.ndarray,         right: np.ndarray,         center: np.ndarray = None,         outer_center: bool = True,     ):         self.left = left         self.right = right         self.center = center          if center is None:             self.init_center(self.radius, outer_center)         else:             det = np.linalg.det(np.vstack((center - left, center - right)))              if np.isclose(                 np.linalg.norm(center - left), np.linalg.norm(center - right)             ):                 raise ValueError(\"invalid center\")             if np.isclose(det, 0):                 raise ValueError(\"collinear points\")<\/code><\/pre>\n<h3>\u041f\u0435\u0440\u0435\u0432\u043e\u0440\u043e\u0442 <\/h3>\n<p>\u0414\u043b\u044f \u043f\u0435\u0440\u0435\u0432\u043e\u0440\u043e\u0442\u0430 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u0440\u0430\u0437\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0443 <code>center<\/code> \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>. \u0421\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u0445\u043e\u0434\u044f \u0438\u0437 \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0441\u043e\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439. \u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c, \u0447\u0442\u043e \u0446\u0438\u0440\u043a\u0443\u043b\u044c \u0443\u0436\u0435 \u043f\u0435\u0440\u0435\u0432\u0451\u0440\u043d\u0443\u0442. \u0422\u043e\u0433\u0434\u0430 \u043d\u0430 \u0442\u0440\u0435\u0445 \u0442\u043e\u0447\u043a\u0430\u0445 <code>left<\/code>, <code>right<\/code>, <code>center<\/code> \u0438 \u0442\u043e\u0447\u043a\u0435 <code>new_center<\/code>, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0439 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0432\u043e\u0440\u043e\u0442\u0430, \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0440\u043e\u043c\u0431. \u042d\u0442\u043e \u0434\u0430\u0451\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435. <\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u043f\u0440\u043e\u0442\u0438\u0432\u043e\u043f\u043e\u043b\u043e\u0436\u043d\u044b\u0435 \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u0440\u0430\u0432\u043d\u044b \u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b, \u0442\u043e \u0435\u0441\u0442\u044c \u0432\u0435\u043a\u0442\u043e\u0440, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u043f\u043e \u0442\u043e\u0447\u043a\u0430\u043c <code>left <\/code>\u0438 <code>new_center<\/code>, \u0440\u0430\u0432\u0435\u043d \u0432\u0435\u043a\u0442\u043e\u0440\u0443, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u043c\u0443 \u043f\u043e \u0442\u043e\u0447\u043a\u0430\u043c <code>center<\/code> \u0438 <code>right<\/code>. <\/p>\n<p>\u0412\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u0434\u0438\u0430\u0433\u043e\u043d\u0430\u043b\u0438 \u0440\u043e\u043c\u0431\u0430 \u043f\u0435\u0440\u043f\u0435\u043d\u0434\u0438\u043a\u0443\u043b\u044f\u0440\u043d\u044b \u0438 \u0434\u0435\u043b\u044f\u0442\u0441\u044f \u043f\u043e\u043f\u043e\u043b\u0430\u043c \u0432 \u0442\u043e\u0447\u043a\u0435 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u044f, \u0442\u043e \u0435\u0441\u0442\u044c \u0442\u043e\u0447\u043a\u0430 <code>new_center<\/code> \u0441\u0438\u043c\u043c\u0435\u0442\u0440\u0438\u0447\u043d\u0430 \u0442\u043e\u0447\u043a\u0435 <code>center<\/code> \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u044f\u043c\u043e\u0439, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code> \u0438 <code>right<\/code>.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 <code>new_center<\/code>, \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u043b\u043e\u0436\u0438\u0442\u044c \u0432\u0435\u043a\u0442\u043e\u0440 <code>center<\/code>, <code>right<\/code> \u043e\u0442 \u0442\u043e\u0447\u043a\u0438 <code>left<\/code>, \u0438 \u043a\u043e\u043d\u0435\u0446 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u0442\u043e\u0447\u043a\u043e\u0439 <code>new_center<\/code>.<\/p>\n<pre><code class=\"python\">def flip(self):     self.center = self.right + self.left - self.center<\/code><\/pre>\n<h3>\u041f\u043e\u0432\u043e\u0440\u043e\u0442\u044b<\/h3>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u043f\u0438\u0448\u0435\u043c \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 <code>point<\/code>.<\/p>\n<figure class=\"full-width\">\n<div><figcaption>\u041f\u043e\u0432\u043e\u0440\u043e\u0442 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0442\u043e\u0447\u043a\u0438 center<\/figcaption><\/div>\n<\/figure>\n<p>\u0414\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u044d\u0442\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c. \u041d\u0443\u0436\u043d\u043e \u0441\u0434\u0432\u0438\u043d\u0443\u0442\u044c \u0432\u0441\u0435 \u0442\u043e\u0447\u043a\u0438 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043d\u0430 <code>-point<\/code>, \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0432\u0448\u0438\u0435\u0441\u044f \u0442\u043e\u0447\u043a\u0438 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0430\u0447\u0430\u043b\u0430 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442, \u0442\u043e \u0435\u0441\u0442\u044c \u0443\u043c\u043d\u043e\u0436\u0438\u0442\u044c \u0441\u043b\u0435\u0432\u0430 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 <\/p>\n<p>\u0438 \u0441\u0434\u0432\u0438\u043d\u0443\u0442\u044c \u0438\u0445 \u043e\u0431\u0440\u0430\u0442\u043d\u043e. \u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0432\u0441\u0435 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u0432\u0438\u0434\u0435 \u0444\u043e\u0440\u043c\u0443\u043b\u044b, \u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043c<\/p>\n<pre><code class=\"python\">def rotate_about_point(self, angle: float, point: np.ndarray):     rot_mat = np.array([         [np.cos(angle), -np.sin(angle)],          [np.sin(angle), np.cos(angle)]     ])     self.left = np.matmul(rot_mat, self.left - point) + point     self.right = np.matmul(rot_mat, self.right - point) + point     self.center = np.matmul(rot_mat, self.center - point) + point<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f. <\/p>\n<pre><code class=\"python\"> def rotate_about_center(self, angle: float):     self.rotate_about_point(angle, self.center)  def rotate_about_left(self, angle: float):     self.rotate_about_point(angle, self.left)  def rotate_about_right(self, angle: float):     self.rotate_about_point(angle, self.right)<\/code><\/pre>\n<p>\u041d\u043e \u0435\u0441\u0442\u044c \u0438 \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u0442\u0438\u043f \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u043e\u0432, \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043e\u0434\u043d\u0443 \u0438\u0437 \u043d\u043e\u0436\u0435\u043a \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0430 \u043f\u043e\u043f\u0430\u043b\u0430 \u0432 \u0442\u043e\u0447\u043a\u0443 <code>point<\/code>.<\/p>\n<figure class=\"full-width\">\n<div><figcaption>\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 \u043f\u0435\u0440\u0432\u043e\u0439 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438, \u0433\u0434\u0435 \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f<\/figcaption><\/div>\n<\/figure>\n<p>\u0412\u0430\u0436\u043d\u043e, \u0447\u0442\u043e \u044d\u0442\u043e \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u043d\u043e\u0436\u043a\u0430 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u043f\u0430\u043b\u0430 \u0432 \u0442\u043e\u0447\u043a\u0443 <code>point<\/code>.<\/p>\n<pre><code class=\"python\">def rotate_left_to(self, point: np.ndarray):     angle = self._angle_vec(self.left - self.right, point - self.right)     self.rotate_about_right(angle)      if not np.allclose(self.left, point):         raise ValueError(\"can't rotate to chosen point\")  def rotate_right_to(self, point: np.ndarray):     angle = self._angle_vec(self.right - self.left, point - self.left)     self.rotate_about_left(angle)      if not np.allclose(self.right, point):         raise ValueError(\"can't rotate to chosen point\")<\/code><\/pre>\n<h3>\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0434\u0438\u0443\u0441\u0430<\/h3>\n<p>\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0440\u0430\u0434\u0438\u0443\u0441 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u043c\u043e\u0436\u043d\u043e \u0434\u0432\u0443\u043c\u044f \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c\u0438. \u041f\u0435\u0440\u0432\u044b\u0439, \u043a\u043e\u0433\u0434\u0430 \u043e\u0434\u043d\u0430 \u0438\u0437 \u043d\u043e\u0436\u0435\u043a \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u0442\u0441\u044f, \u0430 \u0434\u0440\u0443\u0433\u0430\u044f \u0441\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u0434\u043e \u043d\u0443\u0436\u043d\u043e\u0433\u043e \u0440\u0430\u0434\u0438\u0443\u0441\u0430. \u0412\u0442\u043e\u0440\u043e\u0439, \u043a\u043e\u0433\u0434\u0430 \u0434\u0432\u0435 \u043d\u043e\u0436\u043a\u0438 \u0441\u0434\u0432\u0438\u0433\u0430\u044e\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e. \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430.<\/p>\n<figure class=\"full-width\">\n<div><figcaption>\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0434\u0438\u0443\u0441\u0430 \u0446\u0438\u0440\u043a\u0443\u043b\u044f \u0441 \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043b\u0435\u0432\u043e\u0439 \u043d\u043e\u0436\u043a\u043e\u0439<\/figcaption><\/div>\n<\/figure>\n<p>\u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u043d\u0443\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0430 \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0438 \u0432 \u043a\u0430\u043a\u043e\u043c \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u0441\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u043e\u0434\u043d\u0430 \u0438\u0437 \u0442\u043e\u0447\u0435\u043a. \u0414\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u0441\u0442\u0438 \u0431\u0443\u0434\u0435\u043c \u0441\u0447\u0438\u0442\u0430\u0442\u044c, \u0447\u0442\u043e \u0441\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u0447\u043a\u0430 <code>right<\/code>. \u0422\u043e\u0433\u0434\u0430 \u043e\u043d\u0430<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-352507","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/352507","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=352507"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/352507\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=352507"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=352507"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=352507"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}