Commit b10937f2 authored by Philipp Arras's avatar Philipp Arras
Browse files

Merge branch 'NIFTy_5' into domain_tuple_ops

parents c25acc23 3b338acb
...@@ -73,14 +73,15 @@ if __name__ == '__main__': ...@@ -73,14 +73,15 @@ if __name__ == '__main__':
# Minimize the Hamiltonian # Minimize the Hamiltonian
H = ift.Hamiltonian(likelihood, ic_sampling) H = ift.Hamiltonian(likelihood, ic_sampling)
H = ift.EnergyAdapter(position, H) H = ift.EnergyAdapter(position, H, want_metric=True)
# minimizer = ift.L_BFGS(ic_newton) # minimizer = ift.L_BFGS(ic_newton)
H, convergence = minimizer(H) H, convergence = minimizer(H)
reconstruction = sky(H.position) reconstruction = sky(H.position)
ift.plot(reconstruction, title='reconstruction') plot = ift.Plot()
ift.plot(GR.adjoint_times(data), title='data') plot.add(reconstruction, title='reconstruction')
ift.plot(sky(mock_position), title='truth') plot.add(GR.adjoint_times(data), title='data')
ift.plot_finish(nx=3, xsize=16, ysize=5, title="results", plot.add(sky(mock_position), title='truth')
name="bernoulli.png") plot.output(nx=3, xsize=16, ysize=5, title="results",
name="bernoulli.png")
...@@ -103,18 +103,17 @@ if __name__ == '__main__': ...@@ -103,18 +103,17 @@ if __name__ == '__main__':
# PLOTTING # PLOTTING
rg = isinstance(position_space, ift.RGSpace) rg = isinstance(position_space, ift.RGSpace)
plot = ift.Plot()
if rg and len(position_space.shape) == 1: if rg and len(position_space.shape) == 1:
ift.plot([HT(MOCK_SIGNAL), GR.adjoint(data), HT(m)], plot.add([HT(MOCK_SIGNAL), GR.adjoint(data), HT(m)],
label=['Mock signal', 'Data', 'Reconstruction'], label=['Mock signal', 'Data', 'Reconstruction'],
alpha=[1, .3, 1]) alpha=[1, .3, 1])
ift.plot(mask_to_nan(mask, HT(m-MOCK_SIGNAL)), title='Residuals') plot.add(mask_to_nan(mask, HT(m-MOCK_SIGNAL)), title='Residuals')
ift.plot_finish(nx=2, ny=1, xsize=10, ysize=4, plot.output(nx=2, ny=1, xsize=10, ysize=4, title="getting_started_1")
title="getting_started_1")
else: else:
ift.plot(HT(MOCK_SIGNAL), title='Mock Signal') plot.add(HT(MOCK_SIGNAL), title='Mock Signal')
ift.plot(mask_to_nan(mask, (GR(Mask)).adjoint(data)), plot.add(mask_to_nan(mask, (GR(Mask)).adjoint(data)),
title='Data') title='Data')
ift.plot(HT(m), title='Reconstruction') plot.add(HT(m), title='Reconstruction')
ift.plot(mask_to_nan(mask, HT(m-MOCK_SIGNAL)), title='Residuals') plot.add(mask_to_nan(mask, HT(m-MOCK_SIGNAL)), title='Residuals')
ift.plot_finish(nx=2, ny=2, xsize=10, ysize=10, plot.output(nx=2, ny=2, xsize=10, ysize=10, title="getting_started_1")
title="getting_started_1")
...@@ -93,14 +93,15 @@ if __name__ == '__main__': ...@@ -93,14 +93,15 @@ if __name__ == '__main__':
# Minimize the Hamiltonian # Minimize the Hamiltonian
H = ift.Hamiltonian(likelihood) H = ift.Hamiltonian(likelihood)
H = ift.EnergyAdapter(position, H) H = ift.EnergyAdapter(position, H, want_metric=True)
H, convergence = minimizer(H) H, convergence = minimizer(H)
# Plot results # Plot results
signal = sky(mock_position) signal = sky(mock_position)
reconst = sky(H.position) reconst = sky(H.position)
ift.plot(signal, title='Signal') plot = ift.Plot()
ift.plot(GR.adjoint(data), title='Data') plot.add(signal, title='Signal')
ift.plot(reconst, title='Reconstruction') plot.add(GR.adjoint(data), title='Data')
ift.plot(reconst - signal, title='Residuals') plot.add(reconst, title='Reconstruction')
ift.plot_finish(name='getting_started_2.png', xsize=16, ysize=16) plot.add(reconst - signal, title='Residuals')
plot.output(name='getting_started_2.png', xsize=16, ysize=16)
...@@ -41,11 +41,12 @@ if __name__ == '__main__': ...@@ -41,11 +41,12 @@ if __name__ == '__main__':
power_space = A.target[0] power_space = A.target[0]
power_distributor = ift.PowerDistributor(harmonic_space, power_space) power_distributor = ift.PowerDistributor(harmonic_space, power_space)
dummy = ift.Field.from_random('normal', harmonic_space) dummy = ift.Field.from_random('normal', harmonic_space)
domain = ift.MultiDomain.union( domain = ift.MultiDomain.union((A.domain,
(A.domain, ift.MultiDomain.make({'xi': harmonic_space}))) ift.MultiDomain.make({
'xi': harmonic_space
})))
correlated_field = ht( correlated_field = ht(power_distributor(A)*ift.FieldAdapter(domain, "xi"))
power_distributor(A)*ift.FieldAdapter(domain, "xi"))
# alternatively to the block above one can do: # alternatively to the block above one can do:
# correlated_field = ift.CorrelatedField(position_space, A) # correlated_field = ift.CorrelatedField(position_space, A)
...@@ -54,8 +55,7 @@ if __name__ == '__main__': ...@@ -54,8 +55,7 @@ if __name__ == '__main__':
# Building the Line of Sight response # Building the Line of Sight response
LOS_starts, LOS_ends = get_random_LOS(100) LOS_starts, LOS_ends = get_random_LOS(100)
R = ift.LOSResponse(position_space, starts=LOS_starts, R = ift.LOSResponse(position_space, starts=LOS_starts, ends=LOS_ends)
ends=LOS_ends)
# build signal response model and model likelihood # build signal response model and model likelihood
signal_response = R(signal) signal_response = R(signal)
# specify noise # specify noise
...@@ -68,8 +68,7 @@ if __name__ == '__main__': ...@@ -68,8 +68,7 @@ if __name__ == '__main__':
data = signal_response(MOCK_POSITION) + N.draw_sample() data = signal_response(MOCK_POSITION) + N.draw_sample()
# set up model likelihood # set up model likelihood
likelihood = ift.GaussianEnergy( likelihood = ift.GaussianEnergy(mean=data, covariance=N)(signal_response)
mean=data, covariance=N)(signal_response)
# set up minimization and inversion schemes # set up minimization and inversion schemes
ic_sampling = ift.GradientNormController(iteration_limit=100) ic_sampling = ift.GradientNormController(iteration_limit=100)
...@@ -83,34 +82,33 @@ if __name__ == '__main__': ...@@ -83,34 +82,33 @@ if __name__ == '__main__':
INITIAL_POSITION = ift.from_random('normal', domain) INITIAL_POSITION = ift.from_random('normal', domain)
position = INITIAL_POSITION position = INITIAL_POSITION
ift.plot(signal(MOCK_POSITION), title='ground truth') plot = ift.Plot()
ift.plot(R.adjoint_times(data), title='data') plot.add(signal(MOCK_POSITION), title='Ground Truth')
ift.plot([A(MOCK_POSITION)], title='power') plot.add(R.adjoint_times(data), title='Data')
ift.plot_finish(nx=3, xsize=16, ysize=5, title="setup", name="setup.png") plot.add([A(MOCK_POSITION)], title='Power Spectrum')
plot.output(ny=1, nx=3, xsize=24, ysize=6, name="setup.png")
# number of samples used to estimate the KL # number of samples used to estimate the KL
N_samples = 20 N_samples = 20
for i in range(2): for i in range(2):
metric = H(ift.Linearization.make_var(position)).metric KL = ift.KL_Energy(position, H, N_samples, want_metric=True)
samples = [metric.draw_sample(from_inverse=True)
for _ in range(N_samples)]
KL = ift.SampledKullbachLeiblerDivergence(H, samples)
KL = ift.EnergyAdapter(position, KL)
KL, convergence = minimizer(KL) KL, convergence = minimizer(KL)
position = KL.position position = KL.position
ift.plot(signal(position), title="reconstruction") plot = ift.Plot()
ift.plot([A(position), A(MOCK_POSITION)], title="power") plot.add(signal(KL.position), title="reconstruction")
ift.plot_finish(nx=2, xsize=12, ysize=6, title="loop", name="loop.png") plot.add([A(KL.position), A(MOCK_POSITION)], title="power")
plot.output(ny=1, ysize=6, xsize=16, name="loop.png")
plot = ift.Plot()
sc = ift.StatCalculator() sc = ift.StatCalculator()
for sample in samples: for sample in KL.samples:
sc.add(signal(sample+position)) sc.add(signal(sample+KL.position))
ift.plot(sc.mean, title="mean") plot.add(sc.mean, title="Posterior Mean")
ift.plot(ift.sqrt(sc.var), title="std deviation") plot.add(ift.sqrt(sc.var), title="Posterior Standard Deviation")
powers = [A(s+position) for s in samples] powers = [A(s+KL.position) for s in KL.samples]
ift.plot([A(position), A(MOCK_POSITION)]+powers, title="power") plot.add(
ift.plot_finish(nx=3, xsize=16, ysize=5, title="results", [A(KL.position), A(MOCK_POSITION)]+powers,
name="results.png") title="Sampled Posterior Power Spectrum")
plot.output(ny=1, nx=3, xsize=24, ysize=6, name="results.png")
...@@ -20,21 +20,24 @@ def plot_test(): ...@@ -20,21 +20,24 @@ def plot_test():
# Start various plotting tests # Start various plotting tests
ift.plot(field_rg1_1, title='Single plot') plot = ift.Plot()
ift.plot_finish() plot.add(field_rg1_1, title='Single plot')
plot.output()
ift.plot(field_rg2, title='2d rg')
ift.plot([field_rg1_1, field_rg1_2], title='list 1d rg', label=['1', '2']) plot = ift.Plot()
ift.plot(field_rg1_2, title='1d rg, xmin, ymin', xmin=0.5, ymin=0., plot.add(field_rg2, title='2d rg')
plot.add([field_rg1_1, field_rg1_2], title='list 1d rg', label=['1', '2'])
plot.add(field_rg1_2, title='1d rg, xmin, ymin', xmin=0.5, ymin=0.,
xlabel='xmin=0.5', ylabel='ymin=0') xlabel='xmin=0.5', ylabel='ymin=0')
ift.plot_finish(title='Three plots') plot.output(title='Three plots')
ift.plot(field_hp, title='HP planck-color', colormap='Planck-like') plot = ift.Plot()
ift.plot(field_rg1_2, title='1d rg') plot.add(field_hp, title='HP planck-color', colormap='Planck-like')
ift.plot(field_ps) plot.add(field_rg1_2, title='1d rg')
ift.plot(field_gl, title='GL') plot.add(field_ps)
ift.plot(field_rg2, title='2d rg') plot.add(field_gl, title='GL')
ift.plot_finish(nx=2, ny=3, title='Five plots') plot.add(field_rg2, title='2d rg')
plot.output(nx=2, ny=3, title='Five plots')
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -86,15 +86,16 @@ N = ift.DiagonalOperator(ift.from_global_data(d_space, var)) ...@@ -86,15 +86,16 @@ N = ift.DiagonalOperator(ift.from_global_data(d_space, var))
IC = ift.GradientNormController(tol_abs_gradnorm=1e-8) IC = ift.GradientNormController(tol_abs_gradnorm=1e-8)
likelihood = ift.GaussianEnergy(d, N)(R) likelihood = ift.GaussianEnergy(d, N)(R)
H = ift.Hamiltonian(likelihood, IC) Ham = ift.Hamiltonian(likelihood, IC)
H = ift.EnergyAdapter(params, H, IC) H = ift.EnergyAdapter(params, Ham, want_metric=True)
# Minimize # Minimize
minimizer = ift.NewtonCG(IC) minimizer = ift.NewtonCG(IC)
H, _ = minimizer(H) H, _ = minimizer(H)
# Draw posterior samples # Draw posterior samples
samples = [H.metric.draw_sample(from_inverse=True) + H.position metric = Ham(ift.Linearization.make_var(H.position, want_metric=True)).metric
samples = [metric.draw_sample(from_inverse=True) + H.position
for _ in range(N_samples)] for _ in range(N_samples)]
# Plotting # Plotting
......
...@@ -22,7 +22,8 @@ from .operators.operator import Operator ...@@ -22,7 +22,8 @@ from .operators.operator import Operator
from .operators.central_zero_padder import CentralZeroPadder from .operators.central_zero_padder import CentralZeroPadder
from .operators.diagonal_operator import DiagonalOperator from .operators.diagonal_operator import DiagonalOperator
from .operators.distributors import DOFDistributor, PowerDistributor from .operators.distributors import DOFDistributor, PowerDistributor
from .operators.domain_tuple_operators import DomainDistributor, DomainTupleFieldInserter from .operators.domain_tuple_operators import DomainTupleFieldInserter
from .operators.contraction_operator import ContractionOperator
from .operators.endomorphic_operator import EndomorphicOperator from .operators.endomorphic_operator import EndomorphicOperator
from .operators.exp_transform import ExpTransform from .operators.exp_transform import ExpTransform
from .operators.harmonic_operators import ( from .operators.harmonic_operators import (
...@@ -67,9 +68,10 @@ from .minimization.energy import Energy ...@@ -67,9 +68,10 @@ from .minimization.energy import Energy
from .minimization.quadratic_energy import QuadraticEnergy from .minimization.quadratic_energy import QuadraticEnergy
from .minimization.line_energy import LineEnergy from .minimization.line_energy import LineEnergy
from .minimization.energy_adapter import EnergyAdapter from .minimization.energy_adapter import EnergyAdapter
from .minimization.kl_energy import KL_Energy
from .sugar import * from .sugar import *
from .plotting.plot import plot, plot_finish from .plotting.plot import Plot
from .library.amplitude_model import AmplitudeModel from .library.amplitude_model import AmplitudeModel
from .library.inverse_gamma_model import InverseGammaModel from .library.inverse_gamma_model import InverseGammaModel
......
...@@ -41,7 +41,7 @@ def _get_acceptable_location(op, loc, lin): ...@@ -41,7 +41,7 @@ def _get_acceptable_location(op, loc, lin):
for i in range(50): for i in range(50):
try: try:
loc2 = loc+dir loc2 = loc+dir
lin2 = op(Linearization.make_var(loc2)) lin2 = op(Linearization.make_var(loc2, lin.want_metric))
if np.isfinite(lin2.val.sum()) and abs(lin2.val.sum()) < 1e20: if np.isfinite(lin2.val.sum()) and abs(lin2.val.sum()) < 1e20:
break break
except FloatingPointError: except FloatingPointError:
...@@ -54,14 +54,14 @@ def _get_acceptable_location(op, loc, lin): ...@@ -54,14 +54,14 @@ def _get_acceptable_location(op, loc, lin):
def _check_consistency(op, loc, tol, ntries, do_metric): def _check_consistency(op, loc, tol, ntries, do_metric):
for _ in range(ntries): for _ in range(ntries):
lin = op(Linearization.make_var(loc)) lin = op(Linearization.make_var(loc, do_metric))
loc2, lin2 = _get_acceptable_location(op, loc, lin) loc2, lin2 = _get_acceptable_location(op, loc, lin)
dir = loc2-loc dir = loc2-loc
locnext = loc2 locnext = loc2
dirnorm = dir.norm() dirnorm = dir.norm()
for i in range(50): for i in range(50):
locmid = loc + 0.5*dir locmid = loc + 0.5*dir
linmid = op(Linearization.make_var(locmid)) linmid = op(Linearization.make_var(locmid, do_metric))
dirder = linmid.jac(dir) dirder = linmid.jac(dir)
numgrad = (lin2.val-lin.val) numgrad = (lin2.val-lin.val)
xtol = tol * dirder.norm() / np.sqrt(dirder.size) xtol = tol * dirder.norm() / np.sqrt(dirder.size)
......
...@@ -21,8 +21,8 @@ from __future__ import absolute_import, division, print_function ...@@ -21,8 +21,8 @@ from __future__ import absolute_import, division, print_function
from ..compat import * from ..compat import *
from ..domain_tuple import DomainTuple from ..domain_tuple import DomainTuple
from ..multi_domain import MultiDomain from ..multi_domain import MultiDomain
from ..operators.contraction_operator import ContractionOperator
from ..operators.distributors import PowerDistributor from ..operators.distributors import PowerDistributor
from ..operators.domain_tuple_operators import DomainDistributor
from ..operators.harmonic_operators import HarmonicTransformOperator from ..operators.harmonic_operators import HarmonicTransformOperator
from ..operators.simple_linear_operators import FieldAdapter from ..operators.simple_linear_operators import FieldAdapter
from ..sugar import exp from ..sugar import exp
...@@ -65,8 +65,8 @@ def MfCorrelatedField(s_space_spatial, s_space_energy, amplitude_model_spatial, ...@@ -65,8 +65,8 @@ def MfCorrelatedField(s_space_spatial, s_space_energy, amplitude_model_spatial,
pd_energy = PowerDistributor(pd_spatial.domain, p_space_energy, 1) pd_energy = PowerDistributor(pd_spatial.domain, p_space_energy, 1)
pd = pd_spatial(pd_energy) pd = pd_spatial(pd_energy)
dom_distr_spatial = DomainDistributor(pd.domain, 0) dom_distr_spatial = ContractionOperator(pd.domain, 0).adjoint
dom_distr_energy = DomainDistributor(pd.domain, 1) dom_distr_energy = ContractionOperator(pd.domain, 1).adjoint
a_spatial = dom_distr_spatial(amplitude_model_spatial) a_spatial = dom_distr_spatial(amplitude_model_spatial)
a_energy = dom_distr_energy(amplitude_model_energy) a_energy = dom_distr_energy(amplitude_model_energy)
......
...@@ -53,7 +53,7 @@ class InverseGammaModel(Operator): ...@@ -53,7 +53,7 @@ class InverseGammaModel(Operator):
outer = 1/outer_inv outer = 1/outer_inv
jac = makeOp(Field.from_local_data(self._domain, inner*outer)) jac = makeOp(Field.from_local_data(self._domain, inner*outer))
jac = jac(x.jac) jac = jac(x.jac)
return Linearization(points, jac) return x.new(points, jac)
@staticmethod @staticmethod
def IG(field, alpha, q): def IG(field, alpha, q):
......
...@@ -9,13 +9,17 @@ from .sugar import makeOp ...@@ -9,13 +9,17 @@ from .sugar import makeOp
class Linearization(object): class Linearization(object):
def __init__(self, val, jac, metric=None): def __init__(self, val, jac, metric=None, want_metric=False):
self._val = val self._val = val
self._jac = jac self._jac = jac
if self._val.domain != self._jac.target: if self._val.domain != self._jac.target:
raise ValueError("domain mismatch") raise ValueError("domain mismatch")
self._want_metric = want_metric
self._metric = metric self._metric = metric
def new(self, val, jac, metric=None):
return Linearization(val, jac, metric, self._want_metric)
@property @property
def domain(self): def domain(self):
return self._jac.domain return self._jac.domain
...@@ -37,6 +41,10 @@ class Linearization(object): ...@@ -37,6 +41,10 @@ class Linearization(object):
"""Only available if target is a scalar""" """Only available if target is a scalar"""
return self._jac.adjoint_times(Field.scalar(1.)) return self._jac.adjoint_times(Field.scalar(1.))
@property
def want_metric(self):
return self._want_metric
@property @property
def metric(self): def metric(self):
"""Only available if target is a scalar""" """Only available if target is a scalar"""
...@@ -44,35 +52,34 @@ class Linearization(object): ...@@ -44,35 +52,34 @@ class Linearization(object):
def __getitem__(self, name): def __getitem__(self, name):
from .operators.simple_linear_operators import FieldAdapter from .operators.simple_linear_operators import FieldAdapter
return Linearization(self._val[name], FieldAdapter(self.domain, name)) return self.new(self._val[name], FieldAdapter(self.domain, name))
def __neg__(self): def __neg__(self):
return Linearization( return self.new(-self._val, -self._jac,
-self._val, -self._jac, None if self._metric is None else -self._metric)
None if self._metric is None else -self._metric)
def conjugate(self): def conjugate(self):
return Linearization( return self.new(
self._val.conjugate(), self._jac.conjugate(), self._val.conjugate(), self._jac.conjugate(),
None if self._metric is None else self._metric.conjugate()) None if self._metric is None else self._metric.conjugate())
@property @property
def real(self): def real(self):
return Linearization(self._val.real, self._jac.real) return self.new(self._val.real, self._jac.real)
def _myadd(self, other, neg): def _myadd(self, other, neg):
if isinstance(other, Linearization): if isinstance(other, Linearization):
met = None met = None
if self._metric is not None and other._metric is not None: if self._metric is not None and other._metric is not None:
met = self._metric._myadd(other._metric, neg) met = self._metric._myadd(other._metric, neg)
return Linearization( return self.new(
self._val.flexible_addsub(other._val, neg), self._val.flexible_addsub(other._val, neg),
self._jac._myadd(other._jac, neg), met) self._jac._myadd(other._jac, neg), met)
if isinstance(other, (int, float, complex, Field, MultiField)): if isinstance(other, (int, float, complex, Field, MultiField)):
if neg: if neg:
return Linearization(self._val-other, self._jac, self._metric) return self.new(self._val-other, self._jac, self._metric)
else: else:
return Linearization(self._val+other, self._jac, self._metric) return self.new(self._val+other, self._jac, self._metric)
def __add__(self, other): def __add__(self, other):
return self._myadd(other, False) return self._myadd(other, False)
...@@ -91,7 +98,7 @@ class Linearization(object): ...@@ -91,7 +98,7 @@ class Linearization(object):
if isinstance(other, Linearization): if isinstance(other, Linearization):
if self.target != other.target: if self.target != other.target:
raise ValueError("domain mismatch") raise ValueError("domain mismatch")
return Linearization( return self.new(
self._val*other._val, self._val*other._val,
(makeOp(other._val)(self._jac))._myadd( (makeOp(other._val)(self._jac))._myadd(
makeOp(self._val)(other._jac), False)) makeOp(self._val)(other._jac), False))
...@@ -99,11 +106,11 @@ class Linearization(object): ...@@ -99,11 +106,11 @@ class Linearization(object):
if other == 1: if other == 1:
return self return self
met = None if self._metric is None else self._metric.scale(other) met = None if self._metric is None else self._metric.scale(other)
return Linearization(self._val*other, self._jac.scale(other), met) return self.new(self._val*other, self._jac.scale(other), met)
if isinstance(other, (Field, MultiField)): if isinstance(other, (Field, MultiField)):
if self.target != other.domain: if self.target != other.domain:
raise ValueError("domain mismatch") raise ValueError("domain mismatch")
return Linearization(self._val*other, makeOp(other)(self._jac)) return self.new(self._val*other, makeOp(other)(self._jac))
def __rmul__(self, other): def __rmul__(self, other):
return self.__mul__(other) return self.__mul__(other)
...@@ -111,46 +118,48 @@ class Linearization(object): ...@@ -111,46 +118,48 @@ class Linearization(object):
def vdot(self, other): def vdot(self, other):
from .operators.simple_linear_operators import VdotOperator from .operators.simple_linear_operators import VdotOperator
if isinstance(other, (Field, MultiField)): if isinstance(other, (Field, MultiField)):
return Linearization( return self.new(
Field.scalar(self._val.vdot(other)), Field.scalar(self._val.vdot(other)),
VdotOperator(other)(self._jac)) VdotOperator(other)(self._jac))
return Linearization( return self.new(
Field.scalar(self._val.vdot(other._val)), Field.scalar(self._val.vdot(other._val)),
VdotOperator(self._val)(other._jac) + VdotOperator(self._val)(other._jac) +
VdotOperator(other._val)(self._jac)) VdotOperator(other._val)(self._jac))
def sum(self): def sum(self):
from .operators.simple_linear_operators import SumReductionOperator from .operators.simple_linear_operators import SumReductionOperator
return Linearization( return self.new(
Field.scalar(self._val.sum()), Field.scalar(self._val.sum()),
SumReductionOperator(self._jac.target)(self._jac)) SumReductionOperator(self._jac.target)(self._jac))
def exp(self): def exp(self):
tmp = self._val.exp() tmp = self._val.exp()
return Linearization(tmp, makeOp(tmp)(self._jac)) return self.new(tmp, makeOp(tmp)(self._jac))
def log(self): def log(self):
tmp = self._val.log() tmp = self._val.log()
return Linearization(tmp, makeOp(1./self._val)(self._jac)) return self.new(tmp, makeOp(1./self._val)(self._jac))