Skip to content

Commit

Permalink
Merge pull request #60 from HybriD3-database/more_bracket_support_sto…
Browse files Browse the repository at this point in the history
…ichiometry

Updated additional bracket parsing for stoichiometry
  • Loading branch information
uthpalaherath authored Nov 8, 2024
2 parents 3dfbdb1 + 04f5e22 commit 02d98bc
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 38 deletions.
22 changes: 18 additions & 4 deletions materials/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from mainproject.settings import MATD3_NAME

from .models import System, System_Stoichiometry, Stoichiometry_Elements
from fractions import Fraction
from decimal import Decimal, ROUND_HALF_UP
import re

admin.site.site_header = mark_safe(f"{MATD3_NAME} database")
Expand Down Expand Up @@ -71,17 +73,29 @@ class SystemStoichiometryInline(nested_admin.NestedTabularInline):
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
Stoichiometry_Elements.objects.filter(system_stoichiometry=obj).delete()
# Updated regex pattern
element_pattern = r"([A-Z][a-z]*):(\d+(?:\.\d+)?)"
element_pattern = r"([A-Z][a-z]*):(\d+(?:\.\d+)?|\d+(?:\.\d+)?/\d+(?:\.\d+)?)"
elements = re.findall(element_pattern, obj.stoichiometry)
for element, count in elements:
for element, count_str in elements:
if "/" in count_str:
numerator, denominator = count_str.split("/")
count = Decimal(numerator) / Decimal(denominator)
else:
count = Decimal(count_str)
# Format counts
if count == count.to_integral():
count_formatted = str(count.to_integral())
else:
count_formatted = str(
count.quantize(Decimal("0.001"), rounding=ROUND_HALF_UP).normalize()
)
Stoichiometry_Elements.objects.create(
system_stoichiometry=obj,
element=element,
string_value=str(count),
string_value=count_formatted,
float_value=float(count),
)


# SystemAdmin to include the Stoichiometry inline
class SystemAdmin(nested_admin.NestedModelAdmin):
list_display = ("id", "compound_name")
Expand Down
109 changes: 87 additions & 22 deletions materials/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,60 +6,125 @@

from django.db.models.signals import post_save
from .models import System, System_Stoichiometry, Stoichiometry_Elements
from fractions import Fraction
from decimal import Decimal, ROUND_HALF_UP
import re


def parse_formula(formula):
tokens = re.findall(r"([A-Z][a-z]?|\(|\)|\d+)", formula)
# Updated regex to include decimal numbers and fractions inside brackets
token_pattern = r"([A-Z][a-z]?|\d+(\.\d+)?|\([\d\.]+(\/[\d\.]+)?\)|\{[\d\.]+(\/[\d\.]+)?\}|\[[\d\.]+(\/[\d\.]+)?\]|[\(\)\[\]\{\}])"
tokens = re.findall(token_pattern, formula)
# Flatten the tokens list
tokens = [token[0] for token in tokens]
if not tokens:
return {}
stack = [{}]
bracket_stack = []
i = 0
while i < len(tokens):
token = tokens[i]
if token == "(":
if token in "([{":
stack.append({})
bracket_stack.append(token)
i += 1
elif token == ")":
elif token in ")]}":
if not bracket_stack:
raise ValueError("Unmatched closing bracket in formula")
opening = bracket_stack.pop()
expected_closing = {"(": ")", "[": "]", "{": "}"}[opening]
if token != expected_closing:
raise ValueError("Mismatched brackets in formula")
top = stack.pop()
i += 1
# Check if there is a multiplier
if i < len(tokens) and tokens[i].isdigit():
multiplier = int(tokens[i])
multiplier = Decimal("1")
if i < len(tokens) and (
re.match(r"^\d+(\.\d+)?$", tokens[i])
or re.match(r"^[\(\[\{][\d\.]+(\/[\d\.]+)?[\)\]\}]$", tokens[i])
):
if re.match(r"^[\(\[\{][\d\.]+\/[\d\.]+[\)\]\}]$", tokens[i]):
# Handle fractions inside brackets
fraction = tokens[i][1:-1].split("/")
numerator = Decimal(fraction[0])
denominator = Decimal(fraction[1])
multiplier = numerator / denominator
elif re.match(r"^[\(\[\{][\d\.]+[\)\]\}]$", tokens[i]):
# Handle decimal numbers inside brackets
multiplier = Decimal(tokens[i][1:-1])
else:
# Handle plain numbers
multiplier = Decimal(tokens[i])
i += 1
else:
multiplier = 1
for element, count in top.items():
stack[-1][element] = stack[-1].get(element, 0) + count * multiplier
elif re.match(r"[A-Z][a-z]?$", token):
stack[-1][element] = (
stack[-1].get(element, Decimal("0")) + count * multiplier
)
elif re.match(r"^[A-Z][a-z]?$", token):
element = token
i += 1
if i < len(tokens) and tokens[i].isdigit():
count = int(tokens[i])
count = Decimal("1")
if i < len(tokens) and (
re.match(r"^\d+(\.\d+)?$", tokens[i])
or re.match(r"^[\(\[\{][\d\.]+(\/[\d\.]+)?[\)\]\}]$", tokens[i])
):
if re.match(r"^[\(\[\{][\d\.]+\/[\d\.]+[\)\]\}]$", tokens[i]):
# Handle fractions inside brackets
fraction = tokens[i][1:-1].split("/")
numerator = Decimal(fraction[0])
denominator = Decimal(fraction[1])
count = numerator / denominator
elif re.match(r"^[\(\[\{][\d\.]+[\)\]\}]$", tokens[i]):
# Handle decimal numbers inside brackets
count = Decimal(tokens[i][1:-1])
else:
# Handle plain numbers
count = Decimal(tokens[i])
i += 1
else:
count = 1
stack[-1][element] = stack[-1].get(element, 0) + count
stack[-1][element] = stack[-1].get(element, Decimal("0")) + count
else:
i += 1
if bracket_stack:
raise ValueError("Unmatched opening bracket in formula")
return stack[0]


