@bramas/simulant-engine 0.1.0 → 0.1.1

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/explorer.js CHANGED
@@ -72,7 +72,7 @@ export class Explorer {
72
72
  this.concreteInitial.set(id, { config: s.config, index });
73
73
  }
74
74
  });
75
- const ssync = system.exp.model.round === 'ssync';
75
+ const fairScheduler = system.exp.model.round !== 'fsync';
76
76
  for (let cursor = 0; cursor < this.states.length; cursor++) {
77
77
  if ((cursor & 255) === 255)
78
78
  yield;
@@ -101,16 +101,16 @@ export class Explorer {
101
101
  succ.push(id);
102
102
  act.push(new Set(t.activated));
103
103
  }
104
- else if (ssync)
104
+ else if (fairScheduler)
105
105
  for (const i of t.activated)
106
106
  act[at].add(i);
107
107
  }
108
108
  s.succ = succ;
109
- if (ssync)
109
+ if (fairScheduler)
110
110
  s.act = act;
111
111
  this.edges += succ.length;
112
112
  }
113
- const lasso = this.goal === 'safety' ? undefined : this.findLasso(ssync);
113
+ const lasso = this.goal === 'safety' ? undefined : this.findLasso(fairScheduler);
114
114
  if (lasso)
115
115
  return this.result('failure', `an execution never satisfies "${this.goal}"`, lasso);
116
116
  if (this.incomplete)
@@ -135,7 +135,7 @@ export class Explorer {
135
135
  return false;
136
136
  return this.goal === 'eventually' ? s.verdict !== 'success' : true;
137
137
  }
