TTH 1.1.50 bundled with Windows InductEx® 8.0.1; manual 1.1.40 used as a bounded reference
Package applicability is under review; follow the stated version and platform limits.
Use the checked runtime and complete example
This example invokes tth.exe directly; InductEx® does not generate or fit the model during the run. The checked executable is TTH 1.1.50 from the recorded Windows InductEx® 8.0.1 catalog payload. Use the TTH runtime and its companion libraries from a compatible installation, together with an issued licence that grants TTH. The licence file is an existing private prerequisite; it is not included in the download.
The published TetraHenry® 1.1.40 manual explains the JSON and modified FastHenry fields used below. Its version differs from the exercised runtime, so this guide qualifies only the stated intersection and recorded execution. A separate standalone installer, maintained source repository, other platforms and final release packaging have not been qualified.
Extract all three files from the complete teaching ZIP into a writable folder. Python uses only its standard library. Run the command from that folder, select your actual Python executable, TTH executable and existing issued licence, and choose a new result directory each time.
Use the ZIP for the quickest start: all three files are already present. The command below runs that extracted example; the complete selectable file contents follow it for inspection or manual recreation.
Understand the three input roles
bar.inp contains geometry and named physical entities. bar.json maps those names to materials and terminals, defines the voltage excitation and selects the outputs and solver. run_tth.py is a small supplied Python client for this single-drive MQS/DC contract. It is not a built-in TTH command, a general input validator or an arbitrary dependency collector.
The mesh uses micrometres: length 100 µm, width 10 µm and thickness 1 µm. SIGMA is 5 × 10^7 S/m in the JSON, not conductivity per micrometre. The source applies 1 V from bar::positive to bar::negative. Physical names connect the two files exactly; changing a spelling in only one place can break that connection.
TTH uses its modified FastHenry syntax: material properties and excitation belong in JSON. Do not expect sigma/lambda, .external or .freq in a legacy FastHenry deck to configure this solver. This example makes every needed material, terminal and source explicit.
Run the complete standalone example
$ErrorActionPreference = 'Stop'
$python = 'python'
$tth = 'C:\Program Files\SUN Magnetics\InductEx\bin\tth.exe'
$licence = 'C:\Program Files\SUN Magnetics\Licenses\sm_license.txt'
& $python .\run_tth.py --tth $tth --licence $licence --input .\bar.json --result .\run-001
if ($LASTEXITCODE -ne 0) { throw 'TTH teaching run failed; inspect the retained generation and choose a new result directory after correction.' }
Get-Content -LiteralPath .\run-001\run.jsonSave bar.inp
* Uniform teaching conductor; geometry dimensions are micrometres
.Units um
N1 x=0 y=0 z=0
N2 x=100 y=0 z=0
E1 N1 N2 w=10 h=1 nwinc=1 nhinc=1 phys="metal"
.equiv N1 phys="positive"
.equiv N2 phys="negative"
.endSave bar.json
{
"VERSION": 1.0,
"MATERIALS": {"metal": {"SIGMA": 50000000}},
"MESHES": {
"bar": {
"FILE": "bar.inp",
"FORMAT": "FASTHENRY",
"ENTITIES": {
"metal": {"MATERIAL": "metal"},
"positive": {"TERMINAL": true},
"negative": {"TERMINAL": true}
}
}
},
"COMPONENTS": {
"drive": {
"NODES": ["bar::positive", "bar::negative"],
"TYPE": "VOLTAGE",
"VALUE": 1
}
},
"OUTPUT": {
"PRINT": {
"CURRENT": {"COMPONENTS": ["drive"], "FILE": "current.txt"},
"ITERATIONS": {"FILE": "iterations.txt"},
"ERROR": {"FILE": "errors.txt"}
}
},
"SETTINGS": {
"MODE": {"TYPE": "MQS", "ANALYSIS": "DC"},
"PATHS": {"OUTPUT": "output"},
"SOLVER": {
"TYPE": "FGMRES",
"THREADS": 1,
"TOL": 1e-9,
"ABSTOL": 1e-9,
"MAXITER": 100
}
}
}Save run_tth.py
"""Run the supplied single-drive MQS/DC TTH example in a new directory."""
import argparse
import hashlib
import json
import math
from pathlib import Path
import re
import shutil
import subprocess
import sys
def sha(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def load(path):
return json.loads(path.read_text(encoding='utf-8'))
def write(path, data):
path.write_text(json.dumps(data, indent=2)+'\n', encoding='utf-8')
def data_lines(path):
lines = path.read_text(encoding='utf-8').splitlines()
if len([line for line in lines if line.startswith('.freq ')]) != 1:
raise ValueError('Expected exactly one frequency in '+path.name)
frequency = next(float(line.split()[1]) for line in lines if line.startswith('.freq '))
if frequency != 0:
raise ValueError('Expected a zero-hertz DC output in '+path.name)
rows = [line.split() for line in lines if line and not line.startswith(('#', '.'))]
if len(rows) != 1:
raise ValueError('Expected exactly one drive row in '+path.name)
return lines, rows[0]
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--tth', type=Path, required=True)
parser.add_argument('--licence', type=Path, required=True)
parser.add_argument('--input', type=Path, default=Path('bar.json'))
parser.add_argument('--result', type=Path, required=True)
args = parser.parse_args()
result = args.result.resolve()
created = False
receipt = {'schema':'sunmagnetics-tth-run/1', 'status':'failed'}
try:
if result.exists():
raise ValueError('Choose a new result directory; the existing generation is preserved.')
source, binary, licence = args.input.resolve(), args.tth.resolve(), args.licence.resolve()
if not binary.is_file() or not licence.is_file():
raise ValueError('Select an existing TTH executable and issued licence file.')
model = load(source)
if model['SETTINGS']['MODE'] != {'TYPE':'MQS', 'ANALYSIS':'DC'}:
raise ValueError('This teaching runner supports only the supplied MQS/DC contract.')
if set(model['MESHES']) != {'bar'} or set(model['COMPONENTS']) != {'drive'}:
raise ValueError('This teaching runner expects one bar mesh and one drive source.')
drive = model['COMPONENTS']['drive']
if drive['TYPE'] != 'VOLTAGE' or not math.isfinite(float(drive['VALUE'])) or drive['VALUE']==0:
raise ValueError('The teaching drive must be a finite nonzero voltage.')
mesh_name = model['MESHES']['bar']['FILE']
if mesh_name != 'bar.inp' or model['MESHES']['bar']['FORMAT'] != 'FASTHENRY' or model.get('IMPORT'):
raise ValueError('Use the complete supplied two-file input without IMPORT dependencies.')
mesh = source.parent/mesh_name
if not mesh.is_file():
raise ValueError('Missing bar.inp beside the input JSON; restore the complete example.')
expected_print = {
'CURRENT':{'COMPONENTS':['drive'], 'FILE':'current.txt'},
'ITERATIONS':{'FILE':'iterations.txt'}, 'ERROR':{'FILE':'errors.txt'}
}
if model['OUTPUT'] != {'PRINT':expected_print}:
raise ValueError('Keep the supplied three-file output contract for this teaching runner.')
result.mkdir(parents=True)
created = True
saved = result/'inputs'
saved.mkdir()
shutil.copyfile(source, saved/'bar.json')
shutil.copyfile(mesh, saved/'bar.inp')
receipt.update(inputs={p.name:sha(p) for p in saved.iterdir()},
executable_name=binary.name, executable_sha256=sha(binary))
model['MESHES']['bar']['FILE'] = str(saved/'bar.inp')
model['SETTINGS']['PATHS'] = {'OUTPUT':str(result/'output'), 'LICENSE':str(licence)}
write(result/'runtime.json', model)
try:
run = subprocess.run([str(binary), str(result/'runtime.json')], cwd=result,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60,
creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0))
console = run.stdout
receipt['solver_exit_code'] = run.returncode
except subprocess.TimeoutExpired as error:
(result/'solver.log').write_bytes(error.stdout or b'')
raise ValueError('TTH exceeded 60 seconds; retain the failed generation and review its log.')
(result/'solver.log').write_bytes(console)
text = console.decode('utf-8', errors='replace')
if run.returncode != 0:
raise ValueError('TTH failed; read solver.log and correct the input in a new generation.')
if 'CONVERGENCE FAILED' in text or 'Runtime Error:' in text or 'Solved' not in text:
raise ValueError('TTH did not report a consistent solved status; review solver.log.')
lines, row = data_lines(result/'output/current.txt')
if len(row)!=4 or row[:2]!=['drive','drive'] or not row[3].endswith('j'):
raise ValueError('Unexpected TYPE 1 current row; do not guess columns or signs.')
current = complex(float(row[2]), float(row[3][:-1]))
versions = re.findall(r'generated by TTH v([0-9.]+)', '\n'.join(lines))
if len(versions)!=1 or not all(math.isfinite(x) for x in (current.real,current.imag)) or current==0:
raise ValueError('Current or runtime identity is missing/nonfinite.')
_, errors = data_lines(result/'output/errors.txt')
if len(errors)!=3 or errors[0]!='drive' or not errors[2].endswith('j'):
raise ValueError('Unexpected error report shape.')
relative_error = complex(float(errors[1]),float(errors[2][:-1]))
limit = max(float(model['SETTINGS']['SOLVER']['TOL']),float(model['SETTINGS']['SOLVER']['ABSTOL']))
if not math.isfinite(abs(relative_error)) or abs(relative_error)>limit:
raise ValueError('Reported error exceeds this teaching run limit; review solver status and KB-TTH-001.')
_, iterations = data_lines(result/'output/iterations.txt')
if len(iterations)!=2 or iterations[0]!='drive' or int(iterations[1])<1:
raise ValueError('Unexpected iteration report.')
receipt.update(status='passed', solver_version=versions[0], frequency_hz=0,
drive_voltage_V=drive['VALUE'], source_current_A=[current.real,current.imag],
reported_relative_error=[relative_error.real,relative_error.imag],
iterations=int(iterations[1]),
files={n:sha(result/'output'/n) for n in ('current.txt','errors.txt','iterations.txt')},
scope='Supplied single-drive MQS/DC example only; no general convergence or physical calibration claim.')
write(result/'run.json',receipt)
print(json.dumps({k:receipt[k] for k in ('status','solver_version','frequency_hz','source_current_A')}))
return 0
except (OSError, ValueError, KeyError, TypeError, subprocess.SubprocessError) as error:
receipt['reason'] = str(error)
if created:
write(result/'run.json',receipt)
print(json.dumps({'status':'failed','reason':str(error)}),file=sys.stderr)
return 1
if __name__=='__main__':
raise SystemExit(main())Read the current result and sign
The checked run writes run-001/output/current.txt, iterations.txt and errors.txt, plus run-001/run.json. The receipt records the actual TTH version, executable hash, input hashes, signed source current, frequency, reported error and numerical file hashes. Read solver.log for the full calculation messages. runtime.json and the solver-generated _settings.json contain resolved local paths; logs can include licence/status information. Review them before sharing.
At zero hertz the checked current record contains two drive identifiers followed by real and imaginary source-current values in amperes. The first identifies the excitation and the second the reported component. The TYPE 1 row was drive drive -4.9999999983e+00 0.0000000000e+00j. This is the voltage-source branch current, not an unsigned conductor-current magnitude. Retain its sign; the negative value in this setup is consistent with the source supplying the resistive conductor.
The independent uniform-conductor reference is R = length / (conductivity × width × thickness) = 0.2 Ω, giving a 5 A magnitude at 1 V. For this source orientation, compare -V / Re(I_source) with 0.2 Ω. The validation uses a relative current difference below 10^-7 and an imaginary-current magnitude below 10^-12 A for this simple DC test. These are example-specific comparison checks, not general solver or fabrication tolerances.
Recognise the files and checks
| File or signal | Meaning and check |
|---|---|
| run.json status | passed requires the runner checks; read a failed receipt rather than accepting a directory name. |
| solver.log | Current process messages. Nonzero exit, Runtime Error or CONVERGENCE FAILED rejects the run. |
| current.txt | Exactly one zero-hertz drive/drive row with finite real/imaginary amperes. Preserve the source-current sign. |
| errors.txt and iterations.txt | Expected finite drive records. The recipe checks the reported error against the configured 10^-9 teaching limit and a positive iteration count. This is not physical accuracy. |
| inputs/ | Byte-identical saved bar.json and bar.inp. The original teaching files remain unchanged. |
| runtime.json | Generated local JSON with absolute copied-mesh, output and issued-licence paths; no licence contents are copied. |
Vary one quantity and check a new generation
On fresh complete copies, changing only the source VALUE to 0.5 halves the signed source current. Changing only the length from 100 to 200 µm doubles the independent resistance and halves its current magnitude. The validation also retains the 100 × 10 × 1 µm conductor while using four 25 µm longitudinal segments and 2 × 2 cross-section subdivisions; its DC current still agrees with the uniform-conductor reference.
These checks exercise units, source scaling and this simple discretisation. They do not establish an error bound for AC skin effect, inductance, superconducting screening, arbitrary ports or a manufactured device. New physical problems need their own model and refinement checks.
Recover without replacing an earlier run
- If bar.inp is missing, restore it beside bar.json. The client rejects this before creating a result generation.
- For an unknown mesh entity, read solver.log, compare the JSON NODES/ENTITIES with the mesh phys names, correct the source copy and retry with --result .\run-002. The real solver failed with exit 1 for bar::absent; restoring bar::positive recovered.
- If run-001 already exists, keep it and choose a new generation. The client refuses replacement even when the existing files belong to a failed run.
- For conflicting convergence or error fields, use the related diagnostic guide. DIRECT and GMRES produced conflicting summaries on the checked one-conductor example. The supplied FGMRES route passed separately; switching a method is not proof of correctness for another model.
Keep paths deliberate
With raw tth.exe invocation, the checked relative mesh path bar.inp is resolved from the process working directory, not automatically from the JSON location. Calling a JSON by absolute path while running elsewhere failed to open the relative mesh. Running from its directory recovered. The supplied Python client avoids this ambiguity by copying the two inputs into a new result directory, writing absolute mesh/output/licence paths in runtime.json and launching TTH there.
This bounded client accepts the supplied one-mesh, one-voltage-source MQS/DC setup and expected output names. It does not expand IMPORT graphs or convert other formats. Preserve complete dependencies and make their resolution explicit when extending the model.
Sources and verification details
Recorded inputs/builds only. Complete walkthrough and final-package acceptance remain separate.
VAL-036 · Windows InductEx® 8.0.1 catalog-bundled TTH 1.1.50 / CPython 3.13.11
31 Windows checks on the InductEx® 8.0.1 catalog-bundled TTH 1.1.50: complete hand-written modified FastHenry/JSON MQS/DC conductor, independent bulk-resistance/voltage/length/refinement comparisons, separate generations, paths, real failures/recovery and conflicting DIRECT/GMRES summaries. No standalone installer, GUI, other-platform or general physical/convergence qualification
The 1.1.40 manual describes the input vocabulary used here; it is not asserted to be the exact manual for 1.1.50. Maintained TTH source authority and final package/platform acceptance remain open. KB-EQS-001 remains unchanged.
- published:tth-manual-1.1.40 · Sections 1.2.2–1.7, 1.11.1 and 1.12: modified FastHenry, JSON materials/terminals, output and MQS/DC settings; older reference, not exact 1.1.50 pairing
- validation:tth-standalone-windows-20261104 · VAL-036; normal standalone 1.1.50 invocation, observed output contract, controlled variations and failure/recovery
- The checked executable matches the preserved read-only MSI cabinet member and the recorded archive identity. This is standalone invocation of that bundled runtime, not qualification of a separate TTH installer.
- The older manual is a source for the explicitly used format vocabulary. Exact maintained TTH source/manual authority, remaining modes and final package/platform qualification are still open.
