@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 +6 -6
- package/dist/system.js +23 -3
- package/package.json +2 -2
- package/src/explorer.ts +8 -8
- package/src/system.ts +22 -3
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
|
|
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 (
|
|
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 (
|
|
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(
|
|
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(
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
155
|
+
} else if (fairScheduler) for (const i of t.activated) act[at].add(i);
|
|
156
156
|
}
|
|
157
157
|
s.succ = succ;
|
|
158
|
-
if (
|
|
158
|
+
if (fairScheduler) s.act = act;
|
|
159
159
|
this.edges += succ.length;
|
|
160
160
|
}
|
|
161
|
-
const lasso = this.goal === 'safety' ? undefined : this.findLasso(
|
|
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(
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
/**
|