@receiver(post_save, sender=System)
def create_stoichiometry_entries(sender, instance, created, **kwargs):
if created:
formula = instance.formula
# Updated regex pattern to match decimals
element_pattern = r"([A-Z][a-z]*)(\d*(?:\.\d+)?)"
elements = re.findall(element_pattern, formula)
stoichiometry_str = ",".join([f"{el}:{count or 1}" for el, count in elements])
try:
element_counts = parse_formula(formula)
except Exception as e:
# Handle parsing errors if necessary
return

# Create stoichiometry string with formatted counts
stoichiometry_list = []
for el, count in element_counts.items():
if count == count.to_integral():
count_str = str(count.to_integral())
else:
count_str = str(
count.quantize(Decimal("0.001"), rounding=ROUND_HALF_UP).normalize()
)
stoichiometry_list.append(f"{el}:{count_str}")
stoichiometry_str = ",".join(stoichiometry_list)

stoichiometry = System_Stoichiometry.objects.create(
system=instance, stoichiometry=stoichiometry_str
)
for el, count in elements:
for el, count in element_counts.items():
if count == count.to_integral():
count_str = str(count.to_integral())
else:
count_str = str(
count.quantize(Decimal("0.001"), rounding=ROUND_HALF_UP).normalize()
)
Stoichiometry_Elements.objects.create(
system_stoichiometry=stoichiometry,
element=el,
string_value=str(count or "1"),
float_value=float(count) if count else 1.0,
string_value=count_str,
float_value=float(count),
)


