পাইথনের কোন বহুভুজের ভিতরে কোনও বিন্দু রয়েছে কিনা তা যাচাই করার দ্রুততম উপায় কী


87

একটি বহুভুজের মধ্যে কোনও বিন্দু অন্তর্ভুক্ত কিনা তা দেখার জন্য আমি দুটি প্রধান পদ্ধতি পেয়েছি। একটি এখানে ব্যবহৃত রে ট্রেসিং পদ্ধতিটি ব্যবহার করছে যা সবচেয়ে প্রস্তাবিত উত্তর, অন্যটি ম্যাটপ্ল্লোব ব্যবহার করছে path.contains_points(যা আমার কাছে কিছুটা অস্পষ্ট বলে মনে হয়)। আমি ক্রমাগত পয়েন্ট প্রচুর পরীক্ষা করতে হবে। এই দুটির মধ্যে যে কোনওটির তুলনায় অন্যটি সুপারিশযোগ্য বা আরও ভাল তৃতীয় বিকল্প রয়েছে কিনা তা কি কেউ জানেন?

হালনাগাদ:

আমি দুটি পদ্ধতি পরীক্ষা করে দেখেছি এবং ম্যাটপ্ল্লোলিব আরও দ্রুত দেখায়।

from time import time
import numpy as np
import matplotlib.path as mpltPath

# regular polygon for testing
lenpoly = 100
polygon = [[np.sin(x)+0.5,np.cos(x)+0.5] for x in np.linspace(0,2*np.pi,lenpoly)[:-1]]

# random points set of points to test 
N = 10000
points = np.random.rand(N,2)


# Ray tracing
def ray_tracing_method(x,y,poly):

    n = len(poly)
    inside = False

    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y

    return inside

start_time = time()
inside1 = [ray_tracing_method(point[0], point[1], polygon) for point in points]
print("Ray Tracing Elapsed time: " + str(time()-start_time))

# Matplotlib mplPath
start_time = time()
path = mpltPath.Path(polygon)
inside2 = path.contains_points(points)
print("Matplotlib contains_points Elapsed time: " + str(time()-start_time))

যা দেয়,

Ray Tracing Elapsed time: 0.441395998001
Matplotlib contains_points Elapsed time: 0.00994491577148

একই পক্ষের পার্থক্যটি 100 পক্ষের বহুভুজের পরিবর্তে ত্রিভুজ ব্যবহার করে এক পেয়েছিল। আমি এটিকেও সুষ্ঠুভাবে চেক করব কারণ এটি এই ধরণের সমস্যার জন্য উত্সর্গীকৃত একটি প্যাকেজ দেখাচ্ছে


যেহেতু ম্যাটপ্ল্লিটিবের বাস্তবায়ন সি ++, আপনি সম্ভবত এটি আরও দ্রুত হতে পারে বলে আশা করতে পারেন। বিবেচনা করে দেখুন যে ম্যাটপ্লোটলিব খুব ব্যাপকভাবে ব্যবহৃত হয়েছে এবং যেহেতু এটি একটি খুব মৌলিক কাজ - এটি সম্ভবত সঠিকভাবে কাজ করছে এটি ধরে নেওয়াও নিরাপদ (যদিও এটি "অস্পষ্ট" বলে মনে হচ্ছে)। সর্বশেষ তবে অন্তত নয়: কেন এটি কেবল পরীক্ষা করা হয় না?
সেবাস্তিয়ান

আমি পরীক্ষার সাথে প্রশ্নটি আপডেট করেছি, যেমন আপনি পূর্বাভাস দিয়েছেন, ম্যাটপ্ল্লোলিব আরও দ্রুত। আমি উদ্বিগ্ন ছিলাম কারণ ম্যাটপ্লটলিব যে জায়গাগুলি আমি দেখেছি সেখানে সর্বাধিক বিখ্যাত প্রতিক্রিয়া নয় এবং আমি জানতে চেয়েছিলাম যে আমি কোনও কিছু (বা আরও ভাল প্যাকেজ) উপেক্ষা করছি কিনা। এছাড়াও ম্যাটপ্ল্লিটিবকে এমন একটি সাধারণ প্রশ্নের জন্য বড় লোক বলে মনে হয়েছিল ।
রুবেন পেরেজ-ক্যারাসকো

উত্তর:


