সূর্য সিদ্ধান্ত পঞ্জিকা সকল প্রবন্ধ

লগ্নান্ত ও মাসিক লগ্ন তালিকা: দিনের ১২ রাশির উদয়-শেষ সময় কীভাবে নির্ণীত হয়?

Sunrise-to-sunrise লগ্নান্ত, Lahiri Ascendant-এর ৩০° boundary crossing, root-finding, DST ও C# 5 monthly lagna report generator।

গণনাপদ্ধতি · লগ্নান্ত ও প্রতিবেদন · প্রবন্ধ ৩৮

লগ্নান্ত ও মাসিক লগ্ন তালিকা: দিনের ১২ রাশির উদয়-শেষ সময় কীভাবে নির্ণীত হয়?

লগ্ন একটি নির্দিষ্ট মুহূর্তের Ascendant longitude; লগ্নান্ত হলো সেই longitude পরবর্তী নিরয়ণ রাশিসীমা অতিক্রম করার exact instant। মাসিক তালিকা তৈরির জন্য প্রতি মিনিটে rounded sign দেখে অনুমান না করে boundary-কে bracket করে numerical root solve করতে হয়। এই প্রবন্ধে sunrise-to-next-sunrise report এবং C# 5 implementation দেখানো হয়েছে।

এক বাক্যে মূলনীতি: Lahiri nirayana Ascendant যখন ০°, ৩০°, ৬০° … ৩৩০° সীমা অতিক্রম করে, পূর্ববর্তী লগ্ন শেষ এবং পরবর্তী লগ্ন শুরু হয়।

লগ্নান্ত কী?

কোনো মুহূর্তে Ascendant ৬৭° হলে সেটি মিথুন রাশির ৭°—অর্থাৎ মিথুন লগ্ন। Ascendant ৯০° স্পর্শ করলেই মিথুন লগ্ন শেষ এবং কর্কট লগ্ন শুরু। এই crossing instant-ই মিথুন লগ্নান্ত।

Longitude intervalলগ্নশেষ boundary
০° ≤ λ < ৩০°মেষ৩০°
৩০° ≤ λ < ৬০°বৃষ৬০°
৬০° ≤ λ < ৯০°মিথুন৯০°
………
৩৩০° ≤ λ < ৩৬০°মীন৩৬০°/০°

Boundary-তে interval convention হবে start-inclusive/end-exclusive। ঠিক ৯০°-এ নতুন কর্কট লগ্ন কার্যকর; একই instant-কে মিথুন ও কর্কট—দুই লগ্নে দেখানো হবে না।

Monthly report-এর “দিন” কোথা থেকে কোথায়?

তিনটি report interval সম্ভব:

  • Sunrise day: আজকের sunrise থেকে পরবর্তী sunrise—পঞ্জিকার জন্য প্রস্তাবিত;
  • Civil day: local midnight থেকে পরবর্তী midnight;
  • Custom interval: research বা event planning-এর নির্দিষ্ট start/end.

আপনার Printed Panjika ও Daily Details sunrise-based হওয়ায় monthly Lagna report-ও [sunrise, next sunrise) half-open interval ব্যবহার করা উচিত। মধ্যরাতের পরের লগ্নান্ত একই row-তে থাকবে এবং time-এর আগে “পরদিন” লেখা হবে।

Do not assume 24:00: Sunrise→next sunrise duration ঠিক ২৪ ঘণ্টা নাও হতে পারে। UTC instant দিয়ে interval তৈরি করুন; local clock subtraction করবেন না।

প্রতিটি লগ্ন কি দুই ঘণ্টা?

না। পৃথিবী প্রায় এক sidereal day-এ আকাশের তুলনায় একবার ঘোরে, কিন্তু ecliptic horizon-কে oblique angle-এ কাটে। Latitude, obliquity এবং season-এর কারণে ৩০° রাশিগুলোর rising duration অসমান হয়। “১২ রাশি ÷ ২৪ ঘণ্টা = প্রতি লগ্ন দুই ঘণ্টা” কেবল rough average।

