Skip to content

Commit

Permalink
Copy new comments and structure to Nucleara
Browse files Browse the repository at this point in the history
  • Loading branch information
sjd210 committed Nov 12, 2024
1 parent f475abc commit d51cede
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 49 deletions.
23 changes: 7 additions & 16 deletions src/models/Chemistry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AddFrac, CheckerResponse, ChemicalSymbol, ChemistryOptions, Fraction, listComparison, mergeResponses, MultFrac } from './common'
import { AddFrac, CheckerResponse, ChemicalSymbol, ChemistryOptions, Fraction, listComparison, mergeResponses, MultFrac, removeAggregates } from './common'
import isEqual from "lodash/isEqual";

export type Type = 'error'|'element'|'bracket'|'compound'|'ion'|'term'|'expr'|'statement'|'electron';
Expand Down Expand Up @@ -123,6 +123,7 @@ const STARTING_RESPONSE: (options?: ChemistryOptions, coefficientScalingValue?:
sameCoefficient: true,
sameElements: true,
sameState: true,
sameCharge: true,
sameArrow: true,
sameBrackets: true,
isChargeBalanced: true,
Expand Down Expand Up @@ -300,16 +301,6 @@ function typesMatch(compound1: (Element | Bracket)[], compound2: (Element | Brac
return numElementsDifferent === 0 && numMoleculesDifferent === 0;
}

function removeAggregates(response: CheckerResponse): CheckerResponse {
delete response.bracketChargeCount;
delete response.termChargeCount;
delete response.chargeCount;
delete response.bracketAtomCount;
delete response.termAtomCount;
delete response.atomCount;
return response;
}

function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerResponse): CheckerResponse {
if (isElement(test) && isElement(target)) {
// If permutations are disallowed or if the element is its own (uncompounded) term, we can directly compare the elements
Expand Down Expand Up @@ -434,7 +425,8 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon

const comparator = (test: [Molecule, number], target: [Molecule, number], response: CheckerResponse): CheckerResponse => {
const newResponse = checkNodesEqual(test[0], target[0], response);
newResponse.isEqual = newResponse.isEqual && test[1] === target[1];
newResponse.sameCharge = newResponse.sameCharge && test[1] === target[1];
newResponse.isEqual = newResponse.isEqual && (newResponse.sameCharge === true);

if (!test[0].bracketed) {
// If not bracketed, add the charge directly to the chargeCount of the term
Expand Down Expand Up @@ -503,7 +495,7 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon
newResponse.sameState = newResponse.sameState && test.state === target.state;
// TODO: add a new property stating the hydrate was wrong?

// Add the term's atomCount (* coefficient) to the overall statement atomCount
// Add the term's atomCount (* coefficient) to the overall expression atomCount
if (newResponse.termAtomCount) {
for (const [key, value] of Object.entries(newResponse.termAtomCount)) {
if (newResponse.atomCount) {
Expand All @@ -517,7 +509,7 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon
newResponse.termAtomCount = {} as Record<ChemicalSymbol, number | undefined>;
}

// Add the term's chargeCount (* coefficient) to the overall statement chargeCount
// Add the term's chargeCount (* coefficient) to the overall expression chargeCount
if (newResponse.termChargeCount) {
if (newResponse.chargeCount) {
newResponse.chargeCount = AddFrac(newResponse.chargeCount, MultFrac({numerator: newResponse.termChargeCount ?? 0, denominator: 1}, test.coeff));
Expand All @@ -532,11 +524,10 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon
}
else if (isExpression(test) && isExpression(target)) {
if (test.terms && target.terms) {
// If the number of terms in the expression is wrong, there is no way they can be equivalent and we can fail early
// If the number of terms in the expression is wrong, there is no way they can be equivalent
if (test.terms.length !== target.terms.length) {
response.sameElements = false;
response.isEqual = false;
return response;
}

// Check all permutations of the expression until we get a match
Expand Down
75 changes: 43 additions & 32 deletions src/models/Nuclear.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CheckerResponse, ChemicalSymbol, chemicalSymbol, ChemistryOptions, listComparison } from './common'
import { CheckerResponse, ChemicalSymbol, chemicalSymbol, ChemistryOptions, listComparison, mergeResponses, removeAggregates } from './common'

export type ParticleString = 'alphaparticle'|'betaparticle'|'gammaray'|'neutrino'|'antineutrino'|'electron'|'positron'|'neutron'|'proton';
export type Type = 'error'|'particle'|'isotope'|'term'|'expr'|'statement';
Expand Down Expand Up @@ -78,7 +78,7 @@ function augmentNode<T extends ASTNode>(node: T): T {
if(isExpression(node)) {
let terms: Term[] = [];

// Recursively augmentten
// Recursively augment
if (node.rest) {
const augmentedTerms: Expression | Term = augmentNode(node.rest);

Expand Down Expand Up @@ -187,67 +187,76 @@ const STARTING_RESPONSE: (options?: ChemistryOptions) => CheckerResponse = (opti

function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerResponse): CheckerResponse {
if (isParticle(test) && isParticle(target)) {
// Answers can be entered without a mass or atomic number. However, this is always wrong so we throw an error
if (test.mass === null || test.atomic === null) {
response.containsError = true;
response.error = "Check that all atoms have a mass and atomic number!"
response.isEqual = false;
return response;
}

response.validAtomicNumber = (response.validAtomicNumber ?? true) && isValidAtomicNumber(test);
response.sameElements = response.sameElements && checkParticlesEqual(test, target);
response.isEqual = response.isEqual && response.sameElements && response.validAtomicNumber;

if (response.nucleonCount) {
response.nucleonCount = [
response.nucleonCount[0] + test.atomic,
response.nucleonCount[1] + test.mass
// Add the term's nucleon counts to the term's nucleon count
if (response.termNucleonCount) {
response.termNucleonCount = [
response.termNucleonCount[0] + test.atomic,
response.termNucleonCount[1] + test.mass
];
} else {
response.nucleonCount = [test.atomic, test.mass];
response.termNucleonCount = [test.atomic, test.mass];
}

return response;
} else if (isIsotope(test) && isIsotope(target)) {
// Answers can be entered without a mass or atomic number. However, this is always wrong so we throw an error
if (test.mass === null || test.atomic === null) {
response.containsError = true;
response.error = "Check that all atoms have a mass and atomic number!"
response.isEqual = false;
return response;
}

response.validAtomicNumber = (response.validAtomicNumber ?? true) && isValidAtomicNumber(test) && test.mass === target.mass && test.atomic === target.atomic;
response.sameElements = response.sameElements && test.element === target.element;
response.isEqual = response.isEqual && response.sameElements && response.validAtomicNumber;

if (response.nucleonCount) {
response.nucleonCount = [
response.nucleonCount[0] + test.atomic,
response.nucleonCount[1] + test.mass
// Add the term's nucleon counts to the term's nucleon count
if (response.termNucleonCount) {
response.termNucleonCount = [
response.termNucleonCount[0] + test.atomic,
response.termNucleonCount[1] + test.mass
];
} else {
response.nucleonCount = [test.atomic, test.mass];
response.termNucleonCount = [test.atomic, test.mass];
}

return response;
} else if (isTerm(test) && isTerm(target)) {
// If we have a particle-atom mismatch, the elements are not equivalent
if (test.isParticle !== target.isParticle) {
response.sameElements = false;
response.isEqual = false;
}

const newResponse = checkNodesEqual(test.value, target.value, response);

// Set a flag for sameCoefficient here, but apply the isEqual check at the end (because of listComparison)
newResponse.sameCoefficient = test.coeff === target.coeff;

// Add the term's nucleon counts to the overall expression nucleon count
if (newResponse.nucleonCount) {
newResponse.nucleonCount = [
newResponse.nucleonCount[0] * test.coeff,
newResponse.nucleonCount[1] * test.coeff,
newResponse.nucleonCount[0] + (newResponse.termNucleonCount ?? [0,0])[0] * test.coeff,
newResponse.nucleonCount[1] + (newResponse.termNucleonCount ?? [0,0])[1] * test.coeff,
]
}

return newResponse;
} else if (isExpression(test) && isExpression(target)) {
if (test.terms && target.terms) {
// If the number of terms in the expression is wrong, there is no way they can be equivalent
if (test.terms.length !== target.terms.length) {
response.sameElements = false;
response.isEqual = false;
Expand All @@ -261,22 +270,21 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon
return response;
}
} else if (isStatement(test) && isStatement(target)) {
// Determine responses for both the left and right side of the statement
const leftResponse = checkNodesEqual(test.left, target.left, response);
const leftNucleonCount = leftResponse.nucleonCount;
leftResponse.nucleonCount = [0, 0];

const finalResponse = checkNodesEqual(test.right, target.right, leftResponse);

finalResponse.isBalanced = leftNucleonCount && finalResponse.nucleonCount ?
leftNucleonCount[0] === finalResponse.nucleonCount[0] &&
leftNucleonCount[1] === finalResponse.nucleonCount[1] :
false;
finalResponse.balancedAtom = leftNucleonCount && finalResponse.nucleonCount ?
leftNucleonCount[0] === finalResponse.nucleonCount[0] :
false;
finalResponse.balancedMass = leftNucleonCount && finalResponse.nucleonCount ?
leftNucleonCount[1] === finalResponse.nucleonCount[1] :
false;
let rightResponse = STARTING_RESPONSE(leftResponse.options);
rightResponse = checkNodesEqual(test.right, target.right, leftResponse);

// Merge the responses so that the final response contains all the information
const finalResponse = mergeResponses(leftResponse, rightResponse);

// Nuclear question balance is determined by atom/mass count equality
finalResponse.balancedAtom = leftResponse.nucleonCount && rightResponse.nucleonCount ?
leftResponse.nucleonCount[0] === rightResponse.nucleonCount[0] : false;
finalResponse.balancedMass = leftResponse.nucleonCount && rightResponse.nucleonCount ?
leftResponse.nucleonCount[1] === rightResponse.nucleonCount[1] : false;
finalResponse.isBalanced = leftResponse.nucleonCount && rightResponse.nucleonCount ?
finalResponse.balancedAtom && finalResponse.balancedMass : false;
finalResponse.isEqual = finalResponse.isEqual && finalResponse.isBalanced;

return finalResponse
Expand All @@ -286,6 +294,8 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon
response.isEqual = false;
// We must still check the children of the node to get a complete nucleon count
if (test.type == "error") {
response.containsError = true;
response.error = "Error type encountered during checking process.";
return response;
} else {
return checkNodesEqual(test, test, response);
Expand Down Expand Up @@ -322,9 +332,10 @@ export function check(test: NuclearAST, target: NuclearAST): CheckerResponse {
return response;
}

const newResponse = checkNodesEqual(test.result, target.result, response);
let newResponse = checkNodesEqual(test.result, target.result, response);
// We set flags for this properties in checkNodesEqual, but we only apply the isEqual check here due to listComparison
newResponse.isEqual = newResponse.isEqual && newResponse.sameCoefficient;
delete newResponse.nucleonCount;
newResponse = removeAggregates(newResponse);
return newResponse;
}

Expand Down
15 changes: 14 additions & 1 deletion src/models/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface CheckerResponse {
sameElements: boolean;
// properties dependent on type
sameState?: boolean;
sameCharge?: boolean;
sameArrow?: boolean;
sameBrackets?: boolean;
validAtomicNumber?: boolean;
Expand All @@ -41,11 +42,11 @@ export interface CheckerResponse {
termChargeCount?: number;
bracketChargeCount?: number[];
chargeCount?: Fraction;
termNucleonCount?: [number, number];
nucleonCount?: [number, number];
options?: ChemistryOptions;
}


export function mergeResponses(response1: CheckerResponse, response2: CheckerResponse): CheckerResponse {
const newResponse = structuredClone(response1);

Expand All @@ -64,6 +65,18 @@ export function mergeResponses(response1: CheckerResponse, response2: CheckerRes
return newResponse;
}

export function removeAggregates(response: CheckerResponse): CheckerResponse {
delete response.bracketChargeCount;
delete response.termChargeCount;
delete response.chargeCount;
delete response.bracketAtomCount;
delete response.termAtomCount;
delete response.atomCount;
delete response.termNucleonCount;
delete response.nucleonCount;
return response;
}

export function listComparison<T>(
testList: T[],
targetList: T[],
Expand Down

0 comments on commit d51cede

Please sign in to comment.