"""Norse North Atlantic: a second, independent human wave with a failed frontier, tabulated like the Pacific.""" import os import numpy as np import pandas as pd from common import CUR, DATA, SEED, dump_json, haversine, jeffreys def run(verbose=True): rng = np.random.default_rng(SEED + 7) u = pd.read_csv(os.path.join(CUR, "norse_units.csv")) ix = {k: i for i, k in enumerate(u.unit)} u["hop_km"] = [float(haversine(r.lat, r.lon, u.lat[ix[r.parent]], u.lon[ix[r.parent]])) if isinstance(r.parent, str) else np.nan for _, r in u.iterrows()] nmc = 20000 t0 = u.settle_lo.values[None, :] + (u.settle_hi - u.settle_lo).values[None, :] * rng.random((nmc, len(u))) t1 = u.end_lo.values[None, :] + (u.end_hi - u.end_lo).values[None, :] * rng.random((nmc, len(u))) life = t1 - t0 has = np.isfinite(life[0]) # units with both a start and an end window for col, q in (("lifetime_med", 50), ("lifetime_p16", 16), ("lifetime_p84", 84)): v = np.full(len(u), np.nan) v[has] = np.percentile(life[:, has], q, axis=0) u[col] = v lag = {} for i, r in u.iterrows(): if isinstance(r.parent, str) and np.isfinite(r.settle_lo) and np.isfinite(u.settle_lo[ix[r.parent]]): v = t0[:, i] - t0[:, ix[r.parent]] lag[f"{r.parent} -> {r.unit}"] = [float(np.percentile(v, p)) for p in (16, 50, 84)] u["relaunch_lag_med"] = [lag.get(f"{r.parent} -> {r.unit}", [np.nan] * 3)[1] for _, r in u.iterrows()] front = u[u.status != "source"] empty = front[front.empty_before >= 0.5] # hops into land with no (or marginal) resident population strictly_empty = front[front.empty_before >= 1] # no earlier human presence at all (Iceland only) k_ab, n_all = int((front.status == "abandoned").sum()), int(len(front)) k_ab_e, n_e = int((empty.status == "abandoned").sum()), int(len(empty)) span = dict(first=float(np.median(t0[:, ix["Faroe Islands"]])), last=float(np.median(t0[:, ix["Vinland (L'Anse aux Meadows)"]]))) chain_km = float(front.hop_km.sum() - front.hop_km[front.unit == "Greenland Western Settlement"].sum()) res = dict( units=u.drop(columns=["note"]).to_dict("records"), n_frontier_units=n_all, abandoned=k_ab, abandoned_fraction=k_ab / n_all, jeffreys95=jeffreys(k_ab, n_all), empty_land_units=n_e, empty_land_abandoned=k_ab_e, empty_land_abandoned_fraction=k_ab_e / n_e, empty_land_jeffreys95=jeffreys(k_ab_e, n_e), strictly_empty_units=list(strictly_empty.unit), strictly_empty_abandoned=int((strictly_empty.status == "abandoned").sum()), hop_km=dict(median=float(front.hop_km.median()), max=float(front.hop_km.max()), pairs={f"{r.parent} -> {r.unit}": round(float(r.hop_km)) for _, r in front.iterrows()}), relaunch_lag_years_p16_p50_p84=lag, relaunch_lag_median_of_medians=float(np.median([v[1] for k, v in lag.items() if not k.startswith("Western Norway")])), lifetimes_years={r.unit: [float(r.lifetime_p16), float(r.lifetime_med), float(r.lifetime_p84)] for _, r in u.iterrows() if np.isfinite(r.lifetime_med)}, wave=dict(first_settlement=span["first"], last_new_settlement=span["last"], duration_years=span["last"] - span["first"], chain_length_km=chain_km, mean_front_speed_km_per_yr=chain_km / (span["last"] - span["first"]), generations_structure="linear chain: each unit founded at most two daughters (Greenland E -> Greenland W, Vinland)"), ) u.to_csv(os.path.join(DATA, "norse_units_derived.csv"), index=False) dump_json(res, "norse_results.json") if verbose: print("Norse: abandoned %d of %d frontier units; hops (km): %s" % (k_ab, n_all, res["hop_km"]["pairs"])) print(" lifetimes:", {k: [round(x) for x in v] for k, v in res["lifetimes_years"].items()}) print(" relaunch lags:", {k: [round(x) for x in v] for k, v in lag.items()}) return res if __name__ == "__main__": run()