#!/bin/sh
# Regression for `ngspice-hier-op --force`: the clamp generator.
#
# --force hands the next block the previous state as soft clamps (a source
# behind a resistor) instead of a .nodeset, so the already-solved circuit is
# held and only the new block has anything to solve.  Four ways that has gone
# wrong, one assertion each:
#
#   1 clamp nodes saved into a state -> next step clamps them too -> the node
#     count grows by a factor per block (hopclamp_hopclamp_x)
#   2 two nodes sanitising to one element name -> duplicate instance, one
#     silently wins, a node goes unclamped with no diagnostic
#   3 the added block's PORT NET clamped at the value its own absence produced,
#     so the block can never drive it
#   4 a node already held by a source clamped as well -> voltage-source loop ->
#     `singular matrix: check node <src>#branch`.  The include carrying that
#     source is SINGLE-quoted, which a double-quote-only matcher skips.
#
# A shell test rather than a .cir because the tool DRIVES ngspice.

NG=${top_builddir:-../../..}/src/ngspice
TOOL=${top_srcdir:-../../..}/scripts/ngspice-hier-op
DECK=${srcdir:-.}/hier_op-2.cir
INC=${srcdir:-.}/hier_op-2-inc.cir

command -v python3 >/dev/null 2>&1 || { echo "python3 not found"; exit 77; }
[ -x "$NG" ]   || { echo "no ngspice at $NG"; exit 77; }
[ -f "$TOOL" ] || { echo "no tool at $TOOL"; exit 77; }

WORK=`mktemp -d` || exit 1
trap 'rm -rf "$WORK"' EXIT
cp "$INC" "$WORK/hier_op-2-inc.cir" || exit 1
# absolute path for the include: see the comment in hier_op-2.cir
sed "s#@INCDIR@#$WORK#" "$DECK" > "$WORK/deck.cir" || exit 1

( cd "$WORK" && python3 "$OLDPWD/$TOOL" deck.cir --ngspice "$OLDPWD/$NG" \
      --outdir out --min-devices 2 --force > run.log 2>&1 )
rc=$?

fail=0
[ $rc -eq 0 ] || { echo "FAILED: tool exited $rc"; fail=1; }
grep -q "still out   : none" "$WORK/run.log" || \
    { echo "FAILED: not every block was folded"; fail=1; }
# --force must actually have been used
grep -q "soft-clamped" "$WORK/run.log" || \
    { echo "FAILED: no clamp was emitted -- --force did nothing"; fail=1; }

# (1) no clamp node may ever appear in a delivered state
for f in "$WORK"/out/base.ic "$WORK"/out/base.nodeset \
         "$WORK"/out/s1_*.ic "$WORK"/out/s2_*.ic "$WORK"/out/s3_*.ic \
         "$WORK"/out/s1_*.nodeset "$WORK"/out/s2_*.nodeset "$WORK"/out/s3_*.nodeset; do
    case "$f" in *__*) continue ;; esac
    [ -f "$f" ] || continue
    if grep -qi "hopclamp_" "$f"; then
        echo "FAILED: clamp node leaked into the state $f"
        echo "        (it will be clamped again next step -- factorial growth)"
        fail=1
    fi
done

# (2) no two clamp elements may share a name
for f in "$WORK"/out/*__force.cir; do
    [ -f "$f" ] || continue
    d=`grep -v '^\*' "$f" | awk 'NF>=3 {print $1}' | sort | uniq -d`
    if [ -n "$d" ]; then
        echo "FAILED: duplicate clamp element name(s) in $(basename $f): $d"
        fail=1
    fi
done

# (4) `held` is driven by a source in a SINGLE-quoted include: never clamp it
for f in "$WORK"/out/*__force.cir; do
    [ -f "$f" ] || continue
    if grep -v '^\*' "$f" | awk 'NF>=3 {print $2}' | grep -qx "held"; then
        echo 'FAILED: clamped held, which a top-level source already drives'
        echo "        (single-quoted .include was not followed -> source loop)"
        fail=1
    fi
done

# (3) `hold` is blk2's port net: it must be free at the step that folds blk2
for f in "$WORK"/out/*xb2__force.cir; do
    [ -f "$f" ] || continue
    if grep -v '^\*' "$f" | awk 'NF>=3 {print $2}' | grep -qx "hold"; then
        echo 'FAILED: clamped hold, a port net of the block being added'
        echo "        (the block can then never drive its own terminal)"
        fail=1
    fi
done

# a '#' row is a branch current or a device-internal unknown, never a net
for f in "$WORK"/out/*__force.cir; do
    [ -f "$f" ] || continue
    if grep -v '^\*' "$f" | awk 'NF>=3 {print $2}' | grep -q '#'; then
        echo "FAILED: clamped a '#' row in $(basename $f)"; fail=1
    fi
done

[ $fail -eq 0 ] || { sed -n '1,40p' "$WORK/run.log"; exit 1; }
exit 0