106

আপনি সুস্পষ্টভাবে বিবেচনা করতে পারেন :

from shapely.geometry import Point
from shapely.geometry.polygon import Polygon

point = Point(0.5, 0.5)
polygon = Polygon([(0, 0), (0, 1), (1, 1), (1, 0)])
print(polygon.contains(point))

আপনি যে পদ্ধতিগুলি উল্লেখ করেছেন সেগুলি থেকে আমি কেবল দ্বিতীয়টি ব্যবহার করেছি path.contains_points, এবং এটি দুর্দান্ত কাজ করে। যে কোনও ক্ষেত্রে আপনার পরীক্ষার জন্য আপনার প্রয়োজনীয় নির্ভুলতার উপর নির্ভর করে আমি বহুভুজের অভ্যন্তরে সমস্ত নোডের সাথে একটি নম্পি বুল গ্রিড তৈরি করার পরামর্শ দিচ্ছি সত্য (যদি না হয় তবে মিথ্যা)। আপনি যদি অনেকগুলি পয়েন্টের জন্য পরীক্ষা করতে চলেছেন তবে এটি দ্রুত হতে পারে ( যদিও লক্ষ্য করুন এটি নির্ভর করে যে আপনি একটি "পিক্সেল" সহনশীলতার মধ্যে পরীক্ষা দিচ্ছেন ):

from matplotlib import path
import matplotlib.pyplot as plt
import numpy as np

first = -3
size  = (3-first)/100
xv,yv = np.meshgrid(np.linspace(-3,3,100),np.linspace(-3,3,100))
p = path.Path([(0,0), (0, 1), (1, 1), (1, 0)])  # square with legs length 1 and bottom left corner at the origin
flags = p.contains_points(np.hstack((xv.flatten()[:,np.newaxis],yv.flatten()[:,np.newaxis])))
grid = np.zeros((101,101),dtype='bool')
grid[((xv.flatten()-first)/size).astype('int'),((yv.flatten()-first)/size).astype('int')] = flags

xi,yi = np.random.randint(-300,300,100)/100,np.random.randint(-300,300,100)/100
vflag = grid[((xi-first)/size).astype('int'),((yi-first)/size).astype('int')]
plt.imshow(grid.T,origin='lower',interpolation='nearest',cmap='binary')
plt.scatter(((xi-first)/size).astype('int'),((yi-first)/size).astype('int'),c=vflag,cmap='Greens',s=90)
plt.show()

, ফলাফলগুলি হ'ল:

পিক্সেল সহনশীলতার মধ্যে বহুভুজ ভিতরে পয়েন্ট


4
ধন্যবাদ, তার জন্য, এই মুহুর্তের জন্য আমি ম্যাটপ্লটলিবের সাথে লেগে থাকব কারণ এটি কাস্টম রশ্মির ট্রেসিংয়ের চেয়ে অনেক দ্রুত বলে মনে হচ্ছে। তবুও, আমি সত্যিই স্পেস ডিস্রিটেইসেশন উত্তরটি পছন্দ করি, ভবিষ্যতে আমার এটির প্রয়োজন হতে পারে। এই ধরণের সমস্যার জন্য উত্সর্গীকৃত একটি প্যাকেজ দেখায় আমি রুক্ষভাবেও যাচাই করব
রুবেন পেরেজ-ক্যারাসকো

20

গতি যদি আপনার প্রয়োজন হয় এবং অতিরিক্ত নির্ভরতা কোনও সমস্যা না হয় তবে আপনি সম্ভবত numbaবেশ দরকারী (এখন এটি কোনও প্ল্যাটফর্মে ইনস্টল করা বেশ সহজ)। ray_tracingআপনার প্রস্তাবিত ক্লাসিক পদ্ধতির সাজসজ্জার numbaব্যবহার করে numba @jitএবং বহুভুজকে একটি নিম্পী অ্যারেতে কাস্ট করে সহজেই পোর্ট করা যায় । কোডটি দেখতে এমন হওয়া উচিত:

@jit(nopython=True)
def ray_tracing(x,y,poly):
    n = len(poly)
    inside = False
    p2x = 0.0
    p2y = 0.0
    xints = 0.0
    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y

    return inside

প্রথম সম্পাদন পরবর্তী পরবর্তী কলগুলির চেয়ে কিছুটা বেশি সময় নেবে:

%%time
polygon=np.array(polygon)
inside1 = [numba_ray_tracing_method(point[0], point[1], polygon) for 
point in points]

CPU times: user 129 ms, sys: 4.08 ms, total: 133 ms
Wall time: 132 ms

সংকলনের পরে কোনটি হ্রাস পাবে:

CPU times: user 18.7 ms, sys: 320 µs, total: 19.1 ms
Wall time: 18.4 ms

ফাংশনের প্রথম কলটিতে আপনার যদি গতির প্রয়োজন হয় তবে আপনি একটি মডিউল ব্যবহার করে কোডটি প্রাক-সংকলন করতে পারেন pycc। একটি src.py এ ফাংশন সংরক্ষণ করুন:

from numba import jit
from numba.pycc import CC
cc = CC('nbspatial')


@cc.export('ray_tracing',  'b1(f8, f8, f8[:,:])')
@jit(nopython=True)
def ray_tracing(x,y,poly):
    n = len(poly)
    inside = False
    p2x = 0.0
    p2y = 0.0
    xints = 0.0
    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y

    return inside


if __name__ == "__main__":
    cc.compile()

এটি দিয়ে তৈরি করুন python src.pyএবং চালান:

import nbspatial

import numpy as np
lenpoly = 100
polygon = [[np.sin(x)+0.5,np.cos(x)+0.5] for x in 
np.linspace(0,2*np.pi,lenpoly)[:-1]]

# random points set of points to test 
N = 10000
# making a list instead of a generator to help debug
points = zip(np.random.random(N),np.random.random(N))

polygon = np.array(polygon)

%%time
result = [nbspatial.ray_tracing(point[0], point[1], polygon) for point in points]

CPU times: user 20.7 ms, sys: 64 µs, total: 20.8 ms
Wall time: 19.9 ms

নাম্বার কোডটিতে আমি ব্যবহার করেছি: 'বি 1 (f8, f8, f8 [:,:])'

সংকলন করার জন্য nopython=True, প্রতিটি ভার এর আগে ডিক্লেয়ার করা দরকার for loop

প্রি-বিল্ড এসআরসি কোডে লাইনটি:

@cc.export('ray_tracing' , 'b1(f8, f8, f8[:,:])')

ইনপুট হিসাবে ফাংশনটির নাম এবং এর আই / ও ভার প্রকার, একটি বুলিয়ান আউটপুট b1এবং দুটি ফ্লোট f8এবং একটি ত্রিমাত্রিক অ্যারে ভাসমান f8[:,:]হিসাবে ঘোষিত করতে ব্যবহৃত হয়।

জানুয়ারী / 4/2021 সম্পাদনা করুন

আমার ব্যবহারের ক্ষেত্রে, আমাকে একাধিক পয়েন্টগুলি একক বহুভুজের ভিতরে রয়েছে কিনা তা খতিয়ে দেখা দরকার - এরকম প্রসঙ্গে, পয়েন্টের একটি সিরিজ ধরে লুপ করতে নাম্বা সমান্তরাল ক্ষমতাগুলির সুবিধা নেওয়া দরকারী। উপরের উদাহরণটিতে এতে পরিবর্তন করা যেতে পারে:

from numba import jit, njit
import numba
import numpy as np 