138
- findLasso(ssync) {
138
+ findLasso(fairScheduler) {
139
139
  for (const scc of this.sccs()) {
140
140
  const members = new Set(scc);
141
141
  const inside = (u) => this.states[u].succ.map((v, j) => [v, j]).filter(([v]) => members.has(v));
@@ -148,7 +148,7 @@ export class Explorer {
148
148
  continue;
149
149
  // Scheduler fairness: every acting agent is activated on some edge of the component.
150
150
  const needed = [];
151
- if (ssync) {
151
+ if (fairScheduler) {
152
152
  const C = this.states[scc[0]].config;
153
153
  const acting = C.agents.flatMap((a, i) => this.system.rt.acts(a) === 'never' ? [] : [i]);
154
154
  let fair = true;
package/dist/system.js CHANGED
@@ -7,7 +7,7 @@ import { check } from "@bramas/simulant-lang/check";
7
7
  import { T } from "@bramas/simulant-lang/types";
8
8
  import { Compiler, Runtime, } from "./compile.js";
9
9
  import { DynamicRing, Ring } from "./topology.js";
10
- import { encoder, show } from "./values.js";
10
+ import { encoder, equality, show } from "./values.js";
11
11
  export class IRError extends Error {
12
12
  errors;
13
13
  constructor(errors) {
@@ -387,14 +387,15 @@ export class System {
387
387
  }
388
388
  return agents === C.agents && world === C.world ? C : { agents, world };
389
389
  }
390
- /** FSYNC activates every agent that acts; SSYNC lets the adversary choose a nonempty set of them. */
390
+ /** FSYNC: all eligible agents; SSYNC: any nonempty subset; sequential: any singleton. */
391
391
  activate(C, k) {
392
392
  const eligible = C.agents.flatMap((a, i) => this.rt.acts(a) === 'never' ? [] : [i]);
393
393
  if (this.exp.model.round === 'fsync' || eligible.length === 0)
394
394
  return k(eligible);
395
395
  const w = this.rt.witness;
396
396
  let index = 0;
397
- for (let size = 1; size <= eligible.length; size++) {
397
+ const maxSize = this.exp.model.round === 'sequential' ? 1 : eligible.length;
398
+ for (let size = 1; size <= maxSize; size++) {
398
399
  const pick = (from, current) => {
399
400
  if (current.length === size) {
400
401
  w.push({ site: 'activation', index: index++, label: `activates ${current.map(i => `a${i}`).join(', ')}` });
@@ -560,6 +561,25 @@ export function* parameterValues(exp) {
560
561
  }
561
562
  yield* go(0, {});
562
563
  }
564
+ /** Parameters outside their declared domains, without enumerating the settings product. */
565
+ export function parameterDomainViolations(exp, params) {
566
+ const checked = check(exp);
567
+ if (checked.errors.length)
568
+ throw new IRError(checked.errors);
569
+ const rt = new Runtime(checked.layout);
570
+ rt.params = params;
571
+ const compiler = new Compiler(rt, checked.layout, new Map(exp.params.map(p => [p.name, p.type])));
572
+ const defs = new Map((exp.model.defs ?? []).map(d => [d.name, d]));
573
+ rt.topo = makeTopology(exp, runDet(compiler.root(exp.model.topology.size, [], defs), []));
574
+ return exp.params.filter(p => {
575
+ // Parameters without a domain are supplied by the user and impose no domain restriction.
576
+ if (!p.domain)
577
+ return false;
578
+ const values = runDet(compiler.root(p.domain, [], defs), []);
579
+ const equal = equality(p.type);
580
+ return !values.some(value => equal(value, params[p.name]));
581
+ }).map(p => p.name);
582
+ }
563
583
  const definitionCache = new WeakMap();
564
584
  /**
565
585
  * A definition of an experiment (`def f (x : T) : R := e` in `.sim`), as a function on values:
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@bramas/simulant-engine",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "license": "CECILL-B",
6
6
  "type": "module",
7
7
  "dependencies": {
8
- "@bramas/simulant-lang": "0.1.0"
8
+ "@bramas/simulant-lang": "0.1.1"
9
9
  },
10
10
  "exports": {
11
11
  "./*": {
package/src/explorer.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * A reachable `failure` verdict is a counterexample. Liveness goals are
5
5
  * decided on the expanded part of the graph with strongly connected
6
- * components: under a synchronous round every cycle counts; under SSYNC a
6
+ * components: under a synchronous round every cycle counts; under a fair scheduler a
7
7
  * cycle counts only if it is scheduler-fair, i.e. every agent that acts in
8
8
  * it is activated somewhere on it. Flags are never cleared, so which agents
9
9
  * act is the same in every state of a component.
@@ -38,7 +38,7 @@ export type State = {
38
38
  parent?: number;
39
39
  /** Successor ids; undefined while the state is not expanded. */
40
40
  succ?: number[];
41
- /** Per successor edge, the agents some witness activates (SSYNC only). */
41
+ /** Per successor edge, the agents some witness activates (fair schedulers only). */
42
42
  act?: Set<number>[];
43
43
  };
44
44
 
@@ -133,7 +133,7 @@ export class Explorer {
133
133
  this.concreteInitial.set(id, { config: s.config, index });
134
134
  }
135
135
  });
136
- const ssync = system.exp.model.round === 'ssync';
136
+ const fairScheduler = system.exp.model.round !== 'fsync';
137
137
  for (let cursor = 0; cursor < this.states.length; cursor++) {
138
138
  if ((cursor & 255) === 255) yield;
139
139
  const s = this.states[cursor];
@@ -152,13 +152,13 @@ export class Explorer {
152
152
  if (at === undefined) {
153
153
  successorIndex.set(id, succ.length);
154
154
  succ.push(id); act.push(new Set(t.activated));
155
- } else if (ssync) for (const i of t.activated) act[at].add(i);
155
+ } else if (fairScheduler) for (const i of t.activated) act[at].add(i);
156
156
  }
157
157
  s.succ = succ;
158
- if (ssync) s.act = act;
158
+ if (fairScheduler) s.act = act;
159
159
  this.edges += succ.length;
160
160
  }
161
- const lasso = this.goal === 'safety' ? undefined : this.findLasso(ssync);
161
+ const lasso = this.goal === 'safety' ? undefined : this.findLasso(fairScheduler);
162
162
  if (lasso) return this.result('failure', `an execution never satisfies "${this.goal}"`, lasso);
163
163
  if (this.incomplete) return this.result('incomplete', this.incomplete);
164
164
  return this.result('success', 'every admissible execution satisfies the task');
@@ -183,7 +183,7 @@ export class Explorer {
183
183
  return this.goal === 'eventually' ? s.verdict !== 'success' : true;
184
184
  }
185
185
 
186
- private findLasso(ssync: boolean): Counterexample | undefined {
186
+ private findLasso(fairScheduler: boolean): Counterexample | undefined {
187
187
  for (const scc of this.sccs()) {
188
188
  const members = new Set(scc);
189
189
  const inside = (u: number) => this.states[u].succ!.map((v, j) => [v, j] as const).filter(([v]) => members.has(v));
@@ -194,7 +194,7 @@ export class Explorer {
194
194
  if (bad === undefined) continue;
195
195
  // Scheduler fairness: every acting agent is activated on some edge of the component.
196
196
  const needed: [number, number][] = [];
197
- if (ssync) {
197
+ if (fairScheduler) {
198
198
  const C = this.states[scc[0]].config;
199
199
  const acting = C.agents.flatMap((a, i) => this.system.rt.acts(a) === 'never' ? [] : [i]);
200
200
  let fair = true;
package/src/system.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  Compiler, Runtime, type AgentV, type Choice, type Config, type DecisionV, type Effect, type RootFn, type TraceCall,
12
12
  } from './compile';
13
13
  import { DynamicRing, Ring, type Move, type TopologyRuntime } from './topology';
14
- import { encoder, show, type V } from './values';
14
+ import { encoder, equality, show, type V } from './values';
15
15
 
16
16
  export type { AgentV, Choice, Config, DecisionV, TraceCall };
17
17
 
@@ -428,13 +428,14 @@ export class System {
428
428
  return agents === C.agents && world === C.world ? C : { agents, world };
429
429
  }
430
430
 
431
- /** FSYNC activates every agent that acts; SSYNC lets the adversary choose a nonempty set of them. */
431
+ /** FSYNC: all eligible agents; SSYNC: any nonempty subset; sequential: any singleton. */
432
432
  private activate(C: Config, k: (active: number[]) => void) {
433
433
  const eligible = C.agents.flatMap((a, i) => this.rt.acts(a) === 'never' ? [] : [i]);
434
434
  if (this.exp.model.round === 'fsync' || eligible.length === 0) return k(eligible);
435
435
  const w = this.rt.witness;
436
436
  let index = 0;
437
- for (let size = 1; size <= eligible.length; size++) {
437
+ const maxSize = this.exp.model.round === 'sequential' ? 1 : eligible.length;
438
+ for (let size = 1; size <= maxSize; size++) {
438
439
  const pick = (from: number, current: number[]) => {
439
440
  if (current.length === size) {
440
441
  w.push({ site: 'activation', index: index++, label: `activates ${current.map(i => `a${i}`).join(', ')}` });
@@ -597,6 +598,24 @@ export function* parameterValues(exp: Experiment): Generator<Record<string, V>>
597
598
  yield* go(0, {});
598
599
  }
599
600
 
601
+ /** Parameters outside their declared domains, without enumerating the settings product. */
602
+ export function parameterDomainViolations(exp: Experiment, params: Record<string, V>): string[] {
603
+ const checked = check(exp);
604
+ if (checked.errors.length) throw new IRError(checked.errors);
605
+ const rt = new Runtime(checked.layout);
606
+ rt.params = params;
607
+ const compiler = new Compiler(rt, checked.layout, new Map(exp.params.map(p => [p.name, p.type])));
608
+ const defs = new Map((exp.model.defs ?? []).map(d => [d.name, d]));
609
+ rt.topo = makeTopology(exp, runDet(compiler.root(exp.model.topology.size, [], defs), []));
610
+ return exp.params.filter(p => {
611
+ // Parameters without a domain are supplied by the user and impose no domain restriction.
612
+ if (!p.domain) return false;
613
+ const values = runDet(compiler.root(p.domain, [], defs), []) as V[];
614
+ const equal = equality(p.type);
615
+ return !values.some(value => equal(value, params[p.name]));
616
+ }).map(p => p.name);
617
+ }
618
+
600
619
  const definitionCache = new WeakMap<Experiment, Map<string, (...args: V[]) => V>>();
601
620
 
602
621
  /**