From 3e6cc3e55a1586ad5fa333c0e6c87bf2b331f898 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Fri, 22 Dec 2023 21:55:33 +0800 Subject: [PATCH 01/18] update docstrings --- .../src/features/BuiltInCode.ts | 454 +++++++++--------- .../src/features/TemplateCode.js | 32 +- 2 files changed, 254 insertions(+), 232 deletions(-) diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index 4856db8..5dc6dc4 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -5,24 +5,24 @@ const TEMPLATE_F_CODE = `\ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } - * \` + * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } - * \` + * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * \`f({v1, v3, v2}) = v1 * v3 * v2 = 1 * 3 * 2 = 6\`. + * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { // Write the logic here @@ -37,28 +37,30 @@ const TEMPLATE_DFDX_CODE = `\ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } - * \` + * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } - * \` + * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this * function for the following cases: * - x is a constant node (i.e., x will always be a variable) * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume \`xId\` is "v2", then the value is "3" - * since \`f = v1 * v3 * v2\` and \`df/dx = v1 * v3 = 3\`. + * the above example data and assume xId is "v2", then the value is "3" + * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { // Write the logic here @@ -72,24 +74,24 @@ const ADD_F_CODE = `\ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } - * \` + * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } - * \` + * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * \`f({v1, v3, v2}) = v1 * v3 * v2 = 1 * 3 * 2 = 6\`. + * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.a.length !== 1) { @@ -110,31 +112,33 @@ function f(fInputPortToNodes, fInputNodeToValues) { const ADD_DFDX_CODE = `\ /** * Calculates df/dx. - * @param {Record} fInputPortToNodes An object where the - * keys are port IDs and the values are node IDs of the connected input nodes. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`\`\`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } * \`\`\` - * @param {Record} fInputNodeToValues An object where the - * keys are node IDs and the values are node values of the connected input - * nodes. Example data for product: - * \`\`\`javascript + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for product: + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this * function for the following cases: * - x is a constant node (i.e., x will always be a variable) * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume \`xId\` is "v2", then the value is "3" - * since \`f = v1 * v3 * v2\` and \`df/dx = v1 * v3 = 3\`. + * the above example data and assume xId is "v2", then the value is "3" + * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { const hasXInA = fInputPortToNodes.a.includes(xId); @@ -152,24 +156,24 @@ const MULTIPLY_F_CODE = `\ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } - * \` + * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } - * \` + * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * \`f({v1, v3, v2}) = v1 * v3 * v2 = 1 * 3 * 2 = 6\`. + * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.a.length !== 1) { @@ -190,31 +194,33 @@ function f(fInputPortToNodes, fInputNodeToValues) { const MULTIPLY_DFDX_CODE = `\ /** * Calculates df/dx. - * @param {Record} fInputPortToNodes An object where the - * keys are port IDs and the values are node IDs of the connected input nodes. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`\`\`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } * \`\`\` - * @param {Record} fInputNodeToValues An object where the - * keys are node IDs and the values are node values of the connected input - * nodes. Example data for product: - * \`\`\`javascript + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for product: + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this * function for the following cases: * - x is a constant node (i.e., x will always be a variable) * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume \`xId\` is "v2", then the value is "3" - * since \`f = v1 * v3 * v2\` and \`df/dx = v1 * v3 = 3\`. + * the above example data and assume xId is "v2", then the value is "3" + * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { const hasXInA = fInputPortToNodes.a.includes(xId); @@ -236,24 +242,24 @@ const SUM_F_CODE = `\ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } - * \` + * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } - * \` + * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * \`f({v1, v3, v2}) = v1 * v3 * v2 = 1 * 3 * 2 = 6\`. + * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { let sum = 0; @@ -268,31 +274,33 @@ function f(fInputPortToNodes, fInputNodeToValues) { const SUM_DFDX_CODE = `\ /** * Calculates df/dx. - * @param {Record} fInputPortToNodes An object where the - * keys are port IDs and the values are node IDs of the connected input nodes. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`\`\`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } * \`\`\` - * @param {Record} fInputNodeToValues An object where the - * keys are node IDs and the values are node values of the connected input - * nodes. Example data for product: - * \`\`\`javascript + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for product: + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this * function for the following cases: * - x is a constant node (i.e., x will always be a variable) * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume \`xId\` is "v2", then the value is "3" - * since \`f = v1 * v3 * v2\` and \`df/dx = v1 * v3 = 3\`. + * the above example data and assume xId is "v2", then the value is "3" + * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x_i.includes(xId)) { @@ -308,24 +316,24 @@ const PRODUCT_F_CODE = `\ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } - * \` + * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } - * \` + * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * \`f({v1, v3, v2}) = v1 * v3 * v2 = 1 * 3 * 2 = 6\`. + * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { let product = 1; @@ -340,31 +348,33 @@ function f(fInputPortToNodes, fInputNodeToValues) { const PRODUCT_DFDX_CODE = `\ /** * Calculates df/dx. - * @param {Record} fInputPortToNodes An object where the - * keys are port IDs and the values are node IDs of the connected input nodes. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`\`\`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } * \`\`\` - * @param {Record} fInputNodeToValues An object where the - * keys are node IDs and the values are node values of the connected input - * nodes. Example data for product: - * \`\`\`javascript + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for product: + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this * function for the following cases: * - x is a constant node (i.e., x will always be a variable) * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume \`xId\` is "v2", then the value is "3" - * since \`f = v1 * v3 * v2\` and \`df/dx = v1 * v3 = 3\`. + * the above example data and assume xId is "v2", then the value is "3" + * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x_i.includes(xId)) { @@ -387,24 +397,24 @@ const COS_F_CODE = `\ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } - * \` + * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } - * \` + * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * \`f({v1, v3, v2}) = v1 * v3 * v2 = 1 * 3 * 2 = 6\`. + * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.x.length !== 1) { @@ -420,31 +430,33 @@ function f(fInputPortToNodes, fInputNodeToValues) { const COS_DFDX_CODE = `\ /** * Calculates df/dx. - * @param {Record} fInputPortToNodes An object where the - * keys are port IDs and the values are node IDs of the connected input nodes. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`\`\`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } * \`\`\` - * @param {Record} fInputNodeToValues An object where the - * keys are node IDs and the values are node values of the connected input - * nodes. Example data for product: - * \`\`\`javascript + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for product: + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this * function for the following cases: * - x is a constant node (i.e., x will always be a variable) * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume \`xId\` is "v2", then the value is "3" - * since \`f = v1 * v3 * v2\` and \`df/dx = v1 * v3 = 3\`. + * the above example data and assume xId is "v2", then the value is "3" + * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x.includes(xId)) { @@ -466,24 +478,24 @@ const IDENTITY_F_CODE = `\ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } - * \` + * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } - * \` + * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * \`f({v1, v3, v2}) = v1 * v3 * v2 = 1 * 3 * 2 = 6\`. + * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.x.length !== 1) { @@ -498,31 +510,33 @@ function f(fInputPortToNodes, fInputNodeToValues) { const IDENTITY_DFDX_CODE = `\ /** * Calculates df/dx. - * @param {Record} fInputPortToNodes An object where the - * keys are port IDs and the values are node IDs of the connected input nodes. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`\`\`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } * \`\`\` - * @param {Record} fInputNodeToValues An object where the - * keys are node IDs and the values are node values of the connected input - * nodes. Example data for product: - * \`\`\`javascript + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for product: + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this * function for the following cases: * - x is a constant node (i.e., x will always be a variable) * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume \`xId\` is "v2", then the value is "3" - * since \`f = v1 * v3 * v2\` and \`df/dx = v1 * v3 = 3\`. + * the above example data and assume xId is "v2", then the value is "3" + * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x.includes(xId)) { @@ -538,24 +552,24 @@ const RELU_F_CODE = `\ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } - * \` + * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } - * \` + * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * \`f({v1, v3, v2}) = v1 * v3 * v2 = 1 * 3 * 2 = 6\`. + * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.x.length !== 1) { @@ -571,31 +585,33 @@ function f(fInputPortToNodes, fInputNodeToValues) { const RELU_DFDX_CODE = `\ /** * Calculates df/dx. - * @param {Record} fInputPortToNodes An object where the - * keys are port IDs and the values are node IDs of the connected input nodes. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`\`\`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } * \`\`\` - * @param {Record} fInputNodeToValues An object where the - * keys are node IDs and the values are node values of the connected input - * nodes. Example data for product: - * \`\`\`javascript + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for product: + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this * function for the following cases: * - x is a constant node (i.e., x will always be a variable) * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume \`xId\` is "v2", then the value is "3" - * since \`f = v1 * v3 * v2\` and \`df/dx = v1 * v3 = 3\`. + * the above example data and assume xId is "v2", then the value is "3" + * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x.includes(xId)) { @@ -617,24 +633,24 @@ const SIGMOID_F_CODE = `\ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } - * \` + * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } - * \` + * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * \`f({v1, v3, v2}) = v1 * v3 * v2 = 1 * 3 * 2 = 6\`. + * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.x.length !== 1) { @@ -650,31 +666,33 @@ function f(fInputPortToNodes, fInputNodeToValues) { const SIGMOID_DFDX_CODE = `\ /** * Calculates df/dx. - * @param {Record} fInputPortToNodes An object where the - * keys are port IDs and the values are node IDs of the connected input nodes. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`\`\`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } * \`\`\` - * @param {Record} fInputNodeToValues An object where the - * keys are node IDs and the values are node values of the connected input - * nodes. Example data for product: - * \`\`\`javascript + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for product: + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this * function for the following cases: * - x is a constant node (i.e., x will always be a variable) * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume \`xId\` is "v2", then the value is "3" - * since \`f = v1 * v3 * v2\` and \`df/dx = v1 * v3 = 3\`. + * the above example data and assume xId is "v2", then the value is "3" + * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x.includes(xId)) { @@ -697,24 +715,24 @@ const SQUARED_ERROR_F_CODE = `\ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } - * \` + * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * \`javascript + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } - * \` + * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * \`f({v1, v3, v2}) = v1 * v3 * v2 = 1 * 3 * 2 = 6\`. + * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.y_t.length !== 1) { @@ -735,31 +753,33 @@ function f(fInputPortToNodes, fInputNodeToValues) { const SQUARED_ERROR_DFDX_CODE = `\ /** * Calculates df/dx. - * @param {Record} fInputPortToNodes An object where the - * keys are port IDs and the values are node IDs of the connected input nodes. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * \`\`\`javascript + * \`\`\`json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } * \`\`\` - * @param {Record} fInputNodeToValues An object where the - * keys are node IDs and the values are node values of the connected input - * nodes. Example data for product: - * \`\`\`javascript + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for product: + * \`\`\`json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this * function for the following cases: * - x is a constant node (i.e., x will always be a variable) * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume \`xId\` is "v2", then the value is "3" - * since \`f = v1 * v3 * v2\` and \`df/dx = v1 * v3 = 3\`. + * the above example data and assume xId is "v2", then the value is "3" + * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { const hasXInYTrue = fInputPortToNodes.y_t.includes(xId); diff --git a/interactive-computational-graph/src/features/TemplateCode.js b/interactive-computational-graph/src/features/TemplateCode.js index 98f4751..1ac91d5 100644 --- a/interactive-computational-graph/src/features/TemplateCode.js +++ b/interactive-computational-graph/src/features/TemplateCode.js @@ -11,24 +11,24 @@ * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * ```javascript + * ```json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } * ``` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * ```javascript + * ```json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } * ``` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * `f({v1, v3, v2}) = v1 * v3 * v2 = 1 * 3 * 2 = 6`. + * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { // Write the logic here @@ -40,28 +40,30 @@ function f(fInputPortToNodes, fInputNodeToValues) { * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. * Example data for product: - * ```javascript + * ```json * { - * x_i: ["v1", "v3", "v2"] + * "x_i": ["1", "2", "3"] * } * ``` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. * Example data for product: - * ```javascript + * ```json * { - * v1: "1", - * v3: "3", - * v2: "2", + * "1": "1", + * "2": "2", + * "3": "3" * } * ``` * @param {string} xId Node ID of x. Note that the framework will not call this * function for the following cases: * - x is a constant node (i.e., x will always be a variable) * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume `xId` is "v2", then the value is "3" - * since `f = v1 * v3 * v2` and `df/dx = v1 * v3 = 3`. + * the above example data and assume xId is "v2", then the value is "3" + * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { // Write the logic here From 5ed6f73c9cfafd7b844dbad141270a347664bba8 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Fri, 22 Dec 2023 22:43:47 +0800 Subject: [PATCH 02/18] use different examples for diff op --- .../src/features/BuiltInCode.ts | 263 +++++++++--------- 1 file changed, 125 insertions(+), 138 deletions(-) diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index 5dc6dc4..610ffab 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -73,25 +73,25 @@ const ADD_F_CODE = `\ * Calculates f(). * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for add: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "a": ["0"], + * "b": ["1"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for add: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0.2", + * "1": "0.4" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider - * the above example data, then the value is "6" because - * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. + * the above example data, then the value is "0.6" because + * f(a, b) = a + b = 0.2 + 0.4 = 0.6. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.a.length !== 1) { @@ -114,20 +114,20 @@ const ADD_DFDX_CODE = `\ * Calculates df/dx. * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for add: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "a": ["0"], + * "b": ["1"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for add: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0.2", + * "1": "0.4" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -137,8 +137,8 @@ const ADD_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" - * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. + * the above example data and assume xId is "1", then the value is "1" + * since f(a, b) = a + b and df/dx = df/db = 1. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { const hasXInA = fInputPortToNodes.a.includes(xId); @@ -155,25 +155,25 @@ const MULTIPLY_F_CODE = `\ * Calculates f(). * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for multiply: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "a": ["0"], + * "b": ["1"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for multiply: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0.2", + * "1": "0.4" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider - * the above example data, then the value is "6" because - * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. + * the above example data, then the value is "0.08" because + * f(a, b) = a * b = 0.2 * 0.4 = 0.08. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.a.length !== 1) { @@ -196,20 +196,20 @@ const MULTIPLY_DFDX_CODE = `\ * Calculates df/dx. * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for multiply: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "a": ["0"], + * "b": ["1"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for multiply: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0.2", + * "1": "0.4" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -219,8 +219,8 @@ const MULTIPLY_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" - * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. + * the above example data and assume xId is "1", then the value is "0.2" + * since f(a, b) = a * b and df/dx = df/db = a = 0.2. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { const hasXInA = fInputPortToNodes.a.includes(xId); @@ -241,25 +241,25 @@ const SUM_F_CODE = `\ * Calculates f(). * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for sum: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x_i": ["0", "1", "2"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for sum: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0.2", + * "1": "0.4", + * "2": "0.6" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider - * the above example data, then the value is "6" because - * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. + * the above example data, then the value is "1.2" because + * f(x_i) = x_0 + x_1 + x_2 = 0.2 + 0.4 + 0.6 = 1.2. */ function f(fInputPortToNodes, fInputNodeToValues) { let sum = 0; @@ -276,20 +276,20 @@ const SUM_DFDX_CODE = `\ * Calculates df/dx. * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for sum: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x_i": ["0", "1", "2"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for sum: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0.2", + * "1": "0.4", + * "2": "0.6" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -299,8 +299,8 @@ const SUM_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" - * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. + * the above example data and assume xId is "1", then the value is "1" + * since f(x_i) = x_0 + x_1 + x_2 and df/dx = df/d(x_1) = 1. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x_i.includes(xId)) { @@ -318,7 +318,7 @@ const PRODUCT_F_CODE = `\ * Example data for product: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x_i": ["0", "1", "2"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys @@ -326,14 +326,14 @@ const PRODUCT_F_CODE = `\ * Example data for product: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "1", + * "1": "2", + * "2": "3" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. + * f(x_i) = x_0 * x_1 * x_2 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { let product = 1; @@ -353,7 +353,7 @@ const PRODUCT_DFDX_CODE = `\ * Example data for product: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x_i": ["0", "1", "2"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys @@ -361,9 +361,9 @@ const PRODUCT_DFDX_CODE = `\ * Example data for product: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "1", + * "1": "2", + * "2": "3" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -373,8 +373,9 @@ const PRODUCT_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" - * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. + * the above example data and assume xId is "1", then the value is "3" + * since f(x_i) = x_0 * x_1 * x_2 and df/dx = df/d(x_1) = x_0 * x_2 = 1 * 3 = + * 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x_i.includes(xId)) { @@ -396,25 +397,23 @@ const COS_F_CODE = `\ * Calculates f(). * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for cos: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x": ["0"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for cos: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider - * the above example data, then the value is "6" because - * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. + * the above example data, then the value is "1" because + * f(x) = cos(x) = cos(0) = 1. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.x.length !== 1) { @@ -432,20 +431,18 @@ const COS_DFDX_CODE = `\ * Calculates df/dx. * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for cos: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x": ["0"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for cos: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -455,8 +452,8 @@ const COS_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" - * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. + * the above example data and assume xId is "0", then the value is "0" + * since f(x) = cos(x) and df/dx = -sin(x) = 0. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x.includes(xId)) { @@ -477,25 +474,23 @@ const IDENTITY_F_CODE = `\ * Calculates f(). * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for identity: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x": ["0"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for identity: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0.5" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider - * the above example data, then the value is "6" because - * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. + * the above example data, then the value is "0.5" because + * f(x) = x = 0.5. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.x.length !== 1) { @@ -512,20 +507,18 @@ const IDENTITY_DFDX_CODE = `\ * Calculates df/dx. * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for identity: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x": ["0"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for identity: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0.5" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -535,8 +528,8 @@ const IDENTITY_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" - * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. + * the above example data and assume xId is "0", then the value is "1" + * since f(x) = x and df/dx = 1. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x.includes(xId)) { @@ -551,25 +544,23 @@ const RELU_F_CODE = `\ * Calculates f(). * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for ReLU: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x": ["0"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for ReLU: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0.5" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider - * the above example data, then the value is "6" because - * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. + * the above example data, then the value is "0.5" because + * f(x) = max(0, x) = max(0, 0.5) = 0.5. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.x.length !== 1) { @@ -587,20 +578,18 @@ const RELU_DFDX_CODE = `\ * Calculates df/dx. * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for ReLU: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x": ["0"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for ReLU: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0.5" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -610,8 +599,8 @@ const RELU_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" - * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. + * the above example data and assume xId is "0", then the value is "1" + * since f(x) = max(0, x) and df/dx = 1 (x > 0). */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x.includes(xId)) { @@ -632,25 +621,23 @@ const SIGMOID_F_CODE = `\ * Calculates f(). * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for sigmoid: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x": ["0"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for sigmoid: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider - * the above example data, then the value is "6" because - * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. + * the above example data, then the value is "0.5" because + * f(x) = 1 / (1 + e^(-x)) = 1 / (1 + e^0) = 0.5. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.x.length !== 1) { @@ -668,20 +655,18 @@ const SIGMOID_DFDX_CODE = `\ * Calculates df/dx. * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for sigmoid: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x": ["0"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for sigmoid: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -692,7 +677,8 @@ const SIGMOID_DFDX_CODE = `\ * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider * the above example data and assume xId is "v2", then the value is "3" - * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. + * since f(x) = sigmoid(x) and df/dx = sigmoid(x) * (1 - sigmoid(x)) = + * 0.5 * (1 - 0.5) = 0.25. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (!fInputPortToNodes.x.includes(xId)) { @@ -714,25 +700,25 @@ const SQUARED_ERROR_F_CODE = `\ * Calculates f(). * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for squared error: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "y_t": ["0"], + * "y_e": ["1"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for squared error: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0", + * "1": "0.5" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider - * the above example data, then the value is "6" because - * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. + * the above example data, then the value is "0.25" because + * f(y_t, y_e) = (y_t - y_e)^2 = (0 - 0.5)^2 = 0.25. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.y_t.length !== 1) { @@ -755,20 +741,20 @@ const SQUARED_ERROR_DFDX_CODE = `\ * Calculates df/dx. * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for product: + * Example data for squared error: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "y_t": ["0"], + * "y_e": ["1"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for product: + * Example data for squared error: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "0", + * "1": "0.5" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -778,8 +764,9 @@ const SQUARED_ERROR_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" - * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. + * the above example data and assume xId is "0", then the value is "-1" + * since f(y_t, y_e) = (y_t - y_e)^2 and df/d(y_t) = 2 * (y_t - y_e) = + * 2 * (0 - 0.5) = -1. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { const hasXInYTrue = fInputPortToNodes.y_t.includes(xId); From 065e96f9f0458f1e0a391db4387b289a303a22c9 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Fri, 22 Dec 2023 22:46:56 +0800 Subject: [PATCH 03/18] update template code docstrings --- .../src/features/BuiltInCode.ts | 23 ++++++++++--------- .../src/features/TemplateCode.js | 23 ++++++++++--------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index 610ffab..148a5b6 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -7,7 +7,7 @@ const TEMPLATE_F_CODE = `\ * Example data for product: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x_i": ["0", "1", "2"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys @@ -15,14 +15,14 @@ const TEMPLATE_F_CODE = `\ * Example data for product: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "1", + * "1": "2", + * "2": "3" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. + * f(x_i) = x_0 * x_1 * x_2 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { // Write the logic here @@ -39,7 +39,7 @@ const TEMPLATE_DFDX_CODE = `\ * Example data for product: * \`\`\`json * { - * "x_i": ["1", "2", "3"] + * "x_i": ["0", "1", "2"] * } * \`\`\` * @param {Record} fInputNodeToValues An object where the keys @@ -47,9 +47,9 @@ const TEMPLATE_DFDX_CODE = `\ * Example data for product: * \`\`\`json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "1", + * "1": "2", + * "2": "3" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -59,8 +59,9 @@ const TEMPLATE_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" - * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. + * the above example data and assume xId is "1", then the value is "3" + * since f(x_i) = x_0 * x_1 * x_2 and df/dx = df/d(x_1) = x_0 * x_2 = 1 * 3 = + * 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { // Write the logic here diff --git a/interactive-computational-graph/src/features/TemplateCode.js b/interactive-computational-graph/src/features/TemplateCode.js index 1ac91d5..8675829 100644 --- a/interactive-computational-graph/src/features/TemplateCode.js +++ b/interactive-computational-graph/src/features/TemplateCode.js @@ -13,7 +13,7 @@ * Example data for product: * ```json * { - * "x_i": ["1", "2", "3"] + * "x_i": ["0", "1", "2"] * } * ``` * @param {Record} fInputNodeToValues An object where the keys @@ -21,14 +21,14 @@ * Example data for product: * ```json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "1", + * "1": "2", + * "2": "3" * } * ``` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "6" because - * f([v1, v2, v3]) = v1 * v2 * v3 = 1 * 2 * 3 = 6. + * f(x_i) = x_0 * x_1 * x_2 = 1 * 2 * 3 = 6. */ function f(fInputPortToNodes, fInputNodeToValues) { // Write the logic here @@ -42,7 +42,7 @@ function f(fInputPortToNodes, fInputNodeToValues) { * Example data for product: * ```json * { - * "x_i": ["1", "2", "3"] + * "x_i": ["0", "1", "2"] * } * ``` * @param {Record} fInputNodeToValues An object where the keys @@ -50,9 +50,9 @@ function f(fInputPortToNodes, fInputNodeToValues) { * Example data for product: * ```json * { - * "1": "1", - * "2": "2", - * "3": "3" + * "0": "1", + * "1": "2", + * "2": "3" * } * ``` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -62,8 +62,9 @@ function f(fInputPortToNodes, fInputNodeToValues) { * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" - * since f = v1 * v2 * v3 and df/dx = v1 * v3 = 3. + * the above example data and assume xId is "1", then the value is "3" + * since f(x_i) = x_0 * x_1 * x_2 and df/dx = df/d(x_1) = x_0 * x_2 = 1 * 3 = + * 3. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { // Write the logic here From 144efb1a12e55303356e2dbce32a0794cf64d9fc Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Fri, 22 Dec 2023 23:00:05 +0800 Subject: [PATCH 04/18] use zero-based IDs --- .../src/components/GraphContainer.test.tsx | 158 +++++++++--------- .../src/components/GraphContainer.tsx | 4 +- .../GraphContainer.test.tsx.snap | 22 +-- 3 files changed, 92 insertions(+), 92 deletions(-) diff --git a/interactive-computational-graph/src/components/GraphContainer.test.tsx b/interactive-computational-graph/src/components/GraphContainer.test.tsx index 5201f43..ca3168b 100644 --- a/interactive-computational-graph/src/components/GraphContainer.test.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.test.tsx @@ -76,12 +76,12 @@ it("edges and add node itself should be removed after removing add node", () => fireEvent.click(sumItem); // Connect from the constant nodes to the add node - connectEdge("1", "output", "3", "a"); - connectEdge("2", "output", "3", "b"); + connectEdge("0", "output", "2", "a"); + connectEdge("1", "output", "2", "b"); // Remove the add node - removeEdge(["reactflow__edge-1output-3a", "reactflow__edge-2output-3b"]); - removeNode(["3"]); + removeEdge(["reactflow__edge-0output-2a", "reactflow__edge-1output-2b"]); + removeNode(["2"]); expect(screen.getByText("c_1")).toBeInTheDocument(); expect(screen.getByText("c_1")).toBeInTheDocument(); @@ -107,12 +107,12 @@ it("edges and sum node itself should be removed after removing sum node", () => fireEvent.click(sumItem); // Connect from the constant nodes to the sum node - connectEdge("1", "output", "3", "x_i"); - connectEdge("2", "output", "3", "x_i"); + connectEdge("0", "output", "2", "x_i"); + connectEdge("1", "output", "2", "x_i"); // Remove the sum node - removeEdge(["reactflow__edge-1output-3x_i", "reactflow__edge-2output-3x_i"]); - removeNode(["3"]); + removeEdge(["reactflow__edge-0output-2x_i", "reactflow__edge-1output-2x_i"]); + removeNode(["2"]); expect(screen.getByText("c_1")).toBeInTheDocument(); expect(screen.getByText("c_2")).toBeInTheDocument(); @@ -137,12 +137,12 @@ it("can connect same node to multiple ports, then remove the connections", () => fireEvent.click(addItem); // Connect from the variable node to the add node - connectEdge("1", "output", "2", "a"); - connectEdge("1", "output", "2", "b"); + connectEdge("0", "output", "1", "a"); + connectEdge("0", "output", "1", "b"); // Disconnect from the variable node to the add node - removeEdge(["reactflow__edge-1output-2a"]); - removeEdge(["reactflow__edge-1output-2b"]); + removeEdge(["reactflow__edge-0output-1a"]); + removeEdge(["reactflow__edge-0output-1b"]); }); it("input text fields should hide/show properly", () => { @@ -157,22 +157,22 @@ it("input text fields should hide/show properly", () => { const sumItem = screen.getByText("Sum"); fireEvent.click(sumItem); - expect(screen.getByTestId("input-item-3-x_i")).toBeInTheDocument(); + expect(screen.getByTestId("input-item-2-x_i")).toBeInTheDocument(); // Connect from the 1st constant node to the sum node - connectEdge("1", "output", "3", "x_i"); + connectEdge("0", "output", "2", "x_i"); - expect(screen.queryByTestId("input-item-3-x_i")).toBeNull(); + expect(screen.queryByTestId("input-item-2-x_i")).toBeNull(); // Connect from the 2nd constant node to the sum node - connectEdge("2", "output", "3", "x_i"); + connectEdge("1", "output", "2", "x_i"); - expect(screen.queryByTestId("input-item-3-x_i")).toBeNull(); + expect(screen.queryByTestId("input-item-2-x_i")).toBeNull(); // Disconnect from the constant nodes to the sum node - removeEdge(["reactflow__edge-1output-3x_i", "reactflow__edge-2output-3x_i"]); + removeEdge(["reactflow__edge-0output-2x_i", "reactflow__edge-1output-2x_i"]); - expect(screen.getByTestId("input-item-3-x_i")).toBeInTheDocument(); + expect(screen.getByTestId("input-item-2-x_i")).toBeInTheDocument(); }); it("should show error message when connecting the same edge twice", () => { @@ -187,9 +187,9 @@ it("should show error message when connecting the same edge twice", () => { fireEvent.click(sumItem); // Connect from the 1st constant node to the add node port a twice - connectEdge("1", "output", "2", "x_i"); + connectEdge("0", "output", "1", "x_i"); expect(screen.queryByRole("alert")).toBeNull(); - connectEdge("1", "output", "2", "x_i"); + connectEdge("0", "output", "1", "x_i"); const snackbar = screen.getByRole("alert"); expect(snackbar).toBeInTheDocument(); @@ -211,8 +211,8 @@ it("should show error message when connecting to the single-connection port", () fireEvent.click(sumItem); // Connect from the 2nd constant node to the add node port a - connectEdge("1", "output", "3", "a"); - connectEdge("2", "output", "3", "a"); + connectEdge("0", "output", "2", "a"); + connectEdge("1", "output", "2", "a"); const snackbar = screen.getByRole("alert"); expect(snackbar).toBeInTheDocument(); @@ -229,7 +229,7 @@ it("should show error message when causing a cycle", () => { fireEvent.click(sumItem); // Connect from the output of sum node to the input of sum node - connectEdge("1", "output", "1", "x_i"); + connectEdge("0", "output", "0", "x_i"); const snackbar = screen.getByRole("alert"); expect(snackbar).toBeInTheDocument(); @@ -251,15 +251,15 @@ it("derivative target should reset when the target node is removed", () => { fireEvent.click(sumItem); // Connect from the constant nodes to the sum node - connectEdge("1", "output", "3", "x_i"); - connectEdge("2", "output", "3", "x_i"); + connectEdge("0", "output", "2", "x_i"); + connectEdge("1", "output", "2", "x_i"); // Select the sum node as the derivative target setDerivativeTarget("s_1"); // Remove the sum node - removeEdge(["reactflow__edge-1output-3x_i", "reactflow__edge-2output-3x_i"]); - removeNode(["3"]); + removeEdge(["reactflow__edge-0output-2x_i", "reactflow__edge-1output-2x_i"]); + removeNode(["2"]); expect(getDerivativeTarget()).toBe(""); }); @@ -277,14 +277,14 @@ it("derivative target name should update when the node name is updated", () => { fireEvent.click(sumItem); // Connect from the constant nodes to the sum node - connectEdge("1", "output", "3", "x_i"); - connectEdge("2", "output", "3", "x_i"); + connectEdge("0", "output", "2", "x_i"); + connectEdge("1", "output", "2", "x_i"); // Select the sum node as the derivative target setDerivativeTarget("s_1"); // Update the node name of the sum node - setNodeName("3", "s_1"); + setNodeName("2", "s_1"); expect(getDerivativeTarget()).toBe("s_1"); }); @@ -295,12 +295,12 @@ it("outputs should be set correctly after adding the nodes", () => { // Add the nodes const addItem = screen.getByText("Add"); const cosItem = screen.getByText("Cos"); - fireEvent.click(addItem); // id=1 - fireEvent.click(cosItem); // id=2 + fireEvent.click(addItem); // id=0 + fireEvent.click(cosItem); // id=1 // Check the output values - expect(getOutputItemValue("1", "VALUE")).toBe("0"); - expect(getOutputItemValue("2", "VALUE")).toBe("1"); + expect(getOutputItemValue("0", "VALUE")).toBe("0"); + expect(getOutputItemValue("1", "VALUE")).toBe("1"); }); // It uses example from https://colah.github.io/posts/2015-08-Backprop/ @@ -311,121 +311,121 @@ it("outputs should change when derivative mode/target is changed", () => { const variableItem = screen.getByText("Variable"); const addItem = screen.getByText("Add"); const multiplyItem = screen.getByText("Multiply"); + fireEvent.click(variableItem); // id=0 fireEvent.click(variableItem); // id=1 - fireEvent.click(variableItem); // id=2 + fireEvent.click(addItem); // id=2 fireEvent.click(addItem); // id=3 - fireEvent.click(addItem); // id=4 - fireEvent.click(multiplyItem); // id=5 + fireEvent.click(multiplyItem); // id=4 // Connect from the variable nodes to add nodes + connectEdge("0", "output", "2", "a"); + connectEdge("1", "output", "2", "b"); connectEdge("1", "output", "3", "a"); - connectEdge("2", "output", "3", "b"); - connectEdge("2", "output", "4", "a"); // Set add node input values - setInputItemValue("1", "value", "2"); - setInputItemValue("2", "value", "1"); - setInputItemValue("4", "b", "1"); + setInputItemValue("0", "value", "2"); + setInputItemValue("1", "value", "1"); + setInputItemValue("3", "b", "1"); // Connect from the add nodes to multiply node - connectEdge("3", "output", "5", "a"); - connectEdge("4", "output", "5", "b"); + connectEdge("2", "output", "4", "a"); + connectEdge("3", "output", "4", "b"); // Select the multiply node as the derivative target setDerivativeTarget("m_1"); // Check the output values - expect(getOutputItemValue("3", "VALUE")).toBe("3"); - expect(getOutputItemValue("4", "VALUE")).toBe("2"); - expect(getOutputItemValue("5", "VALUE")).toBe("6"); + expect(getOutputItemValue("2", "VALUE")).toBe("3"); + expect(getOutputItemValue("3", "VALUE")).toBe("2"); + expect(getOutputItemValue("4", "VALUE")).toBe("6"); // Check the derivative labels - expect(getOutputItemLabelText("1", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("0", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{m_1}}{\\partial{v_1}}=", ); - expect(getOutputItemLabelText("2", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("1", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{m_1}}{\\partial{v_2}}=", ); - expect(getOutputItemLabelText("3", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("2", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{m_1}}{\\partial{a_1}}=", ); - expect(getOutputItemLabelText("4", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("3", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{m_1}}{\\partial{a_2}}=", ); - expect(getOutputItemLabelText("5", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("4", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{m_1}}{\\partial{m_1}}=", ); // Check the derivative values - expect(getOutputItemValue("1", "DERIVATIVE")).toBe("2"); - expect(getOutputItemValue("2", "DERIVATIVE")).toBe("5"); - expect(getOutputItemValue("3", "DERIVATIVE")).toBe("2"); - expect(getOutputItemValue("4", "DERIVATIVE")).toBe("3"); - expect(getOutputItemValue("5", "DERIVATIVE")).toBe("1"); + expect(getOutputItemValue("0", "DERIVATIVE")).toBe("2"); + expect(getOutputItemValue("1", "DERIVATIVE")).toBe("5"); + expect(getOutputItemValue("2", "DERIVATIVE")).toBe("2"); + expect(getOutputItemValue("3", "DERIVATIVE")).toBe("3"); + expect(getOutputItemValue("4", "DERIVATIVE")).toBe("1"); // Change the differentiation mode to forward mode toggleDifferentiationMode(); // Check the output values - expect(getOutputItemValue("3", "VALUE")).toBe("3"); - expect(getOutputItemValue("4", "VALUE")).toBe("2"); - expect(getOutputItemValue("5", "VALUE")).toBe("6"); + expect(getOutputItemValue("2", "VALUE")).toBe("3"); + expect(getOutputItemValue("3", "VALUE")).toBe("2"); + expect(getOutputItemValue("4", "VALUE")).toBe("6"); // Check the derivative labels - expect(getOutputItemLabelText("1", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("0", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{v_1}}{\\partial{m_1}}=", ); - expect(getOutputItemLabelText("2", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("1", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{v_2}}{\\partial{m_1}}=", ); - expect(getOutputItemLabelText("3", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("2", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{a_1}}{\\partial{m_1}}=", ); - expect(getOutputItemLabelText("4", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("3", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{a_2}}{\\partial{m_1}}=", ); - expect(getOutputItemLabelText("5", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("4", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{m_1}}{\\partial{m_1}}=", ); // Check the derivative values + expect(getOutputItemValue("0", "DERIVATIVE")).toBe("0"); expect(getOutputItemValue("1", "DERIVATIVE")).toBe("0"); expect(getOutputItemValue("2", "DERIVATIVE")).toBe("0"); expect(getOutputItemValue("3", "DERIVATIVE")).toBe("0"); - expect(getOutputItemValue("4", "DERIVATIVE")).toBe("0"); - expect(getOutputItemValue("5", "DERIVATIVE")).toBe("1"); + expect(getOutputItemValue("4", "DERIVATIVE")).toBe("1"); // Select the second variable node as the derivative target setDerivativeTarget("v_2"); // Check the output values - expect(getOutputItemValue("3", "VALUE")).toBe("3"); - expect(getOutputItemValue("4", "VALUE")).toBe("2"); - expect(getOutputItemValue("5", "VALUE")).toBe("6"); + expect(getOutputItemValue("2", "VALUE")).toBe("3"); + expect(getOutputItemValue("3", "VALUE")).toBe("2"); + expect(getOutputItemValue("4", "VALUE")).toBe("6"); // Check the derivative labels - expect(getOutputItemLabelText("1", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("0", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{v_1}}{\\partial{v_2}}=", ); - expect(getOutputItemLabelText("2", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("1", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{v_2}}{\\partial{v_2}}=", ); - expect(getOutputItemLabelText("3", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("2", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{a_1}}{\\partial{v_2}}=", ); - expect(getOutputItemLabelText("4", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("3", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{a_2}}{\\partial{v_2}}=", ); - expect(getOutputItemLabelText("5", "DERIVATIVE")).toBe( + expect(getOutputItemLabelText("4", "DERIVATIVE")).toBe( "\\displaystyle \\frac{\\partial{m_1}}{\\partial{v_2}}=", ); // Check the derivative values - expect(getOutputItemValue("1", "DERIVATIVE")).toBe("0"); + expect(getOutputItemValue("0", "DERIVATIVE")).toBe("0"); + expect(getOutputItemValue("1", "DERIVATIVE")).toBe("1"); expect(getOutputItemValue("2", "DERIVATIVE")).toBe("1"); expect(getOutputItemValue("3", "DERIVATIVE")).toBe("1"); - expect(getOutputItemValue("4", "DERIVATIVE")).toBe("1"); - expect(getOutputItemValue("5", "DERIVATIVE")).toBe("5"); + expect(getOutputItemValue("4", "DERIVATIVE")).toBe("5"); }); const renderGraphContainer = (): void => { diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index bec4839..a8dde24 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -154,9 +154,9 @@ const GraphContainer: FunctionComponent = ({ helpText: "Calculate squared error $ (y_t - y_e)^2 $", }, ]); - const [nextNodeId, setNextNodeId] = useState(1); + const [nextNodeId, setNextNodeId] = useState(0); const [nodeNameBuilder] = useState(new NodeNameBuilder()); - const [nextOperationId, setNextOperationId] = useState(1); + const [nextOperationId, setNextOperationId] = useState(0); // Feature panel states const [explainDerivativeData, setExplainDerivativeData] = useState< diff --git a/interactive-computational-graph/src/components/__snapshots__/GraphContainer.test.tsx.snap b/interactive-computational-graph/src/components/__snapshots__/GraphContainer.test.tsx.snap index b6aa114..28ba03e 100644 --- a/interactive-computational-graph/src/components/__snapshots__/GraphContainer.test.tsx.snap +++ b/interactive-computational-graph/src/components/__snapshots__/GraphContainer.test.tsx.snap @@ -25,7 +25,7 @@ Object { }, "dragHandle": ".drag-handle", "height": 1, - "id": "1", + "id": "0", "position": Object { "x": 100, "y": 100, @@ -55,7 +55,7 @@ Object { }, "dragHandle": ".drag-handle", "height": 1, - "id": "2", + "id": "1", "position": Object { "x": 200, "y": 200, @@ -93,7 +93,7 @@ Object { }, "dragHandle": ".drag-handle", "height": 1, - "id": "1", + "id": "0", "position": Object { "x": 100, "y": 100, @@ -123,7 +123,7 @@ Object { }, "dragHandle": ".drag-handle", "height": 1, - "id": "2", + "id": "1", "position": Object { "x": 200, "y": 200, @@ -161,7 +161,7 @@ Object { }, "dragHandle": ".drag-handle", "height": 1, - "id": "1", + "id": "0", "position": Object { "x": 100, "y": 100, @@ -191,7 +191,7 @@ Object { Object { "labelParts": Array [ Object { - "href": "2", + "href": "1", "id": "derivative", "latex": "\\\\displaystyle \\\\frac{\\\\partial{?}}{\\\\partial{v_1}}", "type": "latexLink", @@ -209,7 +209,7 @@ Object { }, "dragHandle": ".drag-handle", "height": 1, - "id": "2", + "id": "1", "position": Object { "x": 100, "y": 100, @@ -251,7 +251,7 @@ Object { Object { "labelParts": Array [ Object { - "href": "3", + "href": "2", "id": "derivative", "latex": "\\\\displaystyle \\\\frac{\\\\partial{?}}{\\\\partial{s_1}}", "type": "latexLink", @@ -269,7 +269,7 @@ Object { }, "dragHandle": ".drag-handle", "height": 1, - "id": "3", + "id": "2", "position": Object { "x": 100, "y": 100, @@ -307,7 +307,7 @@ Object { }, "dragHandle": ".drag-handle", "height": 1, - "id": "1", + "id": "0", "position": Object { "x": 100, "y": 100, @@ -337,7 +337,7 @@ Object { }, "dragHandle": ".drag-handle", "height": 1, - "id": "2", + "id": "1", "position": Object { "x": 100, "y": 100, From b678581486afec8825ccd6e2c87c534490b04a9b Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Sun, 24 Dec 2023 02:09:29 +0800 Subject: [PATCH 05/18] add subtract operation --- .../src/components/GraphContainer.tsx | 11 +++ .../src/features/BuiltInCode.ts | 85 +++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index a8dde24..375a1ef 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -35,6 +35,8 @@ import { PRODUCT_F_CODE, SQUARED_ERROR_DFDX_CODE, SQUARED_ERROR_F_CODE, + SUBTRACT_DFDX_CODE, + SUBTRACT_F_CODE, SUM_DFDX_CODE, SUM_F_CODE, TEMPLATE_DFDX_CODE, @@ -108,6 +110,15 @@ const GraphContainer: FunctionComponent = ({ inputPorts: [new Port("a", false), new Port("b", false)], helpText: "Add two numbers $ a + b $", }, + { + id: "subtract", + text: "Subtract", + type: "SIMPLE", + namePrefix: "s", + operation: new Operation(SUBTRACT_F_CODE, SUBTRACT_DFDX_CODE), + inputPorts: [new Port("a", false), new Port("b", false)], + helpText: "Subtract two numbers $ a - b $", + }, { id: "multiply", text: "Multiply", diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index 148a5b6..835d659 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -151,6 +151,89 @@ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { } `; +const SUBTRACT_F_CODE = `\ +/** + * Calculates f(). + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for add: + * \`\`\`json + * { + * "a": ["0"], + * "b": ["1"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for add: + * \`\`\`json + * { + * "0": "0.2", + * "1": "0.4" + * } + * \`\`\` + * @returns {string} Evaluated f value. For example: if we consider + * the above example data, then the value is "-0.2" because + * f(a, b) = a - b = 0.2 - 0.4 = -0.2. + */ +function f(fInputPortToNodes, fInputNodeToValues) { + if (fInputPortToNodes.a.length !== 1) { + throw new Error("Should have exactly 1 input node for port a"); + } + if (fInputPortToNodes.b.length !== 1) { + throw new Error("Should have exactly 1 input node for port b"); + } + const aInputNodeId = fInputPortToNodes.a[0]; + const bInputNodeId = fInputPortToNodes.b[0]; + const a = parseFloat(fInputNodeToValues[aInputNodeId]); + const b = parseFloat(fInputNodeToValues[bInputNodeId]); + const y = a - b; + return \`\${y}\`; +} +`; + +const SUBTRACT_DFDX_CODE = `\ +/** + * Calculates df/dx. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for add: + * \`\`\`json + * { + * "a": ["0"], + * "b": ["1"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for add: + * \`\`\`json + * { + * "0": "0.2", + * "1": "0.4" + * } + * \`\`\` + * @param {string} xId Node ID of x. Note that the framework will not call this + * function for the following cases: + * - x is a constant node (i.e., x will always be a variable) + * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) + * @returns {string} Evaluated derivative df/dy. For example, if we consider + * the above example data and assume xId is "1", then the value is "-1" + * since f(a, b) = a - b and df/dx = df/db = -1. + */ +function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + const hasXInA = fInputPortToNodes.a.includes(xId); + const hasXInB = fInputPortToNodes.b.includes(xId); + if (!hasXInA && !hasXInB) { + return "0"; + } + const df = hasXInA ? 1 : -1; + return \`\${df}\`; +} +`; + const MULTIPLY_F_CODE = `\ /** * Calculates f(). @@ -806,6 +889,8 @@ export { SIGMOID_F_CODE, SQUARED_ERROR_DFDX_CODE, SQUARED_ERROR_F_CODE, + SUBTRACT_DFDX_CODE, + SUBTRACT_F_CODE, SUM_DFDX_CODE, SUM_F_CODE, TEMPLATE_DFDX_CODE, From ed14d19e5d2eeb1f5f8dd40e523b2cac23378c81 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Sun, 24 Dec 2023 02:30:10 +0800 Subject: [PATCH 06/18] add divide operation --- .../src/components/GraphContainer.tsx | 11 +++ .../src/features/BuiltInCode.ts | 97 ++++++++++++++++++- 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index 375a1ef..f8af5be 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -29,6 +29,8 @@ import { ADD_F_CODE, COS_DFDX_CODE, COS_F_CODE, + DIVIDE_DFDX_CODE, + DIVIDE_F_CODE, MULTIPLY_DFDX_CODE, MULTIPLY_F_CODE, PRODUCT_DFDX_CODE, @@ -128,6 +130,15 @@ const GraphContainer: FunctionComponent = ({ inputPorts: [new Port("a", false), new Port("b", false)], helpText: "Multiply two numbers $ a * b $", }, + { + id: "divide", + text: "Divide", + type: "SIMPLE", + namePrefix: "d", + operation: new Operation(DIVIDE_F_CODE, DIVIDE_DFDX_CODE), + inputPorts: [new Port("a", false), new Port("b", false)], + helpText: "Divide two numbers $ a / b $", + }, { id: "sum", text: "Sum", diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index 835d659..9aa7da9 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -156,7 +156,7 @@ const SUBTRACT_F_CODE = `\ * Calculates f(). * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for add: + * Example data for subtract: * \`\`\`json * { * "a": ["0"], @@ -165,7 +165,7 @@ const SUBTRACT_F_CODE = `\ * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for add: + * Example data for subtract: * \`\`\`json * { * "0": "0.2", @@ -197,7 +197,7 @@ const SUBTRACT_DFDX_CODE = `\ * Calculates df/dx. * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for add: + * Example data for subtract: * \`\`\`json * { * "a": ["0"], @@ -206,7 +206,7 @@ const SUBTRACT_DFDX_CODE = `\ * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for add: + * Example data for subtract: * \`\`\`json * { * "0": "0.2", @@ -320,6 +320,93 @@ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { } `; +const DIVIDE_F_CODE = `\ +/** + * Calculates f(). + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for divide: + * \`\`\`json + * { + * "a": ["0"], + * "b": ["1"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for divide: + * \`\`\`json + * { + * "0": "0.2", + * "1": "0.4" + * } + * \`\`\` + * @returns {string} Evaluated f value. For example: if we consider + * the above example data, then the value is "0.5" because + * f(a, b) = a / b = 0.2 / 0.4 = 0.5. + */ +function f(fInputPortToNodes, fInputNodeToValues) { + if (fInputPortToNodes.a.length !== 1) { + throw new Error("Should have exactly 1 input node for port a"); + } + if (fInputPortToNodes.b.length !== 1) { + throw new Error("Should have exactly 1 input node for port b"); + } + const aInputNodeId = fInputPortToNodes.a[0]; + const bInputNodeId = fInputPortToNodes.b[0]; + const a = parseFloat(fInputNodeToValues[aInputNodeId]); + const b = parseFloat(fInputNodeToValues[bInputNodeId]); + const y = a / b; + return \`\${y}\`; +} +`; + +const DIVIDE_DFDX_CODE = `\ +/** + * Calculates df/dx. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for divide: + * \`\`\`json + * { + * "a": ["0"], + * "b": ["1"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for divide: + * \`\`\`json + * { + * "0": "0.2", + * "1": "0.4" + * } + * \`\`\` + * @param {string} xId Node ID of x. Note that the framework will not call this + * function for the following cases: + * - x is a constant node (i.e., x will always be a variable) + * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) + * @returns {string} Evaluated derivative df/dy. For example, if we consider + * the above example data and assume xId is "1", then the value is "-1.25" + * since f(a, b) = a / b and df/dx = df/db = -(a / b^2) = -1.25. + */ +function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + const hasXInA = fInputPortToNodes.a.includes(xId); + const hasXInB = fInputPortToNodes.b.includes(xId); + if (!hasXInA && !hasXInB) { + return "0"; + } + const aInputNodeId = fInputPortToNodes.a[0]; + const bInputNodeId = fInputPortToNodes.b[0]; + const a = parseFloat(fInputNodeToValues[aInputNodeId]); + const b = parseFloat(fInputNodeToValues[bInputNodeId]); + const df = hasXInA ? 1 / b : -(a / Math.pow(b, 2)); + return \`\${df}\`; +} +`; + const SUM_F_CODE = `\ /** * Calculates f(). @@ -877,6 +964,8 @@ export { ADD_F_CODE, COS_DFDX_CODE, COS_F_CODE, + DIVIDE_DFDX_CODE, + DIVIDE_F_CODE, IDENTITY_DFDX_CODE, IDENTITY_F_CODE, MULTIPLY_DFDX_CODE, From c04f0364852d0f181abb76f1801b6720273ebb11 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Sun, 24 Dec 2023 12:24:16 +0800 Subject: [PATCH 07/18] add power operation --- .../src/components/GraphContainer.tsx | 11 +++ .../src/features/BuiltInCode.ts | 90 +++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index f8af5be..c8bdd90 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -33,6 +33,8 @@ import { DIVIDE_F_CODE, MULTIPLY_DFDX_CODE, MULTIPLY_F_CODE, + POWER_DFDX_CODE, + POWER_F_CODE, PRODUCT_DFDX_CODE, PRODUCT_F_CODE, SQUARED_ERROR_DFDX_CODE, @@ -139,6 +141,15 @@ const GraphContainer: FunctionComponent = ({ inputPorts: [new Port("a", false), new Port("b", false)], helpText: "Divide two numbers $ a / b $", }, + { + id: "power", + text: "Power", + type: "SIMPLE", + namePrefix: "p", + operation: new Operation(POWER_F_CODE, POWER_DFDX_CODE), + inputPorts: [new Port("x", false), new Port("n", false)], + helpText: "Calculate the power $ x ^ n $", + }, { id: "sum", text: "Sum", diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index 9aa7da9..4b07b34 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -407,6 +407,94 @@ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { } `; +const POWER_F_CODE = `\ +/** + * Calculates f(). + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for power: + * \`\`\`json + * { + * "x": ["0"], + * "n": ["1"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for power: + * \`\`\`json + * { + * "0": "2", + * "1": "3" + * } + * \`\`\` + * @returns {string} Evaluated f value. For example: if we consider + * the above example data, then the value is "8" because + * f(x, n) = x ^ n = 2 ^ 3 = 8. + */ +function f(fInputPortToNodes, fInputNodeToValues) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port a"); + } + if (fInputPortToNodes.n.length !== 1) { + throw new Error("Should have exactly 1 input node for port b"); + } + const xInputNodeId = fInputPortToNodes.x[0]; + const nInputNodeId = fInputPortToNodes.n[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const n = parseFloat(fInputNodeToValues[nInputNodeId]); + const y = Math.pow(x, n); + return \`\${y}\`; +} +`; + +const POWER_DFDX_CODE = `\ +/** + * Calculates df/dx. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for power: + * \`\`\`json + * { + * "x": ["0"], + * "n": ["1"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for power: + * \`\`\`json + * { + * "0": "2", + * "1": "3" + * } + * \`\`\` + * @param {string} xId Node ID of x. Note that the framework will not call this + * function for the following cases: + * - x is a constant node (i.e., x will always be a variable) + * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) + * @returns {string} Evaluated derivative df/dy. For example, if we consider + * the above example data and assume xId is "0", then the value is "-1.25" + * since f(x, n) = x ^ n and df/dx = n * (x ^ (n - 1)) = 3 * (2 ^ (3 - 1)) = + * 12. + */ +function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + const hasXInX = fInputPortToNodes.x.includes(xId); + const hasXInN = fInputPortToNodes.n.includes(xId); + if (!hasXInX && !hasXInN) { + return "0"; + } + const xInputNodeId = fInputPortToNodes.x[0]; + const nInputNodeId = fInputPortToNodes.n[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const n = parseFloat(fInputNodeToValues[nInputNodeId]); + const df = hasXInX ? n * Math.pow(x, n - 1) : Math.pow(x, n) * Math.log(x); + return \`\${df}\`; +} +`; + const SUM_F_CODE = `\ /** * Calculates f(). @@ -970,6 +1058,8 @@ export { IDENTITY_F_CODE, MULTIPLY_DFDX_CODE, MULTIPLY_F_CODE, + POWER_DFDX_CODE, + POWER_F_CODE, PRODUCT_DFDX_CODE, PRODUCT_F_CODE, RELU_DFDX_CODE, From 2523805f143c6370c0e07fa97082b43acf5a35bc Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Sun, 24 Dec 2023 12:29:44 +0800 Subject: [PATCH 08/18] check for connected node number --- .../src/features/BuiltInCode.ts | 61 +++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index 4b07b34..b385789 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -142,6 +142,12 @@ const ADD_DFDX_CODE = `\ * since f(a, b) = a + b and df/dx = df/db = 1. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.a.length !== 1) { + throw new Error("Should have exactly 1 input node for port a"); + } + if (fInputPortToNodes.b.length !== 1) { + throw new Error("Should have exactly 1 input node for port b"); + } const hasXInA = fInputPortToNodes.a.includes(xId); const hasXInB = fInputPortToNodes.b.includes(xId); if (!hasXInA && !hasXInB) { @@ -224,6 +230,12 @@ const SUBTRACT_DFDX_CODE = `\ * since f(a, b) = a - b and df/dx = df/db = -1. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.a.length !== 1) { + throw new Error("Should have exactly 1 input node for port a"); + } + if (fInputPortToNodes.b.length !== 1) { + throw new Error("Should have exactly 1 input node for port b"); + } const hasXInA = fInputPortToNodes.a.includes(xId); const hasXInB = fInputPortToNodes.b.includes(xId); if (!hasXInA && !hasXInB) { @@ -307,6 +319,12 @@ const MULTIPLY_DFDX_CODE = `\ * since f(a, b) = a * b and df/dx = df/db = a = 0.2. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.a.length !== 1) { + throw new Error("Should have exactly 1 input node for port a"); + } + if (fInputPortToNodes.b.length !== 1) { + throw new Error("Should have exactly 1 input node for port b"); + } const hasXInA = fInputPortToNodes.a.includes(xId); const hasXInB = fInputPortToNodes.b.includes(xId); if (!hasXInA && !hasXInB) { @@ -393,6 +411,12 @@ const DIVIDE_DFDX_CODE = `\ * since f(a, b) = a / b and df/dx = df/db = -(a / b^2) = -1.25. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.a.length !== 1) { + throw new Error("Should have exactly 1 input node for port a"); + } + if (fInputPortToNodes.b.length !== 1) { + throw new Error("Should have exactly 1 input node for port b"); + } const hasXInA = fInputPortToNodes.a.includes(xId); const hasXInB = fInputPortToNodes.b.includes(xId); if (!hasXInA && !hasXInB) { @@ -434,10 +458,10 @@ const POWER_F_CODE = `\ */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.x.length !== 1) { - throw new Error("Should have exactly 1 input node for port a"); + throw new Error("Should have exactly 1 input node for port x"); } if (fInputPortToNodes.n.length !== 1) { - throw new Error("Should have exactly 1 input node for port b"); + throw new Error("Should have exactly 1 input node for port n"); } const xInputNodeId = fInputPortToNodes.x[0]; const nInputNodeId = fInputPortToNodes.n[0]; @@ -481,6 +505,12 @@ const POWER_DFDX_CODE = `\ * 12. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } + if (fInputPortToNodes.n.length !== 1) { + throw new Error("Should have exactly 1 input node for port n"); + } const hasXInX = fInputPortToNodes.x.includes(xId); const hasXInN = fInputPortToNodes.n.includes(xId); if (!hasXInX && !hasXInN) { @@ -715,12 +745,12 @@ const COS_DFDX_CODE = `\ * since f(x) = cos(x) and df/dx = -sin(x) = 0. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { - if (!fInputPortToNodes.x.includes(xId)) { - return "0"; - } if (fInputPortToNodes.x.length !== 1) { throw new Error("Should have exactly 1 input node for port x"); } + if (!fInputPortToNodes.x.includes(xId)) { + return "0"; + } const xInputNodeId = fInputPortToNodes.x[0]; const x = parseFloat(fInputNodeToValues[xInputNodeId]); const df = -Math.sin(x); @@ -791,6 +821,9 @@ const IDENTITY_DFDX_CODE = `\ * since f(x) = x and df/dx = 1. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } if (!fInputPortToNodes.x.includes(xId)) { return "0"; } @@ -862,12 +895,12 @@ const RELU_DFDX_CODE = `\ * since f(x) = max(0, x) and df/dx = 1 (x > 0). */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { - if (!fInputPortToNodes.x.includes(xId)) { - return "0"; - } if (fInputPortToNodes.x.length !== 1) { throw new Error("Should have exactly 1 input node for port x"); } + if (!fInputPortToNodes.x.includes(xId)) { + return "0"; + } const xInputNodeId = fInputPortToNodes.x[0]; const x = parseFloat(fInputNodeToValues[xInputNodeId]); const df = x > 0 ? 1 : 0; @@ -940,12 +973,12 @@ const SIGMOID_DFDX_CODE = `\ * 0.5 * (1 - 0.5) = 0.25. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { - if (!fInputPortToNodes.x.includes(xId)) { - return "0"; - } if (fInputPortToNodes.x.length !== 1) { throw new Error("Should have exactly 1 input node for port x"); } + if (!fInputPortToNodes.x.includes(xId)) { + return "0"; + } const xInputNodeId = fInputPortToNodes.x[0]; const x = parseFloat(fInputNodeToValues[xInputNodeId]); const sigmoid = 1 / (1 + Math.exp(-x)); @@ -1028,6 +1061,12 @@ const SQUARED_ERROR_DFDX_CODE = `\ * 2 * (0 - 0.5) = -1. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.y_t.length !== 1) { + throw new Error("Should have exactly 1 input node for port y_t"); + } + if (fInputPortToNodes.y_e.length !== 1) { + throw new Error("Should have exactly 1 input node for port y_e"); + } const hasXInYTrue = fInputPortToNodes.y_t.includes(xId); const hasXInYEstimate = fInputPortToNodes.y_e.includes(xId); if (!hasXInYTrue && !hasXInYEstimate) { From 78d61a13ab64fc540fde3b9139b852db79b7b119 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Sun, 24 Dec 2023 21:03:27 +0800 Subject: [PATCH 09/18] add exp and ln operations --- .../src/components/GraphContainer.tsx | 22 +++ .../src/features/BuiltInCode.ts | 158 ++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index c8bdd90..e5feb7d 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -31,6 +31,10 @@ import { COS_F_CODE, DIVIDE_DFDX_CODE, DIVIDE_F_CODE, + EXP_DFDX_CODE, + EXP_F_CODE, + LN_DFDX_CODE, + LN_F_CODE, MULTIPLY_DFDX_CODE, MULTIPLY_F_CODE, POWER_DFDX_CODE, @@ -150,6 +154,24 @@ const GraphContainer: FunctionComponent = ({ inputPorts: [new Port("x", false), new Port("n", false)], helpText: "Calculate the power $ x ^ n $", }, + { + id: "exp", + text: "Exp", + type: "SIMPLE", + namePrefix: "e", + operation: new Operation(EXP_F_CODE, EXP_DFDX_CODE), + inputPorts: [new Port("x", false)], + helpText: "Calculate the exp $ e ^ x $", + }, + { + id: "ln", + text: "Ln", + type: "SIMPLE", + namePrefix: "l", + operation: new Operation(LN_F_CODE, LN_DFDX_CODE), + inputPorts: [new Port("x", false)], + helpText: "Calculate the log $ \\ln(x) $", + }, { id: "sum", text: "Sum", diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index b385789..99c1872 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -525,6 +525,160 @@ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { } `; +const EXP_F_CODE = `\ +/** + * Calculates f(). + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for exp: + * \`\`\`json + * { + * "x": ["0"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for exp: + * \`\`\`json + * { + * "0": "0" + * } + * \`\`\` + * @returns {string} Evaluated f value. For example: if we consider + * the above example data, then the value is "1" because + * f(x) = e^x = e^0 = 1. + */ +function f(fInputPortToNodes, fInputNodeToValues) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } + const xInputNodeId = fInputPortToNodes.x[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const y = Math.exp(x); + return \`\${y}\`; +} +`; + +const EXP_DFDX_CODE = `\ +/** + * Calculates df/dx. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for exp: + * \`\`\`json + * { + * "x": ["0"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for exp: + * \`\`\`json + * { + * "0": "0" + * } + * \`\`\` + * @param {string} xId Node ID of x. Note that the framework will not call this + * function for the following cases: + * - x is a constant node (i.e., x will always be a variable) + * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) + * @returns {string} Evaluated derivative df/dy. For example, if we consider + * the above example data and assume xId is "0", then the value is "1" + * since f(x) = e^x and df/dx = e^x = e^0 = 1 + */ +function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } + if (!fInputPortToNodes.x.includes(xId)) { + return "0"; + } + const xInputNodeId = fInputPortToNodes.x[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const df = Math.exp(x); + return \`\${df}\`; +} +`; + +const LN_F_CODE = `\ +/** + * Calculates f(). + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for ln: + * \`\`\`json + * { + * "x": ["0"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for ln: + * \`\`\`json + * { + * "0": "1" + * } + * \`\`\` + * @returns {string} Evaluated f value. For example: if we consider + * the above example data, then the value is "0" because + * f(x) = ln(x) = ln(1) = 0. + */ +function f(fInputPortToNodes, fInputNodeToValues) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } + const xInputNodeId = fInputPortToNodes.x[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const y = Math.log(x); + return \`\${y}\`; +} +`; + +const LN_DFDX_CODE = `\ +/** + * Calculates df/dx. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for ln: + * \`\`\`json + * { + * "x": ["0"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for ln: + * \`\`\`json + * { + * "0": "1" + * } + * \`\`\` + * @param {string} xId Node ID of x. Note that the framework will not call this + * function for the following cases: + * - x is a constant node (i.e., x will always be a variable) + * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) + * @returns {string} Evaluated derivative df/dy. For example, if we consider + * the above example data and assume xId is "0", then the value is "1" + * since f(x) = ln(x) and df/dx = 1 / x = 1 / 1 = 1 + */ +function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } + if (!fInputPortToNodes.x.includes(xId)) { + return "0"; + } + const xInputNodeId = fInputPortToNodes.x[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const df = 1 / x; + return \`\${df}\`; +} +`; + const SUM_F_CODE = `\ /** * Calculates f(). @@ -1093,8 +1247,12 @@ export { COS_F_CODE, DIVIDE_DFDX_CODE, DIVIDE_F_CODE, + EXP_DFDX_CODE, + EXP_F_CODE, IDENTITY_DFDX_CODE, IDENTITY_F_CODE, + LN_DFDX_CODE, + LN_F_CODE, MULTIPLY_DFDX_CODE, MULTIPLY_F_CODE, POWER_DFDX_CODE, From 952df13e44d9642f6ecca674a646b4b85e793c42 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Sun, 24 Dec 2023 21:52:40 +0800 Subject: [PATCH 10/18] add sin and tan operations --- .../src/components/GraphContainer.tsx | 22 +++ .../src/features/BuiltInCode.ts | 158 ++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index e5feb7d..bc714e9 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -41,12 +41,16 @@ import { POWER_F_CODE, PRODUCT_DFDX_CODE, PRODUCT_F_CODE, + SIN_DFDX_CODE, + SIN_F_CODE, SQUARED_ERROR_DFDX_CODE, SQUARED_ERROR_F_CODE, SUBTRACT_DFDX_CODE, SUBTRACT_F_CODE, SUM_DFDX_CODE, SUM_F_CODE, + TAN_DFDX_CODE, + TAN_F_CODE, TEMPLATE_DFDX_CODE, TEMPLATE_F_CODE, } from "../features/BuiltInCode"; @@ -190,6 +194,15 @@ const GraphContainer: FunctionComponent = ({ inputPorts: [new Port("x_i", true)], helpText: "Multiply all inputs $ \\prod_i x_{i} $", }, + { + id: "sin", + text: "Sin", + type: "SIMPLE", + namePrefix: "s", + operation: new Operation(SIN_F_CODE, SIN_DFDX_CODE), + inputPorts: [new Port("x", true)], + helpText: "Calculate $ \\sin(x) $", + }, { id: "cos", text: "Cos", @@ -199,6 +212,15 @@ const GraphContainer: FunctionComponent = ({ inputPorts: [new Port("x", true)], helpText: "Calculate $ \\cos(x) $", }, + { + id: "tan", + text: "Tan", + type: "SIMPLE", + namePrefix: "t", + operation: new Operation(TAN_F_CODE, TAN_DFDX_CODE), + inputPorts: [new Port("x", true)], + helpText: "Calculate $ \\tan(x) $", + }, { id: "squared_error", text: "Squared Error", diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index 99c1872..1740bf4 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -835,6 +835,83 @@ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { } `; +const SIN_F_CODE = `\ +/** + * Calculates f(). + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for sin: + * \`\`\`json + * { + * "x": ["0"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for sin: + * \`\`\`json + * { + * "0": "0" + * } + * \`\`\` + * @returns {string} Evaluated f value. For example: if we consider + * the above example data, then the value is "0" because + * f(x) = sin(x) = sin(0) = 0. + */ +function f(fInputPortToNodes, fInputNodeToValues) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } + const xInputNodeId = fInputPortToNodes.x[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const y = Math.sin(x); + return \`\${y}\`; +} +`; + +const SIN_DFDX_CODE = `\ +/** + * Calculates df/dx. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for sin: + * \`\`\`json + * { + * "x": ["0"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for sin: + * \`\`\`json + * { + * "0": "0" + * } + * \`\`\` + * @param {string} xId Node ID of x. Note that the framework will not call this + * function for the following cases: + * - x is a constant node (i.e., x will always be a variable) + * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) + * @returns {string} Evaluated derivative df/dy. For example, if we consider + * the above example data and assume xId is "0", then the value is "1" + * since f(x) = sin(x) and df/dx = cos(x) = 1. + */ +function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } + if (!fInputPortToNodes.x.includes(xId)) { + return "0"; + } + const xInputNodeId = fInputPortToNodes.x[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const df = Math.cos(x); + return \`\${df}\`; +} +`; + const COS_F_CODE = `\ /** * Calculates f(). @@ -912,6 +989,83 @@ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { } `; +const TAN_F_CODE = `\ +/** + * Calculates f(). + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for tan: + * \`\`\`json + * { + * "x": ["0"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for tan: + * \`\`\`json + * { + * "0": "0" + * } + * \`\`\` + * @returns {string} Evaluated f value. For example: if we consider + * the above example data, then the value is "0" because + * f(x) = tan(x) = tan(0) = 0. + */ +function f(fInputPortToNodes, fInputNodeToValues) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } + const xInputNodeId = fInputPortToNodes.x[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const y = Math.tan(x); + return \`\${y}\`; +} +`; + +const TAN_DFDX_CODE = `\ +/** + * Calculates df/dx. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for tan: + * \`\`\`json + * { + * "x": ["0"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for tan: + * \`\`\`json + * { + * "0": "0" + * } + * \`\`\` + * @param {string} xId Node ID of x. Note that the framework will not call this + * function for the following cases: + * - x is a constant node (i.e., x will always be a variable) + * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) + * @returns {string} Evaluated derivative df/dy. For example, if we consider + * the above example data and assume xId is "0", then the value is "1" + * since f(x) = tan(x) and df/dx = 1 / cos(x)^2 = 1. + */ +function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } + if (!fInputPortToNodes.x.includes(xId)) { + return "0"; + } + const xInputNodeId = fInputPortToNodes.x[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const df = 1 / Math.pow(Math.cos(x), 2); + return \`\${df}\`; +} +`; + const IDENTITY_F_CODE = `\ /** * Calculates f(). @@ -1263,12 +1417,16 @@ export { RELU_F_CODE, SIGMOID_DFDX_CODE, SIGMOID_F_CODE, + SIN_DFDX_CODE, + SIN_F_CODE, SQUARED_ERROR_DFDX_CODE, SQUARED_ERROR_F_CODE, SUBTRACT_DFDX_CODE, SUBTRACT_F_CODE, SUM_DFDX_CODE, SUM_F_CODE, + TAN_DFDX_CODE, + TAN_F_CODE, TEMPLATE_DFDX_CODE, TEMPLATE_F_CODE, }; From 6cfdc97e4b4fd5c5853fc4c22c171ff84addeb25 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Sun, 24 Dec 2023 23:14:42 +0800 Subject: [PATCH 11/18] add linear operation --- .../src/components/GraphContainer.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index bc714e9..677c894 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -33,6 +33,8 @@ import { DIVIDE_F_CODE, EXP_DFDX_CODE, EXP_F_CODE, + IDENTITY_DFDX_CODE, + IDENTITY_F_CODE, LN_DFDX_CODE, LN_F_CODE, MULTIPLY_DFDX_CODE, @@ -221,6 +223,15 @@ const GraphContainer: FunctionComponent = ({ inputPorts: [new Port("x", true)], helpText: "Calculate $ \\tan(x) $", }, + { + id: "linear", + text: "Linear", + type: "SIMPLE", + namePrefix: "l", + operation: new Operation(IDENTITY_F_CODE, IDENTITY_DFDX_CODE), + inputPorts: [new Port("x", true)], + helpText: "Linear activation function $ x $", + }, { id: "squared_error", text: "Squared Error", From 9fd5ca9b3915972491034b032f2e36116a9429ba Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Sun, 24 Dec 2023 23:19:58 +0800 Subject: [PATCH 12/18] add sigmoid operation --- .../src/components/GraphContainer.tsx | 11 ++++ .../src/features/BuiltInCode.ts | 56 +++++++++---------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index 677c894..e67e5cb 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -43,6 +43,8 @@ import { POWER_F_CODE, PRODUCT_DFDX_CODE, PRODUCT_F_CODE, + SIGMOID_DFDX_CODE, + SIGMOID_F_CODE, SIN_DFDX_CODE, SIN_F_CODE, SQUARED_ERROR_DFDX_CODE, @@ -232,6 +234,15 @@ const GraphContainer: FunctionComponent = ({ inputPorts: [new Port("x", true)], helpText: "Linear activation function $ x $", }, + { + id: "sigmoid", + text: "Sigmoid", + type: "SIMPLE", + namePrefix: "s", + operation: new Operation(SIGMOID_F_CODE, SIGMOID_DFDX_CODE), + inputPorts: [new Port("x", true)], + helpText: "Sigmoid activation function $ \\frac{1}{1 + e^{-x}} $", + }, { id: "squared_error", text: "Squared Error", diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index 1740bf4..4f43ee7 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -1139,12 +1139,12 @@ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { } `; -const RELU_F_CODE = `\ +const SIGMOID_F_CODE = `\ /** * Calculates f(). * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for ReLU: + * Example data for sigmoid: * \`\`\`json * { * "x": ["0"] @@ -1152,15 +1152,15 @@ const RELU_F_CODE = `\ * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for ReLU: + * Example data for sigmoid: * \`\`\`json * { - * "0": "0.5" + * "0": "0" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "0.5" because - * f(x) = max(0, x) = max(0, 0.5) = 0.5. + * f(x) = 1 / (1 + e^(-x)) = 1 / (1 + e^0) = 0.5. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.x.length !== 1) { @@ -1168,17 +1168,17 @@ function f(fInputPortToNodes, fInputNodeToValues) { } const xInputNodeId = fInputPortToNodes.x[0]; const x = parseFloat(fInputNodeToValues[xInputNodeId]); - const y = Math.max(0, x); + const y = 1 / (1 + Math.exp(-x)); return \`\${y}\`; } `; -const RELU_DFDX_CODE = `\ +const SIGMOID_DFDX_CODE = `\ /** * Calculates df/dx. * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for ReLU: + * Example data for sigmoid: * \`\`\`json * { * "x": ["0"] @@ -1186,10 +1186,10 @@ const RELU_DFDX_CODE = `\ * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for ReLU: + * Example data for sigmoid: * \`\`\`json * { - * "0": "0.5" + * "0": "0" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -1199,8 +1199,9 @@ const RELU_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "0", then the value is "1" - * since f(x) = max(0, x) and df/dx = 1 (x > 0). + * the above example data and assume xId is "v2", then the value is "3" + * since f(x) = sigmoid(x) and df/dx = sigmoid(x) * (1 - sigmoid(x)) = + * 0.5 * (1 - 0.5) = 0.25. */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (fInputPortToNodes.x.length !== 1) { @@ -1211,17 +1212,18 @@ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { } const xInputNodeId = fInputPortToNodes.x[0]; const x = parseFloat(fInputNodeToValues[xInputNodeId]); - const df = x > 0 ? 1 : 0; + const sigmoid = 1 / (1 + Math.exp(-x)); + const df = sigmoid * (1 - sigmoid); return \`\${df}\`; } `; -const SIGMOID_F_CODE = `\ +const RELU_F_CODE = `\ /** * Calculates f(). * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for sigmoid: + * Example data for ReLU: * \`\`\`json * { * "x": ["0"] @@ -1229,15 +1231,15 @@ const SIGMOID_F_CODE = `\ * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for sigmoid: + * Example data for ReLU: * \`\`\`json * { - * "0": "0" + * "0": "0.5" * } * \`\`\` * @returns {string} Evaluated f value. For example: if we consider * the above example data, then the value is "0.5" because - * f(x) = 1 / (1 + e^(-x)) = 1 / (1 + e^0) = 0.5. + * f(x) = max(0, x) = max(0, 0.5) = 0.5. */ function f(fInputPortToNodes, fInputNodeToValues) { if (fInputPortToNodes.x.length !== 1) { @@ -1245,17 +1247,17 @@ function f(fInputPortToNodes, fInputNodeToValues) { } const xInputNodeId = fInputPortToNodes.x[0]; const x = parseFloat(fInputNodeToValues[xInputNodeId]); - const y = 1 / (1 + Math.exp(-x)); + const y = Math.max(0, x); return \`\${y}\`; } `; -const SIGMOID_DFDX_CODE = `\ +const RELU_DFDX_CODE = `\ /** * Calculates df/dx. * @param {Record} fInputPortToNodes An object where the keys * are port IDs and the values are node IDs of the connected input nodes. - * Example data for sigmoid: + * Example data for ReLU: * \`\`\`json * { * "x": ["0"] @@ -1263,10 +1265,10 @@ const SIGMOID_DFDX_CODE = `\ * \`\`\` * @param {Record} fInputNodeToValues An object where the keys * are node IDs and the values are node values of the connected input nodes. - * Example data for sigmoid: + * Example data for ReLU: * \`\`\`json * { - * "0": "0" + * "0": "0.5" * } * \`\`\` * @param {string} xId Node ID of x. Note that the framework will not call this @@ -1276,9 +1278,8 @@ const SIGMOID_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" - * since f(x) = sigmoid(x) and df/dx = sigmoid(x) * (1 - sigmoid(x)) = - * 0.5 * (1 - 0.5) = 0.25. + * the above example data and assume xId is "0", then the value is "1" + * since f(x) = max(0, x) and df/dx = 1 (x > 0). */ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { if (fInputPortToNodes.x.length !== 1) { @@ -1289,8 +1290,7 @@ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { } const xInputNodeId = fInputPortToNodes.x[0]; const x = parseFloat(fInputNodeToValues[xInputNodeId]); - const sigmoid = 1 / (1 + Math.exp(-x)); - const df = sigmoid * (1 - sigmoid); + const df = x > 0 ? 1 : 0; return \`\${df}\`; } `; From a4098438e257e3e78bf28221238fcb1d30a07c71 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Sun, 24 Dec 2023 23:27:35 +0800 Subject: [PATCH 13/18] add tanh operation --- .../src/components/GraphContainer.tsx | 12 +++ .../src/features/BuiltInCode.ts | 83 ++++++++++++++++++- 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index e67e5cb..2a684db 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -53,6 +53,8 @@ import { SUBTRACT_F_CODE, SUM_DFDX_CODE, SUM_F_CODE, + TANH_DFDX_CODE, + TANH_F_CODE, TAN_DFDX_CODE, TAN_F_CODE, TEMPLATE_DFDX_CODE, @@ -243,6 +245,16 @@ const GraphContainer: FunctionComponent = ({ inputPorts: [new Port("x", true)], helpText: "Sigmoid activation function $ \\frac{1}{1 + e^{-x}} $", }, + { + id: "tanh", + text: "Tanh", + type: "SIMPLE", + namePrefix: "s", + operation: new Operation(TANH_F_CODE, TANH_DFDX_CODE), + inputPorts: [new Port("x", true)], + helpText: + "Tanh activation function $ \\frac{e^x - e^{-x}}{e^x + e^{-x}} $", + }, { id: "squared_error", text: "Squared Error", diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index 4f43ee7..2e146cd 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -1199,7 +1199,7 @@ const SIGMOID_DFDX_CODE = `\ * - x is not on the forward/reverse differentiation path (i.e., gradient of x * doesn't flow through f node) * @returns {string} Evaluated derivative df/dy. For example, if we consider - * the above example data and assume xId is "v2", then the value is "3" + * the above example data and assume xId is "0", then the value is "3" * since f(x) = sigmoid(x) and df/dx = sigmoid(x) * (1 - sigmoid(x)) = * 0.5 * (1 - 0.5) = 0.25. */ @@ -1218,6 +1218,85 @@ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { } `; +const TANH_F_CODE = `\ +/** + * Calculates f(). + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for tanh: + * \`\`\`json + * { + * "x": ["0"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for tanh: + * \`\`\`json + * { + * "0": "0" + * } + * \`\`\` + * @returns {string} Evaluated f value. For example: if we consider + * the above example data, then the value is "0" because + * f(x) = (e^x - e^(-x)) / (e^x + e^(-x)) = (e^0 - e^0) / (e^0 + e^0) = 0 / 2 = + * 0. + */ +function f(fInputPortToNodes, fInputNodeToValues) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } + const xInputNodeId = fInputPortToNodes.x[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const y = (Math.exp(x) - Math.exp(-x)) / (Math.exp(x) + Math.exp(-x)); + return \`\${y}\`; +} +`; + +const TANH_DFDX_CODE = `\ +/** + * Calculates df/dx. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for tanh: + * \`\`\`json + * { + * "x": ["0"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for tanh: + * \`\`\`json + * { + * "0": "0" + * } + * \`\`\` + * @param {string} xId Node ID of x. Note that the framework will not call this + * function for the following cases: + * - x is a constant node (i.e., x will always be a variable) + * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) + * @returns {string} Evaluated derivative df/dy. For example, if we consider + * the above example data and assume xId is "0", then the value is "1" + * since f(x) = tanh(x) and df/dx = 1 - tanh(x)^2 = 1 - 0^2 = 1 + */ +function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.x.length !== 1) { + throw new Error("Should have exactly 1 input node for port x"); + } + if (!fInputPortToNodes.x.includes(xId)) { + return "0"; + } + const xInputNodeId = fInputPortToNodes.x[0]; + const x = parseFloat(fInputNodeToValues[xInputNodeId]); + const tanh = (Math.exp(x) - Math.exp(-x)) / (Math.exp(x) + Math.exp(-x)); + const df = 1 - Math.pow(tanh, 2); + return \`\${df}\`; +} +`; + const RELU_F_CODE = `\ /** * Calculates f(). @@ -1425,6 +1504,8 @@ export { SUBTRACT_F_CODE, SUM_DFDX_CODE, SUM_F_CODE, + TANH_DFDX_CODE, + TANH_F_CODE, TAN_DFDX_CODE, TAN_F_CODE, TEMPLATE_DFDX_CODE, From 2cd617faee0e6a1b15b32e728d3662df06840802 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Sun, 24 Dec 2023 23:30:00 +0800 Subject: [PATCH 14/18] add ReLU operation --- .../src/components/GraphContainer.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index 2a684db..f1ea140 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -43,6 +43,8 @@ import { POWER_F_CODE, PRODUCT_DFDX_CODE, PRODUCT_F_CODE, + RELU_DFDX_CODE, + RELU_F_CODE, SIGMOID_DFDX_CODE, SIGMOID_F_CODE, SIN_DFDX_CODE, @@ -255,6 +257,15 @@ const GraphContainer: FunctionComponent = ({ helpText: "Tanh activation function $ \\frac{e^x - e^{-x}}{e^x + e^{-x}} $", }, + { + id: "relu", + text: "ReLU", + type: "SIMPLE", + namePrefix: "r", + operation: new Operation(RELU_F_CODE, RELU_DFDX_CODE), + inputPorts: [new Port("x", true)], + helpText: "ReLU activation function $ \\max(0, x) $", + }, { id: "squared_error", text: "Squared Error", From 5ce3f80f9012d7bade2926767e120b85f6a3841e Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Mon, 25 Dec 2023 00:06:26 +0800 Subject: [PATCH 15/18] add binary cross entropy operation --- .../src/components/GraphContainer.tsx | 15 +++ .../src/features/BuiltInCode.ts | 102 ++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index f1ea140..c359aa6 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -27,6 +27,8 @@ import type AddNodeData from "../features/AddNodeData"; import { ADD_DFDX_CODE, ADD_F_CODE, + BINARY_CROSS_ENTROPY_DFDX_CODE, + BINARY_CROSS_ENTROPY_F_CODE, COS_DFDX_CODE, COS_F_CODE, DIVIDE_DFDX_CODE, @@ -275,6 +277,19 @@ const GraphContainer: FunctionComponent = ({ inputPorts: [new Port("y_t", false), new Port("y_e", false)], helpText: "Calculate squared error $ (y_t - y_e)^2 $", }, + { + id: "binary_cross_entropy", + text: "Binary Cross-Entropy", + type: "SIMPLE", + namePrefix: "b", + operation: new Operation( + BINARY_CROSS_ENTROPY_F_CODE, + BINARY_CROSS_ENTROPY_DFDX_CODE, + ), + inputPorts: [new Port("y_t", false), new Port("y_e", false)], + helpText: + "Calculate binary cross-entropy $ y_t * \\log(y_e) + (1 - y_t) * \\log(1 - y_e) $", + }, ]); const [nextNodeId, setNextNodeId] = useState(0); const [nodeNameBuilder] = useState(new NodeNameBuilder()); diff --git a/interactive-computational-graph/src/features/BuiltInCode.ts b/interactive-computational-graph/src/features/BuiltInCode.ts index 2e146cd..3949d85 100644 --- a/interactive-computational-graph/src/features/BuiltInCode.ts +++ b/interactive-computational-graph/src/features/BuiltInCode.ts @@ -1473,9 +1473,111 @@ function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { } `; +const BINARY_CROSS_ENTROPY_F_CODE = `\ +/** + * Calculates f(). + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for squared error: + * \`\`\`json + * { + * "y_t": ["0"], + * "y_e": ["1"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for squared error: + * \`\`\`json + * { + * "0": "0", + * "1": "0.5" + * } + * \`\`\` + * @returns {string} Evaluated f value. For example: if we consider + * the above example data, then the value is "-0.693" because + * f(y_t, y_e) = y_t * log(y_e) + (1 - y_t) * log(1 - y_e) = + * 0 * log(0.5) + (1 - 0) * log(1 - 0.5) ~= -0.693. + */ +function f(fInputPortToNodes, fInputNodeToValues) { + if (fInputPortToNodes.y_t.length !== 1) { + throw new Error("Should have exactly 1 input node for port y_t"); + } + if (fInputPortToNodes.y_e.length !== 1) { + throw new Error("Should have exactly 1 input node for port y_e"); + } + const yTrueInputNodeId = fInputPortToNodes.y_t[0]; + const yEstimateInputNodeId = fInputPortToNodes.y_e[0]; + const yTrue = parseFloat(fInputNodeToValues[yTrueInputNodeId]); + const yEstimate = parseFloat(fInputNodeToValues[yEstimateInputNodeId]); + const y = yTrue * Math.log(yEstimate) + (1 - yTrue) * Math.log(1 - yEstimate); + return \`\${y}\`; +} +`; + +const BINARY_CROSS_ENTROPY_DFDX_CODE = `\ +/** + * Calculates df/dx. + * @param {Record} fInputPortToNodes An object where the keys + * are port IDs and the values are node IDs of the connected input nodes. + * Example data for squared error: + * \`\`\`json + * { + * "y_t": ["0"], + * "y_e": ["1"] + * } + * \`\`\` + * @param {Record} fInputNodeToValues An object where the keys + * are node IDs and the values are node values of the connected input nodes. + * Example data for squared error: + * \`\`\`json + * { + * "0": "0", + * "1": "0.5" + * } + * \`\`\` + * @param {string} xId Node ID of x. Note that the framework will not call this + * function for the following cases: + * - x is a constant node (i.e., x will always be a variable) + * - x is the node of f (i.e., the derivative is always 1) + * - x is not on the forward/reverse differentiation path (i.e., gradient of x + * doesn't flow through f node) + * @returns {string} Evaluated derivative df/dy. For example, if we consider + * the above example data and assume xId is "0", then the value is "-1.386" + * since f(y_t, y_e) = y_t * log(y_e) + (1 - y_t) * log(1 - y_e) and + * df/d(y_t) = log(y_e) - log(1 - y_e) = log(0.5) - log(1 - 0.5) ~= -1.386. + */ +function dfdx(fInputPortToNodes, fInputNodeToValues, xId) { + if (fInputPortToNodes.y_t.length !== 1) { + throw new Error("Should have exactly 1 input node for port y_t"); + } + if (fInputPortToNodes.y_e.length !== 1) { + throw new Error("Should have exactly 1 input node for port y_e"); + } + const hasXInYTrue = fInputPortToNodes.y_t.includes(xId); + const hasXInYEstimate = fInputPortToNodes.y_e.includes(xId); + if (!hasXInYTrue && !hasXInYEstimate) { + return "0"; + } + const yTrueInputNodeId = fInputPortToNodes.y_t[0]; + const yEstimateInputNodeId = fInputPortToNodes.y_e[0]; + const yTrue = parseFloat(fInputNodeToValues[yTrueInputNodeId]); + const yEstimate = parseFloat(fInputNodeToValues[yEstimateInputNodeId]); + let df = 0; + if (hasXInYTrue) { + df = Math.log(yEstimate) - Math.log(1 - yEstimate); + } else { + df = (yTrue - yEstimate) / (yEstimate - Math.pow(yEstimate, 2)); + } + return \`\${df}\`; +} +`; + export { ADD_DFDX_CODE, ADD_F_CODE, + BINARY_CROSS_ENTROPY_DFDX_CODE, + BINARY_CROSS_ENTROPY_F_CODE, COS_DFDX_CODE, COS_F_CODE, DIVIDE_DFDX_CODE, From 8b645c345ae118329475b3f7b332cbf75c9b9eb2 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Tue, 26 Dec 2023 22:14:46 +0800 Subject: [PATCH 16/18] update operation categories --- .../src/components/AddNodesPanel.test.tsx | 2 +- .../src/components/AddNodesPanel.tsx | 4 +- .../components/EditOperationDialog.test.tsx | 2 +- .../src/components/GraphContainer.tsx | 38 +++++++++---------- .../src/features/CoreGraphAdapter.test.ts | 2 +- .../src/features/NodeNameBuilder.test.ts | 2 +- .../src/features/OperationType.ts | 8 +++- 7 files changed, 32 insertions(+), 26 deletions(-) diff --git a/interactive-computational-graph/src/components/AddNodesPanel.test.tsx b/interactive-computational-graph/src/components/AddNodesPanel.test.tsx index 2d05aa7..e30389f 100644 --- a/interactive-computational-graph/src/components/AddNodesPanel.test.tsx +++ b/interactive-computational-graph/src/components/AddNodesPanel.test.tsx @@ -143,7 +143,7 @@ const getFeatureOperations = (): FeatureOperation[] => { { id: "add", text: "Add", - type: "SIMPLE", + type: "basic", namePrefix: "a", operation: new Operation(ADD_F_CODE, ADD_DFDX_CODE), inputPorts: [new Port("a", false), new Port("b", false)], diff --git a/interactive-computational-graph/src/components/AddNodesPanel.tsx b/interactive-computational-graph/src/components/AddNodesPanel.tsx index 4f8cf24..6806e9a 100644 --- a/interactive-computational-graph/src/components/AddNodesPanel.tsx +++ b/interactive-computational-graph/src/components/AddNodesPanel.tsx @@ -34,10 +34,10 @@ const AddNodesPanel: FunctionComponent = ({ onDeleteOperation, }) => { const simpleOperations = featureOperations.filter( - (operation) => operation.type === "SIMPLE", + (operation) => operation.type === "basic", ); const customOperations = featureOperations.filter( - (operation) => operation.type === "CUSTOM", + (operation) => operation.type === "custom", ); const [isEditDialogOpen, setEditDialogOpen] = useState(false); diff --git a/interactive-computational-graph/src/components/EditOperationDialog.test.tsx b/interactive-computational-graph/src/components/EditOperationDialog.test.tsx index c792b3f..5fb206a 100644 --- a/interactive-computational-graph/src/components/EditOperationDialog.test.tsx +++ b/interactive-computational-graph/src/components/EditOperationDialog.test.tsx @@ -95,7 +95,7 @@ const getFeatureOperation = (): FeatureOperation => { return { id: "add", text: "Add", - type: "SIMPLE", + type: "basic", namePrefix: "a", operation: new Operation(ADD_F_CODE, ADD_DFDX_CODE), inputPorts: [new Port("a", false), new Port("b", false)], diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index c359aa6..7392c69 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -126,7 +126,7 @@ const GraphContainer: FunctionComponent = ({ { id: "add", text: "Add", - type: "SIMPLE", + type: "basic", namePrefix: "a", operation: new Operation(ADD_F_CODE, ADD_DFDX_CODE), inputPorts: [new Port("a", false), new Port("b", false)], @@ -135,7 +135,7 @@ const GraphContainer: FunctionComponent = ({ { id: "subtract", text: "Subtract", - type: "SIMPLE", + type: "basic", namePrefix: "s", operation: new Operation(SUBTRACT_F_CODE, SUBTRACT_DFDX_CODE), inputPorts: [new Port("a", false), new Port("b", false)], @@ -144,7 +144,7 @@ const GraphContainer: FunctionComponent = ({ { id: "multiply", text: "Multiply", - type: "SIMPLE", + type: "basic", namePrefix: "m", operation: new Operation(MULTIPLY_F_CODE, MULTIPLY_DFDX_CODE), inputPorts: [new Port("a", false), new Port("b", false)], @@ -153,7 +153,7 @@ const GraphContainer: FunctionComponent = ({ { id: "divide", text: "Divide", - type: "SIMPLE", + type: "basic", namePrefix: "d", operation: new Operation(DIVIDE_F_CODE, DIVIDE_DFDX_CODE), inputPorts: [new Port("a", false), new Port("b", false)], @@ -162,7 +162,7 @@ const GraphContainer: FunctionComponent = ({ { id: "power", text: "Power", - type: "SIMPLE", + type: "basic", namePrefix: "p", operation: new Operation(POWER_F_CODE, POWER_DFDX_CODE), inputPorts: [new Port("x", false), new Port("n", false)], @@ -171,7 +171,7 @@ const GraphContainer: FunctionComponent = ({ { id: "exp", text: "Exp", - type: "SIMPLE", + type: "basic", namePrefix: "e", operation: new Operation(EXP_F_CODE, EXP_DFDX_CODE), inputPorts: [new Port("x", false)], @@ -180,7 +180,7 @@ const GraphContainer: FunctionComponent = ({ { id: "ln", text: "Ln", - type: "SIMPLE", + type: "basic", namePrefix: "l", operation: new Operation(LN_F_CODE, LN_DFDX_CODE), inputPorts: [new Port("x", false)], @@ -189,7 +189,7 @@ const GraphContainer: FunctionComponent = ({ { id: "sum", text: "Sum", - type: "SIMPLE", + type: "aggregate", namePrefix: "s", operation: new Operation(SUM_F_CODE, SUM_DFDX_CODE), inputPorts: [new Port("x_i", true)], @@ -198,7 +198,7 @@ const GraphContainer: FunctionComponent = ({ { id: "product", text: "Product", - type: "SIMPLE", + type: "aggregate", namePrefix: "p", operation: new Operation(PRODUCT_F_CODE, PRODUCT_DFDX_CODE), inputPorts: [new Port("x_i", true)], @@ -207,7 +207,7 @@ const GraphContainer: FunctionComponent = ({ { id: "sin", text: "Sin", - type: "SIMPLE", + type: "trigonometric", namePrefix: "s", operation: new Operation(SIN_F_CODE, SIN_DFDX_CODE), inputPorts: [new Port("x", true)], @@ -216,7 +216,7 @@ const GraphContainer: FunctionComponent = ({ { id: "cos", text: "Cos", - type: "SIMPLE", + type: "trigonometric", namePrefix: "c", operation: new Operation(COS_F_CODE, COS_DFDX_CODE), inputPorts: [new Port("x", true)], @@ -225,7 +225,7 @@ const GraphContainer: FunctionComponent = ({ { id: "tan", text: "Tan", - type: "SIMPLE", + type: "trigonometric", namePrefix: "t", operation: new Operation(TAN_F_CODE, TAN_DFDX_CODE), inputPorts: [new Port("x", true)], @@ -234,7 +234,7 @@ const GraphContainer: FunctionComponent = ({ { id: "linear", text: "Linear", - type: "SIMPLE", + type: "activation", namePrefix: "l", operation: new Operation(IDENTITY_F_CODE, IDENTITY_DFDX_CODE), inputPorts: [new Port("x", true)], @@ -243,7 +243,7 @@ const GraphContainer: FunctionComponent = ({ { id: "sigmoid", text: "Sigmoid", - type: "SIMPLE", + type: "activation", namePrefix: "s", operation: new Operation(SIGMOID_F_CODE, SIGMOID_DFDX_CODE), inputPorts: [new Port("x", true)], @@ -252,7 +252,7 @@ const GraphContainer: FunctionComponent = ({ { id: "tanh", text: "Tanh", - type: "SIMPLE", + type: "activation", namePrefix: "s", operation: new Operation(TANH_F_CODE, TANH_DFDX_CODE), inputPorts: [new Port("x", true)], @@ -262,7 +262,7 @@ const GraphContainer: FunctionComponent = ({ { id: "relu", text: "ReLU", - type: "SIMPLE", + type: "activation", namePrefix: "r", operation: new Operation(RELU_F_CODE, RELU_DFDX_CODE), inputPorts: [new Port("x", true)], @@ -271,7 +271,7 @@ const GraphContainer: FunctionComponent = ({ { id: "squared_error", text: "Squared Error", - type: "SIMPLE", + type: "loss", namePrefix: "s", operation: new Operation(SQUARED_ERROR_F_CODE, SQUARED_ERROR_DFDX_CODE), inputPorts: [new Port("y_t", false), new Port("y_e", false)], @@ -280,7 +280,7 @@ const GraphContainer: FunctionComponent = ({ { id: "binary_cross_entropy", text: "Binary Cross-Entropy", - type: "SIMPLE", + type: "loss", namePrefix: "b", operation: new Operation( BINARY_CROSS_ENTROPY_F_CODE, @@ -417,7 +417,7 @@ const GraphContainer: FunctionComponent = ({ const newFeatureOperation: FeatureOperation = { id, text: `Operation ${nextOperationId}`, - type: "CUSTOM", + type: "custom", namePrefix: "f", operation: new Operation(TEMPLATE_F_CODE, TEMPLATE_DFDX_CODE), inputPorts: [], diff --git a/interactive-computational-graph/src/features/CoreGraphAdapter.test.ts b/interactive-computational-graph/src/features/CoreGraphAdapter.test.ts index a006567..b22f4b8 100644 --- a/interactive-computational-graph/src/features/CoreGraphAdapter.test.ts +++ b/interactive-computational-graph/src/features/CoreGraphAdapter.test.ts @@ -567,7 +567,7 @@ describe("behavior", () => { const featureOperation: FeatureOperation = { id: "add", text: "Add", - type: "SIMPLE", + type: "basic", namePrefix: "a", operation: new Operation(ADD_F_CODE, ADD_DFDX_CODE), inputPorts: [new Port("a", false), new Port("b", false)], diff --git a/interactive-computational-graph/src/features/NodeNameBuilder.test.ts b/interactive-computational-graph/src/features/NodeNameBuilder.test.ts index 61cb1c8..63d7d90 100644 --- a/interactive-computational-graph/src/features/NodeNameBuilder.test.ts +++ b/interactive-computational-graph/src/features/NodeNameBuilder.test.ts @@ -43,7 +43,7 @@ const getAddOperation = (): FeatureOperation => { return { id: "add", text: "Add", - type: "SIMPLE", + type: "basic", namePrefix: "a", operation: new Operation(ADD_F_CODE, ADD_DFDX_CODE), inputPorts: [new Port("a", false), new Port("b", false)], diff --git a/interactive-computational-graph/src/features/OperationType.ts b/interactive-computational-graph/src/features/OperationType.ts index 27ed817..6828804 100644 --- a/interactive-computational-graph/src/features/OperationType.ts +++ b/interactive-computational-graph/src/features/OperationType.ts @@ -1,3 +1,9 @@ -type OperationType = "SIMPLE" | "CUSTOM"; +type OperationType = + | "basic" + | "aggregate" + | "trigonometric" + | "activation" + | "loss" + | "custom"; export default OperationType; From 5311301e4a6e2d2afeede630b0911d2b577fd555 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Wed, 27 Dec 2023 00:41:14 +0800 Subject: [PATCH 17/18] render different operation types --- .../src/components/AddNodesPanel.test.tsx | 52 +++++++++- .../src/components/AddNodesPanel.tsx | 94 ++++++++++++------- 2 files changed, 111 insertions(+), 35 deletions(-) diff --git a/interactive-computational-graph/src/components/AddNodesPanel.test.tsx b/interactive-computational-graph/src/components/AddNodesPanel.test.tsx index e30389f..e87b0a5 100644 --- a/interactive-computational-graph/src/components/AddNodesPanel.test.tsx +++ b/interactive-computational-graph/src/components/AddNodesPanel.test.tsx @@ -1,13 +1,45 @@ import { fireEvent, render, screen } from "@testing-library/react"; import Operation from "../core/Operation"; import Port from "../core/Port"; -import { ADD_DFDX_CODE, ADD_F_CODE } from "../features/BuiltInCode"; +import { + ADD_DFDX_CODE, + ADD_F_CODE, + SIN_DFDX_CODE, + SIN_F_CODE, + SUM_DFDX_CODE, + SUM_F_CODE, +} from "../features/BuiltInCode"; import type FeatureNodeType from "../features/FeatureNodeType"; import type FeatureOperation from "../features/FeatureOperation"; import AddNodesPanel from "./AddNodesPanel"; jest.mock("./EditOperationDialogMenu"); +test("should render operation types as headers", () => { + const featureOperations = getFeatureOperations(); + const handleAddNode = jest.fn(); + const handleAddOperation = jest.fn(); + const handleEditOperation = jest.fn(); + const handleDeleteOperation = jest.fn(); + render( + , + ); + + const basicHeader = screen.getByText("Basic"); + const aggregateHeader = screen.getByText("Aggregate"); + const trigonometricHeader = screen.getByText("Trigonometric"); + expect(basicHeader).toBeInTheDocument(); + expect(aggregateHeader).toBeInTheDocument(); + expect(trigonometricHeader).toBeInTheDocument(); +}); + test("should trigger event when clicking item", () => { const featureOperations = getFeatureOperations(); const handleAddNode = jest.fn(); @@ -149,5 +181,23 @@ const getFeatureOperations = (): FeatureOperation[] => { inputPorts: [new Port("a", false), new Port("b", false)], helpText: "Add two numbers $ a + b $", }, + { + id: "sum", + text: "Sum", + type: "aggregate", + namePrefix: "s", + operation: new Operation(SUM_F_CODE, SUM_DFDX_CODE), + inputPorts: [new Port("x_i", true)], + helpText: "Add all inputs $ \\sum_i x_{i} $", + }, + { + id: "sin", + text: "Sin", + type: "trigonometric", + namePrefix: "s", + operation: new Operation(SIN_F_CODE, SIN_DFDX_CODE), + inputPorts: [new Port("x", true)], + helpText: "Calculate $ \\sin(x) $", + }, ]; }; diff --git a/interactive-computational-graph/src/components/AddNodesPanel.tsx b/interactive-computational-graph/src/components/AddNodesPanel.tsx index 6806e9a..7ed9c1f 100644 --- a/interactive-computational-graph/src/components/AddNodesPanel.tsx +++ b/interactive-computational-graph/src/components/AddNodesPanel.tsx @@ -10,7 +10,7 @@ import { Stack, Typography, } from "@mui/material"; -import { useState, type FunctionComponent, useCallback } from "react"; +import { useState, type FunctionComponent, useCallback, useMemo } from "react"; import type FeatureNodeType from "../features/FeatureNodeType"; import type FeatureOperation from "../features/FeatureOperation"; import DraggableItem from "./DraggableItem"; @@ -33,17 +33,24 @@ const AddNodesPanel: FunctionComponent = ({ onEditOperation, onDeleteOperation, }) => { - const simpleOperations = featureOperations.filter( - (operation) => operation.type === "basic", + const builtInOperationTypes: string[] = useMemo( + () => ["basic", "aggregate", "trigonometric", "activation", "loss"], + [], ); - const customOperations = featureOperations.filter( - (operation) => operation.type === "custom", + + const customOperations = useMemo( + () => featureOperations.filter((operation) => operation.type === "custom"), + [featureOperations], ); const [isEditDialogOpen, setEditDialogOpen] = useState(false); const [editingOperation, setEditingOperation] = useState(null); + const capitalizeFirstLetter = useCallback((word: string): string => { + return word[0].toUpperCase() + word.slice(1); + }, []); + const handleOpenEditDialog = useCallback( (featureOperation: FeatureOperation) => { setEditingOperation(featureOperation); @@ -114,35 +121,54 @@ const AddNodesPanel: FunctionComponent = ({ - {/* Simple operations */} - - } - aria-controls="simple-content" - id="simple-header" - > - Simple - - - - {simpleOperations.map((operation) => ( - { - handleOpenEditDialog(operation); - }} - /> - ))} - - - + {/* Built-in operations */} + {builtInOperationTypes.map((builtInOperationType) => { + const filteredOperations = featureOperations.filter( + (operation) => operation.type === builtInOperationType, + ); + + return ( + + } + aria-controls={`${builtInOperationType}-content`} + id={`${builtInOperationType}-header`} + > + + {capitalizeFirstLetter(builtInOperationType)} + + + + + {filteredOperations.map((operation) => ( + { + handleOpenEditDialog(operation); + }} + /> + ))} + + + + ); + })} {/* Custom operations */} From c7ef42810cd8ed6da0ccbf62978cfd8a8b8c45f0 Mon Sep 17 00:00:00 2001 From: Shawn Chang Date: Wed, 27 Dec 2023 00:43:33 +0800 Subject: [PATCH 18/18] start from 1 for text --- .../src/components/GraphContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interactive-computational-graph/src/components/GraphContainer.tsx b/interactive-computational-graph/src/components/GraphContainer.tsx index 7392c69..23e7d75 100644 --- a/interactive-computational-graph/src/components/GraphContainer.tsx +++ b/interactive-computational-graph/src/components/GraphContainer.tsx @@ -416,7 +416,7 @@ const GraphContainer: FunctionComponent = ({ const newFeatureOperation: FeatureOperation = { id, - text: `Operation ${nextOperationId}`, + text: `Operation ${nextOperationId + 1}`, type: "custom", namePrefix: "f", operation: new Operation(TEMPLATE_F_CODE, TEMPLATE_DFDX_CODE),