<commit_before>// @flow

import {GetFunc, MakePoint, MakeVector, FuncArrayString} from './UserFunction';

import type {MathFunc, Vector, Point, FuncArray} from './UserFunction';

type PointData = {
  tangent : number,
  normal : number
};

type ForceVectors = {
  Ax : number,
  Ay : number,
  Vx : number,
  Vy : number,
};

// Super duper exciting constants
const halfPi = Math.PI / 2;
const twoPi = Math.PI * 2;
const gravity = 9.8; // m/s^2

const initialSpeed = 0;
const initialAngle = -halfPi;

// How many slices used to calculate random stuff
// This value corresponds to what the 'time' value in the GraphState represents
// It's effectively "milliseconds since beginning"
const timeSlice = .001;
const timeSliceSquared = timeSlice * timeSlice;

// Helper functions
const distSquare = (pt: Point, x: number, y: number): number => {
  const xd = pt.x - x;
  const yd = pt.y - y;
  return xd * xd + yd * yd;
};

const dist = (pt: Point, x: number, y: number): number => {
  return Math.sqrt(distSquare(pt, x, y));
};

// I want to make sure that all angles I get are between pi and -pi
const NormalizeAngle = (n: number): number => {
  let res = n % twoPi;
  if (res > Math.PI) {
    res -= twoPi;
  }
  return res;
};

const getTangentAngle = (x: number, func: MathFunc): number => {
  // Stupid approximation :(
  const slice = 1e-10;
  try {
    const y = func(x);
    const y2 = func(x + slice);
    const delta = y2 - y;
    return Number.isNaN(delta) ? 0 : NormalizeAngle(Math.atan2(delta, slice));
  } catch (e) {
    return 0;
  }
};

const GetPointData = (x: number, func: MathFunc): PointData => {
  const tangent = getTangentAngle(x, func);
  const normal = NormalizeAngle(tangent + halfPi);
  return {tangent, normal};
};

const CalcForceVectors =
  (vec: Vector, normal: number, tangent: number):ForceVectors => {
  const normalForce = vec.line ? Math.cos(normal) : 1.0;
  const A = gravity * normalForce; // Tangential acceleration!
  // const N = gravity * Math.sin(tangent); // This goes away (Normal force)

  const Ax = A * (vec.line ? Math.cos(tangent) : 0);
  const Ay = A * (vec.line ? Math.sin(tangent) : -1);

  const Vx = vec.magnitude * Math.cos(vec.angle);
  const Vy = vec.magnitude * Math.sin(vec.angle);
  return {Ax, Ay, Vx, Vy};
};

let resMap: Array<Vector> = [];
let resMapKey: string = '';

