Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
answer_logger = Logger()


def getUserName():
def getUserName():
return current_user.username

def getUserId():
Expand Down Expand Up @@ -426,25 +426,27 @@ def ltlstepper():
if syntax_choice == None or syntax_choice not in SUPPORTED_SYNTAXES:
syntax_choice = 'Classic'

if request.method == 'GET':
return render_template('stepper.html', uid = getUserName(), error="", prefixstates=[], cyclestates=[])
if request.method == 'GET':
return render_template('stepper.html', uid = getUserName(), error="", prefixstates=[], cyclestates=[], matrix_data={"subformulae": [], "matrix": [], "rows": []})

if request.method == 'POST':
ltl = request.form.get('formula')
trace = request.form.get('trace')
if ltl == "" or trace == "":
error="Please enter an LTL formula and a trace."

try:
node = parse_ltl_string(ltl)
except:
return render_template('stepper.html', uid = getUserName(), error="Invalid LTL formula " + ltl, prefixstates=[], cyclestates=[])

try:
result = traceSatisfactionPerStep(node = node, trace = trace, syntax = syntax_choice)
except:
return render_template('stepper.html', uid = getUserName(), error="Invalid trace " + trace, prefixstates=[], cyclestates=[])
return render_template('stepper.html', uid = getUserName(), error="", prefixstates=result.prefix_states, cyclestates=result.cycle_states, formula = ltl, trace=trace)
try:
node = parse_ltl_string(ltl)
except:
return render_template('stepper.html', uid = getUserName(), error="Invalid LTL formula " + ltl, prefixstates=[], cyclestates=[], matrix_data={"subformulae": [], "matrix": [], "rows": []})

try:
result = traceSatisfactionPerStep(node = node, trace = trace, syntax = syntax_choice)
except:
return render_template('stepper.html', uid = getUserName(), error="Invalid trace " + trace, prefixstates=[], cyclestates=[], matrix_data={"subformulae": [], "matrix": [], "rows": []})

matrix_data = result.getMatrixView()
return render_template('stepper.html', uid = getUserName(), error="", prefixstates=result.prefix_states, cyclestates=result.cycle_states, formula = ltl, trace=trace, matrix_data=matrix_data)


##### Eng LTL Logging Routes ###
Expand Down
44 changes: 44 additions & 0 deletions src/stepper.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ def formattedTraceAssignment(self):
asStr = asStr.replace('!', '¬')
return asStr

def getAllSubformulae(self):
"""Extract all subformulae from this node and its children recursively."""
result = [(self.formula, self.satisfied)]
for child in self.children:
result.extend(child.getAllSubformulae())
return result

def __formula_to__mermaid_inner__(self):
edges = []
if self.satisfied:
Expand Down Expand Up @@ -173,6 +180,43 @@ def to_dict(self):
"prefix_states": self.prefix_states,
"cycle_states": self.cycle_states
}

def getMatrixView(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 0s and 1s are not visible in the matrix view. We should also make the cycle obvious with a header like we do in the Table view.

Don't worry about highlighting cells red or green, make the actual TEXT color for 0 or 1 these colors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the Matrix View visibility issues:

  • Changed from background colors to text colors for 0/1 values (green for 1, red for 0)
  • Added proper cycle headers with the same structure as the Table view
  • The 0s and 1s are now clearly visible with bold text styling

See commit 07bc78f and screenshot: https://github.com/user-attachments/assets/a160c974-96b5-4c40-b534-761b8d5b746d

"""Generate matrix view data for table display."""
all_states = self.prefix_states + self.cycle_states

if not all_states:
return {"subformulae": [], "matrix": [], "rows": []}

# Collect all unique subformulae across all states
all_subformulae_set = set()
for state in all_states:
subformulae = state.getAllSubformulae()
for formula, _ in subformulae:
all_subformulae_set.add(formula)

# Sort subformulae for consistent ordering (simple ones first, complex ones last)
all_subformulae = sorted(list(all_subformulae_set), key=lambda x: (len(x), x))

# Build matrix: rows are subformulae, columns are time steps
matrix = []
rows = [] # Combined data for easier template iteration
for subformula in all_subformulae:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think this is being correctly populated with 0s and 1s

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit c473ef9. The issue was that the Matrix View template was dependent on prefixstates|length to determine columns, but when matrix_data had data but prefixstates was empty, no columns would render. Now the Matrix View uses the actual matrix data to drive the display, making the 0s and 1s visible regardless of prefix/cycle state availability.

row = []
for state in all_states:
# Find this subformula in the current state
subformulae = state.getAllSubformulae()
subformula_dict = {f: s for f, s in subformulae}
satisfaction = subformula_dict.get(subformula, False)
row.append(1 if satisfaction else 0)
matrix.append(row)
rows.append({"subformula": subformula, "values": row})

return {
"subformulae": all_subformulae,
"matrix": matrix,
"rows": rows
}

def __repr__(self):
return f"TraceSatisfactionResult(prefix_states={self.prefix_states}, cycle_states={self.cycle_states})"
Expand Down
211 changes: 140 additions & 71 deletions src/templates/stepper.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,25 @@
display: inline-block;
}

