2025, Oct 31 10:33

SymPy में सम्मिश्र मूलों का तेज़ प्लॉट और संख्यात्मक हल

SymPy से उच्च-डिग्री बहुपदों के सम्मिश्र मूल तेज़ी से निकालें और प्लॉट करें: solveset से बचें, domain coloring, nsolve व Poly.nroots सीखें; यूनिट सर्कल संदर्भ.

जब आप किसी उच्च-डिग्री SymPy अभिव्यक्ति के सम्मिश्र मूल निकालकर उन्हें प्लॉट करने की कोशिश करते हैं, तो देखने में मासूम-सा एक कॉल पूरा प्रोसेस रोक सकता है। इसकी आम वजह यह होती है कि संख्यात्मक मॉड्यूल्स को प्रतीकात्मक मॉड्यूल्स के साथ मिला दिया जाता है और फिर प्लॉट से ठीक पहले भारी प्रतीकात्मक मूलों को जबरन संख्यात्मक रूप में मूल्यांकित कराया जाता है। नीचे संक्षेप में बताया गया है कि दिक्कत कहाँ आती है और उससे कैसे निपटें—साथ ही विज़ुअलाइज़ेशन और संख्यात्मक रूट-फाइंडिंग के मजबूत विकल्प भी दिए गए हैं।

समस्या की रूपरेखा

नीचे दिया गया स्निपेट math से निकले पैरामीटर्स से एक उच्च-डिग्री बहुपद बनाता है, solveset के जरिए प्रतीकात्मक रूप से सभी सम्मिश्र मूल माँगता है, और फिर प्लॉटिंग के लिए उनके वास्तविक और काल्पनिक भाग निकालने की कोशिश करता है। प्लॉट दिखने से पहले ही यह अटक सकता है।

from __future__ import division
from sympy import *
import numpy as np
import math
import matplotlib.pyplot as mp
u1, u2, w, phi = symbols('u1 u2 w phi')
step = 0.04 * math.pi
lag = 2.0 * math.pi / step
print(lag)
root_set = solveset(w**math.floor(lag + 0.5) + 1 - w, w, domain=Complexes)
print(root_set)
ang = np.linspace(0, 2 * np.pi, 100)
ux = np.cos(ang)
uy = np.sin(ang)
re_vals = [rv.as_real_imag()[0] for rv in root_set]
im_vals = [rv.as_real_imag()[1] for rv in root_set]
mp.figure(figsize=(6, 6))
mp.scatter(re_vals, im_vals, color='green', marker='o', label='Complex Solutions')
mp.plot(ux, uy, 'b--', alpha=0.5, label='Unit Circle Reference')
mp.xlabel('Real Axis')
mp.ylabel('Imaginary Axis')
mp.title('Solutions on Unit Circle')
mp.gca().set_aspect('equal')
mp.grid(True)
mp.legend()
mp.show()

असल में गलत क्या होता है

मुख्य अभिव्यक्ति है w**floor(lag + 1/2) + 1 - w, जहाँ floor(lag + 1/2) का मान 50 निकलता है। इस पर solveset कॉल करने से सम्मिश्र डोमेन में 50 मूलों का सेट मिलता है। इनमें से कई ComplexRootOf ऑब्जेक्ट होते हैं—ये सटीक बीजीय संख्याएँ हैं, पर अभी संख्यात्मक रूप में नहीं निकाले गए। इन्हें प्लॉट करने के लिए फ्लोट में बदलना संख्यात्मक मूल्यांकन मांगता है। इस केस में एक अकेले मूल का संख्यात्मक मान निकालना भी बहुत समय ले सकता है। नीचे छोटा-सा प्रयोग बाधा की जड़ दिखाता है:

rt_list = list(root_set)
# सिर्फ एक प्रविष्टि का मूल्यांकन करना भी अत्यधिक धीमा हो सकता है
# rt_list[10].n()

एक दूसरी मुश्किल भी है: math (जो float देता है) और sympy (जो सटीकता बचा सकता है) को मिलाने से SymPy शुरू में ही संख्यात्मक, न कि सटीक, अंकगणित की ओर धक्का खा लेता है—और प्रतीकात्मक अभिव्यक्ति बनाते समय यह अक्सर वांछनीय नहीं होता।

भरोसेमंद समाधान

पहला उपाय है कि अभिव्यक्ति पूरी तरह SymPy ऑब्जेक्ट्स से बनाई जाए। इससे सटीक अंकगणित और प्रतीकात्मक क्रियाएँ एकरूप और तेज़ रहती हैं। दूसरा उपाय है कि जब आपको सिर्फ विज़ुअल या संख्यात्मक अनुमान चाहिए हों, तब प्रतीकात्मक रूप से मूल निकालने की ज़िद न करें। नीचे दो व्यावहारिक रास्ते दिए हैं।

प्रतीकात्मक मूल निकाले बिना तेज़ विज़ुअलाइज़ेशन

डोमेन कलरिंग बिना कुछ हल किए ही सम्मिश्र तल में शून्यों और ध्रुवों की तस्वीर तुरंत दिखा देती है। नीचे SymPy Plotting Backend का उपयोग कर परिमाण पैटर्न रेंडर किया गया है और उसके ऊपर यूनिट सर्कल ओवरले किया गया है।

