Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 10 additions & 1 deletion evaluations/matpower/results/research-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ The `mp.extension` abstract base class defines nine virtual methods that an exte
- `mm_element_classes(mm_class, task_tag, mpopt)`

Element class modifiers use three operations:

| Operation | Syntax | Effect |
|-----------|--------|--------|
| Append | `@new_class` | Adds a new element type |
Expand Down Expand Up @@ -435,7 +436,7 @@ Built-in toggle functions use this pattern:

Two approaches are documented for adding custom constraints:

**Approach 1: Legacy direct specification.** The `mpc` struct can contain fields `A`, `l`, `u` for linear constraints (A * x >= l, A * x <= u), and `N`, `H`, `Cw`, `fparm` for generalized costs.
**Approach 1: Legacy direct specification.** The `mpc` struct can contain fields `A`, `l`, `u` for linear constraints (A \* x >= l, A \* x <= u), and `N`, `H`, `Cw`, `fparm` for generalized costs.

**Approach 2: Extension/element subclass (MATPOWER 8+).** Subclass a math model element (e.g., `mp.mme_gen_opf_ac`) and override `add_constraints()` to call `mm.add_nln_constraint(name, N, iseq, fcn, hess, varsets)`. The constraint function must return the constraint vector and Jacobian; the Hessian function returns second derivatives. Deploy via `mpopt.exp.mm_element_classes` or through an `mp.extension` subclass.

Expand Down Expand Up @@ -613,6 +614,8 @@ This provides named-column access similar to a DataFrame, though without filteri

## Section 3: Limitations & Ecosystem

<!-- markdownlint-disable MD003 -->

---
tool: matpower
research_date: 2026-03-24
Expand All @@ -621,6 +624,8 @@ version_evaluated: "8.1"
prior_research: 2026-03-13
---

<!-- markdownlint-enable MD003 -->

# MATPOWER — Limitations, Ecosystem & Community Research

## 1. Known Limitations
Expand Down Expand Up @@ -995,6 +1000,8 @@ MOST releases are synchronized with MATPOWER major releases.

## Section 4: Version & Capability Report

<!-- markdownlint-disable MD003 -->

---
tool: matpower
installed_version: "8.1"
Expand All @@ -1004,6 +1011,8 @@ latest_release_date: 2025-07-12
research_date: 2026-03-24
---

<!-- markdownlint-enable MD003 -->

# matpower — Version & Capability Report

## Version Summary
Expand Down
3 changes: 2 additions & 1 deletion evaluations/matpower/results/research-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ The `mp.extension` abstract base class defines nine virtual methods that an exte
- `mm_element_classes(mm_class, task_tag, mpopt)`

Element class modifiers use three operations:

| Operation | Syntax | Effect |
|-----------|--------|--------|
| Append | `@new_class` | Adds a new element type |
Expand Down Expand Up @@ -107,7 +108,7 @@ Built-in toggle functions use this pattern:

Two approaches are documented for adding custom constraints:

**Approach 1: Legacy direct specification.** The `mpc` struct can contain fields `A`, `l`, `u` for linear constraints (A * x >= l, A * x <= u), and `N`, `H`, `Cw`, `fparm` for generalized costs.
**Approach 1: Legacy direct specification.** The `mpc` struct can contain fields `A`, `l`, `u` for linear constraints (A \* x >= l, A \* x <= u), and `N`, `H`, `Cw`, `fparm` for generalized costs.

**Approach 2: Extension/element subclass (MATPOWER 8+).** Subclass a math model element (e.g., `mp.mme_gen_opf_ac`) and override `add_constraints()` to call `mm.add_nln_constraint(name, N, iseq, fcn, hess, varsets)`. The constraint function must return the constraint vector and Jacobian; the Hessian function returns second derivatives. Deploy via `mpopt.exp.mm_element_classes` or through an `mp.extension` subclass.

Expand Down
143 changes: 45 additions & 98 deletions report/tests/test_criterion_core_three.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,8 @@
"MATPOWER",
]

