eml-stdlib 0.4.0__py3-none-any.whl
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.
- eml_stdlib/__init__.py +81 -0
- eml_stdlib/ballistics/air_density.eml +73 -0
- eml_stdlib/ballistics/ballistic_solver.eml +102 -0
- eml_stdlib/ballistics/coriolis.eml +60 -0
- eml_stdlib/ballistics/drag.eml +44 -0
- eml_stdlib/ballistics/gravity.eml +52 -0
- eml_stdlib/ballistics/muzzle_velocity.eml +42 -0
- eml_stdlib/ballistics/spin_drift.eml +60 -0
- eml_stdlib/ballistics/time_of_flight.eml +79 -0
- eml_stdlib/ballistics/wind.eml +58 -0
- eml_stdlib/biology/abilities/ballistic_armor.eml +61 -0
- eml_stdlib/biology/abilities/bite_force.eml +48 -0
- eml_stdlib/biology/abilities/chromatophore.eml +50 -0
- eml_stdlib/biology/abilities/discharge.eml +45 -0
- eml_stdlib/biology/abilities/elastic_energy.eml +42 -0
- eml_stdlib/biology/abilities/locomotion.eml +50 -0
- eml_stdlib/biology/abilities/muscle_power.eml +47 -0
- eml_stdlib/biology/abilities/sprint.eml +36 -0
- eml_stdlib/biology/abilities/texture_match.eml +49 -0
- eml_stdlib/biology/abilities/toxin.eml +44 -0
- eml_stdlib/biology/cone_l.eml +36 -0
- eml_stdlib/biology/cone_s.eml +32 -0
- eml_stdlib/biology/cryptochrome.eml +44 -0
- eml_stdlib/biology/geomagnetic_field.eml +32 -0
- eml_stdlib/biology/goldman_equation.eml +41 -0
- eml_stdlib/biology/hill_equation.eml +65 -0
- eml_stdlib/biology/hodgkin_huxley.eml +108 -0
- eml_stdlib/biology/michaelis_menten.eml +65 -0
- eml_stdlib/biology/plume_diffusion.eml +47 -0
- eml_stdlib/biology/receptor_binding.eml +35 -0
- eml_stdlib/biology/rod_sensitivity.eml +38 -0
- eml_stdlib/biology/tapetum.eml +70 -0
- eml_stdlib/carriers/acoustic_cloak.eml +56 -0
- eml_stdlib/carriers/ferron_emission.eml +55 -0
- eml_stdlib/carriers/ferron_logic.eml +45 -0
- eml_stdlib/carriers/ferron_propagation.eml +69 -0
- eml_stdlib/carriers/mach_zehnder.eml +52 -0
- eml_stdlib/carriers/magnon_dispersion.eml +57 -0
- eml_stdlib/carriers/magnon_logic.eml +45 -0
- eml_stdlib/carriers/optical_neuron.eml +55 -0
- eml_stdlib/carriers/phonon_bandgap.eml +54 -0
- eml_stdlib/carriers/ring_resonator.eml +48 -0
- eml_stdlib/carriers/spin_torque.eml +61 -0
- eml_stdlib/carriers/thermal_rectifier.eml +63 -0
- eml_stdlib/catalog.json +4209 -0
- eml_stdlib/circuits/buck_converter.eml +74 -0
- eml_stdlib/circuits/cmos_inverter.eml +75 -0
- eml_stdlib/circuits/h_bridge.eml +69 -0
- eml_stdlib/circuits/ldo.eml +66 -0
- eml_stdlib/circuits/mosfet_iv.eml +59 -0
- eml_stdlib/circuits/pll_loop.eml +63 -0
- eml_stdlib/circuits/rc_filter.eml +98 -0
- eml_stdlib/circuits/voltage_divider.eml +68 -0
- eml_stdlib/control/bangbang.eml +34 -0
- eml_stdlib/control/deadband.eml +28 -0
- eml_stdlib/control/hysteresis.eml +28 -0
- eml_stdlib/control/kalman_1d.eml +53 -0
- eml_stdlib/control/lqr_1d.eml +47 -0
- eml_stdlib/control/pid.eml +38 -0
- eml_stdlib/gaming/animation/bounce.eml +39 -0
- eml_stdlib/gaming/animation/ease_in.eml +27 -0
- eml_stdlib/gaming/animation/ease_in_out.eml +29 -0
- eml_stdlib/gaming/animation/ease_out.eml +27 -0
- eml_stdlib/gaming/animation/ik_2bone.eml +57 -0
- eml_stdlib/gaming/animation/spring.eml +39 -0
- eml_stdlib/gaming/audio/distance_attenuation.eml +50 -0
- eml_stdlib/gaming/audio/doppler.eml +39 -0
- eml_stdlib/gaming/audio/lowpass_audio.eml +32 -0
- eml_stdlib/gaming/audio/reverb_delay.eml +41 -0
- eml_stdlib/gaming/camera/dof_blur.eml +30 -0
- eml_stdlib/gaming/camera/fov_projection.eml +47 -0
- eml_stdlib/gaming/camera/orbit.eml +48 -0
- eml_stdlib/gaming/camera/shake.eml +20 -0
- eml_stdlib/gaming/lighting/ambient_occlusion.eml +40 -0
- eml_stdlib/gaming/lighting/area_light.eml +50 -0
- eml_stdlib/gaming/lighting/fog_exp.eml +28 -0
- eml_stdlib/gaming/lighting/fog_exp2.eml +30 -0
- eml_stdlib/gaming/lighting/point_light.eml +33 -0
- eml_stdlib/gaming/lighting/spot_light.eml +43 -0
- eml_stdlib/gaming/noise/fbm.eml +65 -0
- eml_stdlib/gaming/noise/perlin_2d.eml +65 -0
- eml_stdlib/gaming/noise/perlin_3d.eml +63 -0
- eml_stdlib/gaming/noise/simplex_2d.eml +67 -0
- eml_stdlib/gaming/noise/turbulence.eml +46 -0
- eml_stdlib/gaming/noise/value_noise.eml +45 -0
- eml_stdlib/gaming/noise/voronoi_2d.eml +83 -0
- eml_stdlib/gaming/noise/white_noise.eml +47 -0
- eml_stdlib/gaming/noise/worley.eml +79 -0
- eml_stdlib/gaming/particles/drag.eml +30 -0
- eml_stdlib/gaming/particles/emitter_radial.eml +35 -0
- eml_stdlib/gaming/particles/fade.eml +36 -0
- eml_stdlib/gaming/particles/gravity_particle.eml +32 -0
- eml_stdlib/gaming/particles/size_over_life.eml +28 -0
- eml_stdlib/gaming/physics/aabb_overlap.eml +38 -0
- eml_stdlib/gaming/physics/buoyancy.eml +43 -0
- eml_stdlib/gaming/physics/circle_collision.eml +46 -0
- eml_stdlib/gaming/physics/impulse_resolve.eml +39 -0
- eml_stdlib/gaming/physics/rigid_body_2d.eml +61 -0
- eml_stdlib/gaming/physics/spring_damper.eml +30 -0
- eml_stdlib/gaming/physics/verlet.eml +29 -0
- eml_stdlib/gaming/shading/fresnel.eml +57 -0
- eml_stdlib/gaming/shading/matcap.eml +34 -0
- eml_stdlib/gaming/shading/pbr_diffuse.eml +41 -0
- eml_stdlib/gaming/shading/pbr_specular.eml +75 -0
- eml_stdlib/gaming/shading/subsurface.eml +52 -0
- eml_stdlib/gaming/shading/toon.eml +30 -0
- eml_stdlib/gaming/terrain/biome_select.eml +41 -0
- eml_stdlib/gaming/terrain/cliff_detect.eml +35 -0
- eml_stdlib/gaming/terrain/erosion_hydraulic.eml +57 -0
- eml_stdlib/gaming/terrain/erosion_thermal.eml +44 -0
- eml_stdlib/gaming/terrain/heightmap.eml +33 -0
- eml_stdlib/gaming/textures/brick.eml +27 -0
- eml_stdlib/gaming/textures/caustics.eml +25 -0
- eml_stdlib/gaming/textures/checkerboard.eml +27 -0
- eml_stdlib/gaming/textures/fire.eml +26 -0
- eml_stdlib/gaming/textures/marble.eml +24 -0
- eml_stdlib/gaming/textures/rust.eml +23 -0
- eml_stdlib/gaming/textures/water_surface.eml +49 -0
- eml_stdlib/gaming/textures/wood.eml +41 -0
- eml_stdlib/math/abs.eml +45 -0
- eml_stdlib/math/atan.eml +32 -0
- eml_stdlib/math/atan2.eml +61 -0
- eml_stdlib/math/bessel_j0.eml +65 -0
- eml_stdlib/math/cos.eml +42 -0
- eml_stdlib/math/cosh.eml +24 -0
- eml_stdlib/math/erf.eml +66 -0
- eml_stdlib/math/exp.eml +49 -0
- eml_stdlib/math/gamma.eml +55 -0
- eml_stdlib/math/gaussian.eml +18 -0
- eml_stdlib/math/log.eml +59 -0
- eml_stdlib/math/pow.eml +51 -0
- eml_stdlib/math/relu.eml +63 -0
- eml_stdlib/math/sigmoid.eml +21 -0
- eml_stdlib/math/sin.eml +37 -0
- eml_stdlib/math/sinh.eml +24 -0
- eml_stdlib/math/softmax.eml +87 -0
- eml_stdlib/math/sqrt.eml +46 -0
- eml_stdlib/math/tan.eml +36 -0
- eml_stdlib/math/tanh.eml +54 -0
- eml_stdlib/ml/attention.eml +70 -0
- eml_stdlib/ml/cross_entropy.eml +85 -0
- eml_stdlib/ml/gelu.eml +51 -0
- eml_stdlib/ml/layernorm.eml +49 -0
- eml_stdlib/ml/leaky_relu.eml +51 -0
- eml_stdlib/ml/relu.eml +63 -0
- eml_stdlib/ml/rotary_embed.eml +64 -0
- eml_stdlib/ml/silu.eml +40 -0
- eml_stdlib/ml/softmax.eml +87 -0
- eml_stdlib/physics/coulomb.eml +48 -0
- eml_stdlib/physics/damped_oscillator.eml +20 -0
- eml_stdlib/physics/diffusion.eml +45 -0
- eml_stdlib/physics/gravity.eml +60 -0
- eml_stdlib/physics/hooke.eml +56 -0
- eml_stdlib/physics/navier_stokes_1d.eml +57 -0
- eml_stdlib/physics/planck_radiation.eml +77 -0
- eml_stdlib/physics/stefan_boltzmann.eml +75 -0
- eml_stdlib/physics/wave_propagation.eml +52 -0
- eml_stdlib/quantum/cnot.eml +72 -0
- eml_stdlib/quantum/grover_oracle.eml +69 -0
- eml_stdlib/quantum/hadamard.eml +73 -0
- eml_stdlib/quantum/pauli_x.eml +29 -0
- eml_stdlib/quantum/pauli_z.eml +29 -0
- eml_stdlib/quantum/phase_gate.eml +76 -0
- eml_stdlib/quantum/rotation_rz.eml +92 -0
- eml_stdlib/sensors/accelerometer.eml +59 -0
- eml_stdlib/sensors/hall_effect.eml +42 -0
- eml_stdlib/sensors/photodetector.eml +58 -0
- eml_stdlib/sensors/rtd.eml +57 -0
- eml_stdlib/sensors/strain_gauge.eml +63 -0
- eml_stdlib/sensors/thermistor.eml +71 -0
- eml_stdlib/signal/bandpass.eml +39 -0
- eml_stdlib/signal/chirp.eml +42 -0
- eml_stdlib/signal/dft_single.eml +30 -0
- eml_stdlib/signal/fft_butterfly.eml +36 -0
- eml_stdlib/signal/highpass.eml +45 -0
- eml_stdlib/signal/lowpass.eml +49 -0
- eml_stdlib/signal/matched_filter.eml +48 -0
- eml_stdlib/signal/notch_filter.eml +47 -0
- eml_stdlib/signal/window_hamming.eml +47 -0
- eml_stdlib/signal/window_hann.eml +50 -0
- eml_stdlib-0.4.0.dist-info/METADATA +453 -0
- eml_stdlib-0.4.0.dist-info/RECORD +185 -0
- eml_stdlib-0.4.0.dist-info/WHEEL +5 -0
- eml_stdlib-0.4.0.dist-info/licenses/LICENSE +21 -0
- eml_stdlib-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// buck_converter.eml -- step-down DC-DC buck converter (CCM).
|
|
2
|
+
//
|
|
3
|
+
// In continuous-conduction mode the steady-state output voltage is
|
|
4
|
+
//
|
|
5
|
+
// V_out = duty * V_in
|
|
6
|
+
//
|
|
7
|
+
// Output ripple is set by the inductor and output cap:
|
|
8
|
+
//
|
|
9
|
+
// delta_I_L = (V_in - V_out) * duty / (L * f_sw)
|
|
10
|
+
// delta_V_out = delta_I_L / (8 * Cout * f_sw)
|
|
11
|
+
//
|
|
12
|
+
// The LC corner sets the closed-loop bandwidth (~1/10 of f_sw).
|
|
13
|
+
|
|
14
|
+
module buck_converter;
|
|
15
|
+
|
|
16
|
+
@spice_voltage(name = "Vin", a = "vin", b = "0", value = 12.0)
|
|
17
|
+
@spice_inductor(name = "L1", a = "sw", b = "out", value = 22.0e-6)
|
|
18
|
+
@spice_capacitor(name = "Cout", a = "out", b = "0", value = 47.0e-6)
|
|
19
|
+
@spice_resistor(name = "Rload", a = "out", b = "0", value = 5.0)
|
|
20
|
+
fn circuit() -> Real
|
|
21
|
+
where chain_order <= 0
|
|
22
|
+
{
|
|
23
|
+
0.0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── Mathematical certificates ─────────────────────────────────
|
|
27
|
+
|
|
28
|
+
@verify(lean, theorem = "buck_output_voltage_below_input")
|
|
29
|
+
fn output_voltage(duty: Real, v_in: Real) -> Real
|
|
30
|
+
where chain_order <= 0,
|
|
31
|
+
domain: v_in > 0.0
|
|
32
|
+
requires (duty >= 0.0)
|
|
33
|
+
requires (duty <= 1.0)
|
|
34
|
+
ensures (result >= 0.0)
|
|
35
|
+
ensures (result <= v_in)
|
|
36
|
+
{
|
|
37
|
+
duty * v_in
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Inductor ripple current.
|
|
41
|
+
@verify(lean, theorem = "buck_inductor_ripple_nonneg")
|
|
42
|
+
fn inductor_ripple(v_in: Real, v_out: Real, duty: Real,
|
|
43
|
+
inductance: Real, f_sw: Real) -> Real
|
|
44
|
+
where chain_order <= 0,
|
|
45
|
+
domain: inductance > 0.0 && f_sw > 0.0 && v_in >= v_out
|
|
46
|
+
requires (duty >= 0.0)
|
|
47
|
+
requires (duty <= 1.0)
|
|
48
|
+
ensures (result >= 0.0)
|
|
49
|
+
{
|
|
50
|
+
(v_in - v_out) * duty / (inductance * f_sw)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Output voltage ripple.
|
|
54
|
+
@verify(lean, theorem = "buck_output_ripple_nonneg")
|
|
55
|
+
fn output_ripple(delta_il: Real, c_out: Real, f_sw: Real) -> Real
|
|
56
|
+
where chain_order <= 0,
|
|
57
|
+
domain: c_out > 0.0 && f_sw > 0.0 && delta_il >= 0.0
|
|
58
|
+
ensures (result >= 0.0)
|
|
59
|
+
{
|
|
60
|
+
delta_il / (8.0 * c_out * f_sw)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Critical inductance -- below this, the buck enters DCM.
|
|
64
|
+
@verify(lean, theorem = "buck_critical_inductance_positive")
|
|
65
|
+
fn critical_inductance(v_out: Real, duty: Real,
|
|
66
|
+
i_load: Real, f_sw: Real) -> Real
|
|
67
|
+
where chain_order <= 0,
|
|
68
|
+
domain: v_out > 0.0 && f_sw > 0.0 && i_load > 0.0
|
|
69
|
+
requires (duty >= 0.0)
|
|
70
|
+
requires (duty <= 1.0)
|
|
71
|
+
ensures (result >= 0.0)
|
|
72
|
+
{
|
|
73
|
+
(1.0 - duty) * v_out / (2.0 * i_load * f_sw)
|
|
74
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// cmos_inverter.eml -- TIER 2: ELECTRONICS, second demo.
|
|
2
|
+
//
|
|
3
|
+
// CMOS inverter transfer characteristic. Two MOSFETs (one
|
|
4
|
+
// n-type, one p-type) share a gate and a drain; the gate is the
|
|
5
|
+
// input node, the shared drain is the output. The output is
|
|
6
|
+
// V_DD when V_in is low and 0 when V_in is high -- a digital
|
|
7
|
+
// inverter.
|
|
8
|
+
//
|
|
9
|
+
// In the simple complementary regime (deep enough V_in that
|
|
10
|
+
// only one transistor conducts), the transfer is binary:
|
|
11
|
+
//
|
|
12
|
+
// V_out(0) = V_DD (PMOS on, NMOS off)
|
|
13
|
+
// V_out(V_DD) = 0 (NMOS on, PMOS off)
|
|
14
|
+
//
|
|
15
|
+
// Chain order 0 -- step function (or a smoothed sigmoid in the
|
|
16
|
+
// transition region we don't model here).
|
|
17
|
+
|
|
18
|
+
module cmos_inverter;
|
|
19
|
+
|
|
20
|
+
const V_DD: Real = 3.3 // supply rail (V), 3.3V CMOS
|
|
21
|
+
|
|
22
|
+
// Output at logic-low input. PMOS conducts, NMOS off, output
|
|
23
|
+
// pulled to the supply rail.
|
|
24
|
+
fn vout_at_low_input(v_dd: Real) -> Real
|
|
25
|
+
where chain_order <= 0
|
|
26
|
+
{
|
|
27
|
+
v_dd
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Output at logic-high input. NMOS conducts, PMOS off, output
|
|
31
|
+
// pulled to ground.
|
|
32
|
+
fn vout_at_high_input() -> Real
|
|
33
|
+
where chain_order <= 0
|
|
34
|
+
{
|
|
35
|
+
0.0
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Switching point in a symmetric sized inverter (W_p / W_n
|
|
39
|
+
// chosen so the drive currents match): V_M = V_DD / 2.
|
|
40
|
+
fn switching_threshold(v_dd: Real) -> Real
|
|
41
|
+
where chain_order <= 0
|
|
42
|
+
{
|
|
43
|
+
v_dd * 0.5
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Mathematical certificates (Lean) ────────────────────────
|
|
47
|
+
|
|
48
|
+
// At a logic-low input the output rides exactly the rail.
|
|
49
|
+
@verify(lean, theorem = "cmos_low_in_high_out")
|
|
50
|
+
fn low_input_certificate(v_dd: Real) -> Real
|
|
51
|
+
where chain_order <= 0
|
|
52
|
+
ensures (result == v_dd)
|
|
53
|
+
{
|
|
54
|
+
v_dd
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// At a logic-high input the output sits at ground.
|
|
58
|
+
@verify(lean, theorem = "cmos_high_in_low_out")
|
|
59
|
+
fn high_input_certificate() -> Real
|
|
60
|
+
where chain_order <= 0
|
|
61
|
+
ensures (result == 0.0)
|
|
62
|
+
{
|
|
63
|
+
0.0
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Symmetric switching threshold sits at half the supply -- the
|
|
67
|
+
// design point that maximises noise margin on both rails.
|
|
68
|
+
@verify(lean, theorem = "cmos_threshold_positive_when_supply_positive")
|
|
69
|
+
fn vm_certificate(v_dd: Real) -> Real
|
|
70
|
+
where chain_order <= 0
|
|
71
|
+
requires (v_dd > 0.0)
|
|
72
|
+
ensures (result > 0.0)
|
|
73
|
+
{
|
|
74
|
+
v_dd * 0.5
|
|
75
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// h_bridge.eml -- DC-motor H-bridge driver.
|
|
2
|
+
//
|
|
3
|
+
// Four MOSFETs (Q1..Q4) form an H around the load. Two control
|
|
4
|
+
// signals determine the four valid quadrants:
|
|
5
|
+
//
|
|
6
|
+
// forward : Q1, Q4 on -- current flows + -> motor + -> motor - -> -
|
|
7
|
+
// reverse : Q2, Q3 on -- reversed
|
|
8
|
+
// brake : Q3, Q4 on (low-side) or Q1, Q2 on (high-side)
|
|
9
|
+
// coast : all off
|
|
10
|
+
//
|
|
11
|
+
// PWM the gate signal at 20-50 kHz to control torque. Avoid the
|
|
12
|
+
// shoot-through condition where a high-side and matching low-side
|
|
13
|
+
// device are both ON -- the dead-time ensures a brief moment with
|
|
14
|
+
// both off between transitions.
|
|
15
|
+
|
|
16
|
+
module h_bridge;
|
|
17
|
+
|
|
18
|
+
@spice_voltage(name = "Vbus", a = "vbus", b = "0", value = 12.0)
|
|
19
|
+
@spice_resistor(name = "Rmot", a = "out_a", b = "out_b", value = 2.0)
|
|
20
|
+
@spice_capacitor(name = "Cdc", a = "vbus", b = "0", value = 100.0e-6)
|
|
21
|
+
fn circuit() -> Real
|
|
22
|
+
where chain_order <= 0
|
|
23
|
+
{
|
|
24
|
+
0.0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ── Mathematical certificates ─────────────────────────────────
|
|
28
|
+
|
|
29
|
+
// Output voltage as a function of duty cycle for forward direction.
|
|
30
|
+
// V_motor = duty * V_bus
|
|
31
|
+
@verify(lean, theorem = "h_bridge_forward_proportional_to_duty")
|
|
32
|
+
fn forward_voltage(duty: Real, v_bus: Real) -> Real
|
|
33
|
+
where chain_order <= 0,
|
|
34
|
+
domain: v_bus > 0.0
|
|
35
|
+
requires (duty >= 0.0)
|
|
36
|
+
requires (duty <= 1.0)
|
|
37
|
+
ensures (result >= 0.0)
|
|
38
|
+
ensures (result <= v_bus)
|
|
39
|
+
{
|
|
40
|
+
duty * v_bus
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Reverse direction.
|
|
44
|
+
@verify(lean, theorem = "h_bridge_reverse_proportional_to_duty")
|
|
45
|
+
fn reverse_voltage(duty: Real, v_bus: Real) -> Real
|
|
46
|
+
where chain_order <= 0,
|
|
47
|
+
domain: v_bus > 0.0
|
|
48
|
+
requires (duty >= 0.0)
|
|
49
|
+
requires (duty <= 1.0)
|
|
50
|
+
ensures (result <= 0.0)
|
|
51
|
+
ensures (result >= -v_bus)
|
|
52
|
+
{
|
|
53
|
+
-duty * v_bus
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Dead-time compensation -- the motor sees a slightly smaller
|
|
57
|
+
// voltage than the commanded duty due to the dead-band.
|
|
58
|
+
// V_eff = duty * V_bus * (1 - f_pwm * t_dead * 2)
|
|
59
|
+
@verify(lean, theorem = "h_bridge_dead_time_loss_nonneg")
|
|
60
|
+
fn dead_time_loss(duty: Real, v_bus: Real,
|
|
61
|
+
f_pwm: Real, t_dead: Real) -> Real
|
|
62
|
+
where chain_order <= 0,
|
|
63
|
+
domain: v_bus > 0.0 && f_pwm > 0.0 && t_dead >= 0.0
|
|
64
|
+
requires (duty >= 0.0)
|
|
65
|
+
requires (duty <= 1.0)
|
|
66
|
+
ensures (result >= 0.0)
|
|
67
|
+
{
|
|
68
|
+
duty * v_bus * (2.0 * f_pwm * t_dead)
|
|
69
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// ldo.eml -- low-dropout linear regulator.
|
|
2
|
+
//
|
|
3
|
+
// Output voltage is set by the feedback divider:
|
|
4
|
+
//
|
|
5
|
+
// V_out = V_ref * (1 + R1 / R2)
|
|
6
|
+
//
|
|
7
|
+
// The pass element drops V_in - V_out at full load current; LDO
|
|
8
|
+
// efficiency is V_out / V_in (the rest is dissipated as heat).
|
|
9
|
+
// PSRR (power-supply rejection ratio) is set by the loop gain;
|
|
10
|
+
// stability requires a tame load capacitor with a particular ESR.
|
|
11
|
+
|
|
12
|
+
module ldo;
|
|
13
|
+
|
|
14
|
+
@spice_voltage(name = "Vin", a = "vin", b = "0", value = 5.0)
|
|
15
|
+
@spice_resistor(name = "R1", a = "out", b = "fb", value = 10000.0)
|
|
16
|
+
@spice_resistor(name = "R2", a = "fb", b = "0", value = 10000.0)
|
|
17
|
+
@spice_capacitor(name = "Cout", a = "out", b = "0", value = 10.0e-6)
|
|
18
|
+
fn circuit() -> Real
|
|
19
|
+
where chain_order <= 0
|
|
20
|
+
{
|
|
21
|
+
0.0
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ── Mathematical certificates ─────────────────────────────────
|
|
25
|
+
|
|
26
|
+
@verify(lean, theorem = "ldo_output_voltage_above_reference")
|
|
27
|
+
fn output_voltage(v_ref: Real, r1: Real, r2: Real) -> Real
|
|
28
|
+
where chain_order <= 0,
|
|
29
|
+
domain: v_ref > 0.0 && r1 >= 0.0 && r2 > 0.0
|
|
30
|
+
ensures (result >= v_ref)
|
|
31
|
+
{
|
|
32
|
+
v_ref * (1.0 + r1 / r2)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@verify(lean, theorem = "ldo_efficiency_in_unit_interval")
|
|
36
|
+
fn efficiency(v_in: Real, v_out: Real) -> Real
|
|
37
|
+
where chain_order <= 0,
|
|
38
|
+
domain: v_in > 0.0 && v_out > 0.0 && v_in >= v_out
|
|
39
|
+
ensures (result >= 0.0)
|
|
40
|
+
ensures (result <= 1.0)
|
|
41
|
+
{
|
|
42
|
+
v_out / v_in
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Power dissipated in the pass element at load current i_load.
|
|
46
|
+
@verify(lean, theorem = "ldo_dissipation_nonneg")
|
|
47
|
+
fn dissipation(v_in: Real, v_out: Real, i_load: Real) -> Real
|
|
48
|
+
where chain_order <= 0
|
|
49
|
+
requires (v_in >= v_out)
|
|
50
|
+
requires (i_load >= 0.0)
|
|
51
|
+
ensures (result >= 0.0)
|
|
52
|
+
{
|
|
53
|
+
(v_in - v_out) * i_load
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Dropout voltage -- the minimum (V_in - V_out) the LDO needs to
|
|
57
|
+
// stay in regulation at i_load. Linear approximation.
|
|
58
|
+
@verify(lean, theorem = "ldo_dropout_nonneg")
|
|
59
|
+
fn dropout_voltage(r_ds_on: Real, i_load: Real) -> Real
|
|
60
|
+
where chain_order <= 0,
|
|
61
|
+
domain: r_ds_on > 0.0
|
|
62
|
+
requires (i_load >= 0.0)
|
|
63
|
+
ensures (result >= 0.0)
|
|
64
|
+
{
|
|
65
|
+
r_ds_on * i_load
|
|
66
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// mosfet_iv.eml -- TIER 2: ELECTRONICS (electron carrier)
|
|
2
|
+
//
|
|
3
|
+
// MOSFET drain-current saturation model:
|
|
4
|
+
//
|
|
5
|
+
// I_D = (1/2) * mu * C_ox * (W/L) * (V_GS - V_th)^2
|
|
6
|
+
//
|
|
7
|
+
// Chain order 0 (polynomial). The carrier is the electron drift
|
|
8
|
+
// current; the wave equation reduces to a quasi-static
|
|
9
|
+
// constitutive relation. This is the equation that built every
|
|
10
|
+
// chip you've ever owned.
|
|
11
|
+
//
|
|
12
|
+
// EML demonstration: same compiler that emits ferron and magnon
|
|
13
|
+
// equations also emits this one. Carrier-substrate-independent.
|
|
14
|
+
|
|
15
|
+
module mosfet_iv;
|
|
16
|
+
|
|
17
|
+
// Process constants for a representative 65nm NMOS.
|
|
18
|
+
const MU_N: Real = 0.045 // m^2/V*s, electron mobility
|
|
19
|
+
const C_OX: Real = 1.7e-2 // F/m^2, gate oxide capacitance
|
|
20
|
+
const W_OVER_L: Real = 10.0 // device geometry ratio
|
|
21
|
+
const V_TH: Real = 0.4 // V, threshold voltage
|
|
22
|
+
|
|
23
|
+
// Saturation drain current vs. gate-source overdrive.
|
|
24
|
+
fn drain_current_sat(v_gs: Real, v_th: Real,
|
|
25
|
+
mu: Real, c_ox: Real, w_over_l: Real) -> Real
|
|
26
|
+
where chain_order <= 0
|
|
27
|
+
requires (v_gs >= v_th)
|
|
28
|
+
{
|
|
29
|
+
let vov = v_gs - v_th;
|
|
30
|
+
0.5 * mu * c_ox * w_over_l * (vov * vov)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Mathematical certificates (Lean) ────────────────────────
|
|
34
|
+
|
|
35
|
+
// At threshold (V_GS = V_th) the drain current vanishes. This
|
|
36
|
+
// is the textbook "device just turning on" boundary; any
|
|
37
|
+
// subthreshold model must agree with it at the matching point.
|
|
38
|
+
@verify(lean, theorem = "mosfet_zero_overdrive_zero_current")
|
|
39
|
+
fn id_at_threshold(mu: Real, c_ox: Real, w_over_l: Real) -> Real
|
|
40
|
+
where chain_order <= 0
|
|
41
|
+
ensures (result == 0.0)
|
|
42
|
+
{
|
|
43
|
+
0.5 * mu * c_ox * w_over_l * ((0.0) * (0.0))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Mobility, capacitance, geometry are all positive by
|
|
47
|
+
// construction (process parameters); positivity of the prefactor
|
|
48
|
+
// 0.5 * mu * C_ox * W/L is a precondition for a well-formed
|
|
49
|
+
// device model.
|
|
50
|
+
@verify(lean, theorem = "mosfet_prefactor_positive")
|
|
51
|
+
fn id_prefactor(mu: Real, c_ox: Real, w_over_l: Real) -> Real
|
|
52
|
+
where chain_order <= 0
|
|
53
|
+
requires (mu > 0.0)
|
|
54
|
+
requires (c_ox > 0.0)
|
|
55
|
+
requires (w_over_l > 0.0)
|
|
56
|
+
ensures (result > 0.0)
|
|
57
|
+
{
|
|
58
|
+
mu * c_ox * w_over_l
|
|
59
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// pll_loop.eml -- TIER 2: ELECTRONICS, third demo.
|
|
2
|
+
//
|
|
3
|
+
// Phase-locked loop dynamics, type-1 (no integrator). In the
|
|
4
|
+
// linearised tracking regime the phase-error obeys:
|
|
5
|
+
//
|
|
6
|
+
// d_phi(t) / dt = -K * sin(d_phi(t)) ~ -K * d_phi(t) (small-angle)
|
|
7
|
+
//
|
|
8
|
+
// where K = K_VCO * K_PD is the loop gain. A locked PLL has
|
|
9
|
+
// d_phi -> 0; the locked steady-state phase error is zero, the
|
|
10
|
+
// VCO control voltage is constant, and the output frequency
|
|
11
|
+
// matches the reference.
|
|
12
|
+
//
|
|
13
|
+
// Chain order 0 (algebraic; small-angle approximation pulls the
|
|
14
|
+
// trig out of the chain).
|
|
15
|
+
|
|
16
|
+
module pll_loop;
|
|
17
|
+
|
|
18
|
+
const K_VCO: Real = 1.0e6 // Hz/V, VCO sensitivity
|
|
19
|
+
const K_PD: Real = 0.5 // V/rad, phase-detector gain
|
|
20
|
+
|
|
21
|
+
// Loop gain. Larger K -> faster lock, smaller residual phase
|
|
22
|
+
// error in the steady state, higher loop bandwidth.
|
|
23
|
+
fn loop_gain(k_vco: Real, k_pd: Real) -> Real
|
|
24
|
+
where chain_order <= 0
|
|
25
|
+
{
|
|
26
|
+
k_vco * k_pd
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Steady-state phase error in a locked, type-1 PLL with a
|
|
30
|
+
// constant frequency offset df: e_ss = df / K. Demonstrated
|
|
31
|
+
// here with df = 0 (matched reference) -> error = 0.
|
|
32
|
+
fn steady_state_error_at_zero_offset(k: Real) -> Real
|
|
33
|
+
where chain_order <= 0
|
|
34
|
+
requires (k > 0.0)
|
|
35
|
+
{
|
|
36
|
+
0.0 / k
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Mathematical certificates (Lean) ────────────────────────
|
|
40
|
+
|
|
41
|
+
// Loop gain is positive whenever both stage gains are. The
|
|
42
|
+
// witness for "the loop will lock" -- a non-positive K either
|
|
43
|
+
// stalls (K=0) or runs away (K<0).
|
|
44
|
+
@verify(lean, theorem = "pll_loop_gain_positive")
|
|
45
|
+
fn loop_gain_certificate(k_vco: Real, k_pd: Real) -> Real
|
|
46
|
+
where chain_order <= 0
|
|
47
|
+
requires (k_vco > 0.0)
|
|
48
|
+
requires (k_pd > 0.0)
|
|
49
|
+
ensures (result > 0.0)
|
|
50
|
+
{
|
|
51
|
+
k_vco * k_pd
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// At zero frequency offset the locked steady-state phase error
|
|
55
|
+
// is exactly zero -- the lock IS the proof of zero residual.
|
|
56
|
+
@verify(lean, theorem = "pll_zero_offset_zero_phase_error")
|
|
57
|
+
fn ss_error_certificate(k: Real) -> Real
|
|
58
|
+
where chain_order <= 0
|
|
59
|
+
requires (k > 0.0)
|
|
60
|
+
ensures (result == 0.0)
|
|
61
|
+
{
|
|
62
|
+
0.0 / k
|
|
63
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// rc_filter.eml -- single-pole RC low-pass filter, the canonical
|
|
2
|
+
// SPICE-backend smoke test, now also carrying the mathematical
|
|
3
|
+
// certificate kernels the Lean backend turns into proof obligations.
|
|
4
|
+
//
|
|
5
|
+
// Topology:
|
|
6
|
+
// Vin -> in
|
|
7
|
+
// R1 in -> out (1 kOhm)
|
|
8
|
+
// C1 out -> 0 (1 uF)
|
|
9
|
+
// .tran 1u 10m
|
|
10
|
+
//
|
|
11
|
+
// Cut-off frequency is 1 / (2*pi*R*C) ~= 159 Hz. Run with:
|
|
12
|
+
//
|
|
13
|
+
// eml-compile rc_filter.eml --target spice -o rc_filter.cir
|
|
14
|
+
// ngspice -b rc_filter.cir
|
|
15
|
+
//
|
|
16
|
+
// The @verify(lean, ...) kernel functions below capture the
|
|
17
|
+
// continuous-time properties of the filter that Phase E4 proves
|
|
18
|
+
// against MachLib (see ~/monogate/machlib/foundations/MachLib/
|
|
19
|
+
// Discovered/rc_filter.lean).
|
|
20
|
+
//
|
|
21
|
+
// (Phase E4 -- the SPICE backend emits the netlist; the KiCad
|
|
22
|
+
// backend emits a schematic; the JLCPCB mapper emits a BOM; and
|
|
23
|
+
// the Lean backend emits the proof obligations. Same source, four
|
|
24
|
+
// downstream artifacts.)
|
|
25
|
+
|
|
26
|
+
module rc_filter;
|
|
27
|
+
|
|
28
|
+
// ── Circuit netlist (E1+E2+E3 backends) ─────────────────────
|
|
29
|
+
|
|
30
|
+
@spice_resistor(name = "R1", a = "in", b = "out", value = 1000.0)
|
|
31
|
+
@spice_capacitor(name = "C1", a = "out", b = "0", value = 1.0e-6)
|
|
32
|
+
@spice_voltage(name = "Vin", a = "in", b = "0", value = 5.0)
|
|
33
|
+
@spice_analysis(tran = "1u 10m")
|
|
34
|
+
fn circuit() -> Real
|
|
35
|
+
where chain_order <= 0
|
|
36
|
+
{
|
|
37
|
+
0.0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Mathematical certificates (E4: Lean) ────────────────────
|
|
41
|
+
|
|
42
|
+
// Time constant τ = R * C. Definitional — used by every other
|
|
43
|
+
// claim in this module as the reference time scale.
|
|
44
|
+
@verify(lean, theorem = "rc_time_constant_def")
|
|
45
|
+
fn tau(r: Real, c: Real) -> Real
|
|
46
|
+
where chain_order <= 0
|
|
47
|
+
requires (r > 0.0)
|
|
48
|
+
requires (c > 0.0)
|
|
49
|
+
ensures (result == r * c)
|
|
50
|
+
{
|
|
51
|
+
r * c
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Steady-state output (t -> infinity): the capacitor is fully
|
|
55
|
+
// charged and the output equals the input. This is the hard
|
|
56
|
+
// asymptote any RC step response approaches.
|
|
57
|
+
@verify(lean, theorem = "rc_steady_state_equals_input")
|
|
58
|
+
fn vout_steady(vin: Real) -> Real
|
|
59
|
+
where chain_order <= 0
|
|
60
|
+
ensures (result == vin)
|
|
61
|
+
{
|
|
62
|
+
vin
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Initial output (t = 0): the capacitor starts empty so the
|
|
66
|
+
// output is zero regardless of the input voltage.
|
|
67
|
+
@verify(lean, theorem = "rc_initial_output_zero")
|
|
68
|
+
fn vout_initial() -> Real
|
|
69
|
+
where chain_order <= 0
|
|
70
|
+
ensures (result == 0.0)
|
|
71
|
+
{
|
|
72
|
+
0.0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Step-response decay (closed form): V_out(t) = V_in * (1 - exp(-t/tau))
|
|
76
|
+
// The @verify here proves only the definitional shape; the
|
|
77
|
+
// monotonicity-in-t and bounded-by-vin properties are extracted as
|
|
78
|
+
// separate kernels below so each carries an independent obligation.
|
|
79
|
+
@verify(lean, theorem = "rc_step_response_form")
|
|
80
|
+
fn vout_charging(vin: Real, t: Real, tau_val: Real) -> Real
|
|
81
|
+
where chain_order <= 1
|
|
82
|
+
requires (tau_val > 0.0)
|
|
83
|
+
ensures (result == vin * (1.0 - exp(-t / tau_val)))
|
|
84
|
+
{
|
|
85
|
+
vin * (1.0 - exp(-t / tau_val))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Output at t = 0 of the closed-form solution: V_out(0) = 0.
|
|
89
|
+
// Combines the definition of vout_charging with exp(0) = 1.
|
|
90
|
+
// (Proof closes against MachLib's exp_zero axiom.)
|
|
91
|
+
@verify(lean, theorem = "rc_step_response_at_zero")
|
|
92
|
+
fn vout_charging_at_zero(vin: Real, tau_val: Real) -> Real
|
|
93
|
+
where chain_order <= 1
|
|
94
|
+
requires (tau_val > 0.0)
|
|
95
|
+
ensures (result == 0.0)
|
|
96
|
+
{
|
|
97
|
+
vin * (1.0 - exp(-0.0 / tau_val))
|
|
98
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// voltage_divider.eml -- the textbook resistive divider, the
|
|
2
|
+
// simplest non-trivial SPICE deck, now also carrying the
|
|
3
|
+
// mathematical certificate kernels the Lean backend turns into
|
|
4
|
+
// proof obligations.
|
|
5
|
+
//
|
|
6
|
+
// Topology:
|
|
7
|
+
// Vin -> in
|
|
8
|
+
// R1 in -> mid (10 kOhm)
|
|
9
|
+
// R2 mid -> 0 (10 kOhm)
|
|
10
|
+
// .op
|
|
11
|
+
//
|
|
12
|
+
// Theory: V(mid) = Vin * R2 / (R1 + R2). With R1 = R2 the
|
|
13
|
+
// midpoint sits at exactly Vin / 2.
|
|
14
|
+
|
|
15
|
+
module voltage_divider;
|
|
16
|
+
|
|
17
|
+
// ── Circuit netlist (E1+E2+E3 backends) ─────────────────────
|
|
18
|
+
|
|
19
|
+
@spice_resistor(name = "R1", a = "in", b = "mid", value = 10000.0)
|
|
20
|
+
@spice_resistor(name = "R2", a = "mid", b = "0", value = 10000.0)
|
|
21
|
+
@spice_voltage(name = "Vin", a = "in", b = "0", value = 5.0)
|
|
22
|
+
@spice_analysis(op = "")
|
|
23
|
+
fn circuit() -> Real
|
|
24
|
+
where chain_order <= 0
|
|
25
|
+
{
|
|
26
|
+
0.0
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Mathematical certificates (E4: Lean) ────────────────────
|
|
30
|
+
|
|
31
|
+
// Voltage-divider law: V_out = V_in * R2 / (R1 + R2).
|
|
32
|
+
// Definitional — the closed-form expression IS the body, so the
|
|
33
|
+
// theorem is provable by `rfl` after `unfold`.
|
|
34
|
+
@verify(lean, theorem = "voltage_divider_law")
|
|
35
|
+
fn vout_divider(vin: Real, r1: Real, r2: Real) -> Real
|
|
36
|
+
where chain_order <= 0
|
|
37
|
+
requires (r1 > 0.0)
|
|
38
|
+
requires (r2 > 0.0)
|
|
39
|
+
ensures (result == vin * r2 / (r1 + r2))
|
|
40
|
+
{
|
|
41
|
+
vin * r2 / (r1 + r2)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Sum of the two resistances is strictly positive. Used as a
|
|
45
|
+
// hypothesis in every downstream proof that needs to divide by
|
|
46
|
+
// (R1 + R2).
|
|
47
|
+
@verify(lean, theorem = "voltage_divider_denom_pos")
|
|
48
|
+
fn rsum(r1: Real, r2: Real) -> Real
|
|
49
|
+
where chain_order <= 0
|
|
50
|
+
requires (r1 > 0.0)
|
|
51
|
+
requires (r2 > 0.0)
|
|
52
|
+
ensures (result > 0.0)
|
|
53
|
+
{
|
|
54
|
+
r1 + r2
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Midpoint at the symmetric divider (R1 = R2 = R): V_out = V_in / 2.
|
|
58
|
+
// Captures the most-common design point. Not provable by `rfl`
|
|
59
|
+
// alone (algebra: 2R*x / (R+R) = x reduces to x = x via cancel),
|
|
60
|
+
// but the proof closes against MachLib's mul_inv + div_def axioms.
|
|
61
|
+
@verify(lean, theorem = "voltage_divider_symmetric_half")
|
|
62
|
+
fn vout_symmetric(vin: Real, r: Real) -> Real
|
|
63
|
+
where chain_order <= 0
|
|
64
|
+
requires (r > 0.0)
|
|
65
|
+
ensures (result == vin * r / (r + r))
|
|
66
|
+
{
|
|
67
|
+
vin * r / (r + r)
|
|
68
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// bangbang.eml -- bang-bang (relay) controller.
|
|
2
|
+
//
|
|
3
|
+
// The simplest possible feedback controller:
|
|
4
|
+
// u = u_max if error > 0 else u_min
|
|
5
|
+
//
|
|
6
|
+
// Used in thermostats, on/off relays, and as a sanity-check
|
|
7
|
+
// reference for more sophisticated controllers. Chain order 0;
|
|
8
|
+
// branchless under the optimiser via clamp(sign(...)*range, ...).
|
|
9
|
+
|
|
10
|
+
module bangbang;
|
|
11
|
+
|
|
12
|
+
@verify(lean, theorem = "bangbang_in_actuator_band")
|
|
13
|
+
fn bangbang(error: Real, u_max: Real, u_min: Real) -> Real
|
|
14
|
+
where chain_order <= 0
|
|
15
|
+
requires (u_max >= u_min)
|
|
16
|
+
ensures (result >= u_min)
|
|
17
|
+
ensures (result <= u_max)
|
|
18
|
+
{
|
|
19
|
+
let s = clamp(error * 1.0e30, -1.0, 1.0);
|
|
20
|
+
let mid = 0.5 * (u_max + u_min);
|
|
21
|
+
let amp = 0.5 * (u_max - u_min);
|
|
22
|
+
mid + s * amp
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Symmetric bang-bang -- output is +/- u_amp.
|
|
26
|
+
@verify(lean, theorem = "bangbang_sym_in_band")
|
|
27
|
+
fn bangbang_symmetric(error: Real, u_amp: Real) -> Real
|
|
28
|
+
where chain_order <= 0
|
|
29
|
+
requires (u_amp >= 0.0)
|
|
30
|
+
ensures (abs(result) <= u_amp)
|
|
31
|
+
{
|
|
32
|
+
let s = clamp(error * 1.0e30, -1.0, 1.0);
|
|
33
|
+
s * u_amp
|
|
34
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// deadband.eml -- two-sided dead-zone (deadband) saturation.
|
|
2
|
+
//
|
|
3
|
+
// If |x| <= threshold the output is 0; otherwise the output is x
|
|
4
|
+
// shifted toward zero by `threshold`. Equivalent to
|
|
5
|
+
// y = x - clamp(x, -threshold, threshold)
|
|
6
|
+
//
|
|
7
|
+
// Chain order 0. Used to suppress sensor noise around a setpoint
|
|
8
|
+
// and to keep PIDs from chattering.
|
|
9
|
+
|
|
10
|
+
module deadband;
|
|
11
|
+
|
|
12
|
+
@verify(lean, theorem = "deadband_zero_inside")
|
|
13
|
+
fn deadband(x: Real, threshold: Real) -> Real
|
|
14
|
+
where chain_order <= 0
|
|
15
|
+
requires (threshold >= 0.0)
|
|
16
|
+
{
|
|
17
|
+
x - clamp(x, -threshold, threshold)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Asymmetric deadband: distinct upper/lower thresholds.
|
|
21
|
+
@verify(lean, theorem = "deadband_asym_zero_inside")
|
|
22
|
+
fn deadband_asymmetric(x: Real, low: Real, high: Real) -> Real
|
|
23
|
+
where chain_order <= 0
|
|
24
|
+
requires (low <= 0.0)
|
|
25
|
+
requires (high >= 0.0)
|
|
26
|
+
{
|
|
27
|
+
x - clamp(x, low, high)
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// hysteresis.eml -- Schmitt-trigger / hysteresis switching.
|
|
2
|
+
//
|
|
3
|
+
// if state == 0 (off): switch on iff x > threshold_on
|
|
4
|
+
// if state == 1 (on): switch off iff x < threshold_off
|
|
5
|
+
//
|
|
6
|
+
// Caller threads `state` through frames as a 0/1 Real. The kernel
|
|
7
|
+
// returns the new state. Chain order 0.
|
|
8
|
+
//
|
|
9
|
+
// The asymmetry threshold_on > threshold_off prevents chatter --
|
|
10
|
+
// ubiquitous in thermostat / relay / battery-charger logic.
|
|
11
|
+
|
|
12
|
+
module hysteresis;
|
|
13
|
+
|
|
14
|
+
@verify(lean, theorem = "hysteresis_state_in_zero_one")
|
|
15
|
+
fn hysteresis_step(x: Real, state: Real,
|
|
16
|
+
threshold_on: Real, threshold_off: Real) -> Real
|
|
17
|
+
where chain_order <= 0
|
|
18
|
+
requires (threshold_on > threshold_off)
|
|
19
|
+
requires (state == 0.0 || state == 1.0)
|
|
20
|
+
ensures (result >= 0.0)
|
|
21
|
+
ensures (result <= 1.0)
|
|
22
|
+
{
|
|
23
|
+
let on_via_x = clamp((x - threshold_on) * 1.0e30, 0.0, 1.0);
|
|
24
|
+
let off_via_x = clamp((threshold_off - x) * 1.0e30, 0.0, 1.0);
|
|
25
|
+
let next_when_off = on_via_x;
|
|
26
|
+
let next_when_on = 1.0 - off_via_x;
|
|
27
|
+
state * next_when_on + (1.0 - state) * next_when_off
|
|
28
|
+
}
|