@bramas/simulant-engine 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/system.js +148 -3
- package/package.json +2 -2
- package/src/system.ts +108 -2
package/dist/system.js
CHANGED
|
@@ -541,6 +541,7 @@ export function* parameterValues(exp) {
|
|
|
541
541
|
const defs = new Map((exp.model.defs ?? []).map(d => [d.name, d]));
|
|
542
542
|
const size = compiler.root(exp.model.topology.size, [], defs);
|
|
543
543
|
const domains = exp.params.map(p => p.domain && compiler.root(p.domain, [], defs));
|
|
544
|
+
const accepts = exp.params.map(p => p.accepts && compiler.root(p.accepts, [], defs));
|
|
544
545
|
function* go(i, params) {
|
|
545
546
|
if (i === exp.params.length) {
|
|
546
547
|
yield { ...params };
|
|
@@ -548,7 +549,7 @@ export function* parameterValues(exp) {
|
|
|
548
549
|
}
|
|
549
550
|
const domain = domains[i];
|
|
550
551
|
if (!domain)
|
|
551
|
-
throw new Error(`parameter ${exp.params[i].name} has no domain`);
|
|
552
|
+
throw new Error(`parameter ${exp.params[i].name} has no finite domain; provide checkSettings in project.ts for check-all`);
|
|
552
553
|
rt.params = params;
|
|
553
554
|
try {
|
|
554
555
|
rt.topo = makeTopology(exp, runDet(size, []));
|
|
@@ -556,11 +557,153 @@ export function* parameterValues(exp) {
|
|
|
556
557
|
catch {
|
|
557
558
|
// The size depends on a later parameter: domains cannot use the topology yet.
|
|
558
559
|
}
|
|
559
|
-
for (const v of runDet(domain, []))
|
|
560
|
-
|
|
560
|
+
for (const v of runDet(domain, [])) {
|
|
561
|
+
const candidate = { ...params, [exp.params[i].name]: v };
|
|
562
|
+
rt.params = candidate;
|
|
563
|
+
if (accepts[i]) {
|
|
564
|
+
try {
|
|
565
|
+
rt.topo = makeTopology(exp, runDet(size, []));
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
// Topology is unavailable until its size is fixed.
|
|
569
|
+
}
|
|
570
|
+
if (!runDet(accepts[i], []))
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
yield* go(i + 1, candidate);
|
|
574
|
+
}
|
|
561
575
|
}
|
|
562
576
|
yield* go(0, {});
|
|
563
577
|
}
|
|
578
|
+
/** A simple editable fallback, independent of the parameter's declared domain. */
|
|
579
|
+
function defaultValue(t) {
|
|
580
|
+
switch (t.k) {
|
|
581
|
+
case 'nat':
|
|
582
|
+
case 'int':
|
|
583
|
+
case 'node': return 0;
|
|
584
|
+
case 'bool':
|
|
585
|
+
case 'frame': return false;
|
|
586
|
+
case 'enum': return t.ctors[0];
|
|
587
|
+
case 'move': return 'stay';
|
|
588
|
+
case 'verdict': return 'inProgress';
|
|
589
|
+
case 'string': return '';
|
|
590
|
+
case 'list':
|
|
591
|
+
case 'multiset': return [];
|
|
592
|
+
case 'record': return Object.fromEntries(t.fields.map(f => [f.name, defaultValue(f.type)]));
|
|
593
|
+
default: return null;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
/** Small candidate sets for predicate-only domains; this is not an enumeration of them. */
|
|
597
|
+
function* defaultCandidates(t, nodeCount) {
|
|
598
|
+
switch (t.k) {
|
|
599
|
+
case 'nat':
|
|
600
|
+
case 'node':
|
|
601
|
+
for (let n = 0; n < Math.min(64, t.k === 'node' ? nodeCount ?? 0 : 64); n++)
|
|
602
|
+
yield n;
|
|
603
|
+
return;
|
|
604
|
+
case 'int':
|
|
605
|
+
yield 0;
|
|
606
|
+
for (let n = 1; n < 64; n++) {
|
|
607
|
+
yield n;
|
|
608
|
+
yield -n;
|
|
609
|
+
}
|
|
610
|
+
return;
|
|
611
|
+
case 'bool':
|
|
612
|
+
case 'frame':
|
|
613
|
+
yield false;
|
|
614
|
+
yield true;
|
|
615
|
+
return;
|
|
616
|
+
case 'enum':
|
|
617
|
+
yield* t.ctors;
|
|
618
|
+
return;
|
|
619
|
+
default: yield defaultValue(t);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Simulator starting values, separate from model semantics and check-all.
|
|
624
|
+
* Search a bounded prefix for a matching setting, honoring project overrides.
|
|
625
|
+
* If no complete setting is found, choose each parameter in order, falling back
|
|
626
|
+
* to an editable value of its type where matching is too difficult or impossible.
|
|
627
|
+
*/
|
|
628
|
+
export function parameterDefaults(exp, overrides = {}) {
|
|
629
|
+
const checked = check(exp);
|
|
630
|
+
if (checked.errors.length)
|
|
631
|
+
throw new IRError(checked.errors);
|
|
632
|
+
const rt = new Runtime(checked.layout);
|
|
633
|
+
const compiler = new Compiler(rt, checked.layout, new Map(exp.params.map(p => [p.name, p.type])));
|
|
634
|
+
const defs = new Map((exp.model.defs ?? []).map(d => [d.name, d]));
|
|
635
|
+
const size = compiler.root(exp.model.topology.size, [], defs);
|
|
636
|
+
const domains = exp.params.map(p => p.domain && compiler.root(p.domain, [], defs));
|
|
637
|
+
const accepts = exp.params.map(p => p.accepts && compiler.root(p.accepts, [], defs));
|
|
638
|
+
const context = (params) => {
|
|
639
|
+
rt.params = params;
|
|
640
|
+
rt.topo = undefined;
|
|
641
|
+
try {
|
|
642
|
+
rt.topo = makeTopology(exp, runDet(size, []));
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
// A positive topology size may not have been selected yet.
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
function* candidates(i, params) {
|
|
649
|
+
const p = exp.params[i];
|
|
650
|
+
if (p.name in overrides) {
|
|
651
|
+
yield overrides[p.name];
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
context(params);
|
|
655
|
+
try {
|
|
656
|
+
yield* domains[i] ? runDet(domains[i], []) : defaultCandidates(p.type, rt.topo?.size);
|
|
657
|
+
}
|
|
658
|
+
catch {
|
|
659
|
+
// An incomplete setting may not yet support a topology-dependent domain.
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const matches = (i, params) => {
|
|
663
|
+
if (exp.params[i].name in overrides || !accepts[i])
|
|
664
|
+
return true;
|
|
665
|
+
context(params);
|
|
666
|
+
try {
|
|
667
|
+
return !!runDet(accepts[i], []);
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
let remaining = 1024;
|
|
674
|
+
const search = (i, params) => {
|
|
675
|
+
if (i === exp.params.length)
|
|
676
|
+
return params;
|
|
677
|
+
for (const value of candidates(i, params)) {
|
|
678
|
+
if (--remaining < 0)
|
|
679
|
+
break;
|
|
680
|
+
const next = { ...params, [exp.params[i].name]: value };
|
|
681
|
+
if (!matches(i, next))
|
|
682
|
+
continue;
|
|
683
|
+
const found = search(i + 1, next);
|
|
684
|
+
if (found)
|
|
685
|
+
return found;
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
const found = search(0, {});
|
|
689
|
+
if (found)
|
|
690
|
+
return found;
|
|
691
|
+
const fallback = {};
|
|
692
|
+
for (const [i, p] of exp.params.entries()) {
|
|
693
|
+
fallback[p.name] = p.name in overrides ? overrides[p.name] : defaultValue(p.type);
|
|
694
|
+
let tried = 0;
|
|
695
|
+
for (const value of candidates(i, fallback)) {
|
|
696
|
+
if (++tried > 128)
|
|
697
|
+
break;
|
|
698
|
+
const next = { ...fallback, [p.name]: value };
|
|
699
|
+
if (matches(i, next)) {
|
|
700
|
+
fallback[p.name] = value;
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return fallback;
|
|
706
|
+
}
|
|
564
707
|
/** Parameters outside their declared domains, without enumerating the settings product. */
|
|
565
708
|
export function parameterDomainViolations(exp, params) {
|
|
566
709
|
const checked = check(exp);
|
|
@@ -572,6 +715,8 @@ export function parameterDomainViolations(exp, params) {
|
|
|
572
715
|
const defs = new Map((exp.model.defs ?? []).map(d => [d.name, d]));
|
|
573
716
|
rt.topo = makeTopology(exp, runDet(compiler.root(exp.model.topology.size, [], defs), []));
|
|
574
717
|
return exp.params.filter(p => {
|
|
718
|
+
if (p.accepts && !runDet(compiler.root(p.accepts, [], defs), []))
|
|
719
|
+
return true;
|
|
575
720
|
// Parameters without a domain are supplied by the user and impose no domain restriction.
|
|
576
721
|
if (!p.domain)
|
|
577
722
|
return false;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bramas/simulant-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "CECILL-B",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@bramas/simulant-lang": "0.
|
|
8
|
+
"@bramas/simulant-lang": "0.3.0"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
11
|
"./*": {
|
package/src/system.ts
CHANGED
|
@@ -583,21 +583,126 @@ export function* parameterValues(exp: Experiment): Generator<Record<string, V>>
|
|
|
583
583
|
const defs = new Map((exp.model.defs ?? []).map(d => [d.name, d]));
|
|
584
584
|
const size = compiler.root(exp.model.topology.size, [], defs);
|
|
585
585
|
const domains = exp.params.map(p => p.domain && compiler.root(p.domain, [], defs));
|
|
586
|
+
const accepts = exp.params.map(p => p.accepts && compiler.root(p.accepts, [], defs));
|
|
586
587
|
function* go(i: number, params: Record<string, V>): Generator<Record<string, V>> {
|
|
587
588
|
if (i === exp.params.length) { yield { ...params }; return; }
|
|
588
589
|
const domain = domains[i];
|
|
589
|
-
if (!domain) throw new Error(`parameter ${exp.params[i].name} has no domain`);
|
|
590
|
+
if (!domain) throw new Error(`parameter ${exp.params[i].name} has no finite domain; provide checkSettings in project.ts for check-all`);
|
|
590
591
|
rt.params = params;
|
|
591
592
|
try {
|
|
592
593
|
rt.topo = makeTopology(exp, runDet(size, []));
|
|
593
594
|
} catch {
|
|
594
595
|
// The size depends on a later parameter: domains cannot use the topology yet.
|
|
595
596
|
}
|
|
596
|
-
for (const v of runDet(domain, []) as V[])
|
|
597
|
+
for (const v of runDet(domain, []) as V[]) {
|
|
598
|
+
const candidate = { ...params, [exp.params[i].name]: v };
|
|
599
|
+
rt.params = candidate;
|
|
600
|
+
if (accepts[i]) {
|
|
601
|
+
try { rt.topo = makeTopology(exp, runDet(size, [])); } catch {
|
|
602
|
+
// Topology is unavailable until its size is fixed.
|
|
603
|
+
}
|
|
604
|
+
if (!runDet(accepts[i]!, [])) continue;
|
|
605
|
+
}
|
|
606
|
+
yield* go(i + 1, candidate);
|
|
607
|
+
}
|
|
597
608
|
}
|
|
598
609
|
yield* go(0, {});
|
|
599
610
|
}
|
|
600
611
|
|
|
612
|
+
/** A simple editable fallback, independent of the parameter's declared domain. */
|
|
613
|
+
function defaultValue(t: Type): V {
|
|
614
|
+
switch (t.k) {
|
|
615
|
+
case 'nat': case 'int': case 'node': return 0;
|
|
616
|
+
case 'bool': case 'frame': return false;
|
|
617
|
+
case 'enum': return t.ctors[0];
|
|
618
|
+
case 'move': return 'stay';
|
|
619
|
+
case 'verdict': return 'inProgress';
|
|
620
|
+
case 'string': return '';
|
|
621
|
+
case 'list': case 'multiset': return [];
|
|
622
|
+
case 'record': return Object.fromEntries(t.fields.map(f => [f.name, defaultValue(f.type)]));
|
|
623
|
+
default: return null;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** Small candidate sets for predicate-only domains; this is not an enumeration of them. */
|
|
628
|
+
function* defaultCandidates(t: Type, nodeCount?: number): Generator<V> {
|
|
629
|
+
switch (t.k) {
|
|
630
|
+
case 'nat': case 'node':
|
|
631
|
+
for (let n = 0; n < Math.min(64, t.k === 'node' ? nodeCount ?? 0 : 64); n++) yield n;
|
|
632
|
+
return;
|
|
633
|
+
case 'int':
|
|
634
|
+
yield 0;
|
|
635
|
+
for (let n = 1; n < 64; n++) { yield n; yield -n; }
|
|
636
|
+
return;
|
|
637
|
+
case 'bool': case 'frame': yield false; yield true; return;
|
|
638
|
+
case 'enum': yield* t.ctors; return;
|
|
639
|
+
default: yield defaultValue(t);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Simulator starting values, separate from model semantics and check-all.
|
|
645
|
+
* Search a bounded prefix for a matching setting, honoring project overrides.
|
|
646
|
+
* If no complete setting is found, choose each parameter in order, falling back
|
|
647
|
+
* to an editable value of its type where matching is too difficult or impossible.
|
|
648
|
+
*/
|
|
649
|
+
export function parameterDefaults(exp: Experiment, overrides: Record<string, V> = {}): Record<string, V> {
|
|
650
|
+
const checked = check(exp);
|
|
651
|
+
if (checked.errors.length) throw new IRError(checked.errors);
|
|
652
|
+
const rt = new Runtime(checked.layout);
|
|
653
|
+
const compiler = new Compiler(rt, checked.layout, new Map(exp.params.map(p => [p.name, p.type])));
|
|
654
|
+
const defs = new Map((exp.model.defs ?? []).map(d => [d.name, d]));
|
|
655
|
+
const size = compiler.root(exp.model.topology.size, [], defs);
|
|
656
|
+
const domains = exp.params.map(p => p.domain && compiler.root(p.domain, [], defs));
|
|
657
|
+
const accepts = exp.params.map(p => p.accepts && compiler.root(p.accepts, [], defs));
|
|
658
|
+
const context = (params: Record<string, V>) => {
|
|
659
|
+
rt.params = params;
|
|
660
|
+
rt.topo = undefined as unknown as TopologyRuntime;
|
|
661
|
+
try { rt.topo = makeTopology(exp, runDet(size, [])); } catch {
|
|
662
|
+
// A positive topology size may not have been selected yet.
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
function* candidates(i: number, params: Record<string, V>): Generator<V> {
|
|
666
|
+
const p = exp.params[i];
|
|
667
|
+
if (p.name in overrides) { yield overrides[p.name]; return; }
|
|
668
|
+
context(params);
|
|
669
|
+
try {
|
|
670
|
+
yield* domains[i] ? runDet(domains[i]!, []) as V[] : defaultCandidates(p.type, rt.topo?.size);
|
|
671
|
+
} catch {
|
|
672
|
+
// An incomplete setting may not yet support a topology-dependent domain.
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const matches = (i: number, params: Record<string, V>) => {
|
|
676
|
+
if (exp.params[i].name in overrides || !accepts[i]) return true;
|
|
677
|
+
context(params);
|
|
678
|
+
try { return !!runDet(accepts[i]!, []); } catch { return false; }
|
|
679
|
+
};
|
|
680
|
+
let remaining = 1024;
|
|
681
|
+
const search = (i: number, params: Record<string, V>): Record<string, V> | undefined => {
|
|
682
|
+
if (i === exp.params.length) return params;
|
|
683
|
+
for (const value of candidates(i, params)) {
|
|
684
|
+
if (--remaining < 0) break;
|
|
685
|
+
const next = { ...params, [exp.params[i].name]: value };
|
|
686
|
+
if (!matches(i, next)) continue;
|
|
687
|
+
const found = search(i + 1, next);
|
|
688
|
+
if (found) return found;
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
const found = search(0, {});
|
|
692
|
+
if (found) return found;
|
|
693
|
+
const fallback: Record<string, V> = {};
|
|
694
|
+
for (const [i, p] of exp.params.entries()) {
|
|
695
|
+
fallback[p.name] = p.name in overrides ? overrides[p.name] : defaultValue(p.type);
|
|
696
|
+
let tried = 0;
|
|
697
|
+
for (const value of candidates(i, fallback)) {
|
|
698
|
+
if (++tried > 128) break;
|
|
699
|
+
const next = { ...fallback, [p.name]: value };
|
|
700
|
+
if (matches(i, next)) { fallback[p.name] = value; break; }
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return fallback;
|
|
704
|
+
}
|
|
705
|
+
|
|
601
706
|
/** Parameters outside their declared domains, without enumerating the settings product. */
|
|
602
707
|
export function parameterDomainViolations(exp: Experiment, params: Record<string, V>): string[] {
|
|
603
708
|
const checked = check(exp);
|
|
@@ -608,6 +713,7 @@ export function parameterDomainViolations(exp: Experiment, params: Record<string
|
|
|
608
713
|
const defs = new Map((exp.model.defs ?? []).map(d => [d.name, d]));
|
|
609
714
|
rt.topo = makeTopology(exp, runDet(compiler.root(exp.model.topology.size, [], defs), []));
|
|
610
715
|
return exp.params.filter(p => {
|
|
716
|
+
if (p.accepts && !runDet(compiler.root(p.accepts, [], defs), [])) return true;
|
|
611
717
|
// Parameters without a domain are supplied by the user and impose no domain restriction.
|
|
612
718
|
if (!p.domain) return false;
|
|
613
719
|
const values = runDet(compiler.root(p.domain, [], defs), []) as V[];
|