Saving and Loading Fits
nbragg provides a flexible and robust system for saving and loading both models and fit results. This allows you to:
Save fit results for later analysis
Share fitted models with collaborators
Resume analysis sessions
Archive successful fits
Reconstruct models with fitted parameters
The save/load system avoids pickle-related issues (especially with NCrystal’s ctypes) by using JSON serialization, making files human-readable and portable.
Quick Start
Basic Workflow
from nbragg import CrossSection, TransmissionModel, materials
# Create and fit a model
xs = CrossSection(iron=materials["Fe_sg229_Iron-alpha"])
model = TransmissionModel(xs, vary_background=True)
result = model.fit(data)
# Save the fit result
result.save("my_fit.json")
# Later, load it back
loaded_model = TransmissionModel.load("my_fit.json")
loaded_model.result.plot() # Access the result with all methods
Key Features
Result Objects Have save() Method
All fit results now have a save() method that makes saving straightforward:
result = model.fit(data)
result.save("my_result.json")
This automatically creates two files:
my_result.json- Contains fit statistics and parametersmy_result_model.json- Contains the model configuration
Smart File Loading
TransmissionModel.load() automatically detects whether you’re loading a model or a result file:
# Load a model configuration
model = TransmissionModel.load("my_model.json")
# Load a fit result (automatically loads the model too)
model = TransmissionModel.load("my_result.json")
model.result.redchi # Access fit statistics
Initialize from File
You can initialize a TransmissionModel directly from a saved file:
# From a model file
model = TransmissionModel("my_model.json")
# From a result file
model = TransmissionModel("my_result.json")
model.result.plot() # Has the result attached
Complete Examples
Saving and Loading Models
Save just the model configuration (without fit results):
from nbragg import CrossSection, TransmissionModel, materials
# Create a model with specific settings
xs = CrossSection(iron=materials["Fe_sg229_Iron-alpha"])
model = TransmissionModel(
xs,
vary_background=True,
vary_response=True,
tof_length=12.5
)
# Define custom stages
model.stages = {
'background': 'background',
'scale': ['norm', 'thickness'],
'response': 'response'
}
# Save the model
model.save("my_model.json")
# Load it back
loaded_model = TransmissionModel.load("my_model.json")
# or
loaded_model = TransmissionModel("my_model.json")
# Stages are preserved
print(loaded_model.stages)
# TOF length is preserved
print(loaded_model.tof_length) # 12.5
Saving and Loading Fit Results
Save complete fit results with all statistics and parameters:
# Fit the model
result = model.fit(data, wlmin=1.0, wlmax=5.0)
# Save using result.save()
result.save("my_fit.json")
# Load the result
loaded_model = TransmissionModel.load("my_fit.json")
# Access all fit information
print(f"Reduced chi-square: {loaded_model.result.redchi}")
print(f"AIC: {loaded_model.result.aic}")
print(f"BIC: {loaded_model.result.bic}")
# Access fitted parameters
for param_name, param in loaded_model.result.params.items():
if param.vary:
print(f"{param_name}: {param.value:.6f} ± {param.stderr:.6f}")
# Use all result methods
loaded_model.result.plot()
loaded_model.result.plot_total_xs()
Re-saving Loaded Results
Loaded results can be saved again, useful for archiving or sharing:
# Load a result
model = TransmissionModel.load("original_fit.json")
# Save it with a new name
model.result.save("archived_fit.json")
# Or modify parameters and save
model.params['thickness'].value = 2.5
new_result = model.fit(data)
new_result.save("modified_fit.json")
Multi-Phase Materials
Save/load works seamlessly with multi-phase materials:
# Create a multi-phase cross section
xs = CrossSection({
'alpha': {
'mat': 'Fe_sg229_Iron-alpha.ncmat',
'weight': 0.7
},
'gamma': {
'mat': 'Fe_sg225_Iron-gamma.ncmat',
'weight': 0.3
}
})
model = TransmissionModel(xs, vary_background=True, vary_weights=True)
result = model.fit(data)
# Save the result
result.save("multiphase_fit.json")
# Load it back - all phases are preserved
loaded_model = TransmissionModel.load("multiphase_fit.json")
print(loaded_model._materials.keys()) # Both phases present
Advanced Usage
Workflow Integration
Integrate save/load into your analysis workflow:
def fit_and_save(data, sample_name):
"""Fit data and save result with organized naming."""
xs = CrossSection(iron=materials["Fe_sg229_Iron-alpha"])
model = TransmissionModel(xs, vary_background=True)
result = model.fit(data)
# Save with descriptive name
filename = f"fits/{sample_name}_{result.redchi:.4f}.json"
result.save(filename)
return result
def load_best_fit(sample_name):
"""Load the best fit for a sample."""
import glob
import json
# Find all fits for this sample
pattern = f"fits/{sample_name}_*.json"
fit_files = glob.glob(pattern)
# Load and compare
best_redchi = float('inf')
best_model = None
for fit_file in fit_files:
model = TransmissionModel.load(fit_file)
if model.result.redchi < best_redchi:
best_redchi = model.result.redchi
best_model = model
return best_model
Comparing Fits
Load multiple fits and compare them:
# Load several fits
models = [
TransmissionModel.load("fit1.json"),
TransmissionModel.load("fit2.json"),
TransmissionModel.load("fit3.json")
]
# Compare statistics
for i, model in enumerate(models, 1):
print(f"Fit {i}:")
print(f" Reduced χ²: {model.result.redchi:.4f}")
print(f" AIC: {model.result.aic:.2f}")
print(f" BIC: {model.result.bic:.2f}")
# Find the best fit by AIC
best_model = min(models, key=lambda m: m.result.aic)
print(f"\nBest model has AIC = {best_model.result.aic:.2f}")
Parameter Evolution Tracking
Track how parameters change across multiple fits:
import pandas as pd
fit_files = ["fit_100K.json", "fit_200K.json", "fit_300K.json"]
temperatures = [100, 200, 300]
# Collect fitted parameters
data = []
for temp, fit_file in zip(temperatures, fit_files):
model = TransmissionModel.load(fit_file)
data.append({
'temperature': temp,
'thickness': model.result.params['thickness'].value,
'norm': model.result.params['norm'].value,
'redchi': model.result.redchi
})
df = pd.DataFrame(data)
print(df)
What Gets Saved?
Model Files
When you save a model, the following information is stored:
Material specifications: All phase definitions and properties
Parameters: All parameter values, bounds, vary flags, and expressions
Configuration: TOF length, response type, background type
Stages: Fitting stage definitions
Cross-section metadata: Name, total weight, extinction parameters
Result Files
When you save a result, the following information is stored:
Fitted parameters: Final parameter values with uncertainties
Initial parameters: Starting values before fitting
Fit statistics: χ², reduced χ², AIC, BIC
Fit details: Number of function evaluations, data points, free parameters
Success status: Whether the fit converged
Model configuration: Complete model state (saved in separate _model.json file)
File Format
Files are saved as human-readable JSON:
{
"version": "1.0",
"class": "ModelResult",
"params": "...",
"init_params": "...",
"success": true,
"chisqr": 12.345,
"redchi": 0.678,
"aic": -123.45,
"bic": -98.76,
"nfev": 42,
"nvarys": 5,
"ndata": 100
}
Best Practices
Naming Conventions
Use descriptive filenames that include:
Sample identifier
Temperature or other conditions
Date/time stamp (optional)
Quality metric (optional)
# Good naming examples
result.save(f"iron_powder_RT_{result.redchi:.4f}.json")
result.save(f"steel_100K_2024-01-15.json")
result.save(f"{sample_id}_fit.json")
Organization
Organize your fits in a directory structure:
project/
├── models/
│ ├── iron_powder_model.json
│ └── steel_texture_model.json
├── fits/
│ ├── iron_powder_RT_fit.json
│ ├── iron_powder_100K_fit.json
│ └── steel_texture_fit.json
└── analysis/
└── compare_fits.py
Version Control
JSON files work well with version control:
# Add fits to git
git add fits/*.json
git commit -m "Add temperature series fits"
Archiving
Archive important fits with metadata:
import json
from datetime import datetime
# Save the fit
result.save("fit.json")
# Create metadata file
metadata = {
'date': datetime.now().isoformat(),
'sample': 'Iron powder RT',
'operator': 'John Doe',
'notes': 'High quality fit, use for publication',
'redchi': result.redchi,
'fit_file': 'fit.json'
}
with open("fit_metadata.json", 'w') as f:
json.dump(metadata, f, indent=2)
Troubleshooting
Missing Model File
If you try to load a result file without its corresponding model file:
# This will fail if my_result_model.json is missing
model = TransmissionModel.load("my_result.json")
# FileNotFoundError: Model file my_result_model.json not found
Solution: Ensure both files are in the same directory.
Incompatible Versions
If loading an older version:
# Warning: Loading file saved with version 0.9, current version is 1.0
model = TransmissionModel.load("old_fit.json")
Solution: Re-fit and save with the current version if issues arise.
Large File Sizes
For materials with many phases or parameters, files can be large.
Solution: Use compression:
import gzip
import json
# Save compressed
with gzip.open("fit.json.gz", 'wt') as f:
json.dump(state, f)
# Load compressed
with gzip.open("fit.json.gz", 'rt') as f:
state = json.load(f)
API Reference
result.save(filename)
Save a fit result to a JSON file.
- Parameters:
filename(str): Path to the output JSON file
- Creates:
filename: Fit result datafilename.replace('.json', '_model.json'): Model configuration
Example:
result.save("my_fit.json")
TransmissionModel.save(filename)
Save the model configuration to a JSON file.
- Parameters:
filename(str): Path to the output JSON file
Example:
model.save("my_model.json")
TransmissionModel.load(filename)
Load a model or result from a JSON file.
- Parameters:
filename(str): Path to the input JSON file (model or result)
- Returns:
TransmissionModel: The reconstructed modelIf loading a result,
model.resultcontains the loaded fit result
Example:
# Load a model
model = TransmissionModel.load("my_model.json")
# Load a result
model = TransmissionModel.load("my_result.json")
print(model.result.redchi)
TransmissionModel(filename)
Initialize a TransmissionModel from a saved file.
- Parameters:
First argument can be either a CrossSection object or a filename string
Example:
# Normal initialization
model = TransmissionModel(cross_section, vary_background=True)
# Initialize from file
model = TransmissionModel("my_model.json")
model = TransmissionModel("my_result.json")
See Also
Basic Usage - Getting started with nbragg
Model Parameters - Understanding model parameters
Advanced Fitting - Advanced fitting strategies
API Reference - Complete API reference