joshuabowers / graphca

A graphing calculator and analytic calculus engine
MIT License
0 stars 0 forks source link

Defining the asymptotes of limits #6

Open joshuabowers opened 3 years ago

joshuabowers commented 3 years ago

The limit of an expression, f(x) as x asymptotically approaches a given constant, c, is an interesting mathematical tool, important for defining various calculus related concepts. Having limits would be useful for determining continuity, which would itself be useful for governing how an expression is plotted on a graph: known discontinuities should cause separately drawn segments, rather than connected line segments that jump across an asymptotic cusp (among potentially other interesting cases).

ac1b7d54038301ab1743512e1c11b95b549a5156 introduces a new pegase visitor, limitVisitor, which purports to eventually be able to find limits of any given AST. limitVisitor currently only defines two different node-types of the overall AST: REAL and COMPLEX. (I'm reasonably certain that COMPLEX makes some degree of sense, but am not entirely sure.) A series of problems were discovered in attempting to develop this:

  1. limitVisitor is intended to be a secondary mode of evaluateVisitor: that is, while the latter visitor is interpreting an AST, should it happen across a LIMIT node, it would delegate behavior to the former. By implication, this means that limitVisitor exists within the operational context of evaluateVisitor; i.e. it has a Scope object.
  2. In theory, a scope could represent the asymptotic condition: x -> 5 certainly looks similar in construction to x <- 5. However, doing this unfortunately results in two issues: it masks whatever values might exist in scope for the variables of the expression; it also might result in those variables being substituted immediately for their values rather than symbolically evaluated. It would be cleaner to parameterize the construction of limitVisitor with the asymptotes, to avoid polluting scope.
  3. pegase Visitors do not seemingly exist as constructable objects: the seeming intended use is to define a one-size-fits-all singleton which is effectively stateless and can process any node irrespective of its data. If further data is necessary, it should, presumably, be passed as $context. But, if $context is Scope, and the previous point suggests that should be avoided, some other mechanism would be needed.

Essentially, a pegase Visitor is just an object which has methods that follow a specific naming convention and method signature. There is no direct reason why this should have to be a singleton. So, theoretically, either of the two following approaches should suffice, but would need to be investigated:

// Wrap the object construction in a function which captures the asymptotes so they can be used in the object functions:
export createLimitVisitor = (asymptotes: {[x: string]: number}): Visitor => {
  return new {
    //...
    VARIABLE: (node) => {
       return asymptotes[node.name]
    }
    //...
  }
}
// Classize the visitor
export class LimitVisitor extends Visitor {
  private asymptotes: {[x:string]: number}

  constructor(asymptotes: {[x:string]: number}) {
  // ...
  }

  VARIABLE(node) {
    return this.asymptotes[node.name]
  }
}

In either case, the invoking context---either evaluateVisitor for the intended functionality, or the spec for limitVisitor---would need to create a new instance of limitVisitor with the captured data on a per-parse basis:

const limitVisitor = createLimitVisitor(asymptotes)
// or
const limitVisitor = new LimitVisitor(asymptotes)
joshuabowers commented 1 year ago

Limits have yet to be implemented in GraphCa, so this is somewhat still relevant; however, this particular issue references implementation details that have not been present in the application for quite some time. I am tempted to close this, though further review is warranted.