এক sidereal day প্রায় ২৩ ঘণ্টা ৫৬ মিনিট; তাই প্রায় ২৪ ঘণ্টার sunrise interval-এ Ascendant সাধারণত একটি পূর্ণ cycle-এর সামান্য বেশি অতিক্রম করতে পারে। ফলে row-এর শুরুতে যে sign চলছে, interval শেষ হওয়ার আগে তার পরবর্তী cycle-এর transition-ও কখনও দেখা যেতে পারে। Report data structure-এ প্রতি sign-এর জন্য list রাখুন—single value নয়।

Calculation pipeline

  1. Location, time zone, month এবং report interval profile গ্রহণ করুন;
  2. প্রতিটি local date-এর sunrise ও next sunrise UTC instant নির্ণয় করুন;
  3. Start instant-এ Lahiri nirayana Ascendant নিন;
  4. পরবর্তী ৩০°-multiple boundary নির্ধারণ করুন;
  5. ছোট fixed/adaptive step-এ Ascendant sample করুন;
  6. ০° crossing সামলাতে longitude unwrap করুন;
  7. Unwrapped Ascendant boundary পার হলে bracket সংরক্ষণ করুন;
  8. Bisection দিয়ে crossing instant sub-second precision-এ solve করুন;
  9. Exact UTC instant সংরক্ষণ করে local time-এ render করুন;
  10. দিনের সব transitions chronological order-এ validate করুন।

Sampling interval

Dhaka, Kolkata ও New York-এর মতো non-polar location-এর জন্য ২ মিনিট step conservative এবং সহজ। Step কোনো final precision নয়; এটি শুধু root bracket খোঁজে। Bisection final instant-কে ০.২৫–১ সেকেন্ড tolerance-এ আনে।

০°/৩৬০° longitude unwrap

Ascendant ৩৫৯.৮° থেকে ০.২° হলে raw subtraction −৩৫৯.৬° দেখায়, যদিও প্রকৃত forward motion +০.৪°। তাই signed shortest delta দরকার:

static double Normalize360(double value)
{
    value %= 360.0;
    return value < 0.0 ? value + 360.0 : value;
}

static double SignedArcDelta(double from, double to)
{
    double delta = Normalize360(to - from);
    if (delta > 180.0)
        delta -= 360.0;
    return delta;
}

প্রথম Ascendant ৩৫৯.৮° হলে unwrapped value ৩৫৯.৮। পরের sample ০.২° হলে signed delta +০.৪; নতুন unwrapped value ৩৬০.২। ফলে ৩৬০° boundary সহজেই bracket হয়।

Root-finding: boundary কখন অতিক্রম করল?

ধরা যাক t₁-এ unwrapped Ascendant ৮৯.৭° এবং t₂-এ ৯০.৪°। ৯০° boundary এই interval-এ। Bisection:

  1. Interval-এর midpoint tₘ নিন;
  2. tₘ-এ Ascendant গণনা ও unwrap করুন;
  3. Value ৯০°-এর কম হলে left = midpoint;
  4. অন্যথায় right = midpoint;
  5. right−left tolerance-এর কম না হওয়া পর্যন্ত repeat করুন।

Bisection derivative চায় না এবং bracket ঠিক থাকলে স্থিতিশীল। Swiss Ephemeris-এর swe_houses_ex2() Ascendant/house speed-ও দিতে পারে; speed adaptive stepping বা Newton refinement-এ সহায়ক, কিন্তু final result bracketed method দিয়ে verify করা নিরাপদ।

C# 5 implementation

১. Models

public sealed class LagnaTransition
{
    public DateTimeOffset InstantUtc { get; set; }
    public int EndingRasiIndex { get; set; }   // 0 = Mesha
    public int StartingRasiIndex { get; set; }
    public double BoundaryLongitude { get; set; }
}