Expand Down
73 changes: 61 additions & 12 deletions materials/templates/materials/add_data.html
Original file line number Diff line number Diff line change
Expand Up @@ -675,24 +675,48 @@ <h5 class="modal-title" id="stoichiometryModalLabel">Is this the correct stoichi
var elementsData = {{ elements_json|safe }};
</script>
<script>
function parseFormula(formula) {
// Updated regex to match decimal numbers
const tokens = formula.match(/([A-Z][a-z]?|\(|\)|\d+(\.\d+)?)/g);

function parseFormula(formula) {
const tokens = formula.match(/([A-Z][a-z]?|\d+(\.\d+)?|\([\d\.]+(\/[\d\.]+)?\)|\{[\d\.]+(\/[\d\.]+)?\}|\[[\d\.]+(\/[\d\.]+)?\]|[\(\)\[\]\{\}])/g);
if (!tokens) return {};
const stack = [{}];
const bracketStack = [];
let i = 0;
while (i < tokens.length) {
const token = tokens[i];
if (token === '(') {
if ('([{'.includes(token)) {
stack.push({});
bracketStack.push(token);
i++;
} else if (token === ')') {
} else if (')]}'.includes(token)) {
if (!bracketStack.length) {
console.error('Unmatched closing bracket in formula');
return {};
}
const opening = bracketStack.pop();
const expectedClosing = { '(': ')', '[': ']', '{': '}' }[opening];
if (token !== expectedClosing) {
console.error('Mismatched brackets in formula');
return {};
}
const top = stack.pop();
i++;
let multiplier = 1;
// Updated to parse decimal multipliers
if (i < tokens.length && /^\d+(\.\d+)?$/.test(tokens[i])) {
multiplier = parseFloat(tokens[i]);
if (i < tokens.length && (
/^\d+(\.\d+)?$/.test(tokens[i]) ||
/^[\(\[\{][\d\.]+(\/[\d\.]+)?[\)\]\}]$/.test(tokens[i])
)) {
if (/^[\(\[\{][\d\.]+\/[\d\.]+[\)\]\}]$/.test(tokens[i])) {
// Handle fractions inside brackets
const fraction = tokens[i].slice(1, -1).split('/');
multiplier = parseFloat(fraction[0]) / parseFloat(fraction[1]);
} else if (/^[\(\[\{][\d\.]+[\)\]\}]$/.test(tokens[i])) {
// Handle decimal numbers inside brackets
multiplier = parseFloat(tokens[i].slice(1, -1));
} else {
// Handle plain numbers
multiplier = parseFloat(tokens[i]);
}
i++;
}
for (const [element, count] of Object.entries(top)) {
Expand All @@ -703,9 +727,21 @@ <h5 class="modal-title" id="stoichiometryModalLabel">Is this the correct stoichi
const element = token;
i++;
let count = 1;
// Updated to parse decimal counts
if (i < tokens.length && /^\d+(\.\d+)?$/.test(tokens[i])) {
count = parseFloat(tokens[i]);
if (i < tokens.length && (
/^\d+(\.\d+)?$/.test(tokens[i]) ||
/^[\(\[\{][\d\.]+(\/[\d\.]+)?[\)\]\}]$/.test(tokens[i])
)) {
if (/^[\(\[\{][\d\.]+\/[\d\.]+[\)\]\}]$/.test(tokens[i])) {
// Handle fractions inside brackets
const fraction = tokens[i].slice(1, -1).split('/');
count = parseFloat(fraction[0]) / parseFloat(fraction[1]);
} else if (/^[\(\[\{][\d\.]+[\)\]\}]$/.test(tokens[i])) {
// Handle decimal numbers inside brackets
count = parseFloat(tokens[i].slice(1, -1));
} else {
// Handle plain numbers
count = parseFloat(tokens[i]);
}
i++;
}
stack[stack.length - 1][element] =
Expand All @@ -714,6 +750,10 @@ <h5 class="modal-title" id="stoichiometryModalLabel">Is this the correct stoichi
i++;
}
}
if (bracketStack.length > 0) {
console.error('Unmatched opening bracket in formula');
return {};
}
return stack[0];
}

Expand All @@ -725,15 +765,24 @@ <h5 class="modal-title" id="stoichiometryModalLabel">Is this the correct stoichi
}
const formula = formulaInput.value;
const elementCounts = parseFormula(formula);

let stoichiometryString = '';
for (const element in elementCounts) {
stoichiometryString += `${element}:${elementCounts[element]}, `;
let count = elementCounts[element];
if (Number.isInteger(count)) {
count = count.toString();
} else {
count = parseFloat(count.toFixed(3)).toString();
}
stoichiometryString += `${element}:${count}, `;
}
stoichiometryString = stoichiometryString.slice(0, -2);
document.getElementById('stoichiometryOutput').innerText = `${stoichiometryString}`;
$('#stoichiometryModal').modal('show');
}



function handleStoichiometryConfirmation(answer){
const stoichiometryTextBox = document.getElementById('system-stoichiometry-input');
const newStoichiometrySection = document.getElementById('newStoichiometrySection');
Expand Down

0 comments on commit 02d98bc

Please sign in to comment.