Skip to content

Commit 5d3483f

Browse files
authored
test: expand filter RHS conformance coverage for mapped and alias cases (#199)
1 parent 1a43c13 commit 5d3483f

2 files changed

Lines changed: 185 additions & 0 deletions

File tree

crates/arco-kdl/tests/compile_suite.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,83 @@ scenario "S1" {
380380
Ok(())
381381
}
382382

383+
#[test]
384+
fn lowering_applies_data_param_filters_with_mapped_identifier_rhs()
385+
-> Result<(), Box<dyn std::error::Error>> {
386+
let root = temp_test_dir("data-param-filter-mapped-identifier")?;
387+
fs::create_dir_all(root.join("data"))?;
388+
fs::write(
389+
root.join("data").join("load.csv"),
390+
"period,technology,demand\n1,wind,10\n1,solar,90\n2,wind,20\n2,solar,80\n",
391+
)?;
392+
393+
let path = root.join("input.kdl");
394+
fs::write(
395+
&path,
396+
r#"
397+
set "time" { "1"; "2" }
398+
399+
data "inputs" source="data/load.csv" {
400+
map "time" from="period"
401+
map "tech" from="technology"
402+
403+
param "demand" from="demand" reduce="sum" {
404+
index "time"
405+
// Spec: §9 — map applies to lhs lookup; bare RHS remains literal.
406+
filter { tech == wind }
407+
}
408+
}
409+
410+
model "Dispatch" {
411+
set "time" alias="t"
412+
413+
param "demand" {
414+
index "t"
415+
}
416+
417+
control "x" {
418+
index "t"
419+
}
420+
421+
constraint "balance[t]" {
422+
x[t] = demand[t]
423+
}
424+
425+
minimize "Obj" {
426+
sum(x[t] for t in time)
427+
}
428+
}
429+
430+
scenario "S1" {
431+
use "Dispatch"
432+
}
433+
"#,
434+
)?;
435+
436+
let parsed = parse_program_file(&path)?;
437+
let semantic = validate_program(&parsed.program, &path)?;
438+
let compiled = compile_program(&semantic, &parsed.program, &path)?;
439+
440+
let bal_1 = compiled
441+
.algebra
442+
.constraints
443+
.iter()
444+
.find(|c| c.name == "balance[t][1]")
445+
.ok_or("missing balance constraint at t=1")?;
446+
let bal_2 = compiled
447+
.algebra
448+
.constraints
449+
.iter()
450+
.find(|c| c.name == "balance[t][2]")
451+
.ok_or("missing balance constraint at t=2")?;
452+
453+
assert_eq!(bal_1.rhs, 10.0);
454+
assert_eq!(bal_2.rhs, 20.0);
455+
456+
fs::remove_dir_all(&root)?;
457+
Ok(())
458+
}
459+
383460
#[test]
384461
fn lowering_instantiates_tuple_domain_variables_from_data_rows()
385462
-> Result<(), Box<dyn std::error::Error>> {

crates/arco-kdl/tests/semantic_validation.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ data "tech_data" source="data/tech.csv" {
230230
set "tech"
231231
set "wind_tech" {
232232
in "tech"
233+
// Spec: §9 — bare RHS is categorical literal, not column lookup.
233234
filter { tech == wind }
234235
}
235236
}
@@ -264,6 +265,113 @@ scenario "S1" {
264265
Ok(())
265266
}
266267

268+
#[test]
269+
fn semantic_validation_applies_mapped_column_set_filters() -> Result<(), Box<dyn std::error::Error>>
270+
{
271+
let root = temp_root("semantic-set-filter-mapped-column")?;
272+
fs::create_dir_all(root.join("data"))?;
273+
fs::write(
274+
root.join("data").join("tech.csv"),
275+
"technology\nwind\nsolar\nwind\n",
276+
)?;
277+
278+
let path = root.join("input.kdl");
279+
let text = r#"
280+
data "tech_data" source="data/tech.csv" {
281+
map "tech" from="technology"
282+
283+
set "tech"
284+
set "wind_tech" {
285+
in "tech"
286+
// Spec: §9 — map applies to lhs, RHS bare token remains literal.
287+
filter { tech == wind }
288+
}
289+
}
290+
291+
model "Dispatch" {
292+
set "tech"
293+
294+
control "x" {
295+
index "tech"
296+
}
297+
298+
minimize "Obj" {
299+
sum(x[tech] for tech in tech)
300+
}
301+
}
302+
303+
scenario "S1" {
304+
use "Dispatch"
305+
}
306+
"#;
307+
308+
let parsed = parse_program_text(text, &path)?;
309+
let semantic = validate_program(&parsed.program, &path)?;
310+
311+
let wind_tech = semantic
312+
.set_registry
313+
.get("wind_tech")
314+
.ok_or("missing set wind_tech")?;
315+
assert_eq!(wind_tech.values, vec!["wind".to_string()]);
316+
317+
fs::remove_dir_all(&root)?;
318+
Ok(())
319+
}
320+
321+
#[test]
322+
fn semantic_validation_applies_set_filter_with_parent_alias_and_bare_rhs()
323+
-> Result<(), Box<dyn std::error::Error>> {
324+
let root = temp_root("semantic-subset-filter-parent-bare-rhs")?;
325+
fs::create_dir_all(root.join("data"))?;
326+
fs::write(
327+
root.join("data").join("assets.csv"),
328+
"asset_name,zone_raw\nA,north\nB,south\nC,north\n",
329+
)?;
330+
331+
let path = root.join("input.kdl");
332+
let text = r#"
333+
data "assets_data" source="data/assets.csv" {
334+
map "asset_id" from="asset_name"
335+
map "zone" from="zone_raw"
336+
337+
set "asset_id" alias="a"
338+
set "south_assets" {
339+
in "a"
340+
// Spec: §9 — alias resolution and bare RHS semantics together.
341+
filter { zone == south }
342+
}
343+
}
344+
345+
model "Dispatch" {
346+
set "asset_id" alias="a"
347+
348+
control "x" {
349+
index "a"
350+
}
351+
352+
minimize "Obj" {
353+
sum(x[a] for a in asset_id)
354+
}
355+
}
356+
357+
scenario "S1" {
358+
use "Dispatch"
359+
}
360+
"#;
361+
362+
let parsed = parse_program_text(text, &path)?;
363+
let semantic = validate_program(&parsed.program, &path)?;
364+
365+
let south_assets = semantic
366+
.set_registry
367+
.get("south_assets")
368+
.ok_or("missing set south_assets")?;
369+
assert_eq!(south_assets.values, vec!["B".to_string()]);
370+
371+
fs::remove_dir_all(&root)?;
372+
Ok(())
373+
}
374+
267375
#[test]
268376
fn semantic_validation_applies_numeric_set_filters() -> Result<(), Box<dyn std::error::Error>> {
269377
let root = temp_root("semantic-set-filter-numeric")?;

0 commit comments

Comments
 (0)