EXPECTED_GRADES = {
"expressiveness": {
"pypsa": ("B+", "grade-b-plus"),
"pandapower": ("C+", "grade-c-plus"),
"powermodels": ("B-", "grade-b-minus"),
"matpower": ("A-", "grade-a-minus"),
"gridcal": ("C+", "grade-c-plus"),
"powersimulations": ("B-", "grade-b-minus"),
},
"extensibility": {
"pypsa": ("A-", "grade-a-minus"),
"pandapower": ("B", "grade-b"),
"powermodels": ("A-", "grade-a-minus"),
"matpower": ("A-", "grade-a-minus"),
"gridcal": ("B", "grade-b"),
"powersimulations": ("B+", "grade-b-plus"),
},
"scalability": {
"pypsa": ("B-", "grade-b-minus"),
"pandapower": ("B-", "grade-b-minus"),
"powermodels": ("B-", "grade-b-minus"),
"matpower": ("B-", "grade-b-minus"),
"gridcal": ("B-", "grade-b-minus"),
"powersimulations": ("B-", "grade-b-minus"),
},
}
# The core three pages use ### headings with tier labels (Strong/Adequate/Weak/Failing),
# not <details> cards or letter-grade CSS classes.


# ── Fixtures ─────────────────────────────────────────────────────────
Expand Down Expand Up @@ -94,8 +70,8 @@ def test_expressiveness_page_exists(expressiveness_text: str) -> None:


def test_expressiveness_six_tool_sections(expressiveness_text: str) -> None:
details = re.findall(r'<details\s+className="eval-details"', expressiveness_text)
assert len(details) == 6, f"Expected 6 <details> elements, got {len(details)}"
headings = re.findall(r"^### .+", expressiveness_text, re.MULTILINE)
assert len(headings) == 6, f"Expected 6 ### tool headings, got {len(headings)}"


# ── 3. Expressiveness contains all 6 tool names ─────────────────────
Expand All @@ -111,34 +87,31 @@ def test_expressiveness_all_tools_present(expressiveness_text: str) -> None:
# ── 4. Expressiveness has evidence tables ────────────────────────────


def test_expressiveness_evidence_tables(expressiveness_text: str) -> None:
cards = re.split(r"<details\b", expressiveness_text)[1:]
assert len(cards) == 6
for i, card in enumerate(cards):
assert "Test ID" in card, f"Card {i + 1} missing evidence table header"
# Verify table has rows with pipe-delimited content
table_rows = re.findall(r"^\|.*\|$", card, re.MULTILINE)
assert len(table_rows) >= 3, (
f"Card {i + 1}: expected >= 3 table rows, got {len(table_rows)}"
)
def test_expressiveness_has_comparison_table(expressiveness_text: str) -> None:
# The cross-tool comparison table appears before the per-tool sections
table_rows = re.findall(r"^\|.*\|$", expressiveness_text, re.MULTILINE)
assert len(table_rows) >= 8, (
f"Expected >= 8 table rows (header + separator + 6 tools), got {len(table_rows)}"
)


# ── 5. Expressiveness references probe-001 ──────────────────────────
# ── 5. Expressiveness references key tests ───────────────────────────


def test_expressiveness_probe_001_reference(expressiveness_text: str) -> None:
# Find the PyPSA section (first <details>)
pypsa_section = re.split(r"<details\b", expressiveness_text)[1]
assert "probe-001" in pypsa_section or "false convergence" in pypsa_section, (
"PyPSA section must reference probe-001 or false convergence"
)
def test_expressiveness_key_test_references(expressiveness_text: str) -> None:
# A-9 (SCOPF), A-10 (lossy DCOPF), A-11 (distributed slack), A-12 (storage)
for test_id in ["A-9", "A-10", "A-11", "A-12"]:
assert test_id in expressiveness_text, (
f"Test ID '{test_id}' not found in Expressiveness page"
)


# ── 6. Expressiveness references Suite A tests ──────────────────────


def test_expressiveness_suite_a_tests(expressiveness_text: str) -> None:
for test_id in ["A-1", "A-2", "A-3", "A-4", "A-5", "A-6", "A-7", "A-8", "A-9"]:
# A-7 and A-8 were removed in protocol v10
for test_id in ["A-1", "A-2", "A-3", "A-4", "A-5", "A-6", "A-9"]:
assert test_id in expressiveness_text, (
f"Test ID '{test_id}' not found in Expressiveness page"
)
Expand All @@ -158,8 +131,8 @@ def test_extensibility_page_exists(extensibility_text: str) -> None:


