-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add derivatives, incl. high-order and mixed partial, using macros #3703
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ import utils from "./utils"; | |
import {makeEm} from "./units"; | ||
import ParseError from "./ParseError"; | ||
|
||
import {assembleDerivativeExpr} from "./macros/derivatives"; | ||
|
||
////////////////////////////////////////////////////////////////////// | ||
// macro tools | ||
|
@@ -895,6 +896,17 @@ defineMacro("\\argmin", "\\DOTSB\\operatorname*{arg\\,min}"); | |
defineMacro("\\argmax", "\\DOTSB\\operatorname*{arg\\,max}"); | ||
defineMacro("\\plim", "\\DOTSB\\mathop{\\operatorname{plim}}\\limits"); | ||
|
||
////////////////////////////////////////////////////////////////////// | ||
// derivative.sty | ||
// https://ctan.math.illinois.edu/macros/latex/contrib/derivative/derivative.pdf | ||
|
||
defineMacro( | ||
"\\dv", (context) => assembleDerivativeExpr("\\textnormal{d}", context)); | ||
defineMacro( | ||
"\\odv", (context) => assembleDerivativeExpr("\\textnormal{d}", context)); | ||
defineMacro( | ||
"\\pdv", (context) => assembleDerivativeExpr("\\partial", context)); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would make more sense to move these side-effects into However, we haven't decided to make a |
||
////////////////////////////////////////////////////////////////////// | ||
// braket.sty | ||
// http://ctan.math.washington.edu/tex-archive/macros/latex/contrib/braket/braket.pdf | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// @flow | ||
/** | ||
* Macro utilities for assembling derivatives. | ||
*/ | ||
|
||
import type {MacroContextInterface} from "../defineMacro"; | ||
|
||
function splitByTopLevelComma(s: string): string[] { | ||
if (!s || s.length === 0) { | ||
return []; | ||
} | ||
|
||
// "1+a,n" => ["1+a", "n"] | ||
// "1+a,{n,m}" => ["1+a", "{n,m}"] | ||
// "1+a,{n,{p,q}}" => ["1+a", "{n,{p,q}}"] | ||
// "1+a" => ["1+a"] | ||
// "1+a,,n" => ["1+a", "", "n"] | ||
// "1+a,," => ["1+a", ""] | ||
// "1+a," => ["1+a"] | ||
// "," => [""] | ||
// "" => [] | ||
const res = []; | ||
let currentStr = ""; | ||
let braceDepth = 0; | ||
const isTopLevelDelimiter = (c) => c === "," && braceDepth <= 0; | ||
for (let i = 0; i < s.length; ++i) { | ||
const c = s[i]; | ||
if (c === "{") { | ||
++braceDepth; | ||
} else if (c === "}") { | ||
--braceDepth; | ||
} | ||
if (!isTopLevelDelimiter(c)) { | ||
currentStr += c; | ||
} | ||
if (isTopLevelDelimiter(c) || i === s.length - 1) { | ||
res.push(currentStr); | ||
currentStr = ""; | ||
} | ||
} | ||
|
||
return res; | ||
} | ||
|
||
function addTokenProtector(s: string): string { | ||
return s.length > 1 ? ` ${s} ` : s; | ||
} | ||
|
||
function extractArgs(context: MacroContextInterface): { | ||
func: string, | ||
vars: string[], | ||
totalOrders: string[], | ||
varOrders: string[], | ||
} { | ||
const optionalArgGroups = []; | ||
let arg = context.consumeArg().tokens; | ||
while (arg.length === 1 && arg[0].text === "[") { | ||
let bracketContent = ''; | ||
let token = context.expandNextToken(); | ||
while (token.text !== "]" && token.text !== "EOF") { | ||
bracketContent += token.text; | ||
token = context.expandNextToken(); | ||
} | ||
optionalArgGroups.push(splitByTopLevelComma(bracketContent).reverse()); | ||
arg = context.consumeArg().tokens; | ||
} | ||
|
||
const func = arg.reverse().map( | ||
e => addTokenProtector(e.text)).join(""); | ||
arg = context.consumeArg().tokens; | ||
const vars = splitByTopLevelComma(arg.reverse().map( | ||
e => addTokenProtector(e.text)).join("")); | ||
const varOrders = | ||
optionalArgGroups[0] ? optionalArgGroups[0].reverse() : ["1"]; | ||
const totalOrders = | ||
optionalArgGroups[1] ? optionalArgGroups[1].reverse() : varOrders; | ||
|
||
return {func, vars, totalOrders, varOrders}; | ||
} | ||
|
||
const PLACEHOLDER = "\\square"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this placeholder behavior implemented in any of the existing LaTeX packages? I didn't see it anywhere, but I may have missed it in one of them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No it's not implemented in any existing LaTeX package. That said, I think it's a useful improvement, so that users won't be puzzled by blankness. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The square is a meaningful operator in many contexts and that might confuse more than it helps. I suppose if someone leaves the [options] blank they should expect that location in fact to be blank. |
||
|
||
export function assembleDerivativeExpr( | ||
d: string, context: MacroContextInterface): string { | ||
const {func, vars, totalOrders, varOrders} = extractArgs(context); | ||
const numVars = vars.length; | ||
const numVarOrders = varOrders.length; | ||
if (numVars > numVarOrders) { | ||
varOrders.push(...Array(numVars - numVarOrders).fill(PLACEHOLDER)); | ||
} else if (numVarOrders > numVars) { | ||
vars.push(...Array(numVarOrders - numVars).fill(PLACEHOLDER)); | ||
} | ||
|
||
const makeIndex = (s) => s === "1" ? "" : `^{${s}}`; | ||
const numer = `{${d}}${makeIndex(totalOrders[0] ?? PLACEHOLDER)}{${func}}`; | ||
const denom = vars.map((variable, i) => { | ||
const order = varOrders[i]; | ||
return `{${d}}{${variable}}${makeIndex(order)}`; | ||
}).join(""); | ||
|
||
return `\\frac{${numer}}{${denom}}`; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather see this follow one spec. Or perhaps it already does, and this sentence just needs rewording? Fine to link to other packages, but which one should be considered "correct" for this PR? We don't want to implement a brand new derivative behavior. Stated another way, if someone was to copy KaTeX code over to LaTeX, which
\usepackage
should they include? (This will also help review this PR; I'd rather not read the documentation of three different packages, though I've already spent some time skimming them all.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this follows one spec. Let me reword..