public sealed class DailyLagnaSchedule
{
    public DateTime LocalDate { get; set; }
    public DateTimeOffset Sunrise { get; set; }
    public DateTimeOffset NextSunrise { get; set; }
    public int StartingRasiIndex { get; set; }
    public IList<LagnaTransition> Transitions { get; set; }
}

public sealed class LagnaReportProfile
{
    public string AyanamsaKey { get; set; }
    public TimeSpan ScanStep { get; set; }
    public TimeSpan RootTolerance { get; set; }
    public string IntervalKey { get; set; }
}

২. Exact instant-এ Lahiri Ascendant

static readonly object SwissLock = new object();

static double ToJulianDayUt(DateTimeOffset instant)
{
    DateTime epoch = new DateTime(
        1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    return 2440587.5 +
        (instant.UtcDateTime - epoch).TotalDays;
}

static double AscendantAt(
    DateTimeOffset instant,
    double latitude,
    double longitude,
    SwissEph swe)
{
    double jdUt = ToJulianDayUt(instant);
    double[] cusps = new double[13];
    double[] ascmc = new double[10];

    lock (SwissLock)
    {
        swe.swe_set_sid_mode(
            SwissEph.SE_SIDM_LAHIRI, 0.0, 0.0);
        int status = swe.swe_houses_ex(
            jdUt, SwissEph.SEFLG_SIDEREAL,
            latitude, longitude, 'A', cusps, ascmc);
        if (status < 0)
            throw new InvalidOperationException(
                "Ascendant calculation failed.");
    }
    return Normalize360(ascmc[0]);
}

House code A এখানে শুধু supported house call-এর জন্য; Lagna value ascmc[0]। একই result পেতে অন্য standard house code-ও Ascendant পরিবর্তন করার কথা নয়। Project-wide consistency-এর জন্য একটি code স্থির রাখুন।

৩. Boundary bracket-এর ভিতর bisection

static DateTimeOffset FindBoundary(
    DateTimeOffset leftTime,
    DateTimeOffset rightTime,
    double leftAscendant,
    double leftUnwrapped,
    double target,
    TimeSpan tolerance,
    Func<DateTimeOffset, double> ascendantAt)
{
    DateTimeOffset lo = leftTime;
    DateTimeOffset hi = rightTime;

    while ((hi - lo) > tolerance)
    {
        long middleTicks = lo.UtcDateTime.Ticks +
            (hi.UtcDateTime.Ticks - lo.UtcDateTime.Ticks) / 2L;
        DateTimeOffset mid = new DateTimeOffset(
            new DateTime(middleTicks, DateTimeKind.Utc));

        double midAsc = ascendantAt(mid);
        double midUnwrapped = leftUnwrapped +
            SignedArcDelta(leftAscendant, midAsc);

        if (midUnwrapped < target)
            lo = mid;
        else
            hi = mid;
    }
    return hi;
}

৪. এক sunrise-day-এর transitions

static DailyLagnaSchedule BuildDailySchedule(
    DateTime localDate,
    DateTimeOffset sunrise,
    DateTimeOffset nextSunrise,
    Func<DateTimeOffset, double> ascendantAt,
    LagnaReportProfile profile)
{
    if (nextSunrise <= sunrise)
        throw new ArgumentException("Next sunrise must be later.");
    if (profile.ScanStep <= TimeSpan.Zero)
        throw new ArgumentException("Scan step must be positive.");

    List<LagnaTransition> events =
        new List<LagnaTransition>();
    DateTimeOffset leftTime = sunrise;
    double leftAsc = ascendantAt(leftTime);
    double leftUnwrapped = leftAsc;
    int startRasi = (int)Math.Floor(leftAsc / 30.0);
    double target = (Math.Floor(leftUnwrapped / 30.0) + 1.0) * 30.0;

    while (leftTime < nextSunrise)
    {
        DateTimeOffset rightTime = leftTime + profile.ScanStep;
        if (rightTime > nextSunrise)
            rightTime = nextSunrise;

        double rightAsc = ascendantAt(rightTime);
        double delta = SignedArcDelta(leftAsc, rightAsc);
        if (delta < -0.000001)
            throw new InvalidOperationException(
                "Ascendant was not monotonic; use the polar-safe profile.");

        double rightUnwrapped = leftUnwrapped + delta;
        while (target <= rightUnwrapped + 0.000000001)
        {
            DateTimeOffset root = FindBoundary(
                leftTime, rightTime,
                leftAsc, leftUnwrapped, target,
                profile.RootTolerance, ascendantAt);

            if (root < nextSunrise)
            {
                int starting = Mod((int)Math.Round(target / 30.0), 12);
                int ending = Mod(starting - 1, 12);
                events.Add(new LagnaTransition {
                    InstantUtc = root.ToUniversalTime(),
                    EndingRasiIndex = ending,
                    StartingRasiIndex = starting,
                    BoundaryLongitude = Normalize360(target)
                });
            }
            target += 30.0;
        }

        leftTime = rightTime;
        leftAsc = rightAsc;
        leftUnwrapped = rightUnwrapped;
    }

    return new DailyLagnaSchedule {
        LocalDate = localDate.Date,
        Sunrise = sunrise,
        NextSunrise = nextSunrise,
        StartingRasiIndex = startRasi,
        Transitions = events
    };
}
static int Mod(int value, int modulus)
{
    int result = value % modulus;
    return result < 0 ? result + modulus : result;
}

৫. Root accuracy profile

LagnaReportProfile profile = new LagnaReportProfile {
    AyanamsaKey = "lahiri",
    IntervalKey = "sunrise-to-next-sunrise",
    ScanStep = TimeSpan.FromMinutes(2),
    RootTolerance = TimeSpan.FromMilliseconds(250)
};

Display যদি nearest second হয়, ২৫০ ms root tolerance যথেষ্ট। Printed minute-only table-এর জন্যও internal precision কমাবেন না; exact event cache রেখে presentation-এ round করুন।

মাসিক লগ্ন report তৈরি

Gregorian month হলে local civil date ১ থেকে শেষ দিন iterate করুন। Bengali month হলে BengaliCalendarGridService-এর Gregorian dates ব্যবহার করুন। প্রতিটি row-এর জন্য sunrise service থেকে দুই consecutive sunrise নিয়ে daily schedule বানান।

static IList<DailyLagnaSchedule> BuildGregorianMonth(
    int year,
    int month,
    Func<DateTime, DateTimeOffset> sunriseForDate,
    Func<DateTimeOffset, double> ascendantAt,
    LagnaReportProfile profile)
{
    List<DailyLagnaSchedule> result =
        new List<DailyLagnaSchedule>();
    int days = DateTime.DaysInMonth(year, month);

    for (int day = 1; day <= days; day++)
    {
        DateTime date = new DateTime(year, month, day);
        DateTimeOffset sunrise = sunriseForDate(date);
        DateTimeOffset next = sunriseForDate(date.AddDays(1));
        result.Add(BuildDailySchedule(
            date, sunrise, next, ascendantAt, profile));
    }
    return result;
}

Table structure

প্রস্তাবিত columns:

  1. তারিখ ও বার;
  2. সূর্যোদয়ে চলমান লগ্ন;
  3. মেষ শেষ;
  4. বৃষ শেষ;
  5. মিথুন শেষ;
  6. …
  7. মীন শেষ।

একই sign interval-এর মধ্যে দুবার শেষ হলে cell-এ দুইটি time line-break দিয়ে রাখুন। Dictionary value হবে List<DateTimeOffset>, একটিমাত্র DateTimeOffset নয়।

static IList<DateTimeOffset> EndingTimesFor(
    DailyLagnaSchedule day,
    int rasiIndex)
{
    return day.Transitions
        .Where(x => x.EndingRasiIndex == rasiIndex)
        .Select(x => x.InstantUtc)
        .OrderBy(x => x)
        .ToList();
}

Local time, “পরদিন” এবং DST

Event root UTC-তে সংরক্ষণ করুন। Display-এর সময় target zone-এ convert করে row-এর local date-এর সঙ্গে তুলনা করুন। Calendar date বড় হলে prefix “পরদিন”; প্রয়োজনে “দুই দিন পরে”—যদিও normal sunrise interval-এ সেটি হওয়ার কথা নয়।

static string FormatReportTime(
    DateTimeOffset instantUtc,
    DateTime rowDate,
    TimeZoneInfo zone)
{
    DateTimeOffset local = TimeZoneInfo.ConvertTime(instantUtc, zone);
    string prefix = local.Date > rowDate.Date ? "পরদিন " : "";
    return prefix + local.ToString(
        "hh:mm:ss tt", CultureInfo.InvariantCulture);
}

DST transition day-এ একই clock time দুইবার দেখা যেতে পারে। তাই tooltip/API/XML-এ UTC offset-সহ ISO value রাখুন—যেমন 2026-11-01T01:30:00-04:00 বনাম -05:00। Printed table-এ ambiguity থাকলে offset abbreviation যোগ করুন।

Rounding policy

  • Calculation ও sorting raw UTC instant-এ;
  • Display rounding সবশেষে;
  • Seconds ৫৯.৫ হলে minute carry স্বাভাবিকভাবে হতে দিন;
  • Rounded time পরদিনে গেলে prefix rounded local date অনুযায়ী;
  • দুটি transition একই printed minute-এ পড়লে seconds বা order marker দেখান।

উচ্চ অক্ষাংশ ও polar-safe policy

Polar circle-এর বাইরে eastern-horizon/ecliptic geometry এবং কিছু house system অস্বাভাবিক আচরণ করতে পারে; Swiss Ephemeris কিছু unsupported system-এ Porphyry fallback এবং warning দেয়। Ascendant সর্বদা simple monotonic forward curve—এই assumption বিশ্বব্যাপী নিরাপদ নয়।

এই monthly report-এর standard solver profile Dhaka, Kolkata, New York এবং সাধারণ inhabited mid-latitude location-এর জন্য। Configurable threshold-এর বাইরে:

  • Signed delta negative হলে silently continue করবেন না;
  • Status non_monotonic_ascendant return করুন;
  • Smaller adaptive step ও discontinuity-aware solver ব্যবহার করুন;
  • Swiss warning text সংরক্ষণ করুন;
  • Missing/repeated rising signs table-এ স্পষ্ট লিখুন।

API output

{
  "profile": {
    "interval": "sunrise-to-next-sunrise",
    "ayanamsa": "Lahiri",
    "scan_step_seconds": 120,
    "root_tolerance_seconds": 0.25,
    "longitude_convention": "east-positive"
  },
  "date": "2026-10-08",
  "sunrise": "...",
  "next_sunrise": "...",
  "starting_lagna": "...",
  "transitions": [
    {
      "ending_rasi": "...",
      "starting_rasi": "...",
      "boundary_longitude": 0.0,
      "instant_utc": "...",
      "instant_local": "..."
    }
  ]
}

Ellipsis ও zero value schema illustration; calculated data নয়। API consumer যেন formatted string parse না করে—raw ISO instant এবং indexes ব্যবহার করে।

Validation checklist

  1. Latitude/longitude validated;
  2. East-positive longitude;
  3. Zone ID required;
  4. Sunrise profile explicit;
  5. Next sunrise later than sunrise;
  6. Interval half-open;
  7. JD input UT;
  8. Lahiri mode set under lock;
  9. Sidereal flag present;
  10. Cusp array length ১৩;
  11. ascmc length ১০;
  12. Ascendant normalized ০°–৩৬০°;
  13. Starting sign at exact ০°;
  14. Starting sign at ২৯°৫৯′;
  15. ৩৫৯.৮°→০.২° unwrap;
  16. Signed delta never confuses wrap;
  17. Negative delta raises polar status;
  18. Scan step positive;
  19. Root tolerance positive;
  20. Tolerance smaller than scan step;
  21. ৩০° boundary found;
  22. ৩৬০° boundary found;
  23. Boundary at sample endpoint;
  24. Boundary at report start;
  25. Boundary at next sunrise excluded;
  26. Root returned inside bracket;
  27. Root residual checked;
  28. Events strictly chronological;
  29. Starting/ending signs adjacent;
  30. Boundary longitude matches next sign;
  31. Normal day has expected cycle coverage;
  32. Repeated sign value retained;
  33. No dictionary overwrite;
  34. Post-midnight “পরদিন” label;
  35. UTC instant retained;
  36. ISO offset retained;
  37. DST spring-forward test;
  38. DST fall-back test;
  39. 23-hour civil day;
  40. 25-hour civil day;
  41. Kolkata reference month;
  42. Dhaka reference month;
  43. New York reference month;
  44. Bengali month date list;
  45. Gregorian leap February;
  46. Historical JD profile;
  47. High-latitude warning;
  48. HTML/API/XML agreement;
  49. Second-rounding carry;
  50. Cache key includes location/profile;
  51. Swiss version regression snapshot.

সাধারণ ভুল

ভুলসমস্যাসমাধান
প্রতি লগ্ন ঠিক ২ ঘণ্টাLatitude/season বাদ যায়Ascendant boundary solve
প্রতি ঘণ্টায় sampleFast sign crossing miss হতে পারে২ মিনিট বা adaptive scan
Sample time-ই লগ্নান্তStep-size পর্যন্ত errorBisection refinement
৩৫৯°→০°-কে backward ধরামীন→মেষ transition হারায়Longitude unwrap
Midnight-এ row শেষVedic sunrise-day ভেঙে যায়Next sunrise পর্যন্ত scan
প্রতি sign-এ single timeRepeated transition overwriteList of times
Local clock differenceDST duration ভুলUTC instant arithmetic
Round করে তারপর sortEvent order বদলাতে পারেRaw instant sort

উপসংহার

লগ্নান্ত হলো fixed two-hour slot নয়; এটি location ও instant-নির্ভর nirayana Ascendant-এর পরবর্তী ৩০° zodiac boundary crossing। তাই reliable monthly table-এর জন্য ephemeris evaluation এবং numerical root-finding দুটোই দরকার।

Sunrise-to-next-sunrise interval, longitude unwrapping, two-minute bracketing, sub-second bisection এবং UTC-first storage ব্যবহার করলে daily details, monthly report, printed Panjika, API ও XML একই event instant প্রকাশ করবে।

Normal mid-latitude profile এবং polar/discontinuity profile আলাদা রাখুন। একই sign-এর একাধিক transition retain করুন, post-midnight time-এ “পরদিন” দিন এবং calculation metadata প্রকাশ করুন—তবেই report যাচাইযোগ্য হবে।

তথ্যসূত্র ও আরও পাঠ

  1. Swiss Ephemeris—Programming Interface; swe_houses_ex(), swe_houses_ex2(), ascmc ও angle speeds.
  2. Swiss Ephemeris—General Documentation; high-latitude ও polar house behavior.
  3. U.S. Naval Observatory—Sidereal Time; civil day-এ sidereal-time advance.
  4. NASA—Telling Time by the Stars; sidereal day-এর approximate duration.
  5. U.S. Naval Observatory—Computing Approximate Sidereal Time; UT Julian date ও sidereal calculation context.
  6. সূর্য সিদ্ধান্ত পঞ্জিকা project specification; Lahiri Ascendant, sunrise-based day, Bengali time labels ও monthly Lagna report format.

মন্তব্য, আলোচনা ও প্রশ্ন