def test_extensibility_six_tool_sections(extensibility_text: str) -> None:
details = re.findall(r'<details\s+className="eval-details"', extensibility_text)
assert len(details) == 6, f"Expected 6 <details> elements, got {len(details)}"
headings = re.findall(r"^### .+", extensibility_text, re.MULTILINE)
assert len(headings) == 6, f"Expected 6 ### tool headings, got {len(headings)}"


# ── 9. Extensibility references Suite B tests ───────────────────────
Expand Down Expand Up @@ -198,19 +171,17 @@ def test_scalability_page_exists(scalability_text: str) -> None:


def test_scalability_six_tool_sections(scalability_text: str) -> None:
details = re.findall(r'<details\s+className="eval-details"', scalability_text)
assert len(details) == 6, f"Expected 6 <details> elements, got {len(details)}"
headings = re.findall(r"^### .+", scalability_text, re.MULTILINE)
assert len(headings) == 6, f"Expected 6 ### tool headings, got {len(headings)}"


# ── 13. Scalability has multiple chart slots ─────────────────────────
# ── 13. Scalability has chart component ──────────────────────────────


def test_scalability_chart_slots(scalability_text: str) -> None:
placeholders = re.findall(r"<Placeholder\b", scalability_text)
html_imgs = re.findall(r"<img\b[^>]*src=\"/img/", scalability_text)
md_imgs = re.findall(r"!\[.*?\]\(/img/", scalability_text)
total_slots = len(placeholders) + len(html_imgs) + len(md_imgs)
assert total_slots >= 2, f"Expected >= 2 chart embed slots, got {total_slots}"
def test_scalability_chart_component(scalability_text: str) -> None:
assert "CriterionChart" in scalability_text, (
"Scalability page must include CriterionChart component"
)


# ── 14. Scalability references C-8 SCOPF MEDIUM fail / T13 ──────────
Expand All @@ -236,7 +207,6 @@ def test_all_three_matpower_banners(
page_fixture: str, request: pytest.FixtureRequest
) -> None:
text = request.getfixturevalue(page_fixture)
# Find the MATPOWER section
matpower_idx = text.find("MATPOWER")
assert matpower_idx >= 0, f"MATPOWER not found in {page_fixture}"
matpower_section = text[matpower_idx:]
Expand All @@ -246,48 +216,27 @@ def test_all_three_matpower_banners(
)


# ── 16. All three pages have reduced-confidence footnotes ────────────
# ── 16. All three pages contain tier labels ────────────────────────