export const getPosition = (funcs: FuncArray, time: number): Vector => {
  // Okay, reasonable way to simulate gravity? Just calculate it
  // cumulatively because I've forgotten the Calculus necessary to do it
  // accurately :/
  const firstFunc = funcs[0];
  const start = firstFunc.range.low;
  let vec: Vector = MakeVector(MakePoint(start, firstFunc.func(start)),
                               initialAngle,
                               initialSpeed,
                               true, false);
  const fas = FuncArrayString(funcs);
  if (fas !== resMapKey) {
    resMap = [];
    resMapKey = fas;
  }
  for (let idx = 0; idx <= time; idx++) {
    if (resMap[idx]) {
      // Dynamic programming, FTW...
      vec = resMap[idx];
      continue;
    }

    const userFunc = GetFunc(funcs, vec.origin.x);
    if (!userFunc) {
      return vec;
    }

    const func = userFunc.func;

    // First, approximate the tangent to the curve at the current location
    let {tangent, normal} = GetPointData(vec.origin.x, func);
    let {Ax, Ay, Vx, Vy} = CalcForceVectors(vec, normal, tangent);

    // Calculate the estimated delta from the current location
    let xd = Ax * timeSliceSquared + Vx * timeSlice;
    let yd = Ay * timeSliceSquared + Vy * timeSlice;

    // Get the next position
    let x = xd + vec.origin.x;
    let yc = yd + vec.origin.y;
    let yf = func(x); // The point on the track

    if (idx > 1) {
      const prevFunc = GetFunc(funcs, resMap[idx - 2].origin.x);
      if (prevFunc && prevFunc.text !== userFunc.text) {
        // Figure out which values to test for continuity
        let boundx = userFunc.range.low;
        if (Math.abs(userFunc.range.low - x) > Math.abs(userFunc.range.high - x) ) {
          boundx = userFunc.range.high;
        }
        const curBoundY = func(boundx);
        const prvBoundY = prevFunc.func(boundx);
        if (Math.abs(curBoundY - prvBoundY) > 1e-6) {
          if (curBoundY > prvBoundY)
            return MakeVector(vec.origin, 0, 0, true, true);
        }
      }
    }

    const overTheLine = yc < yf;
    if (overTheLine) {
      // FYI, This is the common case. We need to find a 'good' point on the
      // function that is close to the distance from the origin. This is just a
      // best-effort. The smaller the timeSlice the more accurate this will be,<commit_msg>Only stop when it's on the track and it hits a discontinuity<commit_after>// @flow

import {GetFunc, MakePoint, MakeVector, FuncArrayString} from './UserFunction';

import type {MathFunc, Vector, Point, FuncArray} from './UserFunction';

type PointData = {
  tangent : number,
  normal : number
};

type ForceVectors = {
  Ax : number,
  Ay : number,
  Vx : number,
  Vy : number,
};

// Super duper exciting constants
const halfPi = Math.PI / 2;
const twoPi = Math.PI * 2;
const gravity = 9.8; // m/s^2

const initialSpeed = 0;
const initialAngle = -halfPi;

// How many slices used to calculate random stuff
// This value corresponds to what the 'time' value in the GraphState represents
// It's effectively "milliseconds since beginning"
const timeSlice = .001;
const timeSliceSquared = timeSlice * timeSlice;

// Helper functions
const distSquare = (pt: Point, x: number, y: number): number => {
  const xd = pt.x - x;
  const yd = pt.y - y;
  return xd * xd + yd * yd;
};

const dist = (pt: Point, x: number, y: number): number => {
  return Math.sqrt(distSquare(pt, x, y));
};

// I want to make sure that all angles I get are between pi and -pi
const NormalizeAngle = (n: number): number => {
  let res = n % twoPi;
  if (res > Math.PI) {
    res -= twoPi;
  }
  return res;
};

const getTangentAngle = (x: number, func: MathFunc): number => {
  // Stupid approximation :(
  const slice = 1e-10;
  try {
    const y = func(x);
    const y2 = func(x + slice);
    const delta = y2 - y;
    return Number.isNaN(delta) ? 0 : NormalizeAngle(Math.atan2(delta, slice));
  } catch (e) {
    return 0;
  }
};

const GetPointData = (x: number, func: MathFunc): PointData => {
  const tangent = getTangentAngle(x, func);
  const normal = NormalizeAngle(tangent + halfPi);
  return {tangent, normal};
};

const CalcForceVectors =
  (vec: Vector, normal: number, tangent: number):ForceVectors => {
  const normalForce = vec.line ? Math.cos(normal) : 1.0;
  const A = gravity * normalForce; // Tangential acceleration!
  // const N = gravity * Math.sin(tangent); // This goes away (Normal force)

  const Ax = A * (vec.line ? Math.cos(tangent) : 0);
  const Ay = A * (vec.line ? Math.sin(tangent) : -1);

  const Vx = vec.magnitude * Math.cos(vec.angle);
  const Vy = vec.magnitude * Math.sin(vec.angle);
  return {Ax, Ay, Vx, Vy};
};

let resMap: Array<Vector> = [];
let resMapKey: string = '';

export const getPosition = (funcs: FuncArray, time: number): Vector => {
  // Okay, reasonable way to simulate gravity? Just calculate it
  // cumulatively because I've forgotten the Calculus necessary to do it
  // accurately :/
  const firstFunc = funcs[0];
  const start = firstFunc.range.low;
  let vec: Vector = MakeVector(MakePoint(start, firstFunc.func(start)),
                               initialAngle,
                               initialSpeed,
                               true, false);
  const fas = FuncArrayString(funcs);
  if (fas !== resMapKey) {
    resMap = [];
    resMapKey = fas;
  }
  for (let idx = 0; idx <= time; idx++) {
    if (resMap[idx]) {
      // Dynamic programming, FTW...
      vec = resMap[idx];
      continue;
    }

    const userFunc = GetFunc(funcs, vec.origin.x);
    if (!userFunc) {
      return vec;
    }

    const func = userFunc.func;

    // First, approximate the tangent to the curve at the current location
    let {tangent, normal} = GetPointData(vec.origin.x, func);
    let {Ax, Ay, Vx, Vy} = CalcForceVectors(vec, normal, tangent);

    // Calculate the estimated delta from the current location
    let xd = Ax * timeSliceSquared + Vx * timeSlice;
    let yd = Ay * timeSliceSquared + Vy * timeSlice;

    // Get the next position
    let x = xd + vec.origin.x;
    let yc = yd + vec.origin.y;
    let yf = func(x); // The point on the track

    if (idx > 1) {
      const prevFunc = GetFunc(funcs, resMap[idx - 2].origin.x);
      if (prevFunc && prevFunc.text !== userFunc.text) {
        // Figure out which values to test for continuity
        let boundx = userFunc.range.low;
        if (Math.abs(userFunc.range.low - x) > Math.abs(userFunc.range.high - x) ) {
          boundx = userFunc.range.high;
        }
        const curBoundY = func(boundx);
        const prvBoundY = prevFunc.func(boundx);
        if (Math.abs(curBoundY - prvBoundY) > 1e-6 && vec.line) {
          if (curBoundY > prvBoundY)
            return MakeVector(vec.origin, 0, 0, true, true);
        }
      }
    }

    const overTheLine = yc < yf;
    if (overTheLine) {
      // FYI, This is the common case. We need to find a 'good' point on the
      // function that is close to the distance from the origin. This is just a
      // best-effort. The smaller the timeSlice the more accurate this will be,
{'fraction_non_alphanumeric': 0.12665747321523285, 'fraction_numerical': 0.006152540574944309, 'mean_word_length': 2.6528477334366523, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6048339', 'n_tokens_mistral': 3182, 'n_tokens_neox': 3089, 'n_words': 1015}