from sympy import *
from spb import *
xv, yv, w, ang = symbols('xv yv w ang')
# exact SymPy parameters
step_q = Rational(4, 100) * pi
lag_q = 2 * pi / step_q
pow_deg = floor(lag_q + Rational(1, 2))
poly_f = w**pow_deg + 1 - w
scl = 1.05
graphics(
    domain_coloring(poly_f, (w, -scl - scl*I, scl + scl*I), coloring='k', n=500),
    line_parametric_2d(cos(ang), sin(ang), (ang, 0, 2*pi), rendering_kw={'color': 'b'}, use_cm=False, label='Unit Circle', show_in_legend=False),
    grid=False, aspect='equal', size=(10, 10)
)

इस परिमाण दृश्य में शून्य काले बिंदुओं की तरह दिखते हैं। इस अभिव्यक्ति के लिए वे यूनिट सर्कल के आसपास फैले नजर आते हैं, और यह प्लॉट solveset की ज़रूरत के बिना तुरंत सब दिखा देता है।

जब संख्यात्मक मूल वाकई चाहिए हों

अगर आपको सभी मूलों के संख्यात्मक अनुमान चाहिए, तो किसी संख्यात्मक सॉल्वर को अच्छे शुरुआती अनुमान के साथ इस्तेमाल करें या बहुपद API का लाभ लें। दोनों तरीके इस केस में तेज़ हैं।

पहला तरीका nsolve को यूनिट सर्कल पर बीज (सीड) देकर मूल इकट्ठा करता है:

from sympy import *
from spb import *
w, th = symbols('w th')
step_q = Rational(4, 100) * pi
lag_q = 2 * pi / step_q
pow_deg = floor(lag_q + Rational(1, 2))
poly_f = w**pow_deg + 1 - w
root_count = 50
offset = (2 * pi / root_count) / 2
found_roots = []
for k in range(root_count):
    theta = 2 * pi / root_count * k + offset
    guess = cos(theta) + I * sin(theta)
    try:
        sol = nsolve([poly_f], [w], [complex(guess)])
        found_roots.append(sol[0, 0])
    except:
        pass
scl = 1.05
th = symbols('th')
graphics(
    domain_coloring(poly_f, (w, -scl - scl*I, scl + scl*I), coloring='k', n=500),
    line_parametric_2d(cos(th), sin(th), (th, 0, 2*pi), rendering_kw={'color': 'b'}, use_cm=False, label='Unit Circle', show_in_legend=False),
    complex_points(*found_roots, rendering_kw={'marker': '.', 'color': 'r'}),
    grid=False, aspect='equal', xlabel='Real', ylabel='Imaginary'
)

दूसरा तरीका बहुपद रूप बनाकर सीधे संख्यात्मक मूल निकालता है:

from sympy import *
w = symbols('w')
step_q = Rational(4, 100) * pi
lag_q = 2 * pi / step_q
pow_deg = floor(lag_q + Rational(1, 2))
poly_f = w**pow_deg + 1 - w
prec_digits = int(pow_deg)
P = Poly(poly_f, w)
roots_numeric = P.nroots(n=prec_digits)
print(roots_numeric)

यह क्यों मायने रखता है

जब SymPy ComplexRootOf ऑब्जेक्ट लौटाता है, तो उन्हें फ्लोट में बदलना कुछ उच्च-डिग्री अभिव्यक्तियों के लिए बेहद धीमा हो सकता है। उन मूलों के वास्तविक और काल्पनिक भाग निकालकर प्लॉट करने की कोशिश अनजाने में उसी भारी संख्यात्मक स्टेप को ट्रिगर करती है, जिससे सब रुक-सा जाता है। SymPy की मूलभूत संरचनाएँ इस्तेमाल करके अभिव्यक्तियाँ बनाना और सही समय पर संख्यात्मक रणनीतियों पर स्विच करना इस फंदे से पूरी तरह बचा लेता है। एक व्यावहारिक टिप्पणी यह भी है कि मूल्यांकन काफी तेज़ चल सकता है, अगर द्विघात पद का गुणांक एक के क़रीब (पर ठीक एक नहीं) हो और मूल यूनिट सर्कल के पास रहें।

निष्कर्ष

प्रतीकात्मक और संख्यात्मक संसारों को अलग रखें: अभिव्यक्तियाँ बनाते समय SymPy के Rational, pi और floor का सहारा लें, math को मिलाने से बचें। अगर आपको सिर्फ चित्र या फ्लोटिंग-पॉइंट मान चाहिए, तो सटीक मूल पर ज़ोर न दें; डोमेन कलरिंग और संख्यात्मक रूट-फाइंडिंग यहाँ सही औज़ार हैं। अगर सभी मूल चाहिए हों, तो nsolve को यूनिट सर्कल के चारों ओर सीड करें या Poly.nroots का उपयोग कर तेज़ी से संख्यात्मक अनुमान पाएं। इस तरह, जहाँ सटीकता फायदेमंद है वहाँ उसे बनाए रखते हैं और जहाँ लाभ है वहाँ संख्यात्मक तरीकों पर स्विच कर लेते हैं।

यह लेख StackOverflow पर एक प्रश्न (लेखक: mike marchywka) और Davide_sd के उत्तर पर आधारित है।