SPIRIT is a tool for analyzing X-ray and neutron reflectivity data to determine the scattering length density (SLD) profiles of thin films and interfaces. It allows you to fit experimental reflectivity data using a layer model approach, optimizing parameters like electron density, thickness, and roughness.
- Go (version >= 1.23.3)
- GCC (Follow the instructions in their "Prerequisites")
- Git (Windows usually doesn't have this pre-installed, follow default install wizard options)
-
Install Visual Studio Code When you open the project folder it will ask you if it should install a go extension. Let it do that.
-
Install the Golang language extension from the Extension pack
Open a terminal and run the following command to clone the repository:
git clone https://github.com/empack/PortGUIPhysics/.gitThis will create a local copy of the repository on your machine.
If you prefer to download the repository as a ZIP file, follow these steps:
- Go to the repository page on GitHub: BP-PortGUIPhysics Repository
- Click the "Code" button
- Select "Download ZIP"
- Extract the downloaded ZIP file to your desired location
After cloning or downloading the repository, navigate to the project directory:
cd /path/to/repo/BP-PortGUIPhysicsMake sure you have all the prerequisites installed as mentioned in the Installation section. Then, install any additional dependencies required by the project using:
go mod tidyThere are two ways to run SPIRIT:
go run main.go# Build the application
go build -o spirit
# Run the compiled binary
# On Windows:
./spirit.exe
# On macOS/Linux:
./spiritThe SPIRIT interface consists of several main components:
- Parameter Controls: Input fields for model parameters, organized by category

- Data Visualization: Graphs showing the experimental data and model fits

- Minimization Controls: Options for fitting the model to experimental data

Supported data format is a space/tab-delimited text file with three columns:
- Q-value (momentum transfer)
- Reflectivity
- Error
The first line of the file should contain a single integer indicating the number of data points.
Parameters are organized into functional groups, for example:
- Eden (Electron Density): Controls the SLD values for each layer
- Thickness: Controls the thickness of each layer in Ångströms
- Roughness: Controls the interfacial roughness between adjacent layers
- General: Controls overall parameters like background, scaling, and q-offset
Each parameter can be:
- Manually adjusted by typing values
- Set with minimum/maximum bounds for fitting
- Included/excluded from fitting using checkboxes
- Set initial parameter values
- Check the parameters you want to include in the fit (leave unchecked for fixed parameters)
- Set minimum/maximum bounds for parameters if desired
- Click the "Start" button to start the fitting process
- Review the fit quality on the graphs
While FVal displays the error value and Calls gives the number of penalty function calls since the last update.
You can save your current parameter settings and load them later:
- Save: File > Save
- For JSON-Format use ".json" file extension
- For XML-Format use ".xml" file extension
- All not supported extensions will be saved in the GOB-Format
- Load: File > Load
- When loading JSON-Format make sure the files uses ".json" file extension
- When loading XML-Format make sure the files uses ".xml" file extension
- In All other formats, it is attempted to load them in GOB-Format
SPIRIT is designed to be customizable for different experimental setups. The main areas you might want to customize are:
To modify the number of layers in your model:
- Open
pkg/gui/main.go - Find the
registerParams()function - Add or remove parameter entries for each layer group:
// For a new layer, add parameters like:
eden3, _ := param.FloatMinMax("eden", "Eden 3", 0.458849)
thickness3, _ := param.FloatMinMax("thick", "Thickness 3", 10.0)
roughness23, _ := param.FloatMinMax("rough", "Roughness 2/3", 3.0)
roughness3B, _ := param.FloatMinMax("rough", "Roughness 3/b", 3.0)- Update the parameter container layout:
containers := container.NewVBox(
container.NewGridWithColumns(4, edenA, eden1, eden2, eden3, edenB),
container.NewGridWithColumns(4, roughnessA1, roughness12, roughness23, roughness3B),
container.NewGridWithColumns(4, thickness1, thickness2, thickness3),
container.NewGridWithColumns(4, deltaQ, background, scaling),
)- Update the
minimize()function to include new parameters:
edens := param.GetFloatGroup("eden")
e1 := edens.GetParam("Eden a")
e2 := edens.GetParam("Eden 1")
//...
if err := minimize(e1, e2, e3, e4, e5, t1, t2, t3, r1, r2, r3, r4, delta, background, scaling); err != nil {
}To implement a different physical model:
- Create a new file in the
pkg/physicsdirectory - Implement your model's calculations
- Update the
RecalculateData()function inpkg/gui/main.goto use your new calculations - Update the
penaltyFunction()as described in the next step
Example for a new physical model:
// In pkg/physics/<mymodel>.go
package physics
func MyModelCalculation(parameters []float64) function.Points {
// Your physics model implementation here
return points
}
// Then in pkg/gui/main.go, update RecalculateData() to call your function
myModelPoints := physics.MyModelCalculation(parameterArray)
functionMap["<mymodel>"].SetData(myModelPoints)The penalty function determines how the difference between model and data is calculated:
- Open
pkg/gui/main.go - Find the
penaltyFunction()function - Modify how the error is calculated:
// ...
// Calculate model data
intensityPoints := physics.CalculateIntensityPoints(edenPoints, deltaErr, &physics.IntensityOptions{
Background: backgroundErr,
Scaling: scalingErr,
})
// fetch experimental data they need to be compared to
experimentalData := graphMap["intensity"].GetDataTracks()
dataTracks := make([]function.Points, len(experimentalData))
for i, dataTrack := range experimentalData {
dataTracks[i] = dataTrack.GetData()
}
//penalty calculation, simple sum right now
// go to `pkg/gui/physics/intensity.go` to change it (for example use weights)
diff, err := physics.Sim2SigRMS(dataTracks, intensityPoints)SPIRIT makes use of the Minuit2Go package for minimization,
which uses the Minuit2 algorithm by default, but you can use other algorithms:
- Open
pkg/gui/main.go - Find the
minimize()function - Replace the Minuit2 implementation with another algorithm or library
For small changes, you might modify parameters of the existing algorithm:
// Update the strategy (more precise but slower)
migrad2 := minuit.NewMnMigradWithParameterStateStrategy(mFunc, min.UserState(),
minuit.NewMnStrategyWithStra(minuit.PreciseStrategy))For completely different algorithms, you'd need to implement a new minimizer interface.
If you make changes to the minimizer, make sure you know what you are doing.
We encourage the use of Minuit2Go.
The SPIRIT codebase is organized into several packages:
pkg/data: Data parsing and handlingpkg/function: Function representation and manipulationpkg/gui: GUI components and application logicpkg/gui/graph: Graph renderingpkg/gui/param: Parameter handlingpkg/gui/helper: Utility functions
pkg/minimizer: Optimization algorithmspkg/physics: Physics calculationspkg/trigger: Event handling
Key files to understand:
main.go: Application entry pointpkg/gui/main.go: Main GUI setup and customizationpkg/physics/eden.go: Electron density profile calculationpkg/physics/intensity.go: Reflectivity calculationpkg/minimizer/minuit_minimizer.go: Interface to Minuit2 minimization
Parameters are managed through a flexible system:
- Parameters are organized into groups by type (e.g., "eden", "thick", "rough")
- Each parameter has optional min/max bounds
- Parameters can be toggled for inclusion in fitting
- Changes to parameters trigger recalculation through the trigger system
Graphs are rendered using the Fyne toolkit:
- Multiple graph types can be displayed (eden profile, intensity)
- Data can be plotted in linear or logarithmic scale
- Experimental data can be overlaid for comparison
When parameters change, the following happens:
trigger.Recalc()is called- This triggers the
RecalculateData()function inpkg/gui/main.go - Parameters are fetched using the parameter system
- Physical calculations are performed (eden profile, intensity)
- Results are set to functions that are displayed in graphs
- Graphs are automatically refreshed
When fitting is requested:
- Parameters marked for fitting are collected
- A Minuit function is created that calculates the penalty
- The minimizer iteratively adjusts parameters to reduce the penalty
- Updated parameters are displayed in the GUI
- Graphs are refreshed to show the new fit
Tip
We encourage getting familiar with the use of Git in order to keep code sharing and modification tidy with time.

