From a1989fb1a9582d6daf3085497b797936e44772a5 Mon Sep 17 00:00:00 2001 From: jess Date: Tue, 12 May 2026 15:40:59 -0700 Subject: [PATCH] ffi --- .gitignore | 4 ++ ffi/femm_curr.cpp | 124 +++++++++++++++++++++++++++++++++ ffi/femm_curr.h | 68 +++++++++++++++++++ ffi/femm_elec.cpp | 122 +++++++++++++++++++++++++++++++++ ffi/femm_elec.h | 60 ++++++++++++++++ ffi/femm_heat.cpp | 128 +++++++++++++++++++++++++++++++++++ ffi/femm_heat.h | 62 +++++++++++++++++ ffi/femm_lua_complex_ops.cpp | 33 +++++++++ ffi/femm_mag.cpp | 21 ++++++ scripts/macos/build_ffi.sh | 100 +++++++++++++++++++++++++++ 10 files changed, 722 insertions(+) create mode 100644 .gitignore create mode 100644 ffi/femm_curr.cpp create mode 100644 ffi/femm_curr.h create mode 100644 ffi/femm_elec.cpp create mode 100644 ffi/femm_elec.h create mode 100644 ffi/femm_heat.cpp create mode 100644 ffi/femm_heat.h create mode 100644 ffi/femm_lua_complex_ops.cpp create mode 100755 scripts/macos/build_ffi.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5c85c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/ffi/ +.DS_Store +*.o +*.a diff --git a/ffi/femm_curr.cpp b/ffi/femm_curr.cpp new file mode 100644 index 0000000..c79c4bf --- /dev/null +++ b/ffi/femm_curr.cpp @@ -0,0 +1,124 @@ +// current-flow FFI body: opaque doc, pipeline, mesh accessors. delegates to csolv's CFemmeDocCore. + +#include "../csolv/StdAfx.h" +#include "../csolv/CSOLVDLG.H" +#include "../csolv/complex.h" +#include "../csolv/mesh.h" +#include "../csolv/spars.h" +#include "../csolv/femmedoccore.h" + +#include "femm_curr.h" + +#include +#include +#include + +// stub view: math files reference TheView globally, math methods call SetPos/SetDlgItemText/InvalidateRect on it. +static CcsolvDlg s_stub_view; +CcsolvDlg* TheView = &s_stub_view; + +// math files declare and call these for error reporting. +int MsgBox(const char* fmt, ...) { + va_list ap; va_start(ap, fmt); + std::vfprintf(stderr, fmt, ap); + std::fputc('\n', stderr); + va_end(ap); + return 0; +} +int MsgBox(const std::string& s) { + std::fprintf(stderr, "%s\n", s.c_str()); + return 0; +} + +// referenced by csolv/main.cpp's old_main wait loop; main.cpp itself is not linked here. +inline bool IsWindow(void*) { return true; } + +struct FemmCurrDoc { + CFemmeDocCore doc; + FemmCurrProgressFn cb = nullptr; + void* user = nullptr; + std::string path_buf; +}; + +extern "C" { + +FemmCurrDoc* femm_curr_doc_new(void) { + auto* d = new FemmCurrDoc(); + d->doc.TheView = &s_stub_view; + return d; +} + +void femm_curr_doc_free(FemmCurrDoc* d) { + delete d; +} + +void femm_curr_doc_set_progress(FemmCurrDoc* d, FemmCurrProgressFn fn, void* user) { + if (!d) return; + d->cb = fn; + d->user = user; +} + +int femm_curr_doc_load_fec(FemmCurrDoc* d, const char* path) { + if (!d || !path) return 0; + d->path_buf = path; + d->doc.PathName = const_cast(d->path_buf.c_str()); + return d->doc.OnOpenDocument() ? 1 : 0; +} + +int femm_curr_doc_load_mesh(FemmCurrDoc* d) { + return (d && d->doc.LoadMesh()) ? 1 : 0; +} + +int femm_curr_doc_renumber(FemmCurrDoc* d) { + return (d && d->doc.Cuthill()) ? 1 : 0; +} + +// allocates a CBigComplexLinProb and runs AnalyzeProblem + WriteResults. +int femm_curr_doc_solve(FemmCurrDoc* d) { + if (!d) return 0; + CBigComplexLinProb L; + L.TheView = &s_stub_view; + L.Precision = d->doc.Precision; + if (!L.Create(d->doc.NumNodes + d->doc.NumCircProps, d->doc.BandWidth, d->doc.NumNodes)) return 0; + if (!d->doc.AnalyzeProblem(L)) return 0; + if (!d->doc.WriteResults(L)) return 0; + return 1; +} + +int femm_curr_doc_write_results(FemmCurrDoc* /*d*/, const char* /*out_path*/) { + // results currently emitted inline by solve(); reserved hook for explicit output redirection. + return 1; +} + +double femm_curr_doc_frequency (const FemmCurrDoc* d) { return d ? d->doc.Frequency : 0.0; } +int femm_curr_doc_axisymmetric (const FemmCurrDoc* d) { return (d && d->doc.ProblemType) ? 1 : 0; } +double femm_curr_doc_depth (const FemmCurrDoc* d) { return d ? d->doc.Depth : 0.0; } +double femm_curr_doc_precision (const FemmCurrDoc* d) { return d ? d->doc.Precision : 0.0; } + +int femm_curr_doc_num_nodes (const FemmCurrDoc* d) { return d ? d->doc.NumNodes : 0; } + +void femm_curr_doc_node (const FemmCurrDoc* d, int i, double* x, double* y) { + if (!d || !d->doc.meshnode) return; + if (x) *x = d->doc.meshnode[i].x; + if (y) *y = d->doc.meshnode[i].y; +} + +int femm_curr_doc_num_elements (const FemmCurrDoc* d) { return d ? d->doc.NumEls : 0; } + +void femm_curr_doc_element (const FemmCurrDoc* d, int i, int* p0, int* p1, int* p2) { + if (!d || !d->doc.meshele) return; + if (p0) *p0 = d->doc.meshele[i].p[0]; + if (p1) *p1 = d->doc.meshele[i].p[1]; + if (p2) *p2 = d->doc.meshele[i].p[2]; +} + +int femm_curr_doc_num_materials (const FemmCurrDoc* d) { return d ? d->doc.NumBlockProps : 0; } +int femm_curr_doc_num_boundaries (const FemmCurrDoc* d) { return d ? d->doc.NumLineProps : 0; } +int femm_curr_doc_num_conductors (const FemmCurrDoc* d) { return d ? d->doc.NumCircProps : 0; } + +// field sampling is post-processor territory; not exposed by the solver alone. +double femm_curr_doc_field_at (const FemmCurrDoc* /*d*/, double /*x*/, double /*y*/, FemmCurrField /*c*/) { + return 0.0; +} + +} // extern "C" diff --git a/ffi/femm_curr.h b/ffi/femm_curr.h new file mode 100644 index 0000000..a975252 --- /dev/null +++ b/ffi/femm_curr.h @@ -0,0 +1,68 @@ +// C ABI for the current-flow solver (csolv): opaque doc, pipeline, mesh accessors, progress callback. +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FemmCurrDoc FemmCurrDoc; + +// pct in 0..100, or -1 for label-only updates. label may be NULL. +typedef void (*FemmCurrProgressFn)(int pct, const char* label, void* user); + +// lifetime +FemmCurrDoc* femm_curr_doc_new(void); +void femm_curr_doc_free(FemmCurrDoc* doc); + +// progress reporting routed to the host (Rust/Iced). +void femm_curr_doc_set_progress(FemmCurrDoc* doc, FemmCurrProgressFn fn, void* user); + +// pipeline. each returns 1 on success, 0 on failure. +int femm_curr_doc_load_fec (FemmCurrDoc* doc, const char* path); +int femm_curr_doc_load_mesh (FemmCurrDoc* doc); +int femm_curr_doc_renumber (FemmCurrDoc* doc); +int femm_curr_doc_solve (FemmCurrDoc* doc); +int femm_curr_doc_write_results(FemmCurrDoc* doc, const char* out_path); + +// problem attributes after load_fec. +double femm_curr_doc_frequency (const FemmCurrDoc* doc); +int femm_curr_doc_axisymmetric (const FemmCurrDoc* doc); +double femm_curr_doc_depth (const FemmCurrDoc* doc); +double femm_curr_doc_precision (const FemmCurrDoc* doc); + +// mesh, valid after load_mesh + renumber. +int femm_curr_doc_num_nodes (const FemmCurrDoc* doc); +void femm_curr_doc_node (const FemmCurrDoc* doc, int i, double* x, double* y); +int femm_curr_doc_num_elements (const FemmCurrDoc* doc); +void femm_curr_doc_element (const FemmCurrDoc* doc, int i, int* p0, int* p1, int* p2); + +// property table sizes. +int femm_curr_doc_num_materials (const FemmCurrDoc* doc); +int femm_curr_doc_num_boundaries (const FemmCurrDoc* doc); +int femm_curr_doc_num_conductors (const FemmCurrDoc* doc); + +// field-point sampling after solve. component selects which scalar to fetch. +typedef enum { + FEMM_CURR_FIELD_V_RE = 0, + FEMM_CURR_FIELD_V_IM = 1, + FEMM_CURR_FIELD_EX_RE = 2, + FEMM_CURR_FIELD_EX_IM = 3, + FEMM_CURR_FIELD_EY_RE = 4, + FEMM_CURR_FIELD_EY_IM = 5, + FEMM_CURR_FIELD_E_MAG_RE = 6, + FEMM_CURR_FIELD_E_MAG_IM = 7, + FEMM_CURR_FIELD_JX_RE = 8, + FEMM_CURR_FIELD_JX_IM = 9, + FEMM_CURR_FIELD_JY_RE = 10, + FEMM_CURR_FIELD_JY_IM = 11, + FEMM_CURR_FIELD_J_MAG_RE = 12, + FEMM_CURR_FIELD_J_MAG_IM = 13, +} FemmCurrField; + +double femm_curr_doc_field_at(const FemmCurrDoc* doc, double x, double y, FemmCurrField component); + +#ifdef __cplusplus +} +#endif diff --git a/ffi/femm_elec.cpp b/ffi/femm_elec.cpp new file mode 100644 index 0000000..7524380 --- /dev/null +++ b/ffi/femm_elec.cpp @@ -0,0 +1,122 @@ +// electrostatic FFI body: opaque doc, pipeline, mesh accessors. delegates to belasolv's CFemmeDocCore. + +#include "../belasolv/StdAfx.h" +#include "../belasolv/belasolvDlg.h" +#include "../belasolv/mesh.h" +#include "../belasolv/spars.h" +#include "../belasolv/femmedoccore.h" + +#include "femm_elec.h" + +#include +#include +#include + +// stub view: math files reference TheView globally, math methods call SetPos/SetDlgItemText/InvalidateRect on it. +static CbelasolvDlg s_stub_view; +CbelasolvDlg* TheView = &s_stub_view; + +// math files declare and call these for error reporting. +int MsgBox(const char* fmt, ...) { + va_list ap; va_start(ap, fmt); + std::vfprintf(stderr, fmt, ap); + std::fputc('\n', stderr); + va_end(ap); + return 0; +} +int MsgBox(const std::string& s) { + std::fprintf(stderr, "%s\n", s.c_str()); + return 0; +} + +// referenced by belasolv/main.cpp's old_main wait loop; main.cpp itself is not linked here. +inline bool IsWindow(void*) { return true; } + +struct FemmElecDoc { + CFemmeDocCore doc; + FemmElecProgressFn cb = nullptr; + void* user = nullptr; + std::string path_buf; +}; + +extern "C" { + +FemmElecDoc* femm_elec_doc_new(void) { + auto* d = new FemmElecDoc(); + d->doc.TheView = &s_stub_view; + return d; +} + +void femm_elec_doc_free(FemmElecDoc* d) { + delete d; +} + +void femm_elec_doc_set_progress(FemmElecDoc* d, FemmElecProgressFn fn, void* user) { + if (!d) return; + d->cb = fn; + d->user = user; +} + +int femm_elec_doc_load_fee(FemmElecDoc* d, const char* path) { + if (!d || !path) return 0; + d->path_buf = path; + d->doc.PathName = const_cast(d->path_buf.c_str()); + return d->doc.OnOpenDocument() ? 1 : 0; +} + +int femm_elec_doc_load_mesh(FemmElecDoc* d) { + return (d && d->doc.LoadMesh()) ? 1 : 0; +} + +int femm_elec_doc_renumber(FemmElecDoc* d) { + return (d && d->doc.Cuthill()) ? 1 : 0; +} + +// allocates a real-valued CBigLinProb and runs AnalyzeProblem + WriteResults. +int femm_elec_doc_solve(FemmElecDoc* d) { + if (!d) return 0; + CBigLinProb L; + L.TheView = &s_stub_view; + L.Precision = d->doc.Precision; + if (!L.Create(d->doc.NumNodes + d->doc.NumCircProps, d->doc.BandWidth)) return 0; + if (!d->doc.AnalyzeProblem(L)) return 0; + if (!d->doc.WriteResults(L)) return 0; + return 1; +} + +int femm_elec_doc_write_results(FemmElecDoc* /*d*/, const char* /*out_path*/) { + // results currently emitted inline by solve(); reserved hook for explicit output redirection. + return 1; +} + +int femm_elec_doc_axisymmetric (const FemmElecDoc* d) { return (d && d->doc.ProblemType) ? 1 : 0; } +double femm_elec_doc_depth (const FemmElecDoc* d) { return d ? d->doc.Depth : 0.0; } +double femm_elec_doc_precision (const FemmElecDoc* d) { return d ? d->doc.Precision : 0.0; } + +int femm_elec_doc_num_nodes (const FemmElecDoc* d) { return d ? d->doc.NumNodes : 0; } + +void femm_elec_doc_node (const FemmElecDoc* d, int i, double* x, double* y) { + if (!d || !d->doc.meshnode) return; + if (x) *x = d->doc.meshnode[i].x; + if (y) *y = d->doc.meshnode[i].y; +} + +int femm_elec_doc_num_elements (const FemmElecDoc* d) { return d ? d->doc.NumEls : 0; } + +void femm_elec_doc_element (const FemmElecDoc* d, int i, int* p0, int* p1, int* p2) { + if (!d || !d->doc.meshele) return; + if (p0) *p0 = d->doc.meshele[i].p[0]; + if (p1) *p1 = d->doc.meshele[i].p[1]; + if (p2) *p2 = d->doc.meshele[i].p[2]; +} + +int femm_elec_doc_num_materials (const FemmElecDoc* d) { return d ? d->doc.NumBlockProps : 0; } +int femm_elec_doc_num_boundaries (const FemmElecDoc* d) { return d ? d->doc.NumLineProps : 0; } +int femm_elec_doc_num_conductors (const FemmElecDoc* d) { return d ? d->doc.NumCircProps : 0; } + +// field sampling is post-processor territory; not exposed by the solver alone. +double femm_elec_doc_field_at (const FemmElecDoc* /*d*/, double /*x*/, double /*y*/, FemmElecField /*c*/) { + return 0.0; +} + +} // extern "C" diff --git a/ffi/femm_elec.h b/ffi/femm_elec.h new file mode 100644 index 0000000..09e63b0 --- /dev/null +++ b/ffi/femm_elec.h @@ -0,0 +1,60 @@ +// C ABI for the electrostatic solver (belasolv): opaque doc, pipeline, mesh accessors, progress callback. +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FemmElecDoc FemmElecDoc; + +// pct in 0..100, or -1 for label-only updates. label may be NULL. +typedef void (*FemmElecProgressFn)(int pct, const char* label, void* user); + +// lifetime +FemmElecDoc* femm_elec_doc_new(void); +void femm_elec_doc_free(FemmElecDoc* doc); + +// progress reporting routed to the host (Rust/Iced). +void femm_elec_doc_set_progress(FemmElecDoc* doc, FemmElecProgressFn fn, void* user); + +// pipeline. each returns 1 on success, 0 on failure. +int femm_elec_doc_load_fee (FemmElecDoc* doc, const char* path); +int femm_elec_doc_load_mesh (FemmElecDoc* doc); +int femm_elec_doc_renumber (FemmElecDoc* doc); +int femm_elec_doc_solve (FemmElecDoc* doc); +int femm_elec_doc_write_results(FemmElecDoc* doc, const char* out_path); + +// problem attributes after load_fee. +int femm_elec_doc_axisymmetric (const FemmElecDoc* doc); +double femm_elec_doc_depth (const FemmElecDoc* doc); +double femm_elec_doc_precision (const FemmElecDoc* doc); + +// mesh, valid after load_mesh + renumber. +int femm_elec_doc_num_nodes (const FemmElecDoc* doc); +void femm_elec_doc_node (const FemmElecDoc* doc, int i, double* x, double* y); +int femm_elec_doc_num_elements (const FemmElecDoc* doc); +void femm_elec_doc_element (const FemmElecDoc* doc, int i, int* p0, int* p1, int* p2); + +// property table sizes. +int femm_elec_doc_num_materials (const FemmElecDoc* doc); +int femm_elec_doc_num_boundaries (const FemmElecDoc* doc); +int femm_elec_doc_num_conductors (const FemmElecDoc* doc); + +// field-point sampling after solve. component selects which scalar to fetch. +typedef enum { + FEMM_ELEC_FIELD_V = 0, + FEMM_ELEC_FIELD_EX = 1, + FEMM_ELEC_FIELD_EY = 2, + FEMM_ELEC_FIELD_E_MAG = 3, + FEMM_ELEC_FIELD_DX = 4, + FEMM_ELEC_FIELD_DY = 5, + FEMM_ELEC_FIELD_D_MAG = 6, +} FemmElecField; + +double femm_elec_doc_field_at(const FemmElecDoc* doc, double x, double y, FemmElecField component); + +#ifdef __cplusplus +} +#endif diff --git a/ffi/femm_heat.cpp b/ffi/femm_heat.cpp new file mode 100644 index 0000000..e4d3573 --- /dev/null +++ b/ffi/femm_heat.cpp @@ -0,0 +1,128 @@ +// heat-flow FFI body: opaque doc, pipeline, mesh accessors. delegates to hsolv's Chsolvdoc. + +#include "../hsolv/StdAfx.h" +#include "../hsolv/hsolvDlg.h" +#include "../hsolv/mesh.h" +#include "../hsolv/spars.h" +#include "../hsolv/hsolvdoc.h" + +#include "femm_heat.h" + +#include +#include +#include + +// stub view: math files reference TheView globally, math methods call SetPos/SetDlgItemText/InvalidateRect on it. +static ChsolvDlg s_stub_view; +ChsolvDlg* TheView = &s_stub_view; + +// math files declare and call these for error reporting. +int MsgBox(const char* fmt, ...) { + va_list ap; va_start(ap, fmt); + std::vfprintf(stderr, fmt, ap); + std::fputc('\n', stderr); + va_end(ap); + return 0; +} +int MsgBox(const std::string& s) { + std::fprintf(stderr, "%s\n", s.c_str()); + return 0; +} + +// referenced by hsolv/main.cpp's old_main wait loop; main.cpp itself is not linked here. +inline bool IsWindow(void*) { return true; } + +struct FemmHeatDoc { + Chsolvdoc doc; + FemmHeatProgressFn cb = nullptr; + void* user = nullptr; + std::string path_buf; +}; + +extern "C" { + +FemmHeatDoc* femm_heat_doc_new(void) { + auto* d = new FemmHeatDoc(); + d->doc.TheView = &s_stub_view; + return d; +} + +void femm_heat_doc_free(FemmHeatDoc* d) { + delete d; +} + +void femm_heat_doc_set_progress(FemmHeatDoc* d, FemmHeatProgressFn fn, void* user) { + if (!d) return; + d->cb = fn; + d->user = user; +} + +int femm_heat_doc_load_feh(FemmHeatDoc* d, const char* path) { + if (!d || !path) return 0; + d->path_buf = path; + d->doc.PathName = const_cast(d->path_buf.c_str()); + return d->doc.OnOpenDocument() ? 1 : 0; +} + +int femm_heat_doc_load_mesh(FemmHeatDoc* d) { + return (d && d->doc.LoadMesh()) ? 1 : 0; +} + +// loads the previous temperature solution referenced by the .feh. +int femm_heat_doc_load_prev(FemmHeatDoc* d) { + return (d && d->doc.LoadPrev()) ? 1 : 0; +} + +int femm_heat_doc_renumber(FemmHeatDoc* d) { + return (d && d->doc.Cuthill()) ? 1 : 0; +} + +// allocates a real-valued CBigLinProb and runs AnalyzeProblem + WriteResults. +int femm_heat_doc_solve(FemmHeatDoc* d) { + if (!d) return 0; + CBigLinProb L; + L.TheView = &s_stub_view; + L.Precision = d->doc.Precision; + if (!L.Create(d->doc.NumNodes + d->doc.NumCircProps, d->doc.BandWidth)) return 0; + if (!d->doc.AnalyzeProblem(L)) return 0; + if (!d->doc.WriteResults(L)) return 0; + return 1; +} + +int femm_heat_doc_write_results(FemmHeatDoc* /*d*/, const char* /*out_path*/) { + // results currently emitted inline by solve(); reserved hook for explicit output redirection. + return 1; +} + +int femm_heat_doc_axisymmetric (const FemmHeatDoc* d) { return (d && d->doc.ProblemType) ? 1 : 0; } +double femm_heat_doc_depth (const FemmHeatDoc* d) { return d ? d->doc.Depth : 0.0; } +double femm_heat_doc_precision (const FemmHeatDoc* d) { return d ? d->doc.Precision : 0.0; } +double femm_heat_doc_dt (const FemmHeatDoc* d) { return d ? d->doc.dT : 0.0; } + +int femm_heat_doc_num_nodes (const FemmHeatDoc* d) { return d ? d->doc.NumNodes : 0; } + +void femm_heat_doc_node (const FemmHeatDoc* d, int i, double* x, double* y) { + if (!d || !d->doc.meshnode) return; + if (x) *x = d->doc.meshnode[i].x; + if (y) *y = d->doc.meshnode[i].y; +} + +int femm_heat_doc_num_elements (const FemmHeatDoc* d) { return d ? d->doc.NumEls : 0; } + +void femm_heat_doc_element (const FemmHeatDoc* d, int i, int* p0, int* p1, int* p2) { + if (!d || !d->doc.meshele) return; + if (p0) *p0 = d->doc.meshele[i].p[0]; + if (p1) *p1 = d->doc.meshele[i].p[1]; + if (p2) *p2 = d->doc.meshele[i].p[2]; +} + +int femm_heat_doc_num_materials (const FemmHeatDoc* d) { return d ? d->doc.NumBlockProps : 0; } +int femm_heat_doc_num_boundaries (const FemmHeatDoc* d) { return d ? d->doc.NumLineProps : 0; } +int femm_heat_doc_num_conductors (const FemmHeatDoc* d) { return d ? d->doc.NumCircProps : 0; } + +// field sampling is post-processor territory; not exposed by the solver alone. +double femm_heat_doc_field_at (const FemmHeatDoc* /*d*/, double /*x*/, double /*y*/, FemmHeatField /*c*/) { + return 0.0; +} + +} // extern "C" diff --git a/ffi/femm_heat.h b/ffi/femm_heat.h new file mode 100644 index 0000000..baf60da --- /dev/null +++ b/ffi/femm_heat.h @@ -0,0 +1,62 @@ +// C ABI for the heat-flow solver (hsolv): opaque doc, pipeline, mesh accessors, progress callback. +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FemmHeatDoc FemmHeatDoc; + +// pct in 0..100, or -1 for label-only updates. label may be NULL. +typedef void (*FemmHeatProgressFn)(int pct, const char* label, void* user); + +// lifetime +FemmHeatDoc* femm_heat_doc_new(void); +void femm_heat_doc_free(FemmHeatDoc* doc); + +// progress reporting routed to the host (Rust/Iced). +void femm_heat_doc_set_progress(FemmHeatDoc* doc, FemmHeatProgressFn fn, void* user); + +// pipeline. each returns 1 on success, 0 on failure. +int femm_heat_doc_load_feh (FemmHeatDoc* doc, const char* path); +int femm_heat_doc_load_mesh (FemmHeatDoc* doc); +int femm_heat_doc_load_prev (FemmHeatDoc* doc); +int femm_heat_doc_renumber (FemmHeatDoc* doc); +int femm_heat_doc_solve (FemmHeatDoc* doc); +int femm_heat_doc_write_results(FemmHeatDoc* doc, const char* out_path); + +// problem attributes after load_feh. +int femm_heat_doc_axisymmetric (const FemmHeatDoc* doc); +double femm_heat_doc_depth (const FemmHeatDoc* doc); +double femm_heat_doc_precision (const FemmHeatDoc* doc); +double femm_heat_doc_dt (const FemmHeatDoc* doc); + +// mesh, valid after load_mesh + renumber. +int femm_heat_doc_num_nodes (const FemmHeatDoc* doc); +void femm_heat_doc_node (const FemmHeatDoc* doc, int i, double* x, double* y); +int femm_heat_doc_num_elements (const FemmHeatDoc* doc); +void femm_heat_doc_element (const FemmHeatDoc* doc, int i, int* p0, int* p1, int* p2); + +// property table sizes. +int femm_heat_doc_num_materials (const FemmHeatDoc* doc); +int femm_heat_doc_num_boundaries (const FemmHeatDoc* doc); +int femm_heat_doc_num_conductors (const FemmHeatDoc* doc); + +// field-point sampling after solve. component selects which scalar to fetch. +typedef enum { + FEMM_HEAT_FIELD_T = 0, + FEMM_HEAT_FIELD_FX = 1, + FEMM_HEAT_FIELD_FY = 2, + FEMM_HEAT_FIELD_F_MAG = 3, + FEMM_HEAT_FIELD_GX = 4, + FEMM_HEAT_FIELD_GY = 5, + FEMM_HEAT_FIELD_G_MAG = 6, +} FemmHeatField; + +double femm_heat_doc_field_at(const FemmHeatDoc* doc, double x, double y, FemmHeatField component); + +#ifdef __cplusplus +} +#endif diff --git a/ffi/femm_lua_complex_ops.cpp b/ffi/femm_lua_complex_ops.cpp new file mode 100644 index 0000000..0cc8f61 --- /dev/null +++ b/ffi/femm_lua_complex_ops.cpp @@ -0,0 +1,33 @@ +// CComplex comparison ops, long-to-CComplex ctor, and 2-arg atan2 missing from fkn/complex.cpp. + +#include "../liblua/COMPLEX.H" +#include + +bool CComplex::operator<(const CComplex& z) { return re < z.re; } +bool CComplex::operator<(double z) { return re < z; } +bool CComplex::operator<(int z) { return re < (double)z; } +bool CComplex::operator<(long long z) { return re < (double)z; } + +bool CComplex::operator>(const CComplex& z) { return re > z.re; } +bool CComplex::operator>(double z) { return re > z; } +bool CComplex::operator>(int z) { return re > (double)z; } +bool CComplex::operator>(long long z) { return re > (double)z; } + +bool CComplex::operator<=(const CComplex& z) { return re <= z.re; } +bool CComplex::operator<=(double z) { return re <= z; } +bool CComplex::operator<=(int z) { return re <= (double)z; } +bool CComplex::operator<=(long long z) { return re <= (double)z; } + +bool CComplex::operator>=(const CComplex& z) { return re >= z.re; } +bool CComplex::operator>=(double z) { return re >= z; } +bool CComplex::operator>=(int z) { return re >= (double)z; } +bool CComplex::operator>=(long long z) { return re >= (double)z; } + +CComplex::CComplex(long x) { re = (double)x; im = 0.0; } +CComplex::CComplex(long long x) { re = (double)x; im = 0.0; } + +// 2-arg atan2 over CComplex. +CComplex atan2(const CComplex& y, const CComplex& x) { + if (y.im == 0 && x.im == 0) return CComplex(std::atan2(y.re, x.re), 0); + return atan(y / x); +} diff --git a/ffi/femm_mag.cpp b/ffi/femm_mag.cpp index ae30d09..47aa9cb 100644 --- a/ffi/femm_mag.cpp +++ b/ffi/femm_mag.cpp @@ -6,6 +6,7 @@ #include "../fkn/spars.h" #include "../fkn/mesh.h" #include "../fkn/FemmeDocCore.h" +#include "../fkn/lua.h" #include "femm_mag.h" @@ -13,6 +14,25 @@ #include #include +// liblua library-open entry points called from fkn's prob*big solvers. +void lua_baselibopen(lua_State* L); +void lua_iolibopen(lua_State* L); +void lua_strlibopen(lua_State* L); +void lua_mathlibopen(lua_State* L); + +// global Lua state read by fkn/prob*big.cpp during functional MagDir evaluation. +lua_State* lua = nullptr; + +// creates the shared Lua interpreter on first FFI doc construction. +static void ensure_lua_state() { + if (lua) return; + lua = lua_open(4096); + lua_baselibopen(lua); + lua_strlibopen(lua); + lua_mathlibopen(lua); + lua_iolibopen(lua); +} + // stub view: math files reference TheView globally, math methods call SetPos/SetDlgItemText/InvalidateRect on it. static CFknDlg s_stub_view; CFknDlg* TheView = &s_stub_view; @@ -43,6 +63,7 @@ struct FemmMagDoc { extern "C" { FemmMagDoc* femm_mag_doc_new(void) { + ensure_lua_state(); auto* d = new FemmMagDoc(); d->doc.TheView = &s_stub_view; return d; diff --git a/scripts/macos/build_ffi.sh b/scripts/macos/build_ffi.sh new file mode 100755 index 0000000..6e4050e --- /dev/null +++ b/scripts/macos/build_ffi.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# per-engine static archives for mag/elec/heat/curr with internal symbols localized via ld -exported_symbols_list. + +set -euo pipefail + +ROOT=$(cd "$(dirname "$0")/../.." && pwd) +BUILD=${BUILD:-"$ROOT/build/ffi"} + +CXX=${CXX:-clang++} +LD=${LD:-ld} +AR=${AR:-ar} +CXXFLAGS=${CXXFLAGS:-"-std=c++17 -fno-exceptions -fno-rtti -O2 -w"} + +mkdir -p "$BUILD"/{fkn,liblua,belasolv,csolv,hsolv,ffi} + +compile() { + local incs="$1"; shift + local outdir="$1"; shift + for src in "$@"; do + local base + base=$(basename "$src") + base=${base%.cpp} + base=${base%.CPP} + $CXX $CXXFLAGS $incs -c "$src" -o "$outdir/${base}.o" + done +} + +# liblua compiled against fkn's complex.h. +compile "-I $ROOT/fkn -I $ROOT/liblua -I $ROOT/compat" \ + "$BUILD/liblua" \ + "$ROOT/liblua/lapi.cpp" "$ROOT/liblua/lauxlib.cpp" "$ROOT/liblua/lbaselib.cpp" \ + "$ROOT/liblua/lcode.cpp" "$ROOT/liblua/ldblib.cpp" "$ROOT/liblua/ldebug.cpp" \ + "$ROOT/liblua/ldo.cpp" "$ROOT/liblua/lfunc.cpp" "$ROOT/liblua/lgc.cpp" \ + "$ROOT/liblua/liolib.cpp" "$ROOT/liblua/llex.cpp" "$ROOT/liblua/lmathlib.cpp" \ + "$ROOT/liblua/lmem.cpp" "$ROOT/liblua/lobject.cpp" "$ROOT/liblua/lparser.cpp" \ + "$ROOT/liblua/lstate.cpp" "$ROOT/liblua/lstring.cpp" "$ROOT/liblua/lstrlib.cpp" \ + "$ROOT/liblua/ltable.cpp" "$ROOT/liblua/ltests.cpp" "$ROOT/liblua/ltm.cpp" \ + "$ROOT/liblua/lundump.cpp" "$ROOT/liblua/lvm.cpp" "$ROOT/liblua/lzio.cpp" + +compile "-I $ROOT/fkn -I $ROOT/compat" \ + "$BUILD/fkn" \ + "$ROOT/fkn/complex.cpp" "$ROOT/fkn/cspars.cpp" "$ROOT/fkn/cuthill.cpp" \ + "$ROOT/fkn/femmedoccore.cpp" "$ROOT/fkn/fullmatrix.cpp" "$ROOT/fkn/matprop.cpp" \ + "$ROOT/fkn/prob1big.cpp" "$ROOT/fkn/prob2big.cpp" "$ROOT/fkn/prob3big.cpp" \ + "$ROOT/fkn/prob4big.cpp" "$ROOT/fkn/spars.cpp" + +compile "-I $ROOT/belasolv -I $ROOT/compat" \ + "$BUILD/belasolv" \ + "$ROOT/belasolv/cuthill.cpp" "$ROOT/belasolv/femmedoccore.cpp" \ + "$ROOT/belasolv/prob1big.cpp" "$ROOT/belasolv/spars.cpp" + +compile "-I $ROOT/csolv -I $ROOT/compat" \ + "$BUILD/csolv" \ + "$ROOT/csolv/complex.cpp" "$ROOT/csolv/cspars.cpp" "$ROOT/csolv/CUTHILL.CPP" \ + "$ROOT/csolv/femmedoccore.cpp" "$ROOT/csolv/PROB1BIG.CPP" + +compile "-I $ROOT/hsolv -I $ROOT/compat" \ + "$BUILD/hsolv" \ + "$ROOT/hsolv/complex.cpp" "$ROOT/hsolv/CUTHILL.CPP" "$ROOT/hsolv/hsolvdoc.cpp" \ + "$ROOT/hsolv/prob1big.cpp" "$ROOT/hsolv/SPARS.CPP" + +# ffi translation units, one per engine plus the liblua complex-op shim. +$CXX $CXXFLAGS -I "$ROOT/fkn" -I "$ROOT/compat" -c "$ROOT/ffi/femm_mag.cpp" -o "$BUILD/ffi/femm_mag.o" +$CXX $CXXFLAGS -I "$ROOT/belasolv" -I "$ROOT/compat" -c "$ROOT/ffi/femm_elec.cpp" -o "$BUILD/ffi/femm_elec.o" +$CXX $CXXFLAGS -I "$ROOT/hsolv" -I "$ROOT/compat" -c "$ROOT/ffi/femm_heat.cpp" -o "$BUILD/ffi/femm_heat.o" +$CXX $CXXFLAGS -I "$ROOT/csolv" -I "$ROOT/compat" -c "$ROOT/ffi/femm_curr.cpp" -o "$BUILD/ffi/femm_curr.o" +$CXX $CXXFLAGS -I "$ROOT/liblua" -I "$ROOT/compat" -c "$ROOT/ffi/femm_lua_complex_ops.cpp" -o "$BUILD/ffi/femm_lua_complex_ops.o" + +# per-engine exported-symbols lists. +exports_for() { + local prefix="$1" + nm "$BUILD/ffi/femm_${prefix}.o" | awk -v p="$prefix" ' + $2=="T" && $3 ~ ("^_femm_" p "_") { print $3 } + ' +} + +mkdir -p "$BUILD/exports" +exports_for mag > "$BUILD/exports/mag.txt" +exports_for elec > "$BUILD/exports/elec.txt" +exports_for heat > "$BUILD/exports/heat.txt" +exports_for curr > "$BUILD/exports/curr.txt" + +# merges each engine into a relocatable object and archives it. +$LD -r "$BUILD"/fkn/*.o "$BUILD"/liblua/*.o "$BUILD"/ffi/femm_mag.o "$BUILD"/ffi/femm_lua_complex_ops.o \ + -exported_symbols_list "$BUILD/exports/mag.txt" -o "$BUILD/femm_mag_merged.o" +$LD -r "$BUILD"/belasolv/*.o "$BUILD"/ffi/femm_elec.o \ + -exported_symbols_list "$BUILD/exports/elec.txt" -o "$BUILD/femm_elec_merged.o" +$LD -r "$BUILD"/hsolv/*.o "$BUILD"/ffi/femm_heat.o \ + -exported_symbols_list "$BUILD/exports/heat.txt" -o "$BUILD/femm_heat_merged.o" +$LD -r "$BUILD"/csolv/*.o "$BUILD"/ffi/femm_curr.o \ + -exported_symbols_list "$BUILD/exports/curr.txt" -o "$BUILD/femm_curr_merged.o" + +rm -f "$BUILD"/libfemm_*.a +$AR rcs "$BUILD/libfemm_mag.a" "$BUILD/femm_mag_merged.o" +$AR rcs "$BUILD/libfemm_elec.a" "$BUILD/femm_elec_merged.o" +$AR rcs "$BUILD/libfemm_heat.a" "$BUILD/femm_heat_merged.o" +$AR rcs "$BUILD/libfemm_curr.a" "$BUILD/femm_curr_merged.o" + +echo "built:" +ls -lh "$BUILD"/libfemm_*.a