গণনাপদ্ধতি · লগ্নান্ত ও প্রতিবেদন · প্রবন্ধ ৩৮
লগ্নান্ত ও মাসিক লগ্ন তালিকা: দিনের ১২ রাশির উদয়-শেষ সময় কীভাবে নির্ণীত হয়?
লগ্ন একটি নির্দিষ্ট মুহূর্তের 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
- Location, time zone, month এবং report interval profile গ্রহণ করুন;
- প্রতিটি local date-এর sunrise ও next sunrise UTC instant নির্ণয় করুন;
- Start instant-এ Lahiri nirayana Ascendant নিন;
- পরবর্তী ৩০°-multiple boundary নির্ধারণ করুন;
- ছোট fixed/adaptive step-এ Ascendant sample করুন;
- ০° crossing সামলাতে longitude unwrap করুন;
- Unwrapped Ascendant boundary পার হলে bracket সংরক্ষণ করুন;
- Bisection দিয়ে crossing instant sub-second precision-এ solve করুন;
- Exact UTC instant সংরক্ষণ করে local time-এ render করুন;
- দিনের সব 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:
- Interval-এর midpoint tₘ নিন;
- tₘ-এ Ascendant গণনা ও unwrap করুন;
- Value ৯০°-এর কম হলে left = midpoint;
- অন্যথায় right = midpoint;
- 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:
- তারিখ ও বার;
- সূর্যোদয়ে চলমান লগ্ন;
- মেষ শেষ;
- বৃষ শেষ;
- মিথুন শেষ;
- …
- মীন শেষ।
একই 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_ascendantreturn করুন; - 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
- Latitude/longitude validated;
- East-positive longitude;
- Zone ID required;
- Sunrise profile explicit;
- Next sunrise later than sunrise;
- Interval half-open;
- JD input UT;
- Lahiri mode set under lock;
- Sidereal flag present;
- Cusp array length ১৩;
ascmclength ১০;- Ascendant normalized ০°–৩৬০°;
- Starting sign at exact ০°;
- Starting sign at ২৯°৫৯′;
- ৩৫৯.৮°→০.২° unwrap;
- Signed delta never confuses wrap;
- Negative delta raises polar status;
- Scan step positive;
- Root tolerance positive;
- Tolerance smaller than scan step;
- ৩০° boundary found;
- ৩৬০° boundary found;
- Boundary at sample endpoint;
- Boundary at report start;
- Boundary at next sunrise excluded;
- Root returned inside bracket;
- Root residual checked;
- Events strictly chronological;
- Starting/ending signs adjacent;
- Boundary longitude matches next sign;
- Normal day has expected cycle coverage;
- Repeated sign value retained;
- No dictionary overwrite;
- Post-midnight “পরদিন” label;
- UTC instant retained;
- ISO offset retained;
- DST spring-forward test;
- DST fall-back test;
- 23-hour civil day;
- 25-hour civil day;
- Kolkata reference month;
- Dhaka reference month;
- New York reference month;
- Bengali month date list;
- Gregorian leap February;
- Historical JD profile;
- High-latitude warning;
- HTML/API/XML agreement;
- Second-rounding carry;
- Cache key includes location/profile;
- Swiss version regression snapshot.
সাধারণ ভুল
| ভুল | সমস্যা | সমাধান |
|---|---|---|
| প্রতি লগ্ন ঠিক ২ ঘণ্টা | Latitude/season বাদ যায় | Ascendant boundary solve |
| প্রতি ঘণ্টায় sample | Fast sign crossing miss হতে পারে | ২ মিনিট বা adaptive scan |
| Sample time-ই লগ্নান্ত | Step-size পর্যন্ত error | Bisection refinement |
| ৩৫৯°→০°-কে backward ধরা | মীন→মেষ transition হারায় | Longitude unwrap |
| Midnight-এ row শেষ | Vedic sunrise-day ভেঙে যায় | Next sunrise পর্যন্ত scan |
| প্রতি sign-এ single time | Repeated transition overwrite | List of times |
| Local clock difference | DST duration ভুল | UTC instant arithmetic |
| Round করে তারপর sort | Event 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 যাচাইযোগ্য হবে।
তথ্যসূত্র ও আরও পাঠ
- Swiss Ephemeris—Programming Interface;
swe_houses_ex(),swe_houses_ex2(),ascmcও angle speeds. - Swiss Ephemeris—General Documentation; high-latitude ও polar house behavior.
- U.S. Naval Observatory—Sidereal Time; civil day-এ sidereal-time advance.
- NASA—Telling Time by the Stars; sidereal day-এর approximate duration.
- U.S. Naval Observatory—Computing Approximate Sidereal Time; UT Julian date ও sidereal calculation context.
- সূর্য সিদ্ধান্ত পঞ্জিকা project specification; Lahiri Ascendant, sunrise-based day, Bengali time labels ও monthly Lagna report format.
মন্তব্য, আলোচনা ও প্রশ্ন