food_chain/ecology/movement.js

37 lines
2.4 KiB
JavaScript
Raw Normal View History

2026-09-29 19:10:03 +09:00
const MODE = Object.freeze({
// Hirt et al. time-dependent maximum-speed form:
// v = a * M^b * (1 - exp(-h * M^i)), v in km/h, M in kg.
// Flying, running and swimming coefficients are the published Supplementary
// Table 4 fits for the time-dependent maximum-speed model.
running:{a:25.5,b:.26,h:22,i:-.60,routineFraction:.34,homeRangeFactor:1,calibration:'Hirt-2017'},
swimming:{a:11.2,b:.36,h:19.5,i:-.56,routineFraction:.30,homeRangeFactor:1.25,calibration:'Hirt-2017'},
flying:{a:142.8,b:.24,h:2.4,i:-.72,routineFraction:.42,homeRangeFactor:1.8,calibration:'Hirt-2017'},
});
export const locomotionModes=Object.freeze(Object.keys(MODE));
export const normalizeLocomotionMode=mode=>MODE[mode]?mode:'running';
export const locomotionCalibration=mode=>({...MODE[normalizeLocomotionMode(mode)]});
export function maximumSpeedMPerDay(massKg,mode='running'){
const c=MODE[normalizeLocomotionMode(mode)],m=Math.max(massKg,1e-6),kmh=c.a*Math.pow(m,c.b)*(1-Math.exp(-c.h*Math.pow(m,c.i)));
return Math.max(1,kmh*24000); // km/h -> m/day
}
export const maximumSpeedMultiplier=massKg=>maximumSpeedMPerDay(massKg,'running')/maximumSpeedMPerDay(3,'running');
export function routineTravelSpeedMPerDay(massKg,dailyMovementBudgetM,mode='running'){
const c=MODE[normalizeLocomotionMode(mode)],budgetSpeed=Math.max(0,dailyMovementBudgetM)*c.routineFraction;
return Math.min(maximumSpeedMPerDay(massKg,mode)*.22,Math.max(1,budgetSpeed));
}
export function foragingSpeedMPerDay(massKg,dailyMovementBudgetM,mode='running'){
return Math.min(maximumSpeedMPerDay(massKg,mode)*.42,routineTravelSpeedMPerDay(massKg,dailyMovementBudgetM,mode)*1.35);
}
export function escapeSpeedMPerDay(massKg,dailyMovementBudgetM,mode='running'){
return Math.min(maximumSpeedMPerDay(massKg,mode),routineTravelSpeedMPerDay(massKg,dailyMovementBudgetM,mode)*2.4);
}
export function dailyMovementBudgetM(massKg,geneBudgetM,mode='running'){
const c=MODE[normalizeLocomotionMode(mode)];
return Math.max(25,geneBudgetM)*(0.75+0.25*Math.pow(Math.max(massKg,.01)/3,.08))*(.9+.1*c.homeRangeFactor);
}
export function homeRangeRadiusM(massKg,mode='running'){
const c=MODE[normalizeLocomotionMode(mode)];return Math.min(1500,Math.max(90,170*Math.pow(Math.max(massKg,.01),.22)*c.homeRangeFactor));
}
export function sampleDispersalDistanceM(random,massKg,mode='running'){
const scale=homeRangeRadiusM(massKg,mode)*.55;return Math.min(scale*3,-Math.log(Math.max(1e-9,1-random()))*scale);
}