@jit(nopython=True)
def pointinpolygon(x,y,poly):
    n = len(poly)
    inside = False
    p2x = 0.0
    p2y = 0.0
    xints = 0.0
    p1x,p1y = poly[0]
    for i in numba.prange(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y

    return inside


@njit(parallel=True)
def parallelpointinpolygon(points, polygon):
    D = np.empty(len(points), dtype=numba.boolean) 
    for i in numba.prange(1, len(D)):
        D[i] = pointinpolygon(points[i,0], points[i,1], polygon)
    return D    

দ্রষ্টব্য: উপরের কোডটি প্রাক-সংকলন নাম্বার সমান্তরাল ক্ষমতা সক্ষম করবে না (সমান্তরাল সিপিইউ লক্ষ্য pycc/AOTসংকলন দ্বারা সমর্থিত নয় ) দেখুন: https://github.com/numba/numba/issues/3336

পরীক্ষা:


import numpy as np
lenpoly = 100
polygon = [[np.sin(x)+0.5,np.cos(x)+0.5] for x in np.linspace(0,2*np.pi,lenpoly)[:-1]]
polygon = np.array(polygon)
N = 10000
points = np.random.uniform(-1.5, 1.5, size=(N, 2))

জন্য N=10000একটি 72 কোর মেশিনে, ফেরৎ:

%%timeit
parallelpointinpolygon(points, polygon)
# 480 µs ± 8.19 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

11

আপনার পরীক্ষাটি ভাল, তবে এটি কেবলমাত্র কিছু নির্দিষ্ট পরিস্থিতি পরিমাপ করে: বহুভুজের সাথে আমাদের পরীক্ষা করার জন্য আমাদের একটি বহুভুজ রয়েছে এবং পয়েন্টগুলির দীর্ঘ অ্যারে রয়েছে।

তদুপরি, আমি মনে করি আপনি ম্যাটপ্ল্লোব-অভ্যন্তরের বহুভুজ-পদ্ধতি বনাম রে-পদ্ধতি মাপছেন না, তবে ম্যাটপ্ল্লোব-কোনওরকম-অনুকূলিত-পুনরাবৃত্তি বনাম সাধারণ তালিকা-পুনরাবৃত্তি

N কে আলাদা আলাদা তুলনা করা যাক (পয়েন্ট এবং বহুভুজের জোড়া জোড়া)?

# ... your code...
lenpoly = 100
polygon = [[np.sin(x)+0.5,np.cos(x)+0.5] for x in np.linspace(0,2*np.pi,lenpoly)[:-1]]

M = 10000
start_time = time()
# Ray tracing
for i in range(M):
    x,y = np.random.random(), np.random.random()
    inside1 = ray_tracing_method(x,y, polygon)
print "Ray Tracing Elapsed time: " + str(time()-start_time)

# Matplotlib mplPath
start_time = time()
for i in range(M):
    x,y = np.random.random(), np.random.random()
    inside2 = path.contains_points([[x,y]])
print "Matplotlib contains_points Elapsed time: " + str(time()-start_time)

ফলাফল:

Ray Tracing Elapsed time: 0.548588991165
Matplotlib contains_points Elapsed time: 0.103765010834

ম্যাটপ্ল্লিটিব এখনও আরও অনেক ভাল তবে 100 গুণ ভাল নয়। এখন অনেক সহজ বহুভুজ চেষ্টা করুন ...

lenpoly = 5
# ... same code

ফলাফল:

Ray Tracing Elapsed time: 0.0727779865265
Matplotlib contains_points Elapsed time: 0.105288982391

6

আমি এটি এখানেই রেখে দেব, কেবল নমির ব্যবহার করে উপরের কোডটি পুনরায় লিখে ফেললাম, কারওর পক্ষে এটি দরকারী মনে হয়েছে:

def ray_tracing_numpy(x,y,poly):
    n = len(poly)
    inside = np.zeros(len(x),np.bool_)
    p2x = 0.0
    p2y = 0.0
    xints = 0.0
    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        idx = np.nonzero((y > min(p1y,p2y)) & (y <= max(p1y,p2y)) & (x <= max(p1x,p2x)))[0]
        if p1y != p2y:
            xints = (y[idx]-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
        if p1x == p2x:
            inside[idx] = ~inside[idx]
        else:
            idxx = idx[x[idx] <= xints]
            inside[idxx] = ~inside[idxx]    

        p1x,p1y = p2x,p2y
    return inside    

গুটিয়ে রে_ট্র্যাকিং এ

def ray_tracing_mult(x,y,poly):
    return [ray_tracing(xi, yi, poly[:-1,:]) for xi,yi in zip(x,y)]

100000 পয়েন্টে পরীক্ষিত, ফলাফল:

ray_tracing_mult 0:00:00.850656
ray_tracing_numpy 0:00:00.003769

একটি পলি এবং এক x, y এর জন্য আমি কীভাবে সত্য বা মিথ্যা ফিরিয়ে দিতে পারি?
জাসার ওরিওন

আপনি যদি কেবল একটি পলিই করেন তবে আমি @ পেফ্যানিও সলিউশনটি ব্যবহার করব। NumPy সমাধান বৃহত্তর ব্যাচগুলিতে গণনার জন্য ভাল।
হিচাবী তারতানোগলু
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.