@classytic/stage 0.1.0 → 0.2.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/README.md +2 -2
- package/dist/assets/kit/glyphs.d.mts +5 -5
- package/dist/assets/kit/glyphs.mjs +4 -4
- package/dist/builder/SceneBuilder.mjs +4 -4
- package/dist/builder/editor.d.mts +2 -1
- package/dist/builder/editor.mjs +14 -4
- package/dist/builder/tools.d.mts +1 -1
- package/dist/builder/tools.mjs +1 -1
- package/dist/chem/index.d.mts +5 -5
- package/dist/chem/index.mjs +4 -4
- package/dist/circuit/index.d.mts +92 -0
- package/dist/circuit/index.mjs +333 -0
- package/dist/core/clock.d.mts +1 -1
- package/dist/core/clock.mjs +3 -3
- package/dist/core/control.d.mts +1 -1
- package/dist/core/control.mjs +2 -2
- package/dist/core/coords.d.mts +4 -4
- package/dist/core/coords.mjs +1 -1
- package/dist/core/learner.d.mts +1 -1
- package/dist/core/learner.mjs +1 -1
- package/dist/core/motion.d.mts +6 -6
- package/dist/core/motion.mjs +6 -6
- package/dist/core/richText.d.mts +2 -2
- package/dist/core/vec.d.mts +1 -1
- package/dist/field/index.d.mts +11 -3
- package/dist/field/index.mjs +20 -3
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +3 -1
- package/dist/interaction/MovableDot.d.mts +17 -1
- package/dist/interaction/MovableDot.mjs +141 -43
- package/dist/interaction/index.d.mts +2 -1
- package/dist/interaction/index.mjs +2 -1
- package/dist/interaction/useDraggable.d.mts +1 -1
- package/dist/interaction/useDraggable.mjs +31 -1
- package/dist/interaction/usePressSpring.d.mts +24 -0
- package/dist/interaction/usePressSpring.mjs +67 -0
- package/dist/logic/ast.d.mts +1 -1
- package/dist/logic/index.mjs +8 -3
- package/dist/logic/minimize.d.mts +2 -2
- package/dist/logic/minimize.mjs +56 -18
- package/dist/logic/table.mjs +1 -1
- package/dist/math/ast.d.mts +1 -1
- package/dist/math/ast.mjs +1 -1
- package/dist/math/calculus.mjs +68 -9
- package/dist/math/compile.d.mts +8 -0
- package/dist/math/compile.mjs +40 -0
- package/dist/math/defs.mjs +1 -1
- package/dist/math/index.d.mts +4 -3
- package/dist/math/index.mjs +4 -3
- package/dist/math/parse.mjs +2 -2
- package/dist/math/tokenize.mjs +1 -1
- package/dist/primitives/CanvasLayer.mjs +3 -3
- package/dist/primitives/Grid.d.mts +12 -1
- package/dist/primitives/Grid.mjs +15 -9
- package/dist/primitives/Label.d.mts +1 -1
- package/dist/primitives/Label.mjs +1 -1
- package/dist/primitives/Tex.mjs +2 -2
- package/dist/scene/Scene.mjs +1 -1
- package/dist/scene/assets.d.mts +2 -2
- package/dist/scene/assets.mjs +2 -2
- package/dist/scene/commands.d.mts +10 -1
- package/dist/scene/commands.mjs +42 -2
- package/dist/scene/evaluators.mjs +2 -2
- package/dist/scene/index.d.mts +2 -2
- package/dist/scene/index.mjs +2 -2
- package/dist/scene/migrate.mjs +1 -1
- package/dist/scene/sims.mjs +1 -1
- package/dist/scene/types.d.mts +4 -4
- package/dist/sim/particles.d.mts +2 -2
- package/dist/sim/rate.d.mts +4 -4
- package/dist/sim/rate.mjs +1 -1
- package/dist/sim/registry.d.mts +1 -1
- package/dist/sim/sampler.d.mts +5 -5
- package/dist/sim/thermal.d.mts +3 -3
- package/dist/sim/thermal.mjs +2 -2
- package/dist/sim/types.d.mts +2 -2
- package/dist/sim/wave.d.mts +2 -2
- package/dist/sim/wave.mjs +2 -2
- package/dist/steps/index.d.mts +1 -1
- package/dist/steps/index.mjs +3 -3
- package/dist/thermo/index.d.mts +1 -1
- package/dist/thermo/index.mjs +1 -1
- package/dist/view/Stage.mjs +2 -2
- package/dist/view/useInView.mjs +1 -1
- package/package.json +8 -4
- package/styles.css +23 -0
package/dist/index.d.mts
CHANGED
|
@@ -32,6 +32,7 @@ import { CanvasLayer, CanvasLayerProps } from "./primitives/CanvasLayer.mjs";
|
|
|
32
32
|
import { useScreenToMath } from "./interaction/useScreenToMath.mjs";
|
|
33
33
|
import { AriaSliderProps, Constrain, DraggableHandlers, UseDraggable, UseDraggableArgs, useDraggable } from "./interaction/useDraggable.mjs";
|
|
34
34
|
import { MovableDot, MovableDotProps } from "./interaction/MovableDot.mjs";
|
|
35
|
+
import { PressSpringOpts, useImpulse, usePressSpring } from "./interaction/usePressSpring.mjs";
|
|
35
36
|
import { HitCandidate, hitTest } from "./interaction/hitTest.mjs";
|
|
36
37
|
import { OP_REFS, isRef, parentIds } from "./scene/schema.mjs";
|
|
37
38
|
import { EVALUATORS, EvalCtx } from "./scene/evaluators.mjs";
|
|
@@ -42,6 +43,7 @@ import { RenderOpts, renderElements } from "./scene/render.mjs";
|
|
|
42
43
|
import { BinOp, Node, compileNode, evaluate, freeVars } from "./math/ast.mjs";
|
|
43
44
|
import { parse as parse$1 } from "./math/parse.mjs";
|
|
44
45
|
import { differentiate, simplify } from "./math/calculus.mjs";
|
|
46
|
+
import { CompiledFn, compile } from "./math/compile.mjs";
|
|
45
47
|
import { toLatex as toLatex$1 } from "./math/latex.mjs";
|
|
46
48
|
import { CompiledExpr, ExprError, ExprResult, compileExpr } from "./math/index.mjs";
|
|
47
49
|
import { BinOp as BinOp$1, LNode, evalBool, freeVars as freeVars$1 } from "./logic/ast.mjs";
|
|
@@ -51,4 +53,4 @@ import { toLatex } from "./logic/latex.mjs";
|
|
|
51
53
|
import { Cube, Minimization, cubeCovers, cubeOfSelection, cubeTerm, minimalCover, minimize, primeImplicants } from "./logic/minimize.mjs";
|
|
52
54
|
import { CompiledLogic, LogicResult, compileLogic } from "./logic/index.mjs";
|
|
53
55
|
import { AssetResolveArgs, AssetSpec, assetMeta, getAsset, listAssets, registerAsset } from "./scene/assets.mjs";
|
|
54
|
-
export { type A11yProps, AUTHORING_TOOLS, type AriaSliderProps, type AssetGeometry, type AssetResolveArgs, type AssetSpec, Axes, type AxesProps, type BinOp, type Binding, BucketGlyph, CIRCLE, CURRENT_SCHEMA_VERSION, CanvasLayer, type CanvasLayerProps, Circle, type CircleProps, type CircleVal, type Classification, ClockProvider, type ClockProviderProps, type Command, type CommandResult, type CompiledExpr, type CompiledLogic, type Constrain, type ControlMap, type ControlPick, type ControlSpec, type ControlSurface, type CoordinateSystem, CoordsContext, type CoordsOptions, type Cube, DELETE, type DerivedDef, type DerivedElement, type DerivedKind, type DispatchOpts, Dot, type DotProps, DragOverlayContext, type DraggableHandlers, EVALUATORS, Editor, type ElementBase, type ElementStyle, Ellipse, type EllipseProps, type EvalCtx, type ExprError, type ExprResult, type FrameDriver, type FrameInfo, type FreeElement, type FreeNote, type FreePoint, type FreeScalar, GEOMETRY_TOOLS, type GlyphBox, Grid, type GridProps, HangerHook, type HangerHookProps, type HitCandidate, INTERSECT, type IconName, type Id, type InstanceState, LINE, type LNode, type LabMeta, Label, type LabelProps, type Learner, LearnerProvider, type LearnerResult, Line, type LineProps, type BinOp$1 as LogicBinOp, type LogicResult, MEASURE, MIDPOINT, type Matrix, type Minimization, MovableDot, type MovableDotProps, type MutatePatch, NOTE, type Node, type NumOrRef, OP_REFS, type OfXProps, type OfYProps, type Op, POINT, Palette, type PaletteProps, type ParametricProps, Path, type PathProps, Plot, Point, Polygon, type PolygonProps, Polyline, type PolylineProps, type PriorAttempts, Ray, type Ref, type RenderOpts, type Resolved, type RichSpan, SCALAR, SEGMENT, SELECT, Scene, SceneBuilder, type SceneBuilderProps, type SceneDoc, type SceneElement, type SceneMeta, type SceneProps, SceneStore, type Vec2 as SceneVec2, type Script, Segment, type SegmentProps, type ShapeLineVal, type Size, Stage, StageAssetDefs, type StageProps, StageRefContext, type StepProgress, StepProgressProvider, StepScope, type StepSpec, type StepState, type StyleProps, Tex, type TexProps, type Tool, type ToolCtx, ToolIcon, type TruthRow, type TruthTable, type UseDraggable, type UseDraggableArgs, type Val, type Vec2, Vector, type VectorProps, type ViewBox, WeightGlyph, XBlockGlyph, applyCommand, assetMeta, classify, collectRemoved, compileExpr, compileLogic, compileNode, controlsFromScene, createCoords, cubeCovers, cubeOfSelection, cubeTerm, differentiate, emptyDoc, equivalent, evalBool, evaluate, fmt, freeVars, getAsset, getControlSurface, hitTest, inverse, isAssetGeom, isCircleVal, isDerived, isFree, isLineVal, isRef, isVec2, listAssets, listControlSurfaces, freeVars$1 as logicFreeVars, toLatex as logicToLatex, mat, migrate, minimalCover, minimize, niceStep, numOf, onControlChange, parentIds, parse, parse$1 as parseExpr, parseLogic, parseRichText, primeImplicants, registerAsset, renderElements, resolve, serialize, simplify, toCNF, toDNF, toLatex$1 as toLatex, truthTable, useControlSurface, useCoords, useDragOverlay, useDraggable, useEditor, useElementSize, useFrameLoop, useInView, useLearner, useScreenToMath, useStageRef, useStepProgress, vec };
|
|
56
|
+
export { type A11yProps, AUTHORING_TOOLS, type AriaSliderProps, type AssetGeometry, type AssetResolveArgs, type AssetSpec, Axes, type AxesProps, type BinOp, type Binding, BucketGlyph, CIRCLE, CURRENT_SCHEMA_VERSION, CanvasLayer, type CanvasLayerProps, Circle, type CircleProps, type CircleVal, type Classification, ClockProvider, type ClockProviderProps, type Command, type CommandResult, type CompiledExpr, type CompiledFn, type CompiledLogic, type Constrain, type ControlMap, type ControlPick, type ControlSpec, type ControlSurface, type CoordinateSystem, CoordsContext, type CoordsOptions, type Cube, DELETE, type DerivedDef, type DerivedElement, type DerivedKind, type DispatchOpts, Dot, type DotProps, DragOverlayContext, type DraggableHandlers, EVALUATORS, Editor, type ElementBase, type ElementStyle, Ellipse, type EllipseProps, type EvalCtx, type ExprError, type ExprResult, type FrameDriver, type FrameInfo, type FreeElement, type FreeNote, type FreePoint, type FreeScalar, GEOMETRY_TOOLS, type GlyphBox, Grid, type GridProps, HangerHook, type HangerHookProps, type HitCandidate, INTERSECT, type IconName, type Id, type InstanceState, LINE, type LNode, type LabMeta, Label, type LabelProps, type Learner, LearnerProvider, type LearnerResult, Line, type LineProps, type BinOp$1 as LogicBinOp, type LogicResult, MEASURE, MIDPOINT, type Matrix, type Minimization, MovableDot, type MovableDotProps, type MutatePatch, NOTE, type Node, type NumOrRef, OP_REFS, type OfXProps, type OfYProps, type Op, POINT, Palette, type PaletteProps, type ParametricProps, Path, type PathProps, Plot, Point, Polygon, type PolygonProps, Polyline, type PolylineProps, type PressSpringOpts, type PriorAttempts, Ray, type Ref, type RenderOpts, type Resolved, type RichSpan, SCALAR, SEGMENT, SELECT, Scene, SceneBuilder, type SceneBuilderProps, type SceneDoc, type SceneElement, type SceneMeta, type SceneProps, SceneStore, type Vec2 as SceneVec2, type Script, Segment, type SegmentProps, type ShapeLineVal, type Size, Stage, StageAssetDefs, type StageProps, StageRefContext, type StepProgress, StepProgressProvider, StepScope, type StepSpec, type StepState, type StyleProps, Tex, type TexProps, type Tool, type ToolCtx, ToolIcon, type TruthRow, type TruthTable, type UseDraggable, type UseDraggableArgs, type Val, type Vec2, Vector, type VectorProps, type ViewBox, WeightGlyph, XBlockGlyph, applyCommand, assetMeta, classify, collectRemoved, compile, compileExpr, compileLogic, compileNode, controlsFromScene, createCoords, cubeCovers, cubeOfSelection, cubeTerm, differentiate, emptyDoc, equivalent, evalBool, evaluate, fmt, freeVars, getAsset, getControlSurface, hitTest, inverse, isAssetGeom, isCircleVal, isDerived, isFree, isLineVal, isRef, isVec2, listAssets, listControlSurfaces, freeVars$1 as logicFreeVars, toLatex as logicToLatex, mat, migrate, minimalCover, minimize, niceStep, numOf, onControlChange, parentIds, parse, parse$1 as parseExpr, parseLogic, parseRichText, primeImplicants, registerAsset, renderElements, resolve, serialize, simplify, toCNF, toDNF, toLatex$1 as toLatex, truthTable, useControlSurface, useCoords, useDragOverlay, useDraggable, useEditor, useElementSize, useFrameLoop, useImpulse, useInView, useLearner, usePressSpring, useScreenToMath, useStageRef, useStepProgress, vec };
|
package/dist/index.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import { Plot } from "./primitives/Plot.mjs";
|
|
|
19
19
|
import { CanvasLayer } from "./primitives/CanvasLayer.mjs";
|
|
20
20
|
import { useScreenToMath } from "./interaction/useScreenToMath.mjs";
|
|
21
21
|
import { useDraggable } from "./interaction/useDraggable.mjs";
|
|
22
|
+
import { useImpulse, usePressSpring } from "./interaction/usePressSpring.mjs";
|
|
22
23
|
import { MovableDot } from "./interaction/MovableDot.mjs";
|
|
23
24
|
import { hitTest } from "./interaction/hitTest.mjs";
|
|
24
25
|
import { isAssetGeom, isCircleVal, isDerived, isFree, isLineVal, isVec2, numOf } from "./scene/types.mjs";
|
|
@@ -27,6 +28,7 @@ import { assetMeta, getAsset, listAssets, registerAsset } from "./scene/assets.m
|
|
|
27
28
|
import { compileNode, evaluate, freeVars } from "./math/ast.mjs";
|
|
28
29
|
import { parse as parse$1 } from "./math/parse.mjs";
|
|
29
30
|
import { differentiate, simplify } from "./math/calculus.mjs";
|
|
31
|
+
import { compile } from "./math/compile.mjs";
|
|
30
32
|
import { toLatex as toLatex$1 } from "./math/latex.mjs";
|
|
31
33
|
import { compileExpr } from "./math/index.mjs";
|
|
32
34
|
import { EVALUATORS } from "./scene/evaluators.mjs";
|
|
@@ -52,4 +54,4 @@ import { toLatex } from "./logic/latex.mjs";
|
|
|
52
54
|
import { cubeCovers, cubeOfSelection, cubeTerm, minimalCover, minimize, primeImplicants } from "./logic/minimize.mjs";
|
|
53
55
|
import { compileLogic } from "./logic/index.mjs";
|
|
54
56
|
|
|
55
|
-
export { AUTHORING_TOOLS, Axes, BucketGlyph, CIRCLE, CURRENT_SCHEMA_VERSION, CanvasLayer, Circle, ClockProvider, CoordsContext, DELETE, Dot, DragOverlayContext, EVALUATORS, Editor, Ellipse, GEOMETRY_TOOLS, Grid, HangerHook, INTERSECT, LINE, Label, LearnerProvider, Line, MEASURE, MIDPOINT, MovableDot, NOTE, OP_REFS, POINT, Palette, Path, Plot, Point, Polygon, Polyline, Ray, SCALAR, SEGMENT, SELECT, Scene, SceneBuilder, SceneStore, Segment, Stage, StageAssetDefs, StageRefContext, StepProgressProvider, StepScope, Tex, ToolIcon, Vector, WeightGlyph, XBlockGlyph, applyCommand, assetMeta, classify, collectRemoved, compileExpr, compileLogic, compileNode, controlsFromScene, createCoords, cubeCovers, cubeOfSelection, cubeTerm, differentiate, emptyDoc, equivalent, evalBool, evaluate, fmt, freeVars, getAsset, getControlSurface, hitTest, inverse, isAssetGeom, isCircleVal, isDerived, isFree, isLineVal, isRef, isVec2, listAssets, listControlSurfaces, freeVars$1 as logicFreeVars, toLatex as logicToLatex, mat, migrate, minimalCover, minimize, niceStep, numOf, onControlChange, parentIds, parse, parse$1 as parseExpr, parseLogic, parseRichText, primeImplicants, registerAsset, renderElements, resolve, serialize, simplify, toCNF, toDNF, toLatex$1 as toLatex, truthTable, useControlSurface, useCoords, useDragOverlay, useDraggable, useEditor, useElementSize, useFrameLoop, useInView, useLearner, useScreenToMath, useStageRef, useStepProgress, vec };
|
|
57
|
+
export { AUTHORING_TOOLS, Axes, BucketGlyph, CIRCLE, CURRENT_SCHEMA_VERSION, CanvasLayer, Circle, ClockProvider, CoordsContext, DELETE, Dot, DragOverlayContext, EVALUATORS, Editor, Ellipse, GEOMETRY_TOOLS, Grid, HangerHook, INTERSECT, LINE, Label, LearnerProvider, Line, MEASURE, MIDPOINT, MovableDot, NOTE, OP_REFS, POINT, Palette, Path, Plot, Point, Polygon, Polyline, Ray, SCALAR, SEGMENT, SELECT, Scene, SceneBuilder, SceneStore, Segment, Stage, StageAssetDefs, StageRefContext, StepProgressProvider, StepScope, Tex, ToolIcon, Vector, WeightGlyph, XBlockGlyph, applyCommand, assetMeta, classify, collectRemoved, compile, compileExpr, compileLogic, compileNode, controlsFromScene, createCoords, cubeCovers, cubeOfSelection, cubeTerm, differentiate, emptyDoc, equivalent, evalBool, evaluate, fmt, freeVars, getAsset, getControlSurface, hitTest, inverse, isAssetGeom, isCircleVal, isDerived, isFree, isLineVal, isRef, isVec2, listAssets, listControlSurfaces, freeVars$1 as logicFreeVars, toLatex as logicToLatex, mat, migrate, minimalCover, minimize, niceStep, numOf, onControlChange, parentIds, parse, parse$1 as parseExpr, parseLogic, parseRichText, primeImplicants, registerAsset, renderElements, resolve, serialize, simplify, toCNF, toDNF, toLatex$1 as toLatex, truthTable, useControlSurface, useCoords, useDragOverlay, useDraggable, useEditor, useElementSize, useFrameLoop, useImpulse, useInView, useLearner, usePressSpring, useScreenToMath, useStageRef, useStepProgress, vec };
|
|
@@ -18,6 +18,18 @@ interface MovableDotProps {
|
|
|
18
18
|
/** Keyboard arrow-key nudge in math units. Match the lab's snap grid so a
|
|
19
19
|
* single arrow press actually moves (e.g. 1 for an integer number line). */
|
|
20
20
|
step?: number;
|
|
21
|
+
/** Math-unit snap grid. Tells the handle the lattice so it can preview the
|
|
22
|
+
* candidate landing spots (ghosts) and a snap-tick — purely visual; the lab
|
|
23
|
+
* still owns the actual snapping in its onMove. 0/undefined = no preview. */
|
|
24
|
+
snap?: number;
|
|
25
|
+
/** Faint candidate snap-stops shown while dragging (default: on when `snap`
|
|
26
|
+
* is set). Pass explicit points to drive non-grid lattices (e.g. stops along
|
|
27
|
+
* a ray); otherwise they're derived from `snap` + the view + `constrain`. */
|
|
28
|
+
ghosts?: boolean;
|
|
29
|
+
ghostPoints?: Vec2[];
|
|
30
|
+
/** Live value pill near the handle while dragging. true = default formatter,
|
|
31
|
+
* a fn for custom text, false to hide. Default true. */
|
|
32
|
+
readout?: boolean | ((v: Vec2) => string);
|
|
21
33
|
}
|
|
22
34
|
declare function MovableDot({
|
|
23
35
|
value,
|
|
@@ -27,7 +39,11 @@ declare function MovableDot({
|
|
|
27
39
|
r,
|
|
28
40
|
ariaLabel,
|
|
29
41
|
range,
|
|
30
|
-
step
|
|
42
|
+
step,
|
|
43
|
+
snap,
|
|
44
|
+
ghosts,
|
|
45
|
+
ghostPoints,
|
|
46
|
+
readout
|
|
31
47
|
}: MovableDotProps): ReactNode;
|
|
32
48
|
//#endregion
|
|
33
49
|
export { MovableDot, MovableDotProps };
|
|
@@ -2,12 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCoords } from "../core/context.mjs";
|
|
4
4
|
import { useDraggable } from "./useDraggable.mjs";
|
|
5
|
+
import { useImpulse, usePressSpring } from "./usePressSpring.mjs";
|
|
5
6
|
import { useState } from "react";
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
8
|
|
|
8
9
|
//#region src/interaction/MovableDot.tsx
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const ACCENT = "var(--stage-accent)";
|
|
11
|
+
function fnum(n) {
|
|
12
|
+
const r = Math.round(n * 100) / 100;
|
|
13
|
+
return Object.is(r, -0) ? "0" : String(r);
|
|
14
|
+
}
|
|
15
|
+
/** Candidate snap-stops within a small window around `value`, derived from the
|
|
16
|
+
* grid + which axes are free. Kept LOCAL (±3 steps) so the guide reads as
|
|
17
|
+
* "here are your nearby choices", not a wall of dots. */
|
|
18
|
+
function autoGhosts(value, snap, constrain, view, range) {
|
|
19
|
+
if (!(snap > 0)) return [];
|
|
20
|
+
const W = 3;
|
|
21
|
+
const stops = [];
|
|
22
|
+
const along = (axis) => {
|
|
23
|
+
const base = axis === "x" ? value.x : value.y;
|
|
24
|
+
const lo = axis === "x" ? range?.min ?? view.xMin : range?.min ?? view.yMin;
|
|
25
|
+
const hi = axis === "x" ? range?.max ?? view.xMax : range?.max ?? view.yMax;
|
|
26
|
+
for (let k = -3; k <= W; k++) {
|
|
27
|
+
if (k === 0) continue;
|
|
28
|
+
const v = Math.round(base / snap) * snap + k * snap;
|
|
29
|
+
if (v < lo - 1e-9 || v > hi + 1e-9) continue;
|
|
30
|
+
stops.push(axis === "x" ? {
|
|
31
|
+
x: v,
|
|
32
|
+
y: value.y
|
|
33
|
+
} : {
|
|
34
|
+
x: value.x,
|
|
35
|
+
y: v
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const oneD = constrain === "horizontal" || constrain === "vertical";
|
|
40
|
+
if (constrain === "horizontal") along("x");
|
|
41
|
+
else if (constrain === "vertical") along("y");
|
|
42
|
+
else if (!oneD && typeof constrain !== "function") {
|
|
43
|
+
along("x");
|
|
44
|
+
along("y");
|
|
45
|
+
}
|
|
46
|
+
return stops;
|
|
47
|
+
}
|
|
48
|
+
function MovableDot({ value, onMove, constrain, color = ACCENT, r = 7, ariaLabel = "draggable point", range, step, snap, ghosts, ghostPoints, readout = true }) {
|
|
49
|
+
const c = useCoords();
|
|
50
|
+
const [px, py] = c.toPx(value.x, value.y);
|
|
11
51
|
const { dragging, handlers, ariaProps } = useDraggable({
|
|
12
52
|
value,
|
|
13
53
|
onMove,
|
|
@@ -16,50 +56,108 @@ function MovableDot({ value, onMove, constrain, color = "var(--stage-accent)", r
|
|
|
16
56
|
...step !== void 0 ? { step } : {}
|
|
17
57
|
});
|
|
18
58
|
const [hovered, setHovered] = useState(false);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
})
|
|
59
|
+
const press = usePressSpring(dragging, {
|
|
60
|
+
to: 1,
|
|
61
|
+
stiffness: 360,
|
|
62
|
+
damping: 24
|
|
63
|
+
});
|
|
64
|
+
const dotR = r + (hovered && !dragging ? 1.5 : 0) + press * 3;
|
|
65
|
+
const tickRaw = useImpulse(`${Math.round(value.x * 1e4)}:${Math.round(value.y * 1e4)}`, 280);
|
|
66
|
+
const tick = dragging ? tickRaw : 0;
|
|
67
|
+
const stops = !(dragging && (ghosts ?? (snap != null && snap > 0))) ? [] : ghostPoints ?? autoGhosts(value, snap ?? 0, constrain, c.view, range);
|
|
68
|
+
const label = readout === false ? null : typeof readout === "function" ? readout(value) : constrain === "horizontal" || constrain === "vertical" ? fnum(constrain === "vertical" ? value.y : value.x) : `(${fnum(value.x)}, ${fnum(value.y)})`;
|
|
69
|
+
const pillW = label ? Math.max(30, label.length * 7.5 + 14) : 0;
|
|
70
|
+
const pillY = py - dotR - 30;
|
|
71
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
72
|
+
stops.length > 0 && /* @__PURE__ */ jsx("g", {
|
|
73
|
+
style: { pointerEvents: "none" },
|
|
74
|
+
children: stops.map((s, i) => {
|
|
75
|
+
const [gx, gy] = c.toPx(s.x, s.y);
|
|
76
|
+
const d = Math.hypot(gx - px, gy - py);
|
|
77
|
+
return /* @__PURE__ */ jsx("circle", {
|
|
78
|
+
cx: gx,
|
|
79
|
+
cy: gy,
|
|
80
|
+
r: 3,
|
|
81
|
+
fill: color,
|
|
82
|
+
opacity: Math.max(.12, .5 - d / 360)
|
|
83
|
+
}, i);
|
|
84
|
+
})
|
|
85
|
+
}),
|
|
86
|
+
/* @__PURE__ */ jsxs("g", {
|
|
87
|
+
...handlers,
|
|
88
|
+
...ariaProps,
|
|
89
|
+
"aria-label": ariaLabel,
|
|
90
|
+
onPointerEnter: () => setHovered(true),
|
|
91
|
+
onPointerLeave: () => setHovered(false),
|
|
92
|
+
style: {
|
|
93
|
+
cursor: dragging ? "grabbing" : "grab",
|
|
94
|
+
outline: "none"
|
|
95
|
+
},
|
|
96
|
+
children: [
|
|
97
|
+
/* @__PURE__ */ jsx("circle", {
|
|
98
|
+
cx: px,
|
|
99
|
+
cy: py,
|
|
100
|
+
r: Math.max(r + 14, 12),
|
|
101
|
+
fill: "transparent"
|
|
102
|
+
}),
|
|
103
|
+
dragging ? /* @__PURE__ */ jsx("circle", {
|
|
104
|
+
cx: px,
|
|
105
|
+
cy: py,
|
|
106
|
+
r: r + 6 + press * 4,
|
|
107
|
+
fill: color,
|
|
108
|
+
opacity: .18
|
|
109
|
+
}) : /* @__PURE__ */ jsx("circle", {
|
|
110
|
+
cx: px,
|
|
111
|
+
cy: py,
|
|
112
|
+
r: r + 5,
|
|
113
|
+
fill: "none",
|
|
114
|
+
stroke: color,
|
|
115
|
+
strokeWidth: 1.5,
|
|
116
|
+
opacity: hovered ? .55 : .28
|
|
117
|
+
}),
|
|
118
|
+
/* @__PURE__ */ jsx("circle", {
|
|
119
|
+
cx: px,
|
|
120
|
+
cy: py,
|
|
121
|
+
r: dotR,
|
|
122
|
+
fill: color,
|
|
123
|
+
stroke: "var(--stage-bg)",
|
|
124
|
+
strokeWidth: 2
|
|
125
|
+
})
|
|
126
|
+
]
|
|
127
|
+
}),
|
|
128
|
+
/* @__PURE__ */ jsxs("g", {
|
|
129
|
+
style: { pointerEvents: "none" },
|
|
130
|
+
children: [tick > 0 && /* @__PURE__ */ jsx("circle", {
|
|
45
131
|
cx: px,
|
|
46
132
|
cy: py,
|
|
47
|
-
r:
|
|
133
|
+
r: dotR + (1 - tick) * 12,
|
|
48
134
|
fill: "none",
|
|
49
135
|
stroke: color,
|
|
50
|
-
strokeWidth:
|
|
51
|
-
opacity:
|
|
52
|
-
}),
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
136
|
+
strokeWidth: 2,
|
|
137
|
+
opacity: tick * .6
|
|
138
|
+
}), dragging && label && /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
|
|
139
|
+
x: px - pillW / 2,
|
|
140
|
+
y: pillY,
|
|
141
|
+
width: pillW,
|
|
142
|
+
height: 22,
|
|
143
|
+
rx: 7,
|
|
144
|
+
fill: "var(--stage-bg)",
|
|
145
|
+
stroke: "var(--stage-grid)",
|
|
146
|
+
strokeWidth: 1,
|
|
147
|
+
opacity: .96
|
|
148
|
+
}), /* @__PURE__ */ jsx("text", {
|
|
149
|
+
x: px,
|
|
150
|
+
y: pillY + 11,
|
|
151
|
+
textAnchor: "middle",
|
|
152
|
+
dominantBaseline: "central",
|
|
153
|
+
fontSize: 12,
|
|
154
|
+
fontWeight: 700,
|
|
155
|
+
fill: "var(--stage-fg)",
|
|
156
|
+
style: { fontVariantNumeric: "tabular-nums" },
|
|
157
|
+
children: label
|
|
158
|
+
})] })]
|
|
159
|
+
})
|
|
160
|
+
] });
|
|
63
161
|
}
|
|
64
162
|
|
|
65
163
|
//#endregion
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useScreenToMath } from "./useScreenToMath.mjs";
|
|
2
2
|
import { AriaSliderProps, Constrain, DraggableHandlers, UseDraggable, UseDraggableArgs, useDraggable } from "./useDraggable.mjs";
|
|
3
3
|
import { MovableDot, MovableDotProps } from "./MovableDot.mjs";
|
|
4
|
+
import { PressSpringOpts, useImpulse, usePressSpring } from "./usePressSpring.mjs";
|
|
4
5
|
import { HitCandidate, hitTest } from "./hitTest.mjs";
|
|
5
|
-
export { type AriaSliderProps, type Constrain, type DraggableHandlers, type HitCandidate, MovableDot, type MovableDotProps, type UseDraggable, type UseDraggableArgs, hitTest, useDraggable, useScreenToMath };
|
|
6
|
+
export { type AriaSliderProps, type Constrain, type DraggableHandlers, type HitCandidate, MovableDot, type MovableDotProps, type PressSpringOpts, type UseDraggable, type UseDraggableArgs, hitTest, useDraggable, useImpulse, usePressSpring, useScreenToMath };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useScreenToMath } from "./useScreenToMath.mjs";
|
|
2
2
|
import { useDraggable } from "./useDraggable.mjs";
|
|
3
|
+
import { useImpulse, usePressSpring } from "./usePressSpring.mjs";
|
|
3
4
|
import { MovableDot } from "./MovableDot.mjs";
|
|
4
5
|
import { hitTest } from "./hitTest.mjs";
|
|
5
6
|
|
|
6
|
-
export { MovableDot, hitTest, useDraggable, useScreenToMath };
|
|
7
|
+
export { MovableDot, hitTest, useDraggable, useImpulse, usePressSpring, useScreenToMath };
|
|
@@ -10,7 +10,7 @@ interface UseDraggableArgs {
|
|
|
10
10
|
constrain?: Constrain;
|
|
11
11
|
/** Keyboard nudge step in math units. */
|
|
12
12
|
step?: number;
|
|
13
|
-
/** Bounds on the active axis
|
|
13
|
+
/** Bounds on the active axis, drives aria-valuemin/max for a proper slider
|
|
14
14
|
* (WAI-ARIA APG). Omit for an unbounded 2D point (valuetext still describes it). */
|
|
15
15
|
range?: {
|
|
16
16
|
min: number;
|
|
@@ -57,6 +57,7 @@ function useDraggable({ value, onMove, constrain, step = .5, range }) {
|
|
|
57
57
|
]);
|
|
58
58
|
const onKeyDown = useCallback((e) => {
|
|
59
59
|
const d = step * (e.shiftKey ? 5 : 1) * (e.altKey ? .25 : 1);
|
|
60
|
+
const vertical = constrain === "vertical";
|
|
60
61
|
let np = null;
|
|
61
62
|
if (e.key === "ArrowLeft") np = {
|
|
62
63
|
x: value.x - d,
|
|
@@ -74,6 +75,34 @@ function useDraggable({ value, onMove, constrain, step = .5, range }) {
|
|
|
74
75
|
x: value.x,
|
|
75
76
|
y: value.y - d
|
|
76
77
|
};
|
|
78
|
+
else if (e.key === "PageUp") np = vertical ? {
|
|
79
|
+
x: value.x,
|
|
80
|
+
y: value.y + 5 * step
|
|
81
|
+
} : {
|
|
82
|
+
x: value.x + 5 * step,
|
|
83
|
+
y: value.y
|
|
84
|
+
};
|
|
85
|
+
else if (e.key === "PageDown") np = vertical ? {
|
|
86
|
+
x: value.x,
|
|
87
|
+
y: value.y - 5 * step
|
|
88
|
+
} : {
|
|
89
|
+
x: value.x - 5 * step,
|
|
90
|
+
y: value.y
|
|
91
|
+
};
|
|
92
|
+
else if (e.key === "Home" && range) np = vertical ? {
|
|
93
|
+
x: value.x,
|
|
94
|
+
y: range.min
|
|
95
|
+
} : {
|
|
96
|
+
x: range.min,
|
|
97
|
+
y: value.y
|
|
98
|
+
};
|
|
99
|
+
else if (e.key === "End" && range) np = vertical ? {
|
|
100
|
+
x: value.x,
|
|
101
|
+
y: range.max
|
|
102
|
+
} : {
|
|
103
|
+
x: range.max,
|
|
104
|
+
y: value.y
|
|
105
|
+
};
|
|
77
106
|
if (np) {
|
|
78
107
|
e.preventDefault();
|
|
79
108
|
const p = constrained(np, value, constrain);
|
|
@@ -84,7 +113,8 @@ function useDraggable({ value, onMove, constrain, step = .5, range }) {
|
|
|
84
113
|
value,
|
|
85
114
|
step,
|
|
86
115
|
constrain,
|
|
87
|
-
onMove
|
|
116
|
+
onMove,
|
|
117
|
+
range
|
|
88
118
|
]);
|
|
89
119
|
const oneD = constrain === "horizontal" || constrain === "vertical";
|
|
90
120
|
const vertical = constrain === "vertical";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/interaction/usePressSpring.d.ts
|
|
2
|
+
interface PressSpringOpts {
|
|
3
|
+
/** Resting value when inactive. Default 0. */
|
|
4
|
+
from?: number;
|
|
5
|
+
/** Target value when active. Default 1. */
|
|
6
|
+
to?: number;
|
|
7
|
+
stiffness?: number;
|
|
8
|
+
damping?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A scalar that springs toward `to` while `active`, back to `from` on release,
|
|
12
|
+
* e.g. a 0→1 "press" amount you map to a radius bump or halo grow. The RAF loop
|
|
13
|
+
* is gated on "still moving" so a page full of idle handles costs nothing.
|
|
14
|
+
* Under reduced-motion it returns the target immediately (no animation).
|
|
15
|
+
*/
|
|
16
|
+
declare function usePressSpring(active: boolean, opts?: PressSpringOpts): number;
|
|
17
|
+
/**
|
|
18
|
+
* A one-shot impulse: jumps to 1 whenever `key` changes, then decays to 0 over
|
|
19
|
+
* `ms`. Map it to an expanding ring / flash to make a discrete event (a snap, a
|
|
20
|
+
* correct landing) FEEL like it clicked. Returns 0 under reduced-motion.
|
|
21
|
+
*/
|
|
22
|
+
declare function useImpulse(key: number | string, ms?: number): number;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { PressSpringOpts, useImpulse, usePressSpring };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useFrameLoop } from "../core/clock.mjs";
|
|
4
|
+
import { prefersReducedMotion, spring } from "../core/motion.mjs";
|
|
5
|
+
import { useRef, useState } from "react";
|
|
6
|
+
|
|
7
|
+
//#region src/interaction/usePressSpring.ts
|
|
8
|
+
/**
|
|
9
|
+
* Interaction "feel" hooks, the juice layer for draggable handles. Both are thin
|
|
10
|
+
* wrappers over the pure `spring` / decay maths in core/motion.ts; they own a
|
|
11
|
+
* frame loop that runs ONLY while the value is unsettled (no idle RAF), and they
|
|
12
|
+
* honor prefers-reduced-motion by snapping to the end state. SSR-safe.
|
|
13
|
+
*/
|
|
14
|
+
const EPS = 8e-4;
|
|
15
|
+
/**
|
|
16
|
+
* A scalar that springs toward `to` while `active`, back to `from` on release,
|
|
17
|
+
* e.g. a 0→1 "press" amount you map to a radius bump or halo grow. The RAF loop
|
|
18
|
+
* is gated on "still moving" so a page full of idle handles costs nothing.
|
|
19
|
+
* Under reduced-motion it returns the target immediately (no animation).
|
|
20
|
+
*/
|
|
21
|
+
function usePressSpring(active, opts = {}) {
|
|
22
|
+
const { from = 0, to = 1, stiffness = 320, damping = 26 } = opts;
|
|
23
|
+
const target = active ? to : from;
|
|
24
|
+
const reduce = prefersReducedMotion();
|
|
25
|
+
const [val, setVal] = useState(from);
|
|
26
|
+
const valRef = useRef(from);
|
|
27
|
+
const vel = useRef(0);
|
|
28
|
+
const settled = Math.abs(valRef.current - target) < EPS && Math.abs(vel.current) < EPS;
|
|
29
|
+
useFrameLoop((f) => {
|
|
30
|
+
const next = spring(valRef.current, target, vel, f.dtMs / 1e3, {
|
|
31
|
+
stiffness,
|
|
32
|
+
damping
|
|
33
|
+
});
|
|
34
|
+
if (Math.abs(next - target) < EPS && Math.abs(vel.current) < EPS) {
|
|
35
|
+
valRef.current = target;
|
|
36
|
+
vel.current = 0;
|
|
37
|
+
setVal(target);
|
|
38
|
+
} else {
|
|
39
|
+
valRef.current = next;
|
|
40
|
+
setVal(next);
|
|
41
|
+
}
|
|
42
|
+
}, { running: !reduce && !settled });
|
|
43
|
+
return reduce ? target : val;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A one-shot impulse: jumps to 1 whenever `key` changes, then decays to 0 over
|
|
47
|
+
* `ms`. Map it to an expanding ring / flash to make a discrete event (a snap, a
|
|
48
|
+
* correct landing) FEEL like it clicked. Returns 0 under reduced-motion.
|
|
49
|
+
*/
|
|
50
|
+
function useImpulse(key, ms = 260) {
|
|
51
|
+
const [v, setV] = useState(0);
|
|
52
|
+
const amp = useRef(0);
|
|
53
|
+
const prevKey = useRef(key);
|
|
54
|
+
const reduce = prefersReducedMotion();
|
|
55
|
+
if (prevKey.current !== key) {
|
|
56
|
+
prevKey.current = key;
|
|
57
|
+
if (!reduce) amp.current = 1;
|
|
58
|
+
}
|
|
59
|
+
useFrameLoop((f) => {
|
|
60
|
+
amp.current = Math.max(0, amp.current - f.dtMs / ms);
|
|
61
|
+
setV(amp.current);
|
|
62
|
+
}, { running: !reduce && amp.current > 0 });
|
|
63
|
+
return reduce ? 0 : v;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { useImpulse, usePressSpring };
|
package/dist/logic/ast.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/logic/ast.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Propositional-logic AST
|
|
3
|
+
* Propositional-logic AST, the boolean-domain sibling of the `expr` engine's
|
|
4
4
|
* numeric AST. A formula is variables joined by ¬ ∧ ∨ ⊕ → ↔. Pure + zero-dep;
|
|
5
5
|
* `evalBool` is the single evaluator every consumer (truth tables, equivalence,
|
|
6
6
|
* boolean circuits) runs.
|
package/dist/logic/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { cubeCovers, cubeOfSelection, cubeTerm, minimalCover, minimize, primeImp
|
|
|
6
6
|
|
|
7
7
|
//#region src/logic/index.ts
|
|
8
8
|
/**
|
|
9
|
-
* @classytic/stage logic kernel
|
|
9
|
+
* @classytic/stage logic kernel, a pure, dependency-free propositional-logic
|
|
10
10
|
* engine, the boolean-domain sibling of the `expr` (numeric) engine. Parses
|
|
11
11
|
* ¬ ∧ ∨ ⊕ → ↔ (unicode or ASCII), evaluates, builds truth tables, classifies
|
|
12
12
|
* tautology/contradiction/contingency, checks equivalence, and emits DNF/CNF +
|
|
@@ -18,11 +18,16 @@ import { cubeCovers, cubeOfSelection, cubeTerm, minimalCover, minimize, primeImp
|
|
|
18
18
|
function compileLogic(src) {
|
|
19
19
|
try {
|
|
20
20
|
const ast = parseLogic(src);
|
|
21
|
+
const vars = freeVars(ast);
|
|
21
22
|
return {
|
|
22
23
|
ok: true,
|
|
23
24
|
ast,
|
|
24
|
-
vars
|
|
25
|
-
eval: (env) =>
|
|
25
|
+
vars,
|
|
26
|
+
eval: (env) => {
|
|
27
|
+
const missing = vars.filter((v) => !(v in env));
|
|
28
|
+
if (missing.length) throw new Error(`logic: assignment is missing variable${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`);
|
|
29
|
+
return evalBool(ast, env);
|
|
30
|
+
}
|
|
26
31
|
};
|
|
27
32
|
} catch (e) {
|
|
28
33
|
return {
|
|
@@ -17,11 +17,11 @@ interface Minimization {
|
|
|
17
17
|
}
|
|
18
18
|
/** All minterms a cube covers, within an n-variable space. */
|
|
19
19
|
declare function cubeCovers(c: Cube, n: number): number[];
|
|
20
|
-
/** The product term for a cube
|
|
20
|
+
/** The product term for a cube, a literal per FIXED bit (MSB = first var). */
|
|
21
21
|
declare function cubeTerm(c: Cube, vars: string[], neg?: string, join?: string): string;
|
|
22
22
|
/** Quine–McCluskey prime implicants of a set of minterms (+ optional don't-cares). */
|
|
23
23
|
declare function primeImplicants(minterms: number[], n: number, dontCares?: number[]): Cube[];
|
|
24
|
-
/** A
|
|
24
|
+
/** A minimum SOP cover: essential prime implicants + Petrick's exact cover of the rest. */
|
|
25
25
|
declare function minimalCover(minterms: number[], n: number, dontCares?: number[]): Cube[];
|
|
26
26
|
/** Full minimisation of a formula (or explicit minterms) → cover + SOP string. */
|
|
27
27
|
declare function minimize(input: LNode | {
|
package/dist/logic/minimize.mjs
CHANGED
|
@@ -3,9 +3,9 @@ import { truthTable } from "./table.mjs";
|
|
|
3
3
|
|
|
4
4
|
//#region src/logic/minimize.ts
|
|
5
5
|
/**
|
|
6
|
-
* Boolean minimisation
|
|
6
|
+
* Boolean minimisation, Quine–McCluskey: the algorithm a Karnaugh map performs
|
|
7
7
|
* by eye. Given a function's minterms (the rows where it is 1) over n variables,
|
|
8
|
-
* it finds the PRIME IMPLICANTS (maximal "don't-care cubes"
|
|
8
|
+
* it finds the PRIME IMPLICANTS (maximal "don't-care cubes", groups on the
|
|
9
9
|
* K-map) and a minimal SUM-OF-PRODUCTS cover (essential PIs + a greedy fill).
|
|
10
10
|
* Pure + dependency-free, the boolean sibling of `simplify` in the expr engine;
|
|
11
11
|
* the K-map lab renders these cubes as the coloured groups, and the same routine
|
|
@@ -31,7 +31,7 @@ function cubeCovers(c, n) {
|
|
|
31
31
|
for (let m = 0; m < total; m++) if ((m & c.mask) === c.value) out.push(m);
|
|
32
32
|
return out;
|
|
33
33
|
}
|
|
34
|
-
/** The product term for a cube
|
|
34
|
+
/** The product term for a cube, a literal per FIXED bit (MSB = first var). */
|
|
35
35
|
function cubeTerm(c, vars, neg = "¬", join = " ∧ ") {
|
|
36
36
|
const n = vars.length;
|
|
37
37
|
const lits = [];
|
|
@@ -86,7 +86,56 @@ function coverageOf(pis, required) {
|
|
|
86
86
|
});
|
|
87
87
|
return map;
|
|
88
88
|
}
|
|
89
|
-
/**
|
|
89
|
+
/** Keep only the minimal PI-sets (drop any that is a strict superset of another). */
|
|
90
|
+
function absorb(products) {
|
|
91
|
+
const uniq = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const p of products) {
|
|
93
|
+
const s = new Set(p);
|
|
94
|
+
uniq.set([...s].sort((a, b) => a - b).join(","), s);
|
|
95
|
+
}
|
|
96
|
+
const sets = [...uniq.values()];
|
|
97
|
+
return sets.filter((s) => !sets.some((o) => o !== s && o.size < s.size && [...o].every((x) => s.has(x)))).map((s) => [...s]);
|
|
98
|
+
}
|
|
99
|
+
/** Greedy fallback: take the PI covering the most still-uncovered minterms, repeat. */
|
|
100
|
+
function greedyCover(uncovered, cov, candidates) {
|
|
101
|
+
const left = new Set(uncovered);
|
|
102
|
+
const picked = [];
|
|
103
|
+
while (left.size) {
|
|
104
|
+
let best = -1, bestCount = 0;
|
|
105
|
+
for (const i of candidates) {
|
|
106
|
+
if (picked.includes(i)) continue;
|
|
107
|
+
const c = cov.get(i).filter((m) => left.has(m)).length;
|
|
108
|
+
if (c > bestCount) {
|
|
109
|
+
bestCount = c;
|
|
110
|
+
best = i;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (best < 0) break;
|
|
114
|
+
picked.push(best);
|
|
115
|
+
for (const m of cov.get(best)) left.delete(m);
|
|
116
|
+
}
|
|
117
|
+
return picked;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Petrick's method: the EXACT minimum-size cover of the remaining minterms. Each minterm
|
|
121
|
+
* contributes a clause (the PIs covering it); multiplying the clauses out (with absorption)
|
|
122
|
+
* enumerates every irredundant cover, and we keep the one with the fewest PIs. Falls back to
|
|
123
|
+
* greedy if the residual is large enough that the expansion could blow up (K-map labs are small).
|
|
124
|
+
*/
|
|
125
|
+
function petrickCover(uncovered, cov, candidates) {
|
|
126
|
+
if (candidates.length > 18) return greedyCover(uncovered, cov, candidates);
|
|
127
|
+
let products = [[]];
|
|
128
|
+
for (const m of uncovered) {
|
|
129
|
+
const clause = candidates.filter((i) => cov.get(i).includes(m));
|
|
130
|
+
if (clause.length === 0) return greedyCover(uncovered, cov, candidates);
|
|
131
|
+
const next = [];
|
|
132
|
+
for (const p of products) for (const pi of clause) next.push(p.includes(pi) ? p : [...p, pi]);
|
|
133
|
+
products = absorb(next);
|
|
134
|
+
if (products.length > 4e3) return greedyCover(uncovered, cov, candidates);
|
|
135
|
+
}
|
|
136
|
+
return products.reduce((best, p) => p.length < best.length ? p : best, products[0] ?? []);
|
|
137
|
+
}
|
|
138
|
+
/** A minimum SOP cover: essential prime implicants + Petrick's exact cover of the rest. */
|
|
90
139
|
function minimalCover(minterms, n, dontCares = []) {
|
|
91
140
|
const required = new Set(minterms);
|
|
92
141
|
if (required.size === 0) return [];
|
|
@@ -100,20 +149,9 @@ function minimalCover(minterms, n, dontCares = []) {
|
|
|
100
149
|
if (owners.length === 1) chosen.add(owners[0]);
|
|
101
150
|
}
|
|
102
151
|
for (const i of chosen) for (const m of cov.get(i)) uncovered.delete(m);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (chosen.has(i)) return;
|
|
107
|
-
const c = cov.get(i).filter((m) => uncovered.has(m)).length;
|
|
108
|
-
if (c > bestCount) {
|
|
109
|
-
bestCount = c;
|
|
110
|
-
best = i;
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
if (best < 0 || bestCount <= 0) break;
|
|
114
|
-
chosen.add(best);
|
|
115
|
-
for (const m of cov.get(best)) uncovered.delete(m);
|
|
116
|
-
}
|
|
152
|
+
if (uncovered.size === 0) return [...chosen].map((i) => pis[i]);
|
|
153
|
+
const candidates = pis.map((_, i) => i).filter((i) => !chosen.has(i));
|
|
154
|
+
for (const i of petrickCover([...uncovered], cov, candidates)) chosen.add(i);
|
|
117
155
|
return [...chosen].map((i) => pis[i]);
|
|
118
156
|
}
|
|
119
157
|
/** Full minimisation of a formula (or explicit minterms) → cover + SOP string. */
|
package/dist/logic/table.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { evalBool, freeVars } from "./ast.mjs";
|
|
|
4
4
|
/**
|
|
5
5
|
* Truth tables + the things derived from them: classification (tautology /
|
|
6
6
|
* contradiction / contingency), logical equivalence, and DNF/CNF. Everything is
|
|
7
|
-
* a fold over the 2ⁿ assignments
|
|
7
|
+
* a fold over the 2ⁿ assignments, the truth table is the source of truth, so
|
|
8
8
|
* equivalence is just "same output column" (no algebraic law engine needed).
|
|
9
9
|
*/
|
|
10
10
|
/** Enumerate all 2^vars assignments (MSB = first var), evaluating `n` on each. */
|
package/dist/math/ast.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/math/ast.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* expr/ast
|
|
3
|
+
* expr/ast, the Abstract Syntax Tree: node types, evaluation, free-variable
|
|
4
4
|
* collection, and small builder helpers. The AST is the shared representation
|
|
5
5
|
* that evaluation, differentiation, simplification, and LaTeX printing all
|
|
6
6
|
* operate on.
|