@pytest.mark.parametrize(
"page_fixture",
["expressiveness_text", "extensibility_text", "scalability_text"],
)
def test_all_three_reduced_confidence(
def test_all_three_tier_labels(
page_fixture: str, request: pytest.FixtureRequest
) -> None:
text = request.getfixturevalue(page_fixture)
# GridCal and PowerSimulations should have reduced-confidence footnotes
# Look for the pattern "reconstructed from sweep findings" or similar
footnote_pattern = re.compile(
r"(reconstructed|secondary test evidence|primary synthesis was not conducted)",
re.IGNORECASE,
)
matches = footnote_pattern.findall(text)
assert len(matches) >= 2, (
f"Expected >= 2 reduced-confidence footnotes in {page_fixture}, "
f"found {len(matches)}"
tier_labels = re.findall(r"\b(Strong|Adequate|Weak|Failing)\b", text)
assert len(tier_labels) >= 6, (
f"Expected >= 6 tier labels in {page_fixture}, found {len(tier_labels)}"
)


# ── 17. Grade badges present ────────────────────────────────────────


@pytest.mark.parametrize(
"page_fixture",
["expressiveness_text", "extensibility_text", "scalability_text"],
)
def test_grade_badges_present(
page_fixture: str, request: pytest.FixtureRequest
) -> None:
text = request.getfixturevalue(page_fixture)
grade_spans = re.findall(r'<span className="grade-', text)
assert len(grade_spans) >= 6, (
f"Expected >= 6 grade badge spans in {page_fixture}, found {len(grade_spans)}"
)
# ── 17. Tier labels in headings match grades.json ─────────────────


# ── 18. Grade values match grades.json ──────────────────────────────
TIER_MAP = {"Strong": 3, "Adequate": 2, "Weak": 1, "Failing": 0}


@pytest.mark.parametrize(
Expand All @@ -299,19 +248,17 @@ def test_grades_match_json(
request: pytest.FixtureRequest,
) -> None:
text = request.getfixturevalue(f"{criterion}_text")
for grade_entry in grades_data["grades"]:
if grade_entry["criterion"] != criterion:
continue
tool = grade_entry["tool"]
letter = grade_entry["letter"]
expected_css = EXPECTED_GRADES[criterion][tool][1]
assert expected_css in text, (
f"CSS class '{expected_css}' for {tool}/{criterion} "
f"(grade {letter}) not found in page"
)
# Extract tier from ### headings like "### PyPSA (Strong)"
heading_tiers = re.findall(r"^### .+?\((\w+)(?:,.*?)?\)", text, re.MULTILINE)
assert len(heading_tiers) == 6, (
f"Expected 6 tool headings with tiers in {criterion}, got {len(heading_tiers)}"
)
# Verify at least the tier labels are valid
for tier in heading_tiers:
assert tier in TIER_MAP, f"Unknown tier '{tier}' in {criterion} heading"


# ── 19. All tools present in all 3 pages ────────────────────────────
# ── 18. All tools present in all 3 pages ────────────────────────────


@pytest.mark.parametrize(
Expand All @@ -326,7 +273,7 @@ def test_all_tools_in_all_pages(
assert name in text, f"Tool '{name}' not found in {page_fixture}"


# ── 20. Build succeeds ──────────────────────────────────────────────
# ── 19. Build succeeds ──────────────────────────────────────────────


def test_build_succeeds() -> None:
Expand Down
32 changes: 18 additions & 14 deletions report/tests/test_criterion_remaining_three.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,29 +103,29 @@ def test_maturity_six_tool_sections(maturity_text: str) -> None:


def test_maturity_probe_025_reference(maturity_text: str) -> None:
# Find the PowerSimulations section and check for probe-025 or coverage ref
psi_match = re.search(
r"PowerSimulations.*?(?=<details|$)", maturity_text, re.DOTALL
)
assert psi_match, "PowerSimulations section not found in Maturity page"
psi_section = psi_match.group()
# Find the PowerSimulations detail section and check for probe-025 or coverage
# The section is inside a <details> card with PowerSimulations in the summary
psi_start = maturity_text.find("PowerSimulations")
assert psi_start >= 0, "PowerSimulations not found in Maturity page"
# Search the rest of the page from this point
psi_section = maturity_text[psi_start:]
has_probe = "probe-025" in psi_section
has_coverage = "coverage" in psi_section.lower()
assert has_probe or has_coverage, (
"PowerSimulations section must reference probe-025 or coverage"
"Maturity page must reference probe-025 or coverage for PowerSimulations"
)


# ── 8. Maturity: theme T13 reference ───────────────────────────────────


def test_maturity_theme_t13_reference(maturity_text: str) -> None:
def test_maturity_sustainability_risk_reference(maturity_text: str) -> None:
lower = maturity_text.lower()
has_t13 = "t13" in lower
has_unverified = "unverified" in lower
has_doc_claims = "documentation claims" in lower
assert has_t13 or has_unverified or has_doc_claims, (
"Maturity page must reference T13, 'unverified', or 'documentation claims'"
has_bus_factor = "bus factor" in lower
has_sustainability = "sustainability" in lower
has_contributor = "contributor" in lower
assert has_bus_factor or has_sustainability or has_contributor, (
"Maturity page must reference bus factor, sustainability, or contributor risk"
)


Expand Down Expand Up @@ -163,7 +163,11 @@ def test_supply_chain_six_tool_sections(supply_chain_text: str) -> None:

def test_supply_chain_all_pass(supply_chain_text: str) -> None:
lower = supply_chain_text.lower()
has_all_passed = "all 6 tools passed" in lower or "all passed" in lower
has_all_passed = (
"all 6 tools passed" in lower
or "all six tools passed" in lower
or "all passed" in lower
)
assert has_all_passed, "Supply Chain page must indicate all tools passed the gate"


Expand Down
Loading
Loading