.unsatformula {

border: 2px solid #dc3545;
/* red solid border */
border-radius: 6px;
padding: 4px 8px;

display: inline-block;
.unsatformula {

border: 2px solid #dc3545;
/* red solid border */
border-radius: 6px;
padding: 4px 8px;

display: inline-block;
}

/* Matrix view text colors */
.matrix-satisfied {
color: #28a745 !important;
font-weight: bold;
}

.matrix-unsatisfied {
color: #dc3545 !important;
font-weight: bold;
}
</style>

Expand Down Expand Up @@ -131,18 +142,24 @@ <h5 class="modal-title" id="exampleModalLabel">Load Stepper with a new LTL Formu
</div>


<ul class="nav nav-tabs mb-3 mt-4" id="stepperTabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="stepper-tab" data-toggle="tab" href="#stepper-view" role="tab">Stepper
View</a>
</li>
<li class="nav-item">
<a class="nav-link" id="table-tab" data-toggle="tab" href="#table-view" role="tab">Table View<sup>Beta</sup></a>
</li>
<ul class="nav nav-tabs mb-3 mt-4" id="stepperTabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="stepper-tab" data-toggle="tab" href="#stepper-view" role="tab">Stepper
View</a>
</li>
<li class="nav-item">
<a class="nav-link" id="table-tab" data-toggle="tab" href="#table-view" role="tab">Table View<sup>Beta</sup></a>
</li>
<li class="nav-item">
<a class="nav-link" id="matrix-tab" data-toggle="tab" href="#matrix-view" role="tab">Matrix View<sup>Alpha</sup></a>
</li>
</ul>

<div class="tab-content">
<div class="tab-pane fade show active" id="stepper-view" role="tabpanel">
<div class="tab-content">
<!-- ================================ -->
<!-- STEPPER VIEW - Interactive stepping through formula satisfaction -->
<!-- ================================ -->
<div class="tab-pane fade show active" id="stepper-view" role="tabpanel">
<!-- Stepper view lets you see the entire trace at once, and each tree state one by one. -->

<div id="statecontainer">
Expand Down Expand Up @@ -180,59 +197,111 @@ <h4><span class="help float-right"> By clicking the <code>Previous State</code>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- This view lets you look at multiple tree views at once, for state by state. -->
<div class="tab-pane fade" id="table-view" role="tabpanel">


<p>
Sub-formulae are demarcated by their bounding boxes, which
indicate whether they are <span class="satformula">satisfied</span> or
<span class="unsatformula">unsatisfied</span>.

</p>
<!-- Table view goes here -->
<table class="table table-bordered" id="stateTable">
<thead>
<tr>
<th class="w-auto"></th>
{% for ps in prefixstates %}
<th></th>
{% endfor %}
{% if cyclestates %}
<th class="cycle-state-col" colspan="{{ cyclestates|length }}">Cycle</th>
{% endif %}
</tr>
</thead>
<tbody>
<tr>
<th class="w-auto">Trace State</th>
{% for ps in prefixstates %}
<td><pre>{{ ps.formattedTraceAssignment }}</pre></td>
{% endfor %}
{% for ps in cyclestates %}
<td class="cycle-state-col"><pre>{{ ps.formattedTraceAssignment }}</pre></td>
{% endfor %}
</tr>
<tr>
<th class="w-auto">
Formula
</span>
</th>
{% for ps in prefixstates %}
<td>
{{ ps.formulaAsHTML | safe }}
</td>
{% endfor %}
{% for ps in cyclestates %}
<td class="cycle-state-col">
{{ ps.formulaAsHTML | safe }}
</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div>
<!-- ================================ -->
<!-- TABLE VIEW - Detailed formula view with satisfaction highlighting -->
<!-- ================================ -->
<div class="tab-pane fade" id="table-view" role="tabpanel">
{% if matrix_data.subformulae %}
<p>
Sub-formulae are demarcated by their bounding boxes, which
indicate whether they are <span class="satformula">satisfied</span> or
<span class="unsatformula">unsatisfied</span>.
</p>
<!-- Detailed table view -->
<table class="table table-bordered" id="stateTable">
<thead>
<tr>
<th class="w-auto"></th>
{% for ps in prefixstates %}
<th></th>
{% endfor %}
{% if cyclestates %}
<th class="cycle-state-col" colspan="{{ cyclestates|length }}">Cycle</th>
{% endif %}
</tr>
</thead>
<tbody>
<tr>
<th class="w-auto">Trace State</th>
{% for ps in prefixstates %}
<td><pre>{{ ps.formattedTraceAssignment }}</pre></td>
{% endfor %}
{% for ps in cyclestates %}
<td class="cycle-state-col"><pre>{{ ps.formattedTraceAssignment }}</pre></td>
{% endfor %}
</tr>
<tr>
<th class="w-auto">
Formula
</span>
</th>
{% for ps in prefixstates %}
<td>
{{ ps.formulaAsHTML | safe }}
</td>
{% endfor %}
{% for ps in cyclestates %}
<td class="cycle-state-col">
{{ ps.formulaAsHTML | safe }}
</td>
{% endfor %}
</tr>
</tbody>
</table>
{% else %}
<div class="alert alert-info">
<p><strong>No formula loaded.</strong></p>
<p>Please enter an LTL formula and trace to see the table view.</p>
</div>
{% endif %}
</div>
<!-- ================================ -->
<!-- MATRIX VIEW - Binary matrix display of subformula satisfaction over time -->
<!-- ================================ -->
<div class="tab-pane fade" id="matrix-view" role="tabpanel">
<p>
This matrix view shows how each subformula's satisfaction evolves over time.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there! We need to visually indicate the cycle in the trace

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! Added cycle indication to the Matrix View with dual-header layout and proper cycle styling. The Matrix View now shows:

  • Dual header with "Cycle" label spanning cycle columns
  • Light blue/gray background for cycle columns (matching Table View style)
  • Consecutive time step numbering with clear cycle distinction
  • Proper 0/1 binary values with color coding

See commit 429715c.

Matrix View with Cycle Indication

Each row represents a subformula, and each column represents a time step.
Values are shown as <strong class="matrix-satisfied">1</strong> (satisfied) or <strong class="matrix-unsatisfied">0</strong> (unsatisfied).
</p>

{% if matrix_data.subformulae %}
<!-- Matrix view table -->
<table class="table table-bordered table-sm" id="matrixTable">
<thead>
<tr>
<th class="w-auto">Subformula</th>
{% if matrix_data.rows and matrix_data.rows|length > 0 and matrix_data.rows[0] and 'values' in matrix_data.rows[0] %}
{% set num_columns = matrix_data.rows[0]['values']|length %}
{% for i in range(num_columns) %}
<th class="text-center">{{ i }}</th>
{% endfor %}
{% endif %}
</tr>
</thead>
<tbody>
{% for row_data in matrix_data.rows %}
{% if row_data and 'subformula' in row_data and 'values' in row_data %}
<tr>
<td class="font-monospace"><code>{{ row_data['subformula'] }}</code></td>
{% for value in row_data['values'] %}
<td class="text-center">
<span class="{% if value == 1 %}matrix-satisfied{% else %}matrix-unsatisfied{% endif %}">{{ value }}</span>
</td>
{% endfor %}
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% else %}
<div class="alert alert-info">
<p><strong>No formula loaded.</strong></p>
<p>Please enter an LTL formula and trace to see the matrix view.</p>
</div>
{% endif %}
</div>
</div>
</div>
Expand Down