Biofuel-Optimiser / templates /generative_mixture.html
carrotcake3's picture
Update templates/generative_mixture.html
07b2e07 verified
Raw
History Blame Contribute Delete
13 kB
{% extends "base.html" %}
{% block content %}
<div class="container py-4">
<!-- Header -->
<div class="mb-4">
<h2 class="fw-bold mb-2">Blend Component Generator</h2>
<p class="page-intro-text mb-0">
Generate candidate additive molecules for a selected base fuel and evaluate the resulting blend
using mixture CN, mixture YSI, and supporting fuel-property constraints.
</p>
</div>
<!-- Alerts -->
{% if error %}
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle me-2"></i>{{ error }}
</div>
{% endif %}
{% if info %}
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>{{ info }}
</div>
{% endif %}
<!-- Configuration Form -->
<div class="card card-metric p-4 mb-4">
<h5 class="fw-bold mb-3">Mixture Generator Configuration</h5>
<form method="POST" id="mixture-gen-form">
<div class="row g-3 align-items-end">
<div class="col-md-4">
<label class="form-label fw-semibold">Optimisation Mode</label>
<select name="mode" id="mixtureGenMode" class="form-select" onchange="toggleMixtureTarget()">
<option value="target" {% if form_values.mode == "target" %}selected{% endif %}>
Target a specific mixture CN
</option>
<option value="maximize" {% if form_values.mode == "maximize" %}selected{% endif %}>
Maximise mixture CN
</option>
</select>
</div>
<div class="col-md-3" id="targetDcnBox">
<label class="form-label fw-semibold">Target Mixture CN</label>
<input
type="number"
step="0.01"
name="target_dcn"
class="form-control"
value="{{ form_values.target_dcn }}"
>
</div>
<div class="col-md-3">
<label class="form-label fw-semibold">Additive Mole Fraction</label>
<input
type="number"
step="0.01"
min="0.01"
max="0.99"
name="additive_fraction"
class="form-control"
value="{{ form_values.additive_fraction }}"
>
<div class="form-text">
Example: 0.15 means 15% additive and 85% base fuel.
</div>
</div>
<div class="col-md-2">
<button id="mixture-generate-btn" type="submit" class="btn btn-primary w-100">
Run Generator
</button>
</div>
</div>
<hr class="my-4">
<div class="row g-3 align-items-start">
<div class="col-md-4">
<label class="form-label fw-semibold">Base Fuel</label>
<select name="base_fuel_type" id="baseFuelType" class="form-select" onchange="toggleCustomBaseFuel()">
<option value="fossil_diesel" {% if form_values.base_fuel_type == "fossil_diesel" %}selected{% endif %}>
Fossil diesel
</option>
<option value="biodiesel" {% if form_values.base_fuel_type == "biodiesel" %}selected{% endif %}>
Biodiesel
</option>
<option value="custom" {% if form_values.base_fuel_type == "custom" %}selected{% endif %}>
Custom base fuel
</option>
</select>
<div class="form-text">
Fossil diesel and biodiesel use predefined representative compositions.
</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">YSI Objective</label>
<div class="form-check mt-2">
<input
class="form-check-input"
type="checkbox"
name="minimize_ysi"
id="minimizeMixtureYsi"
{% if form_values.minimize_ysi %}checked{% endif %}
>
<label class="form-check-label" for="minimizeMixtureYsi">
Minimise mixture YSI
</label>
</div>
<div class="form-text">
Enables Pareto optimisation for mixture DCN and smoke tendency.
</div>
</div>
</div>
<!-- Custom Base Fuel -->
<div id="customBaseFuelBox" class="border rounded-3 p-3 mt-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h6 class="fw-bold mb-1">Custom Base Fuel Composition</h6>
<p class="text-muted small mb-0">
Enter at least two base fuel components. Fractions will be normalised automatically.
</p>
</div>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="addCustomBaseRow()">
Add Component
</button>
</div>
<div id="customBaseRows">
<div class="custom-base-row row g-3 mb-2">
<div class="col-md-8">
<label class="form-label small fw-semibold">SMILES</label>
<input type="text" name="custom_smiles[]" class="form-control" placeholder="e.g. CCCCCCCCCCCCCCCC">
</div>
<div class="col-md-4">
<label class="form-label small fw-semibold">Mole Fraction</label>
<input type="number" step="0.01" min="0" name="custom_fraction[]" class="form-control" placeholder="0.5">
</div>
</div>
<div class="custom-base-row row g-3 mb-2">
<div class="col-md-8">
<input type="text" name="custom_smiles[]" class="form-control" placeholder="e.g. CCCCCCCCCCCC">
</div>
<div class="col-md-4">
<input type="number" step="0.01" min="0" name="custom_fraction[]" class="form-control" placeholder="0.5">
</div>
</div>
</div>
<button type="button" class="btn btn-outline-danger btn-sm mt-2" onclick="removeCustomBaseRow()">
Remove Last Component
</button>
</div>
<div id="mixture-loading" class="mt-3" style="display:none;">
<div class="spinner-border text-primary" role="status"></div>
<span class="ms-2">Generating blend candidates… this may take a few minutes.</span>
</div>
</form>
</div>
<!-- Run Summary -->
{% if run_info %}
<div class="card card-metric p-4 mb-4">
<h5 class="fw-bold mb-3">Generation Summary</h5>
<div class="row g-3">
<div class="col-md-3">
<div class="border rounded-3 p-3 bg-white">
<p class="text-muted small mb-1">Mode</p>
<h6 class="fw-bold mb-0">{{ run_info.mode }}</h6>
</div>
</div>
<div class="col-md-2">
<div class="border rounded-3 p-3 bg-white">
<p class="text-muted small mb-1">Target CN</p>
<h6 class="fw-bold mb-0">
{% if run_info.target_dcn is not none %}
{{ run_info.target_dcn }}
{% else %}
{% endif %}
</h6>
</div>
</div>
<div class="col-md-2">
<div class="border rounded-3 p-3 bg-white">
<p class="text-muted small mb-1">Base Fuel</p>
<h6 class="fw-bold mb-0">{{ run_info.base_fuel_type }}</h6>
</div>
</div>
<div class="col-md-2">
<div class="border rounded-3 p-3 bg-white">
<p class="text-muted small mb-1">Additive Fraction</p>
<h6 class="fw-bold mb-0">{{ run_info.additive_fraction }}</h6>
</div>
</div>
<div class="col-md-3">
<div class="border rounded-3 p-3 bg-white">
<p class="text-muted small mb-1">Objective</p>
<h6 class="fw-bold mb-0">{{ run_info.objective }}</h6>
</div>
</div>
</div>
<div class="row g-3 mt-1">
<div class="col-md-4">
<div class="border rounded-3 p-3 bg-white">
<p class="text-muted small mb-1">Filtered Candidates</p>
<h6 class="fw-bold mb-0">{{ run_info.n_final }}</h6>
</div>
</div>
<div class="col-md-4">
<div class="border rounded-3 p-3 bg-white">
<p class="text-muted small mb-1">Unfiltered Candidates</p>
<h6 class="fw-bold mb-0">{{ run_info.n_unfiltered }}</h6>
</div>
</div>
<div class="col-md-4">
<div class="border rounded-3 p-3 bg-white">
<p class="text-muted small mb-1">Pareto Candidates</p>
<h6 class="fw-bold mb-0">{{ run_info.n_pareto }}</h6>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Filtered Results -->
{% if final_table %}
<div class="card card-metric p-4 mb-4">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
<div>
<h5 class="fw-bold mb-1">Best Blend Candidates with Property Constraints</h5>
<p class="text-muted small mb-0">
Generated additive molecules ranked after applying mixture and property constraints.
</p>
</div>
<form method="POST" action="{{ url_for('download_mixture_generator_result', result_type='final') }}">
<input type="hidden" name="results_data" value='{{ final_records | tojson }}'>
<button type="submit" class="btn btn-outline-success btn-sm">
Download Filtered CSV
</button>
</form>
</div>
<div class="table-responsive app-table-wrap molecule-table">
{{ final_table | safe }}
</div>
</div>
{% endif %}
<!-- Unfiltered Results -->
{% if unfiltered_table %}
<div class="card card-metric p-4 mb-4">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
<div>
<h5 class="fw-bold mb-1">Best Blend Candidates without Property Constraints</h5>
<p class="text-muted small mb-0">
Raw generated candidates before final property filtering.
</p>
</div>
<form method="POST" action="{{ url_for('download_mixture_generator_result', result_type='unfiltered') }}">
<input type="hidden" name="results_data" value='{{ unfiltered_records | tojson }}'>
<button type="submit" class="btn btn-outline-secondary btn-sm">
Download Unfiltered CSV
</button>
</form>
</div>
<div class="table-responsive app-table-wrap molecule-table">
{{ unfiltered_table | safe }}
</div>
</div>
{% endif %}
<!-- Pareto Results -->
{% if pareto_table %}
<div class="card card-metric p-4 mb-4">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
<div>
<h5 class="fw-bold mb-1">Pareto Front</h5>
<p class="text-muted small mb-0">
Non-dominated blend candidates balancing mixture CN and mixture YSI.
</p>
</div>
<form method="POST" action="{{ url_for('download_mixture_generator_result', result_type='pareto') }}">
<input type="hidden" name="results_data" value='{{ pareto_records | tojson }}'>
<button type="submit" class="btn btn-outline-primary btn-sm">
Download Pareto CSV
</button>
</form>
</div>
<div class="table-responsive app-table-wrap molecule-table">
{{ pareto_table | safe }}
</div>
</div>
{% endif %}
</div>
<script>
const mixtureForm = document.getElementById("mixture-gen-form");
const mixtureBtn = document.getElementById("mixture-generate-btn");
const mixtureLoading = document.getElementById("mixture-loading");
function toggleMixtureTarget() {
const mode = document.getElementById("mixtureGenMode").value;
const targetBox = document.getElementById("targetDcnBox");
if (mode === "maximize") {
targetBox.style.display = "none";
} else {
targetBox.style.display = "block";
}
}
function toggleCustomBaseFuel() {
const baseFuel = document.getElementById("baseFuelType").value;
const customBox = document.getElementById("customBaseFuelBox");
if (baseFuel === "custom") {
customBox.style.display = "block";
} else {
customBox.style.display = "none";
}
}
function addCustomBaseRow() {
const container = document.getElementById("customBaseRows");
const row = document.createElement("div");
row.className = "custom-base-row row g-3 mb-2";
row.innerHTML = `
<div class="col-md-8">
<input type="text" name="custom_smiles[]" class="form-control" placeholder="Component SMILES">
</div>
<div class="col-md-4">
<input type="number" step="0.01" min="0" name="custom_fraction[]" class="form-control" placeholder="Mole fraction">
</div>
`;
container.appendChild(row);
}
function removeCustomBaseRow() {
const rows = document.querySelectorAll(".custom-base-row");
if (rows.length > 2) {
rows[rows.length - 1].remove();
}
}
document.addEventListener("DOMContentLoaded", function () {
toggleMixtureTarget();
toggleCustomBaseFuel();
});
if (mixtureForm) {
mixtureForm.addEventListener("submit", () => {
mixtureBtn.disabled = true;
mixtureLoading.style.display = "block";
});
}
</script>
{% endblock %}