@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 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
- yield* go(i + 1, { ...params, [exp.params[i].name]: v });
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.2.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.2.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[]) yield* go(i + 1, { ...params, [exp.params[i].name]: 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[];