NAME
Physics::Ellipsometry::VASE::Parameter - Named parameters with bounds, vary/fix control, and internal transformations for model fitting
SYNOPSIS
use PDL;
use Physics::Ellipsometry::VASE;
use Physics::Ellipsometry::VASE::Parameter qw(
param params_to_pdl pdl_to_params make_fit_model get_values
);
# Define model parameters with physical constraints
my @params = (
param(name => 'thickness', value => 100.0,
min => 0.0, max => 500.0),
param(name => 'n_cauchy_A', value => 2.1,
min => 1.0, max => 4.0),
param(name => 'n_cauchy_B', value => 0.01,
min => 0.0, max => 1.0),
param(name => 'angle_offset', value => 0.0, vary => 0), # fixed
);
# Convert varying parameters to a PDL for fitting
my $p0 = params_to_pdl(\@params); # 3-element piddle (angle_offset excluded)
# After fitting, update parameter values from the fitted PDL
pdl_to_params(\@params, $fitted_pdl);
# Read back all values (fixed and varying)
my @values = get_values(\@params);
printf "thickness = %.2f nm\n", $values[0];
DESCRIPTION
Levenberg-Marquardt fitting operates on an unconstrained vector of real numbers, but physical model parameters often have hard constraints: thicknesses cannot be negative, refractive indices must stay within sensible ranges, and some parameters (e.g., a known substrate angle) should be held fixed during the fit.
Physics::Ellipsometry::VASE::Parameter provides a lightweight parameter object that addresses these needs:
- Named parameters
-
Each parameter carries a human-readable name for identification in reports and diagnostics.
- Bounds enforcement via smooth transformations
-
Rather than clamping (which creates discontinuous derivatives and confuses gradient-based optimizers), bounded parameters are mapped to an unbounded internal space using invertible transformations:
- Two-sided bounds (min and max) — logit transform:
-
internal = ln( (v − min) / (max − v) ) - Lower bound only — log transform:
-
internal = ln( v − min ) - Upper bound only — negative log transform:
-
internal = −ln( max − v )
The optimizer works in internal space where all parameters are unconstrained. The transforms are smooth (C∞), so the Jacobian computed by finite differences remains well-behaved near the bounds.
- Vary/fix control
-
Parameters with
vary => 0are held at their current value and excluded from the fitted PDL vector. This lets you freeze known quantities (e.g., substrate optical constants) without rewriting the model function. - Scaling
-
An optional
scalefactor adjusts the internal representation for numerical conditioning. This is useful when parameters span very different orders of magnitude.
FUNCTIONS
param
my $p = param(
name => 'thickness',
value => 100.0,
min => 0.0,
max => 500.0,
vary => 1,
scale => 1.0,
);
Creates a parameter hashref. All keyword arguments are optional:
name— descriptive name (default:'unnamed')value— current value (default: 0.0)min— lower bound, orundeffor unbounded belowmax— upper bound, orundeffor unbounded abovevary— 1 to vary during fitting, 0 to hold fixed (default: 1)scale— internal scaling factor (default: 1.0)
# Unbounded parameter
param(name => 'offset', value => 0.5)
# Lower-bounded only (e.g., thickness ≥ 0)
param(name => 'thickness', value => 50.0, min => 0.0)
# Fixed parameter (excluded from fit)
param(name => 'angle', value => 70.0, vary => 0)
params_to_pdl
my $pdl = params_to_pdl(\@params);
Extracts the varying parameters from the list, transforms each to its internal (unbounded) representation, and returns a 1-D PDL piddle suitable for passing to the fitter. Fixed parameters are skipped.
pdl_to_params
pdl_to_params(\@params, $fitted_pdl);
The inverse of "params_to_pdl". Takes a fitted PDL of internal values and maps each back to physical space, updating the value field of each varying parameter in-place. Fixed parameters are unchanged.
get_values
my @vals = get_values(\@params);
Returns the current value of every parameter (both fixed and varying), preserving order. Convenient for building a full parameter PDL or for printing a summary.
make_fit_model
my $wrapped = make_fit_model(\@params, \&full_model);
Creates a closure that bridges the gap between the fitter (which sees only varying parameters in internal space) and the user's model function (which expects the full physical parameter vector).
The returned closure has the signature expected by "fit" in Physics::Ellipsometry::VASE:
$wrapped->($vary_pdl, $x_data) → $y_pdl
Internally it:
Transforms each element of
$vary_pdlfrom internal to physical space using the inverse bound transform.Inserts fixed parameter values at the correct positions.
Calls
$full_model->($all_params_pdl, $x_data).
COMPLETE FITTING EXAMPLE
use PDL;
use Physics::Ellipsometry::VASE;
use Physics::Ellipsometry::VASE::Parameter qw(
param params_to_pdl pdl_to_params make_fit_model get_values
);
# 1. Define parameters with physical bounds
my @params = (
param(name => 'd', value => 80.0, min => 0, max => 500),
param(name => 'A', value => 2.0, min => 1, max => 4),
param(name => 'B', value => 0.01, min => 0, max => 1),
param(name => 'C', value => 0.0, vary => 0), # fix at zero
);
# 2. Write the model in terms of a full parameter vector
sub my_model {
my ($all_p, $x) = @_;
my ($d, $A, $B, $C) = ($all_p->at(0), $all_p->at(1),
$all_p->at(2), $all_p->at(3));
# ... compute psi, delta using TMM ...
return cat($psi, $delta)->flat;
}
# 3. Wrap the model to handle bounds and fixed params
my $wrapped = make_fit_model(\@params, \&my_model);
# 4. Fit — optimizer sees only 3 unconstrained variables
my $vase = Physics::Ellipsometry::VASE->new(layers => 1);
$vase->load_data('sample.dat');
$vase->set_model($wrapped);
my $p0 = params_to_pdl(\@params);
my $fitted = $vase->fit($p0);
# 5. Map fitted values back to physical space
pdl_to_params(\@params, $fitted);
for my $p (@params) {
printf "%-12s = %8.4f %s\n",
$p->{name}, $p->{value},
$p->{vary} ? '' : '(fixed)';
}
SEE ALSO
Physics::Ellipsometry::VASE, Physics::Ellipsometry::VASE::Optimizer