From 338f3048841211d75abdd9bfd58d5451e124d5d8 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Wed, 7 Sep 2022 16:04:46 +0200 Subject: [PATCH 001/170] Implemented Invertible Neural Network for Pytorch --- phi/torch/flow.py | 2 +- phi/torch/nets.py | 198 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 177 insertions(+), 23 deletions(-) diff --git a/phi/torch/flow.py b/phi/torch/flow.py index ae13b336d..be9e17985 100644 --- a/phi/torch/flow.py +++ b/phi/torch/flow.py @@ -14,7 +14,7 @@ from phi.flow import * from . import TORCH -from .nets import parameter_count, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, SGD, rmsprop, adagrad, conv_classifier +from .nets import parameter_count, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, SGD, rmsprop, adagrad, conv_classifier, coupling_layer, inn import torch import torch.nn.functional as torchf diff --git a/phi/torch/nets.py b/phi/torch/nets.py index 946d985be..b252b9845 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -316,12 +316,12 @@ def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, ac self.layers = layers activation = ACTIVATIONS[activation] self.add_module(f'Conv_in', nn.Sequential(CONV[in_spatial](in_channels, layers[0], kernel_size=3, padding=1, padding_mode='circular'), - NORM[in_spatial](layers[0]) if batch_norm else nn.Identity(), - activation())) + NORM[in_spatial](layers[0]) if batch_norm else nn.Identity(), + activation())) for i in range(1,len(layers)): self.add_module(f'Conv{i}', nn.Sequential(CONV[in_spatial](layers[i-1], layers[i], kernel_size=3, padding=1, padding_mode='circular'), - NORM[in_spatial](layers[i]) if batch_norm else nn.Identity(), - activation())) + NORM[in_spatial](layers[i]) if batch_norm else nn.Identity(), + activation())) self.add_module(f'Conv_out', CONV[in_spatial](layers[len(layers)-1], out_channels, kernel_size=3, padding=1, padding_mode='circular')) def forward(self, x): @@ -381,6 +381,153 @@ def forward(self, x): return out +class Dense_ResNet_Block(nn.Module): + + def __init__(self, in_channels, mid_channels, batch_norm, activation): + super(Dense_ResNet_Block, self).__init__() + + self.bn1 = NORM[1](mid_channels) if batch_norm else nn.Identity() + self.linear1 = nn.Linear(in_channels, mid_channels) + + self.bn2 = NORM[1](in_channels) if batch_norm else nn.Identity() + self.linear2 = nn.Linear(mid_channels, in_channels) + + def forward(self, x): + x = TORCH.as_tensor(x) + out = self.activation()(self.bn1(self.linear1(x))) + + out = self.activation()(self.bn2(self.linear2(out))) + + out = out + x + + return out + +def get_mask(inputs, reverse_mask, data_format = 'NHWC'): + shape = inputs.shape + if len(shape) == 2: + N = shape[-1] + range_n = torch.arange(0, N) + even_ind = range_n % 2 + checker = torch.reshape(even_ind, (-1, N)) + elif len(shape) == 4: + H = shape[2] if data_format == 'NCHW' else shape[1] + W = shape[3] if data_format == 'NCHW' else shape[2] + + range_h = torch.arange(0, H) + range_w = torch.arange(0, W) + + even_ind_h = range_h % 2 + even_ind_w = range_w % 2 + + ind_h = even_ind_h.unsqueeze(-1).repeat(1, W) + ind_w = even_ind_w.unsqueeze( 0).repeat(H, 1) + + checker = torch.logical_xor(ind_h, ind_w) + + checker = checker.reshape(1, 1, H, W) if data_format == 'NCHW' else checker.reshape(1, H, W, 1) + checker = checker.long() + + else: + raise ValueError('Invalid tensor shape. Dimension of the tensor shape must be ' + '2 (NxD) or 4 (NxCxHxW or NxHxWxC), got {}.'.format(inputs.get_shape().as_list())) + + if reverse_mask: + checker = 1 - checker + + return checker.to(TORCH.get_default_device().ref) + +class Coupling_layer(nn.Module): + + def __init__(self, in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask): + super(Coupling_layer, self).__init__() + + self.activation = activation + self.batch_norm = batch_norm + self.reverse_mask = reverse_mask + + + if in_spatial == 0: #for in_spatial = 0, use dense layers + self.s1 = nn.Sequential(Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation), + torch.nn.Tanh()) + self.t1 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + + self.s2 = nn.Sequential(Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation), + torch.nn.Tanh()) + self.t2 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + else: + self.s1 = nn.Sequential(ResNet_Block(in_spatial, in_channels, in_channels, batch_norm, activation), + torch.nn.Tanh()) + self.t1 = ResNet_Block(in_spatial, in_channels, in_channels, batch_norm, activation) + + self.s2 = nn.Sequential(ResNet_Block(in_spatial, in_channels, in_channels, batch_norm, activation), + torch.nn.Tanh()) + self.t2 = ResNet_Block(in_spatial, in_channels, in_channels, batch_norm, activation) + + + def forward(self, x, invert=False): + x = TORCH.as_tensor(x) + mask = get_mask(x, self.reverse_mask, 'NCHW') + + if invert: + v1 = x * mask + v2 = x * (1-mask) + + u2 = (1-mask) * (v2 - self.t1(v1)) * torch.exp(-self.s1(v1)) + u1 = mask * (v1 - self.t2(u2)) * torch.exp(-self.s2(u2)) + + return u1 + u2 + else: + u1 = x * mask + u2 = x * (1-mask) + + v1 = mask * (u1 * torch.exp( self.s2(u2)) + self.t2(u2)) + v2 = (1-mask) * (u2 * torch.exp( self.s1(v1)) + self.t1(v1)) + + return v1 + v2 +class INN(nn.Module): + def __init__(self, in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask): + super(INN, self).__init__() + self.num_blocks = num_blocks + + for i in range(num_blocks): + self.add_module(f'coupling_block{i+1}', Coupling_layer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask)) + + def forward(self, x, backward=False): + if backward: + for i in range(self.num_blocks, 0, -1): + x = getattr(self, f'coupling_block{i}')(x, backward) + else: + for i in range(1, self.num_blocks+1): + x = getattr(self, f'coupling_block{i}')(x, backward) + return x + + +def inn(in_channels: int, + mid_channels: int, + num_blocks: int, + batch_norm: bool = False, + reverse_mask: bool = False, + activation: str or type='ReLU', + in_spatial: tuple or int=2, + backward: bool = False): + if isinstance(in_spatial, tuple): + in_spatial = len(in_spatial) + + return INN(in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask).to(TORCH.get_default_device().ref) + + +def coupling_layer(in_channels: int, + mid_channels: int, + activation: str or type='ReLU', + batch_norm=False, + reverse_mask=False, + in_spatial: tuple or int=2): + if isinstance(in_spatial, tuple): + in_spatial = len(in_spatial) + + net = Coupling_layer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask) + net = net.to(TORCH.get_default_device().ref) + return net class ResNet(nn.Module): @@ -388,21 +535,28 @@ def __init__(self,in_spatial, in_channels, out_channels, layers, batch_norm, act super(ResNet, self).__init__() self.layers = layers - self.add_module('Res_in', ResNet_Block(in_spatial, in_channels, layers[0], batch_norm, activation)) + if len(self.layers) >0: + self.add_module('Res_in', ResNet_Block(in_spatial, in_channels, layers[0], batch_norm, activation)) - for i in range(1, len(layers)): - self.add_module(f'Res{i}', ResNet_Block(in_spatial, layers[i-1], layers[i], batch_norm, activation)) + for i in range(1, len(layers)): + self.add_module(f'Res{i}', ResNet_Block(in_spatial, layers[i-1], layers[i], batch_norm, activation)) - self.add_module('Res_out', ResNet_Block(in_spatial, layers[len(layers)-1], out_channels, batch_norm, activation)) + self.add_module('Res_out', ResNet_Block(in_spatial, layers[len(layers)-1], out_channels, batch_norm, activation)) + else: + self.add_module('Res', ResNet_Block(in_spatial, in_channels, in_channels, batch_norm, activation)) def forward(self, x): x = TORCH.as_tensor(x) - x = getattr(self, 'Res_in')(x) - for i in range(1, len(self.layers)): - x = getattr(self, f'Res{i}')(x) - x = getattr(self, 'Res_out')(x) + if len(self.layers) > 0: + x = getattr(self, 'Res_in')(x) + for i in range(1, len(self.layers)): + x = getattr(self, f'Res{i}')(x) + x = getattr(self, 'Res_out')(x) + else: + print('Single Res block') + x = getattr(self, 'Res')(x) return x - + def res_net(in_channels : int, out_channels : int, layers : tuple, @@ -442,19 +596,19 @@ def __init__(self, d: int, input_shape: list, num_classes: int, batch_norm: bool self.add_module('conv2', DoubleConv(d, 64, 128, 128, batch_norm, ACTIVATIONS['ReLU'])) self.add_module('conv3', nn.Sequential(DoubleConv(d, 128, 256, 256, batch_norm, ACTIVATIONS['ReLU']), - CONV[d](256, 256, 3, padding=1, padding_mode='circular'), - NORM[d](256) if batch_norm else nn.Identity(), - nn.ReLU())) + CONV[d](256, 256, 3, padding=1, padding_mode='circular'), + NORM[d](256) if batch_norm else nn.Identity(), + nn.ReLU())) self.add_module('conv4', nn.Sequential(DoubleConv(d, 256, 512, 512, batch_norm, ACTIVATIONS['ReLU']), - CONV[d](512, 512, 3, padding=1, padding_mode='circular'), - NORM[d](512) if batch_norm else nn.Identity(), - nn.ReLU())) + CONV[d](512, 512, 3, padding=1, padding_mode='circular'), + NORM[d](512) if batch_norm else nn.Identity(), + nn.ReLU())) self.add_module('conv5', nn.Sequential(DoubleConv(d, 512, 512, 512, batch_norm, ACTIVATIONS['ReLU']), - CONV[d](512, 512, 3, padding=1, padding_mode='circular'), - NORM[d](512) if batch_norm else nn.Identity(), - nn.ReLU())) + CONV[d](512, 512, 3, padding=1, padding_mode='circular'), + NORM[d](512) if batch_norm else nn.Identity(), + nn.ReLU())) for i in range(5): for j in range(len(self.spatial_shape_list)): From 942a053436a91b6c297be0521389ae36883be85d Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Wed, 7 Sep 2022 16:11:18 +0200 Subject: [PATCH 002/170] Implemented Invertible Neural Network for Pytorch --- demos/INN_Test_Script.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 demos/INN_Test_Script.py diff --git a/demos/INN_Test_Script.py b/demos/INN_Test_Script.py new file mode 100644 index 000000000..a0083f8b4 --- /dev/null +++ b/demos/INN_Test_Script.py @@ -0,0 +1,35 @@ +import math + +from phi.torch.flow import * +net = inn(1,2,3) +#net2 = inn(1,2,3) +optimizer = adam(net, learning_rate=1e-3) + + +def loss_function(scale: Tensor, smoothness: Tensor): + grid = CenteredGrid(Noise(scale=scale, smoothness=smoothness), x=64, y=64) + + print(f'Grid Shape : {grid.shape}') + pred_scale = field.native_call(net, grid) + return math.l2_loss(pred_scale - scale) + + +gt_scale = math.random_uniform(batch(examples=50), low=1, high=10) +gt_smoothness = math.random_uniform(batch(examples=50), low=.5, high=3) + + +print(gt_scale.shape) +print(gt_smoothness.shape) + +viewer = view(gui='dash', scene=True) +for i in range(100): + loss = update_weights(net, optimizer, loss_function, gt_scale, gt_smoothness) + print(f'Iter : {i}, Loss : {loss}') + viewer.log_scalars(loss=loss) + + +grid = CenteredGrid(Noise(scale=gt_scale, smoothness=gt_smoothness), x=64, y=64) +pred = field.native_call(net, grid, False) +reconstructed_input = field.native_call(net, pred, True) +print('Loss between Predicted Tensor and original grid', math.l2_loss(pred - grid)) +print('Loss between Reconstructed Input and original grid:', math.l2_loss(reconstructed_input - grid)) \ No newline at end of file From 0ac43db395fbffea9279bf0e743ceb79359a74c1 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Thu, 8 Sep 2022 18:12:03 +0200 Subject: [PATCH 003/170] Implemented Invertible Neural Network for tensorflow and jax.stax --- demos/INN_Test_Script.py | 40 ++++++++ phi/jax/stax/flow.py | 2 +- phi/jax/stax/nets.py | 213 ++++++++++++++++++++++++++++++++++++++- phi/tf/flow.py | 2 +- phi/tf/nets.py | 177 +++++++++++++++++++++++++++++--- 5 files changed, 413 insertions(+), 21 deletions(-) create mode 100644 demos/INN_Test_Script.py diff --git a/demos/INN_Test_Script.py b/demos/INN_Test_Script.py new file mode 100644 index 000000000..50131f07d --- /dev/null +++ b/demos/INN_Test_Script.py @@ -0,0 +1,40 @@ +import math + +from phi.jax.stax.flow import * +#from phi.torch.flow import * +#from phi.tf.flow import * +#net = res_net(1, 1, [2], activation='ReLU') +#net = coupling_layer(1,2) +net = inn(1,2,10) +optimizer = adam(net, learning_rate=1e-3) + + +def loss_function(scale: Tensor, smoothness: Tensor): + grid = CenteredGrid(Noise(scale=scale, smoothness=smoothness), x=64, y=64) + + print(f'Grid Shape : {grid.shape}') + pred_scale = field.native_call(net, grid) + return math.l2_loss(pred_scale - scale) + + +gt_scale = math.random_uniform(batch(examples=50), low=1, high=10) +gt_smoothness = math.random_uniform(batch(examples=50), low=.5, high=3) + + +print(gt_scale.shape) +print(gt_smoothness.shape) + +viewer = view(gui='dash', scene=True) +for i in viewer.range(): + if i>10: + break + loss = update_weights(net, optimizer, loss_function, gt_scale, gt_smoothness) + print(f'Iter : {i}, Loss : {loss}') + viewer.log_scalars(loss=loss) + + +grid = CenteredGrid(Noise(scale=gt_scale, smoothness=gt_smoothness), x=64, y=64) +pred = field.native_call(net, grid, False) +reconstructed_input = field.native_call(net, pred, True) +print('Loss between Predicted Tensor and original grid', math.l2_loss(pred - grid)) +print('Loss between Reconstructed Input and original grid:', math.l2_loss(reconstructed_input - grid)) diff --git a/phi/jax/stax/flow.py b/phi/jax/stax/flow.py index 7c850db5f..d74d239cd 100644 --- a/phi/jax/stax/flow.py +++ b/phi/jax/stax/flow.py @@ -12,4 +12,4 @@ See `phi.flow`, `phi.torch.flow`, `phi.tf.flow`. """ from ..flow import * -from .nets import parameter_count, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, adagrad, rmsprop, SGD, conv_classifier \ No newline at end of file +from .nets import parameter_count, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, adagrad, rmsprop, SGD, conv_classifier, coupling_layer, inn \ No newline at end of file diff --git a/phi/jax/stax/nets.py b/phi/jax/stax/nets.py index c1ac900f4..87d2e5ca1 100644 --- a/phi/jax/stax/nets.py +++ b/phi/jax/stax/nets.py @@ -3,6 +3,7 @@ import warnings from typing import Callable +import keras import numpy import jax import jax.numpy as jnp @@ -553,19 +554,19 @@ def res_net(in_channels : int, activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation stax_layers = [] - stax_layers.append(resnet_block(in_channels, layers[0], batch_norm, activation, in_spatial)) + stax_layers.append(ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)) for i in range(1, len(layers)): - stax_layers.append(resnet_block(layers[i-1], layers[i], batch_norm, activation, in_spatial)) + stax_layers.append(ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)) - stax_layers.append(resnet_block(layers[len(layers)-1], out_channels, batch_norm, activation, in_spatial)) + stax_layers.append(ResNet_Block(layers[len(layers)-1], out_channels, batch_norm, activation, in_spatial)) net_init, net_apply = stax.serial(*stax_layers) net = StaxNet(net_init, net_apply, (1,) + d + (in_channels,)) net.initialize() return net -def resnet_block(in_channels : int, +def ResNet_Block(in_channels : int, out_channels : int, batch_norm : bool, activation : str or Callable = 'ReLU', @@ -620,3 +621,207 @@ def net_apply(params, inputs, **kwargs): return net_init, net_apply +def get_mask(inputs, reverse_mask, data_format = 'NHWC'): + shape = inputs.shape + if len(shape) == 2: + N = shape[-1] + range_n = jnp.arange(0, N) + even_ind = range_n % 2 + checker = jnp.reshape(even_ind, (-1, N)) + elif len(shape) == 4: + H = shape[2] if data_format == 'NCHW' else shape[1] + W = shape[3] if data_format == 'NCHW' else shape[2] + + range_h = jnp.arange(0, H) %2 + range_w = jnp.arange(0, W) %2 + + + even_ind_h = range_h.astype(bool) + even_ind_w = range_w.astype(bool) + + ind_h = jnp.tile(jnp.expand_dims(even_ind_h, -1), [1,W]) + ind_w = jnp.tile(jnp.expand_dims(even_ind_w, 0), [H,1]) + #ind_h = even_ind_h.unsqueeze(-1).repeat(1, W) + #ind_w = even_ind_w.unsqueeze( 0).repeat(H, 1) + + + checker = jnp.logical_xor(ind_h, ind_w) + + reshape = [-1, 1, H, W] if data_format == 'NCHW' else [-1, H, W, 1] + checker = jnp.reshape(checker, reshape) + checker = checker.astype(jnp.float32) + + else: + raise ValueError('Invalid tensor shape. Dimension of the tensor shape must be ' + '2 (NxD) or 4 (NxCxHxW or NxHxWxC), got {}.'.format(inputs.get_shape().as_list())) + + if reverse_mask: + checker = 1 - checker + + return checker + +def Dense_ResNet_Block(in_channels: int, + mid_channels: int, + batch_norm: bool = False, + activation : str or Callable = 'ReLU'): + inputs = keras.Input(shape = (in_channels,)) + x_1 = inputs + + activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation + init_fn, apply_fn = {}, {} + init_fn['dense1'], apply_fn['dense1'] = stax.serial(stax.Dense(mid_channels), + stax.BatchNorm(axis=(0, )), + activation) + init_fn['dense2'], apply_fn['dense2'] = stax.serial(stax.Dense(in_channels), + stax.BatchNorm(axis=(0,)), + activation) + init_activation, apply_activation = activation + + def net_init(rng, input_shape): + params = {} + rngs = random.split(rng, 2) + + shape, params['dense1'] = init_fn['dense1'](rngs[0], input_shape) + shape, params['dense2'] = init_fn['dense2'](rngs[1], shape) + shape, params['activation'] = init_activation(rngs[2], shape) + return shape, params + + def net_apply(params, inputs, **kwargs): + x = inputs + + out = apply_fn['dense1'](params['dense1'], x) + out = apply_fn['dense2'](params['dense2'], out) + + out = jnp.add(out, x) + + return out + + return net_init, net_apply + +def coupling_layer(in_channels: int, + mid_channels: int, + activation: str or Callable='ReLU', + batch_norm: bool = False, + in_spatial: int or tuple=2, + reverse_mask: bool = False): + if isinstance(in_spatial, tuple): + in_spatial = len(in_spatial) + + activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation + init_fn, apply_fn = {}, {} + if in_spatial == 0: + init_fn['s1'], apply_fn['s1'] = stax.serial(Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation), + stax.Tanh) + init_fn['t1'], apply_fn['t1'] = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + + init_fn['s2'], apply_fn['s2'] = stax.serial(Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation), + stax.Tanh) + init_fn['t2'], apply_fn['t2'] = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + else: + init_fn['s1'], apply_fn['s1'] = stax.serial(ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial), + stax.Tanh) + init_fn['t1'], apply_fn['t1'] = ResNet_Block(in_channels, in_channels, batch_norm, activation) + + init_fn['s2'], apply_fn['s2'] = stax.serial(ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial), + stax.Tanh) + init_fn['t2'], apply_fn['t2'] = ResNet_Block(in_channels, in_channels, batch_norm, activation) + + def net_init(rng, input_shape): + params = {} + rngs = random.split(rng, 2) + + shape, params['s1'] = init_fn['s1'](rngs[0], input_shape) + shape, params['t1'] = init_fn['t1'](rngs[1], input_shape) + shape, params['s2'] = init_fn['s2'](rngs[2], input_shape) + shape, params['t2'] = init_fn['t2'](rngs[3], input_shape) + + return shape, params + + def net_apply(params, inputs, invert=False): + x = inputs + + mask = get_mask(x, reverse_mask, 'NCHW') + + if invert: + v1 = x * mask + v2 = x * (1-mask) + + s1 = apply_fn['s1'](params['s1'], v1) + t1 = apply_fn['t1'](params['t1'], v1) + + u2 = (1-mask) * (v2 - t1) * jnp.exp(-s1) + + s2 = apply_fn['s2'](params['s2'], u2) + t2 = apply_fn['t2'](params['t2'], u2) + + u1 = mask * (v1 - t2) * jnp.exp(-s2) + + return u1 + u2 + else: + u1 = x * mask + u2 = x * (1-mask) + + s2 = apply_fn['s2'](params['s2'], u2) + t2 = apply_fn['t2'](params['t2'], u2) + + v1 = mask * (u1 * jnp.exp(s2) + t2) + + s1 = apply_fn['s1'](params['s1'], v1) + t1 = apply_fn['t1'](params['t1'], v1) + + v2 = (1-mask) * (u2 * jnp.exp(s1) + t1) + + return v1 + v2 + + return net_init, net_apply + +def inn(in_channels: int, + mid_channels: int, + num_blocks: int, + batch_norm: bool = False, + reverse_mask: bool = False, + activation: str or type='ReLU', + in_spatial: tuple or int=2): + if isinstance(in_spatial, tuple): + in_spatial = len(in_spatial) + + init_fn, apply_fn = {}, {} + + for i in range(num_blocks): + init_fn[f'CouplingLayer{i+1}'], apply_fn[f'CouplingLayer{i+1}'] = \ + coupling_layer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask) + + def net_init(rng, input_shape): + params = {} + rngs = random.split(rng, 2) + + for i in range(num_blocks): + shape, params[f'CouplingLayer{i+1}'] = init_fn[f'CouplingLayer{i+1}'](rngs[i], input_shape) + + return shape, params + + def net_apply(params, inputs, invert=False): + out = inputs + + if invert: + for i in range(num_blocks, 0,-1): + out = apply_fn[f'CouplingLayer{i}']( + params[f'CouplingLayer{i}'],out, invert) + else: + for i in range(1, num_blocks+1): + out = apply_fn[f'CouplingLayer{i}']( + params[f'CouplingLayer{i}'], out) + + return out + if in_spatial == 0: + net = StaxNet(net_init, net_apply, (1,) + (in_channels,)) + else: + net = StaxNet(net_init, net_apply, (1,) + (1,) * in_spatial + (in_channels,)) + net.initialize() + return net + + + + + + diff --git a/phi/tf/flow.py b/phi/tf/flow.py index 6eba080f9..b97cd00ce 100644 --- a/phi/tf/flow.py +++ b/phi/tf/flow.py @@ -14,7 +14,7 @@ from phi.flow import * from . import TENSORFLOW -from .nets import parameter_count, dense_net, u_net, save_state, load_state, update_weights, adam, conv_net, res_net, SGD, adagrad, rmsprop, conv_classifier +from .nets import parameter_count, dense_net, u_net, save_state, load_state, update_weights, adam, conv_net, res_net, SGD, adagrad, rmsprop, conv_classifier, inn import tensorflow from tensorflow import keras from tensorflow.keras import layers diff --git a/phi/tf/nets.py b/phi/tf/nets.py index 444e74966..c67de6829 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -236,24 +236,24 @@ def conv_net(in_channels: int, x = CONV[in_spatial](out_channels, 3, padding='valid')(x) return keras.Model(inputs, x) -def resnet_block(x,in_channels : int, - out_channels : int, - batch_norm : bool = False, +def ResNet_Block(in_channels: int, + out_channels: int, + batch_norm: bool = False, activation: str or Callable = 'ReLU', - in_spatial : int or tuple = 2): + in_spatial: int or tuple = 2): activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation if isinstance(in_spatial, int): d = (None,) * in_spatial else: - assert isinstance(in_spatial, tuple) + #assert isinstance(in_spatial, tuple) d = in_spatial in_spatial = len(d) d = (None,) * in_spatial - #x = inputs = keras.Input(d + (in_channels,)) - x_1 = x - x = pad_periodic(x) + inputs = keras.Input(shape = d + (in_channels,)) + x_1 = inputs + x = pad_periodic(inputs) x = CONV[in_spatial](out_channels, 3, padding='valid')(x) if batch_norm: @@ -273,9 +273,8 @@ def resnet_block(x,in_channels : int, x_1 = kl.BatchNormalization()(x_1) x = kl.Add()([x, x_1]) - #out = activation(out) - return x - #return keras.Model(inputs, out) + + return keras.Model(inputs, x) def res_net(in_channels: int, out_channels: int, @@ -292,13 +291,13 @@ def res_net(in_channels: int, in_spatial = len(d) x = inputs = keras.Input(shape=d + (in_channels,)) - #print('X shape : ', x.shape) - out = resnet_block(x, in_channels, layers[0], batch_norm, activation, in_spatial) + + out = ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)(x) for i in range(1, len(layers)): - out = resnet_block(out, layers[i-1], layers[i], batch_norm, activation, in_spatial) + out = ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)(out) - out = resnet_block(out, layers[len(layers)-1], out_channels, batch_norm, activation, in_spatial) + out = ResNet_Block(layers[len(layers)-1], out_channels, batch_norm, activation, in_spatial)(out) return keras.Model(inputs, out) @@ -359,3 +358,151 @@ def conv_classifier(input_shape: list, num_classes: int, batch_norm: bool, in_sp return keras.Model(inputs, x) +def get_mask(inputs, reverse_mask, data_format = 'NHWC'): + shape = inputs.shape + if len(shape) == 2: + N = shape[-1] + range_n = tf.range(0, N) + even_ind = range_n % 2 + checker = tf.reshape(even_ind, (-1, N)) + elif len(shape) == 4: + H = shape[2] if data_format == 'NCHW' else shape[1] + W = shape[3] if data_format == 'NCHW' else shape[2] + + range_h = tf.range(0, H) + range_w = tf.range(0, W) + + even_ind_h = tf.cast(range_h % 2, dtype=tf.bool) + even_ind_w = tf.cast(range_w % 2, dtype=tf.bool) + + ind_h = tf.tile(tf.expand_dims(even_ind_h, -1), [1,W]) + ind_w = tf.tile(tf.expand_dims(even_ind_w, 0), [H,1]) + #ind_h = even_ind_h.unsqueeze(-1).repeat(1, W) + #ind_w = even_ind_w.unsqueeze( 0).repeat(H, 1) + + checker = tf.math.logical_xor(ind_h, ind_w) + + reshape = [-1, 1, H, W] if data_format == 'NCHW' else [-1, H, W, 1] + checker = tf.reshape(checker, reshape) + checker = tf.cast(checker, dtype=tf.float32) + + else: + raise ValueError('Invalid tensor shape. Dimension of the tensor shape must be ' + '2 (NxD) or 4 (NxCxHxW or NxHxWxC), got {}.'.format(inputs.get_shape().as_list())) + + if reverse_mask: + checker = 1 - checker + + return checker + +def Dense_ResNet_Block(in_channels: int, + mid_channels: int, + batch_norm: bool = False, + activation: str or Callable = 'ReLU'): + inputs = keras.Input(shape = (in_channels,)) + x_1 = inputs + + x = kl.Dense(mid_channels)(x) + if batch_norm: + x = kl.BatchNormalization()(x) + x = activation(x) + + x = kl.Dense(in_channels)(x) + if batch_norm: + x = kl.BatchNormalization()(x) + x = activation(x) + + x = kl.Add()([x, x_1]) + + return keras.Model(inputs, x) + +class Coupling_layer(keras.Model): + + def __init__(self, in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask): + super(Coupling_layer, self).__init__() + + self.activation = activation + self.batch_norm = batch_norm + self.reverse_mask = reverse_mask + + + if in_spatial == 0: #for in_spatial = 0, use dense layers + self.s1 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + self.t1 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + + self.s2 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + self.t2 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + else: + self.s1 = ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial) + self.t1 = ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial) + + self.s2 = ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial) + self.t2 = ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial) + + def call(self, x, invert=False): + mask = get_mask(x, self.reverse_mask, 'NCHW') + + if invert: + v1 = x * mask + v2 = x * (1-mask) + + u2 = (1-mask) * (v2 - self.t1(v1)) * tf.math.exp( tf.tanh(-self.s1(v1))) + u1 = mask * (v1 - self.t2(u2)) * tf.math.exp( tf.tanh(-self.s2(u2))) + + return u1 + u2 + else: + u1 = x * mask + u2 = x * (1-mask) + + v1 = mask * (u1 * tf.math.exp( tf.tanh(self.s2(u2))) + self.t2(u2)) + v2 = (1-mask) * (u2 * tf.math.exp( tf.tanh(self.s1(v1))) + self.t1(v1)) + + return v1 + v2 + +def coupling_layer(in_channels: int, + mid_channels: int, + activation: str or type='ReLU', + batch_norm=False, + reverse_mask=False, + in_spatial: tuple or int=2): + + if isinstance(in_spatial, tuple): + in_spatial = len(in_spatial) + + net = Coupling_layer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask) + return net + +class INN(keras.Model): + def __init__(self, in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask): + super(INN, self).__init__() + self.num_blocks = num_blocks + + self.layer_dict = {} + for i in range(num_blocks): + self.layer_dict[f'coupling_block{i+1}'] = \ + Coupling_layer(in_channels, mid_channels, + activation, batch_norm, + in_spatial, reverse_mask) + + def call(self, x, backward=False): + if backward: + for i in range(self.num_blocks, 0, -1): + x = self.layer_dict[f'coupling_block{i}'](x, backward) + else: + for i in range(1, self.num_blocks+1): + x = self.layer_dict[f'coupling_block{i}'](x) + return x + +def inn(in_channels: int, + mid_channels: int, + num_blocks: int, + batch_norm: bool = False, + reverse_mask: bool = False, + activation: str or type='ReLU', + in_spatial: tuple or int=2): + if isinstance(in_spatial, tuple): + in_spatial = len(in_spatial) + + net = INN(in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask) + return net + From aa2f8dd70f1ef4946ea016c13d6680c2dfb37b0d Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Thu, 8 Sep 2022 18:17:18 +0200 Subject: [PATCH 004/170] Corrected class definitions according to python naming conventions --- phi/torch/nets.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/phi/torch/nets.py b/phi/torch/nets.py index b252b9845..7d645a333 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -436,10 +436,10 @@ def get_mask(inputs, reverse_mask, data_format = 'NHWC'): return checker.to(TORCH.get_default_device().ref) -class Coupling_layer(nn.Module): +class CouplingLayer(nn.Module): def __init__(self, in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask): - super(Coupling_layer, self).__init__() + super(CouplingLayer, self).__init__() self.activation = activation self.batch_norm = batch_norm @@ -484,13 +484,13 @@ def forward(self, x, invert=False): v2 = (1-mask) * (u2 * torch.exp( self.s1(v1)) + self.t1(v1)) return v1 + v2 -class INN(nn.Module): +class Inn(nn.Module): def __init__(self, in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask): - super(INN, self).__init__() + super(Inn, self).__init__() self.num_blocks = num_blocks for i in range(num_blocks): - self.add_module(f'coupling_block{i+1}', Coupling_layer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask)) + self.add_module(f'coupling_block{i+1}', CouplingLayer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask)) def forward(self, x, backward=False): if backward: @@ -513,7 +513,7 @@ def inn(in_channels: int, if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - return INN(in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask).to(TORCH.get_default_device().ref) + return Inn(in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask).to(TORCH.get_default_device().ref) def coupling_layer(in_channels: int, @@ -525,7 +525,7 @@ def coupling_layer(in_channels: int, if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - net = Coupling_layer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask) + net = CouplingLayer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask) net = net.to(TORCH.get_default_device().ref) return net From 937fdacc7e6f066784cd7b14b630a177943ca540 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Thu, 8 Sep 2022 20:04:22 +0200 Subject: [PATCH 005/170] INN implementations for tensorflow and jax.stax --- phi/jax/stax/nets.py | 19 +++++++++++-------- phi/tf/flow.py | 2 +- phi/tf/nets.py | 36 ++++++++++++++++++++---------------- phi/torch/nets.py | 5 +++-- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/phi/jax/stax/nets.py b/phi/jax/stax/nets.py index 87d2e5ca1..6caf7ffc7 100644 --- a/phi/jax/stax/nets.py +++ b/phi/jax/stax/nets.py @@ -269,14 +269,14 @@ def u_net(in_channels: int, d = len(in_spatial) # Create layers if use_res_blocks: - inc_init, inc_apply = resnet_block(in_channels, filters[0], batch_norm, activation, d) + inc_init, inc_apply = ResNet_Block(in_channels, filters[0], batch_norm, activation, d) else: inc_init, inc_apply = create_double_conv(d, filters[0], filters[0], batch_norm, activation) init_functions, apply_functions = {}, {} for i in range(1, levels): if use_res_blocks: - init_functions[f'down{i}'], apply_functions[f'down{i}'] = resnet_block(filters[i-1], filters[i], batch_norm, activation, d) - init_functions[f'up{i}'], apply_functions[f'up{i}'] = resnet_block(filters[i] + filters[i-1], filters[i-1], batch_norm, activation, d) + init_functions[f'down{i}'], apply_functions[f'down{i}'] = ResNet_Block(filters[i-1], filters[i], batch_norm, activation, d) + init_functions[f'up{i}'], apply_functions[f'up{i}'] = ResNet_Block(filters[i] + filters[i-1], filters[i-1], batch_norm, activation, d) else: init_functions[f'down{i}'], apply_functions[f'down{i}'] = create_double_conv(d, filters[i], filters[i], batch_norm, activation) init_functions[f'up{i}'], apply_functions[f'up{i}'] = create_double_conv(d, filters[i - 1], filters[i - 1], batch_norm, activation) @@ -552,14 +552,17 @@ def res_net(in_channels : int, d = (1,) * in_spatial activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation - stax_layers = [] - stax_layers.append(ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)) + stax_layers = [] + if len(layers) > 0: + stax_layers.append(ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)) - for i in range(1, len(layers)): - stax_layers.append(ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)) + for i in range(1, len(layers)): + stax_layers.append(ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)) - stax_layers.append(ResNet_Block(layers[len(layers)-1], out_channels, batch_norm, activation, in_spatial)) + stax_layers.append(ResNet_Block(layers[len(layers)-1], out_channels, batch_norm, activation, in_spatial)) + else: + stax_layers.append(ResNet_Block(in_channels, out_channels, batch_norm, activation, in_spatial)) net_init, net_apply = stax.serial(*stax_layers) net = StaxNet(net_init, net_apply, (1,) + d + (in_channels,)) net.initialize() diff --git a/phi/tf/flow.py b/phi/tf/flow.py index b97cd00ce..fd4de8cec 100644 --- a/phi/tf/flow.py +++ b/phi/tf/flow.py @@ -14,7 +14,7 @@ from phi.flow import * from . import TENSORFLOW -from .nets import parameter_count, dense_net, u_net, save_state, load_state, update_weights, adam, conv_net, res_net, SGD, adagrad, rmsprop, conv_classifier, inn +from .nets import parameter_count, dense_net, u_net, save_state, load_state, update_weights, adam, conv_net, res_net, SGD, adagrad, rmsprop, conv_classifier, coupling_layer,inn import tensorflow from tensorflow import keras from tensorflow.keras import layers diff --git a/phi/tf/nets.py b/phi/tf/nets.py index c67de6829..e9a0df02f 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -166,16 +166,16 @@ def u_net(in_channels: int, filters = (filters,) * levels # --- Construct the U-Net --- x = inputs = keras.Input(shape=in_spatial + (in_channels,)) - x = resnet_block(x, x.shape[-1], filters[0], batch_norm, activation, d) if use_res_blocks else double_conv(x, d, filters[0], filters[0], batch_norm, activation) + x = ResNet_Block(x.shape[-1], filters[0], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[0], filters[0], batch_norm, activation) xs = [x] for i in range(1, levels): x = MAX_POOL[d](2, padding="same")(x) - x = resnet_block(x, x.shape[-1], filters[i], batch_norm, activation, d) if use_res_blocks else double_conv(x, d, filters[i], filters[i], batch_norm, activation) + x = ResNet_Block(x.shape[-1], filters[i], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[i], filters[i], batch_norm, activation) xs.insert(0, x) for i in range(1, levels): x = UPSAMPLE[d](2)(x) x = kl.Concatenate()([x, xs[i]]) - x = resnet_block(x, x.shape[-1], filters[i-1], batch_norm, activation, d) if use_res_blocks else double_conv(x, d, filters[i - 1], filters[i - 1], batch_norm, activation) + x = ResNet_Block(x.shape[-1], filters[i-1], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[i - 1], filters[i - 1], batch_norm, activation) x = CONV[d](out_channels, 1)(x) return keras.Model(inputs, x) @@ -292,12 +292,15 @@ def res_net(in_channels: int, x = inputs = keras.Input(shape=d + (in_channels,)) - out = ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)(x) + if len(layers) > 0: + out = ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)(x) - for i in range(1, len(layers)): - out = ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)(out) + for i in range(1, len(layers)): + out = ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)(out) - out = ResNet_Block(layers[len(layers)-1], out_channels, batch_norm, activation, in_spatial)(out) + out = ResNet_Block(layers[len(layers)-1], out_channels, batch_norm, activation, in_spatial)(out) + else: + out = ResNet_Block(in_channels, out_channels, batch_norm, activation, in_spatial)(x) return keras.Model(inputs, out) @@ -401,8 +404,9 @@ def Dense_ResNet_Block(in_channels: int, activation: str or Callable = 'ReLU'): inputs = keras.Input(shape = (in_channels,)) x_1 = inputs + activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation - x = kl.Dense(mid_channels)(x) + x = kl.Dense(mid_channels)(inputs) if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) @@ -416,10 +420,10 @@ def Dense_ResNet_Block(in_channels: int, return keras.Model(inputs, x) -class Coupling_layer(keras.Model): +class CouplingLayer(keras.Model): def __init__(self, in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask): - super(Coupling_layer, self).__init__() + super(CouplingLayer, self).__init__() self.activation = activation self.batch_norm = batch_norm @@ -469,20 +473,20 @@ def coupling_layer(in_channels: int, if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - net = Coupling_layer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask) + net = CouplingLayer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask) return net -class INN(keras.Model): +class Inn(keras.Model): def __init__(self, in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask): - super(INN, self).__init__() + super(Inn, self).__init__() self.num_blocks = num_blocks self.layer_dict = {} for i in range(num_blocks): self.layer_dict[f'coupling_block{i+1}'] = \ - Coupling_layer(in_channels, mid_channels, + coupling_layer(in_channels, mid_channels, activation, batch_norm, - in_spatial, reverse_mask) + reverse_mask, in_spatial) def call(self, x, backward=False): if backward: @@ -503,6 +507,6 @@ def inn(in_channels: int, if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - net = INN(in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask) + net = Inn(in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask) return net diff --git a/phi/torch/nets.py b/phi/torch/nets.py index 60afd4ef1..5830f7dde 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -323,7 +323,7 @@ def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, ac CONV[in_spatial](layers[i - 1], layers[i], kernel_size=3, padding=1, padding_mode='circular'), NORM[in_spatial](layers[i]) if batch_norm else nn.Identity(), activation())) - self.add_module(f'Conv_out', CONV[in_spatial](layers[len(layers) - 1], out_channels, kernel_size=3, padding=1, padding_mode='circular') + self.add_module(f'Conv_out', CONV[in_spatial](layers[len(layers) - 1], out_channels, kernel_size=3, padding=1, padding_mode='circular')) def forward(self, x): x = getattr(self, f'Conv_in')(x) @@ -484,6 +484,7 @@ def forward(self, x, invert=False): v2 = (1-mask) * (u2 * torch.exp( self.s1(v1)) + self.t1(v1)) return v1 + v2 + class Inn(nn.Module): def __init__(self, in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask): super(Inn, self).__init__() @@ -543,7 +544,7 @@ def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, ac self.add_module('Res_out', ResNet_Block(in_spatial, layers[len(layers)-1], out_channels, batch_norm, activation)) else: - self.add_module('Res', ResNet_Block(in_spatial, in_channels, in_channels, batch_norm, activation)) + self.add_module('Res', ResNet_Block(in_spatial, in_channels, out_channels, batch_norm, activation)) def forward(self, x): x = TORCH.as_tensor(x) From 9d999dbf3d7d7ceed26198182c05942beb54d5b9 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Thu, 15 Sep 2022 12:48:19 +0200 Subject: [PATCH 006/170] Added support for arbitrary neural networks in Invertible nets --- demos/INN_Test_Script.py | 39 ++---- phi/jax/stax/flow.py | 2 +- phi/jax/stax/nets.py | 238 +++++++++++++++++++++++++++++---- phi/tf/flow.py | 2 +- phi/tf/nets.py | 85 ++++++------ phi/torch/flow.py | 2 +- phi/torch/nets.py | 240 ++++++++++++++++++---------------- tests/commit/test_networks.py | 77 +++++++++++ 8 files changed, 469 insertions(+), 216 deletions(-) diff --git a/demos/INN_Test_Script.py b/demos/INN_Test_Script.py index 50131f07d..3bca1d1c6 100644 --- a/demos/INN_Test_Script.py +++ b/demos/INN_Test_Script.py @@ -1,40 +1,29 @@ import math - -from phi.jax.stax.flow import * -#from phi.torch.flow import * -#from phi.tf.flow import * -#net = res_net(1, 1, [2], activation='ReLU') -#net = coupling_layer(1,2) -net = inn(1,2,10) +from phi.torch.flow import * +net = invertible_net(1, 3, True, 'u_net', 'SiLU') optimizer = adam(net, learning_rate=1e-3) +print(parameter_count(net)) -def loss_function(scale: Tensor, smoothness: Tensor): - grid = CenteredGrid(Noise(scale=scale, smoothness=smoothness), x=64, y=64) - - print(f'Grid Shape : {grid.shape}') - pred_scale = field.native_call(net, grid) - return math.l2_loss(pred_scale - scale) - +def loss_function(smoothness: Tensor): + grid = CenteredGrid(Noise(smoothness=smoothness), x=8, y=8) + pred_smoothness = field.native_call(net, grid) -gt_scale = math.random_uniform(batch(examples=50), low=1, high=10) -gt_smoothness = math.random_uniform(batch(examples=50), low=.5, high=3) + return math.l2_loss(pred_smoothness - smoothness) - -print(gt_scale.shape) -print(gt_smoothness.shape) +gt_smoothness = math.random_uniform(batch(examples=10), low=0.5, high=1) viewer = view(gui='dash', scene=True) for i in viewer.range(): - if i>10: - break - loss = update_weights(net, optimizer, loss_function, gt_scale, gt_smoothness) - print(f'Iter : {i}, Loss : {loss}') + if i > 100: break + loss = update_weights(net, optimizer, loss_function, gt_smoothness) + if i % 10 == 0: print(f'Iter : {i}, Loss : {loss}') viewer.log_scalars(loss=loss) - -grid = CenteredGrid(Noise(scale=gt_scale, smoothness=gt_smoothness), x=64, y=64) +grid = CenteredGrid(Noise(scale=1.0, smoothness=gt_smoothness), x=8, y=8) pred = field.native_call(net, grid, False) reconstructed_input = field.native_call(net, pred, True) + print('Loss between Predicted Tensor and original grid', math.l2_loss(pred - grid)) +print('Loss between Predicted Tensor and GT tensor', math.l2_loss(pred - gt_smoothness)) print('Loss between Reconstructed Input and original grid:', math.l2_loss(reconstructed_input - grid)) diff --git a/phi/jax/stax/flow.py b/phi/jax/stax/flow.py index d74d239cd..df4e51b33 100644 --- a/phi/jax/stax/flow.py +++ b/phi/jax/stax/flow.py @@ -12,4 +12,4 @@ See `phi.flow`, `phi.torch.flow`, `phi.tf.flow`. """ from ..flow import * -from .nets import parameter_count, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, adagrad, rmsprop, SGD, conv_classifier, coupling_layer, inn \ No newline at end of file +from .nets import parameter_count, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, adagrad, rmsprop, SGD, conv_classifier, coupling_layer, invertible_net \ No newline at end of file diff --git a/phi/jax/stax/nets.py b/phi/jax/stax/nets.py index 6caf7ffc7..7d0c42783 100644 --- a/phi/jax/stax/nets.py +++ b/phi/jax/stax/nets.py @@ -260,7 +260,7 @@ def u_net(in_channels: int, assert len(filters) == levels, f"List of filters has length {len(filters)} but u-net has {levels} levels." else: filters = (filters,) * levels - activation = ACTIVATIONS[activation] + activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation if isinstance(in_spatial, int): d = in_spatial in_spatial = (1,) * d @@ -490,7 +490,7 @@ def net_apply(params, inputs, **kwargs): def conv_net(in_channels : int, out_channels: int, - layers : tuple, + layers : tuple = [], batch_norm : bool = False, activation:str or Callable = 'ReLU', in_spatial : int or tuple = 2) ->StaxNet: @@ -502,22 +502,31 @@ def conv_net(in_channels : int, if isinstance(activation, str): activation = ACTIVATIONS[activation] - stax_layers = [] - init_fn, apply_fn = {}, {} - for i in range(len(layers)): - init_fn[f'conv{i+1}'], apply_fn[f'conv{i+1}'] = stax.serial(CONV[in_spatial](out_channels, (3,)*in_spatial, padding = 'valid'), + if len(layers) < 1: + layers.append(out_channels) + + init_fn['conv_in'], apply_fn['conv_in'] = stax.serial(CONV[in_spatial](layers[0], (3,)*in_spatial, padding = 'valid'), + stax.BatchNorm(axis=tuple(range(in_spatial+1))) if batch_norm else stax.Identity, + activation) + for i in range(1,len(layers)): + init_fn[f'conv{i}'], apply_fn[f'conv{i}'] = stax.serial(CONV[in_spatial](layers[i], (3,)*in_spatial, padding = 'valid'), stax.BatchNorm(axis=tuple(range(in_spatial+1))) if batch_norm else stax.Identity, activation) + init_fn['conv_out'], apply_fn['conv_out'] = CONV[in_spatial](out_channels, (1,)*in_spatial) + def net_init(rng, input_shape): params = {} rngs = random.split(rng, 2) - shape, params['conv1'] = init_fn['conv1'](rngs[0], input_shape) + shape, params['conv_in'] = init_fn['conv_in'](rngs[0], input_shape) + for i in range(1,len(layers)): shape, params[f'conv{i+1}'] = init_fn[f'conv{i+1}'](rngs[i], shape) + shape, params['conv_out'] = init_fn['conv_out'](rngs[len(layers)], shape) + return shape, params def net_apply(params, inputs): @@ -529,10 +538,15 @@ def net_apply(params, inputs): pad_tuple.append((0,0)) out = jnp.pad(x, pad_width=pad_tuple, mode='wrap') - out = apply_fn['conv1'](params['conv1'], out) + + out = apply_fn['conv_in'](params['conv_in'], out) + for i in range(1,len(layers)): out = jnp.pad(out, pad_width=pad_tuple, mode='wrap') out = apply_fn[f'conv{i+1}'](params[f'conv{i+1}'], out) + + out = apply_fn['conv_out'](params['conv_out'], out) + return out net = StaxNet(net_init, net_apply, (1,) + d + (in_channels,)) @@ -541,7 +555,7 @@ def net_apply(params, inputs): def res_net(in_channels : int, out_channels : int, - layers : tuple, + layers : tuple = [], batch_norm : bool = False, activation : str or Callable = 'ReLU', in_spatial : int or tuple=2) -> StaxNet: @@ -701,11 +715,172 @@ def net_apply(params, inputs, **kwargs): return net_init, net_apply +def conv_net_unit(in_channels : int, + out_channels: int, + layers : tuple = [], + batch_norm : bool = False, + activation:str or Callable = 'ReLU', + in_spatial : int or tuple = 2): + if isinstance(in_spatial,tuple): + d = in_spatial + in_spatial = len(in_spatial) + else: + d = (1,) * in_spatial + if isinstance(activation, str): + activation = ACTIVATIONS[activation] + + init_fn, apply_fn = {}, {} + if len(layers) < 1: + layers.append(out_channels) + init_fn['conv_in'], apply_fn['conv_in'] = stax.serial(CONV[in_spatial](layers[0], (3,)*in_spatial, padding = 'valid'), + stax.BatchNorm(axis=tuple(range(in_spatial+1))) if batch_norm else stax.Identity, + activation) + for i in range(1,len(layers)): + init_fn[f'conv{i}'], apply_fn[f'conv{i}'] = stax.serial(CONV[in_spatial](layers[i], (3,)*in_spatial, padding = 'valid'), + stax.BatchNorm(axis=tuple(range(in_spatial+1))) if batch_norm else stax.Identity, + activation) + + init_fn['conv_out'], apply_fn['conv_out'] = CONV[in_spatial](out_channels, (1,)*in_spatial) + + def net_init(rng, input_shape): + params = {} + rngs = random.split(rng, 2) + + shape, params['conv_in'] = init_fn['conv_in'](rngs[0], input_shape) + + for i in range(1,len(layers)): + shape, params[f'conv{i+1}'] = init_fn[f'conv{i+1}'](rngs[i], shape) + + shape, params['conv_out'] = init_fn['conv_out'](rngs[len(layers)], shape) + + return shape, params + + def net_apply(params, inputs): + x = inputs + + pad_tuple = [(0, 0)] + for i in range(in_spatial): + pad_tuple.append((1,1)) + pad_tuple.append((0,0)) + + out = jnp.pad(x, pad_width=pad_tuple, mode='wrap') + + out = apply_fn['conv_in'](params['conv_in'], out) + + for i in range(1,len(layers)): + out = jnp.pad(out, pad_width=pad_tuple, mode='wrap') + out = apply_fn[f'conv{i+1}'](params[f'conv{i+1}'], out) + + out = apply_fn['conv_out'](params['conv_out'], out) + + return out + + return net_init, net_apply + +def u_net_unit(in_channels: int, + out_channels: int, + levels: int = 4, + filters: int or tuple or list = 16, + batch_norm: bool = True, + activation='ReLU', + in_spatial: tuple or int = 2, + use_res_blocks: bool = False): + if isinstance(filters, (tuple, list)): + assert len(filters) == levels, f"List of filters has length {len(filters)} but u-net has {levels} levels." + else: + filters = (filters,) * levels + activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation + if isinstance(in_spatial, int): + d = in_spatial + in_spatial = (1,) * d + else: + assert isinstance(in_spatial, tuple) + d = len(in_spatial) + # Create layers + if use_res_blocks: + inc_init, inc_apply = ResNet_Block(in_channels, filters[0], batch_norm, activation, d) + else: + inc_init, inc_apply = create_double_conv(d, filters[0], filters[0], batch_norm, activation) + init_functions, apply_functions = {}, {} + for i in range(1, levels): + if use_res_blocks: + init_functions[f'down{i}'], apply_functions[f'down{i}'] = ResNet_Block(filters[i-1], filters[i], batch_norm, activation, d) + init_functions[f'up{i}'], apply_functions[f'up{i}'] = ResNet_Block(filters[i] + filters[i-1], filters[i-1], batch_norm, activation, d) + else: + init_functions[f'down{i}'], apply_functions[f'down{i}'] = create_double_conv(d, filters[i], filters[i], batch_norm, activation) + init_functions[f'up{i}'], apply_functions[f'up{i}'] = create_double_conv(d, filters[i - 1], filters[i - 1], batch_norm, activation) + outc_init, outc_apply = CONV[d](out_channels, (1,) * d, padding='same') + max_pool_init, max_pool_apply = stax.MaxPool((2,) * d, padding='same', strides=(2,) * d) + _, up_apply = create_upsample() + + def net_init(rng, input_shape): + params = {} + rngs = random.split(rng, 2) + shape = input_shape + # Layers + shape, params['inc'] = inc_init(rngs[0], shape) + shapes = [shape] + for i in range(1, levels): + shape, _ = max_pool_init(None, shape) + shape, params[f'down{i}'] = init_functions[f'down{i}'](rngs[i], shape) + shapes.insert(0, shape) + for i in range(1, levels): + shape = shapes[i][:-1] + (shapes[i][-1] + shape[-1],) + shape, params[f'up{i}'] = init_functions[f'up{i}'](rngs[levels+i], shape) + shape, params['outc'] = outc_init(rngs[-1], shape) + return shape, params + + # no @jax.jit needed here since the user can jit this in the loss_function + def net_apply(params, inputs, **kwargs): + x = inputs + x = inc_apply(params['inc'], x, **kwargs) + xs = [x] + for i in range(1, levels): + x = max_pool_apply(None, x, **kwargs) + x = apply_functions[f'down{i}'](params[f'down{i}'], x, **kwargs) + xs.insert(0, x) + for i in range(1, levels): + x = up_apply(None, x, **kwargs) + x = jnp.concatenate([x, xs[i]], axis=-1) + x = apply_functions[f'up{i}'](params[f'up{i}'], x, **kwargs) + x = outc_apply(params['outc'], x, **kwargs) + return x + + return net_init, net_apply + +def res_net_unit(in_channels : int, + out_channels : int, + layers : tuple = [], + batch_norm : bool = False, + activation : str or Callable = 'ReLU', + in_spatial : int or tuple=2): + if isinstance(in_spatial, tuple): + d = in_spatial + in_spatial = len(in_spatial) + else: + d = (1,) * in_spatial + + activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation + + stax_layers = [] + if len(layers) < 1: + layers.append(out_channels) + stax_layers.append(ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)) + + for i in range(1, len(layers)): + stax_layers.append(ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)) + + stax_layers.append(CONV[in_spatial](out_channels, (1,)*in_spatial)) + + return stax.serial(*stax_layers) + +NET = {'u_net': u_net_unit, 'res_net': res_net_unit, 'conv_net': conv_net_unit} + def coupling_layer(in_channels: int, - mid_channels: int, activation: str or Callable='ReLU', batch_norm: bool = False, in_spatial: int or tuple=2, + net: str = 'u_net', reverse_mask: bool = False): if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) @@ -713,21 +888,27 @@ def coupling_layer(in_channels: int, activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation init_fn, apply_fn = {}, {} if in_spatial == 0: - init_fn['s1'], apply_fn['s1'] = stax.serial(Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation), + init_fn['s1'], apply_fn['s1'] = stax.serial(Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation), stax.Tanh) - init_fn['t1'], apply_fn['t1'] = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + init_fn['t1'], apply_fn['t1'] = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) - init_fn['s2'], apply_fn['s2'] = stax.serial(Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation), + init_fn['s2'], apply_fn['s2'] = stax.serial(Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation), stax.Tanh) - init_fn['t2'], apply_fn['t2'] = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + init_fn['t2'], apply_fn['t2'] = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) else: - init_fn['s1'], apply_fn['s1'] = stax.serial(ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial), - stax.Tanh) - init_fn['t1'], apply_fn['t1'] = ResNet_Block(in_channels, in_channels, batch_norm, activation) - - init_fn['s2'], apply_fn['s2'] = stax.serial(ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial), - stax.Tanh) - init_fn['t2'], apply_fn['t2'] = ResNet_Block(in_channels, in_channels, batch_norm, activation) + init_fn['s1'], apply_fn['s1'] = NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) + init_fn['t1'], apply_fn['t1'] = NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) + + init_fn['s2'], apply_fn['s2'] = NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) + init_fn['t2'], apply_fn['t2'] = NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) def net_init(rng, input_shape): params = {} @@ -752,12 +933,12 @@ def net_apply(params, inputs, invert=False): s1 = apply_fn['s1'](params['s1'], v1) t1 = apply_fn['t1'](params['t1'], v1) - u2 = (1-mask) * (v2 - t1) * jnp.exp(-s1) + u2 = (1-mask) * (v2 - t1) * jnp.exp(-jnp.tanh(s1)) s2 = apply_fn['s2'](params['s2'], u2) t2 = apply_fn['t2'](params['t2'], u2) - u1 = mask * (v1 - t2) * jnp.exp(-s2) + u1 = mask * (v1 - t2) * jnp.exp(-jnp.tanh(s2)) return u1 + u2 else: @@ -767,22 +948,21 @@ def net_apply(params, inputs, invert=False): s2 = apply_fn['s2'](params['s2'], u2) t2 = apply_fn['t2'](params['t2'], u2) - v1 = mask * (u1 * jnp.exp(s2) + t2) + v1 = mask * (u1 * jnp.exp(jnp.tanh(s2)) + t2) s1 = apply_fn['s1'](params['s1'], v1) t1 = apply_fn['t1'](params['t1'], v1) - v2 = (1-mask) * (u2 * jnp.exp(s1) + t1) + v2 = (1-mask) * (u2 * jnp.exp(jnp.tanh(s1)) + t1) return v1 + v2 return net_init, net_apply -def inn(in_channels: int, - mid_channels: int, +def invertible_net(in_channels: int, num_blocks: int, batch_norm: bool = False, - reverse_mask: bool = False, + net: str = 'u_net', activation: str or type='ReLU', in_spatial: tuple or int=2): if isinstance(in_spatial, tuple): @@ -792,7 +972,7 @@ def inn(in_channels: int, for i in range(num_blocks): init_fn[f'CouplingLayer{i+1}'], apply_fn[f'CouplingLayer{i+1}'] = \ - coupling_layer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask) + coupling_layer(in_channels, activation, batch_norm, in_spatial, net, (i%2==0)) def net_init(rng, input_shape): params = {} diff --git a/phi/tf/flow.py b/phi/tf/flow.py index fd4de8cec..19330660c 100644 --- a/phi/tf/flow.py +++ b/phi/tf/flow.py @@ -14,7 +14,7 @@ from phi.flow import * from . import TENSORFLOW -from .nets import parameter_count, dense_net, u_net, save_state, load_state, update_weights, adam, conv_net, res_net, SGD, adagrad, rmsprop, conv_classifier, coupling_layer,inn +from .nets import parameter_count, dense_net, u_net, save_state, load_state, update_weights, adam, conv_net, res_net, SGD, adagrad, rmsprop, conv_classifier,invertible_net import tensorflow from tensorflow import keras from tensorflow.keras import layers diff --git a/phi/tf/nets.py b/phi/tf/nets.py index e9a0df02f..65dc76d50 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -214,7 +214,7 @@ def double_conv(x, d: int, out_channels: int, mid_channels: int, batch_norm: boo def conv_net(in_channels: int, out_channels: int, - layers: tuple, + layers: tuple = [], batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial:int or tuple=2) -> keras.Model: @@ -226,14 +226,16 @@ def conv_net(in_channels: int, in_spatial = len(d) activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation x = inputs = keras.Input(shape=d + (in_channels,)) + if len(layers) < 1: + layers.append(out_channels) for i in range(len(layers)): x = pad_periodic(x) x = CONV[in_spatial](layers[i], 3, padding='valid')(x) if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) - x = pad_periodic(x) - x = CONV[in_spatial](out_channels, 3, padding='valid')(x) + #x = pad_periodic(x) + x = CONV[in_spatial](out_channels, 1)(x) return keras.Model(inputs, x) def ResNet_Block(in_channels: int, @@ -278,7 +280,7 @@ def ResNet_Block(in_channels: int, def res_net(in_channels: int, out_channels: int, - layers: tuple, + layers: tuple = [], batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple=2): @@ -292,15 +294,15 @@ def res_net(in_channels: int, x = inputs = keras.Input(shape=d + (in_channels,)) - if len(layers) > 0: - out = ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)(x) + if len(layers) < 1: + layers.append(out_channels) + out = ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)(x) - for i in range(1, len(layers)): - out = ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)(out) + for i in range(1, len(layers)): + out = ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)(out) + + out = CONV[in_spatial](out_channels, 1)(out) - out = ResNet_Block(layers[len(layers)-1], out_channels, batch_norm, activation, in_spatial)(out) - else: - out = ResNet_Block(in_channels, out_channels, batch_norm, activation, in_spatial)(x) return keras.Model(inputs, out) @@ -420,9 +422,10 @@ def Dense_ResNet_Block(in_channels: int, return keras.Model(inputs, x) +NET = {'u_net': u_net, 'res_net': res_net, 'conv_net': conv_net} class CouplingLayer(keras.Model): - def __init__(self, in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask): + def __init__(self, in_channels, activation, batch_norm, in_spatial, net, reverse_mask): super(CouplingLayer, self).__init__() self.activation = activation @@ -431,17 +434,25 @@ def __init__(self, in_channels, mid_channels, activation, batch_norm, in_spatial if in_spatial == 0: #for in_spatial = 0, use dense layers - self.s1 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) - self.t1 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + self.s1 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) + self.t1 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) - self.s2 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) - self.t2 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) + self.s2 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) + self.t2 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) else: - self.s1 = ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial) - self.t1 = ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial) - - self.s2 = ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial) - self.t2 = ResNet_Block(in_channels, in_channels, batch_norm, activation, in_spatial) + self.s1 = NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) + self.t1 = NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) + + self.s2 = NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) + self.t2 = NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) def call(self, x, invert=False): mask = get_mask(x, self.reverse_mask, 'NCHW') @@ -463,30 +474,17 @@ def call(self, x, invert=False): return v1 + v2 -def coupling_layer(in_channels: int, - mid_channels: int, - activation: str or type='ReLU', - batch_norm=False, - reverse_mask=False, - in_spatial: tuple or int=2): - - if isinstance(in_spatial, tuple): - in_spatial = len(in_spatial) - - net = CouplingLayer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask) - return net - -class Inn(keras.Model): - def __init__(self, in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask): - super(Inn, self).__init__() +class InvertibleNet(keras.Model): + def __init__(self, in_channels, num_blocks, activation, batch_norm, in_spatial, net): + super(InvertibleNet, self).__init__() self.num_blocks = num_blocks self.layer_dict = {} for i in range(num_blocks): self.layer_dict[f'coupling_block{i+1}'] = \ - coupling_layer(in_channels, mid_channels, + CouplingLayer(in_channels, activation, batch_norm, - reverse_mask, in_spatial) + in_spatial, net, (i%2==0)) def call(self, x, backward=False): if backward: @@ -497,16 +495,15 @@ def call(self, x, backward=False): x = self.layer_dict[f'coupling_block{i}'](x) return x -def inn(in_channels: int, - mid_channels: int, +def invertible_net(in_channels: int, num_blocks: int, batch_norm: bool = False, - reverse_mask: bool = False, + net: str = 'u_net', activation: str or type='ReLU', in_spatial: tuple or int=2): if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - net = Inn(in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask) - return net + return InvertibleNet(in_channels, num_blocks, activation, + batch_norm, in_spatial, net) diff --git a/phi/torch/flow.py b/phi/torch/flow.py index c15c41042..12aec5b69 100644 --- a/phi/torch/flow.py +++ b/phi/torch/flow.py @@ -14,7 +14,7 @@ from phi.flow import * from . import TORCH -from .nets import parameter_count, get_parameters, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, sgd, sgd as SGD, rmsprop, adagrad, conv_classifier, coupling_layer, inn +from .nets import parameter_count, get_parameters, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, sgd, sgd as SGD, rmsprop, adagrad, conv_classifier, invertible_net import torch import torch.nn.functional as torchf diff --git a/phi/torch/nets.py b/phi/torch/nets.py index 5830f7dde..9b61b1d78 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -311,9 +311,14 @@ class ConvNet(nn.Module): def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, activation): super(ConvNet, self).__init__() - self.layers = layers + activation = ACTIVATIONS[activation] + if len(layers) < 1: + layers.append(out_channels) + + self.layers = layers + self.add_module(f'Conv_in', nn.Sequential( CONV[in_spatial](in_channels, layers[0], kernel_size=3, padding=1, padding_mode='circular'), NORM[in_spatial](layers[0]) if batch_norm else nn.Identity(), @@ -323,19 +328,21 @@ def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, ac CONV[in_spatial](layers[i - 1], layers[i], kernel_size=3, padding=1, padding_mode='circular'), NORM[in_spatial](layers[i]) if batch_norm else nn.Identity(), activation())) - self.add_module(f'Conv_out', CONV[in_spatial](layers[len(layers) - 1], out_channels, kernel_size=3, padding=1, padding_mode='circular')) + self.add_module(f'Conv_out', CONV[in_spatial](layers[len(layers) - 1], out_channels, kernel_size=1)) def forward(self, x): + x = getattr(self, f'Conv_in')(x) for i in range(1, len(self.layers)): x = getattr(self, f'Conv{i}')(x) x = getattr(self, f'Conv_out')(x) + return x def conv_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int], + layers: Tuple[int, ...] or List[int] = [], batch_norm: bool = False, activation: str or type = 'ReLU', in_spatial: int or tuple = 2) -> nn.Module: @@ -386,10 +393,11 @@ class Dense_ResNet_Block(nn.Module): def __init__(self, in_channels, mid_channels, batch_norm, activation): super(Dense_ResNet_Block, self).__init__() - self.bn1 = NORM[1](mid_channels) if batch_norm else nn.Identity() + self.activation = activation + self.bn1 = NORM[1](in_channels) if batch_norm else nn.Identity() self.linear1 = nn.Linear(in_channels, mid_channels) - self.bn2 = NORM[1](in_channels) if batch_norm else nn.Identity() + self.bn2 = NORM[1](mid_channels) if batch_norm else nn.Identity() self.linear2 = nn.Linear(mid_channels, in_channels) def forward(self, x): @@ -436,132 +444,35 @@ def get_mask(inputs, reverse_mask, data_format = 'NHWC'): return checker.to(TORCH.get_default_device().ref) -class CouplingLayer(nn.Module): - - def __init__(self, in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask): - super(CouplingLayer, self).__init__() - - self.activation = activation - self.batch_norm = batch_norm - self.reverse_mask = reverse_mask - - - if in_spatial == 0: #for in_spatial = 0, use dense layers - self.s1 = nn.Sequential(Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation), - torch.nn.Tanh()) - self.t1 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) - - self.s2 = nn.Sequential(Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation), - torch.nn.Tanh()) - self.t2 = Dense_ResNet_Block(in_channels, mid_channels, batch_norm, activation) - else: - self.s1 = nn.Sequential(ResNet_Block(in_spatial, in_channels, in_channels, batch_norm, activation), - torch.nn.Tanh()) - self.t1 = ResNet_Block(in_spatial, in_channels, in_channels, batch_norm, activation) - - self.s2 = nn.Sequential(ResNet_Block(in_spatial, in_channels, in_channels, batch_norm, activation), - torch.nn.Tanh()) - self.t2 = ResNet_Block(in_spatial, in_channels, in_channels, batch_norm, activation) - - - def forward(self, x, invert=False): - x = TORCH.as_tensor(x) - mask = get_mask(x, self.reverse_mask, 'NCHW') - - if invert: - v1 = x * mask - v2 = x * (1-mask) - - u2 = (1-mask) * (v2 - self.t1(v1)) * torch.exp(-self.s1(v1)) - u1 = mask * (v1 - self.t2(u2)) * torch.exp(-self.s2(u2)) - - return u1 + u2 - else: - u1 = x * mask - u2 = x * (1-mask) - - v1 = mask * (u1 * torch.exp( self.s2(u2)) + self.t2(u2)) - v2 = (1-mask) * (u2 * torch.exp( self.s1(v1)) + self.t1(v1)) - - return v1 + v2 - -class Inn(nn.Module): - def __init__(self, in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask): - super(Inn, self).__init__() - self.num_blocks = num_blocks - - for i in range(num_blocks): - self.add_module(f'coupling_block{i+1}', CouplingLayer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask)) - - def forward(self, x, backward=False): - if backward: - for i in range(self.num_blocks, 0, -1): - x = getattr(self, f'coupling_block{i}')(x, backward) - else: - for i in range(1, self.num_blocks+1): - x = getattr(self, f'coupling_block{i}')(x, backward) - return x - - -def inn(in_channels: int, - mid_channels: int, - num_blocks: int, - batch_norm: bool = False, - reverse_mask: bool = False, - activation: str or type='ReLU', - in_spatial: tuple or int=2, - backward: bool = False): - if isinstance(in_spatial, tuple): - in_spatial = len(in_spatial) - - return Inn(in_channels, mid_channels, num_blocks, activation, batch_norm, in_spatial, reverse_mask).to(TORCH.get_default_device().ref) - - -def coupling_layer(in_channels: int, - mid_channels: int, - activation: str or type='ReLU', - batch_norm=False, - reverse_mask=False, - in_spatial: tuple or int=2): - if isinstance(in_spatial, tuple): - in_spatial = len(in_spatial) - - net = CouplingLayer(in_channels, mid_channels, activation, batch_norm, in_spatial, reverse_mask) - net = net.to(TORCH.get_default_device().ref) - return net - class ResNet(nn.Module): def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, activation): super(ResNet, self).__init__() self.layers = layers - if len(self.layers) >0: - self.add_module('Res_in', ResNet_Block(in_spatial, in_channels, layers[0], batch_norm, activation)) + if len(self.layers) < 1: + layers.append(out_channels) + self.add_module('Res_in', ResNet_Block(in_spatial, in_channels, layers[0], batch_norm, activation)) - for i in range(1, len(layers)): - self.add_module(f'Res{i}', ResNet_Block(in_spatial, layers[i-1], layers[i], batch_norm, activation)) + for i in range(1, len(layers)): + self.add_module(f'Res{i}', ResNet_Block(in_spatial, layers[i-1], layers[i], batch_norm, activation)) - self.add_module('Res_out', ResNet_Block(in_spatial, layers[len(layers)-1], out_channels, batch_norm, activation)) - else: - self.add_module('Res', ResNet_Block(in_spatial, in_channels, out_channels, batch_norm, activation)) + self.add_module('Res_out', CONV[in_spatial](layers[len(layers)-1], out_channels, kernel_size=1)) def forward(self, x): x = TORCH.as_tensor(x) - if len(self.layers) > 0: - x = getattr(self, 'Res_in')(x) - for i in range(1, len(self.layers)): - x = getattr(self, f'Res{i}')(x) - x = getattr(self, 'Res_out')(x) - else: - print('Single Res block') - x = getattr(self, 'Res')(x) + + x = getattr(self, 'Res_in')(x) + for i in range(1, len(self.layers)): + x = getattr(self, f'Res{i}')(x) + x = getattr(self, 'Res_out')(x) + return x def res_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int], + layers: Tuple[int, ...] or List[int] = [], batch_norm: bool = False, activation: str or type = 'ReLU', in_spatial: int or tuple = 2) -> nn.Module: @@ -634,3 +545,102 @@ def forward(self, x): x = self.flatten(x) x = self.softmax(self.linear(x)) return x + +NET = {'u_net': u_net, 'res_net': res_net, 'conv_net': conv_net} + +class CouplingLayer(nn.Module): + + def __init__(self, in_channels, activation, batch_norm, in_spatial, net, reverse_mask): + super(CouplingLayer, self).__init__() + + self.activation = activation + self.batch_norm = batch_norm + self.reverse_mask = reverse_mask + + if in_spatial == 0: #for in_spatial = 0, use dense layers + self.s1 = nn.Sequential(Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation), + torch.nn.Tanh()) + self.t1 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) + + self.s2 = nn.Sequential(Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation), + torch.nn.Tanh()) + self.t2 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) + else: + self.s1 = nn.Sequential(NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial), torch.nn.Tanh()) + self.t1 = NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) + self.s2 = nn.Sequential(NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial), torch.nn.Tanh()) + self.t2 = NET[net](in_channels=in_channels, out_channels=in_channels, + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) + + + def forward(self, x, invert=False): + x = TORCH.as_tensor(x) + mask = get_mask(x, self.reverse_mask, 'NCHW') + + if invert: + v1 = x * mask + v2 = x * (1-mask) + + u2 = (1-mask) * (v2 - self.t1(v1)) * torch.exp(-self.s1(v1)) + u1 = mask * (v1 - self.t2(u2)) * torch.exp(-self.s2(u2)) + + return u1 + u2 + else: + u1 = x * mask + u2 = x * (1-mask) + + v1 = mask * (u1 * torch.exp( self.s2(u2)) + self.t2(u2)) + v2 = (1-mask) * (u2 * torch.exp( self.s1(v1)) + self.t1(v1)) + + return v1 + v2 + +class InvertibleNet(nn.Module): + def __init__(self, in_channels, num_blocks, activation, batch_norm, in_spatial, net): + super(InvertibleNet, self).__init__() + self.num_blocks = num_blocks + + for i in range(num_blocks): + self.add_module(f'coupling_block{i+1}', + CouplingLayer(in_channels, activation, + batch_norm, in_spatial, net, (i%2==0))) + + def forward(self, x, backward=False): + if backward: + for i in range(self.num_blocks, 0, -1): + x = getattr(self, f'coupling_block{i}')(x, backward) + else: + for i in range(1, self.num_blocks+1): + x = getattr(self, f'coupling_block{i}')(x, backward) + return x + + +def invertible_net(in_channels: int, + num_blocks: int, + batch_norm: bool = False, + net: str = 'u_net', + activation: str or type='ReLU', + in_spatial: tuple or int=2): + if isinstance(in_spatial, tuple): + in_spatial = len(in_spatial) + + return InvertibleNet(in_channels, num_blocks, activation, batch_norm, in_spatial, net).to(TORCH.get_default_device().ref) + + +def coupling_layer(in_channels: int, + activation: str or type='ReLU', + batch_norm=False, + reverse_mask=False, + in_spatial: tuple or int=2): + if isinstance(in_spatial, tuple): + in_spatial = len(in_spatial) + + net = CouplingLayer(in_channels, activation, batch_norm, in_spatial, reverse_mask) + net = net.to(TORCH.get_default_device().ref) + return net \ No newline at end of file diff --git a/tests/commit/test_networks.py b/tests/commit/test_networks.py index 64cb4c49f..1546da6bf 100644 --- a/tests/commit/test_networks.py +++ b/tests/commit/test_networks.py @@ -106,3 +106,80 @@ def loss_function(x): for i in range(2): lib.update_weights(net, optimizer, loss_function, math.random_uniform(batch(batch=10), channel(vector=2))) + def test_optimize_invertible_conv_net(self): + for lib in LIBRARIES: + net = lib.invertible_net(2, 3, True, 'conv_net', 'SiLU') + optimizer = lib.adam(net) + + def loss_function(x): + print("Running loss_function") + assert isinstance(x, math.Tensor) + pred = math.native_call(net, x) + return math.l2_loss(pred) + + for i in range(2): + lib.update_weights(net, optimizer, loss_function, + math.random_uniform(math.batch(batch=10), math.channel(c=2), math.spatial(x=8, y=8))) + + def test_optimize_invertible_res_net(self): + for lib in LIBRARIES: + net = lib.invertible_net(2, 3, True, 'res_net', 'SiLU') + optimizer = lib.adam(net) + + def loss_function(x): + print("Running loss_function") + assert isinstance(x, math.Tensor) + pred = math.native_call(net, x) + return math.l2_loss(pred) + + for i in range(2): + lib.update_weights(net, optimizer, loss_function, + math.random_uniform(math.batch(batch=10), math.channel(c=2), math.spatial(x=8, y=8))) + + def test_optimize_invertible_u_net(self): + for lib in LIBRARIES: + net = lib.invertible_net(2, 3, True, 'u_net', 'SiLU') + optimizer = lib.adam(net) + + def loss_function(x): + print("Running loss_function") + assert isinstance(x, math.Tensor) + pred = math.native_call(net, x) + return math.l2_loss(pred) + + for i in range(2): + lib.update_weights(net, optimizer, loss_function, + math.random_uniform(math.batch(batch=10), math.channel(c=2), math.spatial(x=8, y=8))) + + def test_optimize_invertible_dense_net(self): + for lib in LIBRARIES: + net = lib.invertible_net(50, 3, True, in_spatial=0) + optimizer = lib.adam(net) + + def loss_function(x): + print("Running loss_function") + assert isinstance(x, math.Tensor) + pred = math.native_call(net, x) + return math.l2_loss(pred) + + for i in range(2): + lib.update_weights(net, optimizer, loss_function, + math.random_uniform(math.batch(batch=10), math.channel(c=50))) + + def test_invertible_net_network_sizes(self): + for lib in LIBRARIES: + net_u = lib.invertible_net(2, 3, True, 'u_net', 'SiLU') + self.assertEqual(454296, lib.parameter_count(net_u)) + net_res = lib.invertible_net(2, 3, True, 'res_net', 'ReLU') + self.assertEqual(1080, lib.parameter_count(net_res)) + net_conv = lib.invertible_net(2, 3, True, 'conv_net', 'ReLU') + self.assertEqual(576, lib.parameter_count(net_conv)) + net_dense = lib.invertible_net(2, 3, True, activation='ReLU', in_spatial=0) + self.assertEqual(240, lib.parameter_count(net_dense)) + + + + + + + From 3bbc2d2261a54762435b42ba4585d0074027abdf Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Fri, 16 Sep 2022 04:56:20 +0200 Subject: [PATCH 007/170] Used Tuple and List --- phi/jax/stax/nets.py | 60 ++++++++++++++++++++++---------------------- phi/tf/nets.py | 17 ++++++------- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/phi/jax/stax/nets.py b/phi/jax/stax/nets.py index 88c76f8b0..0b7d51322 100644 --- a/phi/jax/stax/nets.py +++ b/phi/jax/stax/nets.py @@ -5,18 +5,18 @@ For API documentation, see https://tum-pbs.github.io/PhiFlow/Network_API . """ import functools -import inspect import warnings -from typing import Callable +from typing import Callable, Tuple, List -import keras -import numpy import jax import jax.numpy as jnp +import keras +import numpy from jax import random - from packaging import version -if version.parse(jax.__version__) >= version.parse('0.2.25'): # Stax and Optimizers were moved to jax.example_libraries on Oct 20, 2021 + +if version.parse(jax.__version__) >= version.parse( + '0.2.25'): # Stax and Optimizers were moved to jax.example_libraries on Oct 20, 2021 from jax.example_libraries import stax import jax.example_libraries.optimizers as optim from jax.example_libraries.optimizers import OptimizerState @@ -494,12 +494,12 @@ def net_apply(params, inputs, **kwargs): net.initialize() return net -def conv_net(in_channels : int, - out_channels: int, - layers : tuple = [], - batch_norm : bool = False, - activation:str or Callable = 'ReLU', - in_spatial : int or tuple = 2) ->StaxNet: +def conv_net(in_channels: int, + out_channels: int, + layers: Tuple[int, ...] or List[int, ...] = [], + batch_norm: bool = False, + activation: str or Callable = 'ReLU', + in_spatial: int or tuple = 2) ->StaxNet: if isinstance(in_spatial,tuple): d = in_spatial in_spatial = len(in_spatial) @@ -559,12 +559,12 @@ def net_apply(params, inputs): net.initialize() return net -def res_net(in_channels : int, - out_channels : int, - layers : tuple = [], - batch_norm : bool = False, - activation : str or Callable = 'ReLU', - in_spatial : int or tuple=2) -> StaxNet: +def res_net(in_channels: int, + out_channels: int, + layers: Tuple[int, ...] or List[int, ...] = [], + batch_norm: bool = False, + activation: str or Callable = 'ReLU', + in_spatial: int or tuple = 2) -> StaxNet: if isinstance(in_spatial, tuple): d = in_spatial in_spatial = len(in_spatial) @@ -721,12 +721,12 @@ def net_apply(params, inputs, **kwargs): return net_init, net_apply -def conv_net_unit(in_channels : int, - out_channels: int, - layers : tuple = [], - batch_norm : bool = False, - activation:str or Callable = 'ReLU', - in_spatial : int or tuple = 2): +def conv_net_unit(in_channels: int, + out_channels: int, + layers: Tuple[int, ...] or List[int, ...] = [], + batch_norm: bool = False, + activation: str or Callable = 'ReLU', + in_spatial: int or tuple = 2): if isinstance(in_spatial,tuple): d = in_spatial in_spatial = len(in_spatial) @@ -854,12 +854,12 @@ def net_apply(params, inputs, **kwargs): return net_init, net_apply -def res_net_unit(in_channels : int, - out_channels : int, - layers : tuple = [], - batch_norm : bool = False, - activation : str or Callable = 'ReLU', - in_spatial : int or tuple=2): +def res_net_unit(in_channels: int, + out_channels: int, + layers: Tuple[int, ...] or List[int, ...] = [], + batch_norm: bool = False, + activation: str or Callable = 'ReLU', + in_spatial: int or tuple = 2): if isinstance(in_spatial, tuple): d = in_spatial in_spatial = len(in_spatial) diff --git a/phi/tf/nets.py b/phi/tf/nets.py index d497bcf5d..dbad0376e 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -1,21 +1,18 @@ - """ Jax implementation of the unified machine learning API. Equivalent functions also exist for the other frameworks. For API documentation, see https://tum-pbs.github.io/PhiFlow/Network_API . """ -from typing import Callable, Tuple, List import pickle +from typing import Callable +from typing import Tuple, List import numpy import tensorflow as tf +from tensorflow import Tensor from tensorflow import keras from tensorflow.keras import layers as kl -from tensorflow import Tensor -import pickle - -from typing import Callable def parameter_count(model: keras.Model): @@ -247,10 +244,10 @@ def double_conv(x, d: int, out_channels: int, mid_channels: int, batch_norm: boo def conv_net(in_channels: int, out_channels: int, - layers: tuple = [], + layers: Tuple[int, ...] or List[int, ...] = [], batch_norm: bool = False, activation: str or Callable = 'ReLU', - in_spatial:int or tuple=2) -> keras.Model: + in_spatial: int or tuple = 2) -> keras.Model: if isinstance(in_spatial, int): d = (None,) * in_spatial else: @@ -313,10 +310,10 @@ def ResNet_Block(in_channels: int, def res_net(in_channels: int, out_channels: int, - layers: tuple = [], + layers: Tuple[int, ...] or List[int, ...] = [], batch_norm: bool = False, activation: str or Callable = 'ReLU', - in_spatial: int or tuple=2): + in_spatial: int or tuple = 2): if isinstance(in_spatial, int): d = (None,) * in_spatial From 9969e5fe1478f005a662d3fc94ebde027bc01de3 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Sat, 17 Sep 2022 18:30:37 +0200 Subject: [PATCH 008/170] Updated Network_API.ipynb for Invertible nets --- docs/Network_API.ipynb | 223 +++++++++++++++++++++++++++++++---------- 1 file changed, 172 insertions(+), 51 deletions(-) diff --git a/docs/Network_API.ipynb b/docs/Network_API.ipynb index 9c83694ff..922ff1867 100644 --- a/docs/Network_API.ipynb +++ b/docs/Network_API.ipynb @@ -27,11 +27,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "pycharm": { - "name": "#%%\n", - "is_executing": true + "is_executing": true, + "name": "#%%\n" } }, "outputs": [], @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": { "pycharm": { "name": "#%%\n" @@ -76,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "pycharm": { "name": "#%%\n" @@ -87,8 +87,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Initial loss: \u001B[92m(batchᵇ=100)\u001B[0m \u001B[94m0.278 ± 0.308\u001B[0m \u001B[37m(4e-05...1e+00)\u001B[0m\n", - "Final loss: \u001B[92m(batchᵇ=100)\u001B[0m \u001B[94m0.095 ± 0.131\u001B[0m \u001B[37m(1e-05...9e-01)\u001B[0m\n" + "Initial loss: \u001b[92m(batchᵇ=100)\u001b[0m \u001b[94m0.167 ± 0.171\u001b[0m \u001b[37m(1e-04...5e-01)\u001b[0m\n", + "Final loss: \u001b[92m(batchᵇ=100)\u001b[0m \u001b[94m0.033 ± 0.037\u001b[0m \u001b[37m(9e-07...1e-01)\u001b[0m\n" ] } ], @@ -132,7 +132,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "pycharm": { "name": "#%%\n" @@ -145,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "pycharm": { "name": "#%%\n" @@ -164,7 +164,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "pycharm": { "name": "#%%\n" @@ -176,28 +176,28 @@ "output_type": "stream", "text": [ "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Initial loss: \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.06e+04 ± 6.6e+04\u001B[0m \u001B[37m(9e+03...2e+05)\u001B[0m\n", + "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.69e+04 ± 5.7e+04\u001b[0m \u001b[37m(5e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 0, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.07e+04 ± 6.6e+04\u001B[0m \u001B[37m(9e+03...2e+05)\u001B[0m\n", + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.71e+04 ± 5.7e+04\u001b[0m \u001b[37m(5e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 1, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m8.68e+04 ± 6.0e+04\u001B[0m \u001B[37m(2e+04...2e+05)\u001B[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.37e+04 ± 5.1e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 2, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m8.48e+04 ± 5.6e+04\u001B[0m \u001B[37m(2e+04...2e+05)\u001B[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.22e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 3, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m8.33e+04 ± 5.4e+04\u001B[0m \u001B[37m(2e+04...2e+05)\u001B[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.07e+04 ± 4.7e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 4, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m8.22e+04 ± 5.3e+04\u001B[0m \u001B[37m(2e+04...2e+05)\u001B[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.98e+04 ± 4.6e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 5, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m8.10e+04 ± 5.1e+04\u001B[0m \u001B[37m(2e+04...2e+05)\u001B[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.89e+04 ± 4.6e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 6, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m8.00e+04 ± 5.0e+04\u001B[0m \u001B[37m(2e+04...2e+05)\u001B[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.80e+04 ± 4.4e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 7, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m7.90e+04 ± 4.9e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.73e+04 ± 4.3e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 8, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m7.83e+04 ± 4.8e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.68e+04 ± 4.3e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 9, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m7.76e+04 ± 4.8e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", - "Final loss: \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m7.76e+04 ± 4.8e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n" + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.62e+04 ± 4.3e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.62e+04 ± 4.3e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n" ] } ], @@ -238,7 +238,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "pycharm": { "name": "#%%\n" @@ -251,7 +251,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "pycharm": { "name": "#%%\n" @@ -263,28 +263,28 @@ "output_type": "stream", "text": [ "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Initial loss: \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.76e+04 ± 5.7e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n", + "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.82e+04 ± 6.7e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 0, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.77e+04 ± 5.7e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n", + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.83e+04 ± 6.7e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 1, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.73e+04 ± 5.7e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.81e+04 ± 6.7e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 2, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.71e+04 ± 5.7e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.79e+04 ± 6.7e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 3, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.68e+04 ± 5.7e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.77e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 4, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.65e+04 ± 5.7e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.75e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 5, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.61e+04 ± 5.7e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.73e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 6, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.58e+04 ± 5.7e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.71e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 7, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.55e+04 ± 5.6e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.70e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 8, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.52e+04 ± 5.6e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.67e+04 ± 6.5e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 9, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.48e+04 ± 5.6e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n", - "Final loss: \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m9.48e+04 ± 5.6e+04\u001B[0m \u001B[37m(8e+03...2e+05)\u001B[0m\n" + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.64e+04 ± 6.5e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.64e+04 ± 6.5e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n" ] } ], @@ -323,7 +323,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": { "pycharm": { "name": "#%%\n" @@ -336,7 +336,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": { "pycharm": { "name": "#%%\n" @@ -348,28 +348,28 @@ "output_type": "stream", "text": [ "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Initial loss: \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m6.09e+04 ± 5.1e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.29e+04 ± 6.7e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 0, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m6.12e+04 ± 5.2e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.30e+04 ± 6.8e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 1, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m5.90e+04 ± 4.8e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.22e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 2, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m5.79e+04 ± 4.8e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.14e+04 ± 6.5e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 3, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m5.66e+04 ± 4.6e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.07e+04 ± 6.4e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 4, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m5.52e+04 ± 4.4e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.03e+04 ± 6.3e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 5, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m5.41e+04 ± 4.2e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.97e+04 ± 6.3e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 6, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m5.31e+04 ± 4.1e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.95e+04 ± 6.2e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 7, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m5.22e+04 ± 4.0e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.91e+04 ± 6.2e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 8, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m5.17e+04 ± 3.9e+04\u001B[0m \u001B[37m(1e+04...2e+05)\u001B[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.87e+04 ± 6.1e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 9, Loss : \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m5.06e+04 ± 3.7e+04\u001B[0m \u001B[37m(1e+04...1e+05)\u001B[0m\n", - "Final loss: \u001B[92m(examplesᵇ=50)\u001B[0m \u001B[94m5.06e+04 ± 3.7e+04\u001B[0m \u001B[37m(1e+04...1e+05)\u001B[0m\n" + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.83e+04 ± 6.1e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.83e+04 ± 6.1e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n" ] } ], @@ -384,6 +384,127 @@ " print(f'Iter : {i}, Loss : {loss}')\n", "print(f\"Final loss: {loss}\")" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Invertible Nets\n", + "\n", + "Phiflow also provides invertible neural networks that are capable of inverting the output tensor back to the input tensor initially passed.\\\n", + "These networks have far reaching applications in predicting input parameters of a problem given its observations.\\\n", + "Invertible nets are composed of multiple concatenated coupling blocks wherein each such block consists of arbitrary neural networks.\n", + " \n", + "Currently these arbitrary neural networks could be set as u_net(default), conv_net, res_net or dense_net blocks with in_channels = out_channels. The architecture used is popularized by [Real NVP](https://arxiv.org/abs/1605.08803).\n", + "\n", + "### Arguments\n", + "* `in_channels` : input channels of the feature map, dtype : int\n", + "* `num_blocks` : number of coupling blocks inside the invertible net, dtype : int\n", + "* `activation` : activation function used within the layers, dtype : string\n", + "* `batch_norm` : use of batchnorm after each layer, dtype : bool\n", + "* `in_spatial` : spatial dimensions of the input feature map, dtype : int\n", + "* `net` : type of neural network blocks used in coupling layers, dtype : str\n", + "\n", + "Note: Currently supported values for net are 'u_net'(default), 'conv_net' and 'res_net'. For choosing 'dense_net' as the network block in coupling layers in_spatial must be set to zero." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def loss_function_inn(grid, scale: Tensor, smoothness: Tensor):\n", + " \n", + " \n", + " pred_scale, pred_smoothness = field.native_call(net, grid).vector\n", + " pred_scale = math.expand(pred_scale, channel(c=1))\n", + " pred_smoothness = math.expand(pred_smoothness, channel(c=1))\n", + " output_grid = math.concat((pred_scale, pred_smoothness), dim='c')\n", + " \n", + " return math.l2_loss(pred_scale - scale) + math.l2_loss(pred_smoothness - smoothness), output_grid\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "net = invertible_net(in_channels=2, num_blocks=2, activation='ReLU', batch_norm=True, in_spatial=2, net='u_net')" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m2.20e+04 ± 1.4e+04\u001b[0m \u001b[37m(7e+03...5e+04)\u001b[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.79e+04 ± 1.2e+04\u001b[0m \u001b[37m(4e+03...4e+04)\u001b[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.55e+04 ± 1.0e+04\u001b[0m \u001b[37m(3e+03...4e+04)\u001b[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.37e+04 ± 8.8e+03\u001b[0m \u001b[37m(4e+03...4e+04)\u001b[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.21e+04 ± 7.6e+03\u001b[0m \u001b[37m(4e+03...4e+04)\u001b[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.07e+04 ± 6.7e+03\u001b[0m \u001b[37m(4e+03...4e+04)\u001b[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.49e+03 ± 6.0e+03\u001b[0m \u001b[37m(3e+03...4e+04)\u001b[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.43e+03 ± 5.4e+03\u001b[0m \u001b[37m(3e+03...3e+04)\u001b[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.51e+03 ± 4.8e+03\u001b[0m \u001b[37m(3e+03...3e+04)\u001b[0m\n", + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.68e+03 ± 4.2e+03\u001b[0m \u001b[37m(3e+03...3e+04)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.68e+03 ± 4.2e+03\u001b[0m \u001b[37m(3e+03...3e+04)\u001b[0m\n" + ] + } + ], + "source": [ + "optimizer = adam(net, learning_rate=1e-3)\n", + "gt_scale = math.random_uniform(batch(examples=50), low=1, high=10)\n", + "gt_smoothness = math.random_uniform(batch(examples=50), low=.5, high=3)\n", + "\n", + "input_grid = CenteredGrid(Noise(scale=gt_scale, smoothness=gt_smoothness), x=32, y=32)\n", + " \n", + "# Expanding channels to 2 by repeating it along channel dimension \n", + "# in order to obtain feature maps for both pred_scale and pred_smoothness (in_channels = out_channels = 2)\n", + "input_grid = math.expand(input_grid, channel(c=2))\n", + "\n", + "for i in range(10):\n", + " loss, grid = update_weights(net, optimizer, loss_function_inn, input_grid, gt_scale, gt_smoothness)\n", + " print(f'Iter : {i}, Loss : {loss}')\n", + " \n", + "print(f\"Final loss: {loss}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Original input feature map can be obtained by passing the predicted feature map once again through the n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loss between initial input and prediction \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.66e+04 ± 1.4e+04\u001b[0m \u001b[37m(8e+02...4e+04)\u001b[0m\n", + "Loss between initial input and reconstructed input \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.22e-08 ± 1.1e-08\u001b[0m \u001b[37m(3e-09...5e-08)\u001b[0m\n" + ] + } + ], + "source": [ + "\n", + "grid = field.native_call(net, input_grid, False)\n", + "reconstructed_input = field.native_call(net, grid, True) # invert = True \n", + "print('Loss between initial input and prediction',math.l2_loss(input_grid - grid))\n", + "print('Loss between initial input and reconstructed input',math.l2_loss(input_grid - reconstructed_input))" + ] } ], "metadata": { @@ -410,4 +531,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From b11c8fd9dac0c188a88c47973874485b3f8c6d86 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 19 Sep 2022 13:17:35 +0200 Subject: [PATCH 009/170] [learning] nets.py style pass --- phi/jax/stax/flow.py | 2 +- phi/jax/stax/nets.py | 398 ++++++++++++++++++++++++------------------- phi/tf/flow.py | 2 +- phi/tf/nets.py | 143 ++++++++-------- phi/torch/nets.py | 124 +++++--------- 5 files changed, 339 insertions(+), 330 deletions(-) diff --git a/phi/jax/stax/flow.py b/phi/jax/stax/flow.py index df4e51b33..1ae2f3920 100644 --- a/phi/jax/stax/flow.py +++ b/phi/jax/stax/flow.py @@ -12,4 +12,4 @@ See `phi.flow`, `phi.torch.flow`, `phi.tf.flow`. """ from ..flow import * -from .nets import parameter_count, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, adagrad, rmsprop, SGD, conv_classifier, coupling_layer, invertible_net \ No newline at end of file +from .nets import parameter_count, get_parameters, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, adagrad, rmsprop, sgd, sgd as SGD, conv_classifier, coupling_layer, invertible_net \ No newline at end of file diff --git a/phi/jax/stax/nets.py b/phi/jax/stax/nets.py index 0b7d51322..77fc9d095 100644 --- a/phi/jax/stax/nets.py +++ b/phi/jax/stax/nets.py @@ -5,14 +5,17 @@ For API documentation, see https://tum-pbs.github.io/PhiFlow/Network_API . """ import functools +import inspect import warnings from typing import Callable, Tuple, List +import numpy import jax import jax.numpy as jnp import keras import numpy from jax import random + from packaging import version if version.parse(jax.__version__) >= version.parse( @@ -24,6 +27,7 @@ from jax.experimental import stax import jax.experimental.optimizers as optim from jax.experimental.optimizers import OptimizerState + warnings.warn(f"Found Jax version {jax.__version__}. Using legacy imports.", FutureWarning) from phi import math @@ -95,12 +99,12 @@ def loss_depending_on_net(params_tracer: tuple, *args, **kwargs): update = math.jit_compile(update) self._update_function_cache[loss_function] = update - next_packed_state, loss_output = self._update_function_cache[loss_function](self._state.packed_state, *loss_args, **loss_kwargs) + next_packed_state, loss_output = self._update_function_cache[loss_function](self._state.packed_state, + *loss_args, **loss_kwargs) self._state = OptimizerState(next_packed_state, self._state.tree_def, self._state.subtree_defs) return loss_output - def parameter_count(model: StaxNet) -> int: """ Counts the number of parameters in a model. @@ -132,6 +136,39 @@ def _recursive_count_parameters(obj): return numpy.prod(obj.shape) +def get_parameters(model: StaxNet, wrap=True) -> dict: + result = {} + _recursive_add_parameters(model.parameters, wrap, (), result) + return result + + +def _recursive_add_parameters(param, wrap: bool, prefix: tuple, result: dict): + if isinstance(param, dict): + for name, obj in param.items(): + _recursive_add_parameters(obj, wrap, prefix + (name,), result) + elif isinstance(param, (tuple, list)): + for i, obj in enumerate(param): + _recursive_add_parameters(obj, wrap, prefix + (i,), result) + else: + rank = len(param.shape) + if prefix[-1] == 0 and rank == 2: + name = '.'.join(str(p) for p in prefix[:-1]) + '.weight' + elif prefix[-1] == 1 and rank == 1: + name = '.'.join(str(p) for p in prefix[:-1]) + '.bias' + else: + name = '.'.join(prefix) + if not wrap: + result[name] = param + else: + if rank == 1: + phi_tensor = math.wrap(param, math.channel('output')) + elif rank == 2: + phi_tensor = math.wrap(param, math.channel('input,output')) + else: + raise NotImplementedError + result[name] = phi_tensor + + def save_state(obj: StaxNet or JaxOptimizer, path: str): """ Write the state of a module or optimizer to a file. @@ -148,7 +185,7 @@ def save_state(obj: StaxNet or JaxOptimizer, path: str): if isinstance(obj, StaxNet): numpy.save(path, obj.parameters) else: - pass # ToDo + raise NotImplementedError # ToDo # numpy.save(path, obj._state) @@ -169,7 +206,7 @@ def load_state(obj: StaxNet or JaxOptimizer, path: str): state = numpy.load(path, allow_pickle=True) obj.parameters = tuple([tuple(layer) for layer in state]) else: - pass # ToDo + raise NotImplementedError # ToDo def update_weights(net: StaxNet, optimizer: JaxOptimizer, loss_function: Callable, *loss_args, **loss_kwargs): @@ -202,19 +239,21 @@ def adam(net: StaxNet, learning_rate: float = 1e-3, betas=(0.9, 0.999), epsilon= opt.initialize(net.parameters) return opt -def SGD(net: StaxNet, learning_rate: float = 1e-3, momentum=0, dampening=0, weight_decay=0, nesterov = False): + +def sgd(net: StaxNet, learning_rate: float = 1e-3, momentum=0, dampening=0, weight_decay=0, nesterov=False): """ Creates an SGD optimizer for `net`, alias for [`jax.example_libraries.optimizers.SGD`](https://jax.readthedocs.io/en/latest/jax.example_libraries.optimizers.html). Analogous functions exist for other learning frameworks. """ - if momentum==0: + if momentum == 0: opt = JaxOptimizer(*optim.sgd(learning_rate)) else: opt = JaxOptimizer(*optim.momentum(learning_rate, momentum)) opt.initialize(net.parameters) return opt -def adagrad(net: StaxNet, learning_rate: float = 1e-3, lr_decay=0, weight_decay=0, initial_accumulator_value = 0, eps=1e-10): + +def adagrad(net: StaxNet, learning_rate: float = 1e-3, lr_decay=0, weight_decay=0, initial_accumulator_value=0, eps=1e-10): """ Creates an Adagrad optimizer for `net`, alias for [`jax.example_libraries.optimizers.adagrad`](https://jax.readthedocs.io/en/latest/jax.example_libraries.optimizers.html). Analogue functions exist for other learning frameworks. @@ -223,21 +262,23 @@ def adagrad(net: StaxNet, learning_rate: float = 1e-3, lr_decay=0, weight_decay= opt.initialize(net.parameters) return opt + def rmsprop(net: StaxNet, learning_rate: float = 1e-3, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False): """ Creates an RMSprop optimizer for `net`, alias for [`jax.example_libraries.optimizers.rmsprop`](https://jax.readthedocs.io/en/latest/jax.example_libraries.optimizers.html). Analogue functions exist for other learning frameworks. """ - if momentum==0: + if momentum == 0: opt = JaxOptimizer(*optim.rmsprop(learning_rate, alpha, eps)) else: opt = JaxOptimizer(*optim.rmsprop_momentum(learning_rate, alpha, eps, momentum)) opt.initialize(net.parameters) return opt + def dense_net(in_channels: int, out_channels: int, - layers: tuple or list, + layers: Tuple[int, ...] or List[int], batch_norm=False, activation='ReLU') -> StaxNet: activation = {'ReLU': stax.Relu, 'Sigmoid': stax.Sigmoid, 'tanh': stax.Tanh}[activation] @@ -275,17 +316,22 @@ def u_net(in_channels: int, d = len(in_spatial) # Create layers if use_res_blocks: - inc_init, inc_apply = ResNet_Block(in_channels, filters[0], batch_norm, activation, d) + inc_init, inc_apply = resnet_block(in_channels, filters[0], batch_norm, activation, d) else: inc_init, inc_apply = create_double_conv(d, filters[0], filters[0], batch_norm, activation) init_functions, apply_functions = {}, {} for i in range(1, levels): if use_res_blocks: - init_functions[f'down{i}'], apply_functions[f'down{i}'] = ResNet_Block(filters[i-1], filters[i], batch_norm, activation, d) - init_functions[f'up{i}'], apply_functions[f'up{i}'] = ResNet_Block(filters[i] + filters[i-1], filters[i-1], batch_norm, activation, d) + init_functions[f'down{i}'], apply_functions[f'down{i}'] = resnet_block(filters[i - 1], filters[i], + batch_norm, activation, d) + init_functions[f'up{i}'], apply_functions[f'up{i}'] = resnet_block(filters[i] + filters[i - 1], + filters[i - 1], batch_norm, activation, + d) else: - init_functions[f'down{i}'], apply_functions[f'down{i}'] = create_double_conv(d, filters[i], filters[i], batch_norm, activation) - init_functions[f'up{i}'], apply_functions[f'up{i}'] = create_double_conv(d, filters[i - 1], filters[i - 1], batch_norm, activation) + init_functions[f'down{i}'], apply_functions[f'down{i}'] = create_double_conv(d, filters[i], filters[i], + batch_norm, activation) + init_functions[f'up{i}'], apply_functions[f'up{i}'] = create_double_conv(d, filters[i - 1], filters[i - 1], + batch_norm, activation) outc_init, outc_apply = CONV[d](out_channels, (1,) * d, padding='same') max_pool_init, max_pool_apply = stax.MaxPool((2,) * d, padding='same', strides=(2,) * d) _, up_apply = create_upsample() @@ -303,7 +349,7 @@ def net_init(rng, input_shape): shapes.insert(0, shape) for i in range(1, levels): shape = shapes[i][:-1] + (shapes[i][-1] + shape[-1],) - shape, params[f'up{i}'] = init_functions[f'up{i}'](rngs[levels+i], shape) + shape, params[f'up{i}'] = init_functions[f'up{i}'](rngs[levels + i], shape) shape, params['outc'] = outc_init(rngs[-1], shape) return shape, params @@ -328,11 +374,11 @@ def net_apply(params, inputs, **kwargs): return net -ACTIVATIONS = {'ReLU': stax.Relu, 'Sigmoid': stax.Sigmoid, 'tanh': stax.Tanh, 'SiLU' : stax.Selu} +ACTIVATIONS = {'ReLU': stax.Relu, 'Sigmoid': stax.Sigmoid, 'tanh': stax.Tanh, 'SiLU': stax.Selu} CONV = [None, functools.partial(stax.GeneralConv, ('NWC', 'WIO', 'NWC')), functools.partial(stax.GeneralConv, ('NWHC', 'WHIO', 'NWHC')), - functools.partial(stax.GeneralConv, ('NWHDC', 'WHDIO', 'NWHDC')),] + functools.partial(stax.GeneralConv, ('NWHDC', 'WHDIO', 'NWHDC')), ] ''' def create_double_conv(d: int, out_channels: int, mid_channels: int, batch_norm: bool, activation: Callable): @@ -345,23 +391,23 @@ def create_double_conv(d: int, out_channels: int, mid_channels: int, batch_norm: stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, activation) ''' + + # Periodic Implementation def create_double_conv(d: int, out_channels: int, mid_channels: int, batch_norm: bool, activation: Callable): - init_fn, apply_fn = {}, {} - init_fn['conv1'], apply_fn['conv1'] = stax.serial(CONV[d](mid_channels, (3,) * d, padding='valid'), - stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, - activation) - + stax.BatchNorm( + axis=tuple(range(d + 1))) if batch_norm else stax.Identity, + activation) init_fn['conv2'], apply_fn['conv2'] = stax.serial(CONV[d](mid_channels, (3,) * d, padding='valid'), - stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, - activation) + stax.BatchNorm( + axis=tuple(range(d + 1))) if batch_norm else stax.Identity, + activation) def net_init(rng, input_shape): params = {} rngs = random.split(rng, 2) - shape, params['conv1'] = init_fn['conv1'](rngs[0], input_shape) shape, params['conv2'] = init_fn['conv2'](rngs[1], shape) @@ -369,27 +415,28 @@ def net_init(rng, input_shape): def net_apply(params, inputs): x = inputs - pad_tuple = [[0, 0]] + [[1, 1] for i in range(d)] + [[0, 0]] - out = jnp.pad(x, pad_width=pad_tuple, mode='wrap') out = apply_fn['conv1'](params['conv1'], out) - out = jnp.pad(out ,pad_width=pad_tuple, mode='wrap') + out = jnp.pad(out, pad_width=pad_tuple, mode='wrap') out = apply_fn['conv2'](params['conv2'], out) - return out return net_init, net_apply + def create_upsample(): # def upsample_init(rng, input_shape): # return shape, [] def upsample_apply(params, inputs, **kwargs): - x = math.wrap(inputs, math.batch('batch'), *[math.spatial(f'{i}') for i in range(len(inputs.shape) - 2)], math.channel('vector')) + x = math.wrap(inputs, math.batch('batch'), *[math.spatial(f'{i}') for i in range(len(inputs.shape) - 2)], + math.channel('vector')) x = math.upsample2x(x) return x.native(x.shape) + return NotImplemented, upsample_apply + def conv_classifier(input_shape_list: list, num_classes: int, batch_norm: bool, in_spatial: int or tuple): if isinstance(in_spatial, int): d = in_spatial @@ -400,11 +447,8 @@ def conv_classifier(input_shape_list: list, num_classes: int, batch_norm: bool, stax_conv_layers = [] stax_dense_layers = [] spatial_shape_list = list(input_shape_list[1:]) - in_channels = input_shape_list[0] - channels = [64, 128, 256, 512, 512] - init_fn, apply_fn = {}, {} init_fn['conv1'], apply_fn['conv1'] = create_double_conv(d, 64, 64, batch_norm, ACTIVATIONS['ReLU']) init_fn['max_pool1'], apply_fn['max_pool1'] = stax.MaxPool((2,) * d, padding='valid', strides=(2,) * d) @@ -414,20 +458,23 @@ def conv_classifier(input_shape_list: list, num_classes: int, batch_norm: bool, init_fn['conv3_1'], apply_fn['conv3_1'] = create_double_conv(d, 256, 256, batch_norm, ACTIVATIONS['ReLU']) init_fn['conv3_2'], apply_fn['conv3_2'] = stax.serial(CONV[d](256, (3,) * d, padding='valid'), - stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, - ACTIVATIONS['ReLU']) + stax.BatchNorm(axis=tuple( + range(d + 1))) if batch_norm else stax.Identity, + ACTIVATIONS['ReLU']) init_fn['max_pool3'], apply_fn['max_pool3'] = stax.MaxPool((2,) * d, padding='valid', strides=(2,) * d) init_fn['conv4_1'], apply_fn['conv4_1'] = create_double_conv(d, 512, 512, batch_norm, ACTIVATIONS['ReLU']) init_fn['conv4_2'], apply_fn['conv4_2'] = stax.serial(CONV[d](512, (3,) * d, padding='valid'), - stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, - ACTIVATIONS['ReLU']) + stax.BatchNorm(axis=tuple( + range(d + 1))) if batch_norm else stax.Identity, + ACTIVATIONS['ReLU']) init_fn['max_pool4'], apply_fn['max_pool4'] = stax.MaxPool((2,) * d, padding='valid', strides=(2,) * d) init_fn['conv5_1'], apply_fn['conv5_1'] = create_double_conv(d, 512, 512, batch_norm, ACTIVATIONS['ReLU']) init_fn['conv5_2'], apply_fn['conv5_2'] = stax.serial(CONV[d](512, (3,) * d, padding='valid'), - stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, + stax.BatchNorm(axis=tuple( + range(d + 1))) if batch_norm else stax.Identity, ACTIVATIONS['ReLU']) init_fn['max_pool5'], apply_fn['max_pool5'] = stax.MaxPool((2,) * d, padding='valid', strides=(2,) * d) @@ -445,47 +492,36 @@ def conv_classifier(input_shape_list: list, num_classes: int, batch_norm: bool, stax_dense_layers.append(stax.BatchNorm(axis=(0,))) stax_dense_layers.append(stax.Dense(num_classes)) softmax = stax.elementwise(stax.softmax, axis=-1) - stax_dense_layers.append(softmax) - dense_init, dense_apply = stax.serial(*stax_dense_layers) def net_init(rng, input_shape): params = {} rngs = random.split(rng, 2) - for i in range(5): for j in range(len(spatial_shape_list)): spatial_shape_list[j] = math.floor((spatial_shape_list[j] - 2) / 2) + 1 - flattened_input_dim = 1 for i in range(len(spatial_shape_list)): flattened_input_dim *= spatial_shape_list[i] flattened_input_dim *= 512 flattened_input_dim = int(flattened_input_dim) - shape = input_shape - N = len(net_list) for i in range(N): shape, params[f'{net_list[i]}'] = \ init_fn[f'{net_list[i]}'](rngs[i], shape) - shape, params['flatten'] = init_fn['flatten'](rngs[N], shape) - shape, params['dense'] = dense_init(rngs[N+1], (1,) + (flattened_input_dim,)) - + shape, params['dense'] = dense_init(rngs[N + 1], (1,) + (flattened_input_dim,)) return shape, params def net_apply(params, inputs, **kwargs): x = inputs - - pad_tuple = [[0, 0]] + [[1,1] for i in range(d)] + [[0,0]] - + pad_tuple = [[0, 0]] + [[1, 1] for i in range(d)] + [[0, 0]] for i in range(len(net_list)): if net_list[i] in ['conv3_2', 'conv4_2', 'conv5_2']: x = jnp.pad(x, pad_width=pad_tuple, mode='wrap') x = apply_fn[f'{net_list[i]}'](params[f'{net_list[i]}'], x) - x = apply_fn['flatten'](params['flatten'], x) out = dense_apply(params['dense'], x, **kwargs) return out @@ -494,74 +530,65 @@ def net_apply(params, inputs, **kwargs): net.initialize() return net + def conv_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int, ...] = [], + layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or Callable = 'ReLU', - in_spatial: int or tuple = 2) ->StaxNet: - if isinstance(in_spatial,tuple): + in_spatial: int or tuple = 2) -> StaxNet: + if isinstance(in_spatial, tuple): d = in_spatial in_spatial = len(in_spatial) else: d = (1,) * in_spatial if isinstance(activation, str): activation = ACTIVATIONS[activation] - init_fn, apply_fn = {}, {} if len(layers) < 1: layers.append(out_channels) - - init_fn['conv_in'], apply_fn['conv_in'] = stax.serial(CONV[in_spatial](layers[0], (3,)*in_spatial, padding = 'valid'), - stax.BatchNorm(axis=tuple(range(in_spatial+1))) if batch_norm else stax.Identity, - activation) - for i in range(1,len(layers)): - init_fn[f'conv{i}'], apply_fn[f'conv{i}'] = stax.serial(CONV[in_spatial](layers[i], (3,)*in_spatial, padding = 'valid'), - stax.BatchNorm(axis=tuple(range(in_spatial+1))) if batch_norm else stax.Identity, - activation) - - init_fn['conv_out'], apply_fn['conv_out'] = CONV[in_spatial](out_channels, (1,)*in_spatial) + init_fn['conv_in'], apply_fn['conv_in'] = stax.serial( + CONV[in_spatial](layers[0], (3,) * in_spatial, padding='valid'), + stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + activation) + for i in range(1, len(layers)): + init_fn[f'conv{i}'], apply_fn[f'conv{i}'] = stax.serial( + CONV[in_spatial](layers[i], (3,) * in_spatial, padding='valid'), + stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + activation) + init_fn['conv_out'], apply_fn['conv_out'] = CONV[in_spatial](out_channels, (1,) * in_spatial) def net_init(rng, input_shape): params = {} rngs = random.split(rng, 2) - shape, params['conv_in'] = init_fn['conv_in'](rngs[0], input_shape) - - for i in range(1,len(layers)): - shape, params[f'conv{i+1}'] = init_fn[f'conv{i+1}'](rngs[i], shape) - + for i in range(1, len(layers)): + shape, params[f'conv{i + 1}'] = init_fn[f'conv{i + 1}'](rngs[i], shape) shape, params['conv_out'] = init_fn['conv_out'](rngs[len(layers)], shape) - return shape, params def net_apply(params, inputs): x = inputs - pad_tuple = [(0, 0)] for i in range(in_spatial): - pad_tuple.append((1,1)) - pad_tuple.append((0,0)) - + pad_tuple.append((1, 1)) + pad_tuple.append((0, 0)) out = jnp.pad(x, pad_width=pad_tuple, mode='wrap') - out = apply_fn['conv_in'](params['conv_in'], out) - - for i in range(1,len(layers)): + for i in range(1, len(layers)): out = jnp.pad(out, pad_width=pad_tuple, mode='wrap') - out = apply_fn[f'conv{i+1}'](params[f'conv{i+1}'], out) - + out = apply_fn[f'conv{i + 1}'](params[f'conv{i + 1}'], out) out = apply_fn['conv_out'](params['conv_out'], out) - return out net = StaxNet(net_init, net_apply, (1,) + d + (in_channels,)) net.initialize() return net + def res_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int, ...] = [], + layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2) -> StaxNet: @@ -572,29 +599,27 @@ def res_net(in_channels: int, d = (1,) * in_spatial activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation - stax_layers = [] if len(layers) > 0: - stax_layers.append(ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)) + stax_layers.append(resnet_block(in_channels, layers[0], batch_norm, activation, in_spatial)) for i in range(1, len(layers)): - stax_layers.append(ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)) + stax_layers.append(resnet_block(layers[i - 1], layers[i], batch_norm, activation, in_spatial)) - stax_layers.append(ResNet_Block(layers[len(layers)-1], out_channels, batch_norm, activation, in_spatial)) + stax_layers.append(resnet_block(layers[len(layers) - 1], out_channels, batch_norm, activation, in_spatial)) else: - stax_layers.append(ResNet_Block(in_channels, out_channels, batch_norm, activation, in_spatial)) + stax_layers.append(resnet_block(in_channels, out_channels, batch_norm, activation, in_spatial)) net_init, net_apply = stax.serial(*stax_layers) net = StaxNet(net_init, net_apply, (1,) + d + (in_channels,)) net.initialize() return net -def ResNet_Block(in_channels : int, - out_channels : int, - batch_norm : bool, - activation : str or Callable = 'ReLU', - in_spatial : int or tuple = 2): - +def resnet_block(in_channels: int, + out_channels: int, + batch_norm: bool, + activation: str or Callable = 'ReLU', + in_spatial: int or tuple = 2): if isinstance(in_spatial, int): d = (1,) * in_spatial else: @@ -603,17 +628,20 @@ def ResNet_Block(in_channels : int, activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation init_fn, apply_fn = {}, {} - init_fn['conv1'], apply_fn['conv1'] = stax.serial(CONV[in_spatial](out_channels, (3,)*in_spatial, padding = 'valid'), - stax.BatchNorm(axis=tuple(range(in_spatial+1))) if batch_norm else stax.Identity, - activation) - init_fn['conv2'], apply_fn['conv2'] = stax.serial(CONV[in_spatial](out_channels, (3,)*in_spatial, padding = 'valid'), - stax.BatchNorm(axis=tuple(range(in_spatial+1))) if batch_norm else stax.Identity, - activation) + init_fn['conv1'], apply_fn['conv1'] = stax.serial( + CONV[in_spatial](out_channels, (3,) * in_spatial, padding='valid'), + stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + activation) + init_fn['conv2'], apply_fn['conv2'] = stax.serial( + CONV[in_spatial](out_channels, (3,) * in_spatial, padding='valid'), + stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + activation) init_activation, apply_activation = activation if in_channels != out_channels: - init_fn['sample_conv'], apply_fn['sample_conv'] = stax.serial(CONV[in_spatial](out_channels, (1,)*in_spatial, padding = 'VALID'), - stax.BatchNorm(axis=tuple(range(in_spatial+1))) if batch_norm else stax.Identity) + init_fn['sample_conv'], apply_fn['sample_conv'] = stax.serial( + CONV[in_spatial](out_channels, (1,) * in_spatial, padding='VALID'), + stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity) else: init_fn['sample_conv'], apply_fn['sample_conv'] = stax.Identity @@ -639,12 +667,13 @@ def net_apply(params, inputs, **kwargs): out = apply_fn['conv2'](params['conv2'], out) skip_x = apply_fn['sample_conv'](params['sample_conv'], x, **kwargs) out = jnp.add(out, skip_x) - #out = apply_activation(params['activation'], out) + # out = apply_activation(params['activation'], out) return out return net_init, net_apply -def get_mask(inputs, reverse_mask, data_format = 'NHWC'): + +def get_mask(inputs, reverse_mask, data_format='NHWC'): shape = inputs.shape if len(shape) == 2: N = shape[-1] @@ -655,18 +684,16 @@ def get_mask(inputs, reverse_mask, data_format = 'NHWC'): H = shape[2] if data_format == 'NCHW' else shape[1] W = shape[3] if data_format == 'NCHW' else shape[2] - range_h = jnp.arange(0, H) %2 - range_w = jnp.arange(0, W) %2 - + range_h = jnp.arange(0, H) % 2 + range_w = jnp.arange(0, W) % 2 even_ind_h = range_h.astype(bool) even_ind_w = range_w.astype(bool) - ind_h = jnp.tile(jnp.expand_dims(even_ind_h, -1), [1,W]) - ind_w = jnp.tile(jnp.expand_dims(even_ind_w, 0), [H,1]) - #ind_h = even_ind_h.unsqueeze(-1).repeat(1, W) - #ind_w = even_ind_w.unsqueeze( 0).repeat(H, 1) - + ind_h = jnp.tile(jnp.expand_dims(even_ind_h, -1), [1, W]) + ind_w = jnp.tile(jnp.expand_dims(even_ind_w, 0), [H, 1]) + # ind_h = even_ind_h.unsqueeze(-1).repeat(1, W) + # ind_w = even_ind_w.unsqueeze( 0).repeat(H, 1) checker = jnp.logical_xor(ind_h, ind_w) @@ -683,17 +710,18 @@ def get_mask(inputs, reverse_mask, data_format = 'NHWC'): return checker -def Dense_ResNet_Block(in_channels: int, + +def Dense_resnet_block(in_channels: int, mid_channels: int, batch_norm: bool = False, - activation : str or Callable = 'ReLU'): - inputs = keras.Input(shape = (in_channels,)) + activation: str or Callable = 'ReLU'): + inputs = keras.Input(shape=(in_channels,)) x_1 = inputs activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation init_fn, apply_fn = {}, {} init_fn['dense1'], apply_fn['dense1'] = stax.serial(stax.Dense(mid_channels), - stax.BatchNorm(axis=(0, )), + stax.BatchNorm(axis=(0,)), activation) init_fn['dense2'], apply_fn['dense2'] = stax.serial(stax.Dense(in_channels), stax.BatchNorm(axis=(0,)), @@ -721,13 +749,14 @@ def net_apply(params, inputs, **kwargs): return net_init, net_apply + def conv_net_unit(in_channels: int, out_channels: int, layers: Tuple[int, ...] or List[int, ...] = [], batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2): - if isinstance(in_spatial,tuple): + if isinstance(in_spatial, tuple): d = in_spatial in_spatial = len(in_spatial) else: @@ -738,15 +767,17 @@ def conv_net_unit(in_channels: int, init_fn, apply_fn = {}, {} if len(layers) < 1: layers.append(out_channels) - init_fn['conv_in'], apply_fn['conv_in'] = stax.serial(CONV[in_spatial](layers[0], (3,)*in_spatial, padding = 'valid'), - stax.BatchNorm(axis=tuple(range(in_spatial+1))) if batch_norm else stax.Identity, - activation) - for i in range(1,len(layers)): - init_fn[f'conv{i}'], apply_fn[f'conv{i}'] = stax.serial(CONV[in_spatial](layers[i], (3,)*in_spatial, padding = 'valid'), - stax.BatchNorm(axis=tuple(range(in_spatial+1))) if batch_norm else stax.Identity, - activation) + init_fn['conv_in'], apply_fn['conv_in'] = stax.serial( + CONV[in_spatial](layers[0], (3,) * in_spatial, padding='valid'), + stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + activation) + for i in range(1, len(layers)): + init_fn[f'conv{i}'], apply_fn[f'conv{i}'] = stax.serial( + CONV[in_spatial](layers[i], (3,) * in_spatial, padding='valid'), + stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + activation) - init_fn['conv_out'], apply_fn['conv_out'] = CONV[in_spatial](out_channels, (1,)*in_spatial) + init_fn['conv_out'], apply_fn['conv_out'] = CONV[in_spatial](out_channels, (1,) * in_spatial) def net_init(rng, input_shape): params = {} @@ -754,8 +785,8 @@ def net_init(rng, input_shape): shape, params['conv_in'] = init_fn['conv_in'](rngs[0], input_shape) - for i in range(1,len(layers)): - shape, params[f'conv{i+1}'] = init_fn[f'conv{i+1}'](rngs[i], shape) + for i in range(1, len(layers)): + shape, params[f'conv{i + 1}'] = init_fn[f'conv{i + 1}'](rngs[i], shape) shape, params['conv_out'] = init_fn['conv_out'](rngs[len(layers)], shape) @@ -766,16 +797,16 @@ def net_apply(params, inputs): pad_tuple = [(0, 0)] for i in range(in_spatial): - pad_tuple.append((1,1)) - pad_tuple.append((0,0)) + pad_tuple.append((1, 1)) + pad_tuple.append((0, 0)) out = jnp.pad(x, pad_width=pad_tuple, mode='wrap') out = apply_fn['conv_in'](params['conv_in'], out) - for i in range(1,len(layers)): + for i in range(1, len(layers)): out = jnp.pad(out, pad_width=pad_tuple, mode='wrap') - out = apply_fn[f'conv{i+1}'](params[f'conv{i+1}'], out) + out = apply_fn[f'conv{i + 1}'](params[f'conv{i + 1}'], out) out = apply_fn['conv_out'](params['conv_out'], out) @@ -783,14 +814,15 @@ def net_apply(params, inputs): return net_init, net_apply + def u_net_unit(in_channels: int, - out_channels: int, - levels: int = 4, - filters: int or tuple or list = 16, - batch_norm: bool = True, - activation='ReLU', - in_spatial: tuple or int = 2, - use_res_blocks: bool = False): + out_channels: int, + levels: int = 4, + filters: int or tuple or list = 16, + batch_norm: bool = True, + activation='ReLU', + in_spatial: tuple or int = 2, + use_res_blocks: bool = False): if isinstance(filters, (tuple, list)): assert len(filters) == levels, f"List of filters has length {len(filters)} but u-net has {levels} levels." else: @@ -804,17 +836,22 @@ def u_net_unit(in_channels: int, d = len(in_spatial) # Create layers if use_res_blocks: - inc_init, inc_apply = ResNet_Block(in_channels, filters[0], batch_norm, activation, d) + inc_init, inc_apply = resnet_block(in_channels, filters[0], batch_norm, activation, d) else: inc_init, inc_apply = create_double_conv(d, filters[0], filters[0], batch_norm, activation) init_functions, apply_functions = {}, {} for i in range(1, levels): if use_res_blocks: - init_functions[f'down{i}'], apply_functions[f'down{i}'] = ResNet_Block(filters[i-1], filters[i], batch_norm, activation, d) - init_functions[f'up{i}'], apply_functions[f'up{i}'] = ResNet_Block(filters[i] + filters[i-1], filters[i-1], batch_norm, activation, d) + init_functions[f'down{i}'], apply_functions[f'down{i}'] = resnet_block(filters[i - 1], filters[i], + batch_norm, activation, d) + init_functions[f'up{i}'], apply_functions[f'up{i}'] = resnet_block(filters[i] + filters[i - 1], + filters[i - 1], batch_norm, activation, + d) else: - init_functions[f'down{i}'], apply_functions[f'down{i}'] = create_double_conv(d, filters[i], filters[i], batch_norm, activation) - init_functions[f'up{i}'], apply_functions[f'up{i}'] = create_double_conv(d, filters[i - 1], filters[i - 1], batch_norm, activation) + init_functions[f'down{i}'], apply_functions[f'down{i}'] = create_double_conv(d, filters[i], filters[i], + batch_norm, activation) + init_functions[f'up{i}'], apply_functions[f'up{i}'] = create_double_conv(d, filters[i - 1], filters[i - 1], + batch_norm, activation) outc_init, outc_apply = CONV[d](out_channels, (1,) * d, padding='same') max_pool_init, max_pool_apply = stax.MaxPool((2,) * d, padding='same', strides=(2,) * d) _, up_apply = create_upsample() @@ -832,7 +869,7 @@ def net_init(rng, input_shape): shapes.insert(0, shape) for i in range(1, levels): shape = shapes[i][:-1] + (shapes[i][-1] + shape[-1],) - shape, params[f'up{i}'] = init_functions[f'up{i}'](rngs[levels+i], shape) + shape, params[f'up{i}'] = init_functions[f'up{i}'](rngs[levels + i], shape) shape, params['outc'] = outc_init(rngs[-1], shape) return shape, params @@ -854,6 +891,7 @@ def net_apply(params, inputs, **kwargs): return net_init, net_apply + def res_net_unit(in_channels: int, out_channels: int, layers: Tuple[int, ...] or List[int, ...] = [], @@ -871,21 +909,23 @@ def res_net_unit(in_channels: int, stax_layers = [] if len(layers) < 1: layers.append(out_channels) - stax_layers.append(ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)) + stax_layers.append(resnet_block(in_channels, layers[0], batch_norm, activation, in_spatial)) for i in range(1, len(layers)): - stax_layers.append(ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)) + stax_layers.append(resnet_block(layers[i - 1], layers[i], batch_norm, activation, in_spatial)) - stax_layers.append(CONV[in_spatial](out_channels, (1,)*in_spatial)) + stax_layers.append(CONV[in_spatial](out_channels, (1,) * in_spatial)) return stax.serial(*stax_layers) + NET = {'u_net': u_net_unit, 'res_net': res_net_unit, 'conv_net': conv_net_unit} + def coupling_layer(in_channels: int, - activation: str or Callable='ReLU', + activation: str or Callable = 'ReLU', batch_norm: bool = False, - in_spatial: int or tuple=2, + in_spatial: int or tuple = 2, net: str = 'u_net', reverse_mask: bool = False): if isinstance(in_spatial, tuple): @@ -894,24 +934,26 @@ def coupling_layer(in_channels: int, activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation init_fn, apply_fn = {}, {} if in_spatial == 0: - init_fn['s1'], apply_fn['s1'] = stax.serial(Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation), - stax.Tanh) - init_fn['t1'], apply_fn['t1'] = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) - - init_fn['s2'], apply_fn['s2'] = stax.serial(Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation), - stax.Tanh) - init_fn['t2'], apply_fn['t2'] = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) + init_fn['s1'], apply_fn['s1'] = stax.serial( + Dense_resnet_block(in_channels, in_channels, batch_norm, activation), + stax.Tanh) + init_fn['t1'], apply_fn['t1'] = Dense_resnet_block(in_channels, in_channels, batch_norm, activation) + + init_fn['s2'], apply_fn['s2'] = stax.serial( + Dense_resnet_block(in_channels, in_channels, batch_norm, activation), + stax.Tanh) + init_fn['t2'], apply_fn['t2'] = Dense_resnet_block(in_channels, in_channels, batch_norm, activation) else: init_fn['s1'], apply_fn['s1'] = NET[net](in_channels=in_channels, out_channels=in_channels, - batch_norm=batch_norm, activation=activation, - in_spatial=in_spatial) + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) init_fn['t1'], apply_fn['t1'] = NET[net](in_channels=in_channels, out_channels=in_channels, batch_norm=batch_norm, activation=activation, in_spatial=in_spatial) init_fn['s2'], apply_fn['s2'] = NET[net](in_channels=in_channels, out_channels=in_channels, - batch_norm=batch_norm, activation=activation, - in_spatial=in_spatial) + batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) init_fn['t2'], apply_fn['t2'] = NET[net](in_channels=in_channels, out_channels=in_channels, batch_norm=batch_norm, activation=activation, in_spatial=in_spatial) @@ -934,12 +976,12 @@ def net_apply(params, inputs, invert=False): if invert: v1 = x * mask - v2 = x * (1-mask) + v2 = x * (1 - mask) s1 = apply_fn['s1'](params['s1'], v1) t1 = apply_fn['t1'](params['t1'], v1) - u2 = (1-mask) * (v2 - t1) * jnp.exp(-jnp.tanh(s1)) + u2 = (1 - mask) * (v2 - t1) * jnp.exp(-jnp.tanh(s1)) s2 = apply_fn['s2'](params['s2'], u2) t2 = apply_fn['t2'](params['t2'], u2) @@ -949,7 +991,7 @@ def net_apply(params, inputs, invert=False): return u1 + u2 else: u1 = x * mask - u2 = x * (1-mask) + u2 = x * (1 - mask) s2 = apply_fn['s2'](params['s2'], u2) t2 = apply_fn['t2'](params['t2'], u2) @@ -959,33 +1001,34 @@ def net_apply(params, inputs, invert=False): s1 = apply_fn['s1'](params['s1'], v1) t1 = apply_fn['t1'](params['t1'], v1) - v2 = (1-mask) * (u2 * jnp.exp(jnp.tanh(s1)) + t1) + v2 = (1 - mask) * (u2 * jnp.exp(jnp.tanh(s1)) + t1) return v1 + v2 return net_init, net_apply + def invertible_net(in_channels: int, - num_blocks: int, - batch_norm: bool = False, - net: str = 'u_net', - activation: str or type='ReLU', - in_spatial: tuple or int=2): + num_blocks: int, + batch_norm: bool = False, + net: str = 'u_net', + activation: str or type = 'ReLU', + in_spatial: tuple or int = 2): if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) init_fn, apply_fn = {}, {} for i in range(num_blocks): - init_fn[f'CouplingLayer{i+1}'], apply_fn[f'CouplingLayer{i+1}'] = \ - coupling_layer(in_channels, activation, batch_norm, in_spatial, net, (i%2==0)) + init_fn[f'CouplingLayer{i + 1}'], apply_fn[f'CouplingLayer{i + 1}'] = \ + coupling_layer(in_channels, activation, batch_norm, in_spatial, net, (i % 2 == 0)) def net_init(rng, input_shape): params = {} rngs = random.split(rng, 2) for i in range(num_blocks): - shape, params[f'CouplingLayer{i+1}'] = init_fn[f'CouplingLayer{i+1}'](rngs[i], input_shape) + shape, params[f'CouplingLayer{i + 1}'] = init_fn[f'CouplingLayer{i + 1}'](rngs[i], input_shape) return shape, params @@ -993,24 +1036,19 @@ def net_apply(params, inputs, invert=False): out = inputs if invert: - for i in range(num_blocks, 0,-1): + for i in range(num_blocks, 0, -1): out = apply_fn[f'CouplingLayer{i}']( - params[f'CouplingLayer{i}'],out, invert) + params[f'CouplingLayer{i}'], out, invert) else: - for i in range(1, num_blocks+1): + for i in range(1, num_blocks + 1): out = apply_fn[f'CouplingLayer{i}']( params[f'CouplingLayer{i}'], out) return out + if in_spatial == 0: net = StaxNet(net_init, net_apply, (1,) + (in_channels,)) else: net = StaxNet(net_init, net_apply, (1,) + (1,) * in_spatial + (in_channels,)) net.initialize() return net - - - - - - diff --git a/phi/tf/flow.py b/phi/tf/flow.py index 19330660c..9cd099682 100644 --- a/phi/tf/flow.py +++ b/phi/tf/flow.py @@ -14,7 +14,7 @@ from phi.flow import * from . import TENSORFLOW -from .nets import parameter_count, dense_net, u_net, save_state, load_state, update_weights, adam, conv_net, res_net, SGD, adagrad, rmsprop, conv_classifier,invertible_net +from .nets import parameter_count, get_parameters, dense_net, u_net, save_state, load_state, update_weights, adam, conv_net, res_net, sgd, sgd as SGD, adagrad, rmsprop, conv_classifier, invertible_net import tensorflow from tensorflow import keras from tensorflow.keras import layers diff --git a/phi/tf/nets.py b/phi/tf/nets.py index dbad0376e..7614a3a30 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -4,6 +4,7 @@ For API documentation, see https://tum-pbs.github.io/PhiFlow/Network_API . """ +from typing import Callable, Tuple, List import pickle from typing import Callable from typing import Tuple, List @@ -13,6 +14,9 @@ from tensorflow import Tensor from tensorflow import keras from tensorflow.keras import layers as kl +from tensorflow import Tensor + +from .. import math def parameter_count(model: keras.Model): @@ -30,6 +34,7 @@ def parameter_count(model: keras.Model): total += numpy.prod(parameter.shape) return int(total) + def get_parameters(model: keras.Model, wrap=True) -> dict: result = {} for var in model.trainable_weights: @@ -56,6 +61,7 @@ def get_parameters(model: keras.Model, wrap=True) -> dict: result[name] = phi_tensor return result + def save_state(obj: keras.models.Model or keras.optimizers.Optimizer, path: str): """ Write the state of a module or optimizer to a file. @@ -129,6 +135,7 @@ def update_weights(net: keras.Model, optimizer: keras.optimizers.Optimizer, loss optimizer.apply_gradients(zip(gradients, net.trainable_variables)) return output + def adam(net: keras.Model, learning_rate: float = 1e-3, betas=(0.9, 0.999), epsilon=1e-07): """ Creates an Adam optimizer for `net`, alias for [`keras.optimizers.Adam`](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam). @@ -136,20 +143,23 @@ def adam(net: keras.Model, learning_rate: float = 1e-3, betas=(0.9, 0.999), epsi """ return keras.optimizers.Adam(learning_rate, betas[0], betas[1], epsilon) -def SGD(net: keras.Model, learning_rate: float = 1e-3, momentum=0, dampening=0, weight_decay=0, nesterov = False): + +def sgd(net: keras.Model, learning_rate: float = 1e-3, momentum=0, dampening=0, weight_decay=0, nesterov=False): """ Creates an SGD optimizer for 'net', alias for ['keras.optimizers.SGD'](https://keras.io/api/optimizers/sgd/) Analogous functions exist for other learning frameworks. """ return keras.optimizers.SGD(learning_rate, momentum, nesterov) -def adagrad(net: keras.Model, learning_rate: float = 1e-3, lr_decay=0, weight_decay=0, initial_accumulator_value = 0, eps=1e-10): + +def adagrad(net: keras.Model, learning_rate: float = 1e-3, lr_decay=0, weight_decay=0, initial_accumulator_value=0, eps=1e-10): """ Creates an Adagrad optimizer for 'net', alias for ['keras.optimizers.Adagrad'](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adagrad) Analogous functions exist for other learning frameworks. """ return keras.optimizers.Adagrad(learning_rate, initial_accumulator_value, eps) + def rmsprop(net: keras.Model, learning_rate: float = 1e-3, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False): """ Creates an RMSProp optimizer for 'net', alias for ['keras.optimizers.RMSprop'](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/RMSprop) @@ -160,10 +170,9 @@ def rmsprop(net: keras.Model, learning_rate: float = 1e-3, alpha=0.99, eps=1e-08 def dense_net(in_channels: int, out_channels: int, - layers: tuple or list, + layers: Tuple[int, ...] or List[int], batch_norm=False, activation='ReLU') -> keras.Model: - activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation keras_layers = [] for neuron_count in layers: @@ -196,16 +205,16 @@ def u_net(in_channels: int, filters = (filters,) * levels # --- Construct the U-Net --- x = inputs = keras.Input(shape=in_spatial + (in_channels,)) - x = ResNet_Block(x.shape[-1], filters[0], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[0], filters[0], batch_norm, activation) + x = resnet_block(x, x.shape[-1], filters[0], batch_norm, activation, d) if use_res_blocks else double_conv(x, d, filters[0], filters[0], batch_norm, activation) xs = [x] for i in range(1, levels): x = MAX_POOL[d](2, padding="same")(x) - x = ResNet_Block(x.shape[-1], filters[i], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[i], filters[i], batch_norm, activation) + x = resnet_block(x, x.shape[-1], filters[i], batch_norm, activation, d) if use_res_blocks else double_conv(x, d, filters[i], filters[i], batch_norm, activation) xs.insert(0, x) for i in range(1, levels): x = UPSAMPLE[d](2)(x) x = kl.Concatenate()([x, xs[i]]) - x = ResNet_Block(x.shape[-1], filters[i-1], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[i - 1], filters[i - 1], batch_norm, activation) + x = resnet_block(x, x.shape[-1], filters[i - 1], batch_norm, activation, d) if use_res_blocks else double_conv(x, d, filters[i - 1], filters[i - 1], batch_norm, activation) x = CONV[d](out_channels, 1)(x) return keras.Model(inputs, x) @@ -216,35 +225,38 @@ def u_net(in_channels: int, ACTIVATIONS = {'tanh': keras.activations.tanh, 'ReLU': keras.activations.relu, 'Sigmoid': keras.activations.sigmoid, 'SiLU': keras.activations.selu} -def pad_periodic(x:Tensor): +def pad_periodic(x: Tensor): d = len(x.shape) - 2 - if d>=1: + if d >= 1: x = tf.concat([tf.expand_dims(x[:, -1, ...], axis=1), x, tf.expand_dims(x[:, 0, ...], axis=1)], axis=1) - if d>=2: + if d >= 2: x = tf.concat([tf.expand_dims(x[:, :, -1, ...], axis=2), x, tf.expand_dims(x[:, :, 0, ...], axis=2)], axis=2) - if d>=3: - x = tf.concat([tf.expand_dims(x[:, :, :, -1, ...], axis=3), x, tf.expand_dims(x[:, :, :, 0, ...], axis=3)], axis=3) + if d >= 3: + x = tf.concat([tf.expand_dims(x[:, :, :, -1, ...], axis=3), x, tf.expand_dims(x[:, :, :, 0, ...], axis=3)], + axis=3) return x -def double_conv(x, d: int, out_channels: int, mid_channels: int, batch_norm: bool, activation: Callable): + +def double_conv(x, d: int, out_channels: int, mid_channels: int, batch_norm: bool, activation: Callable): x = pad_periodic(x) x = CONV[d](mid_channels, 3, padding='valid')(x) - #x = CONV[d](mid_channels, 3, padding='same')(x) + # x = CONV[d](mid_channels, 3, padding='same')(x) if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) x = pad_periodic(x) x = CONV[d](out_channels, 3, padding='valid')(x) - #x = CONV[d](out_channels, 3, padding='same')(x) + # x = CONV[d](out_channels, 3, padding='same')(x) if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) return x + def conv_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int, ...] = [], + layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2) -> keras.Model: @@ -264,11 +276,12 @@ def conv_net(in_channels: int, if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) - #x = pad_periodic(x) + # x = pad_periodic(x) x = CONV[in_spatial](out_channels, 1)(x) return keras.Model(inputs, x) -def ResNet_Block(in_channels: int, + +def resnet_block(x, in_channels: int, out_channels: int, batch_norm: bool = False, activation: str or Callable = 'ReLU', @@ -277,44 +290,36 @@ def ResNet_Block(in_channels: int, if isinstance(in_spatial, int): d = (None,) * in_spatial else: - #assert isinstance(in_spatial, tuple) + assert isinstance(in_spatial, tuple) d = in_spatial in_spatial = len(d) - d = (None,) * in_spatial - - inputs = keras.Input(shape = d + (in_channels,)) + inputs = keras.Input(shape=d + (in_channels,)) x_1 = inputs x = pad_periodic(inputs) - x = CONV[in_spatial](out_channels, 3, padding='valid')(x) if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) - x = pad_periodic(x) - x = CONV[in_spatial](out_channels, 3, padding='valid')(x) if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) - if in_channels != out_channels: x_1 = CONV[in_spatial](out_channels, 1)(x_1) if batch_norm: x_1 = kl.BatchNormalization()(x_1) - x = kl.Add()([x, x_1]) - return keras.Model(inputs, x) + def res_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int, ...] = [], + layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2): - if isinstance(in_spatial, int): d = (None,) * in_spatial else: @@ -323,36 +328,31 @@ def res_net(in_channels: int, in_spatial = len(d) x = inputs = keras.Input(shape=d + (in_channels,)) - if len(layers) < 1: layers.append(out_channels) - out = ResNet_Block(in_channels, layers[0], batch_norm, activation, in_spatial)(x) - + out = resnet_block(in_channels, layers[0], batch_norm, activation, in_spatial)(x) for i in range(1, len(layers)): - out = ResNet_Block(layers[i-1], layers[i], batch_norm, activation, in_spatial)(out) - + out = resnet_block(layers[i - 1], layers[i], batch_norm, activation, in_spatial)(out) out = CONV[in_spatial](out_channels, 1)(out) - return keras.Model(inputs, out) def conv_classifier(input_shape: list, num_classes: int, batch_norm: bool, in_spatial: int or tuple): if isinstance(in_spatial, int): d = in_spatial - in_spatial = (None, ) * d + in_spatial = (None,) * d else: assert isinstance(in_spatial, tuple) d = len(in_spatial) - #input_shape[0] = Channels + # input_shape[0] = Channels spatial_shape_list = list(input_shape[1:]) - x = inputs = keras.Input(shape= in_spatial + (input_shape[0],)) + x = inputs = keras.Input(shape=in_spatial + (input_shape[0],)) x = double_conv(x, d, 64, 64, batch_norm, ACTIVATIONS['ReLU']) x = MAX_POOL[d](2)(x) x = double_conv(x, d, 128, 128, batch_norm, ACTIVATIONS['ReLU']) x = MAX_POOL[d](2)(x) - x = double_conv(x, d, 256, 256, batch_norm, ACTIVATIONS['ReLU']) x = pad_periodic(x) x = CONV[d](256, 3, padding='valid')(x) @@ -393,7 +393,8 @@ def conv_classifier(input_shape: list, num_classes: int, batch_norm: bool, in_sp return keras.Model(inputs, x) -def get_mask(inputs, reverse_mask, data_format = 'NHWC'): + +def get_mask(inputs, reverse_mask, data_format='NHWC'): shape = inputs.shape if len(shape) == 2: N = shape[-1] @@ -410,10 +411,10 @@ def get_mask(inputs, reverse_mask, data_format = 'NHWC'): even_ind_h = tf.cast(range_h % 2, dtype=tf.bool) even_ind_w = tf.cast(range_w % 2, dtype=tf.bool) - ind_h = tf.tile(tf.expand_dims(even_ind_h, -1), [1,W]) - ind_w = tf.tile(tf.expand_dims(even_ind_w, 0), [H,1]) - #ind_h = even_ind_h.unsqueeze(-1).repeat(1, W) - #ind_w = even_ind_w.unsqueeze( 0).repeat(H, 1) + ind_h = tf.tile(tf.expand_dims(even_ind_h, -1), [1, W]) + ind_w = tf.tile(tf.expand_dims(even_ind_w, 0), [H, 1]) + # ind_h = even_ind_h.unsqueeze(-1).repeat(1, W) + # ind_w = even_ind_w.unsqueeze( 0).repeat(H, 1) checker = tf.math.logical_xor(ind_h, ind_w) @@ -430,11 +431,12 @@ def get_mask(inputs, reverse_mask, data_format = 'NHWC'): return checker -def Dense_ResNet_Block(in_channels: int, + +def Dense_resnet_block(in_channels: int, mid_channels: int, batch_norm: bool = False, activation: str or Callable = 'ReLU'): - inputs = keras.Input(shape = (in_channels,)) + inputs = keras.Input(shape=(in_channels,)) x_1 = inputs activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation @@ -452,7 +454,10 @@ def Dense_ResNet_Block(in_channels: int, return keras.Model(inputs, x) + NET = {'u_net': u_net, 'res_net': res_net, 'conv_net': conv_net} + + class CouplingLayer(keras.Model): def __init__(self, in_channels, activation, batch_norm, in_spatial, net, reverse_mask): @@ -462,13 +467,12 @@ def __init__(self, in_channels, activation, batch_norm, in_spatial, net, reverse self.batch_norm = batch_norm self.reverse_mask = reverse_mask + if in_spatial == 0: # for in_spatial = 0, use dense layers + self.s1 = Dense_resnet_block(in_channels, in_channels, batch_norm, activation) + self.t1 = Dense_resnet_block(in_channels, in_channels, batch_norm, activation) - if in_spatial == 0: #for in_spatial = 0, use dense layers - self.s1 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) - self.t1 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) - - self.s2 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) - self.t2 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) + self.s2 = Dense_resnet_block(in_channels, in_channels, batch_norm, activation) + self.t2 = Dense_resnet_block(in_channels, in_channels, batch_norm, activation) else: self.s1 = NET[net](in_channels=in_channels, out_channels=in_channels, batch_norm=batch_norm, activation=activation, @@ -489,21 +493,22 @@ def call(self, x, invert=False): if invert: v1 = x * mask - v2 = x * (1-mask) + v2 = x * (1 - mask) - u2 = (1-mask) * (v2 - self.t1(v1)) * tf.math.exp( tf.tanh(-self.s1(v1))) - u1 = mask * (v1 - self.t2(u2)) * tf.math.exp( tf.tanh(-self.s2(u2))) + u2 = (1 - mask) * (v2 - self.t1(v1)) * tf.math.exp(tf.tanh(-self.s1(v1))) + u1 = mask * (v1 - self.t2(u2)) * tf.math.exp(tf.tanh(-self.s2(u2))) return u1 + u2 else: u1 = x * mask - u2 = x * (1-mask) + u2 = x * (1 - mask) - v1 = mask * (u1 * tf.math.exp( tf.tanh(self.s2(u2))) + self.t2(u2)) - v2 = (1-mask) * (u2 * tf.math.exp( tf.tanh(self.s1(v1))) + self.t1(v1)) + v1 = mask * (u1 * tf.math.exp(tf.tanh(self.s2(u2))) + self.t2(u2)) + v2 = (1 - mask) * (u2 * tf.math.exp(tf.tanh(self.s1(v1))) + self.t1(v1)) return v1 + v2 + class InvertibleNet(keras.Model): def __init__(self, in_channels, num_blocks, activation, batch_norm, in_spatial, net): super(InvertibleNet, self).__init__() @@ -511,29 +516,29 @@ def __init__(self, in_channels, num_blocks, activation, batch_norm, in_spatial, self.layer_dict = {} for i in range(num_blocks): - self.layer_dict[f'coupling_block{i+1}'] = \ + self.layer_dict[f'coupling_block{i + 1}'] = \ CouplingLayer(in_channels, - activation, batch_norm, - in_spatial, net, (i%2==0)) + activation, batch_norm, + in_spatial, net, (i % 2 == 0)) def call(self, x, backward=False): if backward: for i in range(self.num_blocks, 0, -1): x = self.layer_dict[f'coupling_block{i}'](x, backward) else: - for i in range(1, self.num_blocks+1): + for i in range(1, self.num_blocks + 1): x = self.layer_dict[f'coupling_block{i}'](x) return x + def invertible_net(in_channels: int, - num_blocks: int, - batch_norm: bool = False, - net: str = 'u_net', - activation: str or type='ReLU', - in_spatial: tuple or int=2): + num_blocks: int, + batch_norm: bool = False, + net: str = 'u_net', + activation: str or type = 'ReLU', + in_spatial: tuple or int = 2): if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) return InvertibleNet(in_channels, num_blocks, activation, batch_norm, in_spatial, net) - diff --git a/phi/torch/nets.py b/phi/torch/nets.py index 0014d58fe..df08e5e1f 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -4,12 +4,11 @@ For API documentation, see https://tum-pbs.github.io/PhiFlow/Network_API . """ -from typing import Callable, Dict, List, Tuple +from typing import Callable, List, Tuple import numpy import torch import torch.nn as nn -import torch.nn.functional as F from torch import optim from .. import math @@ -214,7 +213,7 @@ def __init__(self, d: int, in_channels: int, out_channels: int, filters: tuple, self._levels = len(filters) self._spatial_rank = d if use_res_blocks: - self.add_module('inc', ResNet_Block(d, in_channels, filters[0], batch_norm, activation)) + self.add_module('inc', resnet_block(d, in_channels, filters[0], batch_norm, activation)) else: self.add_module('inc', DoubleConv(d, in_channels, filters[0], filters[0], batch_norm, activation)) for i in range(1, self._levels): @@ -267,7 +266,7 @@ def __init__(self, d: int, in_channels: int, out_channels: int, batch_norm: bool super().__init__() self.add_module('maxpool', MAX_POOL[d](2)) if use_res_blocks: - self.add_module('conv', ResNet_Block(d, in_channels, out_channels, batch_norm, activation)) + self.add_module('conv', resnet_block(d, in_channels, out_channels, batch_norm, activation)) else: self.add_module('conv', DoubleConv(d, in_channels, out_channels, out_channels, batch_norm, activation)) @@ -288,13 +287,13 @@ def __init__(self, d: int, in_channels: int, out_channels: int, batch_norm: bool # if bilinear, use the normal convolutions to reduce the number of channels up = nn.Upsample(scale_factor=2, mode=Up._MODES[d]) if use_res_blocks: - conv = ResNet_Block(d, in_channels, out_channels, batch_norm, activation) + conv = resnet_block(d, in_channels, out_channels, batch_norm, activation) else: conv = DoubleConv(d, in_channels, out_channels, in_channels // 2, batch_norm, activation) else: up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2) if use_res_blocks: - conv = ResNet_Block(d, in_channels, out_channels, batch_norm, activation) + conv = resnet_block(d, in_channels, out_channels, batch_norm, activation) else: conv = DoubleConv(d, in_channels, out_channels, out_channels, batch_norm, activation) self.add_module('up', up) @@ -317,14 +316,10 @@ class ConvNet(nn.Module): def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, activation): super(ConvNet, self).__init__() - activation = ACTIVATIONS[activation] - if len(layers) < 1: layers.append(out_channels) - self.layers = layers - self.add_module(f'Conv_in', nn.Sequential( CONV[in_spatial](in_channels, layers[0], kernel_size=3, padding=1, padding_mode='circular'), NORM[in_spatial](layers[0]) if batch_norm else nn.Identity(), @@ -337,18 +332,16 @@ def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, ac self.add_module(f'Conv_out', CONV[in_spatial](layers[len(layers) - 1], out_channels, kernel_size=1)) def forward(self, x): - x = getattr(self, f'Conv_in')(x) for i in range(1, len(self.layers)): x = getattr(self, f'Conv{i}')(x) x = getattr(self, f'Conv_out')(x) - return x def conv_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int] = [], + layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or type = 'ReLU', in_spatial: int or tuple = 2) -> nn.Module: @@ -362,61 +355,52 @@ def conv_net(in_channels: int, return net -class ResNet_Block(nn.Module): +class resnet_block(nn.Module): def __init__(self, in_spatial, in_channels, out_channels, batch_norm, activation): # Since in_channels and out_channels might be different # we need a sampling layer for up/down sampling input # in order to add it as a skip connection - super(ResNet_Block, self).__init__() + super(resnet_block, self).__init__() if in_channels != out_channels: self.sample_input = CONV[in_spatial](in_channels, out_channels, kernel_size=1, padding=0) self.bn_sample = NORM[in_spatial](out_channels) if batch_norm else nn.Identity() else: self.sample_input = nn.Identity() self.bn_sample = nn.Identity() - self.activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation - self.bn1 = NORM[in_spatial](out_channels) if batch_norm else nn.Identity() self.conv1 = CONV[in_spatial](in_channels, out_channels, kernel_size=3, padding=1, padding_mode='circular') - self.bn2 = NORM[in_spatial](out_channels) if batch_norm else nn.Identity() self.conv2 = CONV[in_spatial](out_channels, out_channels, kernel_size=3, padding=1, padding_mode='circular') def forward(self, x): x = TORCH.as_tensor(x) out = self.activation()(self.bn1(self.conv1(x))) - out = self.activation()(self.bn2(self.conv2(out))) - out = (out + self.bn_sample(self.sample_input(x))) - return out -class Dense_ResNet_Block(nn.Module): - def __init__(self, in_channels, mid_channels, batch_norm, activation): - super(Dense_ResNet_Block, self).__init__() +class Dense_resnet_block(nn.Module): + def __init__(self, in_channels, mid_channels, batch_norm, activation): + super(Dense_resnet_block, self).__init__() self.activation = activation self.bn1 = NORM[1](in_channels) if batch_norm else nn.Identity() self.linear1 = nn.Linear(in_channels, mid_channels) - self.bn2 = NORM[1](mid_channels) if batch_norm else nn.Identity() self.linear2 = nn.Linear(mid_channels, in_channels) def forward(self, x): x = TORCH.as_tensor(x) out = self.activation()(self.bn1(self.linear1(x))) - out = self.activation()(self.bn2(self.linear2(out))) - out = out + x - return out -def get_mask(inputs, reverse_mask, data_format = 'NHWC'): + +def get_mask(inputs, reverse_mask, data_format='NHWC'): shape = inputs.shape if len(shape) == 2: N = shape[-1] @@ -434,7 +418,7 @@ def get_mask(inputs, reverse_mask, data_format = 'NHWC'): even_ind_w = range_w % 2 ind_h = even_ind_h.unsqueeze(-1).repeat(1, W) - ind_w = even_ind_w.unsqueeze( 0).repeat(H, 1) + ind_w = even_ind_w.unsqueeze(0).repeat(H, 1) checker = torch.logical_xor(ind_h, ind_w) @@ -450,39 +434,35 @@ def get_mask(inputs, reverse_mask, data_format = 'NHWC'): return checker.to(TORCH.get_default_device().ref) + class ResNet(nn.Module): def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, activation): super(ResNet, self).__init__() self.layers = layers - if len(self.layers) < 1: layers.append(out_channels) - self.add_module('Res_in', ResNet_Block(in_spatial, in_channels, layers[0], batch_norm, activation)) - + self.add_module('Res_in', resnet_block(in_spatial, in_channels, layers[0], batch_norm, activation)) for i in range(1, len(layers)): - self.add_module(f'Res{i}', ResNet_Block(in_spatial, layers[i-1], layers[i], batch_norm, activation)) - - self.add_module('Res_out', CONV[in_spatial](layers[len(layers)-1], out_channels, kernel_size=1)) + self.add_module(f'Res{i}', resnet_block(in_spatial, layers[i - 1], layers[i], batch_norm, activation)) + self.add_module('Res_out', CONV[in_spatial](layers[len(layers) - 1], out_channels, kernel_size=1)) def forward(self, x): x = TORCH.as_tensor(x) - x = getattr(self, 'Res_in')(x) for i in range(1, len(self.layers)): x = getattr(self, f'Res{i}')(x) x = getattr(self, 'Res_out')(x) - return x def res_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int] = [], + layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or type = 'ReLU', in_spatial: int or tuple = 2) -> nn.Module: - if (isinstance(in_spatial, int)): + if isinstance(in_spatial, int): d = in_spatial else: assert isinstance(in_spatial, tuple) @@ -510,26 +490,20 @@ def __init__(self, d: int, input_shape: list, num_classes: int, batch_norm: bool self.spatial_shape_list = list(input_shape[1:]) self.add_module('maxpool', MAX_POOL[d](2)) - self.add_module('conv1', DoubleConv(d, input_shape[0], 64, 64, batch_norm, ACTIVATIONS['ReLU'])) - self.add_module('conv2', DoubleConv(d, 64, 128, 128, batch_norm, ACTIVATIONS['ReLU'])) - self.add_module('conv3', nn.Sequential(DoubleConv(d, 128, 256, 256, batch_norm, ACTIVATIONS['ReLU']), CONV[d](256, 256, 3, padding=1, padding_mode='circular'), NORM[d](256) if batch_norm else nn.Identity(), nn.ReLU())) - self.add_module('conv4', nn.Sequential(DoubleConv(d, 256, 512, 512, batch_norm, ACTIVATIONS['ReLU']), CONV[d](512, 512, 3, padding=1, padding_mode='circular'), NORM[d](512) if batch_norm else nn.Identity(), nn.ReLU())) - self.add_module('conv5', nn.Sequential(DoubleConv(d, 512, 512, 512, batch_norm, ACTIVATIONS['ReLU']), CONV[d](512, 512, 3, padding=1, padding_mode='circular'), NORM[d](512) if batch_norm else nn.Identity(), nn.ReLU())) - for i in range(5): for j in range(len(self.spatial_shape_list)): self.spatial_shape_list[j] = math.floor((self.spatial_shape_list[j] - 2) / 2) + 1 @@ -538,13 +512,11 @@ def __init__(self, d: int, input_shape: list, num_classes: int, batch_norm: bool for i in range(len(self.spatial_shape_list)): flattened_input_dim *= self.spatial_shape_list[i] flattened_input_dim *= 512 - self.linear = dense_net(flattened_input_dim, num_classes, [4096, 4096, 100], batch_norm, 'ReLU') self.flatten = nn.Flatten() self.softmax = nn.Softmax() def forward(self, x): - for i in range(5): x = getattr(self, f'conv{i + 1}')(x) x = self.maxpool(x) @@ -552,8 +524,10 @@ def forward(self, x): x = self.softmax(self.linear(x)) return x + NET = {'u_net': u_net, 'res_net': res_net, 'conv_net': conv_net} + class CouplingLayer(nn.Module): def __init__(self, in_channels, activation, batch_norm, in_spatial, net, reverse_mask): @@ -563,14 +537,14 @@ def __init__(self, in_channels, activation, batch_norm, in_spatial, net, reverse self.batch_norm = batch_norm self.reverse_mask = reverse_mask - if in_spatial == 0: #for in_spatial = 0, use dense layers - self.s1 = nn.Sequential(Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation), + if in_spatial == 0: # for in_spatial = 0, use dense layers + self.s1 = nn.Sequential(Dense_resnet_block(in_channels, in_channels, batch_norm, activation), torch.nn.Tanh()) - self.t1 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) + self.t1 = Dense_resnet_block(in_channels, in_channels, batch_norm, activation) - self.s2 = nn.Sequential(Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation), + self.s2 = nn.Sequential(Dense_resnet_block(in_channels, in_channels, batch_norm, activation), torch.nn.Tanh()) - self.t2 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) + self.t2 = Dense_resnet_block(in_channels, in_channels, batch_norm, activation) else: self.s1 = nn.Sequential(NET[net](in_channels=in_channels, out_channels=in_channels, batch_norm=batch_norm, activation=activation, @@ -585,68 +559,60 @@ def __init__(self, in_channels, activation, batch_norm, in_spatial, net, reverse batch_norm=batch_norm, activation=activation, in_spatial=in_spatial) - def forward(self, x, invert=False): x = TORCH.as_tensor(x) mask = get_mask(x, self.reverse_mask, 'NCHW') - if invert: v1 = x * mask - v2 = x * (1-mask) - - u2 = (1-mask) * (v2 - self.t1(v1)) * torch.exp(-self.s1(v1)) + v2 = x * (1 - mask) + u2 = (1 - mask) * (v2 - self.t1(v1)) * torch.exp(-self.s1(v1)) u1 = mask * (v1 - self.t2(u2)) * torch.exp(-self.s2(u2)) - return u1 + u2 else: u1 = x * mask - u2 = x * (1-mask) - - v1 = mask * (u1 * torch.exp( self.s2(u2)) + self.t2(u2)) - v2 = (1-mask) * (u2 * torch.exp( self.s1(v1)) + self.t1(v1)) - + u2 = x * (1 - mask) + v1 = mask * (u1 * torch.exp(self.s2(u2)) + self.t2(u2)) + v2 = (1 - mask) * (u2 * torch.exp(self.s1(v1)) + self.t1(v1)) return v1 + v2 + class InvertibleNet(nn.Module): def __init__(self, in_channels, num_blocks, activation, batch_norm, in_spatial, net): super(InvertibleNet, self).__init__() self.num_blocks = num_blocks - for i in range(num_blocks): - self.add_module(f'coupling_block{i+1}', + self.add_module(f'coupling_block{i + 1}', CouplingLayer(in_channels, activation, - batch_norm, in_spatial, net, (i%2==0))) + batch_norm, in_spatial, net, (i % 2 == 0))) def forward(self, x, backward=False): if backward: for i in range(self.num_blocks, 0, -1): x = getattr(self, f'coupling_block{i}')(x, backward) else: - for i in range(1, self.num_blocks+1): + for i in range(1, self.num_blocks + 1): x = getattr(self, f'coupling_block{i}')(x, backward) return x def invertible_net(in_channels: int, - num_blocks: int, - batch_norm: bool = False, - net: str = 'u_net', - activation: str or type='ReLU', - in_spatial: tuple or int=2): + num_blocks: int, + batch_norm: bool = False, + net: str = 'u_net', + activation: str or type = 'ReLU', + in_spatial: tuple or int = 2): if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - return InvertibleNet(in_channels, num_blocks, activation, batch_norm, in_spatial, net).to(TORCH.get_default_device().ref) def coupling_layer(in_channels: int, - activation: str or type='ReLU', + activation: str or type = 'ReLU', batch_norm=False, reverse_mask=False, - in_spatial: tuple or int=2): + in_spatial: tuple or int = 2): if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - - net = CouplingLayer(in_channels, activation, batch_norm, in_spatial, reverse_mask) + net = CouplingLayer(in_channels, activation, batch_norm, in_spatial, reverse_mask) net = net.to(TORCH.get_default_device().ref) - return net \ No newline at end of file + return net From 410aeaf844b2391baa0eff9e406cbeb1230b64f0 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Tue, 20 Sep 2022 13:15:03 +0200 Subject: [PATCH 010/170] Ported Fourier_Neural_Operators for pytorch --- ...n_identify_noise.py => FNO_train_noise.py} | 7 +- phi/torch/flow.py | 6 - phi/torch/nets.py | 191 +++++++++++++++++- 3 files changed, 185 insertions(+), 19 deletions(-) rename demos/{train_identify_noise.py => FNO_train_noise.py} (88%) diff --git a/demos/train_identify_noise.py b/demos/FNO_train_noise.py similarity index 88% rename from demos/train_identify_noise.py rename to demos/FNO_train_noise.py index 07bc8ee1f..efdc8a5fc 100644 --- a/demos/train_identify_noise.py +++ b/demos/FNO_train_noise.py @@ -1,10 +1,8 @@ import math -from phi.jax.stax.flow import * - - -net = u_net(1, 2, in_spatial=2, use_res_blocks=True, activation='SiLU') +from phi.torch.flow import * +net = fno(1, 2, 3, modes=12, activation='GeLU') optimizer = adam(net, learning_rate=1e-3) @@ -25,6 +23,7 @@ def loss_function(scale: Tensor, smoothness: Tensor): viewer = view(gui='dash', scene=True) for i in viewer.range(): + if i == 100: break loss = update_weights(net, optimizer, loss_function, gt_scale, gt_smoothness) print(f'Iter : {i}, Loss : {loss}') viewer.log_scalars(loss=loss) \ No newline at end of file diff --git a/phi/torch/flow.py b/phi/torch/flow.py index 12aec5b69..b4b1c4aa5 100644 --- a/phi/torch/flow.py +++ b/phi/torch/flow.py @@ -14,12 +14,6 @@ from phi.flow import * from . import TORCH -from .nets import parameter_count, get_parameters, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, sgd, sgd as SGD, rmsprop, adagrad, conv_classifier, invertible_net - -import torch -import torch.nn.functional as torchf -import torch.optim as optim - if not backend.context_backend(): backend.set_global_default_backend(TORCH) diff --git a/phi/torch/nets.py b/phi/torch/nets.py index 0014d58fe..59eceb6f9 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -4,17 +4,16 @@ For API documentation, see https://tum-pbs.github.io/PhiFlow/Network_API . """ -from typing import Callable, Dict, List, Tuple +from typing import Callable, List, Tuple import numpy import torch import torch.nn as nn -import torch.nn.functional as F from torch import optim -from .. import math from . import TORCH from ._torch_backend import register_module_call +from .. import math from ..math import channel @@ -141,7 +140,7 @@ def rmsprop(net: nn.Module, learning_rate: float = 1e-3, alpha=0.99, eps=1e-08, CONV = [None, nn.Conv1d, nn.Conv2d, nn.Conv3d] NORM = [None, nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d] -ACTIVATIONS = {'ReLU': nn.ReLU, 'Sigmoid': nn.Sigmoid, 'tanh': nn.Tanh, 'SiLU': nn.SiLU} +ACTIVATIONS = {'ReLU': nn.ReLU, 'Sigmoid': nn.Sigmoid, 'tanh': nn.Tanh, 'SiLU': nn.SiLU, 'GeLU': nn.GELU} def dense_net(in_channels: int, @@ -636,17 +635,191 @@ def invertible_net(in_channels: int, if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - return InvertibleNet(in_channels, num_blocks, activation, batch_norm, in_spatial, net).to(TORCH.get_default_device().ref) + return InvertibleNet(in_channels, num_blocks, activation, batch_norm, in_spatial, + net).to(TORCH.get_default_device().ref) def coupling_layer(in_channels: int, - activation: str or type='ReLU', + activation: str or type = 'ReLU', batch_norm=False, reverse_mask=False, - in_spatial: tuple or int=2): + in_spatial: tuple or int = 2): if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - net = CouplingLayer(in_channels, activation, batch_norm, in_spatial, reverse_mask) + net = CouplingLayer(in_channels, activation, batch_norm, in_spatial, reverse_mask) net = net.to(TORCH.get_default_device().ref) - return net \ No newline at end of file + return net + + +################################################################################################################## +# Fourier Neural Operators +# source: https://github.com/zongyi-li/fourier_neural_operator +################################################################################################################### + +class SpectralConv(nn.Module): + + def __init__(self, in_channels, out_channels, modes, in_spatial): + + super(SpectralConv, self).__init__() + + self.in_channels = in_channels + self.out_channels = out_channels + self.in_spatial = in_spatial + assert in_spatial >= 1 and in_spatial <= 3 + if isinstance(modes, int): + mode = modes + modes = [mode for i in range(in_spatial)] + + self.scale = 1 / (in_channels * out_channels) + + self.modes = {i + 1: modes[i] for i in range(len(modes))} + self.weights = {} + + rand_shape = [in_channels, out_channels] + rand_shape += [self.modes[i] for i in range(1, in_spatial + 1)] + + for i in range(2 ** (in_spatial - 1)): + self.weights[f'w{i + 1}'] = nn.Parameter(self.scale * torch.randn(rand_shape, dtype=torch.cfloat)) + + def complex_mul(self, input, weights): + + if self.in_spatial == 1: + return torch.einsum("bix,iox->box", input, weights) + elif self.in_spatial == 2: + return torch.einsum("bixy,ioxy->boxy", input, weights) + elif self.in_spatial == 3: + return torch.einsum("bixyz,ioxyz->boxyz", input, weights) + + def forward(self, x): + batch_size = x.shape[0] + + ##Convert to Fourier space + dims = [-i for i in range(self.in_spatial, 0, -1)] + x_ft = torch.fft.rfftn(x, dim=dims) + + outft_dims = [batch_size, self.out_channels] + \ + [x.size(-i) for i in range(self.in_spatial, 1, -1)] + [x.size(-1) // 2 + 1] + out_ft = torch.zeros(outft_dims, dtype=torch.cfloat, device=x.device) + + ##Multiply relevant fourier modes + if self.in_spatial == 1: + out_ft[:, :, :self.modes[1]] = \ + self.complex_mul(x_ft[:, :, :self.modes[1]], + self.weights['w1'].to(x_ft.device)) + elif self.in_spatial == 2: + out_ft[:, :, :self.modes[1], :self.modes[2]] = \ + self.complex_mul(x_ft[:, :, :self.modes[1], :self.modes[2]], + self.weights['w1'].to(x_ft.device)) + out_ft[:, :, -self.modes[1]:, :self.modes[2]] = \ + self.complex_mul(x_ft[:, :, -self.modes[1]:, :self.modes[2]], + self.weights['w2'].to(x_ft.device)) + elif self.in_spatial == 3: + out_ft[:, :, :self.modes[1], :self.modes[2], :self.modes[3]] = \ + self.complex_mul(x_ft[:, :, :self.modes[1], :self.modes[2], :self.modes[3]], + self.weights['w1'].to(x_ft.device)) + out_ft[:, :, -self.modes[1]:, :self.modes[2], :self.modes[3]] = \ + self.complex_mul(x_ft[:, :, -self.modes[1]:, :self.modes[2], :self.modes[3]], + self.weights['w2'].to(x_ft.device)) + out_ft[:, :, :self.modes[1], -self.modes[2]:, :self.modes[3]] = \ + self.complex_mul(x_ft[:, :, :self.modes[1], -self.modes[2]:, :self.modes[3]], + self.weights['w3'].to(x_ft.device)) + out_ft[:, :, -self.modes[1]:, -self.modes[2]:, :self.modes[3]] = \ + self.complex_mul(x_ft[:, :, -self.modes[1]:, -self.modes[2]:, :self.modes[3]], + self.weights['w4'].to(x_ft.device)) + + ##Return to Physical Space + x = torch.fft.irfftn(out_ft, s=[x.size(-i) for i in range(self.in_spatial, 0, -1)]) + + return x + + +class FNO(nn.Module): + + def __init__(self, in_channels, out_channels, width, modes, activation, batch_norm, in_spatial): + super(FNO, self).__init__() + + """ + The overall network. It contains 4 layers of the Fourier layer. + 1. Lift the input to the desire channel dimension by self.fc0 . + 2. 4 layers of the integral operators u' = (W + K)(u). + W defined by self.w; K defined by self.conv . + 3. Project from the channel space to the output space by self.fc1 and self.fc2 . + + input: the solution of the first 10 timesteps + 3 locations (u(1, x, y), ..., u(10, x, y), x, y, t). + It's a constant function in time, except for the last index. + input shape: (batchsize, x=64, y=64, t=40, c=13) + output: the solution of the next 40 timesteps + output shape: (batchsize, x=64, y=64, t=40, c=1) + """ + + self.activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation + self.width = width + self.in_spatial = in_spatial + + self.fc0 = nn.Linear(in_channels + in_spatial, self.width) + + for i in range(4): + self.add_module(f'conv{i}', SpectralConv(self.width, self.width, modes, in_spatial)) + self.add_module(f'w{i}', CONV[in_spatial](self.width, self.width, kernel_size=1)) + self.add_module(f'bn{i}', NORM[in_spatial](self.width) if batch_norm else nn.Identity()) + + self.fc1 = nn.Linear(self.width, 128) + self.fc2 = nn.Linear(128, out_channels) + + # Adding extra spatial channels eg. x, y, z, .... to input x + def get_grid(self, shape, device): + batch_size = shape[0] + grid_channel_sizes = shape[2:] # shape = (batch_size, channels, *spatial) + self.grid_channels = {} + for i in range(self.in_spatial): + self.grid_channels[f'dim{i}'] = torch.tensor(torch.linspace(0, 1, grid_channel_sizes[i]), + dtype=torch.float) + reshape_dim_tuple = [1, 1] + [1 if i != j else grid_channel_sizes[j] for j in range(self.in_spatial)] + repeat_dim_tuple = [batch_size, 1] + [1 if i == j else grid_channel_sizes[j] for j in + range(self.in_spatial)] + self.grid_channels[f'dim{i}'] = self.grid_channels[f'dim{i}'].reshape(reshape_dim_tuple) \ + .repeat(repeat_dim_tuple) + + return torch.cat([self.grid_channels[f'dim{i}'] for i in range(self.in_spatial)], dim=1).to(device) + + def forward(self, x): + grid = self.get_grid(x.shape, x.device) + x = torch.cat([x, grid], dim=1) + + permute_tuple = [0] + [2 + i for i in range(self.in_spatial)] + [1] + permute_tuple_reverse = [0] + [self.in_spatial + 1] + [i + 1 for i in range(self.in_spatial)] + + # Transpose x such that channels shape lies at the end to pass it through linear layers + x = x.permute(permute_tuple) + + x = self.fc0(x) + + # Transpose x back to its original shape to pass it through convolutional layers + x = x.permute(permute_tuple_reverse) + + for i in range(4): + x1 = getattr(self, f'w{i}')(x) + x2 = getattr(self, f'conv{i}')(x) + x = getattr(self, f'bn{i}')(x1) + getattr(self, f'bn{i}')(x2) + x = self.activation()(x) + + x = x.permute(permute_tuple) + x = self.activation()(self.fc1(x)) + x = self.fc2(x) + + x = x.permute(permute_tuple_reverse) + + return x + + +def fno(in_channels: int, + out_channels: int, + mid_channels: int, + modes: Tuple[int, ...] or List[int], + activation: str or type = 'ReLU', + batch_norm: bool = False, + in_spatial: int = 2): + net = FNO(in_channels, out_channels, mid_channels, modes, activation, batch_norm, in_spatial) + net = net.to(TORCH.get_default_device().ref) + return net From a6a086cad74e770a66b57ba14d40adadb6ee3e6c Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Tue, 20 Sep 2022 13:21:14 +0200 Subject: [PATCH 011/170] Ported Fourier_Neural_Operators for pytorch --- phi/torch/nets.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/phi/torch/nets.py b/phi/torch/nets.py index 59eceb6f9..f5416ed06 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -744,13 +744,9 @@ def __init__(self, in_channels, out_channels, width, modes, activation, batch_no 1. Lift the input to the desire channel dimension by self.fc0 . 2. 4 layers of the integral operators u' = (W + K)(u). W defined by self.w; K defined by self.conv . - 3. Project from the channel space to the output space by self.fc1 and self.fc2 . + 3. Project from the channel space to the output space by self.fc1 and self.fc2. - input: the solution of the first 10 timesteps + 3 locations (u(1, x, y), ..., u(10, x, y), x, y, t). - It's a constant function in time, except for the last index. - input shape: (batchsize, x=64, y=64, t=40, c=13) - output: the solution of the next 40 timesteps - output shape: (batchsize, x=64, y=64, t=40, c=1) + input shape and output shape: (batchsize b, channels c, *spatial) """ self.activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation From 1990dd0ff545e983ab5a845c04fb8251204e71cf Mon Sep 17 00:00:00 2001 From: Kartik Bali <102038814+kbali1297@users.noreply.github.com> Date: Tue, 20 Sep 2022 13:42:53 +0200 Subject: [PATCH 012/170] Update flow.py Fixed unintended errors in flow.py. --- phi/torch/flow.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phi/torch/flow.py b/phi/torch/flow.py index b4b1c4aa5..272fbc388 100644 --- a/phi/torch/flow.py +++ b/phi/torch/flow.py @@ -15,6 +15,12 @@ from phi.flow import * from . import TORCH +from .nets import parameter_count, get_parameters, save_state, load_state, dense_net, u_net, update_weights, adam, conv_net, res_net, sgd, sgd as SGD, rmsprop, adagrad, conv_classifier, invertible_net, fno + +import torch +import torch.nn.functional as torchf +import torch.optim as optim + if not backend.context_backend(): backend.set_global_default_backend(TORCH) else: From fff48060ab7d64dfaff4707381655c3748631c80 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Thu, 22 Sep 2022 15:11:17 +0200 Subject: [PATCH 013/170] Removed layers default argument in conv_net and res_net and added default argument layers=[] in the CouplingLayer class. Added **kwargs as argument in u_net, conv_net and res_net to accommodate missing different argument in u_net function definition analogous to layers in conv_net and res_net function definitions. --- phi/jax/stax/nets.py | 58 +++++++++++++++++++++++--------------------- phi/tf/nets.py | 33 +++++++++++++------------ phi/torch/nets.py | 36 +++++++++++++-------------- 3 files changed, 66 insertions(+), 61 deletions(-) diff --git a/phi/jax/stax/nets.py b/phi/jax/stax/nets.py index 0b7d51322..6a9ae1099 100644 --- a/phi/jax/stax/nets.py +++ b/phi/jax/stax/nets.py @@ -496,7 +496,7 @@ def net_apply(params, inputs, **kwargs): def conv_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int, ...] = [], + layers: Tuple[int, ...] or List[int, ...], batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2) ->StaxNet: @@ -561,7 +561,7 @@ def net_apply(params, inputs): def res_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int, ...] = [], + layers: Tuple[int, ...] or List[int, ...], batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2) -> StaxNet: @@ -721,13 +721,14 @@ def net_apply(params, inputs, **kwargs): return net_init, net_apply + def conv_net_unit(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int, ...] = [], + layers: Tuple[int, ...] or List[int, ...], batch_norm: bool = False, activation: str or Callable = 'ReLU', - in_spatial: int or tuple = 2): - if isinstance(in_spatial,tuple): + in_spatial: int or tuple = 2, **kwargs): + if isinstance(in_spatial, tuple): d = in_spatial in_spatial = len(in_spatial) else: @@ -783,14 +784,15 @@ def net_apply(params, inputs): return net_init, net_apply + def u_net_unit(in_channels: int, - out_channels: int, - levels: int = 4, - filters: int or tuple or list = 16, - batch_norm: bool = True, - activation='ReLU', - in_spatial: tuple or int = 2, - use_res_blocks: bool = False): + out_channels: int, + levels: int = 4, + filters: int or tuple or list = 16, + batch_norm: bool = True, + activation='ReLU', + in_spatial: tuple or int = 2, + use_res_blocks: bool = False, **kwargs): if isinstance(filters, (tuple, list)): assert len(filters) == levels, f"List of filters has length {len(filters)} but u-net has {levels} levels." else: @@ -854,12 +856,13 @@ def net_apply(params, inputs, **kwargs): return net_init, net_apply + def res_net_unit(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int, ...] = [], + layers: Tuple[int, ...] or List[int, ...], batch_norm: bool = False, activation: str or Callable = 'ReLU', - in_spatial: int or tuple = 2): + in_spatial: int or tuple = 2, **kwargs): if isinstance(in_spatial, tuple): d = in_spatial in_spatial = len(in_spatial) @@ -903,17 +906,17 @@ def coupling_layer(in_channels: int, init_fn['t2'], apply_fn['t2'] = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) else: init_fn['s1'], apply_fn['s1'] = NET[net](in_channels=in_channels, out_channels=in_channels, - batch_norm=batch_norm, activation=activation, - in_spatial=in_spatial) + layers=[], batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) init_fn['t1'], apply_fn['t1'] = NET[net](in_channels=in_channels, out_channels=in_channels, - batch_norm=batch_norm, activation=activation, + layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial) init_fn['s2'], apply_fn['s2'] = NET[net](in_channels=in_channels, out_channels=in_channels, - batch_norm=batch_norm, activation=activation, - in_spatial=in_spatial) + layers=[], batch_norm=batch_norm, activation=activation, + in_spatial=in_spatial) init_fn['t2'], apply_fn['t2'] = NET[net](in_channels=in_channels, out_channels=in_channels, - batch_norm=batch_norm, activation=activation, + layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial) def net_init(rng, input_shape): @@ -965,20 +968,21 @@ def net_apply(params, inputs, invert=False): return net_init, net_apply + def invertible_net(in_channels: int, - num_blocks: int, - batch_norm: bool = False, - net: str = 'u_net', - activation: str or type='ReLU', - in_spatial: tuple or int=2): + num_blocks: int, + batch_norm: bool = False, + net: str = 'u_net', + activation: str or type = 'ReLU', + in_spatial: tuple or int = 2, **kwargs): if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) init_fn, apply_fn = {}, {} for i in range(num_blocks): - init_fn[f'CouplingLayer{i+1}'], apply_fn[f'CouplingLayer{i+1}'] = \ - coupling_layer(in_channels, activation, batch_norm, in_spatial, net, (i%2==0)) + init_fn[f'CouplingLayer{i + 1}'], apply_fn[f'CouplingLayer{i + 1}'] = \ + coupling_layer(in_channels, activation, batch_norm, in_spatial, net, (i % 2 == 0)) def net_init(rng, input_shape): params = {} diff --git a/phi/tf/nets.py b/phi/tf/nets.py index dbad0376e..e4a60f0d6 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -182,7 +182,7 @@ def u_net(in_channels: int, batch_norm: bool = True, activation: str or Callable = 'ReLU', in_spatial: tuple or int = 2, - use_res_blocks: bool = False) -> keras.Model: + use_res_blocks: bool = False, **kwargs) -> keras.Model: if isinstance(in_spatial, int): d = in_spatial in_spatial = (None,) * d @@ -242,12 +242,13 @@ def double_conv(x, d: int, out_channels: int, mid_channels: int, batch_norm: boo x = activation(x) return x + def conv_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int, ...] = [], + layers: Tuple[int, ...] or List[int, ...], batch_norm: bool = False, activation: str or Callable = 'ReLU', - in_spatial: int or tuple = 2) -> keras.Model: + in_spatial: int or tuple = 2, **kwargs) -> keras.Model: if isinstance(in_spatial, int): d = (None,) * in_spatial else: @@ -308,13 +309,13 @@ def ResNet_Block(in_channels: int, return keras.Model(inputs, x) + def res_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int, ...] = [], + layers: Tuple[int, ...] or List[int, ...], batch_norm: bool = False, activation: str or Callable = 'ReLU', - in_spatial: int or tuple = 2): - + in_spatial: int or tuple = 2, **kwargs): if isinstance(in_spatial, int): d = (None,) * in_spatial else: @@ -470,17 +471,17 @@ def __init__(self, in_channels, activation, batch_norm, in_spatial, net, reverse self.s2 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) self.t2 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) else: - self.s1 = NET[net](in_channels=in_channels, out_channels=in_channels, + self.s1 = NET[net](in_channels=in_channels, out_channels=in_channels, layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial) - self.t1 = NET[net](in_channels=in_channels, out_channels=in_channels, + self.t1 = NET[net](in_channels=in_channels, out_channels=in_channels, layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial) - self.s2 = NET[net](in_channels=in_channels, out_channels=in_channels, + self.s2 = NET[net](in_channels=in_channels, out_channels=in_channels, layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial) - self.t2 = NET[net](in_channels=in_channels, out_channels=in_channels, + self.t2 = NET[net](in_channels=in_channels, out_channels=in_channels, layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial) @@ -525,15 +526,15 @@ def call(self, x, backward=False): x = self.layer_dict[f'coupling_block{i}'](x) return x + def invertible_net(in_channels: int, - num_blocks: int, - batch_norm: bool = False, - net: str = 'u_net', - activation: str or type='ReLU', - in_spatial: tuple or int=2): + num_blocks: int, + batch_norm: bool = False, + net: str = 'u_net', + activation: str or type = 'ReLU', + in_spatial: tuple or int = 2, **kwargs): if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) return InvertibleNet(in_channels, num_blocks, activation, batch_norm, in_spatial, net) - diff --git a/phi/torch/nets.py b/phi/torch/nets.py index 0014d58fe..1b62816e7 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -4,17 +4,16 @@ For API documentation, see https://tum-pbs.github.io/PhiFlow/Network_API . """ -from typing import Callable, Dict, List, Tuple +from typing import Callable, List, Tuple import numpy import torch import torch.nn as nn -import torch.nn.functional as F from torch import optim -from .. import math from . import TORCH from ._torch_backend import register_module_call +from .. import math from ..math import channel @@ -189,7 +188,7 @@ def u_net(in_channels: int, batch_norm: bool = True, activation: str or type = 'ReLU', in_spatial: tuple or int = 2, - use_res_blocks: bool = False) -> nn.Module: + use_res_blocks: bool = False, **kwargs) -> nn.Module: if isinstance(filters, (tuple, list)): assert len(filters) == levels, f"List of filters has length {len(filters)} but u-net has {levels} levels." else: @@ -348,10 +347,10 @@ def forward(self, x): def conv_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int] = [], + layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or type = 'ReLU', - in_spatial: int or tuple = 2) -> nn.Module: + in_spatial: int or tuple = 2, **kwargs) -> nn.Module: if isinstance(in_spatial, int): d = in_spatial else: @@ -478,10 +477,10 @@ def forward(self, x): def res_net(in_channels: int, out_channels: int, - layers: Tuple[int, ...] or List[int] = [], + layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or type = 'ReLU', - in_spatial: int or tuple = 2) -> nn.Module: + in_spatial: int or tuple = 2, **kwargs) -> nn.Module: if (isinstance(in_spatial, int)): d = in_spatial else: @@ -573,16 +572,16 @@ def __init__(self, in_channels, activation, batch_norm, in_spatial, net, reverse self.t2 = Dense_ResNet_Block(in_channels, in_channels, batch_norm, activation) else: self.s1 = nn.Sequential(NET[net](in_channels=in_channels, out_channels=in_channels, - batch_norm=batch_norm, activation=activation, + layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial), torch.nn.Tanh()) self.t1 = NET[net](in_channels=in_channels, out_channels=in_channels, - batch_norm=batch_norm, activation=activation, + layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial) self.s2 = nn.Sequential(NET[net](in_channels=in_channels, out_channels=in_channels, - batch_norm=batch_norm, activation=activation, + layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial), torch.nn.Tanh()) self.t2 = NET[net](in_channels=in_channels, out_channels=in_channels, - batch_norm=batch_norm, activation=activation, + layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial) @@ -628,15 +627,16 @@ def forward(self, x, backward=False): def invertible_net(in_channels: int, - num_blocks: int, - batch_norm: bool = False, - net: str = 'u_net', - activation: str or type='ReLU', - in_spatial: tuple or int=2): + num_blocks: int, + batch_norm: bool = False, + net: str = 'u_net', + activation: str or type = 'ReLU', + in_spatial: tuple or int = 2, **kwargs): if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - return InvertibleNet(in_channels, num_blocks, activation, batch_norm, in_spatial, net).to(TORCH.get_default_device().ref) + return InvertibleNet(in_channels, num_blocks, activation, batch_norm, in_spatial, net).to( + TORCH.get_default_device().ref) def coupling_layer(in_channels: int, From 5e6aa827fc3f6b82f9397d67f16a191a05b93b7b Mon Sep 17 00:00:00 2001 From: Kartik Bali <102038814+kbali1297@users.noreply.github.com> Date: Fri, 23 Sep 2022 16:37:47 +0200 Subject: [PATCH 014/170] Update nets.py Changed resnet_block function definition from def resnet_block(x: tensor, in_channels: int, out_channels: int , batch_norm: bool, activation: str or Callable, in_spatial: int or tuple) to def resnet_block(in_channels: int, out_channels: int , batch_norm: bool, activation: str or Callable, in_spatial: int or tuple) --- phi/tf/nets.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phi/tf/nets.py b/phi/tf/nets.py index 41ac8dc55..8009f20ca 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -205,16 +205,16 @@ def u_net(in_channels: int, filters = (filters,) * levels # --- Construct the U-Net --- x = inputs = keras.Input(shape=in_spatial + (in_channels,)) - x = resnet_block(x, x.shape[-1], filters[0], batch_norm, activation, d) if use_res_blocks else double_conv(x, d, filters[0], filters[0], batch_norm, activation) + x = resnet_block(x.shape[-1], filters[0], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[0], filters[0], batch_norm, activation) xs = [x] for i in range(1, levels): x = MAX_POOL[d](2, padding="same")(x) - x = resnet_block(x, x.shape[-1], filters[i], batch_norm, activation, d) if use_res_blocks else double_conv(x, d, filters[i], filters[i], batch_norm, activation) + x = resnet_block(x.shape[-1], filters[i], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[i], filters[i], batch_norm, activation) xs.insert(0, x) for i in range(1, levels): x = UPSAMPLE[d](2)(x) x = kl.Concatenate()([x, xs[i]]) - x = resnet_block(x, x.shape[-1], filters[i - 1], batch_norm, activation, d) if use_res_blocks else double_conv(x, d, filters[i - 1], filters[i - 1], batch_norm, activation) + x = resnet_block(x.shape[-1], filters[i - 1], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[i - 1], filters[i - 1], batch_norm, activation) x = CONV[d](out_channels, 1)(x) return keras.Model(inputs, x) @@ -281,7 +281,7 @@ def conv_net(in_channels: int, return keras.Model(inputs, x) -def resnet_block(x, in_channels: int, +def resnet_block(in_channels: int, out_channels: int, batch_norm: bool = False, activation: str or Callable = 'ReLU', From bf5c9a71f3d3a95b3812763b9a6becf8d6cac780 Mon Sep 17 00:00:00 2001 From: Elias Djossou Date: Fri, 18 Mar 2022 13:53:30 +0100 Subject: [PATCH 015/170] Add support for higher order differentiation schemes for periodic boundary conditions [field] extend spatial_gradient, laplace, divergence in _field_math to arbitrary finite difference schemes [field] introduce dyadic interpolation for resampling grids with an offset of exactly half a cell [math] add tensor implementation for dyadic interpolation in _nd [math] add ANTISYMMETRIC and ANTIREFLECT extrapolations for later use in higher order boundaries [physics] extend advect, diffuse by universal finite_difference implementations [physics] Add Scheme to make_incompressible() --- phi/field/_field_math.py | 270 +++++++++++++++++++++++++++++++++----- phi/field/_grid.py | 11 ++ phi/math/__init__.py | 2 +- phi/math/_nd.py | 52 +++++++- phi/math/extrapolation.py | 23 ++++ phi/physics/advect.py | 49 ++++++- phi/physics/diffuse.py | 28 ++++ phi/physics/fluid.py | 28 ++-- 8 files changed, 414 insertions(+), 49 deletions(-) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index ac7c2ecac..a8bffa89a 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -1,15 +1,17 @@ +from functools import partial from numbers import Number from typing import Callable, List, Tuple from phi import geom from phi import math from phi.geom import Box, Geometry, Sphere, Cuboid -from phi.math import Tensor, spatial, instance, tensor, masked_fill, channel, Shape, batch, unstack, wrap, vec +from phi.math import Tensor, spatial, instance, tensor, masked_fill, channel, Shape, batch, unstack, wrap, vec, \ + rename_dims, solve_linear, jit_compile_linear from ._field import Field, SampledField, SampledFieldType, as_extrapolation from ._grid import CenteredGrid, Grid, StaggeredGrid, GridType from ._point_cloud import PointCloud from .numerical import Scheme -from ..math.extrapolation import Extrapolation +from ..math.extrapolation import Extrapolation, SYMMETRIC, REFLECT, ANTIREFLECT, ANTISYMMETRIC, combine_by_direction, map def bake_extrapolation(grid: GridType) -> GridType: @@ -38,16 +40,71 @@ def bake_extrapolation(grid: GridType) -> GridType: raise ValueError(f"Not a valid grid: {grid}") -def laplace(field: GridType, axes=spatial) -> GridType: - """ Finite-difference laplace operator for Grids. See `phi.math.laplace()`. """ - result = field._op1(lambda tensor: math.laplace(tensor, dx=field.dx, padding=field.extrapolation, dims=axes)) +def laplace(field: GridType, axes=spatial, scheme: Scheme = Scheme(2)) -> GridType: + """ + Spatial Laplace operator for scalar grid. + If a vector grid is passed, it is assumed to be centered and the laplace is computed component-wise. + + Args: + field: n-dimensional `CenteredGrid` + axes: The second derivative along these dimensions is summed over + scheme: finite difference `Scheme` used for differentiation + + Returns: + laplacian field as `CenteredGrid` + """ + + axes_names = field.shape.only(axes).names + extrapol_map = {} + if not scheme.is_implicit: + if scheme.order == 2: + values, needed_shifts = [1, -2, 1], (-1, 0, 1) + + elif scheme.order == 4: + values, needed_shifts = [-1/12, 4/3, -5/2, 4/3, -1/12], (-2, -1, 0, 1, 2) + + else: + extrapol_map_rhs = {} + if scheme.order == 6: + values, needed_shifts = [3/44, 12/11, -51/22, 12/11, 3/44], (-2, -1, 0, 1, 2) + extrapol_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) + + values_rhs, needed_shifts_rhs = [2/11, 1, 2/11], (-1, 0, 1) + extrapol_map_rhs['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) + + base_widths = (abs(min(needed_shifts)), max(needed_shifts)) + field.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) + + padded_components = [pad(field, {dim: base_widths}) for dim in axes_names] + + shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, axes_names)] + result_components = [sum([value * shift for value, shift in zip(values, shifted_component)]) / field.dx.vector[dim]**2 for shifted_component, dim in zip(shifted_components, axes_names)] + + + if scheme.is_implicit: + result_components = stack(result_components, channel('laplacian')) + result_components.with_values(result_components.values._cache()) + result_components = result_components.with_extrapolation(map(_ex_map_f(extrapol_map_rhs), field.extrapolation)) + scheme.solve.x0 = result_components + result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=scheme.solve, + f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, + "stack_dim": channel('laplacian')}) + result_components = unstack(result_components, 'laplacian') + extrapol_map = extrapol_map_rhs + + result_components = [component.with_bounds(field.bounds) for component in result_components] + result = sum(result_components) + result = result.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) + return result -def spatial_gradient(field: Field, - extrapolation: Extrapolation = None, +def spatial_gradient(field: CenteredGrid, + gradient_extrapolation: Extrapolation = None, type: type = CenteredGrid, - stack_dim: Shape = channel('vector')): + stack_dim: Shape = channel('vector'), + scheme: Scheme = Scheme(2)): + """ Finite difference spatial_gradient. @@ -58,27 +115,114 @@ def spatial_gradient(field: Field, Args: field: centered grid of any number of dimensions (scalar field, vector field, tensor field) + gradient_extrapolation: Extrapolation of the output type: either `CenteredGrid` or `StaggeredGrid` stack_dim: Dimension to be added. This dimension lists the spatial_gradient w.r.t. the spatial dimensions. The `field` must not have a dimension of the same name. + scheme: finite difference `Scheme` used for differentiation Returns: spatial_gradient field of type `type`. """ - assert isinstance(field, Grid) - if extrapolation is None: - extrapolation = field.extrapolation.spatial_gradient() + + if gradient_extrapolation == None: + gradient_extrapolation = field.extrapolation + + extrapol_map = {} + if not scheme.is_implicit: + if scheme.order == 2: + if type == CenteredGrid: + values, needed_shifts = [-1/2, 1/2], (-1, 1) + else: + values, needed_shifts = [-1, 1], (0, 1) + + elif scheme.order == 4: + if type == CenteredGrid: + values, needed_shifts = [1/12, -2/3, 2/3, -1/12], (-2, -1, 1, 2) + else: + values, needed_shifts = [1/24, -27/24, 27/24, -1/24], (-1, 0, 1, 2) + else: + extrapol_map_rhs = {} + if scheme.order == 6: + if type == CenteredGrid: + values, needed_shifts = [-1/36, -14/18, 14/18, 1/36], (-2, -1, 1, 2) + values_rhs, needed_shifts_rhs = [1/3, 1, 1/3], (-1, 0, 1) + + else: + values, needed_shifts = [-17/186, -63/62, 63/62, 17/186], (-1, 0, 1, 2) + extrapol_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) + + values_rhs, needed_shifts_rhs = [9/62, 1, 9/62], (-1, 0, 1) + extrapol_map_rhs['symmetric'] = combine_by_direction(ANTIREFLECT, ANTISYMMETRIC) + + + base_widths = (abs(min(needed_shifts)), max(needed_shifts)) + field.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) + + if scheme.is_implicit: + gradient_extrapolation = map(_ex_map_f(extrapol_map_rhs), gradient_extrapolation) + if type == CenteredGrid: - values = math.spatial_gradient(field.values, field.dx.vector.as_channel(name=stack_dim.name), difference='central', padding=field.extrapolation, stack_dim=stack_dim) - return CenteredGrid(values, bounds=field.bounds, extrapolation=extrapolation) - elif type == StaggeredGrid: - assert stack_dim.name == 'vector' - return stagger(field, lambda lower, upper: (upper - lower) / field.dx, extrapolation) - raise NotImplementedError(f"{type(field)} not supported. Only CenteredGrid and StaggeredGrid allowed.") + padded_components = [pad(field, {dim: base_widths}) for dim in field.shape.spatial.names] + else: + base_widths = (base_widths[0], base_widths[1]-1) + padded_components = pad_for_staggered_output(field, gradient_extrapolation, + field.shape.spatial.names, base_widths) + shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, field.shape.spatial.names)] + result_components = [sum([value * shift for value, shift in zip(values, shifted_component)]) / field.dx.vector[dim] for shifted_component, dim in zip(shifted_components, field.shape.spatial.names)] -def shift(grid: CenteredGrid, offsets: tuple, stack_dim: Shape = channel('shift')): + if type == CenteredGrid: + result = stack(result_components, stack_dim) + else: + result = StaggeredGrid(math.stack([component.values for component in result_components], channel('vector')), + bounds=field.bounds, extrapolation=gradient_extrapolation) + + result = result.with_extrapolation(gradient_extrapolation) + + if scheme.is_implicit: + scheme.solve.x0 = result + result = result + result = solve_linear(_lhs_for_implicit_scheme, result, solve=scheme.solve, + f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, + "stack_dim": stack_dim, "staggered_output": type != CenteredGrid}) + + return result.with_bounds(field.bounds) + +def _ex_map_f(ext_dict: dict): + def f(ext: Extrapolation): + return ext_dict[ext.__repr__()] if ext.__repr__() in ext_dict else ext + return f + + +@partial(jit_compile_linear, auxiliary_args="values_rhs, needed_shifts_rhs, stack_dim, staggered_output") +def _lhs_for_implicit_scheme(x, values_rhs, needed_shifts_rhs, stack_dim, staggered_output=False): + result = [] + for dim, component in zip(x.shape.only(math.spatial).names, unstack(x, stack_dim.name)): + shifted = shift(component, needed_shifts_rhs, stack_dim=None, dims=dim) + result.append(sum([value * shift for value, shift in zip(values_rhs, shifted)])) + + if staggered_output: + result = x.with_values(math.stack([component.values for component in result], channel('vector'))) + else: + result = stack(result, stack_dim) + + return result + + +def pad_for_staggered_output(field: CenteredGrid, output_extrapolation: Extrapolation, dims: tuple, base_widths: tuple): + padded_components = [] + for dim in dims: + border_valid = output_extrapolation.valid_outer_faces(dim) + padding_widths = (border_valid[0] + base_widths[0], border_valid[1] + base_widths[1]) + padded_components.append(pad(field, {dim: padding_widths})) + + return padded_components + + +def shift(grid: CenteredGrid, offsets: tuple, stack_dim: Shape = channel('shift'), dims=math.spatial, + pad: bool = True): """ Wraps :func:`math.shift` for CenteredGrid. @@ -87,8 +231,19 @@ def shift(grid: CenteredGrid, offsets: tuple, stack_dim: Shape = channel('shift' offsets: tuple: stack_dim: (Default value = 'shift') """ - data = math.shift(grid.values, offsets, padding=grid.extrapolation, stack_dim=stack_dim) - return [CenteredGrid(data[i], bounds=grid.bounds, extrapolation=grid.extrapolation) for i in range(len(offsets))] + + if pad: + padding = grid.extrapolation + new_bounds = grid.bounds + else: + padding = None + max_lower_shift = min(offsets) if min(offsets) < 0 else 0 + max_upper_shift = max(offsets) if max(offsets) > 0 else 0 + w_lower = math.wrap([max_lower_shift if dim in dims else 0 for dim in grid.shape.spatial.names]) + w_upper = math.wrap([max_upper_shift if dim in dims else 0 for dim in grid.shape.spatial.names]) + new_bounds = Box(grid.box.lower - w_lower * grid.dx, grid.box.upper - w_upper * grid.dx) + data = math.shift(grid.values, offsets, dims=dims, padding=padding, stack_dim=stack_dim) + return [type(grid)(data[i], bounds=new_bounds, extrapolation=grid.extrapolation) for i in range(len(offsets))] def stagger(field: CenteredGrid, @@ -141,7 +296,7 @@ def stagger(field: CenteredGrid, raise ValueError(type) -def divergence(field: Grid) -> CenteredGrid: +def divergence(field: Grid, scheme: Scheme = Scheme(2)) -> CenteredGrid: """ Computes the divergence of a grid using finite differences. @@ -152,26 +307,69 @@ def divergence(field: Grid) -> CenteredGrid: Args: field: vector field as `CenteredGrid` or `StaggeredGrid` + scheme: finite difference `Scheme` used for differentiation Returns: Divergence field as `CenteredGrid` """ + + extrapol_map = {} + if not scheme.is_implicit: + if scheme.order == 2: + if isinstance(field, CenteredGrid): + values, needed_shifts = [-1 / 2, 1 / 2], (-1, 1) + else: + values, needed_shifts = [-1, 1], (0, 1) + + elif scheme.order == 4: + if isinstance(field, CenteredGrid): + values, needed_shifts = [1 / 12, -2 / 3, 2 / 3, -1 / 12], (-2, -1, 1, 2) + else: + values, needed_shifts = [1 / 24, -27 / 24, 27 / 24, -1 / 24], (-1, 0, 1, 2) + else: + extrapol_map_rhs = {} + if scheme.order == 6: + extrapol_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) + extrapol_map_rhs['symmetric'] = combine_by_direction(ANTIREFLECT, ANTISYMMETRIC) + + if isinstance(field, CenteredGrid): + values, needed_shifts = [-1 / 36, -14 / 18, 14 / 18, 1 / 36], (-2, -1, 1, 2) + values_rhs, needed_shifts_rhs = [1 / 3, 1, 1 / 3], (-1, 0, 1) + + else: + values, needed_shifts = [-17 / 186, -63 / 62, 63 / 62, 17 / 186], (-1, 0, 1, 2) + values_rhs, needed_shifts_rhs = [9 / 62, 1, 9 / 62], (-1, 0, 1) + + + base_widths = (abs(min(needed_shifts)), max(needed_shifts)) + field.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) + + if isinstance(field, StaggeredGrid): - field = bake_extrapolation(field) - components = [] - for dim in field.shape.spatial.names: - div_dim = math.spatial_gradient(field.values.vector[dim], field.dx, 'forward', None, dims=dim, stack_dim=None) - components.append(div_dim) - data = math.sum(components, dim='0') - return CenteredGrid(data, bounds=field.bounds, extrapolation=field.extrapolation.spatial_gradient()) + base_widths = (base_widths[0]+1, base_widths[1]) + padded_components = [] + for dim, component in zip(field.shape.spatial.names, unstack(field, 'vector')): + border_valid = field.extrapolation.valid_outer_faces(dim) + padding_widths = (base_widths[0] - border_valid[0], base_widths[1] - border_valid[1]) + padded_components.append(pad(component, {dim: padding_widths})) elif isinstance(field, CenteredGrid): - left, right = shift(field, (-1, 1), stack_dim=batch('div_')) - grad = (right - left) / (field.dx * 2) - components = [grad.vector[i].div_[i] for i in range(grad.div_.size)] - result = sum(components) - return result - else: - raise NotImplementedError(f"{type(field)} not supported. Only StaggeredGrid allowed.") + padded_components = [pad(component, {dim: base_widths}) for dim, component in zip(field.shape.spatial.names, unstack(field, 'vector'))] + + shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, field.shape.spatial.names)] + result_components = [sum([value * shift for value, shift in zip(values, shifted_component)]) / field.dx.vector[dim] for shifted_component, dim in zip(shifted_components, field.shape.spatial.names)] + + if scheme.is_implicit: + result_components = stack(result_components, channel('vector')) + result_components.with_values(result_components.values._cache()) + scheme.solve.x0 = field + result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=scheme.solve, + f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, + "stack_dim": channel('vector')}) + result_components = unstack(result_components, 'vector') + + result_components = [component.with_bounds(field.bounds) for component in result_components] + result = sum(result_components) + return result def curl(field: Grid, type: type = CenteredGrid): @@ -307,7 +505,7 @@ def pad(grid: GridType, widths: int or tuple or list or dict) -> GridType: widths = {axis: (width if isinstance(width, (tuple, list)) else (width, width)) for axis, width in zip(grid.shape.spatial.names, widths)} else: assert isinstance(widths, dict) - widths_list = [widths[axis] for axis in grid.shape.spatial.names] + widths_list = [widths[axis] if axis in widths.keys() else (0, 0) for axis in grid.shape.spatial.names] if isinstance(grid, Grid): data = math.pad(grid.values, widths, grid.extrapolation, bounds=grid.bounds) w_lower = math.wrap([w[0] for w in widths_list]) diff --git a/phi/field/_grid.py b/phi/field/_grid.py index 3be2cdb8d..16c14188a 100644 --- a/phi/field/_grid.py +++ b/phi/field/_grid.py @@ -217,6 +217,11 @@ def _sample(self, geometry: Geometry, scheme: Scheme) -> Tensor: if self.elements == geometry: return self.values elif math.close(self.dx, geometry.size): + if all([math.close(offset, geometry.half_size) or math.close(offset, 0) + for offset in math.abs(self.bounds.lower - geometry.bounds.lower)]): + dyadic_interpolated = self._dyadic_interplate(geometry.resolution, geometry.bounds, scheme) + if dyadic_interpolated is not NotImplemented: + return dyadic_interpolated fast_resampled = self._shift_resample(geometry.resolution, geometry.bounds) if fast_resampled is not NotImplemented: return fast_resampled @@ -233,6 +238,12 @@ def _sample(self, geometry: Geometry, scheme: Scheme) -> Tensor: return math.where(inside, resampled_values, ext_values) return resampled_values + def _dyadic_interplate(self, resolution: Shape, bounds: Box, scheme: Scheme): + from phi.math._nd import _dyadic_interpolate + offsets = bounds.lower - self.bounds.lower + interpolation_dirs = [0 if math.close(offset, 0) else int(math.sign(offset)) for offset in offsets] + return _dyadic_interpolate(self.values, interpolation_dirs, self.extrapolation, scheme) + def _shift_resample(self, resolution: Shape, bounds: Box, threshold=1e-5, max_padding=20): assert math.all_available(bounds.lower, bounds.upper), "Shift resampling requires 'bounds' to be available." lower = math.to_int32(math.ceil(math.maximum(0, self.box.lower - bounds.lower) / self.dx - threshold)) diff --git a/phi/math/__init__.py b/phi/math/__init__.py index 831a646cf..ea19cd157 100644 --- a/phi/math/__init__.py +++ b/phi/math/__init__.py @@ -60,7 +60,7 @@ spatial_gradient, laplace, fourier_laplace, fourier_poisson, abs_square, downsample2x, upsample2x, sample_subgrid, - masked_fill, finite_fill, + masked_fill, finite_fill ) from ._functional import ( LinearFunction, jit_compile_linear, jit_compile, diff --git a/phi/math/_nd.py b/phi/math/_nd.py index 98dbf8490..b79fd9d0d 100644 --- a/phi/math/_nd.py +++ b/phi/math/_nd.py @@ -1,10 +1,12 @@ -from typing import Tuple, Optional +from functools import partial +from typing import Tuple, Optional, List import numpy as np from . import _ops as math from . import extrapolation as extrapolation from ._magic_ops import stack, rename_dims, concat, variable_values +from ._functional import solve_linear, jit_compile_linear from ._shape import Shape, channel, batch, spatial, DimFilter, parse_dim_order from ._tensors import Tensor, wrap from .magic import PhiTreeNode @@ -648,6 +650,54 @@ def sample_subgrid(grid: Tensor, start: Tensor, size: Shape) -> Tensor: return grid +def _dyadic_interpolate(grid: Tensor, interpolation_dirs: List, padding: Extrapolation, scheme): + """ + Samples a sub-grid from `grid` with an offset of half a grid cell in directions defined by `interpolation_dirs`. + + Args: + grid: `Tensor` to be resampled. + interpolation_dirs: List which defines for every spatial dimension of `grid` if interpolation should be performed, + in positive direction `1` / negative direction `-1` / no interpolation`0` + len(interpolation_dirs) == len(grid.shape.spatial.names) is assumed + Example: With `grid.shape.spatial.names=['x', 'y']` and `interpolation_dirs: [1, -1]` + grid will be interpolated half a grid cell in positive x direction and half a grid cell in negative y direction + padding: Extrapolation used for the needed out of Domain values + scheme: finite difference `Scheme` used for interpolation + + Returns: + Sub-grid as `Tensor` + """ + if scheme.is_implicit: + if scheme.order == 6: + values, needed_shifts = [1 / 20, 3 / 4, 3 / 4, 1 / 20], (-1, 0, 1, 2) + values_rhs, needed_shifts_rhs = [3 / 10, 1, 3 / 10], (-1, 0, 1) + else: + return NotImplemented + else: + return NotImplemented + + result = grid + for dim, dir in zip(grid.shape.spatial.names, interpolation_dirs): + if dir == 0: continue + is_neg_dir = dir == -1 + current_widths = [abs(min(needed_shifts)) + is_neg_dir, max(needed_shifts) - is_neg_dir] + padded = math.pad(result, {dim: tuple(current_widths)}, padding) + shifted = shift(padded, needed_shifts, [dim], padding=None, stack_dim=None) + result = sum([value * shift for value, shift in zip(values, shifted)]) + + if scheme.is_implicit: + scheme.solve.x0 = result + result = solve_linear(dyadic_interpolate_lhs, result, solve=scheme.solve, + f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, + "dim": dim, "padding": padding}) + return result + +@partial(jit_compile_linear, auxiliary_args="values_rhs, needed_shifts_rhs") +def dyadic_interpolate_lhs(x, values_rhs, needed_shifts_rhs, dim, padding): + shifted = shift(x, needed_shifts_rhs, stack_dim=None, dims=[dim], padding=padding) + return sum([value * shift for value, shift in zip(values_rhs, shifted)]) + + # Poisson Brackets diff --git a/phi/math/extrapolation.py b/phi/math/extrapolation.py index a5ad473f2..10d965d03 100644 --- a/phi/math/extrapolation.py +++ b/phi/math/extrapolation.py @@ -607,6 +607,15 @@ def pad_values(self, value: Tensor, width: int, dim: str, upper_edge: bool, **kw else: return value[{dim: slice(0, width)}].flip(dim) +class _AntiSymmetricExtrapolation(_SymmetricExtrapolation): + """Like _SymmetricExtrapolation but symmetric counterparts are negated for padding""" + + def __repr__(self): + return 'antisymmetric' + + def pad_values(self, *args, **kwargs) -> Tensor: + return -super().pad_values(*args, **kwargs) + class _ReflectExtrapolation(_CopyExtrapolation): """Mirror of inner elements. The boundary value is not duplicated.""" @@ -632,6 +641,13 @@ def transform_coordinates(self, coordinates: Tensor, shape: Shape, **kwargs) -> return (shape - 1) - math.abs_((shape - 1) - coordinates) +class _AntiReflectExtrapolation(_ReflectExtrapolation): + """Like _ReflectExtrapolation but symmetric counterparts are negated for padding""" + + def pad_values(self, *args, **kwargs) -> Tensor: + return -super().pad_values(*args, **kwargs) + + class _NoExtrapolation(Extrapolation): # singleton def to_dict(self) -> dict: return {'type': 'none'} @@ -763,8 +779,11 @@ def __rtruediv__(self, other): """ Extends a grid with its edge values (Neumann boundary condition). The value of a point lying outside the grid is determined by the closest grid value(s). """ SYMMETRIC = _SymmetricExtrapolation(3) """ Extends a grid by tiling it. Every other copy of the grid is flipped. Edge values occur twice per seam. """ +ANTISYMMETRIC = _AntiSymmetricExtrapolation(3) REFLECT = _ReflectExtrapolation(4) """ Like SYMMETRIC but the edge values are not copied and only occur once per seam. """ +ANTIREFLECT = _AntiReflectExtrapolation(4) + NONE = _NoExtrapolation(-1) """ Raises AssertionError when used to determine outside values. Padding operations will have no effect with this extrapolation. """ @@ -1067,8 +1086,12 @@ def from_dict(dictionary: dict) -> Extrapolation: return BOUNDARY elif etype == 'symmetric': return SYMMETRIC + elif etype == 'antisymmetric': + return ANTISYMMETRIC elif etype == 'reflect': return REFLECT + elif etype == 'antireflect': + return ANTISYMMETRIC elif etype == 'mixed': dims: Dict[str, tuple] = dictionary['dims'] extrapolations = {dim: (from_dict(lo_up[0]), from_dict(lo_up[1])) for dim, lo_up in dims.items()} diff --git a/phi/physics/advect.py b/phi/physics/advect.py index d1c0ec7d4..ccee5b944 100644 --- a/phi/physics/advect.py +++ b/phi/physics/advect.py @@ -8,9 +8,11 @@ * runge_kutta_4 (particle) """ from phi import math -from phi.field import SampledField, Field, PointCloud, Grid, sample, reduce_sample +from phi.field import SampledField, Field, PointCloud, Grid, sample, reduce_sample, \ + spatial_gradient, unstack, stack, CenteredGrid, StaggeredGrid from phi.field._field import FieldType from phi.field._field_math import GridType +from phi.field.numerical import Scheme from phi.geom import Geometry @@ -47,7 +49,9 @@ def finite_rk4(elements: Geometry, velocity: Grid, dt: float, v0: math.Tensor = def advect(field: SampledField, velocity: Field, dt: float or math.Tensor, - integrator=euler) -> FieldType: + integrator=euler, + scheme: Scheme = None + ) -> FieldType: """ Advect `field` along the `velocity` vectors using the specified integrator. @@ -65,6 +69,9 @@ def advect(field: SampledField, Returns: Advected field of same type as `field` """ + + if scheme is not None and isinstance(field, Grid): + return finite_difference(field, velocity, dt=dt, scheme=scheme) if isinstance(field, PointCloud): return points(field, velocity, dt=dt, integrator=integrator) elif isinstance(field, Grid): @@ -72,6 +79,44 @@ def advect(field: SampledField, raise NotImplementedError(field) +def finite_difference(grid: Grid, + velocity: Field, + dt: float or math.Tensor, + scheme: Scheme = Scheme(2)) -> Field: + + """ + Finite difference advection using the differentiation Scheme indicated by `scheme` and a simple Euler step + + Args: + grid: Grid to be advected + velocity: `Grid` that can be sampled in the elements of `grid`. + dt: Time increment + scheme: finite difference `Scheme` used for differentiation + + Returns: + Advected grid of same type as `grid` + """ + + + if isinstance(grid, StaggeredGrid): + field_components = unstack(grid, 'vector') + grad_list = [spatial_gradient(field_component, stack_dim=math.channel('gradient'), scheme=scheme) for + field_component in + field_components] + grad_grid = grid.with_values(math.stack([component.values for component in grad_list], math.channel('vector'))) + velocity._scheme = True + ammounts = [grad * vel.at(grad, scheme=scheme) for grad, vel in + zip(unstack(grad_grid, dim='gradient'), unstack(velocity, dim='vector'))] + ammount = sum(ammounts) + else: + grad = spatial_gradient(grid, stack_dim=math.channel('gradient'), scheme=scheme) + velocity = stack(unstack(velocity, dim='vector'), dim=math.channel('gradient')) + ammounts = velocity * grad + ammount = sum(unstack(ammounts, dim='gradient')) + + return grid - dt * ammount + + def points(field: PointCloud, velocity: Field, dt: float, integrator=euler): """ Advects the sample points of a point cloud using a simple Euler step. diff --git a/phi/physics/diffuse.py b/phi/physics/diffuse.py index da61c92a2..c147b3638 100644 --- a/phi/physics/diffuse.py +++ b/phi/physics/diffuse.py @@ -5,6 +5,7 @@ from phi.field import Grid, Field, laplace, solve_linear, jit_compile_linear from phi.field._field import FieldType from phi.field._grid import GridType +from phi.field.numerical import Scheme from phi.math import copy_with @@ -63,6 +64,33 @@ def sharpen(x): return solve_linear(sharpen, y=field, solve=solve) +def finite_difference(grid: Grid, + diffusivity: float or math.Tensor or Field, + dt: float or math.Tensor, + scheme: Scheme = Scheme(2)) -> FieldType: + + """ + Diffusion by using a finite difference scheme. + + + Args: + grid: CenteredGrid or StaggeredGrid + diffusivity: Diffusion per time. `diffusion_amount = diffusivity * dt` + dt: Time interval. `diffusion_amount = diffusivity * dt` + scheme: finite difference `Scheme` used for differentiation + + Returns: + Diffused grid of same type as `grid`. + """ + + amount = diffusivity * dt + if isinstance(amount, Field): + amount = amount.at(grid) + + grid += amount * laplace(grid, scheme=scheme).with_extrapolation(grid.extrapolation) + return grid + + def fourier(field: GridType, diffusivity: float or math.Tensor, dt: float or math.Tensor) -> FieldType: diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index 7fd762b63..a6c7f5fc3 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -11,6 +11,7 @@ from phi.geom import union, Geometry from ..field._embed import FieldEmbedding from ..field._grid import GridType +from ..field.numerical import Scheme from ..math import extrapolation, NUMPY, batch, shape, non_channel, expand from ..math._magic_ops import copy_with from ..math.extrapolation import combine_sides, Extrapolation @@ -53,7 +54,8 @@ def copied_with(self, **kwargs): def make_incompressible(velocity: GridType, obstacles: tuple or list = (), solve=math.Solve('auto', 1e-5, 1e-5, gradient_solve=math.Solve('auto', 1e-5, 1e-5)), - active: CenteredGrid = None) -> Tuple[GridType, CenteredGrid]: + active: CenteredGrid = None, + scheme: Scheme = Scheme(2)) -> Tuple[GridType, CenteredGrid]: """ Projects the given velocity field by solving for the pressure and subtracting its spatial_gradient. @@ -66,12 +68,15 @@ def make_incompressible(velocity: GridType, active: (Optional) Mask for which cells the pressure should be solved. If given, the velocity may take `NaN` values where it does not contribute to the pressure. Also, the total divergence will never be subtracted if active is given, even if all values are 1. + scheme: finite difference `Scheme` used for differentiation + Returns: velocity: divergence-free velocity of type `type(velocity)` pressure: solved pressure field, `CenteredGrid` """ assert isinstance(obstacles, (tuple, list)), f"obstacles must be a tuple or list but got {type(obstacles)}" + assert (scheme.order == 2 and not scheme.is_implicit) or obstacles == (), f"obstacles are not supported with higher order schemes" obstacles = [Obstacle(o) if isinstance(o, Geometry) else o for o in obstacles] for obstacle in obstacles: assert obstacle.geometry.vector.item_names == velocity.vector.item_names, f"Obstacles must live in the same physical space as the velocity field {velocity.vector.item_names} but got {type(obstacle.geometry).__name__} obstacle with order {obstacle.geometry.vector.item_names}" @@ -88,7 +93,7 @@ def make_incompressible(velocity: GridType, active *= accessible # no pressure inside obstacles # --- Linear solve --- velocity = apply_boundary_conditions(velocity, obstacles) - div = divergence(velocity) * active + div = divergence(velocity, scheme=scheme) * active if not all_active: # NaN in velocity allowed div = field.where(field.is_finite(div), div, 0) if not input_velocity.extrapolation.is_flexible and all_active: @@ -99,15 +104,15 @@ def make_incompressible(velocity: GridType, solve = copy_with(solve, x0=CenteredGrid(0, pressure_extrapolation, div.bounds, div.resolution)) if batch(math.merge_shapes(*obstacles)).without(batch(solve.x0)): # The initial pressure guess must contain all batch dimensions solve = copy_with(solve, x0=expand(solve.x0, batch(math.merge_shapes(*obstacles)))) - pressure = math.solve_linear(masked_laplace, f_args=[hard_bcs, active], y=div, solve=solve) + pressure = math.solve_linear(masked_laplace, f_args=[hard_bcs, active], f_kwargs={"scheme": scheme}, y=div, solve=solve) # --- Subtract grad p --- - grad_pressure = field.spatial_gradient(pressure, input_velocity.extrapolation, type=type(velocity)) * hard_bcs + grad_pressure = field.spatial_gradient(pressure, input_velocity.extrapolation, type=type(velocity), scheme=scheme) * hard_bcs velocity = velocity - grad_pressure return velocity, pressure @math.jit_compile_linear # jit compilation is required for boundary conditions that add a constant offset solving Ax + b = y -def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid) -> CenteredGrid: +def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid, scheme: Scheme) -> CenteredGrid: """ Computes the laplace of `pressure` in the presence of obstacles. @@ -123,10 +128,15 @@ def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid) Returns: `CenteredGrid` """ - grad = spatial_gradient(pressure, hard_bcs.extrapolation, type=type(hard_bcs)) - valid_grad = grad * hard_bcs - div = divergence(valid_grad) - laplace = where(active, div, pressure) + + if scheme.order == 2 and not scheme.is_implicit: + grad = spatial_gradient(pressure, hard_bcs.extrapolation, type=type(hard_bcs)) + valid_grad = grad * hard_bcs + div = divergence(valid_grad) + laplace = where(active, div, pressure) + else: + laplace = field.laplace(pressure, scheme=scheme) + return laplace From a917b7e387afd42f8ba4fbef2c567f9d61db04f8 Mon Sep 17 00:00:00 2001 From: Elias Djossou Date: Wed, 28 Sep 2022 11:54:57 +0200 Subject: [PATCH 016/170] Improve documentation concerning Higher-order schemes --- phi/field/_field.py | 5 +++-- phi/field/_field_math.py | 3 +++ phi/math/extrapolation.py | 5 +++++ phi/physics/advect.py | 13 ++++++------- phi/physics/diffuse.py | 4 +++- phi/physics/fluid.py | 5 ++++- 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/phi/field/_field.py b/phi/field/_field.py index f31777dad..09ea51109 100644 --- a/phi/field/_field.py +++ b/phi/field/_field.py @@ -78,7 +78,7 @@ def at(self, representation: 'SampledField', keep_extrapolation=False, scheme: S keep_extrapolation: Only available if `self` is a `SampledField`. If True, the resampled field will inherit the extrapolation from `self` instead of `representation`. This can result in non-compatible value tensors for staggered grids where the tensor size depends on the extrapolation type. - scheme: Numerical scheme for resampling. + scheme: Numerical scheme for resampling. See `reduce_sample` Returns: Field object of same type as `representation` @@ -358,7 +358,8 @@ def reduce_sample(field: Field, geometry: Geometry, dim=channel('vector'), schem field: Source `Field` to sample. geometry: Single or batched `phi.geom.Geometry`. dim: Dimension of result, resulting from reduction of channel dimensions. - scheme: Numerical scheme. + scheme: Numerical scheme. By default linear interpolation is used + supported: implicit 6th oder (only for sampling at mid-points, sampling at other locations and unsupported schemes result in automatic fallback to linear interpolation) Returns: Sampled values as a `phi.math.Tensor` diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index a8bffa89a..ba5a42fa1 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -49,6 +49,7 @@ def laplace(field: GridType, axes=spatial, scheme: Scheme = Scheme(2)) -> GridTy field: n-dimensional `CenteredGrid` axes: The second derivative along these dimensions is summed over scheme: finite difference `Scheme` used for differentiation + supported: explicit 2/4th order - implicit 6th order Returns: laplacian field as `CenteredGrid` @@ -120,6 +121,7 @@ def spatial_gradient(field: CenteredGrid, stack_dim: Dimension to be added. This dimension lists the spatial_gradient w.r.t. the spatial dimensions. The `field` must not have a dimension of the same name. scheme: finite difference `Scheme` used for differentiation + supported: explicit 2/4th order - implicit 6th order Returns: spatial_gradient field of type `type`. @@ -308,6 +310,7 @@ def divergence(field: Grid, scheme: Scheme = Scheme(2)) -> CenteredGrid: Args: field: vector field as `CenteredGrid` or `StaggeredGrid` scheme: finite difference `Scheme` used for differentiation + supported: explicit 2/4th order - implicit 6th order Returns: Divergence field as `CenteredGrid` diff --git a/phi/math/extrapolation.py b/phi/math/extrapolation.py index 10d965d03..553cd86e5 100644 --- a/phi/math/extrapolation.py +++ b/phi/math/extrapolation.py @@ -644,6 +644,9 @@ def transform_coordinates(self, coordinates: Tensor, shape: Shape, **kwargs) -> class _AntiReflectExtrapolation(_ReflectExtrapolation): """Like _ReflectExtrapolation but symmetric counterparts are negated for padding""" + def __repr__(self): + return 'antireflect' + def pad_values(self, *args, **kwargs) -> Tensor: return -super().pad_values(*args, **kwargs) @@ -780,9 +783,11 @@ def __rtruediv__(self, other): SYMMETRIC = _SymmetricExtrapolation(3) """ Extends a grid by tiling it. Every other copy of the grid is flipped. Edge values occur twice per seam. """ ANTISYMMETRIC = _AntiSymmetricExtrapolation(3) +""" Like REFLECT but extends a grid with the negative value of the corresponding counterpart instead. """ REFLECT = _ReflectExtrapolation(4) """ Like SYMMETRIC but the edge values are not copied and only occur once per seam. """ ANTIREFLECT = _AntiReflectExtrapolation(4) +""" Like REFLECT but extends a grid with the negative value of the corresponding counterpart instead. """ NONE = _NoExtrapolation(-1) """ Raises AssertionError when used to determine outside values. Padding operations will have no effect with this extrapolation. """ diff --git a/phi/physics/advect.py b/phi/physics/advect.py index ccee5b944..0c5d395d7 100644 --- a/phi/physics/advect.py +++ b/phi/physics/advect.py @@ -8,8 +8,7 @@ * runge_kutta_4 (particle) """ from phi import math -from phi.field import SampledField, Field, PointCloud, Grid, sample, reduce_sample, \ - spatial_gradient, unstack, stack, CenteredGrid, StaggeredGrid +from phi.field import SampledField, Field, PointCloud, Grid, sample, reduce_sample, spatial_gradient, unstack, stack, CenteredGrid, StaggeredGrid from phi.field._field import FieldType from phi.field._field_math import GridType from phi.field.numerical import Scheme @@ -50,8 +49,7 @@ def advect(field: SampledField, velocity: Field, dt: float or math.Tensor, integrator=euler, - scheme: Scheme = None - ) -> FieldType: + scheme: Scheme = None) -> FieldType: """ Advect `field` along the `velocity` vectors using the specified integrator. @@ -65,6 +63,8 @@ def advect(field: SampledField, velocity: Any `phi.field.Field` that can be sampled in the elements of `field`. dt: Time increment integrator: ODE integrator for solving the movement. + scheme: differentiation 'Scheme' if provided 'finite_difference' is used + if 'None' is given other functions are used which is the case by default Returns: Advected field of same type as `field` @@ -92,17 +92,16 @@ def finite_difference(grid: Grid, velocity: `Grid` that can be sampled in the elements of `grid`. dt: Time increment scheme: finite difference `Scheme` used for differentiation + supported: explicit 2/4th order - implicit 6th order Returns: Advected grid of same type as `grid` """ - if isinstance(grid, StaggeredGrid): field_components = unstack(grid, 'vector') grad_list = [spatial_gradient(field_component, stack_dim=math.channel('gradient'), scheme=scheme) for - field_component in - field_components] + field_component in field_components] grad_grid = grid.with_values(math.stack([component.values for component in grad_list], math.channel('vector'))) velocity._scheme = True ammounts = [grad * vel.at(grad, scheme=scheme) for grad, vel in diff --git a/phi/physics/diffuse.py b/phi/physics/diffuse.py index c147b3638..e6c654743 100644 --- a/phi/physics/diffuse.py +++ b/phi/physics/diffuse.py @@ -71,13 +71,15 @@ def finite_difference(grid: Grid, """ Diffusion by using a finite difference scheme. - + In contrast to `explicit` and `implicit` accuracy can be increased by using stencils of higher-order rather than calculating substeps. + This is controlled by the `scheme` passed. Args: grid: CenteredGrid or StaggeredGrid diffusivity: Diffusion per time. `diffusion_amount = diffusivity * dt` dt: Time interval. `diffusion_amount = diffusivity * dt` scheme: finite difference `Scheme` used for differentiation + supported: explicit 2/4th order - implicit 6th order Returns: Diffused grid of same type as `grid`. diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index a6c7f5fc3..071644093 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -69,7 +69,9 @@ def make_incompressible(velocity: GridType, If given, the velocity may take `NaN` values where it does not contribute to the pressure. Also, the total divergence will never be subtracted if active is given, even if all values are 1. scheme: finite difference `Scheme` used for differentiation - + For Higher-order schemes the laplace operation is not conducted with a stencil exactly corresponding to the one used in divergence calculations but a smaller one instead, + while this disrupts the formal correctness of the method it only induces insignificant errors and yields considerable performance gains + supported: explicit 2/4th order - implicit 6th order (obstacles are only supported with explicit 2nd order) Returns: velocity: divergence-free velocity of type `type(velocity)` @@ -124,6 +126,7 @@ def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid, active: Mask indicating for which cells the pressure value is valid. Linear solves will only determine the pressure for these cells. This is generally zero inside obstacles and in non-simulated regions. + scheme: finite difference `Scheme` used for laplace calculation Returns: `CenteredGrid` From 51f4f615ed46b344b94ecdaab3e393f0a6512a87 Mon Sep 17 00:00:00 2001 From: Elias Djossou Date: Thu, 29 Sep 2022 13:42:59 +0200 Subject: [PATCH 017/170] [math] Implement _pad_linear_tracer for Symmetric, Antisymmetric, Reflect, Antireflect Extrapolations --- phi/math/extrapolation.py | 173 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/phi/math/extrapolation.py b/phi/math/extrapolation.py index 553cd86e5..d0dbc190d 100644 --- a/phi/math/extrapolation.py +++ b/phi/math/extrapolation.py @@ -607,6 +607,63 @@ def pad_values(self, value: Tensor, width: int, dim: str, upper_edge: bool, **kw else: return value[{dim: slice(0, width)}].flip(dim) + def _pad_linear_tracer(self, value: 'ShiftLinTracer', widths: dict) -> 'ShiftLinTracer': + """ + *Warning*: + This implementation discards corners, i.e. values that lie outside the original tensor in more than one dimension. + These are typically sliced off in differential operators. Corners are instead assigned the value 0. + To take corners into account, call pad() for each axis individually. This is inefficient with ShiftLinTracer. + + Args: + value: ShiftLinTracer: + widths: dict: + + Returns: + + """ + lower = {dim: -lo for dim, (lo, _) in widths.items()} + result = value.shift(lower, new_shape=value.shape.after_pad(widths), val_fun=lambda v: ZERO.pad(v, widths), bias_fun=lambda b: ZERO.pad(b, widths)) # inner values ~half the computation time + for bound_dim, (bound_lo, bound_hi) in widths.items(): + for i in range(bound_lo): # i=0 means outer + # this sets corners to 0 + lower = {dim: bound_lo-1-2*i if dim == bound_dim else -lo for dim, (lo, _) in widths.items()} + mask = self._lower_mask(value.shape.only(result.dependent_dims), widths, bound_dim, bound_lo, bound_hi, i) + boundary = value.shift(lower, new_shape=result.shape, val_fun=lambda v: self.pad(v, widths) * mask, bias_fun=lambda b: ZERO.pad(b, widths)) + result += boundary + for i in range(bound_hi): + lower = {dim: -(bound_hi-1-2*i) - bound_lo - bound_hi if dim == bound_dim else -lo for dim, (lo, hi) in widths.items()} + mask = self._upper_mask(value.shape.only(result.dependent_dims), widths, bound_dim, bound_lo, bound_hi, i) + boundary = value.shift(lower, new_shape=result.shape, val_fun=lambda v: self.pad(v, widths) * mask, bias_fun=lambda b: ZERO.pad(b, widths)) # ~ half the computation time + result += boundary # this does basically nothing if value is the identity + return result + + def _lower_mask(self, shape, widths, bound_dim, bound_lo, bound_hi, i): + # key = (shape, tuple(widths.keys()), tuple(widths.values()), bound_dim, bound_lo, bound_hi, i) + # if key in _BoundaryExtrapolation._CACHED_LOWER_MASKS: + # result = math.tensor(_BoundaryExtrapolation._CACHED_LOWER_MASKS[key]) + # _BoundaryExtrapolation._CACHED_LOWER_MASKS[key] = result + # return result + # else: + mask = ZERO.pad(math.zeros(shape), {bound_dim: (bound_lo - i - 1, 0)}) + mask = ONE.pad(mask, {bound_dim: (1, 0)}) + mask = ZERO.pad(mask, {dim: (i, bound_hi) if dim == bound_dim else (lo, hi) for dim, (lo, hi) in widths.items()}) + # _BoundaryExtrapolation._CACHED_LOWER_MASKS[key] = mask + return mask + + def _upper_mask(self, shape, widths, bound_dim, bound_lo, bound_hi, i): + # key = (shape, tuple(widths.keys()), tuple(widths.values()), bound_dim, bound_lo, bound_hi, i) + # if key in _BoundaryExtrapolation._CACHED_UPPER_MASKS: + # result = math.tensor(_BoundaryExtrapolation._CACHED_UPPER_MASKS[key]) + # _BoundaryExtrapolation._CACHED_UPPER_MASKS[key] = result + # return result + # else: + mask = ZERO.pad(math.zeros(shape), {bound_dim: (0, bound_hi - i - 1)}) + mask = ONE.pad(mask, {bound_dim: (0, 1)}) + mask = ZERO.pad(mask, {dim: (bound_lo, i) if dim == bound_dim else (lo, hi) for dim, (lo, hi) in widths.items()}) + # _BoundaryExtrapolation._CACHED_UPPER_MASKS[key] = mask + return mask + + class _AntiSymmetricExtrapolation(_SymmetricExtrapolation): """Like _SymmetricExtrapolation but symmetric counterparts are negated for padding""" @@ -616,6 +673,36 @@ def __repr__(self): def pad_values(self, *args, **kwargs) -> Tensor: return -super().pad_values(*args, **kwargs) + def _pad_linear_tracer(self, value: 'ShiftLinTracer', widths: dict) -> 'ShiftLinTracer': + """ + *Warning*: + This implementation discards corners, i.e. values that lie outside the original tensor in more than one dimension. + These are typically sliced off in differential operators. Corners are instead assigned the value 0. + To take corners into account, call pad() for each axis individually. This is inefficient with ShiftLinTracer. + + Args: + value: ShiftLinTracer: + widths: dict: + + Returns: + + """ + lower = {dim: -lo for dim, (lo, _) in widths.items()} + result = value.shift(lower, new_shape=value.shape.after_pad(widths), val_fun=lambda v: ZERO.pad(v, widths), bias_fun=lambda b: ZERO.pad(b, widths)) # inner values ~half the computation time + for bound_dim, (bound_lo, bound_hi) in widths.items(): + for i in range(bound_lo): # i=0 means outer + # this sets corners to 0 + lower = {dim: bound_lo-1-2*i if dim == bound_dim else -lo for dim, (lo, _) in widths.items()} + mask = self._lower_mask(value.shape.only(result.dependent_dims), widths, bound_dim, bound_lo, bound_hi, i) + boundary = value.shift(lower, new_shape=result.shape, val_fun=lambda v: self.pad(v, widths) * mask, bias_fun=lambda b: ZERO.pad(b, widths)) + result -= boundary + for i in range(bound_hi): + lower = {dim: -(bound_hi-1-2*i) - bound_lo - bound_hi if dim == bound_dim else -lo for dim, (lo, hi) in widths.items()} + mask = self._upper_mask(value.shape.only(result.dependent_dims), widths, bound_dim, bound_lo, bound_hi, i) + boundary = value.shift(lower, new_shape=result.shape, val_fun=lambda v: self.pad(v, widths) * mask, bias_fun=lambda b: ZERO.pad(b, widths)) # ~ half the computation time + result -= boundary # this does basically nothing if value is the identity + return result + class _ReflectExtrapolation(_CopyExtrapolation): """Mirror of inner elements. The boundary value is not duplicated.""" @@ -640,6 +727,62 @@ def transform_coordinates(self, coordinates: Tensor, shape: Shape, **kwargs) -> coordinates = coordinates % (2 * shape - 2) return (shape - 1) - math.abs_((shape - 1) - coordinates) + def _pad_linear_tracer(self, value: 'ShiftLinTracer', widths: dict) -> 'ShiftLinTracer': + """ + *Warning*: + This implementation discards corners, i.e. values that lie outside the original tensor in more than one dimension. + These are typically sliced off in differential operators. Corners are instead assigned the value 0. + To take corners into account, call pad() for each axis individually. This is inefficient with ShiftLinTracer. + + Args: + value: ShiftLinTracer: + widths: dict: + + Returns: + + """ + lower = {dim: -lo for dim, (lo, _) in widths.items()} + result = value.shift(lower, new_shape=value.shape.after_pad(widths), val_fun=lambda v: ZERO.pad(v, widths), bias_fun=lambda b: ZERO.pad(b, widths)) # inner values ~half the computation time + for bound_dim, (bound_lo, bound_hi) in widths.items(): + for i in range(bound_lo): # i=0 means outer + # this sets corners to 0 + lower = {dim: bound_lo-2*i if dim == bound_dim else -lo for dim, (lo, _) in widths.items()} + mask = self._lower_mask(value.shape.only(result.dependent_dims), widths, bound_dim, bound_lo, bound_hi, i) + boundary = value.shift(lower, new_shape=result.shape, val_fun=lambda v: self.pad(v, widths) * mask, bias_fun=lambda b: ZERO.pad(b, widths)) + result += boundary + for i in range(bound_hi): + lower = {dim: -(bound_hi-2*i) - bound_lo - bound_hi if dim == bound_dim else -lo for dim, (lo, hi) in widths.items()} + mask = self._upper_mask(value.shape.only(result.dependent_dims), widths, bound_dim, bound_lo, bound_hi, i) + boundary = value.shift(lower, new_shape=result.shape, val_fun=lambda v: self.pad(v, widths) * mask, bias_fun=lambda b: ZERO.pad(b, widths)) # ~ half the computation time + result += boundary # this does basically nothing if value is the identity + return result + + def _lower_mask(self, shape, widths, bound_dim, bound_lo, bound_hi, i): + # key = (shape, tuple(widths.keys()), tuple(widths.values()), bound_dim, bound_lo, bound_hi, i) + # if key in _BoundaryExtrapolation._CACHED_LOWER_MASKS: + # result = math.tensor(_BoundaryExtrapolation._CACHED_LOWER_MASKS[key]) + # _BoundaryExtrapolation._CACHED_LOWER_MASKS[key] = result + # return result + # else: + mask = ZERO.pad(math.zeros(shape), {bound_dim: (bound_lo - i - 1, 0)}) + mask = ONE.pad(mask, {bound_dim: (1, 0)}) + mask = ZERO.pad(mask, {dim: (i, bound_hi) if dim == bound_dim else (lo, hi) for dim, (lo, hi) in widths.items()}) + # _BoundaryExtrapolation._CACHED_LOWER_MASKS[key] = mask + return mask + + def _upper_mask(self, shape, widths, bound_dim, bound_lo, bound_hi, i): + # key = (shape, tuple(widths.keys()), tuple(widths.values()), bound_dim, bound_lo, bound_hi, i) + # if key in _BoundaryExtrapolation._CACHED_UPPER_MASKS: + # result = math.tensor(_BoundaryExtrapolation._CACHED_UPPER_MASKS[key]) + # _BoundaryExtrapolation._CACHED_UPPER_MASKS[key] = result + # return result + # else: + mask = ZERO.pad(math.zeros(shape), {bound_dim: (0, bound_hi - i - 1)}) + mask = ONE.pad(mask, {bound_dim: (0, 1)}) + mask = ZERO.pad(mask, {dim: (bound_lo, i) if dim == bound_dim else (lo, hi) for dim, (lo, hi) in widths.items()}) + # _BoundaryExtrapolation._CACHED_UPPER_MASKS[key] = mask + return mask + class _AntiReflectExtrapolation(_ReflectExtrapolation): """Like _ReflectExtrapolation but symmetric counterparts are negated for padding""" @@ -650,6 +793,36 @@ def __repr__(self): def pad_values(self, *args, **kwargs) -> Tensor: return -super().pad_values(*args, **kwargs) + def _pad_linear_tracer(self, value: 'ShiftLinTracer', widths: dict) -> 'ShiftLinTracer': + """ + *Warning*: + This implementation discards corners, i.e. values that lie outside the original tensor in more than one dimension. + These are typically sliced off in differential operators. Corners are instead assigned the value 0. + To take corners into account, call pad() for each axis individually. This is inefficient with ShiftLinTracer. + + Args: + value: ShiftLinTracer: + widths: dict: + + Returns: + + """ + lower = {dim: -lo for dim, (lo, _) in widths.items()} + result = value.shift(lower, new_shape=value.shape.after_pad(widths), val_fun=lambda v: ZERO.pad(v, widths), bias_fun=lambda b: ZERO.pad(b, widths)) # inner values ~half the computation time + for bound_dim, (bound_lo, bound_hi) in widths.items(): + for i in range(bound_lo): # i=0 means outer + # this sets corners to 0 + lower = {dim: bound_lo-2*i if dim == bound_dim else -lo for dim, (lo, _) in widths.items()} + mask = self._lower_mask(value.shape.only(result.dependent_dims), widths, bound_dim, bound_lo, bound_hi, i) + boundary = value.shift(lower, new_shape=result.shape, val_fun=lambda v: self.pad(v, widths) * mask, bias_fun=lambda b: ZERO.pad(b, widths)) + result -= boundary + for i in range(bound_hi): + lower = {dim: -(bound_hi-2*i) - bound_lo - bound_hi if dim == bound_dim else -lo for dim, (lo, hi) in widths.items()} + mask = self._upper_mask(value.shape.only(result.dependent_dims), widths, bound_dim, bound_lo, bound_hi, i) + boundary = value.shift(lower, new_shape=result.shape, val_fun=lambda v: self.pad(v, widths) * mask, bias_fun=lambda b: ZERO.pad(b, widths)) # ~ half the computation time + result -= boundary # this does basically nothing if value is the identity + return result + class _NoExtrapolation(Extrapolation): # singleton def to_dict(self) -> dict: From 4dbb82892d7d79a67379fe40635a044dbcf4ce70 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Mon, 3 Oct 2022 02:07:06 +0200 Subject: [PATCH 018/170] Tensorflow implementation for FNOs --- phi/tf/nets.py | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/phi/tf/nets.py b/phi/tf/nets.py index 7614a3a30..acca42edf 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -542,3 +542,181 @@ def invertible_net(in_channels: int, return InvertibleNet(in_channels, num_blocks, activation, batch_norm, in_spatial, net) + + +################################################################################################################## +# Fourier Neural Operators +# source: https://github.com/zongyi-li/fourier_neural_operator +################################################################################################################### +RFFT = [tf.signal.rfft, tf.signal.rfft2d, tf.signal.rfft3d] +FFT = [tf.signal.fft, tf.signal.fft2d, tf.signal.fft3d] +IRFFT = [tf.signal.irfft, tf.signal.irfft2d, tf.signal.irfft3d] + +class SpectralConv(keras.Model): + + def __init__(self, in_channels, out_channels, modes, in_spatial): + + super(SpectralConv, self).__init__() + + self.in_channels = in_channels + self.out_channels = out_channels + self.in_spatial = in_spatial + assert in_spatial >= 1 and in_spatial <= 3 + if isinstance(modes, int): + mode = modes + modes = [mode for i in range(in_spatial)] + + self.scale = 1 / (in_channels * out_channels) + + self.modes = {i + 1: modes[i] for i in range(len(modes))} + self.weights = {} + + rand_shape = [in_channels, out_channels] + rand_shape += [self.modes[i] for i in range(1, in_spatial + 1)] + + for i in range(2 ** (in_spatial - 1)): + self.weights[f'w{i + 1}'] = tf.Variable(self.scale * tf.random.normal(shape=rand_shape, dtype=tf.dtypes.complex64), trainable=True) + #self.weights[f'w{i + 1}'] = nn.Parameter(self.scale * torch.randn(rand_shape, dtype=torch.cfloat)) + + def complex_mul(self, input, weights): + + if self.in_spatial == 1: + return tf.einsum("bix,iox->box", input, weights) + elif self.in_spatial == 2: + return tf.einsum("bixy,ioxy->boxy", input, weights) + elif self.in_spatial == 3: + return tf.einsum("bixyz,ioxyz->boxyz", input, weights) + + + def forward(self, x): + batch_size = x.shape[0] + + ##Convert to Fourier space + #dims = [-i for i in range(self.in_spatial, 0, -1)] + #x_ft = tf.signal.fft.rfftn(x, dim=dims) + x_ft = RFFT[self.in_spatial](x) + + outft_dims = [batch_size, self.out_channels] + \ + [x.size(-i) for i in range(self.in_spatial, 1, -1)] + [x.size(-1) // 2 + 1] + out_ft = tf.zeros(outft_dims, dtype=tf.dtypes.complex64) + + ##Multiply relevant fourier modes + if self.in_spatial == 1: + out_ft[:, :, :self.modes[1]] = \ + self.complex_mul(x_ft[:, :, :self.modes[1]], + self.weights['w1'].to(x_ft.device)) + elif self.in_spatial == 2: + out_ft[:, :, :self.modes[1], :self.modes[2]] = \ + self.complex_mul(x_ft[:, :, :self.modes[1], :self.modes[2]], + self.weights['w1'].to(x_ft.device)) + out_ft[:, :, -self.modes[1]:, :self.modes[2]] = \ + self.complex_mul(x_ft[:, :, -self.modes[1]:, :self.modes[2]], + self.weights['w2'].to(x_ft.device)) + elif self.in_spatial == 3: + out_ft[:, :, :self.modes[1], :self.modes[2], :self.modes[3]] = \ + self.complex_mul(x_ft[:, :, :self.modes[1], :self.modes[2], :self.modes[3]], + self.weights['w1'].to(x_ft.device)) + out_ft[:, :, -self.modes[1]:, :self.modes[2], :self.modes[3]] = \ + self.complex_mul(x_ft[:, :, -self.modes[1]:, :self.modes[2], :self.modes[3]], + self.weights['w2'].to(x_ft.device)) + out_ft[:, :, :self.modes[1], -self.modes[2]:, :self.modes[3]] = \ + self.complex_mul(x_ft[:, :, :self.modes[1], -self.modes[2]:, :self.modes[3]], + self.weights['w3'].to(x_ft.device)) + out_ft[:, :, -self.modes[1]:, -self.modes[2]:, :self.modes[3]] = \ + self.complex_mul(x_ft[:, :, -self.modes[1]:, -self.modes[2]:, :self.modes[3]], + self.weights['w4'].to(x_ft.device)) + + ##Return to Physical Space + x = IRFFT[self.in_spatial](out_ft) + #x = torch.fft.irfftn(out_ft, s=[x.size(-i) for i in range(self.in_spatial, 0, -1)]) + + return x + + +class FNO(keras.Model): + + def __init__(self, in_channels, out_channels, width, modes, activation, batch_norm, in_spatial): + super(FNO, self).__init__() + + """ + The overall network. It contains 4 layers of the Fourier layer. + 1. Lift the input to the desire channel dimension by self.fc0 . + 2. 4 layers of the integral operators u' = (W + K)(u). + W defined by self.w; K defined by self.conv . + 3. Project from the channel space to the output space by self.fc1 and self.fc2. + + input shape and output shape: (batchsize b, channels c, *spatial) + """ + + self.activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation + self.width = width + self.in_spatial = in_spatial + + self.fc0 = kl.Dense(self.width) + #self.fc0 = nn.Linear(in_channels + in_spatial, self.width) + + self.model_dict = {} + for i in range(4): + self.model_dict[f'conv{i}'] = SpectralConv(self.width, self.width, modes, in_spatial) + self.model_dict[f'w{i}'] = CONV[self.in_spatial](self.width, kernel_size=1) + self.model_dict[f'bn{i}'] = kl.BatchNormalization() + + self.fc1 = kl.Dense(128) + self.fc2 = kl.Dense(out_channels) + + # Adding extra spatial channels eg. x, y, z, .... to input x + def get_grid(self, shape, device): + batch_size = shape[0] + grid_channel_sizes = shape[2:] # shape = (batch_size, channels, *spatial) + self.grid_channels = {} + for i in range(self.in_spatial): + self.grid_channels[f'dim{i}'] = tf.tensor(tf.linspace(0, 1, grid_channel_sizes[i]), + dtype=tf.dtypes.float32) + reshape_dim_tuple = [1, 1] + [1 if i != j else grid_channel_sizes[j] for j in range(self.in_spatial)] + repeat_dim_tuple = [batch_size, 1] + [1 if i == j else grid_channel_sizes[j] for j in + range(self.in_spatial)] + self.grid_channels[f'dim{i}'] = self.grid_channels[f'dim{i}'].reshape(reshape_dim_tuple) \ + .repeat(repeat_dim_tuple) + + return torch.cat([self.grid_channels[f'dim{i}'] for i in range(self.in_spatial)], dim=1).to(device) + + def forward(self, x): + grid = self.get_grid(x.shape, x.device) + x = torch.cat([x, grid], dim=1) + + permute_tuple = [0] + [2 + i for i in range(self.in_spatial)] + [1] + permute_tuple_reverse = [0] + [self.in_spatial + 1] + [i + 1 for i in range(self.in_spatial)] + + # Transpose x such that channels shape lies at the end to pass it through linear layers + x = x.permute(permute_tuple) + + x = self.fc0(x) + + # Transpose x back to its original shape to pass it through convolutional layers + x = x.permute(permute_tuple_reverse) + + for i in range(4): + x1 = getattr(self, f'w{i}')(x) + x2 = getattr(self, f'conv{i}')(x) + x = getattr(self, f'bn{i}')(x1) + getattr(self, f'bn{i}')(x2) + x = self.activation()(x) + + x = x.permute(permute_tuple) + x = self.activation()(self.fc1(x)) + x = self.fc2(x) + + x = x.permute(permute_tuple_reverse) + + return x + + +def fno(in_channels: int, + out_channels: int, + mid_channels: int, + modes: Tuple[int, ...] or List[int], + activation: str or type = 'ReLU', + batch_norm: bool = False, + in_spatial: int = 2): + net = FNO(in_channels, out_channels, mid_channels, modes, activation, batch_norm, in_spatial) + net = net.to(TORCH.get_default_device().ref) + return net \ No newline at end of file From 60d32798e03c588842a109a2c4823972adae44b9 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Mon, 3 Oct 2022 02:30:02 +0200 Subject: [PATCH 019/170] Added description for new **kwargs argument in Network_API.ipynb --- docs/Network_API.ipynb | 179 ++++++++++++++++++++++++----------------- 1 file changed, 106 insertions(+), 73 deletions(-) diff --git a/docs/Network_API.ipynb b/docs/Network_API.ipynb index 922ff1867..ebe2d531c 100644 --- a/docs/Network_API.ipynb +++ b/docs/Network_API.ipynb @@ -28,13 +28,33 @@ { "cell_type": "code", "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os, sys\n", + "dir2 = os.path.abspath('../phi/')\n", + "dir1 = os.path.dirname(dir2)\n", + "if not dir1 in sys.path: sys.path.append(dir1)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, "metadata": { "pycharm": { "is_executing": true, "name": "#%%\n" } }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)\n" + ] + } + ], "source": [ "from phi.tf.flow import *\n", "from phi.jax.stax.flow import *\n", @@ -63,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": { "pycharm": { "name": "#%%\n" @@ -76,7 +96,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": { "pycharm": { "name": "#%%\n" @@ -87,8 +107,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Initial loss: \u001b[92m(batchᵇ=100)\u001b[0m \u001b[94m0.167 ± 0.171\u001b[0m \u001b[37m(1e-04...5e-01)\u001b[0m\n", - "Final loss: \u001b[92m(batchᵇ=100)\u001b[0m \u001b[94m0.033 ± 0.037\u001b[0m \u001b[37m(9e-07...1e-01)\u001b[0m\n" + "Initial loss: \u001b[92m(batchᵇ=100)\u001b[0m \u001b[94m0.239 ± 0.243\u001b[0m \u001b[37m(1e-06...8e-01)\u001b[0m\n", + "Final loss: \u001b[92m(batchᵇ=100)\u001b[0m \u001b[94m0.045 ± 0.044\u001b[0m \u001b[37m(1e-05...3e-01)\u001b[0m\n" ] } ], @@ -127,12 +147,13 @@ "* `activation` : activation function used within the layers, dtype : string\n", "* `batch_norm` : use of batchnorm after each conv layer, dtype : bool\n", "* `in_spatial` : spatial dimensions of the input feature map, dtype : int\n", - "* `use_res_blocks` : use convolutional blocks with skip connections instead of regular convolutional blocks, dtype : bool" + "* `use_res_blocks` : use convolutional blocks with skip connections instead of regular convolutional blocks, dtype : bool\n", + "* `**kwargs` : placeholder for arguments not supported by the function (such as layers in res_net and conv_net)" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "metadata": { "pycharm": { "name": "#%%\n" @@ -145,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "metadata": { "pycharm": { "name": "#%%\n" @@ -164,7 +185,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "metadata": { "pycharm": { "name": "#%%\n" @@ -176,28 +197,28 @@ "output_type": "stream", "text": [ "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.69e+04 ± 5.7e+04\u001b[0m \u001b[37m(5e+03...2e+05)\u001b[0m\n", + "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.98e+04 ± 6.0e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.71e+04 ± 5.7e+04\u001b[0m \u001b[37m(5e+03...2e+05)\u001b[0m\n", + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.99e+04 ± 6.0e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.37e+04 ± 5.1e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.70e+04 ± 5.5e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.22e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.52e+04 ± 5.2e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.07e+04 ± 4.7e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.38e+04 ± 4.9e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.98e+04 ± 4.6e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.26e+04 ± 4.8e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.89e+04 ± 4.6e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.15e+04 ± 4.7e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.80e+04 ± 4.4e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.04e+04 ± 4.6e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.73e+04 ± 4.3e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.94e+04 ± 4.5e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.68e+04 ± 4.3e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.86e+04 ± 4.5e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.62e+04 ± 4.3e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", - "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.62e+04 ± 4.3e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n" + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.79e+04 ± 4.4e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.79e+04 ± 4.4e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n" ] } ], @@ -233,12 +254,13 @@ "* `layers` : list or tuple of output channels for each intermediate layer between the input and final output channels, dtype : list or tuple
\n", "* `activation` : activation function used within the layers, dtype : string
\n", "* `batch_norm` : use of batchnorm after each conv layer, dtype : bool
\n", - "* `in_spatial` : spatial dimensions of the input feature map, dtype : int
\n" + "* `in_spatial` : spatial dimensions of the input feature map, dtype : int
\n", + "* `**kwargs` : placeholder for arguments not supported by the function\n" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "metadata": { "pycharm": { "name": "#%%\n" @@ -251,7 +273,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "metadata": { "pycharm": { "name": "#%%\n" @@ -263,28 +285,28 @@ "output_type": "stream", "text": [ "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.82e+04 ± 6.7e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.47e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.83e+04 ± 6.7e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.47e+04 ± 5.1e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.81e+04 ± 6.7e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.43e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.79e+04 ± 6.7e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.41e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.77e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.39e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.75e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.38e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.73e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.36e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.71e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.34e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.70e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.33e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.67e+04 ± 6.5e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.32e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.64e+04 ± 6.5e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", - "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.64e+04 ± 6.5e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n" + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.31e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.31e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n" ] } ], @@ -318,12 +340,13 @@ "* `layers` : list or tuple of output channels for each intermediate layer between the input and final output channels, dtype : list or tuple\n", "* `activation` : activation function used within the layers, dtype : string\n", "* `batch_norm` : use of batchnorm after each conv layer, dtype : bool\n", - "* `in_spatial` : spatial dimensions of the input feature map, dtype : int" + "* `in_spatial` : spatial dimensions of the input feature map, dtype : int\n", + "* `**kwargs` : placeholder for arguments not supported by the function" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 11, "metadata": { "pycharm": { "name": "#%%\n" @@ -336,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 12, "metadata": { "pycharm": { "name": "#%%\n" @@ -348,28 +371,28 @@ "output_type": "stream", "text": [ "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.29e+04 ± 6.7e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.65e+04 ± 6.7e+04\u001b[0m \u001b[37m(7e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.30e+04 ± 6.8e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.64e+04 ± 6.7e+04\u001b[0m \u001b[37m(7e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.22e+04 ± 6.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.63e+04 ± 6.7e+04\u001b[0m \u001b[37m(7e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.14e+04 ± 6.5e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.59e+04 ± 6.7e+04\u001b[0m \u001b[37m(7e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.07e+04 ± 6.4e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.57e+04 ± 6.6e+04\u001b[0m \u001b[37m(8e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.03e+04 ± 6.3e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.54e+04 ± 6.6e+04\u001b[0m \u001b[37m(8e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.97e+04 ± 6.3e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.52e+04 ± 6.6e+04\u001b[0m \u001b[37m(8e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.95e+04 ± 6.2e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.50e+04 ± 6.5e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.91e+04 ± 6.2e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.48e+04 ± 6.5e+04\u001b[0m \u001b[37m(8e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.87e+04 ± 6.1e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.45e+04 ± 6.5e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.83e+04 ± 6.1e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", - "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.83e+04 ± 6.1e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n" + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.44e+04 ± 6.5e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.44e+04 ± 6.5e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n" ] } ], @@ -404,13 +427,14 @@ "* `batch_norm` : use of batchnorm after each layer, dtype : bool\n", "* `in_spatial` : spatial dimensions of the input feature map, dtype : int\n", "* `net` : type of neural network blocks used in coupling layers, dtype : str\n", + "* `**kwargs` : placeholder for arguments not supported by the function\n", "\n", "Note: Currently supported values for net are 'u_net'(default), 'conv_net' and 'res_net'. For choosing 'dense_net' as the network block in coupling layers in_spatial must be set to zero." ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -429,7 +453,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -438,24 +462,24 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m2.20e+04 ± 1.4e+04\u001b[0m \u001b[37m(7e+03...5e+04)\u001b[0m\n", - "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.79e+04 ± 1.2e+04\u001b[0m \u001b[37m(4e+03...4e+04)\u001b[0m\n", - "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.55e+04 ± 1.0e+04\u001b[0m \u001b[37m(3e+03...4e+04)\u001b[0m\n", - "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.37e+04 ± 8.8e+03\u001b[0m \u001b[37m(4e+03...4e+04)\u001b[0m\n", - "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.21e+04 ± 7.6e+03\u001b[0m \u001b[37m(4e+03...4e+04)\u001b[0m\n", - "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.07e+04 ± 6.7e+03\u001b[0m \u001b[37m(4e+03...4e+04)\u001b[0m\n", - "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.49e+03 ± 6.0e+03\u001b[0m \u001b[37m(3e+03...4e+04)\u001b[0m\n", - "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.43e+03 ± 5.4e+03\u001b[0m \u001b[37m(3e+03...3e+04)\u001b[0m\n", - "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.51e+03 ± 4.8e+03\u001b[0m \u001b[37m(3e+03...3e+04)\u001b[0m\n", - "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.68e+03 ± 4.2e+03\u001b[0m \u001b[37m(3e+03...3e+04)\u001b[0m\n", - "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.68e+03 ± 4.2e+03\u001b[0m \u001b[37m(3e+03...3e+04)\u001b[0m\n" + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.97e+04 ± 1.4e+04\u001b[0m \u001b[37m(3e+03...5e+04)\u001b[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.67e+04 ± 1.2e+04\u001b[0m \u001b[37m(2e+03...4e+04)\u001b[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.44e+04 ± 1.0e+04\u001b[0m \u001b[37m(2e+03...4e+04)\u001b[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.24e+04 ± 8.5e+03\u001b[0m \u001b[37m(2e+03...4e+04)\u001b[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.07e+04 ± 7.2e+03\u001b[0m \u001b[37m(2e+03...3e+04)\u001b[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.21e+03 ± 6.1e+03\u001b[0m \u001b[37m(2e+03...3e+04)\u001b[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.91e+03 ± 5.1e+03\u001b[0m \u001b[37m(2e+03...3e+04)\u001b[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.79e+03 ± 4.1e+03\u001b[0m \u001b[37m(2e+03...2e+04)\u001b[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m5.81e+03 ± 3.4e+03\u001b[0m \u001b[37m(2e+03...2e+04)\u001b[0m\n", + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m4.99e+03 ± 2.8e+03\u001b[0m \u001b[37m(2e+03...2e+04)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m4.99e+03 ± 2.8e+03\u001b[0m \u001b[37m(2e+03...2e+04)\u001b[0m\n" ] } ], @@ -486,15 +510,15 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Loss between initial input and prediction \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.66e+04 ± 1.4e+04\u001b[0m \u001b[37m(8e+02...4e+04)\u001b[0m\n", - "Loss between initial input and reconstructed input \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.22e-08 ± 1.1e-08\u001b[0m \u001b[37m(3e-09...5e-08)\u001b[0m\n" + "Loss between initial input and prediction \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.91e+04 ± 1.5e+04\u001b[0m \u001b[37m(1e+03...4e+04)\u001b[0m\n", + "Loss between initial input and reconstructed input \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m5.04e-09 ± 3.5e-09\u001b[0m \u001b[37m(1e-09...1e-08)\u001b[0m\n" ] } ], @@ -505,14 +529,18 @@ "print('Loss between initial input and prediction',math.l2_loss(input_grid - grid))\n", "print('Loss between initial input and reconstructed input',math.l2_loss(input_grid - reconstructed_input))" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { - "interpreter": { - "hash": "82902df7c83405ec4dfb07b40523262a4e655c5cd82d0ed637b48934b3b1a8ec" - }, "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3.10.4 ('torch-tf-jax': conda)", "language": "python", "name": "python3" }, @@ -526,7 +554,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.10.4" + }, + "vscode": { + "interpreter": { + "hash": "3188f476244e99b7f99d90d67605181ae6381060b871ae5a45168d18984807c5" + } } }, "nbformat": 4, From 07ba1a8f39c38e535689215168fa62c2e552009e Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Mon, 3 Oct 2022 02:34:55 +0200 Subject: [PATCH 020/170] Additional fixes in Network_API.ipynb --- docs/Network_API.ipynb | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/docs/Network_API.ipynb b/docs/Network_API.ipynb index ebe2d531c..ef7998cc9 100644 --- a/docs/Network_API.ipynb +++ b/docs/Network_API.ipynb @@ -27,34 +27,14 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import os, sys\n", - "dir2 = os.path.abspath('../phi/')\n", - "dir1 = os.path.dirname(dir2)\n", - "if not dir1 in sys.path: sys.path.append(dir1)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, + "execution_count": 17, "metadata": { "pycharm": { "is_executing": true, "name": "#%%\n" } }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)\n" - ] - } - ], + "outputs": [], "source": [ "from phi.tf.flow import *\n", "from phi.jax.stax.flow import *\n", From a8571b9bea06b24033d94c4ab24ea171b7aa15b3 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Mon, 3 Oct 2022 10:22:33 +0200 Subject: [PATCH 021/170] Fixed Cell Execution Count Order in Network_API.ipynb --- docs/Network_API.ipynb | 130 ++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/docs/Network_API.ipynb b/docs/Network_API.ipynb index ef7998cc9..e88254295 100644 --- a/docs/Network_API.ipynb +++ b/docs/Network_API.ipynb @@ -27,7 +27,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 1, "metadata": { "pycharm": { "is_executing": true, @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": { "pycharm": { "name": "#%%\n" @@ -76,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "pycharm": { "name": "#%%\n" @@ -87,8 +87,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Initial loss: \u001b[92m(batchᵇ=100)\u001b[0m \u001b[94m0.239 ± 0.243\u001b[0m \u001b[37m(1e-06...8e-01)\u001b[0m\n", - "Final loss: \u001b[92m(batchᵇ=100)\u001b[0m \u001b[94m0.045 ± 0.044\u001b[0m \u001b[37m(1e-05...3e-01)\u001b[0m\n" + "Initial loss: \u001b[92m(batchᵇ=100)\u001b[0m \u001b[94m0.246 ± 0.178\u001b[0m \u001b[37m(2e-04...6e-01)\u001b[0m\n", + "Final loss: \u001b[92m(batchᵇ=100)\u001b[0m \u001b[94m0.087 ± 0.073\u001b[0m \u001b[37m(4e-04...3e-01)\u001b[0m\n" ] } ], @@ -133,7 +133,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "metadata": { "pycharm": { "name": "#%%\n" @@ -146,7 +146,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "metadata": { "pycharm": { "name": "#%%\n" @@ -165,7 +165,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "metadata": { "pycharm": { "name": "#%%\n" @@ -177,28 +177,28 @@ "output_type": "stream", "text": [ "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.98e+04 ± 6.0e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.28e+04 ± 5.6e+04\u001b[0m \u001b[37m(8e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.99e+04 ± 6.0e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.28e+04 ± 5.6e+04\u001b[0m \u001b[37m(8e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.70e+04 ± 5.5e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.09e+04 ± 5.3e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.52e+04 ± 5.2e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.95e+04 ± 5.1e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.38e+04 ± 4.9e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.85e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.26e+04 ± 4.8e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.74e+04 ± 4.8e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.15e+04 ± 4.7e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.68e+04 ± 4.7e+04\u001b[0m \u001b[37m(8e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.04e+04 ± 4.6e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.60e+04 ± 4.7e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.94e+04 ± 4.5e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.55e+04 ± 4.6e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.86e+04 ± 4.5e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.49e+04 ± 4.6e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.79e+04 ± 4.4e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", - "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.79e+04 ± 4.4e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n" + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.41e+04 ± 4.5e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.41e+04 ± 4.5e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n" ] } ], @@ -240,7 +240,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "metadata": { "pycharm": { "name": "#%%\n" @@ -253,7 +253,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "metadata": { "pycharm": { "name": "#%%\n" @@ -265,28 +265,28 @@ "output_type": "stream", "text": [ "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.47e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", + "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.21e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.47e+04 ± 5.1e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.21e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.43e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.20e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.41e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.18e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.39e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.17e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.38e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.16e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.36e+04 ± 5.0e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.15e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.34e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.14e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.33e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.12e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.32e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.11e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.31e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n", - "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.31e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+03...2e+05)\u001b[0m\n" + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.10e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.10e+04 ± 4.9e+04\u001b[0m \u001b[37m(1e+04...2e+05)\u001b[0m\n" ] } ], @@ -326,7 +326,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "metadata": { "pycharm": { "name": "#%%\n" @@ -339,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "metadata": { "pycharm": { "name": "#%%\n" @@ -351,28 +351,28 @@ "output_type": "stream", "text": [ "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.65e+04 ± 6.7e+04\u001b[0m \u001b[37m(7e+03...2e+05)\u001b[0m\n", + "Initial loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.13e+04 ± 5.6e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.64e+04 ± 6.7e+04\u001b[0m \u001b[37m(7e+03...2e+05)\u001b[0m\n", + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.13e+04 ± 5.6e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.63e+04 ± 6.7e+04\u001b[0m \u001b[37m(7e+03...2e+05)\u001b[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.10e+04 ± 5.6e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.59e+04 ± 6.7e+04\u001b[0m \u001b[37m(7e+03...2e+05)\u001b[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.08e+04 ± 5.6e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.57e+04 ± 6.6e+04\u001b[0m \u001b[37m(8e+03...2e+05)\u001b[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.05e+04 ± 5.6e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.54e+04 ± 6.6e+04\u001b[0m \u001b[37m(8e+03...2e+05)\u001b[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.04e+04 ± 5.6e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.52e+04 ± 6.6e+04\u001b[0m \u001b[37m(8e+03...2e+05)\u001b[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.01e+04 ± 5.5e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.50e+04 ± 6.5e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.00e+04 ± 5.6e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.48e+04 ± 6.5e+04\u001b[0m \u001b[37m(8e+03...2e+05)\u001b[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.97e+04 ± 5.5e+04\u001b[0m \u001b[37m(3e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.45e+04 ± 6.5e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.97e+04 ± 5.6e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", "Grid Shape : (examplesᵇ=50, xˢ=64, yˢ=64)\n", - "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.44e+04 ± 6.5e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n", - "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.44e+04 ± 6.5e+04\u001b[0m \u001b[37m(9e+03...2e+05)\u001b[0m\n" + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.95e+04 ± 5.6e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.95e+04 ± 5.6e+04\u001b[0m \u001b[37m(2e+04...2e+05)\u001b[0m\n" ] } ], @@ -414,7 +414,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -433,7 +433,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -442,24 +442,24 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.97e+04 ± 1.4e+04\u001b[0m \u001b[37m(3e+03...5e+04)\u001b[0m\n", - "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.67e+04 ± 1.2e+04\u001b[0m \u001b[37m(2e+03...4e+04)\u001b[0m\n", - "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.44e+04 ± 1.0e+04\u001b[0m \u001b[37m(2e+03...4e+04)\u001b[0m\n", - "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.24e+04 ± 8.5e+03\u001b[0m \u001b[37m(2e+03...4e+04)\u001b[0m\n", - "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.07e+04 ± 7.2e+03\u001b[0m \u001b[37m(2e+03...3e+04)\u001b[0m\n", - "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.21e+03 ± 6.1e+03\u001b[0m \u001b[37m(2e+03...3e+04)\u001b[0m\n", - "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m7.91e+03 ± 5.1e+03\u001b[0m \u001b[37m(2e+03...3e+04)\u001b[0m\n", - "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m6.79e+03 ± 4.1e+03\u001b[0m \u001b[37m(2e+03...2e+04)\u001b[0m\n", - "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m5.81e+03 ± 3.4e+03\u001b[0m \u001b[37m(2e+03...2e+04)\u001b[0m\n", - "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m4.99e+03 ± 2.8e+03\u001b[0m \u001b[37m(2e+03...2e+04)\u001b[0m\n", - "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m4.99e+03 ± 2.8e+03\u001b[0m \u001b[37m(2e+03...2e+04)\u001b[0m\n" + "Iter : 0, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m2.06e+04 ± 1.3e+04\u001b[0m \u001b[37m(4e+03...5e+04)\u001b[0m\n", + "Iter : 1, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.77e+04 ± 1.2e+04\u001b[0m \u001b[37m(3e+03...4e+04)\u001b[0m\n", + "Iter : 2, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.56e+04 ± 1.1e+04\u001b[0m \u001b[37m(3e+03...4e+04)\u001b[0m\n", + "Iter : 3, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.42e+04 ± 9.9e+03\u001b[0m \u001b[37m(3e+03...4e+04)\u001b[0m\n", + "Iter : 4, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.30e+04 ± 9.1e+03\u001b[0m \u001b[37m(3e+03...4e+04)\u001b[0m\n", + "Iter : 5, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.19e+04 ± 8.2e+03\u001b[0m \u001b[37m(3e+03...3e+04)\u001b[0m\n", + "Iter : 6, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.09e+04 ± 7.4e+03\u001b[0m \u001b[37m(3e+03...3e+04)\u001b[0m\n", + "Iter : 7, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.00e+04 ± 6.6e+03\u001b[0m \u001b[37m(3e+03...3e+04)\u001b[0m\n", + "Iter : 8, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m9.18e+03 ± 5.9e+03\u001b[0m \u001b[37m(3e+03...2e+04)\u001b[0m\n", + "Iter : 9, Loss : \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.43e+03 ± 5.3e+03\u001b[0m \u001b[37m(3e+03...2e+04)\u001b[0m\n", + "Final loss: \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.43e+03 ± 5.3e+03\u001b[0m \u001b[37m(3e+03...2e+04)\u001b[0m\n" ] } ], @@ -490,15 +490,15 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Loss between initial input and prediction \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m1.91e+04 ± 1.5e+04\u001b[0m \u001b[37m(1e+03...4e+04)\u001b[0m\n", - "Loss between initial input and reconstructed input \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m5.04e-09 ± 3.5e-09\u001b[0m \u001b[37m(1e-09...1e-08)\u001b[0m\n" + "Loss between initial input and prediction \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m8.82e+03 ± 6.9e+03\u001b[0m \u001b[37m(7e+02...2e+04)\u001b[0m\n", + "Loss between initial input and reconstructed input \u001b[92m(examplesᵇ=50)\u001b[0m \u001b[94m4.84e-09 ± 2.3e-09\u001b[0m \u001b[37m(2e-09...1e-08)\u001b[0m\n" ] } ], From 04424765c26373956e27623af63df27eb33437b6 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Tue, 18 Oct 2022 08:55:19 +0200 Subject: [PATCH 022/170] Fourier Neural Operators for tensorflow + minor bug fixes in pytorch version --- phi/tf/flow.py | 2 +- phi/tf/nets.py | 126 +++++++++++++++++++++++----------------------- phi/torch/nets.py | 12 +++-- 3 files changed, 71 insertions(+), 69 deletions(-) diff --git a/phi/tf/flow.py b/phi/tf/flow.py index 9cd099682..8ac39f0fd 100644 --- a/phi/tf/flow.py +++ b/phi/tf/flow.py @@ -14,7 +14,7 @@ from phi.flow import * from . import TENSORFLOW -from .nets import parameter_count, get_parameters, dense_net, u_net, save_state, load_state, update_weights, adam, conv_net, res_net, sgd, sgd as SGD, adagrad, rmsprop, conv_classifier, invertible_net +from .nets import parameter_count, get_parameters, dense_net, u_net, save_state, load_state, update_weights, adam, conv_net, res_net, sgd, sgd as SGD, adagrad, rmsprop, conv_classifier, invertible_net, fno import tensorflow from tensorflow import keras from tensorflow.keras import layers diff --git a/phi/tf/nets.py b/phi/tf/nets.py index acca42edf..127a2981a 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -548,9 +548,9 @@ def invertible_net(in_channels: int, # Fourier Neural Operators # source: https://github.com/zongyi-li/fourier_neural_operator ################################################################################################################### -RFFT = [tf.signal.rfft, tf.signal.rfft2d, tf.signal.rfft3d] -FFT = [tf.signal.fft, tf.signal.fft2d, tf.signal.fft3d] -IRFFT = [tf.signal.irfft, tf.signal.irfft2d, tf.signal.irfft3d] +RFFT = [None, tf.signal.rfft, tf.signal.rfft2d, tf.signal.rfft3d] +FFT = [None, tf.signal.fft, tf.signal.fft2d, tf.signal.fft3d] +IRFFT = [None, tf.signal.irfft, tf.signal.irfft2d, tf.signal.irfft3d] class SpectralConv(keras.Model): @@ -569,14 +569,14 @@ def __init__(self, in_channels, out_channels, modes, in_spatial): self.scale = 1 / (in_channels * out_channels) self.modes = {i + 1: modes[i] for i in range(len(modes))} - self.weights = {} + self.weights_ = {} rand_shape = [in_channels, out_channels] rand_shape += [self.modes[i] for i in range(1, in_spatial + 1)] for i in range(2 ** (in_spatial - 1)): - self.weights[f'w{i + 1}'] = tf.Variable(self.scale * tf.random.normal(shape=rand_shape, dtype=tf.dtypes.complex64), trainable=True) - #self.weights[f'w{i + 1}'] = nn.Parameter(self.scale * torch.randn(rand_shape, dtype=torch.cfloat)) + self.weights_[f'w{i + 1}'] = tf.complex(tf.Variable(self.scale * tf.random.normal(shape=rand_shape, dtype=tf.dtypes.float32), trainable=True), + tf.Variable(self.scale * tf.random.normal(shape=rand_shape, dtype=tf.dtypes.float32), trainable=True)) def complex_mul(self, input, weights): @@ -588,47 +588,48 @@ def complex_mul(self, input, weights): return tf.einsum("bixyz,ioxyz->boxyz", input, weights) - def forward(self, x): + def call(self, x): batch_size = x.shape[0] - ##Convert to Fourier space - #dims = [-i for i in range(self.in_spatial, 0, -1)] - #x_ft = tf.signal.fft.rfftn(x, dim=dims) x_ft = RFFT[self.in_spatial](x) outft_dims = [batch_size, self.out_channels] + \ - [x.size(-i) for i in range(self.in_spatial, 1, -1)] + [x.size(-1) // 2 + 1] - out_ft = tf.zeros(outft_dims, dtype=tf.dtypes.complex64) + [x.shape[-i] for i in range(self.in_spatial, 1, -1)] + [x.shape[-1] // 2 + 1] + out_ft0 = tf.complex(tf.Variable(tf.zeros(outft_dims, dtype=tf.dtypes.float32)), + tf.Variable(tf.zeros(outft_dims, dtype=tf.dtypes.float32))) - ##Multiply relevant fourier modes if self.in_spatial == 1: - out_ft[:, :, :self.modes[1]] = \ - self.complex_mul(x_ft[:, :, :self.modes[1]], - self.weights['w1'].to(x_ft.device)) + out_ft1 = self.complex_mul(x_ft[:, :, :self.modes[1]], + self.weights_['w1']) + out_ft = tf.concat([out_ft1, out_ft0[:, :, self.modes[1]:]], axis=-1) elif self.in_spatial == 2: - out_ft[:, :, :self.modes[1], :self.modes[2]] = \ - self.complex_mul(x_ft[:, :, :self.modes[1], :self.modes[2]], - self.weights['w1'].to(x_ft.device)) - out_ft[:, :, -self.modes[1]:, :self.modes[2]] = \ - self.complex_mul(x_ft[:, :, -self.modes[1]:, :self.modes[2]], - self.weights['w2'].to(x_ft.device)) + out_ft1 = self.complex_mul(x_ft[:, :, :self.modes[1], :self.modes[2]], + self.weights_['w1']) + out_ft2 = self.complex_mul(x_ft[:, :, -self.modes[1]:, :self.modes[2]], + self.weights_['w2']) + out_ft3 = tf.concat([out_ft1, out_ft0[:, :, self.modes[1]:-self.modes[1], + :self.modes[2]], out_ft2], axis=-2) + out_ft = tf.concat([out_ft3, out_ft0[:, :, :, self.modes[2]:]], axis=-1) elif self.in_spatial == 3: - out_ft[:, :, :self.modes[1], :self.modes[2], :self.modes[3]] = \ - self.complex_mul(x_ft[:, :, :self.modes[1], :self.modes[2], :self.modes[3]], - self.weights['w1'].to(x_ft.device)) - out_ft[:, :, -self.modes[1]:, :self.modes[2], :self.modes[3]] = \ - self.complex_mul(x_ft[:, :, -self.modes[1]:, :self.modes[2], :self.modes[3]], - self.weights['w2'].to(x_ft.device)) - out_ft[:, :, :self.modes[1], -self.modes[2]:, :self.modes[3]] = \ - self.complex_mul(x_ft[:, :, :self.modes[1], -self.modes[2]:, :self.modes[3]], - self.weights['w3'].to(x_ft.device)) - out_ft[:, :, -self.modes[1]:, -self.modes[2]:, :self.modes[3]] = \ - self.complex_mul(x_ft[:, :, -self.modes[1]:, -self.modes[2]:, :self.modes[3]], - self.weights['w4'].to(x_ft.device)) + out_ft1 = self.complex_mul(x_ft[:, :, :self.modes[1], :self.modes[2], :self.modes[3]], + self.weights_['w1']) + out_ft2 = self.complex_mul(x_ft[:, :, -self.modes[1]:, :self.modes[2], :self.modes[3]], + self.weights_['w2']) + out_ft3 = self.complex_mul(x_ft[:, :, :self.modes[1], -self.modes[2]:, :self.modes[3]], + self.weights_['w3']) + out_ft4 = self.complex_mul(x_ft[:, :, -self.modes[1]:, -self.modes[2]:, :self.modes[3]], + self.weights_['w4']) + + out_ft5 = tf.concat([out_ft1, out_ft0[:, :, self.modes[1]:-self.modes[1], :self.modes[2], :self.modes[3]] + , out_ft2], axis=-3) + out_ft6 = tf.concat([out_ft3, out_ft0[:, :, self.modes[1]:-self.modes[1], -self.modes[2]:, :self.modes[3]] + , out_ft4], axis=-3) + out_ft7 = tf.concat([out_ft5, out_ft0[:, :, :, self.modes[2]:-self.modes[2], :self.modes[3]], out_ft6], + axis=-2) + out_ft = tf.concat([out_ft7, out_ft0[:, :, :, :, self.modes[3]:]], axis=-1) ##Return to Physical Space x = IRFFT[self.in_spatial](out_ft) - #x = torch.fft.irfftn(out_ft, s=[x.size(-i) for i in range(self.in_spatial, 0, -1)]) return x @@ -653,7 +654,6 @@ def __init__(self, in_channels, out_channels, width, modes, activation, batch_no self.in_spatial = in_spatial self.fc0 = kl.Dense(self.width) - #self.fc0 = nn.Linear(in_channels + in_spatial, self.width) self.model_dict = {} for i in range(4): @@ -667,46 +667,45 @@ def __init__(self, in_channels, out_channels, width, modes, activation, batch_no # Adding extra spatial channels eg. x, y, z, .... to input x def get_grid(self, shape, device): batch_size = shape[0] - grid_channel_sizes = shape[2:] # shape = (batch_size, channels, *spatial) + grid_channel_sizes = shape[1:-1] # shape = (batch_size, *spatial, channels) self.grid_channels = {} for i in range(self.in_spatial): - self.grid_channels[f'dim{i}'] = tf.tensor(tf.linspace(0, 1, grid_channel_sizes[i]), - dtype=tf.dtypes.float32) - reshape_dim_tuple = [1, 1] + [1 if i != j else grid_channel_sizes[j] for j in range(self.in_spatial)] - repeat_dim_tuple = [batch_size, 1] + [1 if i == j else grid_channel_sizes[j] for j in - range(self.in_spatial)] - self.grid_channels[f'dim{i}'] = self.grid_channels[f'dim{i}'].reshape(reshape_dim_tuple) \ - .repeat(repeat_dim_tuple) + self.grid_channels[f'dim{i}'] = tf.cast(tf.linspace(0, 1, + grid_channel_sizes[i]), dtype=tf.dtypes.float32) #tf.tensor(tf.linspace(0, 1, grid_channel_sizes[i]), dtype=tf.dtypes.float32) + reshape_dim_tuple = [1,] + [1 if i != j else grid_channel_sizes[j] + for j in range(self.in_spatial)] + [1,] + repeat_dim_tuple = [batch_size,] + [1 if i == j else grid_channel_sizes[j] + for j in range(self.in_spatial)] + [1,] - return torch.cat([self.grid_channels[f'dim{i}'] for i in range(self.in_spatial)], dim=1).to(device) + self.grid_channels[f'dim{i}'] = tf.tile(tf.reshape(self.grid_channels[f'dim{i}'], reshape_dim_tuple), repeat_dim_tuple) - def forward(self, x): + return tf.concat([self.grid_channels[f'dim{i}'] for i in range(self.in_spatial)], axis=-1) + + def call(self, x): grid = self.get_grid(x.shape, x.device) - x = torch.cat([x, grid], dim=1) + x = tf.concat([x, grid], axis=-1) - permute_tuple = [0] + [2 + i for i in range(self.in_spatial)] + [1] - permute_tuple_reverse = [0] + [self.in_spatial + 1] + [i + 1 for i in range(self.in_spatial)] + permute_tuple= [0] + [self.in_spatial + 1] + [i + 1 for i in range(self.in_spatial)] + permute_tuple_reverse = [0] + [2 + i for i in range(self.in_spatial)] + [1] - # Transpose x such that channels shape lies at the end to pass it through linear layers - x = x.permute(permute_tuple) + # No need to Transpose x such that channels shape lies + # at the end to pass it through linear layers as it's the default in tf + #x = tf.transpose(x, permute_tuple) x = self.fc0(x) - # Transpose x back to its original shape to pass it through convolutional layers - x = x.permute(permute_tuple_reverse) - for i in range(4): - x1 = getattr(self, f'w{i}')(x) - x2 = getattr(self, f'conv{i}')(x) - x = getattr(self, f'bn{i}')(x1) + getattr(self, f'bn{i}')(x2) - x = self.activation()(x) - - x = x.permute(permute_tuple) - x = self.activation()(self.fc1(x)) + x1 = self.model_dict[f'w{i}'](x) + # Spectral conv expects a shape : [batch, channel, *spatial] + # hence the transpose: + x2 = self.model_dict[f'conv{i}'](tf.transpose(x, permute_tuple)) + x2 = tf.transpose(x2, permute_tuple_reverse) + x = self.model_dict[f'bn{i}'](x1) + self.model_dict[f'bn{i}'](x2) + x = self.activation(x) + + x = self.activation(self.fc1(x)) x = self.fc2(x) - x = x.permute(permute_tuple_reverse) - return x @@ -718,5 +717,4 @@ def fno(in_channels: int, batch_norm: bool = False, in_spatial: int = 2): net = FNO(in_channels, out_channels, mid_channels, modes, activation, batch_norm, in_spatial) - net = net.to(TORCH.get_default_device().ref) return net \ No newline at end of file diff --git a/phi/torch/nets.py b/phi/torch/nets.py index e0d3ce916..3757cc756 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -650,9 +650,13 @@ def __init__(self, in_channels, out_channels, modes, in_spatial): for i in range(2 ** (in_spatial - 1)): self.weights[f'w{i + 1}'] = nn.Parameter(self.scale * torch.randn(rand_shape, dtype=torch.cfloat)) - + #print('TORCH self.weights:', self.weights_[f'w{i + 1}'].shape) + #print(self.weights[f'w{i + 1}'].shape) def complex_mul(self, input, weights): + #print(input.shape) + #print(weights.shape) + #exit(1) if self.in_spatial == 1: return torch.einsum("bix,iox->box", input, weights) elif self.in_spatial == 2: @@ -663,14 +667,15 @@ def complex_mul(self, input, weights): def forward(self, x): batch_size = x.shape[0] + #print('x.shape:', x.shape) ##Convert to Fourier space dims = [-i for i in range(self.in_spatial, 0, -1)] x_ft = torch.fft.rfftn(x, dim=dims) - + #print('After RFFT torch', x_ft.shape) outft_dims = [batch_size, self.out_channels] + \ [x.size(-i) for i in range(self.in_spatial, 1, -1)] + [x.size(-1) // 2 + 1] out_ft = torch.zeros(outft_dims, dtype=torch.cfloat, device=x.device) - + #print('outft shape before', out_ft.shape) ##Multiply relevant fourier modes if self.in_spatial == 1: out_ft[:, :, :self.modes[1]] = \ @@ -699,7 +704,6 @@ def forward(self, x): ##Return to Physical Space x = torch.fft.irfftn(out_ft, s=[x.size(-i) for i in range(self.in_spatial, 0, -1)]) - return x From 61e0c9e1d17071c2903f6940cdc2a336f6eee76e Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Thu, 20 Oct 2022 10:13:52 +0200 Subject: [PATCH 023/170] Added Docstrings for major Built-in Neural Networks --- phi/jax/stax/nets.py | 91 +++++++++++++++++++++++++++++++++++ phi/tf/nets.py | 110 +++++++++++++++++++++++++++++++++++++++++- phi/torch/nets.py | 112 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 311 insertions(+), 2 deletions(-) diff --git a/phi/jax/stax/nets.py b/phi/jax/stax/nets.py index 4c7c8f485..b463827ed 100644 --- a/phi/jax/stax/nets.py +++ b/phi/jax/stax/nets.py @@ -281,6 +281,18 @@ def dense_net(in_channels: int, layers: Tuple[int, ...] or List[int], batch_norm=False, activation='ReLU') -> StaxNet: + """ + Fully-connected neural networks are available in ΦFlow via dense_net(). + Arguments: + in_channels : size of input layer, int + out_channels = size of output layer, int + layers : tuple of linear layers between input and output neurons, list or tuple + activation : activation function used within the layers, string + batch_norm : use of batch norm after each linear layer, bool + + Returns: + Dense net model as specified by input arguments + """ activation = {'ReLU': stax.Relu, 'Sigmoid': stax.Sigmoid, 'tanh': stax.Tanh}[activation] stax_layers = [] for neuron_count in layers: @@ -303,6 +315,25 @@ def u_net(in_channels: int, activation='ReLU', in_spatial: tuple or int = 2, use_res_blocks: bool = False) -> StaxNet: + """ + ΦFlow provides a built-in U-net architecture, classically popular for Semantic Segmentation in Computer Vision, composed of downsampling and upsampling layers. + + Arguments: + + in_channels: input channels of the feature map, dtype : int + out_channels : output channels of the feature map, dtype : int + levels : number of levels of down-sampling and upsampling, dtype : int + filters : filter sizes at each down/up sampling convolutional layer, if the input is integer all conv layers have the same filter size, + dtype : int or tuple + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each conv layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + use_res_blocks : use convolutional blocks with skip connections instead of regular convolutional blocks, dtype : bool + + Returns: + + U-net model as specified by input arguments + """ if isinstance(filters, (tuple, list)): assert len(filters) == levels, f"List of filters has length {len(filters)} but u-net has {levels} levels." else: @@ -537,6 +568,21 @@ def conv_net(in_channels: int, batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2) -> StaxNet: + """ + Built in Conv-Nets are also provided. Contrary to the classical convolutional neural networks, the feature map spatial size remains the same throughout the layers. Each layer of the network is essentially a convolutional block comprising of two conv layers. A filter size of 3 is used in the convolutional layers. + Arguments: + + in_channels : input channels of the feature map, dtype : int + out_channels : output channels of the feature map, dtype : int + layers : list or tuple of output channels for each intermediate layer between the input and final output channels, dtype : list or tuple + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each conv layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + + Returns: + + Conv-net model as specified by input arguments + """ if isinstance(in_spatial, tuple): d = in_spatial in_spatial = len(in_spatial) @@ -592,6 +638,24 @@ def res_net(in_channels: int, batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2) -> StaxNet: + """ + Built in Res-Nets are provided in the ΦFlow framework. Similar to the conv-net, the feature map spatial size remains the same throughout the layers. + These networks use residual blocks composed of two conv layers with a skip connection added from the input to the output feature map. + A default filter size of 3 is used in the convolutional layers. + + Arguments: + + in_channels : input channels of the feature map, dtype : int + out_channels : output channels of the feature map, dtype : int + layers : list or tuple of output channels for each intermediate layer between the input and final output channels, dtype : list or tuple + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each conv layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + + Returns: + + Res-net model as specified by input arguments + """ if isinstance(in_spatial, tuple): d = in_spatial in_spatial = len(in_spatial) @@ -674,6 +738,7 @@ def net_apply(params, inputs, **kwargs): def get_mask(inputs, reverse_mask, data_format='NHWC'): + """ Compute mask for slicing input feature map for Invertible Nets """ shape = inputs.shape if len(shape) == 2: N = shape[-1] @@ -756,6 +821,7 @@ def conv_net_unit(in_channels: int, batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2, **kwargs): + """ Conv-net unit for Invertible Nets""" if isinstance(in_spatial, tuple): d = in_spatial in_spatial = len(in_spatial) @@ -823,6 +889,7 @@ def u_net_unit(in_channels: int, activation='ReLU', in_spatial: tuple or int = 2, use_res_blocks: bool = False, **kwargs): + """ U-net unit for Invertible Nets""" if isinstance(filters, (tuple, list)): assert len(filters) == levels, f"List of filters has length {len(filters)} but u-net has {levels} levels." else: @@ -898,6 +965,7 @@ def res_net_unit(in_channels: int, batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2, **kwargs): + """ Res-net unit for Invertible Nets""" if isinstance(in_spatial, tuple): d = in_spatial in_spatial = len(in_spatial) @@ -1014,6 +1082,29 @@ def invertible_net(in_channels: int, net: str = 'u_net', activation: str or type = 'ReLU', in_spatial: tuple or int = 2, **kwargs): + """ + ΦFlow also provides invertible neural networks that are capable of inverting the output tensor back to the input tensor initially passed.\ These networks have far reaching applications in predicting input parameters of a problem given its observations.\ Invertible nets are composed of multiple concatenated coupling blocks wherein each such block consists of arbitrary neural networks. + + Currently, these arbitrary neural networks could be set to u_net(default), conv_net, res_net or dense_net blocks with in_channels = out_channels. + The architecture used is popularized by ["Real NVP"](https://arxiv.org/abs/1605.08803). + + Arguments: + + in_channels : input channels of the feature map, dtype : int + num_blocks : number of coupling blocks inside the invertible net, dtype : int + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + net : type of neural network blocks used in coupling layers, dtype : str + **kwargs : placeholder for arguments not supported by the function + + Returns: + + Invertible Net model as specified by input arguments + + Note: Currently supported values for net are 'u_net'(default), 'conv_net' and 'res_net'. + For choosing 'dense_net' as the network block in coupling layers in_spatial must be set to zero. + """ if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) diff --git a/phi/tf/nets.py b/phi/tf/nets.py index b97114ec7..f83513e7d 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -173,6 +173,18 @@ def dense_net(in_channels: int, layers: Tuple[int, ...] or List[int], batch_norm=False, activation='ReLU') -> keras.Model: + """ + Fully-connected neural networks are available in ΦFlow via dense_net(). + Arguments: + in_channels : size of input layer, int + out_channels = size of output layer, int + layers : tuple of linear layers between input and output neurons, list or tuple + activation : activation function used within the layers, string + batch_norm : use of batch norm after each linear layer, bool + + Returns: + Dense net model as specified by input arguments + """ activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation keras_layers = [] for neuron_count in layers: @@ -192,6 +204,25 @@ def u_net(in_channels: int, activation: str or Callable = 'ReLU', in_spatial: tuple or int = 2, use_res_blocks: bool = False, **kwargs) -> keras.Model: + """ + ΦFlow provides a built-in U-net architecture, classically popular for Semantic Segmentation in Computer Vision, composed of downsampling and upsampling layers. + + Arguments: + + in_channels: input channels of the feature map, dtype : int + out_channels : output channels of the feature map, dtype : int + levels : number of levels of down-sampling and upsampling, dtype : int + filters : filter sizes at each down/up sampling convolutional layer, if the input is integer all conv layers have the same filter size, + dtype : int or tuple + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each conv layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + use_res_blocks : use convolutional blocks with skip connections instead of regular convolutional blocks, dtype : bool + + Returns: + + U-net model as specified by input arguments + """ if isinstance(in_spatial, int): d = in_spatial in_spatial = (None,) * d @@ -260,6 +291,21 @@ def conv_net(in_channels: int, batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2, **kwargs) -> keras.Model: + """ + Built in Conv-Nets are also provided. Contrary to the classical convolutional neural networks, the feature map spatial size remains the same throughout the layers. Each layer of the network is essentially a convolutional block comprising of two conv layers. A filter size of 3 is used in the convolutional layers. + Arguments: + + in_channels : input channels of the feature map, dtype : int + out_channels : output channels of the feature map, dtype : int + layers : list or tuple of output channels for each intermediate layer between the input and final output channels, dtype : list or tuple + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each conv layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + + Returns: + + Conv-net model as specified by input arguments + """ if isinstance(in_spatial, int): d = (None,) * in_spatial else: @@ -267,7 +313,7 @@ def conv_net(in_channels: int, d = in_spatial in_spatial = len(d) activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation - x = inputs = keras.Input(shape=d + (in_channels,)) + x = inputs = keras.InpuΦFlowt(shape=d + (in_channels,)) if len(layers) < 1: layers.append(out_channels) for i in range(len(layers)): @@ -320,6 +366,24 @@ def res_net(in_channels: int, batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2, **kwargs): + """ + Built in Res-Nets are provided in the ΦFlow framework. Similar to the conv-net, the feature map spatial size remains the same throughout the layers. + These networks use residual blocks composed of two conv layers with a skip connection added from the input to the output feature map. + A default filter size of 3 is used in the convolutional layers. + + Arguments: + + in_channels : input channels of the feature map, dtype : int + out_channels : output channels of the feature map, dtype : int + layers : list or tuple of output channels for each intermediate layer between the input and final output channels, dtype : list or tuple + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each conv layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + + Returns: + + Res-net model as specified by input arguments + """ if isinstance(in_spatial, int): d = (None,) * in_spatial else: @@ -395,6 +459,7 @@ def conv_classifier(input_shape: list, num_classes: int, batch_norm: bool, in_sp def get_mask(inputs, reverse_mask, data_format='NHWC'): + """ Compute mask for slicing input feature map for Invertible Nets """ shape = inputs.shape if len(shape) == 2: N = shape[-1] @@ -537,6 +602,29 @@ def invertible_net(in_channels: int, net: str = 'u_net', activation: str or type = 'ReLU', in_spatial: tuple or int = 2, **kwargs): + """ + ΦFlow also provides invertible neural networks that are capable of inverting the output tensor back to the input tensor initially passed.\ These networks have far reaching applications in predicting input parameters of a problem given its observations.\ Invertible nets are composed of multiple concatenated coupling blocks wherein each such block consists of arbitrary neural networks. + + Currently, these arbitrary neural networks could be set to u_net(default), conv_net, res_net or dense_net blocks with in_channels = out_channels. + The architecture used is popularized by ["Real NVP"](https://arxiv.org/abs/1605.08803). + + Arguments: + + in_channels : input channels of the feature map, dtype : int + num_blocks : number of coupling blocks inside the invertible net, dtype : int + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + net : type of neural network blocks used in coupling layers, dtype : str + **kwargs : placeholder for arguments not supported by the function + + Returns: + + Invertible Net model as specified by input arguments + + Note: Currently supported values for net are 'u_net'(default), 'conv_net' and 'res_net'. + For choosing 'dense_net' as the network block in coupling layers in_spatial must be set to zero. + """ if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) @@ -716,5 +804,25 @@ def fno(in_channels: int, activation: str or type = 'ReLU', batch_norm: bool = False, in_spatial: int = 2): + """ + ["Fourier Neural Operator"](https://github.com/zongyi-li/fourier_neural_operator) network contains 4 layers of the Fourier layer. + 1. Lift the input to the desire channel dimension by self.fc0 . + 2. 4 layers of the integral operators u' = (W + K)(u). W defined by self.w; K defined by self.conv . + 3. Project from the channel space to the output space by self.fc1 and self.fc2. + + Arguments: + + in_channels : input channels of the feature map, dtype : int + out_channels : output channels of the feature map, dtype : int + mid_channels : channels used in Spectral Convolution Layers, dtype : int + modes : Fourier modes for each spatial channel, dtype : List[int] or int (in case all number modes are to be the same for each spatial channel) + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each conv layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + + Returns: + + Fourier Neural Operator model as specified by input arguments. + """ net = FNO(in_channels, out_channels, mid_channels, modes, activation, batch_norm, in_spatial) return net \ No newline at end of file diff --git a/phi/torch/nets.py b/phi/torch/nets.py index 76e39ef78..22a97e340 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -148,6 +148,18 @@ def dense_net(in_channels: int, layers: Tuple[int, ...] or List[int], batch_norm=False, activation: str or Callable = 'ReLU') -> nn.Module: + """ + Fully-connected neural networks are available in ΦFlow via dense_net(). + Arguments: + in_channels : size of input layer, int + out_channels = size of output layer, int + layers : tuple of linear layers between input and output neurons, list or tuple + activation : activation function used within the layers, string + batch_norm : use of batch norm after each linear layer, bool + + Returns: + Dense net model as specified by input arguments + """ layers = [in_channels, *layers, out_channels] activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation net = DenseNet(layers, activation, batch_norm) @@ -189,6 +201,26 @@ def u_net(in_channels: int, activation: str or type = 'ReLU', in_spatial: tuple or int = 2, use_res_blocks: bool = False, **kwargs) -> nn.Module: + """ + ΦFlow provides a built-in U-net architecture, classically popular for Semantic Segmentation in Computer Vision, composed of downsampling and upsampling layers. + + Arguments: + + in_channels: input channels of the feature map, dtype : int + out_channels : output channels of the feature map, dtype : int + levels : number of levels of down-sampling and upsampling, dtype : int + filters : filter sizes at each down/up sampling convolutional layer, if the input is integer all conv layers have the same filter size, + dtype : int or tuple + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each conv layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + use_res_blocks : use convolutional blocks with skip connections instead of regular convolutional blocks, dtype : bool + + Returns: + + U-net model as specified by input arguments + + """ if isinstance(filters, (tuple, list)): assert len(filters) == levels, f"List of filters has length {len(filters)} but u-net has {levels} levels." else: @@ -345,6 +377,21 @@ def conv_net(in_channels: int, batch_norm: bool = False, activation: str or type = 'ReLU', in_spatial: int or tuple = 2, **kwargs) -> nn.Module: + """ + Built in Conv-Nets are also provided. Contrary to the classical convolutional neural networks, the feature map spatial size remains the same throughout the layers. Each layer of the network is essentially a convolutional block comprising of two conv layers. A filter size of 3 is used in the convolutional layers. + Arguments: + + in_channels : input channels of the feature map, dtype : int + out_channels : output channels of the feature map, dtype : int + layers : list or tuple of output channels for each intermediate layer between the input and final output channels, dtype : list or tuple + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each conv layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + + Returns: + + Conv-net model as specified by input arguments + """ if isinstance(in_spatial, int): d = in_spatial else: @@ -401,6 +448,7 @@ def forward(self, x): def get_mask(inputs, reverse_mask, data_format='NHWC'): + """ Compute mask for slicing input feature map for Invertible Nets """ shape = inputs.shape if len(shape) == 2: N = shape[-1] @@ -462,6 +510,25 @@ def res_net(in_channels: int, batch_norm: bool = False, activation: str or type = 'ReLU', in_spatial: int or tuple = 2, **kwargs) -> nn.Module: + """ + Built in Res-Nets are provided in the ΦFlow framework. Similar to the conv-net, the feature map spatial size remains the same throughout the layers. + These networks use residual blocks composed of two conv layers with a skip connection added from the input to the output feature map. + A default filter size of 3 is used in the convolutional layers. + + Arguments: + + in_channels : input channels of the feature map, dtype : int + out_channels : output channels of the feature map, dtype : int + layers : list or tuple of output channels for each intermediate layer between the input and final output channels, dtype : list or tuple + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each conv layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + + Returns: + + Res-net model as specified by input arguments + + """ if (isinstance(in_spatial, int)): d = in_spatial else: @@ -601,6 +668,29 @@ def invertible_net(in_channels: int, net: str = 'u_net', activation: str or type = 'ReLU', in_spatial: tuple or int = 2, **kwargs): + """ + Phiflow also provides invertible neural networks that are capable of inverting the output tensor back to the input tensor initially passed.\ These networks have far reaching applications in predicting input parameters of a problem given its observations.\ Invertible nets are composed of multiple concatenated coupling blocks wherein each such block consists of arbitrary neural networks. + + Currently, these arbitrary neural networks could be set to u_net(default), conv_net, res_net or dense_net blocks with in_channels = out_channels. + The architecture used is popularized by ["Real NVP"](https://arxiv.org/abs/1605.08803). + + Arguments: + + in_channels : input channels of the feature map, dtype : int + num_blocks : number of coupling blocks inside the invertible net, dtype : int + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + net : type of neural network blocks used in coupling layers, dtype : str + **kwargs : placeholder for arguments not supported by the function + + Returns: + + Invertible Net model as specified by input arguments + + Note: Currently supported values for net are 'u_net'(default), 'conv_net' and 'res_net'. + For choosing 'dense_net' as the network block in coupling layers in_spatial must be set to zero. + """ if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) @@ -714,7 +804,7 @@ def __init__(self, in_channels, out_channels, width, modes, activation, batch_no super(FNO, self).__init__() """ - The overall network. It contains 4 layers of the Fourier layer. + The overall network contains 4 layers of the ["Fourier layer"](https://github.com/zongyi-li/fourier_neural_operator). 1. Lift the input to the desire channel dimension by self.fc0 . 2. 4 layers of the integral operators u' = (W + K)(u). W defined by self.w; K defined by self.conv . @@ -790,6 +880,26 @@ def fno(in_channels: int, activation: str or type = 'ReLU', batch_norm: bool = False, in_spatial: int = 2): + """ + ["Fourier Neural Operator"](https://github.com/zongyi-li/fourier_neural_operator) network contains 4 layers of the Fourier layer. + 1. Lift the input to the desire channel dimension by self.fc0 . + 2. 4 layers of the integral operators u' = (W + K)(u). W defined by self.w; K defined by self.conv . + 3. Project from the channel space to the output space by self.fc1 and self.fc2. + + Arguments: + + in_channels : input channels of the feature map, dtype : int + out_channels : output channels of the feature map, dtype : int + mid_channels : channels used in Spectral Convolution Layers, dtype : int + modes : Fourier modes for each spatial channel, dtype : List[int] or int (in case all number modes are to be the same for each spatial channel) + activation : activation function used within the layers, dtype : string + batch_norm : use of batchnorm after each conv layer, dtype : bool + in_spatial : spatial dimensions of the input feature map, dtype : int + + Returns: + + Fourier Neural Operator model as specified by input arguments. + """ net = FNO(in_channels, out_channels, mid_channels, modes, activation, batch_norm, in_spatial) net = net.to(TORCH.get_default_device().ref) return net From 10751ee377eaa00ccd821660d3ee82fab14c8890 Mon Sep 17 00:00:00 2001 From: kbali1297 Date: Sat, 22 Oct 2022 20:09:58 +0200 Subject: [PATCH 024/170] Fixed minor typo in tf/nets.py to fix Unit Test Errors --- phi/tf/nets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phi/tf/nets.py b/phi/tf/nets.py index f83513e7d..60ca58926 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -313,7 +313,7 @@ def conv_net(in_channels: int, d = in_spatial in_spatial = len(d) activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation - x = inputs = keras.InpuΦFlowt(shape=d + (in_channels,)) + x = inputs = keras.Input(shape=d + (in_channels,)) if len(layers) < 1: layers.append(out_channels) for i in range(len(layers)): From b0bb06d8f527b3148d348cf83c4fe2a4ccba9326 Mon Sep 17 00:00:00 2001 From: Elias Djossou Date: Wed, 16 Nov 2022 16:55:26 +0100 Subject: [PATCH 025/170] [math] fix weights of laplacian --- phi/field/_field_math.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index e1fc37cd7..e5389a39f 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -101,6 +101,9 @@ def laplace(field: GridType, axes=spatial, scheme: Scheme = Scheme(2), weights: assert channel(weights).rank == 1 and channel(weights).item_names is not None, f"weights must have one channel dimension listing the laplace dims but got {shape(weights)}" assert set(channel(weights).item_names[0]) >= set(axes_names), f"the channel dim of weights must contain all laplace dims {axes_names} but only has {channel(weights).item_names}" result_components = [c * weights[ax] for c, ax in zip(result_components, axes_names)] + else: + weights = 1 + result = sum(result_components * weights) result = result.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) @@ -210,7 +213,6 @@ def f(ext: Extrapolation): @partial(jit_compile_linear, auxiliary_args="values_rhs, needed_shifts_rhs, stack_dim, staggered_output") def _lhs_for_implicit_scheme(x, values_rhs, needed_shifts_rhs, stack_dim, staggered_output=False): - from phi.math._nd import shift result = [] for dim, component in zip(x.shape.only(math.spatial).names, unstack(x, stack_dim.name)): shifted = shift(component, needed_shifts_rhs, stack_dim=None, dims=dim) From 596c748e92c3b2de71cccbfe605a14b8e0e27480 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 20 Dec 2022 17:58:57 +0100 Subject: [PATCH 026/170] [math] Refactor reduce operations The code is now contained within the reduce function itself, not in the Tensor subclasses. * Remove Tensor._tensor_reduce() This will simplify adding support for sparse matrices. --- phi/math/_functional.py | 10 -- phi/math/_ops.py | 228 ++++++++++++++++++++++++----- phi/math/_tensors.py | 121 ++------------- tests/commit/math/test__tensors.py | 10 ++ 4 files changed, 218 insertions(+), 151 deletions(-) diff --git a/phi/math/_functional.py b/phi/math/_functional.py index a66daf6ae..5aa43870a 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -1120,16 +1120,6 @@ def _op2(self, other: Tensor, else: raise ValueError(f"Unsupported operation encountered while tracing linear function: {native_function}") - def _tensor_reduce(self, - dims: Tuple[str], - dtype: type or None, - native_function: Callable, - collapsed_function: Callable = lambda inner_reduced, collapsed_dims_to_reduce: inner_reduced, - unaffected_function: Callable = lambda value: value): - if all(dim not in self._shape for dim in dims): - return unaffected_function(self) - raise NotImplementedError("Reducing linear tracers is not yet supported.") - def _natives(self) -> tuple: """ This function should only be used to determine the compatible backends, this tensor should be regarded as not available. diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 3238daa9c..e57a3ba68 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -10,7 +10,8 @@ from ._magic_ops import expand, pack_dims, flatten, unpack_dim, cast, copy_with, value_attributes from ._shape import (Shape, EMPTY_SHAPE, spatial, batch, channel, instance, merge_shapes, parse_dim_order, concat_shapes, - IncompatibleShapes, DimFilter, non_batch) + IncompatibleShapes, DimFilter, non_batch, non_channel) +from ._sparse import CompressedSparseTensor from ._tensors import Tensor, wrap, tensor, broadcastable_native_tensors, NativeTensor, TensorStack, CollapsedTensor, \ custom_op2, compatible_tensor, variable_attributes, disassemble_tree, assemble_tree, \ cached, is_scalar, Layout @@ -701,6 +702,8 @@ def range_tensor(shape: Shape): def stack_tensors(values: tuple or list, dim: Shape): + if len(values) == 1 and not dim: + return values[0] values = [wrap(v) for v in values] values = cast_same(*values) @@ -1012,32 +1015,28 @@ def unbatched_nonzero(value: Tensor): return broadcast_op(unbatched_nonzero, [value], iter_dims=value.shape.batch.names) -def _reduce(value: Tensor or list or tuple, - dim: DimFilter, - dtype: type or None, - native_function: Callable, - collapsed_function: Callable = lambda inner_reduced, collapsed_dims_to_reduce: inner_reduced, - unaffected_function: Callable = lambda value: value) -> Tensor: - """ - Args: - value: - dim: Which dimensions should be reduced - dtype: (Optional) Whether the reducing operation converts the data to a different type like bool. - native_function: - collapsed_function: handles collapsed dimensions, called as `collapsed_function(inner_reduced, collapsed_dims_to_reduce)` - unaffected_function: returns `unaffected_function(value)` if `len(dims) > 0` but none of them are part of `value` - """ - if dim in ((), [], EMPTY_SHAPE): +def reduce_(f, value, dims, require_all_dims_present=False, required_kind: type = None): + if dims in ((), [], EMPTY_SHAPE): return value else: if isinstance(value, (tuple, list)): values = [wrap(v) for v in value] value = stack_tensors(values, instance('0')) - assert dim in ('0', None), "dim must be '0' or None when passing a sequence of tensors" + assert dims in ('0', None), "dim must be '0' or None when passing a sequence of tensors" + elif isinstance(value, Layout): + if not value.shape.without(dims): # reduce all + dims = batch('_flat_layout') + values = value._as_list() + if required_kind is not None: + values = [required_kind(v) for v in values] + value = wrap(values, dims) else: value = wrap(value) - dims = value.shape.only(dim) - return value._tensor_reduce(dims.names, dtype, native_function, collapsed_function, unaffected_function) + dims = value.shape.only(dims) + if require_all_dims_present and any(d not in value.shape for d in dims): + raise ValueError(f"Cannot sum dimensions {dims} because tensor {value.shape} is missing at least one of them") + value = value._simplify() + return f(value, dims) def sum_(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: @@ -1058,9 +1057,21 @@ def sum_(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: Returns: `Tensor` without the reduced dimensions. """ - return _reduce(value, dim, float, - native_function=lambda backend, native, dim: backend.sum(native, dim), - collapsed_function=lambda inner, red_shape: inner * red_shape.volume) + return reduce_(_sum, value, dim, require_all_dims_present=True) + + +def _sum(value: Tensor, dims: Shape) -> Tensor: + if isinstance(value, NativeTensor): + result = value.default_backend.sum(value.native(value.shape), value.shape.indices(dims)) + return NativeTensor(result, value.shape.without(dims)) + elif isinstance(value, CollapsedTensor): + result = _sum(value._inner, dims.only(value._inner.shape)) * value.collapsed_dims.only(dims).volume + return expand_tensor(result, value.shape.without(dims)) + elif isinstance(value, TensorStack): + reduced_inners = [_sum(t, dims.without(value.stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: x + y, reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + else: + raise ValueError(type(value)) def prod(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: @@ -1081,9 +1092,21 @@ def prod(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: Returns: `Tensor` without the reduced dimensions. """ - return _reduce(value, dim, None, - native_function=lambda backend, native, dim: backend.prod(native, dim), - collapsed_function=lambda inner, red_shape: inner ** red_shape.volume) + return reduce_(_prod, value, dim, require_all_dims_present=True) + + +def _prod(value: Tensor, dims: Shape) -> Tensor: + if isinstance(value, NativeTensor): + result = value.default_backend.prod(value.native(value.shape), value.shape.indices(dims)) + return NativeTensor(result, value.shape.without(dims)) + elif isinstance(value, CollapsedTensor): + result = _prod(value._inner, dims.only(value._inner.shape)) ** value.collapsed_dims.only(dims).volume + return expand_tensor(result, value.shape.without(dims)) + elif isinstance(value, TensorStack): + reduced_inners = [_prod(t, dims.without(value.stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: x * y, reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + else: + raise ValueError(type(value)) def mean(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: @@ -1104,7 +1127,21 @@ def mean(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: Returns: `Tensor` without the reduced dimensions. """ - return _reduce(value, dim, float, native_function=lambda backend, native, dim: backend.mean(native, dim)) + return reduce_(_mean, value, dim) + + +def _mean(value: Tensor, dims: Shape) -> Tensor: + if isinstance(value, NativeTensor): + result = value.default_backend.mean(value.native(value.shape), value.shape.indices(dims)) + return NativeTensor(result, value.shape.without(dims)) + elif isinstance(value, CollapsedTensor): + result = _mean(value._inner, dims.only(value._inner.shape)) + return expand_tensor(result, value.shape.without(dims)) + elif isinstance(value, TensorStack): + reduced_inners = [_mean(t, dims.without(value.stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: x + y, reduced_inners) / len(reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + else: + raise ValueError(type(value)) def std(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: @@ -1127,10 +1164,12 @@ def std(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: Returns: `Tensor` without the reduced dimensions. """ - return _reduce(cached(value), dim, float, - native_function=lambda backend, native, dim: backend.std(native, dim), - collapsed_function=lambda inner, red_shape: inner, - unaffected_function=lambda value: value * 0) + return reduce_(_std, value, dim) + + +def _std(value: Tensor, dims: Shape) -> Tensor: + result = value.default_backend.std(value.native(value.shape), value.shape.indices(dims)) + return NativeTensor(result, value.shape.without(dims)) def any_(boolean_tensor: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: @@ -1151,7 +1190,21 @@ def any_(boolean_tensor: Tensor or list or tuple, dim: DimFilter = non_batch) -> Returns: `Tensor` without the reduced dimensions. """ - return _reduce(boolean_tensor, dim, bool, native_function=lambda backend, native, dim: backend.any(native, dim)) + return reduce_(_any, boolean_tensor, dim) + + +def _any(value: Tensor, dims: Shape) -> Tensor: + if isinstance(value, NativeTensor): + result = value.default_backend.any(value.native(value.shape), value.shape.indices(dims)) + return NativeTensor(result, value.shape.without(dims)) + elif isinstance(value, CollapsedTensor): + result = _any(value._inner, dims.only(value._inner.shape)) + return expand_tensor(result, value.shape.without(dims)) + elif isinstance(value, TensorStack): + reduced_inners = [_any(t, dims.without(value.stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: x | y, reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + else: + raise ValueError(type(value)) def all_(boolean_tensor: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: @@ -1172,7 +1225,21 @@ def all_(boolean_tensor: Tensor or list or tuple, dim: DimFilter = non_batch) -> Returns: `Tensor` without the reduced dimensions. """ - return _reduce(boolean_tensor, dim, bool, native_function=lambda backend, native, dim: backend.all(native, dim)) + return reduce_(_all, boolean_tensor, dim) + + +def _all(value: Tensor, dims: Shape) -> Tensor: + if isinstance(value, NativeTensor): + result = value.default_backend.all(value.native(value.shape), value.shape.indices(dims)) + return NativeTensor(result, value.shape.without(dims)) + elif isinstance(value, CollapsedTensor): + result = _all(value._inner, dims.only(value._inner.shape)) + return expand_tensor(result, value.shape.without(dims)) + elif isinstance(value, TensorStack): + reduced_inners = [_all(t, dims.without(value.stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: x & y, reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + else: + raise ValueError(type(value)) def max_(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: @@ -1193,7 +1260,21 @@ def max_(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: Returns: `Tensor` without the reduced dimensions. """ - return _reduce(value, dim, None, native_function=lambda backend, native, dim: backend.max(native, dim)) + return reduce_(_max, value, dim) + + +def _max(value: Tensor, dims: Shape) -> Tensor: + if isinstance(value, NativeTensor): + result = value.default_backend.max(value.native(value.shape), value.shape.indices(dims)) + return NativeTensor(result, value.shape.without(dims)) + elif isinstance(value, CollapsedTensor): + result = _max(value._inner, dims.only(value._inner.shape)) + return expand_tensor(result, value.shape.without(dims)) + elif isinstance(value, TensorStack): + reduced_inners = [_max(t, dims.without(value.stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: maximum(x, y), reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + else: + raise ValueError(type(value)) def min_(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: @@ -1214,7 +1295,21 @@ def min_(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: Returns: `Tensor` without the reduced dimensions. """ - return _reduce(value, dim, None, native_function=lambda backend, native, dim: backend.min(native, dim)) + return reduce_(_min, value, dim) + + +def _min(value: Tensor, dims: Shape) -> Tensor: + if isinstance(value, NativeTensor): + result = value.default_backend.min(value.native(value.shape), value.shape.indices(dims)) + return NativeTensor(result, value.shape.without(dims)) + elif isinstance(value, CollapsedTensor): + result = _min(value._inner, dims.only(value._inner.shape)) + return expand_tensor(result, value.shape.without(dims)) + elif isinstance(value, TensorStack): + reduced_inners = [_min(t, dims.without(value.stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: minimum(x, y), reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + else: + raise ValueError(type(value)) def finite_min(value, dim: DimFilter = non_batch, default: complex or float = float('NaN')): @@ -2176,3 +2271,66 @@ def stop_gradient(x): return assemble_tree(nest, new_values) else: return wrap(choose_backend(x).stop_gradient(x)) + + +def pairwise_distances(positions: Tensor, max_distance: float or Tensor = None, others_dims=instance('others'), format='dense') -> Tensor: + """ + + + + Args: + positions: `Tensor`. + Channel dimensions are interpreted as position components. + Instance and spatial dimensions list nodes. + max_distance: Scalar or `Tensor` specifying a max_radius for each point separately. + Can contain additional batch dimensions but spatial/instance dimensions must match `positions` if present. + If not specified, uses an infinite cutoff radius, i.e. all points will be considered neighbors. + others_dims: These dimensions will be added to the result to list the neighbours of each point. + If `positions` contains multiple spatial/instance dimensions, it is recommended to specify a neighbor dim for each of them. + format: + One of `'dense', 'csr'` + + Returns: + `Tensor` + """ + if format == 'dense': + # if not count_self: + # warnings.warn(f"count_self has no effect when using format '{format}'", SyntaxWarning, stacklevel=2) + dx = positions - unpack_dim(pack_dims(positions, non_batch(positions).non_channel, instance('_tmp')), '_tmp', others_dims) + if max_distance is not None: + neighbors = dx ** 2 <= max_distance ** 2 + dx = where(neighbors, dx, 0) + return dx + else: # sparse + backend = choose_backend_t(positions, max_distance) + batch_shape = batch(positions) & batch(max_distance) + pos_i_shape = non_batch(positions).non_channel + native_positions = reshaped_native(positions, [batch_shape, pos_i_shape, channel(positions)], force_expand=True) + if isinstance(max_distance, Tensor): + if max_distance.shape: + rad_i_shape = non_batch(max_distance).non_channel + if rad_i_shape: # different values for each particle + assert rad_i_shape == pos_i_shape, f"spatial/instance dimensions of max_radius {rad_i_shape} must match positions {pos_i_shape} if present." + max_distance = reshaped_native(max_distance, [batch_shape, rad_i_shape], force_expand=True) + else: + max_distance = reshaped_native(max_distance, [batch_shape], force_expand=True) + else: + max_distance = max_distance.native() + if not others_dims.well_defined: + assert others_dims.rank == 1, f"others_dims sizes must be specified when passing more then one dimension but got {others_dims}" + others_dims = others_dims.with_size(pos_i_shape.volume) + sparse_natives = backend.pairwise_distances(native_positions, max_distance, format) + tensors = [] + if format == 'csr': + for indices, pointers, values in sparse_natives: + indices = wrap(indices, instance('nnz')) + pointers = wrap(pointers, instance('pointers')) + values = wrap(values, instance('nnz'), channel(positions)) + tensors.append(CompressedSparseTensor(indices, pointers, others_dims, values, concat_shapes(pos_i_shape, others_dims))) + elif format == 'coo': + raise NotImplementedError + elif format == 'csc': + raise NotImplementedError + else: + raise ValueError(format) + return stack_tensors(tensors, batch_shape) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index 23061e280..f0e2bc210 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -722,15 +722,8 @@ def _expand(self): warnings.warn("Tensor._expand() is deprecated, use cached(Tensor) instead.", DeprecationWarning) raise NotImplementedError(self.__class__) - def _tensor_reduce(self, - dims: Tuple[str], - dtype: type or None, - native_function: Callable, - collapsed_function: Callable = lambda inner_reduced, collapsed_dims_to_reduce: inner_reduced, - unaffected_function: Callable = lambda value: value): - raise NotImplementedError(self.__class__) - def _simplify(self): + """ Does not cache this value but if it is already cached, returns the cached version. """ return self @@ -1010,46 +1003,13 @@ def _op1(self, native_function): @staticmethod def _recursive_op1(obj, shape: Shape, native_function): - if shape: - if isinstance(obj, (tuple, list)): - return type(obj)([Layout._recursive_op1(i, shape[1:]) for i in obj]) - - def _tensor_reduce(self, - dims: Tuple[str], - dtype: type or None, - native_function: Callable, - collapsed_function: Callable = lambda inner_reduced, collapsed_dims_to_reduce: inner_reduced, - unaffected_function: Callable = lambda value: value): - if all(dim not in self._shape for dim in dims): - return unaffected_function(self) - if self._shape[0].name in dims and len(dims) == 1: - if dtype is not None: - values = [dtype(i) for i in self._as_list()] - result = native_function(choose_backend(*values), self._obj, 0) - else: - result = native_function(choose_backend(self._as_list()), self._obj, 0) - return wrap(result) - if not self._shape.without(dims): - return self.__flatten__(batch('_flat'), flatten_batch=True)._tensor_reduce(('_flat',), dtype, native_function, collapsed_function, unaffected_function) - else: - raise NotImplementedError(f"Partial Layout reduction not yet supported. Shape={self._shape}, reduce={dims}") - # # --- inner reduce --- - # inner_axes = [dim for dim in dims if dim != self.stack_dim.name] - # red_inners = [t._tensor_reduce(inner_axes, dtype, native_function, collapsed_function, unaffected_function) for t in - # self._tensors] - # # --- outer reduce --- - # if self.stack_dim.name in dims: - # if any([t._is_tracer for t in red_inners]): - # return sum(red_inners[1:], red_inners[0]) # TODO this may not always be the sum + raise NotImplementedError + # if shape: + # if isinstance(obj, (tuple, list)): + # return type(obj)([Layout._recursive_op1(i, shape[1:], native_function) for i in obj]) # else: - # inner_order = red_inners[0].shape.names - # natives = [t.native(inner_order) for t in red_inners] - # backend = choose_backend(*natives) - # result = native_function(backend, backend.stack(natives), - # dim=0) # TODO not necessary if tensors are CollapsedTensors - # return NativeTensor(result, red_inners[0].shape) # else: - # return TensorStack(red_inners, self.stack_dim) + # return native_function(obj) @staticmethod def _recursive_cast(obj, shape: Shape, dtype: DType): @@ -1178,19 +1138,6 @@ def _natives(self) -> tuple: def _expand(self): pass - def _tensor_reduce(self, - dims: Tuple[str], - dtype: type or None, - native_function: Callable, - collapsed_function: Callable = lambda inner_reduced, collapsed_dims_to_reduce: inner_reduced, - unaffected_function: Callable = lambda value: value): - if all(dim not in self._shape for dim in dims): - return unaffected_function(self) - dims = [dim for dim in dims if dim in self.shape] - backend = choose_backend(self._native) - result = native_function(backend, self._native, dim=self._shape.indices(dims)) - return NativeTensor(result, self._shape.without(dims)) - class CollapsedTensor(Tensor): # package-private """ @@ -1219,6 +1166,10 @@ def __init__(self, tensor: Tensor, shape: Shape): self._shape = shape self._cached = None # NativeTensor. Once cached, use only _cached + @property + def collapsed_dims(self): + return self._shape.without(self._inner.shape) + def _cache(self): if self._cached is None: if self._inner._is_tracer: @@ -1353,26 +1304,6 @@ def _with_natives_replaced(self, natives: list): def _expand(self): self._cache() - # from phi.math import all_available - # if not all_available(self._cached): - # raise AssertionError("Cannot cache a Tensor while it is being traced.") - - def _tensor_reduce(self, - dims: Tuple[str], - dtype: type or None, - native_function: Callable, - collapsed_function: Callable = lambda inner_reduced, collapsed_dims_to_reduce: inner_reduced, - unaffected_function: Callable = lambda value: value): - if self.is_cached: - return self._cached._tensor_reduce(dims, dtype, native_function, collapsed_function, unaffected_function) - if all(dim not in self._shape for dim in dims): - return unaffected_function(self) - inner_dims = [dim for dim in dims if dim in self._inner.shape] - inner_reduce = self._inner._tensor_reduce(tuple(inner_dims), dtype, native_function, collapsed_function, unaffected_function) - collapsed_dims = self._shape.without(self._inner.shape) - final_shape = self._shape.without(dims) - total_reduce = collapsed_function(inner_reduce, collapsed_dims.only(dims)) - return CollapsedTensor(total_reduce, final_shape) class TensorStack(Tensor): @@ -1548,38 +1479,16 @@ def _expand(self): t._expand() self._cache() + @property + def is_cached(self): + return self._cached is not None + def _simplify(self): - if self._cached is not None: + if self.is_cached: return self._cached else: return self - def _tensor_reduce(self, - dims: Tuple[str], - dtype: type or None, - native_function: Callable, - collapsed_function: Callable = lambda inner_reduced, collapsed_dims_to_reduce: inner_reduced, - unaffected_function: Callable = lambda value: value): - if all(dim not in self._shape for dim in dims): - return unaffected_function(self) - if self._cached is not None: - return self._cached._tensor_reduce(dims, dtype, native_function, collapsed_function, unaffected_function) - # --- inner reduce --- - inner_axes = [dim for dim in dims if dim != self.stack_dim.name] - red_inners = [t._tensor_reduce(inner_axes, dtype, native_function, collapsed_function, unaffected_function) for t in self._tensors] - # --- outer reduce --- - if self.stack_dim.name in dims: - if any([t._is_tracer for t in red_inners]): - return sum(red_inners[1:], red_inners[0]) # TODO this may not always be the sum - else: - inner_order = red_inners[0].shape.names - natives = [t.native(inner_order) for t in red_inners] - backend = choose_backend(*natives) - result = native_function(backend, backend.stack(natives), dim=0) # TODO not necessary if tensors are CollapsedTensors - return NativeTensor(result, red_inners[0].shape) - else: - return TensorStack(red_inners, self.stack_dim) - def tensor(data: Tensor or Shape or tuple or list or numbers.Number, *shape: Shape, diff --git a/tests/commit/math/test__tensors.py b/tests/commit/math/test__tensors.py index d4f73a7d5..73f2142c5 100644 --- a/tests/commit/math/test__tensors.py +++ b/tests/commit/math/test__tensors.py @@ -438,6 +438,16 @@ def test_reduction_properties(self): self.assertEqual(False, t.all) self.assertEqual(True, t.any) + def test_nested_reduce(self): + t = math.expand(stack([math.ones(spatial(x=4, y=3)), -math.ones(spatial(x=4, y=3))], channel(vector='x,y')), batch(b=10)) + self.assertEqual(0, t.mean) + self.assertEqual(1, t.std) + self.assertEqual(-1, t.min) + self.assertEqual(1, t.max) + self.assertEqual(0, t.sum) + self.assertEqual(True, t.all) + self.assertEqual(True, t.any) + def test_iter_dim(self): slices = tuple(math.zeros(channel(vector='x,y')).vector) self.assertEqual(2, len(slices)) From e7d89c25cf420367bbc7fb3dc5cc72e1b1b37b02 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 21 Dec 2022 12:12:54 +0100 Subject: [PATCH 027/170] [math] Initial support for CSR matrices --- phi/math/__init__.py | 4 +- phi/math/_ops.py | 16 ++- phi/math/_sparse.py | 180 ++++++++++++++++++++++++++++++ phi/math/backend/_backend.py | 33 ++++++ tests/commit/math/test__sparse.py | 14 +++ 5 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 phi/math/_sparse.py create mode 100644 tests/commit/math/test__sparse.py diff --git a/phi/math/__init__.py b/phi/math/__init__.py index ea19cd157..c5effd1e9 100644 --- a/phi/math/__init__.py +++ b/phi/math/__init__.py @@ -24,6 +24,7 @@ ) from ._magic_ops import unstack, stack, concat, expand, rename_dims, pack_dims, unpack_dim, unpack_dim as unpack_dims, flatten, copy_with from ._tensors import wrap, tensor, layout, Tensor, Dict, to_dict, from_dict, is_scalar +from ._sparse import dense, get_sparsity from .extrapolation import Extrapolation from ._ops import ( choose_backend_t as choose_backend, all_available, convert, seed, @@ -50,7 +51,8 @@ fft, ifft, convolve, cumulative_sum, dtype, cast, close, assert_close, - stop_gradient + stop_gradient, + pairwise_distances, ) from ._nd import ( shift, diff --git a/phi/math/_ops.py b/phi/math/_ops.py index e57a3ba68..8f417c422 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -1070,6 +1070,20 @@ def _sum(value: Tensor, dims: Shape) -> Tensor: elif isinstance(value, TensorStack): reduced_inners = [_sum(t, dims.without(value.stack_dim)) for t in value._tensors] return functools.reduce(lambda x, y: x + y, reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + elif isinstance(value, CompressedSparseTensor): + if value.sparse_dims in dims: # reduce all sparse dims + return _sum(value._values, dims.without(value.sparse_dims) & instance(value._values)) + value_only_dims = dims.only(value._values.shape).without(value.sparsity_batch) + value = value._with_values(_sum(value._values, value_only_dims)) + dims = dims.without(value_only_dims) + if value._uncompressed_dims in dims and value._compressed_dims.only(dims).is_empty: + # We can ignore the pointers + result_base = zeros(value.shape.without(value._uncompressed_dims)) + return scatter(result_base, value._indices, value._values, mode='add', outside_handling='undefined') + elif value.sparse_dims.only(dims): # reduce some sparse dims + raise NotImplementedError(f"only sum along non-pointer dimensions supported at the moment, i.e. sum along {value.shape.without(value._compressed_dims)}") + return value + # first sum value dims that are not part of indices else: raise ValueError(type(value)) @@ -2326,7 +2340,7 @@ def pairwise_distances(positions: Tensor, max_distance: float or Tensor = None, indices = wrap(indices, instance('nnz')) pointers = wrap(pointers, instance('pointers')) values = wrap(values, instance('nnz'), channel(positions)) - tensors.append(CompressedSparseTensor(indices, pointers, others_dims, values, concat_shapes(pos_i_shape, others_dims))) + tensors.append(CompressedSparseTensor(indices, pointers, values, others_dims, pos_i_shape)) elif format == 'coo': raise NotImplementedError elif format == 'csc': diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py new file mode 100644 index 000000000..ee3e4fc18 --- /dev/null +++ b/phi/math/_sparse.py @@ -0,0 +1,180 @@ +import warnings +from numbers import Number +from typing import List, Tuple, Callable + +from .backend._dtype import DType +from ._shape import Shape, non_batch, merge_shapes, instance, batch, non_instance, shape, channel, spatial +from ._tensors import Tensor, TensorStack, CollapsedTensor, NativeTensor, cached + + +class SparseCoordinateTensor(Tensor): + + def __init__(self, indices: Tensor, values: Tensor, dense_shape: Shape, can_contain_double_entries: bool, indices_sorted: bool): + assert instance(indices), "indices must have an instance dimension" + assert 'vector' in indices.shape, "indices must have a vector dimension" + assert indices.vector.item_names is not None and len(indices.vector.item_names) == non_batch(values).non_channel.rank, "The 'vector' dimension of indices must list the dense dimensions as item names" + self._shape = merge_shapes(dense_shape, batch(indices), non_instance(values)) + self._indices = indices + self._values = values + self._can_contain_double_entries = can_contain_double_entries + self._indices_sorted = indices_sorted + + @property + def shape(self) -> Shape: + return self._shape + + @property + def dtype(self) -> DType: + return self._values.dtype + + +class CompressedSparseTensor(Tensor): + + def __init__(self, indices: Tensor, pointers: Tensor, values: Tensor, uncompressed_dims: Shape, compressed_dims: Shape): + """ + + Args: + indices: indices must be sorted in ascending order by compressed_dim and other sparse dims. + Must have one or multiple instance dimensions and can have any number of batch dimensions. + No spatial and channel dimensions allowed. + pointers: + values: + compressed_dims: Sparse dimensions with compressed pointer representation. + Only one pointer array is used per matrix, i.e. the dimensions are packed internally. + These dimensions are indexed by `pointers`. + uncompressed_dims: Sparse dimensions with full index storage. + These dimensions are indexed by `indices`. + """ + assert instance(indices), "indices must have an instance dimension" + assert instance(pointers), "pointers must have an instance dimension" + assert instance(values) == instance(indices), "Instance dimensions of values and indices must match exactly" + assert not channel(indices) and not spatial(indices), f"channel and spatial dimensions not allowed on indices but got {shape(indices)}" + assert not channel(pointers) and not spatial(pointers), f"channel and spatial dimensions not allowed on pointers but got {shape(pointers)}" + self._shape = merge_shapes(compressed_dims, uncompressed_dims, batch(indices), batch(pointers), non_instance(values)) + self._indices = indices + self._pointers = pointers + self._values = values + self._uncompressed_dims = uncompressed_dims + self._compressed_dims = compressed_dims + + @property + def shape(self) -> Shape: + return self._shape + + @property + def sparse_dims(self): + return self._compressed_dims & self._uncompressed_dims + + @property + def sparsity_batch(self): + return batch(self._indices) & batch(self._pointers) + + @property + def dtype(self) -> DType: + return self._values.dtype + + @property + def _is_tracer(self) -> bool: + return self._values._is_tracer or self._indices._is_tracer or self._pointers._is_tracer + + def _natives(self) -> tuple: + return self._values._natives() + self._indices._natives() + self._pointers._natives() + + def _getitem(self, selection: dict) -> 'Tensor': + if self._compressed_dims.only(tuple(selection)): + raise NotImplementedError + if self._uncompressed_dims.only(tuple(selection)): + raise NotImplementedError + batch_selection = {dim: selection[dim] for dim in self._shape.only(tuple(selection)).names} + return CompressedSparseTensor(self._indices[batch_selection], self._pointers[batch_selection], self._values[batch_selection], self._uncompressed_dims, self._compressed_dims) + + def _op1(self, native_function): + return self._with_values(self._values._op1(native_function)) + + def _op2(self, other, operator: Callable, native_function: Callable, op_name: str = 'unknown', op_symbol: str = '?') -> 'Tensor': + other_shape = shape(other) + affects_only_values = self.sparse_dims not in other_shape and non_instance(self._indices).only(other_shape).is_empty + if affects_only_values: + return self._with_values(operator(self._values, other)) + # if op_name == 'pow': + # if affects_only_values: + # return self._with_values(self._values ** other) + # if op_name == 'maximum': + + raise NotImplementedError + + def _with_values(self, new_values: Tensor): + return CompressedSparseTensor(self._indices, self._pointers, new_values, self._uncompressed_dims, self._compressed_dims) + + +def dense(x: Tensor, order: str or tuple or list or Shape): + if isinstance(x, SparseCoordinateTensor): + native = x.default_backend.coo_to_dense(x.native()) + # fallback: scatter + grid = x.default_backend.zeros([], dtype=x.dtype) + native = x.default_backend.scatter(grid, x.indices, x.values, 'update') + raise NotImplementedError + elif isinstance(x, CompressedSparseTensor): + raise NotImplementedError + else: + assert isinstance(x, Tensor), f"must be a Tensor but got {type(x).__name__}" + return x + + +def get_sparsity(x: Tensor): + """ + Fraction of values currently stored on disk for the given `Tensor` `x`. + For sparse tensors, this is `nnz / shape`. + + This is a lower limit on the number of values that will need to be processed for operations involving `x`. + The actual number is often higher since many operations require data be laid out in a certain format. + In these cases, missing values, such as zeros, are filled in before the operation. + + The following operations may return tensors whose values are only partially stored: + + * `phi.math.expand()` + * `phi.math.pairwise_distance()` with `max_distance` set. + * Tracers used in `phi.math.jit_compile_linear()` + * Stacking any of the above. + + Args: + x: `Tensor` + + Returns: + The number of values that are actually stored on disk. + This does not include additional information, such as position information / indices. + For sparse matrices, this is equal to the number of nonzero values. + """ + # ToDo this does not give the correct result for linear tracers since the matrix shape is not taken into account + return sum([t.shape.volume for t in stored_values(x)]) / x.shape.volume + + +def stored_values(x: Tensor) -> List[Tensor]: + """ + Returns the values currently stored on disk for the given `Tensor` `x`. + + Some operations may require non-stored values to be explicitly stored, or they may be filled in for performance reasons. + + Args: + x: `Tensor` + + Returns: + List of `Tensor`s representing all values stored to represent `x`. + """ + if isinstance(x, NativeTensor): + return [x] + elif isinstance(x, CollapsedTensor): + return [cached(x)] if x.is_cached else stored_values(x._inner) + elif isinstance(x, TensorStack): + return [cached(x)] if x.is_cached else sum([stored_values(t) for t in x._tensors], []) + elif isinstance(x, CompressedSparseTensor): + return [x._values] + elif isinstance(x, SparseCoordinateTensor): + if x._can_contain_double_entries: + warnings.warn(f"Sparsity of sparse tensor {x.shape} is unknown as multiple values can reference the same position.") + return [x._values] + else: + from phi.math._functional import ShiftLinTracer + if isinstance(x, ShiftLinTracer): + return sum([stored_values(v) for v in x.val.values()], []) + raise ValueError(x) diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 823c498a4..768cd294e 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -864,6 +864,39 @@ def coordinates(self, tensor): """ raise NotImplementedError(self) + def pairwise_distances(self, positions, max_radius, format: str) -> list: + """ + + Args: + positions: Point locations of shape (batch, instances, vector) + max_radius: Scalar or (batch,) or (batch, instances) + format: 'csr', 'coo' or 'csc' + + Returns: + Sequence of batch_size sparse distance matrices + """ + from sklearn import neighbors + batch_size, point_count, _vec_count = self.staticshape(positions) + positions_np_batched = self.numpy(positions) + result = [] + for i in range(batch_size): + tree = neighbors.KDTree(positions_np_batched[i]) + radius = float(max_radius) if len(self.staticshape(max_radius)) == 0 else max_radius[i] + nested_neighbors = tree.query_radius(positions_np_batched[i], r=radius) # ndarray[ndarray] + if format == 'csr': + column_indices = numpy.concatenate(nested_neighbors) # flattened_neighbors + neighbor_counts = [len(nlist) for nlist in nested_neighbors] + row_pointers = numpy.concatenate([[0], numpy.cumsum(neighbor_counts)]) + pos_neighbors = positions[i, column_indices] + pos_self = numpy.repeat(positions[i], neighbor_counts, axis=0) + values = pos_neighbors - pos_self + result.append((column_indices, row_pointers, values)) + # sparse_matrix = self.csr_matrix(column_indices, row_pointers, values, (point_count, point_count)) + # sparse_matrix.eliminate_zeros() # setdiag(0) keeps zero entries + else: + raise NotImplementedError(format) + return result + def minimize(self, method: str, f, x0, atol, max_iter, trj: bool): if method == 'GD': return self._minimize_gradient_descent(f, x0, atol, max_iter, trj) diff --git a/tests/commit/math/test__sparse.py b/tests/commit/math/test__sparse.py new file mode 100644 index 000000000..b86cf0b1b --- /dev/null +++ b/tests/commit/math/test__sparse.py @@ -0,0 +1,14 @@ +from unittest import TestCase + +from phi.math import batch, get_sparsity, expand, wrap, stack, zeros, channel, spatial, ones, instance +from phi.math._sparse import SparseCoordinateTensor, CompressedSparseTensor + + +class TestSprase(TestCase): + + def test_sparsity(self): + self.assertEqual(1, get_sparsity(wrap(1))) + self.assertEqual(0.25, get_sparsity(expand(1., batch(b=4)))) + self.assertEqual(0.25, get_sparsity(stack([zeros(batch(b=4))] * 3, channel('vector')))) + self.assertEqual(0.3, get_sparsity(SparseCoordinateTensor(ones(instance(nnz=3), channel(vector='x')), ones(instance(nnz=3)), spatial(x=10), True, False))) + self.assertEqual(0.03, get_sparsity(CompressedSparseTensor(ones(instance(nnz=3), channel(vector='x')), ones(instance(y_pointers=4)), spatial(y=10), ones(instance(nnz=3)), spatial(x=10, y=10)))) From 0eaf0c8d217fdf75a78465f2e7d041adf6808ad7 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Thu, 22 Dec 2022 22:13:08 +0100 Subject: [PATCH 028/170] [math] CSR-dense multiplication for NumPy --- phi/math/_ops.py | 16 ++++++++++++---- phi/math/_sparse.py | 25 ++++++++++++++++++++++--- phi/math/backend/_backend.py | 21 +++++++++++++++++++++ phi/math/backend/_numpy_backend.py | 11 +++++++++++ 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 0a35890e5..ea1575493 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -11,7 +11,7 @@ from ._shape import (Shape, EMPTY_SHAPE, spatial, batch, channel, instance, merge_shapes, parse_dim_order, concat_shapes, IncompatibleShapes, DimFilter, non_batch, non_channel) -from ._sparse import CompressedSparseTensor +from ._sparse import CompressedSparseTensor, dot_compressed_dense from ._tensors import Tensor, wrap, tensor, broadcastable_native_tensors, NativeTensor, TensorStack, CollapsedTensor, \ custom_op2, compatible_tensor, variable_attributes, disassemble_tree, assemble_tree, \ cached, is_scalar, Layout @@ -1083,12 +1083,12 @@ def _sum(value: Tensor, dims: Shape) -> Tensor: value_only_dims = dims.only(value._values.shape).without(value.sparsity_batch) value = value._with_values(_sum(value._values, value_only_dims)) dims = dims.without(value_only_dims) - if value._uncompressed_dims in dims and value._compressed_dims.only(dims).is_empty: + if value._compressed_dims in dims and value._uncompressed_dims.only(dims).is_empty: # We can ignore the pointers - result_base = zeros(value.shape.without(value._uncompressed_dims)) + result_base = zeros(value.shape.without(value._compressed_dims)) return scatter(result_base, value._indices, value._values, mode='add', outside_handling='undefined') elif value.sparse_dims.only(dims): # reduce some sparse dims - raise NotImplementedError(f"only sum along non-pointer dimensions supported at the moment, i.e. sum along {value.shape.without(value._compressed_dims)}") + return dot(value, dims, ones(dims), dims) # this is what SciPy does in both axes, actually. return value # first sum value dims that are not part of indices else: @@ -1533,6 +1533,14 @@ def dot(x: Tensor, assert x_dims.volume == 1, f"Cannot compute dot product between dimensions {x_dims} on {x.shape} and {y_dims} on {y.shape}" x = x[{d: 0 for d in x_dims.names}] return x * y + if isinstance(x, CompressedSparseTensor): + if isinstance(y, CompressedSparseTensor): + raise NotImplementedError + return dot_compressed_dense(x, x_dims, y, y_dims) + elif isinstance(y, CompressedSparseTensor): + if isinstance(x, CompressedSparseTensor): + raise NotImplementedError + return dot_compressed_dense(y, y_dims, x, x_dims) x_native = x.native(x.shape) y_native = y.native(y.shape) backend = choose_backend(x_native, y_native) diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index ee3e4fc18..2066bf379 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -1,10 +1,10 @@ import warnings -from numbers import Number -from typing import List, Tuple, Callable +from typing import List, Callable -from .backend._dtype import DType from ._shape import Shape, non_batch, merge_shapes, instance, batch, non_instance, shape, channel, spatial from ._tensors import Tensor, TensorStack, CollapsedTensor, NativeTensor, cached +from .backend import choose_backend, Backend +from .backend._dtype import DType class SparseCoordinateTensor(Tensor): @@ -178,3 +178,22 @@ def stored_values(x: Tensor) -> List[Tensor]: if isinstance(x, ShiftLinTracer): return sum([stored_values(v) for v in x.val.values()], []) raise ValueError(x) + + +def dot_compressed_dense(compressed: CompressedSparseTensor, cdims: Shape, dense: Tensor, ddims: Shape): + from phi.math import reshaped_native, reshaped_tensor + backend = choose_backend(*compressed._natives() + dense._natives()) + if compressed._uncompressed_dims in cdims: # proper matrix-vector multiplication + ind_batch = batch(compressed._indices & compressed._pointers) + channels = non_instance(compressed._values).without(ind_batch) + rhs_channels = shape(dense).without(ddims).without(channels) + native_indices = reshaped_native(compressed._indices, [ind_batch, instance], force_expand=True) + native_pointers = reshaped_native(compressed._pointers, [ind_batch, instance], force_expand=True) + native_values = reshaped_native(compressed._values, [ind_batch, instance, channels]) + native_shape = compressed._uncompressed_dims.volume, compressed._compressed_dims.volume + dense_native = reshaped_native(dense, [ind_batch, channels, ddims, rhs_channels], force_expand=True) + result_native = backend.mul_csr_dense(native_indices, native_pointers, native_values, native_shape, dense_native) + result = reshaped_tensor(result_native, [ind_batch, channels, instance(compressed._compressed_dims), rhs_channels]) + return result + else: # transposed matrix vector multiplication. This is inefficient + raise NotImplementedError diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 768cd294e..c1769ffd4 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -830,6 +830,27 @@ def csr_matrix(self, column_indices, row_pointers, values, shape: tuple): """ raise NotImplementedError(self) + def mul_csr_dense(self, column_indices, row_pointers, matrix_values, shape: tuple, rhs): + """ + Create a sparse matrix in compressed sparse row (CSR) format. + + Optional feature. + + See Also: + `Backend.sparse_coo_tensor()`, `Backend.csc_matrix()`. + + Args: + column_indices: (batch, nnz) + row_pointers: (batch, rows + 1) + matrix_values: (batch, nnz, channels) + shape: Shape of the full matrix (cols, rows) + rhs: (batch, channels, rhs_rows=cols, rhs_cols) + + Returns: + (batch, channels, rhs_rows=cols, rhs_cols) + """ + raise NotImplementedError(self) + def csc_matrix(self, column_pointers, row_indices, values, shape: tuple): """ Create a sparse matrix in compressed sparse column (CSC) format. diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index a5d87202a..813be59af 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -344,6 +344,17 @@ def csr_matrix(self, column_indices, row_pointers, values, shape: tuple): def csc_matrix(self, column_pointers, row_indices, values, shape: tuple): return scipy.sparse.csc_matrix((values, row_indices, column_pointers), shape=shape) + def mul_csr_dense(self, column_indices, row_pointers, matrix_values, shape: tuple, rhs): + batch_size, nnz, channel_count = self.staticshape(matrix_values) + result = [] + for b in range(batch_size): + b_result = [] + for c in range(channel_count): + mat = scipy.sparse.csr_matrix((matrix_values[b, :, c], column_indices[b], row_pointers[b]), shape=shape) + b_result.append(mat * rhs[b, c]) + result.append(np.stack(b_result)) + return np.stack(result) + def coordinates(self, tensor): assert scipy.sparse.issparse(tensor) coo = tensor.tocoo() From 0fae26e5c235173b73fea21aee6f3db14f258743 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 28 Dec 2022 12:16:43 +0100 Subject: [PATCH 029/170] [math] Add Backend.gather() --- phi/jax/_jax_backend.py | 4 ++++ phi/math/backend/_backend.py | 14 ++++++++++++++ phi/math/backend/_numpy_backend.py | 4 ++++ phi/tf/_tf_backend.py | 4 ++++ phi/torch/_torch_backend.py | 4 ++++ tests/commit/math/backend/test__backend.py | 8 ++++++++ 6 files changed, 38 insertions(+) diff --git a/phi/jax/_jax_backend.py b/phi/jax/_jax_backend.py index e90512dc4..95ca34599 100644 --- a/phi/jax/_jax_backend.py +++ b/phi/jax/_jax_backend.py @@ -345,6 +345,10 @@ def cast(self, x, dtype: DType): else: return jnp.array(x, to_numpy_dtype(dtype)) + def gather(self, values, indices, axis: int): + slices = [indices if i == axis else slice(None) for i in range(self.ndims(values))] + return values[tuple(slices)] + def batched_gather_nd(self, values, indices): values = self.as_tensor(values) indices = self.as_tensor(indices) diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index c1769ffd4..3b2b87853 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -644,6 +644,20 @@ def to_int64(self, x): def to_complex(self, x): return self.cast(x, DType(complex, max(64, self.precision * 2))) + def gather(self, values, indices, axis: int): + """ + Gathers values from the tensor `values` at locations `indices`. + + Args: + values: tensor + indices: 1D tensor + axis: Axis along which to gather slices + + Returns: + tensor, with size along `axis` being the length of `indices` + """ + raise NotImplementedError(self) + def batched_gather_nd(self, values, indices): """ Gathers values from the tensor `values` at locations `indices`. diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index 813be59af..f5163edba 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -243,6 +243,10 @@ def cast(self, x, dtype: DType): else: return np.array(x, to_numpy_dtype(dtype)) + def gather(self, values, indices, axis: int): + slices = [indices if i == axis else slice(None) for i in range(self.ndims(values))] + return values[tuple(slices)] + def batched_gather_nd(self, values, indices): assert indices.shape[-1] == self.ndims(values) - 2 batch_size = combined_dim(values.shape[0], indices.shape[0]) diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index f7dc7498c..b003944a3 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -387,6 +387,10 @@ def staticshape(self, tensor): else: return np.shape(tensor) + def gather(self, values, indices, axis: int): + with self._device_for(values, indices): + return tf.gather(values, indices, axis=axis) + def batched_gather_nd(self, values, indices): with self._device_for(values, indices): values_shape = self.staticshape(values) diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index 9193dda3c..8dff14d79 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -496,6 +496,10 @@ def staticshape(self, tensor): else: return NUMPY.staticshape(tensor) + def gather(self, values, indices, axis: int): + slices = [indices if i == axis else slice(None) for i in range(self.ndims(values))] + return values[tuple(slices)] + def batched_gather_nd(self, values, indices): values = self.as_tensor(values) indices = self.as_tensor(indices).long() diff --git a/tests/commit/math/backend/test__backend.py b/tests/commit/math/backend/test__backend.py index a3ab66eb7..d2913efe4 100644 --- a/tests/commit/math/backend/test__backend.py +++ b/tests/commit/math/backend/test__backend.py @@ -34,3 +34,11 @@ def test_allocate_on_device(self): t_ = backend.allocate_on_device(t, backend.get_default_device()) assert backend.get_device(t_) == backend.get_default_device() + def test_gather(self): + for backend in BACKENDS: + t = backend.zeros((4, 3, 2)) + indices = [0, 1] + result = backend.gather(t, indices, axis=0) + self.assertEqual((2, 3, 2), backend.staticshape(result)) + + From f13c101e75ae9bb3e27d5b92b98b3fd989c1ce31 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 28 Dec 2022 14:13:56 +0100 Subject: [PATCH 030/170] [math] Add Backend.repeat() --- phi/jax/_jax_backend.py | 3 +++ phi/math/backend/_backend.py | 17 +++++++++++-- phi/math/backend/_numpy_backend.py | 1 + phi/tf/_tf_backend.py | 5 ++++ phi/torch/_torch_backend.py | 5 ++++ tests/commit/math/test__sparse.py | 39 ++++++++++++++++++++++++------ 6 files changed, 61 insertions(+), 9 deletions(-) diff --git a/phi/jax/_jax_backend.py b/phi/jax/_jax_backend.py index 95ca34599..a00d13891 100644 --- a/phi/jax/_jax_backend.py +++ b/phi/jax/_jax_backend.py @@ -361,6 +361,9 @@ def batched_gather_nd(self, values, indices): results.append(b_values[b_indices]) return jnp.stack(results) + def repeat(self, x, repeats, axis: int): + return jnp.repeat(x, self.as_tensor(repeats), axis) + def std(self, x, axis=None, keepdims=False): return jnp.std(x, axis, keepdims=keepdims) diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 3b2b87853..604f5fb9c 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -792,7 +792,7 @@ def dtype(self, array) -> DType: def tile(self, value, multiples): """ - Repeats the tensor along each axis the number of times given by multiples. + Repeats the full tensor along each axis the number of times given by multiples. If `multiples` has more dimensions than `value`, these dimensions are added to `value` as outer dimensions. Args: @@ -800,8 +800,21 @@ def tile(self, value, multiples): multiples: tuple or list of integers Returns: - tile tensor + tiled tensor + """ + raise NotImplementedError(self) + + def repeat(self, x, repeats, axis: int): + """ + Repeats the elements along `axis` `repeats` times. + Args: + x: Tensor + repeats: How often to repeat each element. 1D tensor of length x.shape[axis] + axis: Which axis to repeat elements along + + Returns: + repeated Tensor """ raise NotImplementedError(self) diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index f5163edba..fc91e25e0 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -41,6 +41,7 @@ def prefers_channels_last(self) -> bool: concat = staticmethod(np.concatenate) stack = staticmethod(np.stack) tile = staticmethod(np.tile) + repeat = staticmethod(np.repeat) transpose = staticmethod(np.transpose) sqrt = np.sqrt exp = np.exp diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index b003944a3..4035d1c8b 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -155,6 +155,11 @@ def tile(self, value, multiples): value = self.expand_dims(value, axis=0, number=len(multiples) - self.ndims(value)) return tf.tile(value, multiples) + def repeat(self, x, repeats, axis: int): + x = self.as_tensor(x) + with tf.device(x.device): + return tf.repeat(x, repeats, axis) + def stack(self, values, axis=0): with self._device_for(*values): return tf.stack(values, axis=axis) diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index 8dff14d79..5e3003968 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -606,6 +606,11 @@ def tile(self, value, multiples): multiples = multiples.tolist() return self.as_tensor(value).repeat(multiples) + def repeat(self, x, repeats, axis: int): + if isinstance(repeats, (np.ndarray, tuple, list)): + repeats = self.as_tensor(repeats) + return torch.repeat_interleave(self.as_tensor(x), repeats, axis) + def sparse_coo_tensor(self, indices, values, shape): indices_ = self.to_int64(indices) values_ = self.to_float(values) diff --git a/tests/commit/math/test__sparse.py b/tests/commit/math/test__sparse.py index b86cf0b1b..3bfa1edab 100644 --- a/tests/commit/math/test__sparse.py +++ b/tests/commit/math/test__sparse.py @@ -1,14 +1,39 @@ from unittest import TestCase -from phi.math import batch, get_sparsity, expand, wrap, stack, zeros, channel, spatial, ones, instance +import phi +from phi import math +from phi.math import batch, get_sparsity, expand, wrap, stack, zeros, channel, spatial, ones, instance, tensor, sum, pairwise_distances, vec_length, dense, assert_close from phi.math._sparse import SparseCoordinateTensor, CompressedSparseTensor +BACKENDS = phi.detect_backends() -class TestSprase(TestCase): + +class TestSparse(TestCase): def test_sparsity(self): - self.assertEqual(1, get_sparsity(wrap(1))) - self.assertEqual(0.25, get_sparsity(expand(1., batch(b=4)))) - self.assertEqual(0.25, get_sparsity(stack([zeros(batch(b=4))] * 3, channel('vector')))) - self.assertEqual(0.3, get_sparsity(SparseCoordinateTensor(ones(instance(nnz=3), channel(vector='x')), ones(instance(nnz=3)), spatial(x=10), True, False))) - self.assertEqual(0.03, get_sparsity(CompressedSparseTensor(ones(instance(nnz=3), channel(vector='x')), ones(instance(y_pointers=4)), spatial(y=10), ones(instance(nnz=3)), spatial(x=10, y=10)))) + # self.assertEqual(1, get_sparsity(wrap(1))) + # self.assertEqual(0.25, get_sparsity(expand(1., batch(b=4)))) + # self.assertEqual(0.25, get_sparsity(stack([zeros(batch(b=4))] * 3, channel('vector')))) + # self.assertEqual(0.3, get_sparsity(SparseCoordinateTensor(ones(instance(nnz=3), channel(vector='x')), ones(instance(nnz=3)), spatial(x=10), True, False))) + self.assertEqual(0.03, get_sparsity(CompressedSparseTensor(indices=ones(instance(nnz=3)), + pointers=ones(instance(y_pointers=4)), + values=ones(instance(nnz=3)), + uncompressed_dims=spatial(x=10), + compressed_dims=spatial(y=10)))) + + def test_csr(self): + for backend in BACKENDS: + with backend: + indices = tensor([0, 1, 0], instance('nnz')) + pointers = tensor([0, 2, 3, 3], instance('pointers')) + values = tensor([2, 3, 4], instance('nnz')) + matrix = CompressedSparseTensor(indices, pointers, values, channel(right=3), channel(down=3)) + math.print(dense(matrix)) + assert_close((2, 3, 0), dense(matrix).down[0]) + assert_close((4, 0, 0), dense(matrix).down[1]) + assert_close((0, 0, 0), dense(matrix).down[2]) + # Multiplication + assert_close((5, 4, 0), matrix.right * (1, 1, 1)) + # Simple arithmetic + assert_close(matrix, (matrix + matrix * 2) / 3) + From 516f7ae0540194619d038ddf987c4bc143aab17d Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 30 Dec 2022 18:08:07 +0100 Subject: [PATCH 031/170] [math] CSR multiplication Implements the following functionality: * mul_coo_dense * coo_to_dense * mul_csr_dense * csr_to_dense * assert_close for compressed sparse * dense(compressed sparse) PyTorch overrides mul_csr_dense and TF overrides mul_coo_dense. It has not been tested whether these implementations are faster than the generic Backend ones. * Add unit tests --- phi/math/_ops.py | 16 ++++++- phi/math/_sparse.py | 75 +++++++++++++++++++---------- phi/math/backend/_backend.py | 77 +++++++++++++++++++++++++----- phi/math/backend/_numpy_backend.py | 2 +- phi/tf/_tf_backend.py | 13 +++++ phi/torch/_torch_backend.py | 22 +++++++++ tests/commit/math/test__sparse.py | 8 ++-- 7 files changed, 168 insertions(+), 45 deletions(-) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index ea1575493..d1245ddf9 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -11,7 +11,7 @@ from ._shape import (Shape, EMPTY_SHAPE, spatial, batch, channel, instance, merge_shapes, parse_dim_order, concat_shapes, IncompatibleShapes, DimFilter, non_batch, non_channel) -from ._sparse import CompressedSparseTensor, dot_compressed_dense +from ._sparse import CompressedSparseTensor, dot_compressed_dense, dense from ._tensors import Tensor, wrap, tensor, broadcastable_native_tensors, NativeTensor, TensorStack, CollapsedTensor, \ custom_op2, compatible_tensor, variable_attributes, disassemble_tree, assemble_tree, \ cached, is_scalar, Layout @@ -1081,7 +1081,8 @@ def _sum(value: Tensor, dims: Shape) -> Tensor: if value.sparse_dims in dims: # reduce all sparse dims return _sum(value._values, dims.without(value.sparse_dims) & instance(value._values)) value_only_dims = dims.only(value._values.shape).without(value.sparsity_batch) - value = value._with_values(_sum(value._values, value_only_dims)) + if value_only_dims: + value = value._with_values(_sum(value._values, value_only_dims)) dims = dims.without(value_only_dims) if value._compressed_dims in dims and value._uncompressed_dims.only(dims).is_empty: # We can ignore the pointers @@ -2227,6 +2228,17 @@ def _assert_close(tensor1: Tensor, tensor2: Tensor, rel_tolerance: float, abs_to tensor1._assert_close(tensor2, rel_tolerance, abs_tolerance, msg, verbose) elif isinstance(tensor2, Layout): tensor2._assert_close(tensor1, rel_tolerance, abs_tolerance, msg, verbose) + elif isinstance(tensor1, CompressedSparseTensor): + if isinstance(tensor2, CompressedSparseTensor): + _assert_close(tensor1._values, tensor2._values, rel_tolerance, abs_tolerance, msg, verbose) + _assert_close(tensor1._indices, tensor2._indices, 0, 0, msg, verbose) + _assert_close(tensor1._pointers, tensor2._pointers, 0, 0, msg, verbose) + elif tensor1._compressed_dims.only(tensor2.shape): + _assert_close(dense(tensor1), tensor2, rel_tolerance, abs_tolerance, msg, verbose) + else: + _assert_close(tensor1._values, tensor2._values, rel_tolerance, abs_tolerance, msg, verbose) + elif isinstance(tensor2, CompressedSparseTensor): + return _assert_close(tensor2, tensor1, rel_tolerance, abs_tolerance, msg, verbose) else: def inner_assert_close(tensor1, tensor2): new_shape, (native1, native2) = broadcastable_native_tensors(tensor1, tensor2) diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index 2066bf379..2ef5a736d 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -1,4 +1,5 @@ import warnings +from numbers import Number from typing import List, Callable from ._shape import Shape, non_batch, merge_shapes, instance, batch, non_instance, shape, channel, spatial @@ -27,6 +28,9 @@ def shape(self) -> Shape: def dtype(self) -> DType: return self._values.dtype + def native(self, order: str or tuple or list or Shape = None): + raise RuntimeError("Sparse tensors do not have a native representation. Use math.dense(tensor).native() instead") + class CompressedSparseTensor(Tensor): @@ -50,6 +54,7 @@ def __init__(self, indices: Tensor, pointers: Tensor, values: Tensor, uncompress assert instance(values) == instance(indices), "Instance dimensions of values and indices must match exactly" assert not channel(indices) and not spatial(indices), f"channel and spatial dimensions not allowed on indices but got {shape(indices)}" assert not channel(pointers) and not spatial(pointers), f"channel and spatial dimensions not allowed on pointers but got {shape(pointers)}" + assert uncompressed_dims.only(compressed_dims).is_empty, f"Dimensions cannot be compressed and uncompressed at the same time but got compressed={compressed_dims}, uncompressed={uncompressed_dims}" self._shape = merge_shapes(compressed_dims, uncompressed_dims, batch(indices), batch(pointers), non_instance(values)) self._indices = indices self._pointers = pointers @@ -96,29 +101,31 @@ def _op2(self, other, operator: Callable, native_function: Callable, op_name: st affects_only_values = self.sparse_dims not in other_shape and non_instance(self._indices).only(other_shape).is_empty if affects_only_values: return self._with_values(operator(self._values, other)) - # if op_name == 'pow': - # if affects_only_values: - # return self._with_values(self._values ** other) - # if op_name == 'maximum': - + elif isinstance(other, CompressedSparseTensor): + if other._indices is self._indices and other._pointers is self._pointers: + return self._with_values(operator(self._values, other._values)) + elif op_symbol == '+': + raise NotImplementedError("Compressed addition not yet implemented") + else: + # convert to COO, then perform operation + raise NotImplementedError raise NotImplementedError def _with_values(self, new_values: Tensor): return CompressedSparseTensor(self._indices, self._pointers, new_values, self._uncompressed_dims, self._compressed_dims) + def _native_csr_components(self): + from phi.math import reshaped_native + ind_batch = batch(self._indices & self._pointers) + channels = non_instance(self._values).without(ind_batch) + native_indices = reshaped_native(self._indices, [ind_batch, instance], force_expand=True) + native_pointers = reshaped_native(self._pointers, [ind_batch, instance], force_expand=True) + native_values = reshaped_native(self._values, [ind_batch, instance, channels]) + native_shape = self._uncompressed_dims.volume, self._compressed_dims.volume + return ind_batch, channels, native_indices, native_pointers, native_values, native_shape -def dense(x: Tensor, order: str or tuple or list or Shape): - if isinstance(x, SparseCoordinateTensor): - native = x.default_backend.coo_to_dense(x.native()) - # fallback: scatter - grid = x.default_backend.zeros([], dtype=x.dtype) - native = x.default_backend.scatter(grid, x.indices, x.values, 'update') - raise NotImplementedError - elif isinstance(x, CompressedSparseTensor): - raise NotImplementedError - else: - assert isinstance(x, Tensor), f"must be a Tensor but got {type(x).__name__}" - return x + def native(self, order: str or tuple or list or Shape = None): + raise RuntimeError("Sparse tensors do not have a native representation. Use math.dense(tensor).native() instead") def get_sparsity(x: Tensor): @@ -180,20 +187,36 @@ def stored_values(x: Tensor) -> List[Tensor]: raise ValueError(x) +def dense(x: Tensor): + from phi.math import reshaped_tensor + if isinstance(x, SparseCoordinateTensor): + raise NotImplementedError + native_dense = x.default_backend.coo_to_dense() + elif isinstance(x, CompressedSparseTensor): + ind_batch, channels, native_indices, native_pointers, native_values, native_shape = x._native_csr_components() + native_dense = x.default_backend.csr_to_dense(native_indices, native_pointers, native_values, native_shape) + return reshaped_tensor(native_dense, [ind_batch, x._compressed_dims, x._uncompressed_dims, channels]) + elif isinstance(x, NativeTensor): + return x + elif isinstance(x, Tensor): + return cached(x) + elif isinstance(x, (Number, bool)): + return x + + def dot_compressed_dense(compressed: CompressedSparseTensor, cdims: Shape, dense: Tensor, ddims: Shape): from phi.math import reshaped_native, reshaped_tensor backend = choose_backend(*compressed._natives() + dense._natives()) if compressed._uncompressed_dims in cdims: # proper matrix-vector multiplication - ind_batch = batch(compressed._indices & compressed._pointers) - channels = non_instance(compressed._values).without(ind_batch) + ind_batch, channels, native_indices, native_pointers, native_values, native_shape = compressed._native_csr_components() rhs_channels = shape(dense).without(ddims).without(channels) - native_indices = reshaped_native(compressed._indices, [ind_batch, instance], force_expand=True) - native_pointers = reshaped_native(compressed._pointers, [ind_batch, instance], force_expand=True) - native_values = reshaped_native(compressed._values, [ind_batch, instance, channels]) - native_shape = compressed._uncompressed_dims.volume, compressed._compressed_dims.volume dense_native = reshaped_native(dense, [ind_batch, channels, ddims, rhs_channels], force_expand=True) - result_native = backend.mul_csr_dense(native_indices, native_pointers, native_values, native_shape, dense_native) - result = reshaped_tensor(result_native, [ind_batch, channels, instance(compressed._compressed_dims), rhs_channels]) + if backend.supports(Backend.mul_csr_dense): + result_native = backend.mul_csr_dense(native_indices, native_pointers, native_values, native_shape, dense_native) + else: + native_coo_indices = backend.csr_to_coo(native_indices, native_pointers) + result_native = backend.mul_coo_dense(native_coo_indices, native_values, native_shape, dense_native) + result = reshaped_tensor(result_native, [ind_batch, channels, compressed._compressed_dims, rhs_channels]) return result else: # transposed matrix vector multiplication. This is inefficient - raise NotImplementedError + raise NotImplementedError("Transposed sparse matrix multiplication not yet implemented") diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 604f5fb9c..1f35cc411 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -8,7 +8,7 @@ import logging import numpy -from ._dtype import DType, combine_types +from ._dtype import DType, combine_types, to_numpy_dtype SolveResult = namedtuple('SolveResult', [ @@ -796,8 +796,8 @@ def tile(self, value, multiples): If `multiples` has more dimensions than `value`, these dimensions are added to `value` as outer dimensions. Args: - value: tensor - multiples: tuple or list of integers + value: tensor + multiples: tuple or list of integers Returns: tiled tensor @@ -837,6 +837,37 @@ def sparse_coo_tensor(self, indices: tuple or list, values, shape: tuple): """ raise NotImplementedError(self) + def mul_coo_dense(self, indices, values, shape, dense): + """ + Multiply a batch of sparse coordinate matrices by a batch of dense matrices. + Every backend should implement this feature. + This is the fallback if CSR multiplication is not supported. + + Args: + indices: (batch, nnz, ndims) + values: (batch, nnz, channels) + shape: Shape of the full matrix, tuple of length ndims + dense: (batch, channels, rhs_rows=cols, rhs_cols) + + Returns: + (batch, channels, rhs_rows=cols, rhs_cols) + """ + values, dense = self.auto_cast(values, dense) + batch_size, nnz, channel_count = self.staticshape(values) + _, _, rhs_rows, rhs_cols = self.staticshape(dense) + dense_formatted = self.reshape(self.transpose(dense, [0, 2, 1, 3]), (batch_size, rhs_rows, rhs_cols * channel_count)) # (batch, channels, rhs_rows=cols, rhs_cols) -> (batch, spatial..., channel) + dense_gathered = self.batched_gather_nd(dense_formatted, indices[:, :, 1:2]) + base_grid = self.zeros((batch_size, shape[0], dense.shape[3] * rhs_cols), self.dtype(dense)) + assert rhs_cols == 1 + result = self.scatter(base_grid, indices[:, :, 0:1], values * dense_gathered, mode='add') + return self.reshape(result, (batch_size, channel_count, rhs_rows, rhs_cols)) + + def coo_to_dense(self, indices, values, shape, contains_duplicates: bool): + batch_size, nnz, channel_count = self.staticshape(values) + base = self.zeros((batch_size, *shape, channel_count)) + result = self.scatter(base, indices, values, mode='add' if contains_duplicates else 'update') + return result + def csr_matrix(self, column_indices, row_pointers, values, shape: tuple): """ Create a sparse matrix in compressed sparse row (CSR) format. @@ -857,9 +888,9 @@ def csr_matrix(self, column_indices, row_pointers, values, shape: tuple): """ raise NotImplementedError(self) - def mul_csr_dense(self, column_indices, row_pointers, matrix_values, shape: tuple, rhs): + def mul_csr_dense(self, column_indices, row_pointers, matrix_values, shape: tuple, dense): """ - Create a sparse matrix in compressed sparse row (CSR) format. + Multiply a batch of compressed sparse row matrices by a batch of dense matrices. Optional feature. @@ -871,13 +902,34 @@ def mul_csr_dense(self, column_indices, row_pointers, matrix_values, shape: tupl row_pointers: (batch, rows + 1) matrix_values: (batch, nnz, channels) shape: Shape of the full matrix (cols, rows) - rhs: (batch, channels, rhs_rows=cols, rhs_cols) + dense: (batch, channels, rhs_rows=cols, rhs_cols) Returns: (batch, channels, rhs_rows=cols, rhs_cols) """ raise NotImplementedError(self) + def csr_to_coo(self, column_indices, row_pointers): + """ + Convert a batch of compressed sparse matrices to sparse coordinate matrices. + + Args: + column_indices: (batch, nnz) + row_pointers: (batch, rows + 1) + + Returns: + indices: (batch, nnz, 2) + """ + batch_size = self.staticshape(column_indices)[0] + repeats = row_pointers[:, 1:] - row_pointers[:, :-1] + row_count = self.shape(repeats)[-1] + row_indices = [self.repeat(self.range(row_count), repeats[b], -1) for b in range(batch_size)] + return self.stack([self.stack(row_indices), column_indices], axis=-1) + + def csr_to_dense(self, column_indices, row_pointers, values, shape: tuple): + indices = self.csr_to_coo(column_indices, row_pointers) + return self.coo_to_dense(indices, values, shape, contains_duplicates=False) + def csc_matrix(self, column_pointers, row_indices, values, shape: tuple): """ Create a sparse matrix in compressed sparse column (CSC) format. @@ -912,13 +964,14 @@ def coordinates(self, tensor): """ raise NotImplementedError(self) - def pairwise_distances(self, positions, max_radius, format: str) -> list: + def pairwise_distances(self, positions, max_radius, format: str, index_dtype=DType(int, 32)) -> list: """ Args: positions: Point locations of shape (batch, instances, vector) max_radius: Scalar or (batch,) or (batch, instances) - format: 'csr', 'coo' or 'csc' + format: 'csr', Not yet implemented: 'sparse', 'coo', 'csc' + index_dtype: Either int32 or int64 Returns: Sequence of batch_size sparse distance matrices @@ -932,11 +985,11 @@ def pairwise_distances(self, positions, max_radius, format: str) -> list: radius = float(max_radius) if len(self.staticshape(max_radius)) == 0 else max_radius[i] nested_neighbors = tree.query_radius(positions_np_batched[i], r=radius) # ndarray[ndarray] if format == 'csr': - column_indices = numpy.concatenate(nested_neighbors) # flattened_neighbors + column_indices = numpy.concatenate(nested_neighbors).astype(to_numpy_dtype(index_dtype)) # flattened_neighbors neighbor_counts = [len(nlist) for nlist in nested_neighbors] - row_pointers = numpy.concatenate([[0], numpy.cumsum(neighbor_counts)]) - pos_neighbors = positions[i, column_indices] - pos_self = numpy.repeat(positions[i], neighbor_counts, axis=0) + row_pointers = numpy.concatenate([[0], numpy.cumsum(neighbor_counts)]).astype(to_numpy_dtype(index_dtype)) + pos_neighbors = self.gather(positions[i], column_indices, 0) + pos_self = self.repeat(positions[i], neighbor_counts, axis=0) values = pos_neighbors - pos_self result.append((column_indices, row_pointers, values)) # sparse_matrix = self.csr_matrix(column_indices, row_pointers, values, (point_count, point_count)) diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index fc91e25e0..ab4572f04 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -350,7 +350,7 @@ def csc_matrix(self, column_pointers, row_indices, values, shape: tuple): return scipy.sparse.csc_matrix((values, row_indices, column_pointers), shape=shape) def mul_csr_dense(self, column_indices, row_pointers, matrix_values, shape: tuple, rhs): - batch_size, nnz, channel_count = self.staticshape(matrix_values) + batch_size, nnz, channel_count = matrix_values.shape result = [] for b in range(batch_size): b_result = [] diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index 4035d1c8b..4a91ebe92 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -567,6 +567,19 @@ def sparse_coo_tensor(self, indices, values, shape): indices = tf.cast(tf.stack(indices, axis=-1), tf.int64) return tf.SparseTensor(indices=indices, values=values, dense_shape=shape) + def mul_coo_dense(self, indices, values, shape, dense): + values, dense = self.auto_cast(values, dense) + batch_size, nnz, channel_count = self.staticshape(values) + indices = tf.cast(indices, np.int64) + result = [] + for b in range(batch_size): + b_result = [] + for c in range(channel_count): + matrix = tf.SparseTensor(indices=indices[b], values=values[b, :, c], dense_shape=shape) + b_result.append(tf.sparse.sparse_dense_matmul(matrix, dense[b, c])) + result.append(tf.stack(b_result)) + return tf.stack(result) + def coordinates(self, tensor): assert isinstance(tensor, tf.SparseTensor) idx = tensor.indices diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index 5e3003968..39b22c8ae 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -625,6 +625,28 @@ def sparse_coo_tensor(values, indices, cols: int, rows: int, dtype: torch.dtype) result = torch.sparse_coo_tensor(indices_, values_, shape, dtype=to_torch_dtype(self.float_type)) return result + def mul_csr_dense(self, column_indices, row_pointers, matrix_values, shape: tuple, rhs): + matrix_values, rhs = self.auto_cast(matrix_values, rhs, bool_to_int=True, int_to_float=True) + batch_size, nnz, channel_count = matrix_values.shape + result = [] + for b in range(batch_size): + b_result = [] + for c in range(channel_count): + matrix = torch.sparse_csr_tensor(row_pointers[b], column_indices[b], matrix_values[b, :, c], shape, device=matrix_values.device) + # mat = scipy.sparse.csr_matrix((matrix_values[b, :, c], column_indices[b], row_pointers[b]), shape=shape) + b_result.append(torch.sparse.mm(matrix, self.as_tensor(rhs[b, c]))) + result.append(torch.stack(b_result)) + return torch.stack(result) + # if channel_count == 1: + # matrix = torch.sparse_csr_tensor(row_pointers, column_indices, matrix_values[:, :, 0], (batch_size, *shape), device=matrix_values.device) + # matrix.matmul(self.as_tensor(rhs[:, 0, :, :])) + # # torch.sparse.mm(matrix, self.as_tensor(rhs[:, 0, :, :])) + # raise NotImplementedError + # else: + # # tile + # raise NotImplementedError + + def coordinates(self, tensor): assert isinstance(tensor, torch.Tensor) and tensor.is_sparse idx = tensor._indices() diff --git a/tests/commit/math/test__sparse.py b/tests/commit/math/test__sparse.py index 3bfa1edab..5ff682b32 100644 --- a/tests/commit/math/test__sparse.py +++ b/tests/commit/math/test__sparse.py @@ -11,10 +11,10 @@ class TestSparse(TestCase): def test_sparsity(self): - # self.assertEqual(1, get_sparsity(wrap(1))) - # self.assertEqual(0.25, get_sparsity(expand(1., batch(b=4)))) - # self.assertEqual(0.25, get_sparsity(stack([zeros(batch(b=4))] * 3, channel('vector')))) - # self.assertEqual(0.3, get_sparsity(SparseCoordinateTensor(ones(instance(nnz=3), channel(vector='x')), ones(instance(nnz=3)), spatial(x=10), True, False))) + self.assertEqual(1, get_sparsity(wrap(1))) + self.assertEqual(0.25, get_sparsity(expand(1., batch(b=4)))) + self.assertEqual(0.25, get_sparsity(stack([zeros(batch(b=4))] * 3, channel('vector')))) + self.assertEqual(0.3, get_sparsity(SparseCoordinateTensor(ones(instance(nnz=3), channel(vector='x')), ones(instance(nnz=3)), spatial(x=10), True, False))) self.assertEqual(0.03, get_sparsity(CompressedSparseTensor(indices=ones(instance(nnz=3)), pointers=ones(instance(y_pointers=4)), values=ones(instance(nnz=3)), From 03cb5fcb93ba490abd645c659551caad19c237d0 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 30 Dec 2022 20:20:29 +0100 Subject: [PATCH 032/170] [math] Fix sum over tracers --- phi/math/_ops.py | 2 ++ phi/math/_sparse.py | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index d1245ddf9..5712d182c 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -1068,6 +1068,8 @@ def sum_(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: def _sum(value: Tensor, dims: Shape) -> Tensor: + if not dims: + return value if isinstance(value, NativeTensor): result = value.default_backend.sum(value.native(value.shape), value.shape.indices(dims)) return NativeTensor(result, value.shape.without(dims)) diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index 2ef5a736d..21570b73b 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -3,7 +3,7 @@ from typing import List, Callable from ._shape import Shape, non_batch, merge_shapes, instance, batch, non_instance, shape, channel, spatial -from ._tensors import Tensor, TensorStack, CollapsedTensor, NativeTensor, cached +from ._tensors import Tensor, TensorStack, CollapsedTensor, NativeTensor, cached, wrap from .backend import choose_backend, Backend from .backend._dtype import DType @@ -187,7 +187,17 @@ def stored_values(x: Tensor) -> List[Tensor]: raise ValueError(x) -def dense(x: Tensor): +def dense(x: Tensor) -> Tensor: + """ + Convert a sparse tensor representation to an equivalent dense one in which all values are explicitly stored contiguously in memory. + + Args: + x: Any `Tensor`. + Python primitives like `float`, `int` or `bool` will be converted to `Tensors` in the process. + + Returns: + Dense tensor. + """ from phi.math import reshaped_tensor if isinstance(x, SparseCoordinateTensor): raise NotImplementedError @@ -201,7 +211,7 @@ def dense(x: Tensor): elif isinstance(x, Tensor): return cached(x) elif isinstance(x, (Number, bool)): - return x + return wrap(x) def dot_compressed_dense(compressed: CompressedSparseTensor, cdims: Shape, dense: Tensor, ddims: Shape): From eba4cccdf4d657951fba15d48ed4b55d39f4ad60 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 30 Dec 2022 20:24:22 +0100 Subject: [PATCH 033/170] [math] Fix csr_to_coo int/long clash --- phi/math/backend/_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 1f35cc411..3cb450ae6 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -923,7 +923,7 @@ def csr_to_coo(self, column_indices, row_pointers): batch_size = self.staticshape(column_indices)[0] repeats = row_pointers[:, 1:] - row_pointers[:, :-1] row_count = self.shape(repeats)[-1] - row_indices = [self.repeat(self.range(row_count), repeats[b], -1) for b in range(batch_size)] + row_indices = [self.repeat(self.range(row_count, dtype=self.dtype(column_indices)), repeats[b], -1) for b in range(batch_size)] return self.stack([self.stack(row_indices), column_indices], axis=-1) def csr_to_dense(self, column_indices, row_pointers, values, shape: tuple): From 17410167631877893b05abfc76f82df5a6027c1d Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 30 Dec 2022 22:09:32 +0100 Subject: [PATCH 034/170] [math] Add fallback to TensorFlow mul_coo_dense --- phi/tf/_tf_backend.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index 4a91ebe92..37fdc8360 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -8,6 +8,7 @@ import os import tensorflow as tf from tensorflow.python.client import device_lib +from tensorflow.python.framework.errors_impl import NotFoundError from ..math.backend._backend import combined_dim, TensorType from ..math.backend._dtype import DType, to_numpy_dtype, from_numpy_dtype @@ -576,7 +577,10 @@ def mul_coo_dense(self, indices, values, shape, dense): b_result = [] for c in range(channel_count): matrix = tf.SparseTensor(indices=indices[b], values=values[b, :, c], dense_shape=shape) - b_result.append(tf.sparse.sparse_dense_matmul(matrix, dense[b, c])) + try: + b_result.append(tf.sparse.sparse_dense_matmul(matrix, dense[b, c])) + except NotFoundError: # These data types are probably not supported by TensorFlow + return Backend.mul_coo_dense(self, indices, values, shape, dense) result.append(tf.stack(b_result)) return tf.stack(result) From eb1b4ff9ac05babc573b6d2db8e99dea729e2e88 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 31 Dec 2022 12:45:58 +0100 Subject: [PATCH 035/170] [math] Fix max_distance in pairwise_distance() --- phi/math/_ops.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 5712d182c..7c2907dbf 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -2318,8 +2318,9 @@ def stop_gradient(x): def pairwise_distances(positions: Tensor, max_distance: float or Tensor = None, others_dims=instance('others'), format='dense') -> Tensor: """ - - + Computes the distance matrix containing the pairwise position differences between each pair of points. + Points that are further apart than `max_distance` are assigned a distance value of `0`. + The diagonal of the matrix (self-distance) also consists purely of zero-vectors. Args: positions: `Tensor`. @@ -2335,16 +2336,25 @@ def pairwise_distances(positions: Tensor, max_distance: float or Tensor = None, Returns: `Tensor` + + Examples: + ```python + pos = vec(x=0, y=tensor([0, 1, 2.5], instance('particles'))) + dx = math.pairwise_distances(pos, format='dense', max_distance=2) + dx.particles[0] + # Out: (x=0.000, y=0.000); (x=0.000, y=1.000); (x=0.000, y=0.000) (othersⁱ=3, vectorᶜ=x,y) + ``` """ if format == 'dense': # if not count_self: # warnings.warn(f"count_self has no effect when using format '{format}'", SyntaxWarning, stacklevel=2) - dx = positions - unpack_dim(pack_dims(positions, non_batch(positions).non_channel, instance('_tmp')), '_tmp', others_dims) + dx = unpack_dim(pack_dims(positions, non_batch(positions).non_channel, instance('_tmp')), '_tmp', others_dims) - positions if max_distance is not None: - neighbors = dx ** 2 <= max_distance ** 2 + neighbors = sum_(dx ** 2, channel) <= max_distance ** 2 dx = where(neighbors, dx, 0) return dx else: # sparse + assert max_distance is not None, "max_distance must be specified when computing distance in sparse format" backend = choose_backend_t(positions, max_distance) batch_shape = batch(positions) & batch(max_distance) pos_i_shape = non_batch(positions).non_channel From 34bf833e10609f5cb999bdd744ac93cb8809b5fb Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 1 Jan 2023 12:01:55 +0100 Subject: [PATCH 036/170] [math] Implement CSR slicing, concat --- phi/math/_magic_ops.py | 2 +- phi/math/_shape.py | 3 ++ phi/math/_sparse.py | 90 ++++++++++++++++++++++++++++--- phi/math/_tensors.py | 2 + tests/commit/math/test__sparse.py | 18 +++++++ 5 files changed, 107 insertions(+), 8 deletions(-) diff --git a/phi/math/_magic_ops.py b/phi/math/_magic_ops.py index 760c64fee..ed0c71564 100644 --- a/phi/math/_magic_ops.py +++ b/phi/math/_magic_ops.py @@ -222,7 +222,7 @@ def concat(values: tuple or list, dim: str or Shape, **kwargs): if hasattr(v, '__concat__'): result = v.__concat__(values, dim, **kwargs) if result is not NotImplemented: - assert isinstance(result, Shapable), "__concat__ must return a Shapable object" + assert isinstance(result, Shapable), f"__concat__ must return a Shapable object but got {type(result).__name__} from {type(v).__name__} {v}" return result # --- Next: try concat attributes for tree nodes --- if all(isinstance(v, PhiTreeNode) for v in values): diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 05c0b6e13..7c9b6f7f4 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -960,6 +960,9 @@ def after_gather(self, selection: dict) -> 'Shape': gathered_sizes = [(int(s) if isinstance(s, Tensor) and s.rank == 0 else s) for s in gathered_sizes] result = result.with_sizes(gathered_sizes, keep_item_names=True).without(sel_dim) elif isinstance(selection, slice): + assert isinstance(selection.step, int) or selection.step is None, f"slice step must be an int or None but got {type(selection.step).__name__}" + assert isinstance(selection.start, int) or selection.start is None, f"slice start must be an int or None but got {type(selection.start).__name__}" + assert isinstance(selection.stop, int) or selection.stop is None, f"slice stop must be an int or None but got {type(selection.stop).__name__}" step = selection.step or 1 start = selection.start if isinstance(selection.start, int) else (0 if step > 0 else self.get_size(sel_dim)-1) stop = selection.stop if isinstance(selection.stop, int) else (self.get_size(sel_dim) if step > 0 else -1) diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index 21570b73b..40c6fdd39 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -3,6 +3,7 @@ from typing import List, Callable from ._shape import Shape, non_batch, merge_shapes, instance, batch, non_instance, shape, channel, spatial +from ._magic_ops import concat from ._tensors import Tensor, TensorStack, CollapsedTensor, NativeTensor, cached, wrap from .backend import choose_backend, Backend from .backend._dtype import DType @@ -34,7 +35,7 @@ def native(self, order: str or tuple or list or Shape = None): class CompressedSparseTensor(Tensor): - def __init__(self, indices: Tensor, pointers: Tensor, values: Tensor, uncompressed_dims: Shape, compressed_dims: Shape): + def __init__(self, indices: Tensor, pointers: Tensor, values: Tensor, uncompressed_dims: Shape, compressed_dims: Shape, uncompressed_offset: int = None): """ Args: @@ -48,6 +49,12 @@ def __init__(self, indices: Tensor, pointers: Tensor, values: Tensor, uncompress These dimensions are indexed by `pointers`. uncompressed_dims: Sparse dimensions with full index storage. These dimensions are indexed by `indices`. + uncompressed_offset: For sliced sparse tensors. + If `None`, indicates that all entries lie within bounds. + If an `int`, indicate that this is a slice of a larger compressed sparse matrix. + Indices actually refer to `indices - uncompressed_offset` within this matrix, i.e. they may reference phantom values to the left or right of the matrix. + The `values` corresponding to phantom entries must all be 0. + The size of the slice is given by `compressed_dims.volume`. """ assert instance(indices), "indices must have an instance dimension" assert instance(pointers), "pointers must have an instance dimension" @@ -61,6 +68,7 @@ def __init__(self, indices: Tensor, pointers: Tensor, values: Tensor, uncompress self._values = values self._uncompressed_dims = uncompressed_dims self._compressed_dims = compressed_dims + self._uncompressed_offset = uncompressed_offset @property def shape(self) -> Shape: @@ -86,12 +94,77 @@ def _natives(self) -> tuple: return self._values._natives() + self._indices._natives() + self._pointers._natives() def _getitem(self, selection: dict) -> 'Tensor': - if self._compressed_dims.only(tuple(selection)): - raise NotImplementedError - if self._uncompressed_dims.only(tuple(selection)): - raise NotImplementedError batch_selection = {dim: selection[dim] for dim in self._shape.only(tuple(selection)).names} - return CompressedSparseTensor(self._indices[batch_selection], self._pointers[batch_selection], self._values[batch_selection], self._uncompressed_dims, self._compressed_dims) + indices = self._indices[batch_selection] + pointers = self._pointers[batch_selection] + values = self._values[batch_selection] + uncompressed = self._uncompressed_dims + compressed = self._compressed_dims + uncompressed_offset = self._uncompressed_offset + if compressed.only(tuple(selection)): + if compressed.rank > 1: + raise NotImplementedError + ptr_sel = selection[compressed.name] + if isinstance(ptr_sel, int): + raise NotImplementedError(f"Slicing with int not yet supported for sparse tensors. Use a range instead, e.g. [{ptr_sel}:{ptr_sel+1}] instead of [{ptr_sel}]") + elif isinstance(ptr_sel, slice): + assert ptr_sel.step in (None, 1), f"Only step size 1 supported for sparse indexing but got {ptr_sel.step}" + if batch(indices): + raise NotImplementedError("Slicing not yet supported for batched sparse tensors") + start = ptr_sel.start or 0 + stop = uncompressed.volume if ptr_sel.stop is None else ptr_sel.stop + pointers = pointers[start:stop+1] + indices = indices[{instance(indices).name: slice(int(pointers[0]), int(pointers[-1]))}] + values = values[{instance(values).name: slice(int(pointers[0]), int(pointers[-1]))}] + pointers -= pointers[0] + compressed = compressed.after_gather({compressed.name: ptr_sel}) + else: + raise NotImplementedError + if uncompressed.only(tuple(selection)): + if self._uncompressed_dims.rank > 1: + raise NotImplementedError + ind_sel = selection[uncompressed.name] + if isinstance(ind_sel, int): + raise NotImplementedError(f"Slicing with int not yet supported for sparse tensors. Use a range instead, e.g. [{ind_sel}:{ind_sel+1}] instead of [{ind_sel}]") + elif isinstance(ind_sel, slice): + assert ind_sel.step in (None, 1), f"Only step size 1 supported for sparse indexing but got {ind_sel.step}" + start = ind_sel.start or 0 + stop = uncompressed.volume if ind_sel.stop is None else ind_sel.stop + keep = (start <= self._indices) & (self._indices < stop) + from phi.math import where + values = where(keep, values, 0) + uncompressed_offset = start + uncompressed = uncompressed.after_gather({uncompressed.name: ind_sel}) + else: + raise NotImplementedError + return CompressedSparseTensor(indices, pointers, values, uncompressed, compressed, uncompressed_offset) + + def __concat__(self, tensors: tuple, dim: str, **kwargs) -> 'CompressedSparseTensor': + if not all(isinstance(t, CompressedSparseTensor) for t in tensors): + return NotImplemented + if dim == self._compressed_dims[0].name: + indices = concat([t._indices for t in tensors], instance(self._indices), **kwargs) + values = concat([t._values for t in tensors], instance(self._values), **kwargs) + pointers = [] + pointer_offset = 0 + for i, t in enumerate(tensors): + pointers.append((t._pointers[1:] if i else t._pointers) + pointer_offset) + pointer_offset += t._pointers[-1] + assert pointer_offset == instance(indices).volume + pointers = concat(pointers, instance(self._pointers)) + compressed = self._compressed_dims.with_dim_size(dim, sum(t.shape.get_size(dim) for t in tensors)) + return CompressedSparseTensor(indices, pointers, values, self._uncompressed_dims, compressed, self._uncompressed_offset) + elif dim == self._uncompressed_dims[0].name: + if all(t._indices is self._indices and t._pointers is self._pointers for t in tensors): + # ToDo test if offsets match and ordered correctly + from ._ops import sum_ + values = sum_([t._values for t in tensors], '0') + uncompressed = self._uncompressed_dims.with_dim_size(dim, sum(t.shape.get_size(dim) for t in tensors)) + return CompressedSparseTensor(self._indices, self._pointers, values, uncompressed, self._compressed_dims, uncompressed_offset=None) + else: + raise NotImplementedError("concatenating arbitrary compressed sparse tensors along uncompressed dim is not yet supported") + else: + raise NotImplementedError("concatenating compressed sparse tensors along non-sparse dims not yet supported") def _op1(self, native_function): return self._with_values(self._values._op1(native_function)) @@ -121,7 +194,10 @@ def _native_csr_components(self): native_indices = reshaped_native(self._indices, [ind_batch, instance], force_expand=True) native_pointers = reshaped_native(self._pointers, [ind_batch, instance], force_expand=True) native_values = reshaped_native(self._values, [ind_batch, instance, channels]) - native_shape = self._uncompressed_dims.volume, self._compressed_dims.volume + native_shape = self._compressed_dims.volume, self._uncompressed_dims.volume + if self._uncompressed_offset is not None: + native_indices -= self._uncompressed_offset + native_indices = choose_backend(native_indices).clip(native_indices, 0, self._uncompressed_dims.volume - 1) return ind_batch, channels, native_indices, native_pointers, native_values, native_shape def native(self, order: str or tuple or list or Shape = None): diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index 718691a61..e331b13a4 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -2405,6 +2405,8 @@ def _format_number(num, options: PrintOptions, dtype: DType): def format_tensor(self: Tensor, options: PrintOptions) -> str: + from ._sparse import dense + self = dense(self) if options.layout == 'auto': if not self.shape: return format_summary(self, options) diff --git a/tests/commit/math/test__sparse.py b/tests/commit/math/test__sparse.py index 5ff682b32..1d93e920b 100644 --- a/tests/commit/math/test__sparse.py +++ b/tests/commit/math/test__sparse.py @@ -37,3 +37,21 @@ def test_csr(self): # Simple arithmetic assert_close(matrix, (matrix + matrix * 2) / 3) + def test_csr_slice_concat(self): + pos = tensor([(0, 0), (0, 1), (0, 2)], instance('particles'), channel(vector='x,y')) + dx = math.pairwise_distances(pos, max_distance=1.5, format='csr') + self.assertEqual(0, dx.sum) + dist = math.vec_length(dx, eps=1e-6) + self.assertEqual(instance(particles=3, others=3), dist.shape) + self.assertGreater(dist.sum, 0) + # Slice channel + dx_y = dx['y'] + self.assertEqual(instance(particles=3, others=3), dx_y.shape) + # Slice / concat compressed + concat_particles = math.concat([dx.particles[:1], dx.particles[1:]], 'particles') + math.assert_close(dx, concat_particles) + # Slice / concat uncompressed + concat_others = math.concat([dx.others[:1], dx.others[1:]], 'others') + math.assert_close(dx, concat_others) + + From 2b97bfa3f193a6f25f2b3e26f105d3af25bbd5e3 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 1 Jan 2023 15:06:56 +0100 Subject: [PATCH 037/170] [math] Refactor Backend CSR handling --- phi/math/_sparse.py | 8 ++--- phi/math/backend/_backend.py | 50 ++++++++++++++++++++++-------- phi/math/backend/_numpy_backend.py | 11 ++++--- phi/tf/_tf_backend.py | 5 ++- phi/torch/_torch_backend.py | 18 +++++------ 5 files changed, 59 insertions(+), 33 deletions(-) diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index 40c6fdd39..852f823e6 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -296,12 +296,8 @@ def dot_compressed_dense(compressed: CompressedSparseTensor, cdims: Shape, dense if compressed._uncompressed_dims in cdims: # proper matrix-vector multiplication ind_batch, channels, native_indices, native_pointers, native_values, native_shape = compressed._native_csr_components() rhs_channels = shape(dense).without(ddims).without(channels) - dense_native = reshaped_native(dense, [ind_batch, channels, ddims, rhs_channels], force_expand=True) - if backend.supports(Backend.mul_csr_dense): - result_native = backend.mul_csr_dense(native_indices, native_pointers, native_values, native_shape, dense_native) - else: - native_coo_indices = backend.csr_to_coo(native_indices, native_pointers) - result_native = backend.mul_coo_dense(native_coo_indices, native_values, native_shape, dense_native) + dense_native = reshaped_native(dense, [ind_batch, ddims, channels, rhs_channels], force_expand=True) + result_native = backend.mul_csr_dense(native_indices, native_pointers, native_values, native_shape, dense_native) result = reshaped_tensor(result_native, [ind_batch, channels, compressed._compressed_dims, rhs_channels]) return result else: # transposed matrix vector multiplication. This is inefficient diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 3cb450ae6..3e6e67e45 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -818,6 +818,18 @@ def repeat(self, x, repeats, axis: int): """ raise NotImplementedError(self) + def indexed_segment_sum(self, x, indices, axis: int): + """ + Args: + x: Values to sum. Segments are laid out contiguously along `axis`. (batch, ...) + indices: should start with 0 along `axis`. (batch, indices) + axis: Axis along which to sum + + Returns: + Tensor with `len(indices)` elements along `axis`. (batch, ..., indices, ...) + """ + raise NotImplementedError(self) + def sparse_coo_tensor(self, indices: tuple or list, values, shape: tuple): """ Create a sparse matrix in coordinate list (COO) format. @@ -847,20 +859,20 @@ def mul_coo_dense(self, indices, values, shape, dense): indices: (batch, nnz, ndims) values: (batch, nnz, channels) shape: Shape of the full matrix, tuple of length ndims - dense: (batch, channels, rhs_rows=cols, rhs_cols) + dense: (batch, dense_rows=sparse_cols, channels, dense_cols) Returns: - (batch, channels, rhs_rows=cols, rhs_cols) + (batch, channels, dense_rows=sparse_cols, dense_cols) """ values, dense = self.auto_cast(values, dense) batch_size, nnz, channel_count = self.staticshape(values) - _, _, rhs_rows, rhs_cols = self.staticshape(dense) - dense_formatted = self.reshape(self.transpose(dense, [0, 2, 1, 3]), (batch_size, rhs_rows, rhs_cols * channel_count)) # (batch, channels, rhs_rows=cols, rhs_cols) -> (batch, spatial..., channel) + _, dense_rows, _, dense_cols = self.staticshape(dense) + dense_formatted = self.reshape(dense, (batch_size, dense_rows, dense_cols * channel_count)) dense_gathered = self.batched_gather_nd(dense_formatted, indices[:, :, 1:2]) - base_grid = self.zeros((batch_size, shape[0], dense.shape[3] * rhs_cols), self.dtype(dense)) - assert rhs_cols == 1 + base_grid = self.zeros((batch_size, shape[0], dense.shape[3] * dense_cols), self.dtype(dense)) + assert dense_cols == 1 result = self.scatter(base_grid, indices[:, :, 0:1], values * dense_gathered, mode='add') - return self.reshape(result, (batch_size, channel_count, rhs_rows, rhs_cols)) + return self.reshape(result, (batch_size, channel_count, dense_rows, dense_cols)) def coo_to_dense(self, indices, values, shape, contains_duplicates: bool): batch_size, nnz, channel_count = self.staticshape(values) @@ -888,7 +900,7 @@ def csr_matrix(self, column_indices, row_pointers, values, shape: tuple): """ raise NotImplementedError(self) - def mul_csr_dense(self, column_indices, row_pointers, matrix_values, shape: tuple, dense): + def mul_csr_dense(self, column_indices, row_pointers, values, shape: tuple, dense): """ Multiply a batch of compressed sparse row matrices by a batch of dense matrices. @@ -900,14 +912,26 @@ def mul_csr_dense(self, column_indices, row_pointers, matrix_values, shape: tupl Args: column_indices: (batch, nnz) row_pointers: (batch, rows + 1) - matrix_values: (batch, nnz, channels) + values: (batch, nnz, channels) shape: Shape of the full matrix (cols, rows) - dense: (batch, channels, rhs_rows=cols, rhs_cols) + dense: (batch, dense_rows=sparse_cols, channels, dense_cols) Returns: - (batch, channels, rhs_rows=cols, rhs_cols) - """ - raise NotImplementedError(self) + (batch, channels, dense_rows=sparse_cols, dense_cols) + """ + # if not self.supports(Backend.indexed_segment_sum): + native_coo_indices = self.csr_to_coo(column_indices, row_pointers) + return self.mul_coo_dense(native_coo_indices, values, shape, dense) + # values, dense = self.auto_cast(values, dense) + # batch_size, nnz, channel_count = self.staticshape(values) + # _, dense_rows, _, dense_cols = self.staticshape(dense) + # assert dense_cols == 1 + # dense_formatted = self.reshape(dense, (batch_size, dense_rows, channel_count * dense_cols)) + # dense_gathered = self.batched_gather_nd(dense_formatted, self.expand_dims(column_indices, -1)) # (batch, nnz, channels*rhs_cols) + # dense_gathered = self.reshape(dense_gathered, (batch_size, nnz, channel_count, dense_cols)) + # values = self.reshape(values, (batch_size, nnz, channel_count, 1)) + # result = self.indexed_segment_sum(values * dense_gathered, row_pointers[:, :-1], 1) + # return self.reshape(result, (batch_size, channel_count, rhs_rows, rhs_cols)) def csr_to_coo(self, column_indices, row_pointers): """ diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index ab4572f04..60317698d 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -335,6 +335,9 @@ def dtype(self, array) -> DType: array = np.array(array) return from_numpy_dtype(array.dtype) + def indexed_segment_sum(self, x, indices, axis: int): + return np.stack([np.add.reduceat(x[b], indices[b], axis-1) for b in range(x.shape[0])]) + def sparse_coo_tensor(self, indices, values, shape): if not isinstance(indices, (tuple, list)): indices = self.unstack(indices, -1) @@ -349,14 +352,14 @@ def csr_matrix(self, column_indices, row_pointers, values, shape: tuple): def csc_matrix(self, column_pointers, row_indices, values, shape: tuple): return scipy.sparse.csc_matrix((values, row_indices, column_pointers), shape=shape) - def mul_csr_dense(self, column_indices, row_pointers, matrix_values, shape: tuple, rhs): - batch_size, nnz, channel_count = matrix_values.shape + def mul_csr_dense(self, column_indices, row_pointers, values, shape: tuple, dense): + batch_size, nnz, channel_count = values.shape result = [] for b in range(batch_size): b_result = [] for c in range(channel_count): - mat = scipy.sparse.csr_matrix((matrix_values[b, :, c], column_indices[b], row_pointers[b]), shape=shape) - b_result.append(mat * rhs[b, c]) + mat = scipy.sparse.csr_matrix((values[b, :, c], column_indices[b], row_pointers[b]), shape=shape) + b_result.append(mat * dense[b, :, c, :]) result.append(np.stack(b_result)) return np.stack(result) diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index 37fdc8360..07a5c5f9c 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -373,6 +373,7 @@ def conv(self, value, kernel, zero_padding=True): return result def expand_dims(self, a, axis=0, number=1): + a = self.as_tensor(a) with tf.device(a.device): if number == 0: return a @@ -571,6 +572,8 @@ def sparse_coo_tensor(self, indices, values, shape): def mul_coo_dense(self, indices, values, shape, dense): values, dense = self.auto_cast(values, dense) batch_size, nnz, channel_count = self.staticshape(values) + if batch_size > 1: + return Backend.mul_coo_dense(self, indices, values, shape, dense) indices = tf.cast(indices, np.int64) result = [] for b in range(batch_size): @@ -578,7 +581,7 @@ def mul_coo_dense(self, indices, values, shape, dense): for c in range(channel_count): matrix = tf.SparseTensor(indices=indices[b], values=values[b, :, c], dense_shape=shape) try: - b_result.append(tf.sparse.sparse_dense_matmul(matrix, dense[b, c])) + b_result.append(tf.sparse.sparse_dense_matmul(matrix, dense[b, :, c, :])) except NotFoundError: # These data types are probably not supported by TensorFlow return Backend.mul_coo_dense(self, indices, values, shape, dense) result.append(tf.stack(b_result)) diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index 39b22c8ae..06a118be1 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -625,22 +625,22 @@ def sparse_coo_tensor(values, indices, cols: int, rows: int, dtype: torch.dtype) result = torch.sparse_coo_tensor(indices_, values_, shape, dtype=to_torch_dtype(self.float_type)) return result - def mul_csr_dense(self, column_indices, row_pointers, matrix_values, shape: tuple, rhs): - matrix_values, rhs = self.auto_cast(matrix_values, rhs, bool_to_int=True, int_to_float=True) - batch_size, nnz, channel_count = matrix_values.shape + def mul_csr_dense(self, column_indices, row_pointers, values, shape: tuple, dense): + values, dense = self.auto_cast(values, dense, bool_to_int=True, int_to_float=True) + batch_size, nnz, channel_count = values.shape result = [] for b in range(batch_size): b_result = [] for c in range(channel_count): - matrix = torch.sparse_csr_tensor(row_pointers[b], column_indices[b], matrix_values[b, :, c], shape, device=matrix_values.device) - # mat = scipy.sparse.csr_matrix((matrix_values[b, :, c], column_indices[b], row_pointers[b]), shape=shape) - b_result.append(torch.sparse.mm(matrix, self.as_tensor(rhs[b, c]))) + matrix = torch.sparse_csr_tensor(row_pointers[b], column_indices[b], values[b, :, c], shape, device=values.device) + # mat = scipy.sparse.csr_matrix((values[b, :, c], column_indices[b], row_pointers[b]), shape=shape) + b_result.append(torch.sparse.mm(matrix, self.as_tensor(dense[b, :, c, :]))) result.append(torch.stack(b_result)) return torch.stack(result) # if channel_count == 1: - # matrix = torch.sparse_csr_tensor(row_pointers, column_indices, matrix_values[:, :, 0], (batch_size, *shape), device=matrix_values.device) - # matrix.matmul(self.as_tensor(rhs[:, 0, :, :])) - # # torch.sparse.mm(matrix, self.as_tensor(rhs[:, 0, :, :])) + # matrix = torch.sparse_csr_tensor(row_pointers, column_indices, values[:, :, 0], (batch_size, *shape), device=values.device) + # matrix.matmul(self.as_tensor(dense[:, 0, :, :])) + # # torch.sparse.mm(matrix, self.as_tensor(dense[:, 0, :, :])) # raise NotImplementedError # else: # # tile From 36f445bec436271ed8c72183b7ccb918dbc5e777 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 1 Jan 2023 16:51:16 +0100 Subject: [PATCH 038/170] [math] Implement missing trigonometric/hyperbolic functions And their inverses. Arctan is implemented as two Backend functions but one public math function. * Add unit tests --- phi/jax/_jax_backend.py | 8 ++++++ phi/math/__init__.py | 3 +- phi/math/_ops.py | 46 ++++++++++++++++++++++++++++++ phi/math/_tensors.py | 25 ++++++++++------ phi/math/backend/_backend.py | 24 ++++++++++++++++ phi/math/backend/_numpy_backend.py | 8 ++++++ phi/tf/_tf_backend.py | 33 +++++++++++++++++++++ phi/torch/_torch_backend.py | 11 +++++++ tests/commit/math/test__ops.py | 26 +++++++++++++++++ 9 files changed, 174 insertions(+), 10 deletions(-) diff --git a/phi/jax/_jax_backend.py b/phi/jax/_jax_backend.py index a00d13891..9e4d189dd 100644 --- a/phi/jax/_jax_backend.py +++ b/phi/jax/_jax_backend.py @@ -116,6 +116,14 @@ def allocate_on_device(self, tensor: TensorType, device: ComputeDevice) -> Tenso cos = staticmethod(jnp.cos) arccos = staticmethod(jnp.arccos) tan = staticmethod(jnp.tan) + arctan = staticmethod(np.arctan) + arctan2 = staticmethod(np.arctan2) + sinh = staticmethod(np.sinh) + arcsinh = staticmethod(np.arcsinh) + cosh = staticmethod(np.cosh) + arccosh = staticmethod(np.arccosh) + tanh = staticmethod(np.tanh) + arctanh = staticmethod(np.arctanh) log = staticmethod(jnp.log) log2 = staticmethod(jnp.log2) log10 = staticmethod(jnp.log10) diff --git a/phi/math/__init__.py b/phi/math/__init__.py index c5effd1e9..bf14256a2 100644 --- a/phi/math/__init__.py +++ b/phi/math/__init__.py @@ -42,7 +42,8 @@ abs_ as abs, sign, round_ as round, ceil, floor, maximum, minimum, clip, - sqrt, exp, sin, cos, tan, log, log2, log10, sigmoid, arcsin, arccos, + sqrt, exp, log, log2, log10, sigmoid, + sin, cos, tan, sinh, cosh, tanh, arcsin, arccos, arctan, arcsinh, arccosh, arctanh, to_float, to_int32, to_int64, to_complex, imag, real, conjugate, degrees, boolean_mask, diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 7c2907dbf..464e9419b 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -1781,6 +1781,52 @@ def tan(x) -> Tensor or PhiTreeNode: return _backend_op1(x, Backend.tan) +def arctan(x, divide_by=None) -> Tensor or PhiTreeNode: + """ + Computes the inverse of *tan(x)* of the `Tensor` or `PhiTreeNode` `x`. + + Args: + x: Input. The single-argument `arctan` function cannot output π/2 or -π/2 since tan(π/2) is infinite. + divide_by: If specified, computes `arctan(x/divide_by)` so that it can return π/2 and -π/2. + This is equivalent to the common `arctan2` function. + """ + if divide_by is None: + return _backend_op1(x, Backend.arctan) + else: + divide_by = to_float(divide_by) + return custom_op2(x, divide_by, arctan, lambda a, b: choose_backend(a, b).arctan2(a, b), 'arctan') + + +def sinh(x) -> Tensor or PhiTreeNode: + """ Computes *sinh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + return _backend_op1(x, Backend.sinh) + + +def arcsinh(x) -> Tensor or PhiTreeNode: + """ Computes the inverse of *sinh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + return _backend_op1(x, Backend.arcsinh) + + +def cosh(x) -> Tensor or PhiTreeNode: + """ Computes *cosh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + return _backend_op1(x, Backend.cosh) + + +def arccosh(x) -> Tensor or PhiTreeNode: + """ Computes the inverse of *cosh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + return _backend_op1(x, Backend.arccosh) + + +def tanh(x) -> Tensor or PhiTreeNode: + """ Computes *tanh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + return _backend_op1(x, Backend.tanh) + + +def arctanh(x) -> Tensor or PhiTreeNode: + """ Computes the inverse of *tanh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + return _backend_op1(x, Backend.arctanh) + + def log(x) -> Tensor or PhiTreeNode: """ Computes the natural logarithm of the `Tensor` or `PhiTreeNode` `x`. """ return _backend_op1(x, Backend.log) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index e331b13a4..080a2e14e 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -1760,28 +1760,35 @@ def op2_native(x: Tensor, y: Tensor, native_function: Callable): return NativeTensor(result_tensor, new_shape) -def custom_op2(x: Tensor or float, y: Tensor or float, l_operator, l_native_function, r_operator=None, r_native_function=None, op_name: str = 'unknown') -> Tensor: +def custom_op2(x: Tensor or float, y: Tensor or float, l_operator, l_native_function, r_operator=None, r_native_function=None, op_name: str = 'unknown', op_symbol: str = None) -> Tensor: """ Perform a custom operator on two tensors. This method first tries calling _op2() on the first tensor and if that fails, tries it on the second tensor. Args: - x: Tensor or float: - y: Tensor or float: - l_operator: - l_native_function: - r_operator: (Default value = None) - r_native_function: (Default value = None) + x: Left argument + y: Right argument + l_operator: Operator function acting on Tensors + l_native_function: Operator function acting on natives + r_operator: Argument-reversed operator function acting on Tensors + r_native_function: Argument-reversed operator function acting on natives op_name: Name of the operator function for debugging purposes. Leading 'r' will be added for the operand-reversed version. + op_symbol: Short name for the operator, independent of argument order. Returns: `Tensor` """ + if op_symbol is None: + op_symbol = op_name x = wrap(x) y = wrap(y) - result = x._op2(y, l_operator, l_native_function, op_name, op_name) + result = x._op2(y, l_operator, l_native_function, op_name, op_symbol) if result is NotImplemented: - result = y._op2(x, r_operator or l_operator, r_native_function or l_native_function, f'r{op_name}', op_name) + if r_operator is None: + r_operator = lambda a, b: l_operator(b, a) + if r_native_function is None: + r_native_function = lambda a, b: l_native_function(b, a) + result = y._op2(x, r_operator, r_native_function, f'r{op_name}', op_symbol) if result is NotImplemented: raise NotImplementedError(f"Operation not supported between {type(x)} and {type(y)}") return result diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 3e6e67e45..c373b0655 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -774,6 +774,30 @@ def arccos(self, x): def tan(self, x): raise NotImplementedError(self) + def arctan(self, x): + raise NotImplementedError(self) + + def arctan2(self, y, x): + raise NotImplementedError(self) + + def sinh(self, x): + raise NotImplementedError(self) + + def arcsinh(self, x): + raise NotImplementedError(self) + + def cosh(self, x): + raise NotImplementedError(self) + + def arccosh(self, x): + raise NotImplementedError(self) + + def tanh(self, x): + raise NotImplementedError(self) + + def arctanh(self, x): + raise NotImplementedError(self) + def log(self, x): """ Natural logarithm """ raise NotImplementedError(self) diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index 60317698d..2c87c915b 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -50,6 +50,14 @@ def prefers_channels_last(self) -> bool: cos = np.cos arccos = np.arccos tan = np.tan + arctan = np.arctan + arctan2 = staticmethod(np.arctan2) + sinh = np.sinh + arcsinh = np.arcsinh + cosh = np.cosh + arccosh = np.arccosh + tanh = np.tanh + arctanh = np.arctanh log = np.log log2 = np.log2 log10 = np.log10 diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index 07a5c5f9c..3e4308327 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -540,6 +540,39 @@ def tan(self, x): with tf.device(x.device): return tf.math.tan(x) + def arctan(self, x): + with tf.device(x.device): + return tf.math.atan(x) + + def arctan2(self, y, x): + y, x = self.auto_cast(y, x) + with tf.device(x.device): + return tf.math.atan2(y, x) + + def sinh(self, x): + with tf.device(x.device): + return tf.math.sinh(x) + + def arcsinh(self, x): + with tf.device(x.device): + return tf.math.asinh(x) + + def cosh(self, x): + with tf.device(x.device): + return tf.math.cosh(x) + + def arccosh(self, x): + with tf.device(x.device): + return tf.math.acosh(x) + + def tanh(self, x): + with tf.device(x.device): + return tf.math.tanh(x) + + def arctanh(self, x): + with tf.device(x.device): + return tf.math.atanh(x) + def log(self, x): with tf.device(x.device): return tf.math.log(x) diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index 06a118be1..46d02171c 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -132,6 +132,13 @@ def multi_slice(self, tensor, slices: tuple): cos = torch.cos arccos = torch.arccos tan = torch.tan + arctan = torch.arctan + sinh = torch.sinh + arcsinh = torch.arcsinh + cosh = torch.cosh + arccosh = torch.arccosh + tanh = torch.tanh + arctanh = torch.arctanh log = torch.log log2 = torch.log2 log10 = torch.log10 @@ -552,6 +559,10 @@ def scatter(self, base_grid, indices, values, mode: str): result = scatter(base_grid_flat, dim=1, index=indices, src=values) return torch.reshape(result, base_grid.shape) + def arctan2(self, y, x): + y, x = self.auto_cast(y, x) + return torch.arctan2(y, x) + def fft(self, x, axes: tuple or list): if not x.is_complex(): x = self.to_complex(x) diff --git a/tests/commit/math/test__ops.py b/tests/commit/math/test__ops.py index cf782315c..409802a7c 100644 --- a/tests/commit/math/test__ops.py +++ b/tests/commit/math/test__ops.py @@ -486,6 +486,32 @@ def test_cos(self): math.assert_close(math.cos(math.tensor(math.PI)), -1, abs_tolerance=1e-6, msg=backend.name) math.assert_close(math.cos(math.tensor(math.PI * 3 / 2)), 0, abs_tolerance=1e-6, msg=backend.name) + def test_trigonometric_hyperbolic(self): + for f in [math.sin, math.cos, math.tan, math.sinh, math.cosh, math.tanh, + math.arcsin, math.arccos, math.arctan, math.arcsinh, math.arccosh, math.arctanh]: + results = [] + for backend in BACKENDS: + with backend: + value = math.tensor(0.3421) + results.append(f(value)) + math.assert_close(results, msg=f.__name__) + + def test_arccosh(self): + results = [] + for backend in BACKENDS: + with backend: + value = math.tensor(1.3421) + results.append(math.arccosh(value)) + math.assert_close(results) + + def test_arctan(self): + results = [] + for backend in BACKENDS: + with backend: + value = math.tensor(1.3421) + results.append(math.arctan(value, divide_by=0)) + math.assert_close(results) + def test_any(self): for backend in BACKENDS: with backend: From bdb7c3028e59c93e75a01588cc264460a27ea47f Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 1 Jan 2023 17:21:40 +0100 Subject: [PATCH 039/170] [ci] Install scikit-learn on GitHub Actions --- .github/workflows/unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 2f1945f39..a9bf1f433 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install --quiet tensorflow tensorflow-probability torch jax jaxlib plotly nbformat ipython pylint coverage pytest + pip install --quiet tensorflow tensorflow-probability torch jax jaxlib scikit-learn plotly nbformat ipython pylint coverage pytest pip install . - name: Test with pytest From 31e0d0c777b9c926b5ce1189d51a6a711a2fca2e Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 4 Jan 2023 13:26:36 +0100 Subject: [PATCH 040/170] [math] Add range, f_kwargs_ arguments to iterate() --- phi/math/_functional.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phi/math/_functional.py b/phi/math/_functional.py index 5aa43870a..26a563ce7 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -1832,7 +1832,7 @@ def map_i2b(f: Callable) -> Callable: return map_types(f, instance, batch) -def iterate(f: Callable, iterations: int or Shape, *x0, f_kwargs: dict = None): +def iterate(f: Callable, iterations: int or Shape, *x0, f_kwargs: dict = None, range=range, **f_kwargs_): """ Repeatedly call `function`, passing the previous output as the next input. @@ -1842,14 +1842,17 @@ def iterate(f: Callable, iterations: int or Shape, *x0, f_kwargs: dict = None): If `int`, returns the final output of `f`. If `Shape`, returns the trajectory (`x0` and all outputs of `f`), stacking the values along this dimension. x0: Initial positional arguments for `f`. + range: Range function. Can be used to generate tqdm output by passing `trange`. f_kwargs: Additional keyword arguments to be passed to `f`. These arguments can be of any type. + f_kwargs_: More keyword arguments. Returns: Trajectory of final output of `f`, depending on `iterations`. """ if f_kwargs is None: f_kwargs = {} + f_kwargs.update(f_kwargs_) x = x0 if isinstance(iterations, int): for i in range(iterations): From 17b03e7243dbd475a4abe9795a23a80a26e4508c Mon Sep 17 00:00:00 2001 From: Elias Djossou Date: Wed, 4 Jan 2023 20:20:45 +0100 Subject: [PATCH 041/170] [math] Fix laplacian weights and gradient extrapolation --- phi/field/_field_math.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index e5389a39f..3fd142301 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -101,10 +101,8 @@ def laplace(field: GridType, axes=spatial, scheme: Scheme = Scheme(2), weights: assert channel(weights).rank == 1 and channel(weights).item_names is not None, f"weights must have one channel dimension listing the laplace dims but got {shape(weights)}" assert set(channel(weights).item_names[0]) >= set(axes_names), f"the channel dim of weights must contain all laplace dims {axes_names} but only has {channel(weights).item_names}" result_components = [c * weights[ax] for c, ax in zip(result_components, axes_names)] - else: - weights = 1 - result = sum(result_components * weights) + result = sum(result_components) result = result.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) return result @@ -138,8 +136,8 @@ def spatial_gradient(field: CenteredGrid, """ - if gradient_extrapolation == None: - gradient_extrapolation = field.extrapolation + if gradient_extrapolation is None or gradient_extrapolation is math.extrapolation.NONE: + gradient_extrapolation = field.extrapolation.spatial_gradient() extrapol_map = {} if not scheme.is_implicit: From 3f4ce7adc66c4ae249f2b8de1c4ec245459b0243 Mon Sep 17 00:00:00 2001 From: Elias Djossou Date: Thu, 5 Jan 2023 10:10:23 +0100 Subject: [PATCH 042/170] [physics] fix extrapolation for second order make_incompressible --- phi/physics/fluid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index 7dda9d62a..449c45e11 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -132,8 +132,8 @@ def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid, `CenteredGrid` """ if scheme.order == 2 and not scheme.is_implicit: - grad = spatial_gradient(pressure, extrapolation.NONE, type=type(hard_bcs)) - valid_grad = grad * field.bake_extrapolation(hard_bcs) + grad = spatial_gradient(pressure, hard_bcs.extrapolation, type=type(hard_bcs)) + valid_grad = grad * field.bake_extrapolation(hard_bcs).with_extrapolation(grad.extrapolation) div = divergence(valid_grad) laplace = where(active, div, pressure) else: From d29f76cc08e4e0ee2283d7da4598efd5cd4721f4 Mon Sep 17 00:00:00 2001 From: Elias Djossou Date: Thu, 5 Jan 2023 10:10:23 +0100 Subject: [PATCH 043/170] [physics] fix extrapolation for second order make_incompressible --- phi/field/_field_math.py | 2 +- phi/physics/fluid.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 3fd142301..b5a1c8343 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -136,7 +136,7 @@ def spatial_gradient(field: CenteredGrid, """ - if gradient_extrapolation is None or gradient_extrapolation is math.extrapolation.NONE: + if gradient_extrapolation is None or gradient_extrapolation: gradient_extrapolation = field.extrapolation.spatial_gradient() extrapol_map = {} diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index 7dda9d62a..449c45e11 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -132,8 +132,8 @@ def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid, `CenteredGrid` """ if scheme.order == 2 and not scheme.is_implicit: - grad = spatial_gradient(pressure, extrapolation.NONE, type=type(hard_bcs)) - valid_grad = grad * field.bake_extrapolation(hard_bcs) + grad = spatial_gradient(pressure, hard_bcs.extrapolation, type=type(hard_bcs)) + valid_grad = grad * field.bake_extrapolation(hard_bcs).with_extrapolation(grad.extrapolation) div = divergence(valid_grad) laplace = where(active, div, pressure) else: From 0e94e66d8a21865d4fce2fa314c975262f89e3c3 Mon Sep 17 00:00:00 2001 From: Elias Djossou Date: Thu, 5 Jan 2023 16:54:35 +0100 Subject: [PATCH 044/170] [math] implement proper extrapolation.NONE treatment for CentralGrids in spatial_gradient and divergence --- phi/field/_field_math.py | 32 +++++++++++++++++++++----------- phi/physics/fluid.py | 4 ++-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index b5a1c8343..8cc2ced07 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -136,7 +136,7 @@ def spatial_gradient(field: CenteredGrid, """ - if gradient_extrapolation is None or gradient_extrapolation: + if gradient_extrapolation is None: gradient_extrapolation = field.extrapolation.spatial_gradient() extrapol_map = {} @@ -173,17 +173,22 @@ def spatial_gradient(field: CenteredGrid, if scheme.is_implicit: gradient_extrapolation = map(_ex_map_f(extrapol_map_rhs), gradient_extrapolation) + spatial_dims = field.shape.spatial.names if type == CenteredGrid: # ToDo if extrapolation == math.extrapolation.NONE, extend size by 1 # pad = 1 if extrapolation == math.extrapolation.NONE else 0 # bounds = Box(field.bounds.lower - field.dx, field.bounds.upper + field.dx) if extrapolation == math.extrapolation.NONE else field.bounds - padded_components = [pad(field, {dim: base_widths}) for dim in field.shape.spatial.names] + std_widths = (0, 0) + if gradient_extrapolation == math.extrapolation.NONE: + base_widths = (abs(min(needed_shifts))+1, max(needed_shifts)+1) + std_widths = (1, 1) + padded_components = [pad(field, {dim_: base_widths if dim_ == dim else std_widths for dim_ in spatial_dims}) for dim in spatial_dims] else: base_widths = (base_widths[0], base_widths[1]-1) padded_components = pad_for_staggered_output(field, gradient_extrapolation, field.shape.spatial.names, base_widths) - shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, field.shape.spatial.names)] + shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, spatial_dims)] result_components = [sum([value * shift for value, shift in zip(values, shifted_component)]) / field.dx.vector[dim] for shifted_component, dim in zip(shifted_components, field.shape.spatial.names)] if type == CenteredGrid: @@ -200,8 +205,12 @@ def spatial_gradient(field: CenteredGrid, result = solve_linear(_lhs_for_implicit_scheme, result, solve=scheme.solve, f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, "stack_dim": stack_dim, "staggered_output": type != CenteredGrid}) + if type == CenteredGrid and gradient_extrapolation == math.extrapolation.NONE: + result = result.with_bounds(Box(field.bounds.lower - field.dx, field.bounds.upper + field.dx)) + else: + result = result.with_bounds(field.bounds) - return result.with_bounds(field.bounds) + return result def _ex_map_f(ext_dict: dict): def f(ext: Extrapolation): @@ -364,7 +373,7 @@ def divergence(field: Grid, scheme: Scheme = Scheme(2)) -> CenteredGrid: base_widths = (abs(min(needed_shifts)), max(needed_shifts)) field.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) - + spatial_dims = field.shape.spatial.names if isinstance(field, StaggeredGrid): base_widths = (base_widths[0]+1, base_widths[1]) padded_components = [] @@ -373,10 +382,12 @@ def divergence(field: Grid, scheme: Scheme = Scheme(2)) -> CenteredGrid: padding_widths = (base_widths[0] - border_valid[0], base_widths[1] - border_valid[1]) padded_components.append(pad(component, {dim: padding_widths})) elif isinstance(field, CenteredGrid): - padded_components = [pad(component, {dim: base_widths}) for dim, component in zip(field.shape.spatial.names, unstack(field, 'vector'))] + padded_components = [pad(component, {dim: base_widths}) for dim, component in zip(spatial_dims, unstack(field, 'vector'))] + if field.extrapolation == math.extrapolation.NONE: + padded_components = [pad(component, {dim_: (0, 0) if dim_ == dim else (-1, -1) for dim_ in spatial_dims}) for dim, component in zip(spatial_dims, padded_components)] - shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, field.shape.spatial.names)] - result_components = [sum([value * shift for value, shift in zip(values, shifted_component)]) / field.dx.vector[dim] for shifted_component, dim in zip(shifted_components, field.shape.spatial.names)] + shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, spatial_dims)] + result_components = [sum([value * shift for value, shift in zip(values, shifted_component)]) / field.dx.vector[dim] for shifted_component, dim in zip(shifted_components, spatial_dims)] if scheme.is_implicit: result_components = stack(result_components, channel('vector')) @@ -389,9 +400,8 @@ def divergence(field: Grid, scheme: Scheme = Scheme(2)) -> CenteredGrid: result_components = [component.with_bounds(field.bounds) for component in result_components] result = sum(result_components) - # ToDo adjust bounds if extrapolation was NONE - # if field.extrapolation == math.extrapolation.NONE: - # result = result.with_bounds(Box(field.bounds.lower + field.dx, field.bounds.upper - field.dx)) + if field.extrapolation == math.extrapolation.NONE and isinstance(field, CenteredGrid): + result = result.with_bounds(Box(field.bounds.lower + field.dx, field.bounds.upper - field.dx)) return result diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index 449c45e11..7dda9d62a 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -132,8 +132,8 @@ def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid, `CenteredGrid` """ if scheme.order == 2 and not scheme.is_implicit: - grad = spatial_gradient(pressure, hard_bcs.extrapolation, type=type(hard_bcs)) - valid_grad = grad * field.bake_extrapolation(hard_bcs).with_extrapolation(grad.extrapolation) + grad = spatial_gradient(pressure, extrapolation.NONE, type=type(hard_bcs)) + valid_grad = grad * field.bake_extrapolation(hard_bcs) div = divergence(valid_grad) laplace = where(active, div, pressure) else: From 965323d9e4de94921d1f2a3ee0fd5cf3be1b14ba Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 11 Jan 2023 13:38:01 +0100 Subject: [PATCH 045/170] [math] Add Shape.non_uniform --- phi/math/_shape.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 7c9b6f7f4..3c3003367 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -706,6 +706,15 @@ def is_non_uniform(self) -> bool: return True return False + @property + def non_uniform(self) -> 'Shape': + """ + Returns only the non-uniform dimensions of this shape, i.e. the dimensions whose size varies along another dimension. + """ + from phi.math import Tensor + indices = [i for i, size in enumerate(self.sizes) if isinstance(size, Tensor) and size.rank > 0] + return self[indices] + def with_size(self, size: int or None): """ Only for single-dimension shapes. From 5f1719bdbc467fa1137fb2d40aa603b2885b24e7 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 11 Jan 2023 13:38:51 +0100 Subject: [PATCH 046/170] [math] Fix pack_dims for non-uniform tensors For simple cases only, i.e. one non-uniform dimension --- phi/math/_tensors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index 080a2e14e..ab3fd7e9b 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -500,8 +500,8 @@ def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or No value = cached(self) assert isinstance(value, TensorStack) assert value.stack_dim.name in dims - concat_dim = value.shape.without(value.stack_dim)[0] - c = concat_tensor(value._tensors, concat_dim) + concat_dim = (value.shape.without(value.stack_dim).non_uniform or value.shape.without(value.stack_dim))[0] + c = concat_tensor(value._tensors, concat_dim.name) return pack_dims(c, [d for d in dims if d != value.stack_dim.name], packed_dim, pos=pos) def __cast__(self, dtype: DType): From 21d66124e64d70014c95cfa3cc12531c509ef770 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 11 Jan 2023 13:39:57 +0100 Subject: [PATCH 047/170] [vis] Line plots for PointClouds --- phi/vis/_dash/_plotly_plots.py | 2 ++ phi/vis/_matplotlib/_matplotlib_plots.py | 29 ++++++++++++++++++------ tests/commit/vis/test__plots.py | 11 ++++++++- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/phi/vis/_dash/_plotly_plots.py b/phi/vis/_dash/_plotly_plots.py index dccce949e..a3fd37a4c 100644 --- a/phi/vis/_dash/_plotly_plots.py +++ b/phi/vis/_dash/_plotly_plots.py @@ -170,6 +170,8 @@ def _plot(data: SampledField, for d in data_list: _plot(d, fig, size, colormap, show_color_bar, vmin, vmax, row=row, col=col) else: + if spatial(data): + raise NotImplementedError("Plotly does not yet support plotting point clouds with spatial dimensions") x, y = math.reshaped_numpy(data.points.vector[dims], [vector, data.shape.non_channel]) color = data.color.native() subplot_height = (subplot.yaxis.domain[1] - subplot.yaxis.domain[0]) * size[1] diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index 9724be9da..36b5aaa9c 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -17,6 +17,7 @@ from phi.field import Grid, StaggeredGrid, PointCloud, Scene, SampledField from phi.field._scene import _str from phi.geom import Sphere, BaseBox, Point, Box +from phi.geom._stack import GeometryStack from phi.math import Tensor, batch, channel, spatial, instance, non_channel from phi.math.backend import PHI_LOGGER from phi.vis._plot_util import smooth_uniform_curve @@ -226,7 +227,7 @@ def _plot(axis, data: SampledField, space: Box, show_color_bar, vmin, vmax, **pl norm = matplotlib.colors.Normalize(vmin=np.min(values), vmax=np.max(values)) colors = cmap(norm(values)) axis.voxels(x, y, z, values, facecolors=colors, edgecolor='k') - elif isinstance(data, PointCloud) and data.spatial_rank == 2 and 'vector' in channel(data): + elif isinstance(data, PointCloud) and data.spatial_rank == 2 and 'vector' in channel(data): # vector cloud axis.set_aspect('equal', adjustable='box') vector = data.points.shape['vector'] x, y = math.reshaped_numpy(data.points, [vector, data.shape.without('vector')]) @@ -236,10 +237,10 @@ def _plot(axis, data: SampledField, space: Box, show_color_bar, vmin, vmax, **pl else: color = data.color.native() axis.quiver(x, y, u, v, color=color, units='xy', scale=1) - elif isinstance(data, PointCloud) and data.spatial_rank == 2: + elif isinstance(data, PointCloud) and data.spatial_rank == 2: # point cloud axis.set_aspect('equal', adjustable='box') - if data.points.shape.without('vector').rank > 1: # multiple instance / spatial dimensions - data_list = field.unstack(data, data.points.shape.without('vector')[0].name) + if channel(data.points).without('vector'): # multiple channel dimensions + data_list = field.unstack(data, channel(data.points).without('vector')[0].name) for d in data_list: _plot_points(axis, d, dims, vector, **plt_args) else: @@ -273,9 +274,20 @@ def _plot(axis, data: SampledField, space: Box, show_color_bar, vmin, vmax, **pl def _plot_points(axis, data: PointCloud, dims, vector, **plt_args): x, y = math.reshaped_numpy(data.points.vector[dims], [vector, data.shape.non_channel]) - color = [d.native() for d in data.color.points.unstack(len(x))] - if isinstance(data.elements, Point): - axis.scatter(x, y, marker='x', color=color, s=6 ** 2, alpha=0.8) + if data.color.dtype.kind == int: + cycle = list(plt.rcParams['axes.prop_cycle'].by_key()['color']) + color = [cycle[int(d)] for d in data.color.points.unstack(len(x))] + else: + color = [d.native() for d in data.color.points.unstack(len(x))] + if isinstance(data.elements, GeometryStack): + stack_dim = data.elements.geometries.shape[0] + parts = math.unstack(data, stack_dim) + for part in parts: + _plot_points(axis, part, dims, vector, **plt_args) + return + elif isinstance(data.elements, Point): + if spatial(data.points).is_empty: + axis.scatter(x, y, marker='x', color=color, s=6 ** 2, alpha=0.8) else: if isinstance(data.elements, Sphere): rad = math.reshaped_numpy(data.elements.bounding_radius(), [data.shape.non_channel], force_expand=True) @@ -288,6 +300,9 @@ def _plot_points(axis, data: PointCloud, dims, vector, **plt_args): shapes = [plt.Circle((xi, yi), radius=ri, linewidth=0, alpha=0.8, facecolor=ci) for xi, yi, ri, ci in zip(x, y, rad, color)] c = matplotlib.collections.PatchCollection(shapes, match_original=True) axis.add_collection(c) + if spatial(data.points): # Connect by line + x, y = math.reshaped_numpy(data.points.vector[dims], [vector, spatial(data), instance(data)]) + axis.plot(x, y, color=color[0]) if non_channel(data).rank == 1 and non_channel(data).item_names[0]: _annotate_points(axis, data.points, non_channel(data)) diff --git a/tests/commit/vis/test__plots.py b/tests/commit/vis/test__plots.py index 24333aec4..a4edc15a7 100644 --- a/tests/commit/vis/test__plots.py +++ b/tests/commit/vis/test__plots.py @@ -5,7 +5,7 @@ from phi import geom, field, math from phi.field import CenteredGrid, StaggeredGrid, PointCloud, Noise, SoftGeometryMask from phi.geom import Sphere, Box -from phi.math import extrapolation, wrap, instance, channel, batch, spatial +from phi.math import extrapolation, wrap, instance, channel, batch, spatial, vec, stack from phi.vis import show, overlay, plot, close import matplotlib.pyplot as plt @@ -124,6 +124,15 @@ def test_plot_staggered_grid_3d(self): def test_plot_point_cloud_3d_points(self): self._test_plot(PointCloud(math.random_normal(instance(points=5), channel(vector='x,y,z')))) + def test_plot_arbitrary_lines(self): + points = vec(resolution=wrap([0, 1, 4], spatial('line')), error=wrap([0, 1, .5], spatial('line'))) + points = stack([points, points + (0, -1)], instance('disconnected')) + points = stack([points, points * (1, -1)], channel('categories')) + try: + self._test_plot(PointCloud(points, color=wrap([0, 1], channel('categories')))) + except NotImplementedError: + pass + def test_animate(self): values = math.random_uniform(batch(time=3), spatial(x=32, y=32)) anim = plot(values, animate='time', show_color_bar=False, frame_time=100, lib='matplotlib') From 4f23b4cb9a2470abf5c87df578a8aba036c73c96 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 13 Jan 2023 14:42:46 +0100 Subject: [PATCH 048/170] [math] use layout when tensor does not work --- phi/math/_tensors.py | 29 +++++++++++++++-------------- tests/commit/math/test__tensors.py | 9 +++++++++ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index ab3fd7e9b..1e5b2c85e 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -1596,20 +1596,21 @@ def tensor(data: Tensor or Shape or tuple or list or numbers.Number, assert array.dtype != object data = array elif all(isinstance(d, str) for d in data): - if shape: - return layout(data, shape) - else: - return layout(data, channel('vector')) - else: - inner_shape = [] if shape is None else [shape[1:]] - tensors = [d if isinstance(d, Tensor) else tensor(d, *inner_shape, convert=convert) for d in data] - common_shape = merge_shapes(*[e.shape for e in tensors]) - stack_dim = default_list_dim if shape is None else shape[0].with_sizes([len(tensors)]) - assert all(stack_dim not in t.shape for t in tensors), f"Cannot stack tensors with dimension '{stack_dim}' because a tensor already has that dimension." - elements = [CollapsedTensor(e, common_shape) if e.shape.rank < common_shape.rank else e for e in tensors] - from ._ops import cast_same - elements = cast_same(*elements) - return TensorStack(elements, stack_dim) + return layout(data, shape or default_list_dim) + else: + try: + inner_shape = [] if shape is None else [shape[1:]] + tensors = [d if isinstance(d, Tensor) else tensor(d, *inner_shape, convert=convert) for d in data] + common_shape = merge_shapes(*[e.shape for e in tensors]) + stack_dim = default_list_dim if shape is None else shape[0].with_sizes([len(tensors)]) + assert all(stack_dim not in t.shape for t in tensors), f"Cannot stack tensors with dimension '{stack_dim}' because a tensor already has that dimension." + elements = [CollapsedTensor(e, common_shape) if e.shape.rank < common_shape.rank else e for e in tensors] + from ._ops import cast_same + elements = cast_same(*elements) + return TensorStack(elements, stack_dim) + except ValueError: + assert not convert, f"Cannot convert {data} to tensor" + return layout(data, shape or default_list_dim) try: backend = choose_backend(data) if shape is None: diff --git a/tests/commit/math/test__tensors.py b/tests/commit/math/test__tensors.py index ccad5bf9a..29bcde78f 100644 --- a/tests/commit/math/test__tensors.py +++ b/tests/commit/math/test__tensors.py @@ -637,3 +637,12 @@ def test_tensor_expand_vararg(self): self.assertEqual(zeros.shape, wrapped.shape) self.assertEqual(zeros.shape, tens.shape) + def test_auto_layout(self): + t = wrap(['a', object()]) + self.assertEqual(channel(vector=2), t.shape) + try: + tensor(['a', object()]) + raise RuntimeError + except AssertionError: + pass + From 8723b340e28b35762d3e71a109c56ab7a16b6693 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 13 Jan 2023 16:30:27 +0100 Subject: [PATCH 049/170] [learning] Expose layer count and periodicity of networks --- phi/jax/stax/nets.py | 335 +++++++++++++++------------------- phi/tf/nets.py | 168 ++++++++--------- phi/torch/nets.py | 194 ++++++++++---------- tests/commit/test_networks.py | 12 +- 4 files changed, 326 insertions(+), 383 deletions(-) diff --git a/phi/jax/stax/nets.py b/phi/jax/stax/nets.py index b463827ed..14adfde9b 100644 --- a/phi/jax/stax/nets.py +++ b/phi/jax/stax/nets.py @@ -14,6 +14,7 @@ import jax.numpy as jnp import keras import numpy +import numpy as np from jax import random from packaging import version @@ -145,10 +146,10 @@ def get_parameters(model: StaxNet, wrap=True) -> dict: def _recursive_add_parameters(param, wrap: bool, prefix: tuple, result: dict): if isinstance(param, dict): for name, obj in param.items(): - _recursive_add_parameters(obj, wrap, prefix + (name,), result) + _recursive_add_parameters(obj, wrap, prefix + (str(name),), result) elif isinstance(param, (tuple, list)): for i, obj in enumerate(param): - _recursive_add_parameters(obj, wrap, prefix + (i,), result) + _recursive_add_parameters(obj, wrap, prefix + (str(i),), result) else: rank = len(param.shape) if prefix[-1] == 0 and rank == 2: @@ -164,8 +165,14 @@ def _recursive_add_parameters(param, wrap: bool, prefix: tuple, result: dict): phi_tensor = math.wrap(param, math.channel('output')) elif rank == 2: phi_tensor = math.wrap(param, math.channel('input,output')) + elif rank == 3: + phi_tensor = math.wrap(param, math.channel('x,input,output')) + elif rank == 4: + phi_tensor = math.wrap(param, math.channel('x,y,input,output')) + elif rank == 5: + phi_tensor = math.wrap(param, math.channel('x,y,z,input,output')) else: - raise NotImplementedError + raise NotImplementedError(rank) result[name] = phi_tensor @@ -280,7 +287,8 @@ def dense_net(in_channels: int, out_channels: int, layers: Tuple[int, ...] or List[int], batch_norm=False, - activation='ReLU') -> StaxNet: + activation='ReLU', + softmax=False) -> StaxNet: """ Fully-connected neural networks are available in ΦFlow via dense_net(). Arguments: @@ -301,6 +309,8 @@ def dense_net(in_channels: int, if batch_norm: stax_layers.append(stax.BatchNorm(axis=(0,))) stax_layers.append(stax.Dense(out_channels)) + if softmax: + stax_layers.append(stax.elementwise(stax.softmax, axis=-1)) net_init, net_apply = stax.serial(*stax_layers) net = StaxNet(net_init, net_apply, (-1, in_channels)) net.initialize() @@ -314,6 +324,7 @@ def u_net(in_channels: int, batch_norm: bool = True, activation='ReLU', in_spatial: tuple or int = 2, + periodic=False, use_res_blocks: bool = False) -> StaxNet: """ ΦFlow provides a built-in U-net architecture, classically popular for Semantic Segmentation in Computer Vision, composed of downsampling and upsampling layers. @@ -347,22 +358,17 @@ def u_net(in_channels: int, d = len(in_spatial) # Create layers if use_res_blocks: - inc_init, inc_apply = resnet_block(in_channels, filters[0], batch_norm, activation, d) + inc_init, inc_apply = resnet_block(in_channels, filters[0], periodic, batch_norm, activation, d) else: - inc_init, inc_apply = create_double_conv(d, filters[0], filters[0], batch_norm, activation) + inc_init, inc_apply = create_double_conv(d, filters[0], filters[0], batch_norm, activation, periodic) init_functions, apply_functions = {}, {} for i in range(1, levels): if use_res_blocks: - init_functions[f'down{i}'], apply_functions[f'down{i}'] = resnet_block(filters[i - 1], filters[i], - batch_norm, activation, d) - init_functions[f'up{i}'], apply_functions[f'up{i}'] = resnet_block(filters[i] + filters[i - 1], - filters[i - 1], batch_norm, activation, - d) + init_functions[f'down{i}'], apply_functions[f'down{i}'] = resnet_block(filters[i - 1], filters[i], periodic, batch_norm, activation, d) + init_functions[f'up{i}'], apply_functions[f'up{i}'] = resnet_block(filters[i] + filters[i - 1], filters[i - 1], periodic, batch_norm, activation, d) else: - init_functions[f'down{i}'], apply_functions[f'down{i}'] = create_double_conv(d, filters[i], filters[i], - batch_norm, activation) - init_functions[f'up{i}'], apply_functions[f'up{i}'] = create_double_conv(d, filters[i - 1], filters[i - 1], - batch_norm, activation) + init_functions[f'down{i}'], apply_functions[f'down{i}'] = create_double_conv(d, filters[i], filters[i], batch_norm, activation, periodic) + init_functions[f'up{i}'], apply_functions[f'up{i}'] = create_double_conv(d, filters[i - 1], filters[i - 1], batch_norm, activation, periodic) outc_init, outc_apply = CONV[d](out_channels, (1,) * d, padding='same') max_pool_init, max_pool_apply = stax.MaxPool((2,) * d, padding='same', strides=(2,) * d) _, up_apply = create_upsample() @@ -425,16 +431,10 @@ def create_double_conv(d: int, out_channels: int, mid_channels: int, batch_norm: # Periodic Implementation -def create_double_conv(d: int, out_channels: int, mid_channels: int, batch_norm: bool, activation: Callable): +def create_double_conv(d: int, out_channels: int, mid_channels: int, batch_norm: bool, activation: Callable, periodic: bool): init_fn, apply_fn = {}, {} - init_fn['conv1'], apply_fn['conv1'] = stax.serial(CONV[d](mid_channels, (3,) * d, padding='valid'), - stax.BatchNorm( - axis=tuple(range(d + 1))) if batch_norm else stax.Identity, - activation) - init_fn['conv2'], apply_fn['conv2'] = stax.serial(CONV[d](mid_channels, (3,) * d, padding='valid'), - stax.BatchNorm( - axis=tuple(range(d + 1))) if batch_norm else stax.Identity, - activation) + init_fn['conv1'], apply_fn['conv1'] = stax.serial(CONV[d](mid_channels, (3,) * d, padding='valid'), stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, activation) + init_fn['conv2'], apply_fn['conv2'] = stax.serial(CONV[d](mid_channels, (3,) * d, padding='valid'), stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, activation) def net_init(rng, input_shape): params = {} @@ -447,9 +447,9 @@ def net_init(rng, input_shape): def net_apply(params, inputs): x = inputs pad_tuple = [[0, 0]] + [[1, 1] for i in range(d)] + [[0, 0]] - out = jnp.pad(x, pad_width=pad_tuple, mode='wrap') + out = jnp.pad(x, pad_width=pad_tuple, mode='wrap' if periodic else 'constant') out = apply_fn['conv1'](params['conv1'], out) - out = jnp.pad(out, pad_width=pad_tuple, mode='wrap') + out = jnp.pad(out, pad_width=pad_tuple, mode='wrap' if periodic else 'constant') out = apply_fn['conv2'](params['conv2'], out) return out @@ -468,96 +468,77 @@ def upsample_apply(params, inputs, **kwargs): return NotImplemented, upsample_apply -def conv_classifier(input_shape_list: list, num_classes: int, batch_norm: bool, in_spatial: int or tuple): +def conv_classifier(in_features: int, + in_spatial: tuple or list, + num_classes: int, + blocks=(64, 128, 256, 256, 512, 512), + dense_layers=(4096, 4096, 100), + batch_norm=True, + activation='ReLU', + softmax=True, + periodic=False): + """ + Based on VGG16. + """ if isinstance(in_spatial, int): d = in_spatial in_spatial = (1,) * d else: assert isinstance(in_spatial, tuple) d = len(in_spatial) - stax_conv_layers = [] + activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation stax_dense_layers = [] - spatial_shape_list = list(input_shape_list[1:]) - in_channels = input_shape_list[0] - channels = [64, 128, 256, 512, 512] init_fn, apply_fn = {}, {} - init_fn['conv1'], apply_fn['conv1'] = create_double_conv(d, 64, 64, batch_norm, ACTIVATIONS['ReLU']) - init_fn['max_pool1'], apply_fn['max_pool1'] = stax.MaxPool((2,) * d, padding='valid', strides=(2,) * d) - - init_fn['conv2'], apply_fn['conv2'] = create_double_conv(d, 128, 128, batch_norm, ACTIVATIONS['ReLU']) - init_fn['max_pool2'], apply_fn['max_pool2'] = stax.MaxPool((2,) * d, padding='valid', strides=(2,) * d) - - init_fn['conv3_1'], apply_fn['conv3_1'] = create_double_conv(d, 256, 256, batch_norm, ACTIVATIONS['ReLU']) - init_fn['conv3_2'], apply_fn['conv3_2'] = stax.serial(CONV[d](256, (3,) * d, padding='valid'), - stax.BatchNorm(axis=tuple( - range(d + 1))) if batch_norm else stax.Identity, - ACTIVATIONS['ReLU']) - - init_fn['max_pool3'], apply_fn['max_pool3'] = stax.MaxPool((2,) * d, padding='valid', strides=(2,) * d) - - init_fn['conv4_1'], apply_fn['conv4_1'] = create_double_conv(d, 512, 512, batch_norm, ACTIVATIONS['ReLU']) - init_fn['conv4_2'], apply_fn['conv4_2'] = stax.serial(CONV[d](512, (3,) * d, padding='valid'), - stax.BatchNorm(axis=tuple( - range(d + 1))) if batch_norm else stax.Identity, - ACTIVATIONS['ReLU']) - init_fn['max_pool4'], apply_fn['max_pool4'] = stax.MaxPool((2,) * d, padding='valid', strides=(2,) * d) - - init_fn['conv5_1'], apply_fn['conv5_1'] = create_double_conv(d, 512, 512, batch_norm, ACTIVATIONS['ReLU']) - init_fn['conv5_2'], apply_fn['conv5_2'] = stax.serial(CONV[d](512, (3,) * d, padding='valid'), - stax.BatchNorm(axis=tuple( - range(d + 1))) if batch_norm else stax.Identity, - ACTIVATIONS['ReLU']) - init_fn['max_pool5'], apply_fn['max_pool5'] = stax.MaxPool((2,) * d, padding='valid', strides=(2,) * d) - - net_list = ['conv1', 'max_pool1', 'conv2', 'max_pool2', - 'conv3_1', 'conv3_2', 'max_pool3', - 'conv4_1', 'conv4_2', 'max_pool4', - 'conv5_1', 'conv5_2', 'max_pool5'] - init_fn['flatten'], apply_fn['flatten'] = stax.Flatten - dense_layers = [4096, 4096, 100] + net_list = [] + for i, (prev, next) in enumerate(zip((in_features,) + blocks[:-1], blocks)): + if i in (0, 1): + net_list.append(f'conv{i+1}') + init_fn[net_list[-1]], apply_fn[net_list[-1]] = create_double_conv(d, next, next, batch_norm, activation, periodic) + else: + net_list.append(f'conv{i+1}_1') + init_fn[net_list[-1]], apply_fn[net_list[-1]] = create_double_conv(d, 256, 256, batch_norm, activation, periodic) + net_list.append(f'conv{i+1}_2') + init_fn[net_list[-1]], apply_fn[net_list[-1]] = stax.serial(CONV[d](256, (3,) * d, padding='valid'), + stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, + activation) + net_list.append(f'max_pool{i+1}') + init_fn[net_list[-1]], apply_fn[net_list[-1]] = stax.MaxPool((2,) * d, padding='valid', strides=(2,) * d) + init_fn['flatten'], apply_fn['flatten'] = stax.Flatten for i, neuron_count in enumerate(dense_layers): stax_dense_layers.append(stax.Dense(neuron_count)) - stax_dense_layers.append(ACTIVATIONS['ReLU']) + stax_dense_layers.append(activation) if batch_norm: stax_dense_layers.append(stax.BatchNorm(axis=(0,))) stax_dense_layers.append(stax.Dense(num_classes)) - softmax = stax.elementwise(stax.softmax, axis=-1) - stax_dense_layers.append(softmax) + if softmax: + stax_dense_layers.append(stax.elementwise(stax.softmax, axis=-1)) dense_init, dense_apply = stax.serial(*stax_dense_layers) def net_init(rng, input_shape): params = {} rngs = random.split(rng, 2) - for i in range(5): - for j in range(len(spatial_shape_list)): - spatial_shape_list[j] = math.floor((spatial_shape_list[j] - 2) / 2) + 1 - flattened_input_dim = 1 - for i in range(len(spatial_shape_list)): - flattened_input_dim *= spatial_shape_list[i] - flattened_input_dim *= 512 - flattened_input_dim = int(flattened_input_dim) shape = input_shape N = len(net_list) for i in range(N): - shape, params[f'{net_list[i]}'] = \ - init_fn[f'{net_list[i]}'](rngs[i], shape) + shape, params[f'{net_list[i]}'] = init_fn[f'{net_list[i]}'](rngs[i], shape) shape, params['flatten'] = init_fn['flatten'](rngs[N], shape) - shape, params['dense'] = dense_init(rngs[N + 1], (1,) + (flattened_input_dim,)) + flat_size = int(np.prod(in_spatial) * blocks[-1] / (2**d) ** len(blocks)) + shape, params['dense'] = dense_init(rngs[N + 1], (1,) + (flat_size,)) return shape, params def net_apply(params, inputs, **kwargs): x = inputs - pad_tuple = [[0, 0]] + [[1, 1] for i in range(d)] + [[0, 0]] + pad_tuple = [[0, 0]] + [[1, 1]] * d + [[0, 0]] for i in range(len(net_list)): if net_list[i] in ['conv3_2', 'conv4_2', 'conv5_2']: - x = jnp.pad(x, pad_width=pad_tuple, mode='wrap') + x = jnp.pad(x, pad_width=pad_tuple, mode='wrap' if periodic else 'constant') x = apply_fn[f'{net_list[i]}'](params[f'{net_list[i]}'], x) x = apply_fn['flatten'](params['flatten'], x) out = dense_apply(params['dense'], x, **kwargs) return out - net = StaxNet(net_init, net_apply, (1,) + in_spatial + (in_channels,)) + net = StaxNet(net_init, net_apply, (1,) + in_spatial + (in_features,)) net.initialize() return net @@ -567,6 +548,7 @@ def conv_net(in_channels: int, layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or Callable = 'ReLU', + periodic=False, in_spatial: int or tuple = 2) -> StaxNet: """ Built in Conv-Nets are also provided. Contrary to the classical convolutional neural networks, the feature map spatial size remains the same throughout the layers. Each layer of the network is essentially a convolutional block comprising of two conv layers. A filter size of 3 is used in the convolutional layers. @@ -583,26 +565,26 @@ def conv_net(in_channels: int, Conv-net model as specified by input arguments """ - if isinstance(in_spatial, tuple): + if isinstance(in_spatial, int): d = in_spatial - in_spatial = len(in_spatial) + in_spatial = (1,) * d else: - d = (1,) * in_spatial - if isinstance(activation, str): - activation = ACTIVATIONS[activation] + assert isinstance(in_spatial, tuple) + d = len(in_spatial) + activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation init_fn, apply_fn = {}, {} if len(layers) < 1: layers.append(out_channels) init_fn['conv_in'], apply_fn['conv_in'] = stax.serial( - CONV[in_spatial](layers[0], (3,) * in_spatial, padding='valid'), - stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + CONV[d](layers[0], (3,) * d, padding='valid'), + stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, activation) for i in range(1, len(layers)): init_fn[f'conv{i}'], apply_fn[f'conv{i}'] = stax.serial( - CONV[in_spatial](layers[i], (3,) * in_spatial, padding='valid'), - stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + CONV[d](layers[i], (3,) * d, padding='valid'), + stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, activation) - init_fn['conv_out'], apply_fn['conv_out'] = CONV[in_spatial](out_channels, (1,) * in_spatial) + init_fn['conv_out'], apply_fn['conv_out'] = CONV[d](out_channels, (1,) * d) def net_init(rng, input_shape): params = {} @@ -616,18 +598,18 @@ def net_init(rng, input_shape): def net_apply(params, inputs): x = inputs pad_tuple = [(0, 0)] - for i in range(in_spatial): + for i in range(d): pad_tuple.append((1, 1)) pad_tuple.append((0, 0)) - out = jnp.pad(x, pad_width=pad_tuple, mode='wrap') + out = jnp.pad(x, pad_width=pad_tuple, mode='wrap' if periodic else 'constant') out = apply_fn['conv_in'](params['conv_in'], out) for i in range(1, len(layers)): - out = jnp.pad(out, pad_width=pad_tuple, mode='wrap') + out = jnp.pad(out, pad_width=pad_tuple, mode='wrap' if periodic else 'constant') out = apply_fn[f'conv{i + 1}'](params[f'conv{i + 1}'], out) out = apply_fn['conv_out'](params['conv_out'], out) return out - net = StaxNet(net_init, net_apply, (1,) + d + (in_channels,)) + net = StaxNet(net_init, net_apply, (1,) + in_spatial + (in_channels,)) net.initialize() return net @@ -637,6 +619,7 @@ def res_net(in_channels: int, layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or Callable = 'ReLU', + periodic=False, in_spatial: int or tuple = 2) -> StaxNet: """ Built in Res-Nets are provided in the ΦFlow framework. Similar to the conv-net, the feature map spatial size remains the same throughout the layers. @@ -656,56 +639,51 @@ def res_net(in_channels: int, Res-net model as specified by input arguments """ - if isinstance(in_spatial, tuple): + if isinstance(in_spatial, int): d = in_spatial - in_spatial = len(in_spatial) + in_spatial = (1,) * d else: - d = (1,) * in_spatial - + assert isinstance(in_spatial, tuple) + d = len(in_spatial) activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation stax_layers = [] if len(layers) > 0: - stax_layers.append(resnet_block(in_channels, layers[0], batch_norm, activation, in_spatial)) + stax_layers.append(resnet_block(in_channels, layers[0], periodic, batch_norm, activation, d)) for i in range(1, len(layers)): - stax_layers.append(resnet_block(layers[i - 1], layers[i], batch_norm, activation, in_spatial)) + stax_layers.append(resnet_block(layers[i - 1], layers[i], periodic, batch_norm, activation, d)) - stax_layers.append(resnet_block(layers[len(layers) - 1], out_channels, batch_norm, activation, in_spatial)) + stax_layers.append(resnet_block(layers[len(layers) - 1], out_channels, periodic, batch_norm, activation, d)) else: - stax_layers.append(resnet_block(in_channels, out_channels, batch_norm, activation, in_spatial)) + stax_layers.append(resnet_block(in_channels, out_channels, periodic, batch_norm, activation, d)) net_init, net_apply = stax.serial(*stax_layers) - net = StaxNet(net_init, net_apply, (1,) + d + (in_channels,)) + net = StaxNet(net_init, net_apply, (1,) + in_spatial + (in_channels,)) net.initialize() return net def resnet_block(in_channels: int, out_channels: int, + periodic: bool, batch_norm: bool, activation: str or Callable = 'ReLU', - in_spatial: int or tuple = 2): - if isinstance(in_spatial, int): - d = (1,) * in_spatial - else: - d = in_spatial - in_spatial = len(in_spatial) - + d: int or tuple = 2): activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation init_fn, apply_fn = {}, {} init_fn['conv1'], apply_fn['conv1'] = stax.serial( - CONV[in_spatial](out_channels, (3,) * in_spatial, padding='valid'), - stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + CONV[d](out_channels, (3,) * d, padding='valid'), + stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, activation) init_fn['conv2'], apply_fn['conv2'] = stax.serial( - CONV[in_spatial](out_channels, (3,) * in_spatial, padding='valid'), - stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + CONV[d](out_channels, (3,) * d, padding='valid'), + stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, activation) init_activation, apply_activation = activation if in_channels != out_channels: init_fn['sample_conv'], apply_fn['sample_conv'] = stax.serial( - CONV[in_spatial](out_channels, (1,) * in_spatial, padding='VALID'), - stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity) + CONV[d](out_channels, (1,) * d, padding='VALID'), + stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity) else: init_fn['sample_conv'], apply_fn['sample_conv'] = stax.Identity @@ -723,11 +701,11 @@ def net_init(rng, input_shape): def net_apply(params, inputs, **kwargs): x = inputs - pad_tuple = [[0, 0]] + [[1, 1] for i in range(in_spatial)] + [[0, 0]] + pad_tuple = [[0, 0]] + [[1, 1] for i in range(d)] + [[0, 0]] - out = jnp.pad(x, pad_width=pad_tuple, mode='wrap') + out = jnp.pad(x, pad_width=pad_tuple, mode='wrap' if periodic else 'constant') out = apply_fn['conv1'](params['conv1'], out) - out = jnp.pad(out, pad_width=pad_tuple, mode='wrap') + out = jnp.pad(out, pad_width=pad_tuple, mode='wrap' if periodic else 'constant') out = apply_fn['conv2'](params['conv2'], out) skip_x = apply_fn['sample_conv'](params['sample_conv'], x, **kwargs) out = jnp.add(out, skip_x) @@ -818,15 +796,16 @@ def net_apply(params, inputs, **kwargs): def conv_net_unit(in_channels: int, out_channels: int, layers: Tuple[int, ...] or List[int, ...], + periodic: bool = False, batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2, **kwargs): """ Conv-net unit for Invertible Nets""" - if isinstance(in_spatial, tuple): + if isinstance(in_spatial, int): d = in_spatial - in_spatial = len(in_spatial) else: - d = (1,) * in_spatial + assert isinstance(in_spatial, tuple) + d = len(in_spatial) if isinstance(activation, str): activation = ACTIVATIONS[activation] @@ -834,16 +813,16 @@ def conv_net_unit(in_channels: int, if len(layers) < 1: layers.append(out_channels) init_fn['conv_in'], apply_fn['conv_in'] = stax.serial( - CONV[in_spatial](layers[0], (3,) * in_spatial, padding='valid'), - stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + CONV[d](layers[0], (3,) * d, padding='valid'), + stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, activation) for i in range(1, len(layers)): init_fn[f'conv{i}'], apply_fn[f'conv{i}'] = stax.serial( - CONV[in_spatial](layers[i], (3,) * in_spatial, padding='valid'), - stax.BatchNorm(axis=tuple(range(in_spatial + 1))) if batch_norm else stax.Identity, + CONV[d](layers[i], (3,) * d, padding='valid'), + stax.BatchNorm(axis=tuple(range(d + 1))) if batch_norm else stax.Identity, activation) - init_fn['conv_out'], apply_fn['conv_out'] = CONV[in_spatial](out_channels, (1,) * in_spatial) + init_fn['conv_out'], apply_fn['conv_out'] = CONV[d](out_channels, (1,) * d) def net_init(rng, input_shape): params = {} @@ -862,16 +841,16 @@ def net_apply(params, inputs): x = inputs pad_tuple = [(0, 0)] - for i in range(in_spatial): + for i in range(d): pad_tuple.append((1, 1)) pad_tuple.append((0, 0)) - out = jnp.pad(x, pad_width=pad_tuple, mode='wrap') + out = jnp.pad(x, pad_width=pad_tuple, mode='wrap' if periodic else 'constant') out = apply_fn['conv_in'](params['conv_in'], out) for i in range(1, len(layers)): - out = jnp.pad(out, pad_width=pad_tuple, mode='wrap') + out = jnp.pad(out, pad_width=pad_tuple, mode='wrap' if periodic else 'constant') out = apply_fn[f'conv{i + 1}'](params[f'conv{i + 1}'], out) out = apply_fn['conv_out'](params['conv_out'], out) @@ -887,6 +866,7 @@ def u_net_unit(in_channels: int, filters: int or tuple or list = 16, batch_norm: bool = True, activation='ReLU', + periodic=False, in_spatial: tuple or int = 2, use_res_blocks: bool = False, **kwargs): """ U-net unit for Invertible Nets""" @@ -903,22 +883,17 @@ def u_net_unit(in_channels: int, d = len(in_spatial) # Create layers if use_res_blocks: - inc_init, inc_apply = resnet_block(in_channels, filters[0], batch_norm, activation, d) + inc_init, inc_apply = resnet_block(in_channels, filters[0], periodic, batch_norm, activation, d) else: - inc_init, inc_apply = create_double_conv(d, filters[0], filters[0], batch_norm, activation) + inc_init, inc_apply = create_double_conv(d, filters[0], filters[0], batch_norm, activation, periodic) init_functions, apply_functions = {}, {} for i in range(1, levels): if use_res_blocks: - init_functions[f'down{i}'], apply_functions[f'down{i}'] = resnet_block(filters[i - 1], filters[i], - batch_norm, activation, d) - init_functions[f'up{i}'], apply_functions[f'up{i}'] = resnet_block(filters[i] + filters[i - 1], - filters[i - 1], batch_norm, activation, - d) + init_functions[f'down{i}'], apply_functions[f'down{i}'] = resnet_block(filters[i - 1], filters[i], periodic, batch_norm, activation, d) + init_functions[f'up{i}'], apply_functions[f'up{i}'] = resnet_block(filters[i] + filters[i - 1], filters[i - 1], periodic, batch_norm, activation, d) else: - init_functions[f'down{i}'], apply_functions[f'down{i}'] = create_double_conv(d, filters[i], filters[i], - batch_norm, activation) - init_functions[f'up{i}'], apply_functions[f'up{i}'] = create_double_conv(d, filters[i - 1], filters[i - 1], - batch_norm, activation) + init_functions[f'down{i}'], apply_functions[f'down{i}'] = create_double_conv(d, filters[i], filters[i], batch_norm, activation, periodic) + init_functions[f'up{i}'], apply_functions[f'up{i}'] = create_double_conv(d, filters[i - 1], filters[i - 1], batch_norm, activation, periodic) outc_init, outc_apply = CONV[d](out_channels, (1,) * d, padding='same') max_pool_init, max_pool_apply = stax.MaxPool((2,) * d, padding='same', strides=(2,) * d) _, up_apply = create_upsample() @@ -964,26 +939,22 @@ def res_net_unit(in_channels: int, layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or Callable = 'ReLU', + periodic=False, in_spatial: int or tuple = 2, **kwargs): """ Res-net unit for Invertible Nets""" - if isinstance(in_spatial, tuple): + if isinstance(in_spatial, int): d = in_spatial - in_spatial = len(in_spatial) else: - d = (1,) * in_spatial - + assert isinstance(in_spatial, tuple) + d = len(in_spatial) activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation - stax_layers = [] if len(layers) < 1: layers.append(out_channels) - stax_layers.append(resnet_block(in_channels, layers[0], batch_norm, activation, in_spatial)) - + stax_layers.append(resnet_block(in_channels, layers[0], periodic, batch_norm, activation, d)) for i in range(1, len(layers)): - stax_layers.append(resnet_block(layers[i - 1], layers[i], batch_norm, activation, in_spatial)) - - stax_layers.append(CONV[in_spatial](out_channels, (1,) * in_spatial)) - + stax_layers.append(resnet_block(layers[i - 1], layers[i], periodic, batch_norm, activation, d)) + stax_layers.append(CONV[d](out_channels, (1,) * d)) return stax.serial(*stax_layers) @@ -995,13 +966,16 @@ def coupling_layer(in_channels: int, batch_norm: bool = False, in_spatial: int or tuple = 2, net: str = 'u_net', - reverse_mask: bool = False): - if isinstance(in_spatial, tuple): - in_spatial = len(in_spatial) - + reverse_mask: bool = False, + **kwargs): + if isinstance(in_spatial, int): + d = in_spatial + else: + assert isinstance(in_spatial, tuple) + d = len(in_spatial) activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation init_fn, apply_fn = {}, {} - if in_spatial == 0: + if d == 0: init_fn['s1'], apply_fn['s1'] = stax.serial( Dense_resnet_block(in_channels, in_channels, batch_norm, activation), stax.Tanh) @@ -1012,19 +986,10 @@ def coupling_layer(in_channels: int, stax.Tanh) init_fn['t2'], apply_fn['t2'] = Dense_resnet_block(in_channels, in_channels, batch_norm, activation) else: - init_fn['s1'], apply_fn['s1'] = NET[net](in_channels=in_channels, out_channels=in_channels, - layers=[], batch_norm=batch_norm, activation=activation, - in_spatial=in_spatial) - init_fn['t1'], apply_fn['t1'] = NET[net](in_channels=in_channels, out_channels=in_channels, - layers=[], batch_norm=batch_norm, activation=activation, - in_spatial=in_spatial) - - init_fn['s2'], apply_fn['s2'] = NET[net](in_channels=in_channels, out_channels=in_channels, - layers=[], batch_norm=batch_norm, activation=activation, - in_spatial=in_spatial) - init_fn['t2'], apply_fn['t2'] = NET[net](in_channels=in_channels, out_channels=in_channels, - layers=[], batch_norm=batch_norm, activation=activation, - in_spatial=in_spatial) + init_fn['s1'], apply_fn['s1'] = NET[net](in_channels=in_channels, out_channels=in_channels, layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial, **kwargs) + init_fn['t1'], apply_fn['t1'] = NET[net](in_channels=in_channels, out_channels=in_channels, layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial, **kwargs) + init_fn['s2'], apply_fn['s2'] = NET[net](in_channels=in_channels, out_channels=in_channels, layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial, **kwargs) + init_fn['t2'], apply_fn['t2'] = NET[net](in_channels=in_channels, out_channels=in_channels, layers=[], batch_norm=batch_norm, activation=activation, in_spatial=in_spatial, **kwargs) def net_init(rng, input_shape): params = {} @@ -1105,41 +1070,35 @@ def invertible_net(in_channels: int, Note: Currently supported values for net are 'u_net'(default), 'conv_net' and 'res_net'. For choosing 'dense_net' as the network block in coupling layers in_spatial must be set to zero. """ - if isinstance(in_spatial, tuple): - in_spatial = len(in_spatial) - + if isinstance(in_spatial, int): + d = in_spatial + else: + assert isinstance(in_spatial, tuple) + d = len(in_spatial) init_fn, apply_fn = {}, {} - for i in range(num_blocks): - init_fn[f'CouplingLayer{i + 1}'], apply_fn[f'CouplingLayer{i + 1}'] = \ - coupling_layer(in_channels, activation, batch_norm, in_spatial, net, (i % 2 == 0)) + init_fn[f'CouplingLayer{i + 1}'], apply_fn[f'CouplingLayer{i + 1}'] = coupling_layer(in_channels, activation, batch_norm, d, net, (i % 2 == 0), **kwargs) def net_init(rng, input_shape): params = {} rngs = random.split(rng, 2) - for i in range(num_blocks): shape, params[f'CouplingLayer{i + 1}'] = init_fn[f'CouplingLayer{i + 1}'](rngs[i], input_shape) - return shape, params def net_apply(params, inputs, invert=False): out = inputs - if invert: for i in range(num_blocks, 0, -1): - out = apply_fn[f'CouplingLayer{i}']( - params[f'CouplingLayer{i}'], out, invert) + out = apply_fn[f'CouplingLayer{i}'](params[f'CouplingLayer{i}'], out, invert) else: for i in range(1, num_blocks + 1): - out = apply_fn[f'CouplingLayer{i}']( - params[f'CouplingLayer{i}'], out) - + out = apply_fn[f'CouplingLayer{i}'](params[f'CouplingLayer{i}'], out) return out - if in_spatial == 0: + if d == 0: net = StaxNet(net_init, net_apply, (1,) + (in_channels,)) else: - net = StaxNet(net_init, net_apply, (1,) + (1,) * in_spatial + (in_channels,)) + net = StaxNet(net_init, net_apply, (1,) + (1,) * d + (in_channels,)) net.initialize() return net diff --git a/phi/tf/nets.py b/phi/tf/nets.py index 60ca58926..09da32ab1 100644 --- a/phi/tf/nets.py +++ b/phi/tf/nets.py @@ -10,6 +10,7 @@ from typing import Tuple, List import numpy +import numpy as np import tensorflow as tf from tensorflow import Tensor from tensorflow import keras @@ -53,11 +54,20 @@ def get_parameters(model: keras.Model, wrap=True) -> dict: result[name] = var else: if name.endswith('.weight'): - phi_tensor = math.wrap(var, math.channel('input,output')) + if var.ndim == 2: + phi_tensor = math.wrap(var, math.channel('input,output')) + elif var.ndim == 3: + phi_tensor = math.wrap(var, math.channel('x,input,output')) + elif var.ndim == 4: + phi_tensor = math.wrap(var, math.channel('x,y,input,output')) + elif var.ndim == 5: + phi_tensor = math.wrap(var, math.channel('x,y,z,input,output')) elif name.endswith('.bias'): phi_tensor = math.wrap(var, math.channel('output')) + elif var.ndim == 1: + phi_tensor = math.wrap(var, math.channel('output')) else: - raise NotImplementedError(name) + raise NotImplementedError(name, var) result[name] = phi_tensor return result @@ -172,7 +182,8 @@ def dense_net(in_channels: int, out_channels: int, layers: Tuple[int, ...] or List[int], batch_norm=False, - activation='ReLU') -> keras.Model: + activation='ReLU', + softmax=False) -> keras.Model: """ Fully-connected neural networks are available in ΦFlow via dense_net(). Arguments: @@ -193,7 +204,8 @@ def dense_net(in_channels: int, keras_layers.append(kl.BatchNormalization()) return keras.models.Sequential([kl.InputLayer(input_shape=(in_channels,)), *keras_layers, - kl.Dense(out_channels, activation='linear')]) + kl.Dense(out_channels, activation='linear'), + *([kl.Softmax()] if softmax else [])]) def u_net(in_channels: int, @@ -203,6 +215,7 @@ def u_net(in_channels: int, batch_norm: bool = True, activation: str or Callable = 'ReLU', in_spatial: tuple or int = 2, + periodic=False, use_res_blocks: bool = False, **kwargs) -> keras.Model: """ ΦFlow provides a built-in U-net architecture, classically popular for Semantic Segmentation in Computer Vision, composed of downsampling and upsampling layers. @@ -236,16 +249,16 @@ def u_net(in_channels: int, filters = (filters,) * levels # --- Construct the U-Net --- x = inputs = keras.Input(shape=in_spatial + (in_channels,)) - x = resnet_block(x.shape[-1], filters[0], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[0], filters[0], batch_norm, activation) + x = resnet_block(x.shape[-1], filters[0], periodic, batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[0], filters[0], batch_norm, activation, periodic) xs = [x] for i in range(1, levels): x = MAX_POOL[d](2, padding="same")(x) - x = resnet_block(x.shape[-1], filters[i], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[i], filters[i], batch_norm, activation) + x = resnet_block(x.shape[-1], filters[i], periodic, batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[i], filters[i], batch_norm, activation, periodic) xs.insert(0, x) for i in range(1, levels): x = UPSAMPLE[d](2)(x) x = kl.Concatenate()([x, xs[i]]) - x = resnet_block(x.shape[-1], filters[i - 1], batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[i - 1], filters[i - 1], batch_norm, activation) + x = resnet_block(x.shape[-1], filters[i - 1], periodic, batch_norm, activation, d)(x) if use_res_blocks else double_conv(x, d, filters[i - 1], filters[i - 1], batch_norm, activation, periodic) x = CONV[d](out_channels, 1)(x) return keras.Model(inputs, x) @@ -268,17 +281,12 @@ def pad_periodic(x: Tensor): return x -def double_conv(x, d: int, out_channels: int, mid_channels: int, batch_norm: bool, activation: Callable): - x = pad_periodic(x) - x = CONV[d](mid_channels, 3, padding='valid')(x) - # x = CONV[d](mid_channels, 3, padding='same')(x) +def double_conv(x, d: int, out_channels: int, mid_channels: int, batch_norm: bool, activation: Callable, periodic: bool): + x = CONV[d](mid_channels, 3, padding='valid')(pad_periodic(x)) if periodic else CONV[d](mid_channels, 3, padding='same')(x) if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) - - x = pad_periodic(x) - x = CONV[d](out_channels, 3, padding='valid')(x) - # x = CONV[d](out_channels, 3, padding='same')(x) + x = CONV[d](out_channels, 3, padding='valid')(pad_periodic(x)) if periodic else CONV[d](out_channels, 3, padding='same')(x) if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) @@ -290,6 +298,7 @@ def conv_net(in_channels: int, layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or Callable = 'ReLU', + periodic=False, in_spatial: int or tuple = 2, **kwargs) -> keras.Model: """ Built in Conv-Nets are also provided. Contrary to the classical convolutional neural networks, the feature map spatial size remains the same throughout the layers. Each layer of the network is essentially a convolutional block comprising of two conv layers. A filter size of 3 is used in the convolutional layers. @@ -307,53 +316,47 @@ def conv_net(in_channels: int, Conv-net model as specified by input arguments """ if isinstance(in_spatial, int): - d = (None,) * in_spatial + d = in_spatial + in_spatial = (None,) * d else: assert isinstance(in_spatial, tuple) - d = in_spatial - in_spatial = len(d) + d = len(in_spatial) activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation - x = inputs = keras.Input(shape=d + (in_channels,)) + x = inputs = keras.Input(shape=in_spatial + (in_channels,)) if len(layers) < 1: layers.append(out_channels) for i in range(len(layers)): - x = pad_periodic(x) - x = CONV[in_spatial](layers[i], 3, padding='valid')(x) + x = CONV[d](layers[i], 3, padding='valid')(pad_periodic(x)) if periodic else CONV[d](layers[i], 3, padding='same')(x) if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) - # x = pad_periodic(x) - x = CONV[in_spatial](out_channels, 1)(x) + x = CONV[d](out_channels, 1)(x) return keras.Model(inputs, x) def resnet_block(in_channels: int, out_channels: int, + periodic: bool, batch_norm: bool = False, activation: str or Callable = 'ReLU', in_spatial: int or tuple = 2): activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation if isinstance(in_spatial, int): - d = (None,) * in_spatial + d = in_spatial else: assert isinstance(in_spatial, tuple) - d = in_spatial - in_spatial = len(d) - d = (None,) * in_spatial - inputs = keras.Input(shape=d + (in_channels,)) - x_1 = inputs - x = pad_periodic(inputs) - x = CONV[in_spatial](out_channels, 3, padding='valid')(x) + d = len(in_spatial) + x = x_1 = inputs = keras.Input(shape=(None,) * d + (in_channels,)) + x = CONV[d](out_channels, 3, padding='valid')(pad_periodic(x)) if periodic else CONV[d](out_channels, 3, padding='same')(x) if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) - x = pad_periodic(x) - x = CONV[in_spatial](out_channels, 3, padding='valid')(x) + x = CONV[d](out_channels, 3, padding='valid')(pad_periodic(x)) if periodic else CONV[d](out_channels, 3, padding='same')(x) if batch_norm: x = kl.BatchNormalization()(x) x = activation(x) if in_channels != out_channels: - x_1 = CONV[in_spatial](out_channels, 1)(x_1) + x_1 = CONV[d](out_channels, 1)(x_1) if batch_norm: x_1 = kl.BatchNormalization()(x_1) x = kl.Add()([x, x_1]) @@ -365,6 +368,7 @@ def res_net(in_channels: int, layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or Callable = 'ReLU', + periodic=False, in_spatial: int or tuple = 2, **kwargs): """ Built in Res-Nets are provided in the ΦFlow framework. Similar to the conv-net, the feature map spatial size remains the same throughout the layers. @@ -385,76 +389,52 @@ def res_net(in_channels: int, Res-net model as specified by input arguments """ if isinstance(in_spatial, int): - d = (None,) * in_spatial + d = in_spatial + in_spatial = (None,) * d else: assert isinstance(in_spatial, tuple) - d = in_spatial - in_spatial = len(d) + d = len(in_spatial) - x = inputs = keras.Input(shape=d + (in_channels,)) + x = inputs = keras.Input(shape=in_spatial + (in_channels,)) if len(layers) < 1: layers.append(out_channels) - out = resnet_block(in_channels, layers[0], batch_norm, activation, in_spatial)(x) + out = resnet_block(in_channels, layers[0], periodic, batch_norm, activation, d)(x) for i in range(1, len(layers)): - out = resnet_block(layers[i - 1], layers[i], batch_norm, activation, in_spatial)(out) - out = CONV[in_spatial](out_channels, 1)(out) + out = resnet_block(layers[i - 1], layers[i], periodic, batch_norm, activation, d)(out) + out = CONV[d](out_channels, 1)(out) return keras.Model(inputs, out) -def conv_classifier(input_shape: list, num_classes: int, batch_norm: bool, in_spatial: int or tuple): - if isinstance(in_spatial, int): - d = in_spatial - in_spatial = (None,) * d - else: - assert isinstance(in_spatial, tuple) - d = len(in_spatial) - # input_shape[0] = Channels - spatial_shape_list = list(input_shape[1:]) - x = inputs = keras.Input(shape=in_spatial + (input_shape[0],)) - x = double_conv(x, d, 64, 64, batch_norm, ACTIVATIONS['ReLU']) - x = MAX_POOL[d](2)(x) - - x = double_conv(x, d, 128, 128, batch_norm, ACTIVATIONS['ReLU']) - x = MAX_POOL[d](2)(x) - - x = double_conv(x, d, 256, 256, batch_norm, ACTIVATIONS['ReLU']) - x = pad_periodic(x) - x = CONV[d](256, 3, padding='valid')(x) - if batch_norm: - x = kl.BatchNormalization()(x) - x = ACTIVATIONS['ReLU'](x) - x = MAX_POOL[d](2)(x) - - x = double_conv(x, d, 512, 512, batch_norm, ACTIVATIONS['ReLU']) - x = pad_periodic(x) - x = CONV[d](512, 3, padding='valid')(x) - if batch_norm: - x = kl.BatchNormalization()(x) - x = ACTIVATIONS['ReLU'](x) - x = MAX_POOL[d](2)(x) - - x = double_conv(x, d, 512, 512, batch_norm, ACTIVATIONS['ReLU']) - x = pad_periodic(x) - x = CONV[d](512, 3, padding='valid')(x) - if batch_norm: - x = kl.BatchNormalization()(x) - x = ACTIVATIONS['ReLU'](x) - x = MAX_POOL[d](2)(x) - - for i in range(5): - for j in range(len(spatial_shape_list)): - spatial_shape_list[j] = math.floor((spatial_shape_list[j] - 2) / 2) + 1 - - flattened_input_dim = 1 - for i in range(len(spatial_shape_list)): - flattened_input_dim *= spatial_shape_list[i] - flattened_input_dim *= 512 - +def conv_classifier(in_features: int, + in_spatial: tuple or list, + num_classes: int, + blocks=(64, 128, 256, 256, 512, 512), + dense_layers=(4096, 4096, 100), + batch_norm=True, + activation='ReLU', + softmax=True, + periodic=False): + """ + Based on VGG16. + """ + assert isinstance(in_spatial, (tuple, list)) + activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation + d = len(in_spatial) + x = inputs = keras.Input(shape=in_spatial + (in_features,)) + for i, next in enumerate(blocks): + if i in (0, 1): + x = double_conv(x, d, next, next, batch_norm, activation, periodic) + x = MAX_POOL[d](2)(x) + else: + x = double_conv(x, d, next, next, batch_norm, activation, periodic) + x = CONV[d](next, 3, padding='valid')(pad_periodic(x)) if periodic else CONV[d](next, 3, padding='same')(x) + if batch_norm: + x = kl.BatchNormalization()(x) + x = activation(x) + x = MAX_POOL[d](2)(x) x = kl.Flatten()(x) - x = dense_net(flattened_input_dim, num_classes, [4096, 4096, 100], batch_norm, 'ReLU')(x) - - x = kl.Softmax()(x) - + flat_size = int(np.prod(in_spatial) * blocks[-1] / (2**d) ** len(blocks)) + x = dense_net(flat_size, num_classes, dense_layers, batch_norm, activation, softmax)(x) return keras.Model(inputs, x) diff --git a/phi/torch/nets.py b/phi/torch/nets.py index 22a97e340..7a073f71f 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -7,6 +7,7 @@ from typing import Callable, List, Tuple import numpy +import numpy as np import torch import torch.nn as nn from torch import optim @@ -39,7 +40,14 @@ def get_parameters(net: nn.Module, wrap=True) -> dict: result = {} for name, param in net.named_parameters(): if name.endswith('.weight'): - phi_tensor = math.wrap(param, channel('input,output')) + if param.ndim == 2: + phi_tensor = math.wrap(param, channel('input,output')) + elif param.ndim == 3: + phi_tensor = math.wrap(param, channel('x,input,output')) + elif param.ndim == 4: + phi_tensor = math.wrap(param, channel('x,y,input,output')) + elif param.ndim == 5: + phi_tensor = math.wrap(param, channel('x,y,z,input,output')) elif name.endswith('.bias'): phi_tensor = math.wrap(param, channel('output')) else: @@ -147,9 +155,10 @@ def dense_net(in_channels: int, out_channels: int, layers: Tuple[int, ...] or List[int], batch_norm=False, - activation: str or Callable = 'ReLU') -> nn.Module: + activation: str or Callable = 'ReLU', + softmax=False) -> nn.Module: """ - Fully-connected neural networks are available in ΦFlow via dense_net(). + Fully-connected neural networks are available in Φ-Flow via dense_net(). Arguments: in_channels : size of input layer, int out_channels = size of output layer, int @@ -162,9 +171,8 @@ def dense_net(in_channels: int, """ layers = [in_channels, *layers, out_channels] activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation - net = DenseNet(layers, activation, batch_norm) - net = net.to(TORCH.get_default_device().ref) - return net + net = DenseNet(layers, activation, batch_norm, softmax) + return net.to(TORCH.get_default_device().ref) class DenseNet(nn.Module): @@ -172,7 +180,8 @@ class DenseNet(nn.Module): def __init__(self, layers: list, activation: type, - batch_norm: bool): + batch_norm: bool, + use_softmax: bool): super(DenseNet, self).__init__() self._layers = layers self._activation = activation @@ -181,6 +190,7 @@ def __init__(self, self.add_module(f'linear{i}', nn.Linear(s1, s2, bias=True)) if batch_norm: self.add_module(f'norm{i}', nn.BatchNorm1d(s2)) + self.softmax = nn.Softmax() if use_softmax else None def forward(self, x): register_module_call(self) @@ -190,6 +200,8 @@ def forward(self, x): if self._batch_norm: x = getattr(self, f'norm{i}')(x) x = getattr(self, f'linear{len(self._layers) - 2}')(x) + if self.softmax: + x = self.softmax(x) return x @@ -200,7 +212,9 @@ def u_net(in_channels: int, batch_norm: bool = True, activation: str or type = 'ReLU', in_spatial: tuple or int = 2, - use_res_blocks: bool = False, **kwargs) -> nn.Module: + periodic=False, + use_res_blocks: bool = False, + **kwargs) -> nn.Module: """ ΦFlow provides a built-in U-net architecture, classically popular for Semantic Segmentation in Computer Vision, composed of downsampling and upsampling layers. @@ -210,7 +224,6 @@ def u_net(in_channels: int, out_channels : output channels of the feature map, dtype : int levels : number of levels of down-sampling and upsampling, dtype : int filters : filter sizes at each down/up sampling convolutional layer, if the input is integer all conv layers have the same filter size, - dtype : int or tuple activation : activation function used within the layers, dtype : string batch_norm : use of batchnorm after each conv layer, dtype : bool in_spatial : spatial dimensions of the input feature map, dtype : int @@ -231,26 +244,23 @@ def u_net(in_channels: int, else: assert isinstance(in_spatial, tuple) d = len(in_spatial) - net = UNet(d, in_channels, out_channels, filters, batch_norm, activation, use_res_blocks) - net = net.to(TORCH.get_default_device().ref) - # net = torch.jit.trace_module(net, {'forward': torch.zeros((1, in_channels) + (32,) * d, device=TORCH.get_default_device().ref)}) - return net + net = UNet(d, in_channels, out_channels, filters, batch_norm, activation, periodic, use_res_blocks) + return net.to(TORCH.get_default_device().ref) class UNet(nn.Module): - def __init__(self, d: int, in_channels: int, out_channels: int, filters: tuple, batch_norm: bool, activation: type, - use_res_blocks: bool): + def __init__(self, d: int, in_channels: int, out_channels: int, filters: tuple, batch_norm: bool, activation: type, periodic: bool, use_res_blocks: bool): super(UNet, self).__init__() self._levels = len(filters) self._spatial_rank = d if use_res_blocks: - self.add_module('inc', resnet_block(d, in_channels, filters[0], batch_norm, activation)) + self.add_module('inc', resnet_block(d, in_channels, filters[0], batch_norm, activation, periodic)) else: - self.add_module('inc', DoubleConv(d, in_channels, filters[0], filters[0], batch_norm, activation)) + self.add_module('inc', DoubleConv(d, in_channels, filters[0], filters[0], batch_norm, activation, periodic)) for i in range(1, self._levels): - self.add_module(f'down{i}', Down(d, filters[i - 1], filters[i], batch_norm, activation, use_res_blocks)) - self.add_module(f'up{i}', Up(d, filters[i] + filters[i - 1], filters[i - 1], batch_norm, activation, + self.add_module(f'down{i}', Down(d, filters[i - 1], filters[i], batch_norm, activation, periodic, use_res_blocks)) + self.add_module(f'up{i}', Up(d, filters[i] + filters[i - 1], filters[i - 1], batch_norm, activation, periodic, use_res_blocks=use_res_blocks)) self.add_module('outc', CONV[d](filters[0], out_channels, kernel_size=1)) @@ -271,14 +281,13 @@ def forward(self, x): class DoubleConv(nn.Module): """(convolution => [BN] => ReLU) * 2""" - def __init__(self, d: int, in_channels: int, out_channels: int, mid_channels: int, batch_norm: bool, - activation: type): + def __init__(self, d: int, in_channels: int, out_channels: int, mid_channels: int, batch_norm: bool, activation: type, periodic: bool): super().__init__() self.add_module('double_conv', nn.Sequential( - CONV[d](in_channels, mid_channels, kernel_size=3, padding=1, padding_mode='circular'), + CONV[d](in_channels, mid_channels, kernel_size=3, padding=1, padding_mode='circular' if periodic else 'zeros'), NORM[d](mid_channels) if batch_norm else nn.Identity(), activation(), - CONV[d](mid_channels, out_channels, kernel_size=3, padding=1, padding_mode='circular'), + CONV[d](mid_channels, out_channels, kernel_size=3, padding=1, padding_mode='circular' if periodic else 'zeros'), NORM[d](out_channels) if batch_norm else nn.Identity(), nn.ReLU(inplace=True) )) @@ -293,14 +302,13 @@ def forward(self, x): class Down(nn.Module): """Downscaling with maxpool then double conv or resnet_block""" - def __init__(self, d: int, in_channels: int, out_channels: int, batch_norm: bool, activation: str or type, - use_res_blocks: bool): + def __init__(self, d: int, in_channels: int, out_channels: int, batch_norm: bool, activation: str or type, use_res_blocks: bool, periodic): super().__init__() self.add_module('maxpool', MAX_POOL[d](2)) if use_res_blocks: - self.add_module('conv', resnet_block(d, in_channels, out_channels, batch_norm, activation)) + self.add_module('conv', resnet_block(d, in_channels, out_channels, batch_norm, activation, periodic)) else: - self.add_module('conv', DoubleConv(d, in_channels, out_channels, out_channels, batch_norm, activation)) + self.add_module('conv', DoubleConv(d, in_channels, out_channels, out_channels, batch_norm, activation, periodic)) def forward(self, x): x = self.maxpool(x) @@ -312,22 +320,21 @@ class Up(nn.Module): _MODES = [None, 'linear', 'bilinear', 'trilinear'] - def __init__(self, d: int, in_channels: int, out_channels: int, batch_norm: bool, activation: type, linear=True, - use_res_blocks: bool = False): + def __init__(self, d: int, in_channels: int, out_channels: int, batch_norm: bool, activation: type, periodic: bool, linear=True, use_res_blocks: bool = False): super().__init__() if linear: # if bilinear, use the normal convolutions to reduce the number of channels up = nn.Upsample(scale_factor=2, mode=Up._MODES[d]) if use_res_blocks: - conv = resnet_block(d, in_channels, out_channels, batch_norm, activation) + conv = resnet_block(d, in_channels, out_channels, batch_norm, activation, periodic) else: - conv = DoubleConv(d, in_channels, out_channels, in_channels // 2, batch_norm, activation) + conv = DoubleConv(d, in_channels, out_channels, in_channels // 2, batch_norm, activation, periodic) else: up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2) if use_res_blocks: - conv = resnet_block(d, in_channels, out_channels, batch_norm, activation) + conv = resnet_block(d, in_channels, out_channels, batch_norm, activation, periodic) else: - conv = DoubleConv(d, in_channels, out_channels, out_channels, batch_norm, activation) + conv = DoubleConv(d, in_channels, out_channels, out_channels, batch_norm, activation, periodic) self.add_module('up', up) self.add_module('conv', conv) @@ -346,19 +353,19 @@ def forward(self, x1, x2): class ConvNet(nn.Module): - def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, activation): + def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, activation, periodic: bool): super(ConvNet, self).__init__() activation = ACTIVATIONS[activation] if len(layers) < 1: layers.append(out_channels) self.layers = layers self.add_module(f'Conv_in', nn.Sequential( - CONV[in_spatial](in_channels, layers[0], kernel_size=3, padding=1, padding_mode='circular'), + CONV[in_spatial](in_channels, layers[0], kernel_size=3, padding=1, padding_mode='circular' if periodic else 'zeros'), NORM[in_spatial](layers[0]) if batch_norm else nn.Identity(), activation())) for i in range(1, len(layers)): self.add_module(f'Conv{i}', nn.Sequential( - CONV[in_spatial](layers[i - 1], layers[i], kernel_size=3, padding=1, padding_mode='circular'), + CONV[in_spatial](layers[i - 1], layers[i], kernel_size=3, padding=1, padding_mode='circular' if periodic else 'zeros'), NORM[in_spatial](layers[i]) if batch_norm else nn.Identity(), activation())) self.add_module(f'Conv_out', CONV[in_spatial](layers[len(layers) - 1], out_channels, kernel_size=1)) @@ -376,7 +383,9 @@ def conv_net(in_channels: int, layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or type = 'ReLU', - in_spatial: int or tuple = 2, **kwargs) -> nn.Module: + in_spatial: int or tuple = 2, + periodic=False, + **kwargs) -> nn.Module: """ Built in Conv-Nets are also provided. Contrary to the classical convolutional neural networks, the feature map spatial size remains the same throughout the layers. Each layer of the network is essentially a convolutional block comprising of two conv layers. A filter size of 3 is used in the convolutional layers. Arguments: @@ -397,14 +406,14 @@ def conv_net(in_channels: int, else: assert isinstance(in_spatial, tuple) d = len(in_spatial) - net = ConvNet(d, in_channels, out_channels, layers, batch_norm, activation) + net = ConvNet(d, in_channels, out_channels, layers, batch_norm, activation, periodic) net = net.to(TORCH.get_default_device().ref) return net class resnet_block(nn.Module): - def __init__(self, in_spatial, in_channels, out_channels, batch_norm, activation): + def __init__(self, in_spatial, in_channels, out_channels, batch_norm, activation, periodic: bool): # Since in_channels and out_channels might be different # we need a sampling layer for up/down sampling input # in order to add it as a skip connection @@ -417,9 +426,9 @@ def __init__(self, in_spatial, in_channels, out_channels, batch_norm, activation self.bn_sample = nn.Identity() self.activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation self.bn1 = NORM[in_spatial](out_channels) if batch_norm else nn.Identity() - self.conv1 = CONV[in_spatial](in_channels, out_channels, kernel_size=3, padding=1, padding_mode='circular') + self.conv1 = CONV[in_spatial](in_channels, out_channels, kernel_size=3, padding=1, padding_mode='circular' if periodic else 'zeros') self.bn2 = NORM[in_spatial](out_channels) if batch_norm else nn.Identity() - self.conv2 = CONV[in_spatial](out_channels, out_channels, kernel_size=3, padding=1, padding_mode='circular') + self.conv2 = CONV[in_spatial](out_channels, out_channels, kernel_size=3, padding=1, padding_mode='circular' if periodic else 'zeros') def forward(self, x): x = TORCH.as_tensor(x) @@ -485,14 +494,14 @@ def get_mask(inputs, reverse_mask, data_format='NHWC'): class ResNet(nn.Module): - def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, activation): + def __init__(self, in_spatial, in_channels, out_channels, layers, batch_norm, activation, periodic: bool): super(ResNet, self).__init__() self.layers = layers if len(self.layers) < 1: layers.append(out_channels) - self.add_module('Res_in', resnet_block(in_spatial, in_channels, layers[0], batch_norm, activation)) + self.add_module('Res_in', resnet_block(in_spatial, in_channels, layers[0], batch_norm, activation, periodic)) for i in range(1, len(layers)): - self.add_module(f'Res{i}', resnet_block(in_spatial, layers[i - 1], layers[i], batch_norm, activation)) + self.add_module(f'Res{i}', resnet_block(in_spatial, layers[i - 1], layers[i], batch_norm, activation, periodic)) self.add_module('Res_out', CONV[in_spatial](layers[len(layers) - 1], out_channels, kernel_size=1)) def forward(self, x): @@ -509,7 +518,9 @@ def res_net(in_channels: int, layers: Tuple[int, ...] or List[int], batch_norm: bool = False, activation: str or type = 'ReLU', - in_spatial: int or tuple = 2, **kwargs) -> nn.Module: + in_spatial: int or tuple = 2, + periodic=False, + **kwargs) -> nn.Module: """ Built in Res-Nets are provided in the ΦFlow framework. Similar to the conv-net, the feature map spatial size remains the same throughout the layers. These networks use residual blocks composed of two conv layers with a skip connection added from the input to the output feature map. @@ -534,61 +545,55 @@ def res_net(in_channels: int, else: assert isinstance(in_spatial, tuple) d = len(in_spatial) - net = ResNet(d, in_channels, out_channels, layers, batch_norm, activation) + net = ResNet(d, in_channels, out_channels, layers, batch_norm, activation, periodic) net = net.to(TORCH.get_default_device().ref) return net -def conv_classifier(input_shape: list, num_classes: int, batch_norm: bool, in_spatial: int or tuple): - if isinstance(in_spatial, int): - d = in_spatial - else: - assert isinstance(in_spatial, tuple) - d = len(in_spatial) - net = ConvClassifier(d, input_shape, num_classes, batch_norm) - net = net.to(TORCH.get_default_device().ref) - return net +def conv_classifier(in_features: int, + in_spatial: tuple or list, + num_classes: int, + blocks=(64, 128, 256, 256, 512, 512), + dense_layers=(4096, 4096, 100), + batch_norm=True, + activation='ReLU', + softmax=True, + periodic=False): + """ + Based on VGG16. + """ + assert isinstance(in_spatial, (tuple, list)) + activation = ACTIVATIONS[activation] if isinstance(activation, str) else activation + net = ConvClassifier(in_features, in_spatial, num_classes, batch_norm, softmax, blocks, dense_layers, periodic, activation) + return net.to(TORCH.get_default_device().ref) class ConvClassifier(nn.Module): - def __init__(self, d: int, input_shape: list, num_classes: int, batch_norm: bool): + def __init__(self, in_features, in_spatial: list, num_classes: int, batch_norm: bool, use_softmax: bool, blocks: tuple, dense_layers: tuple, periodic: bool, activation): super(ConvClassifier, self).__init__() - - self.spatial_shape_list = list(input_shape[1:]) + d = len(in_spatial) + self.in_spatial = in_spatial self.add_module('maxpool', MAX_POOL[d](2)) - self.add_module('conv1', DoubleConv(d, input_shape[0], 64, 64, batch_norm, ACTIVATIONS['ReLU'])) - self.add_module('conv2', DoubleConv(d, 64, 128, 128, batch_norm, ACTIVATIONS['ReLU'])) - self.add_module('conv3', nn.Sequential(DoubleConv(d, 128, 256, 256, batch_norm, ACTIVATIONS['ReLU']), - CONV[d](256, 256, 3, padding=1, padding_mode='circular'), - NORM[d](256) if batch_norm else nn.Identity(), - nn.ReLU())) - self.add_module('conv4', nn.Sequential(DoubleConv(d, 256, 512, 512, batch_norm, ACTIVATIONS['ReLU']), - CONV[d](512, 512, 3, padding=1, padding_mode='circular'), - NORM[d](512) if batch_norm else nn.Identity(), - nn.ReLU())) - self.add_module('conv5', nn.Sequential(DoubleConv(d, 512, 512, 512, batch_norm, ACTIVATIONS['ReLU']), - CONV[d](512, 512, 3, padding=1, padding_mode='circular'), - NORM[d](512) if batch_norm else nn.Identity(), - nn.ReLU())) - for i in range(5): - for j in range(len(self.spatial_shape_list)): - self.spatial_shape_list[j] = math.floor((self.spatial_shape_list[j] - 2) / 2) + 1 - - flattened_input_dim = 1 - for i in range(len(self.spatial_shape_list)): - flattened_input_dim *= self.spatial_shape_list[i] - flattened_input_dim *= 512 - self.linear = dense_net(flattened_input_dim, num_classes, [4096, 4096, 100], batch_norm, 'ReLU') + for i, (prev, next) in enumerate(zip((in_features,) + blocks[:-1], blocks)): + if i in (0, 1): + conv = DoubleConv(d, prev, next, next, batch_norm, activation, periodic) + else: + conv = nn.Sequential(DoubleConv(d, prev, next, next, batch_norm, activation, periodic), + CONV[d](next, next, 3, padding=1, padding_mode='circular' if periodic else 'zeros'), + NORM[d](next) if batch_norm else nn.Identity(), + activation()) + self.add_module(f'conv{i+1}', conv) + flat_size = int(np.prod(in_spatial) * blocks[-1] / (2**d) ** len(blocks)) + self.dense_net = dense_net(flat_size, num_classes, dense_layers, batch_norm, activation, use_softmax) self.flatten = nn.Flatten() - self.softmax = nn.Softmax() def forward(self, x): for i in range(5): - x = getattr(self, f'conv{i + 1}')(x) + x = getattr(self, f'conv{i+1}')(x) x = self.maxpool(x) x = self.flatten(x) - x = self.softmax(self.linear(x)) + x = self.dense_net(x) return x @@ -693,9 +698,8 @@ def invertible_net(in_channels: int, """ if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - - return InvertibleNet(in_channels, num_blocks, activation, batch_norm, in_spatial, net).to(TORCH.get_default_device().ref) + return InvertibleNet(in_channels, num_blocks, activation, batch_norm, in_spatial, net).to(TORCH.get_default_device().ref) def coupling_layer(in_channels: int, @@ -706,7 +710,6 @@ def coupling_layer(in_channels: int, if isinstance(in_spatial, tuple): in_spatial = len(in_spatial) - net = CouplingLayer(in_channels, activation, batch_norm, in_spatial, reverse_mask) net = net.to(TORCH.get_default_device().ref) return net @@ -741,13 +744,14 @@ def __init__(self, in_channels, out_channels, modes, in_spatial): for i in range(2 ** (in_spatial - 1)): self.weights[f'w{i + 1}'] = nn.Parameter(self.scale * torch.randn(rand_shape, dtype=torch.cfloat)) - #print('TORCH self.weights:', self.weights_[f'w{i + 1}'].shape) - #print(self.weights[f'w{i + 1}'].shape) + # print('TORCH self.weights:', self.weights_[f'w{i + 1}'].shape) + # print(self.weights[f'w{i + 1}'].shape) + def complex_mul(self, input, weights): - #print(input.shape) - #print(weights.shape) - #exit(1) + # print(input.shape) + # print(weights.shape) + # exit(1) if self.in_spatial == 1: return torch.einsum("bix,iox->box", input, weights) elif self.in_spatial == 2: @@ -758,15 +762,15 @@ def complex_mul(self, input, weights): def forward(self, x): batch_size = x.shape[0] - #print('x.shape:', x.shape) + # print('x.shape:', x.shape) ##Convert to Fourier space dims = [-i for i in range(self.in_spatial, 0, -1)] x_ft = torch.fft.rfftn(x, dim=dims) - #print('After RFFT torch', x_ft.shape) + # print('After RFFT torch', x_ft.shape) outft_dims = [batch_size, self.out_channels] + \ [x.size(-i) for i in range(self.in_spatial, 1, -1)] + [x.size(-1) // 2 + 1] out_ft = torch.zeros(outft_dims, dtype=torch.cfloat, device=x.device) - #print('outft shape before', out_ft.shape) + # print('outft shape before', out_ft.shape) ##Multiply relevant fourier modes if self.in_spatial == 1: out_ft[:, :, :self.modes[1]] = \ diff --git a/tests/commit/test_networks.py b/tests/commit/test_networks.py index 1546da6bf..771a31694 100644 --- a/tests/commit/test_networks.py +++ b/tests/commit/test_networks.py @@ -16,23 +16,23 @@ class TestNetworks(TestCase): def test_u_net_2d_network_sizes(self): for lib in LIBRARIES: net = lib.u_net(2, 3, levels=3, filters=8, batch_norm=False, activation='ReLU', in_spatial=(64, 32)) - self.assertEqual(6587, lib.parameter_count(net)) + self.assertEqual(6587, lib.parameter_count(net), msg=lib) net_res = lib.u_net(2, 3, batch_norm=False, activation='SiLU', in_spatial=2, use_res_blocks=True) - self.assertEqual(39059, lib.parameter_count(net_res)) + self.assertEqual(39059, lib.parameter_count(net_res), msg=lib) def test_u_net_3d_norm_network_sizes(self): for lib in LIBRARIES: net = lib.u_net(2, 3, levels=3, filters=8, batch_norm=True, activation='Sigmoid', in_spatial=3) - self.assertEqual(19707, lib.parameter_count(net)) + self.assertEqual(19707, lib.parameter_count(net), msg=lib) net_res = lib.u_net(2, 3, batch_norm=True, activation='SiLU', in_spatial=3, use_res_blocks=True) - self.assertEqual(113939, lib.parameter_count(net_res)) + self.assertEqual(113939, lib.parameter_count(net_res), msg=lib) def test_u_net_1d_norm_network_sizes(self): for lib in LIBRARIES: net = lib.u_net(2, 3, levels=2, filters=16, batch_norm=True, activation='tanh', in_spatial=1) - self.assertEqual(5043, lib.parameter_count(net)) + self.assertEqual(5043, lib.parameter_count(net), msg=lib) net_res = lib.u_net(2, 3, batch_norm=True, activation='SiLU', in_spatial=1, use_res_blocks=True) - self.assertEqual(14867, lib.parameter_count(net_res)) + self.assertEqual(14867, lib.parameter_count(net_res), msg=lib) def test_optimize_u_net(self): for lib in LIBRARIES: From 28bf81b763c6783952435bf1a73fcbc4256a7656 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 13 Jan 2023 22:43:37 +0100 Subject: [PATCH 050/170] [vis] log_dims, PointCloud legend (Matplotlib only) --- phi/vis/_dash/_plotly_plots.py | 3 +- phi/vis/_matplotlib/_matplotlib_plots.py | 59 +++++++++++++++++++----- phi/vis/_vis.py | 8 +++- phi/vis/_vis_base.py | 4 +- 4 files changed, 59 insertions(+), 15 deletions(-) diff --git a/phi/vis/_dash/_plotly_plots.py b/phi/vis/_dash/_plotly_plots.py index a3fd37a4c..392cf8ee5 100644 --- a/phi/vis/_dash/_plotly_plots.py +++ b/phi/vis/_dash/_plotly_plots.py @@ -26,7 +26,8 @@ def create_figure(self, rows: int, cols: int, subplots: Dict[Tuple[int, int], Box], - titles: Dict[Tuple[int, int], str]) -> Tuple[Any, Dict[Tuple[int, int], Any]]: + titles: Dict[Tuple[int, int], str], + log_dims: Tuple[str, ...]) -> Tuple[Any, Dict[Tuple[int, int], Any]]: titles = [titles.get((r, c), None) for r in range(rows) for c in range(cols)] specs = [[{'type': 'xy' if subplots.get((row, col), Box()).spatial_rank < 3 else 'surface'} for col in range(cols)] for row in range(rows)] fig = self.current_figure = make_subplots(rows=rows, cols=cols, subplot_titles=titles, specs=specs) diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index 36b5aaa9c..f31eb98a3 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -1,6 +1,7 @@ import logging import os import sys +import warnings from numbers import Number from typing import Callable, Tuple, Any, Dict @@ -10,6 +11,7 @@ import numpy as np from matplotlib import animation, cbook from matplotlib import rc +from matplotlib.patches import Patch from matplotlib.transforms import Bbox from mpl_toolkits.mplot3d import Axes3D @@ -34,7 +36,8 @@ def create_figure(self, rows: int, cols: int, spaces: Dict[Tuple[int, int], Box], - titles: Dict[Tuple[int, int], str]) -> Tuple[Any, Dict[Tuple[int, int], Any]]: + titles: Dict[Tuple[int, int], str], + log_dims: Tuple[str, ...]) -> Tuple[Any, Dict[Tuple[int, int], Any]]: figure, axes = plt.subplots(rows, cols, figsize=size) self.current_figure = figure axes = np.reshape(axes, (rows, cols)) @@ -49,12 +52,26 @@ def create_figure(self, if bounds.spatial_rank == 1: axis.set_xlabel(bounds.vector.item_names[0]) axis.set_xlim(_get_range(bounds, 0)) + if bounds.vector.item_names[0] in log_dims: + axis.set_xscale('log') + if '_' in log_dims: + axis.set_yscale('log') elif bounds.spatial_rank == 2: axis.set_xlabel(bounds.vector.item_names[0]) axis.set_ylabel(bounds.vector.item_names[1]) - axis.set_xlim(_get_range(bounds, 0)) - axis.set_ylim(_get_range(bounds, 1)) - axis.set_aspect('equal', adjustable='box') + x_range, y_range = [_get_range(bounds, i) for i in (0, 1)] + axis.set_xlim(x_range) + axis.set_ylim(y_range) + x_size, y_size = x_range[1] - x_range[0], y_range[1] - y_range[0] + any_log = False + if bounds.vector.item_names[0] in log_dims: + axis.set_xscale('log') + any_log = True + if bounds.vector.item_names[1] in log_dims: + axis.set_yscale('log') + any_log = True + if not any_log and max(x_size/y_size, y_size/x_size) < 5: + axis.set_aspect('equal', adjustable='box') elif bounds.spatial_rank == 3: axis.remove() axis = axes[row, col] = figure.add_subplot(rows, cols, cols*row + col + 1, projection='3d') @@ -64,6 +81,14 @@ def create_figure(self, axis.set_xlim(_get_range(bounds, 0)) axis.set_ylim(_get_range(bounds, 1)) axis.set_zlim(_get_range(bounds, 2)) + if bounds.vector.item_names[0] in log_dims: + warnings.warn("Only z axis can be log scaled in 3D plot with Matplotlib. Please reorder the dimensions.", RuntimeWarning) + # axis.set_xscale('log') + if bounds.vector.item_names[1] in log_dims: + warnings.warn("Only z axis can be log scaled in 3D plot with Matplotlib. Please reorder the dimensions.", RuntimeWarning) + # axis.set_yscale('log') + if bounds.vector.item_names[2] in log_dims: + axis.set_zscale('log') axis.set_title(titles.get((row, col), None)) axes_by_pos[(row, col)] = axes[row, col] return figure, axes_by_pos @@ -181,8 +206,6 @@ def _plot(axis, data: SampledField, space: Box, show_color_bar, vmin, vmax, **pl requires_legend = requires_legend or label if requires_legend: axis.legend() - if data.values.dtype.kind != complex and data.values.min > 0 and data.values.max > 100 * data.values.min: - axis.set_yscale('log') elif vmin is not None and vmax is not None: axis.set_ylim((vmin - .02 * (vmax - vmin), vmax + .02 * (vmax - vmin))) elif isinstance(data, Grid) and channel(data).volume == 1 and data.spatial_rank == 2: @@ -228,7 +251,6 @@ def _plot(axis, data: SampledField, space: Box, show_color_bar, vmin, vmax, **pl colors = cmap(norm(values)) axis.voxels(x, y, z, values, facecolors=colors, edgecolor='k') elif isinstance(data, PointCloud) and data.spatial_rank == 2 and 'vector' in channel(data): # vector cloud - axis.set_aspect('equal', adjustable='box') vector = data.points.shape['vector'] x, y = math.reshaped_numpy(data.points, [vector, data.shape.without('vector')]) u, v = math.reshaped_numpy(data.values, [vector, data.shape.without('vector')], force_expand=True) @@ -238,11 +260,14 @@ def _plot(axis, data: SampledField, space: Box, show_color_bar, vmin, vmax, **pl color = data.color.native() axis.quiver(x, y, u, v, color=color, units='xy', scale=1) elif isinstance(data, PointCloud) and data.spatial_rank == 2: # point cloud - axis.set_aspect('equal', adjustable='box') if channel(data.points).without('vector'): # multiple channel dimensions - data_list = field.unstack(data, channel(data.points).without('vector')[0].name) - for d in data_list: - _plot_points(axis, d, dims, vector, **plt_args) + channel_dim = channel(data.points).without('vector')[0] + legend_patches = [] + for name, d in zip(channel_dim.item_names[0] or (None,) * channel_dim.size, field.unstack(data, channel_dim.name)): + col = _plot_points(axis, d, dims, vector, **plt_args) + legend_patches.append(Patch(color=_rgba(col), label=name)) + if channel_dim.item_names: + axis.legend(handles=legend_patches) else: _plot_points(axis, data, dims, vector, **plt_args) elif isinstance(data, PointCloud) and data.spatial_rank == 3: @@ -284,7 +309,7 @@ def _plot_points(axis, data: PointCloud, dims, vector, **plt_args): parts = math.unstack(data, stack_dim) for part in parts: _plot_points(axis, part, dims, vector, **plt_args) - return + return color elif isinstance(data.elements, Point): if spatial(data.points).is_empty: axis.scatter(x, y, marker='x', color=color, s=6 ** 2, alpha=0.8) @@ -305,6 +330,7 @@ def _plot_points(axis, data: PointCloud, dims, vector, **plt_args): axis.plot(x, y, color=color[0]) if non_channel(data).rank == 1 and non_channel(data).item_names[0]: _annotate_points(axis, data.points, non_channel(data)) + return color[0] def _annotate_points(axis, points: math.Tensor, labelled_dim: math.Shape): @@ -317,6 +343,15 @@ def _annotate_points(axis, points: math.Tensor, labelled_dim: math.Shape): axis.annotate(label, (x_ + .01 * x_view, y_ + .01 * y_view)) +def _rgba(col): + if isinstance(col, str) and col.startswith('#'): + col = tuple(int(col.lstrip('#')[i:i+2], 16) for i in (0, 2, 4)) + col = np.asarray(col) + if col.dtype.kind == 'i': + col = col / 255. + return col + + def _get_pixels_per_unit(fig: plt.Figure, axis: plt.Axes, dpi=90): M = axis.transData.get_matrix() diff --git a/phi/vis/_vis.py b/phi/vis/_vis.py index d911c5387..5634d284c 100644 --- a/phi/vis/_vis.py +++ b/phi/vis/_vis.py @@ -15,6 +15,7 @@ from ..field._scene import _slugify_filename from ..geom import Geometry, Box, embed from ..math import Tensor, layout, batch, Shape, spatial, channel +from ..math._shape import parse_dim_names, parse_dim_order from ..math._tensors import Layout @@ -277,6 +278,7 @@ def plot(*fields: SampledField or Tensor or Layout, title: str or Tensor = None, size=(12, 5), same_scale=True, + log_dims: str or tuple or list or Shape='', show_color_bar=True, frame_time=100, repeat=True, @@ -296,6 +298,9 @@ def plot(*fields: SampledField or Tensor or Layout, title: String `Tensor` with dimensions `rows` and `cols`. size: Figure size in inches, `(width, height)`. same_scale: Whether to use the same axis limits for all sub-figures. + log_dims: Dimensions for which the plot axes should be scaled logarithmically. + Can be given as a comma-separated `str`, a sequence of dimension names or a `Shape`. + Use `'_'` to scale unnamed axes logarithmically, e.g. the y-axis of scalar functions. show_color_bar: Whether to display color bars for heat maps. animate: Time dimension to animate. If not present in the data, will produce a regular plot instead. @@ -330,8 +335,9 @@ def plot(*fields: SampledField or Tensor or Layout, else: assert title is None, f"title must be a str or Tensor but got {title}" title = {pos: ", ".join([i for dim, i in index.items() if isinstance(i, str)]) for pos, index in indices.items()} + log_dims = parse_dim_order(log_dims) or () if fig_shape.volume == 1: - figure, axes = plots.create_figure(size, nrows, ncols, subplots, title) + figure, axes = plots.create_figure(size, nrows, ncols, subplots, title, log_dims) if animate: def plot_frame(frame: int): for pos, fields in positioning.items(): diff --git a/phi/vis/_vis_base.py b/phi/vis/_vis_base.py index 8d6e44bb8..6cfe925dc 100644 --- a/phi/vis/_vis_base.py +++ b/phi/vis/_vis_base.py @@ -339,7 +339,8 @@ def create_figure(self, rows: int, cols: int, spaces: Dict[Tuple[int, int], Box], - titles: Dict[Tuple[int, int], str]) -> Tuple[Any, Dict[Tuple[int, int], Any]]: + titles: Dict[Tuple[int, int], str], + log_dims: Tuple[str, ...]) -> Tuple[Any, Dict[Tuple[int, int], Any]]: """ Args: size: Figure size in inches. @@ -348,6 +349,7 @@ def create_figure(self, spaces: Axes and range per sub-plot: `(x,y) -> Box`. Only subplot locations contained as keys should be plotted. To indicate automatic limit, the box will have a lower or upper limit of -inf or inf, respectively. titles: Subplot titles. + log_dims: Dimensions along which axes should be log-scaled Returns: figure: Native figure object From fb6174d85a0d6330b59038679f12af57a76ce15d Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 15 Jan 2023 16:53:07 +0100 Subject: [PATCH 051/170] [math] Multi-output map --- phi/math/_magic_ops.py | 2 +- phi/math/_ops.py | 28 +++- phi/math/_shape.py | 21 ++- tests/commit/math/test__ops.py | 279 +++++++++++++++++---------------- 4 files changed, 183 insertions(+), 147 deletions(-) diff --git a/phi/math/_magic_ops.py b/phi/math/_magic_ops.py index d292d9c33..0a4ba59bd 100644 --- a/phi/math/_magic_ops.py +++ b/phi/math/_magic_ops.py @@ -384,7 +384,7 @@ def pack_dims(value, dims: DimFilter, packed_dim: Shape, pos: int or None = None ``` """ assert isinstance(value, Shapable) and isinstance(value, Sliceable) and isinstance(value, Shaped), f"value must be Shapable but got {type(value)}" - dims = shape(value).only(dims) + dims = shape(value).only(dims, reorder=True) if packed_dim in shape(value): assert packed_dim in dims, f"Cannot pack dims into new dimension {packed_dim} because it already exists on value {value} and is not packed." if len(dims) == 0 or all(dim not in shape(value) for dim in dims): diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 464e9419b..f821147ec 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -387,15 +387,29 @@ def map_(function, *values, **kwargs) -> Tensor or None: """ values = [wrap(v) for v in values] shape = merge_shapes(*[v.shape for v in values]) - values_reshaped = [expand(v, shape) for v in values] - flat = [flatten(v, flatten_batch=True) for v in values_reshaped] + flat = [pack_dims(expand(v, shape), shape, batch('flat')) for v in values] result = [] + results = None for items in zip(*flat): - result.append(function(*items, **kwargs)) - if None in result: - assert all(r is None for r in result), f"map function returned None for some elements, {result}" - return None - return unpack_dim(wrap(result, channel('_c')), '_c', shape) + f_output = function(*items, **kwargs) + if isinstance(f_output, tuple): + if results is None: + results = [[] for _ in f_output] + for result_i, output_i in zip(results, f_output): + result_i.append(output_i) + else: + result.append(f_output) + if results is None: + if None in result: + assert all(r is None for r in result), f"map function returned None for some elements, {result}" + return None + return unpack_dim(wrap(result, channel('_c')), '_c', shape) + else: + for i, result_i in enumerate(results): + if None in result_i: + assert all(r is None for r in result_i), f"map function returned None for some elements at output index {i}, {result_i}" + results[i] = None + return tuple([unpack_dim(wrap(result_i, channel('_c')), '_c', shape) for result_i in results]) def _initialize(uniform_initializer, shapes: tuple) -> Tensor: diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 3c3003367..501d7635d 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -572,7 +572,7 @@ def without(self, dims: 'DimFilter') -> 'Shape': else: raise ValueError(dims) - def only(self, dims: 'DimFilter'): + def only(self, dims: 'DimFilter', reorder=False): """ Builds a new shape from this one that only contains the given dimensions. Dimensions in `dims` that are not part of this Shape are ignored. @@ -581,23 +581,28 @@ def only(self, dims: 'DimFilter'): Args: dims: comma-separated dimension names (str) or instance of dimensions (tuple, list, Shape) or filter function. + reorder: If `False`, keeps the dimension order as defined in this shape. + If `True`, reorders the dimensions of this shape to match the order of `dims`. Returns: Shape containing only specified dimensions """ + if dims is None: # keep none + return EMPTY_SHAPE if callable(dims): dims = dims(self) if isinstance(dims, str): dims = parse_dim_order(dims) - if isinstance(dims, (tuple, list)): - return self[[i for i in range(self.rank) if self.names[i] in dims]] - elif isinstance(dims, Shape): - return self[[i for i in range(self.rank) if self.names[i] in dims.names]] - elif dims is None: # keep none - return EMPTY_SHAPE + if isinstance(dims, Shape): + dims = dims.names + if reorder: + if isinstance(dims, (tuple, list)): + return self[[self.names.index(d) for d in dims if d in self.names]] else: - raise ValueError(dims) + if isinstance(dims, (tuple, list)): + return self[[i for i in range(self.rank) if self.names[i] in dims]] + raise ValueError(dims) @property def rank(self) -> int: diff --git a/tests/commit/math/test__ops.py b/tests/commit/math/test__ops.py index 409802a7c..f932b506b 100644 --- a/tests/commit/math/test__ops.py +++ b/tests/commit/math/test__ops.py @@ -4,7 +4,8 @@ import phi from phi import math -from phi.math import extrapolation, spatial, channel, instance, batch, DType, IncompatibleShapes, NAN, vec, non_spatial +from phi.field import assert_close +from phi.math import extrapolation, spatial, channel, instance, batch, DType, IncompatibleShapes, NAN, vec, non_spatial, wrap from phi.math.backend import Backend @@ -13,7 +14,7 @@ def assert_not_close(*tensors, rel_tolerance, abs_tolerance): try: - math.assert_close(*tensors, rel_tolerance, abs_tolerance) + assert_close(*tensors, rel_tolerance, abs_tolerance) raise Exception(AssertionError('Values are not close')) except AssertionError: pass @@ -23,23 +24,23 @@ class TestMathFunctions(TestCase): def test_assert_close(self): a = spatial(a=10) - math.assert_close(math.zeros(a), math.zeros(a), math.zeros(a), rel_tolerance=0, abs_tolerance=0) + assert_close(math.zeros(a), math.zeros(a), math.zeros(a), rel_tolerance=0, abs_tolerance=0) assert_not_close(math.zeros(a), math.ones(a), rel_tolerance=0, abs_tolerance=0) for scale in (1, 0.1, 10): - math.assert_close(math.zeros(a), math.ones(a) * scale, rel_tolerance=0, abs_tolerance=scale * 1.001) - math.assert_close(math.zeros(a), math.ones(a) * scale, rel_tolerance=1, abs_tolerance=0) + assert_close(math.zeros(a), math.ones(a) * scale, rel_tolerance=0, abs_tolerance=scale * 1.001) + assert_close(math.zeros(a), math.ones(a) * scale, rel_tolerance=1, abs_tolerance=0) assert_not_close(math.zeros(a), math.ones(a) * scale, rel_tolerance=0.9, abs_tolerance=0) assert_not_close(math.zeros(a), math.ones(a) * scale, rel_tolerance=0, abs_tolerance=0.9 * scale) with math.precision(64): assert_not_close(math.zeros(a), math.ones(a) * 1e-100, rel_tolerance=0, abs_tolerance=0) - math.assert_close(math.zeros(a), math.ones(a) * 1e-100, rel_tolerance=0, abs_tolerance=1e-15) + assert_close(math.zeros(a), math.ones(a) * 1e-100, rel_tolerance=0, abs_tolerance=1e-15) def test_concat(self): c = math.concat([math.zeros(spatial(b=3, a=2)), math.ones(spatial(a=2, b=4))], spatial('b')) self.assertEqual(2, c.shape.get_size('a')) self.assertEqual(7, c.shape.get_size('b')) - math.assert_close(c.b[:3], 0) - math.assert_close(c.b[3:], 1) + assert_close(c.b[:3], 0) + assert_close(c.b[3:], 1) def test_concat_missing_batch(self): t = math.random_normal(instance(particles=2)) @@ -68,72 +69,72 @@ def test_nonzero_batched(self): def test_maximum(self): v = math.ones(spatial(x=4, y=3) & channel(vector=2)) - math.assert_close(math.maximum(0, v), 1) - math.assert_close(math.maximum(0, -v), 0) + assert_close(math.maximum(0, v), 1) + assert_close(math.maximum(0, -v), 0) def test_finite_min(self): t = math.tensor([0, 1, -1, -math.INF, math.INF, math.NAN]) - math.assert_close(math.finite_min(t), -1) + assert_close(math.finite_min(t), -1) t = math.tensor([-math.INF, math.INF, math.NAN]) - math.assert_close(math.finite_min(t, default=0), 0) + assert_close(math.finite_min(t, default=0), 0) def test_finite_max(self): t = math.tensor([0, 1, -1, -math.INF, math.INF, math.NAN]) - math.assert_close(math.finite_max(t), 1) + assert_close(math.finite_max(t), 1) t = math.tensor([-math.INF, math.INF, math.NAN]) - math.assert_close(math.finite_max(t, default=0), 0) + assert_close(math.finite_max(t, default=0), 0) def test_finite_sum(self): t = math.tensor([0, 1, 1, -math.INF, math.INF, math.NAN]) - math.assert_close(math.finite_sum(t), 2) + assert_close(math.finite_sum(t), 2) t = math.tensor([-math.INF, math.INF, math.NAN]) - math.assert_close(math.finite_sum(t), math.NAN) + assert_close(math.finite_sum(t), math.NAN) def test_finite_mean(self): t = math.tensor([0, 1, 1, -math.INF, math.INF, math.NAN]) - math.assert_close(math.finite_mean(t), 2/3) + assert_close(math.finite_mean(t), 2/3) t = math.tensor([-math.INF, math.INF, math.NAN]) - math.assert_close(math.finite_mean(t), math.NAN) + assert_close(math.finite_mean(t), math.NAN) def test_sum_collapsed(self): ones = math.ones(spatial(x=40000, y=30000)) - math.assert_close(40000 * 30000, math.sum(ones)) + assert_close(40000 * 30000, math.sum(ones)) def test_prod_collapsed(self): ones = math.ones(spatial(x=40000, y=30000)) - math.assert_close(1, math.prod(ones)) + assert_close(1, math.prod(ones)) def test_mean_collapsed(self): ones = math.ones(spatial(x=40000, y=30000)) data = math.stack([ones, ones * 2], spatial('vector')) - math.assert_close(1.5, math.mean(data)) + assert_close(1.5, math.mean(data)) def test_std_collapsed(self): ones = math.ones(spatial(x=4, y=3)) # hi-res disabled because the current implementation caches the tensor, causes out-of-memory std = math.std(ones) - math.assert_close(0, std) + assert_close(0, std) def test_sum_by_type(self): a = math.ones(spatial(x=3, y=4), batch(b=10), instance(i=2), channel(vector=2)) - math.assert_close(math.sum(a, spatial), 12) + assert_close(math.sum(a, spatial), 12) def test_sum_bool(self): for backend in BACKENDS: with backend: a = math.tensor([True, False, True, False], spatial('x')) - math.assert_close(2, math.sum(a)) + assert_close(2, math.sum(a)) def test_unstack(self): a = math.random_uniform(batch(b=10), spatial(x=4, y=3), channel(vector=2)) u = math.unstack(a, 'vector') self.assertIsInstance(u, tuple) self.assertEqual(len(u), 2) - math.assert_close(u, math.unstack(a, channel)) + assert_close(u, math.unstack(a, channel)) # Multiple dimensions u = math.unstack(a, 'x,y') self.assertIsInstance(u, tuple) self.assertEqual(len(u), 12) - math.assert_close(u, math.unstack(a, spatial)) + assert_close(u, math.unstack(a, spatial)) def test_grid_sample(self): for backend in BACKENDS: @@ -141,14 +142,14 @@ def test_grid_sample(self): grid = math.sum(math.meshgrid(x=[1, 2, 3], y=[0, 3]), 'vector') # 1 2 3 | 4 5 6 coords = math.tensor([(0, 0), (0.5, 0), (0, 0.5), (-2, -1)], instance('list'), channel('vector')) interp = math.grid_sample(grid, coords, extrapolation.ZERO) - math.assert_close(interp, [1, 1.5, 2.5, 0], msg=backend.name) + assert_close(interp, [1, 1.5, 2.5, 0], msg=backend.name) def test_grid_sample_1d(self): grid = math.tensor([0, 1, 2, 3], spatial('x')) coords = math.tensor([[0], [1], [0.5]], spatial('x'), channel('vector')) sampled = math.grid_sample(grid, coords, None) math.print(sampled) - math.assert_close(sampled, [0, 1, 0.5]) + assert_close(sampled, [0, 1, 0.5]) def test_grid_sample_backend_equality_2d(self): grid = math.random_normal(spatial(y=10, x=7)) @@ -165,7 +166,7 @@ def test_grid_sample_backend_equality_2d(self): coords_ = math.tensor(coords_) sampled.append(math.grid_sample(grid, coords, extrap)) sampled.append(math.grid_sample(grid_, coords_, extrap)) - math.assert_close(*sampled, abs_tolerance=1e-6) + assert_close(*sampled, abs_tolerance=1e-6) def test_grid_sample_backend_equality_2d_batched(self): grid = math.random_normal(batch(mybatch=10) & spatial(y=10, x=7)) @@ -182,7 +183,7 @@ def test_grid_sample_backend_equality_2d_batched(self): coords_ = math.tensor(coords_) sampled.append(math.grid_sample(grid, coords, extrap)) sampled.append(math.grid_sample(grid_, coords_, extrap)) - math.assert_close(*sampled, abs_tolerance=1e-5) + assert_close(*sampled, abs_tolerance=1e-5) def test_grid_sample_gradient_1d(self): def f(grid, coords): @@ -197,8 +198,8 @@ def f(grid, coords): grid = math.tensor([0., 1, 2, 3], spatial('x')) coords = math.tensor([0.5, 1.5], instance('points')) grad_grid, grad_coords = f_grad(grid, coords) - math.assert_close(grad_grid, math.tensor([0.125, 0.5, 0.375, 0], spatial('x')), msg=backend) - math.assert_close(grad_coords, math.tensor([0.25, 0.75], instance('points')), msg=backend) + assert_close(grad_grid, math.tensor([0.125, 0.5, 0.375, 0], spatial('x')), msg=backend) + assert_close(grad_coords, math.tensor([0.25, 0.75], instance('points')), msg=backend) def test_grid_sample_gradient_2d(self): def f(grid, coords): @@ -217,14 +218,14 @@ def f(grid, coords): grad_grid, grad_coords = f_grad(grid, coords) grads_grid.append(grad_grid) grads_coords.append(grad_coords) - math.assert_close(*grads_grid) - math.assert_close(*grads_coords) + assert_close(*grads_grid) + assert_close(*grads_coords) def test_closest_grid_values_1d(self): grid = math.tensor([0, 1, 2, 3], spatial('x')) coords = math.tensor([[0.1], [1.9], [0.5], [3.1]], spatial('x'), channel('vector')) closest = math.closest_grid_values(grid, coords, extrapolation.ZERO) - math.assert_close(closest, math.tensor([(0, 1), (1, 2), (0, 1), (3, 0)], spatial('x'), channel('closest_x'))) + assert_close(closest, math.tensor([(0, 1), (1, 2), (0, 1), (3, 0)], spatial('x'), channel('closest_x'))) def test_join_dimensions(self): grid = math.random_normal(batch(batch=10) & spatial(x=4, y=3) & channel(vector=2)) @@ -238,7 +239,7 @@ def test_split_dimension(self): points = math.pack_dims(grid, grid.shape.spatial, instance('points')) split = points.points.split(grid.shape.spatial) self.assertEqual(grid.shape, split.shape) - math.assert_close(grid, split) + assert_close(grid, split) def test_cumulative_sum(self): t = math.tensor([(0, 1, 2, 3), (-1, -2, -3, -4)], spatial('y,x')) @@ -246,9 +247,9 @@ def test_cumulative_sum(self): with backend: t_ = math.tensor(t) x_ = math.cumulative_sum(t_, 'x') - math.assert_close(x_, [(0, 1, 3, 6), (-1, -3, -6, -10)], msg=backend.name) + assert_close(x_, [(0, 1, 3, 6), (-1, -3, -6, -10)], msg=backend.name) y_ = math.cumulative_sum(t_, t.shape[0]) - math.assert_close(y_, [(0, 1, 2, 3), (-1, -1, -1, -1)], msg=backend.name) + assert_close(y_, [(0, 1, 2, 3), (-1, -1, -1, -1)], msg=backend.name) def test_quantile(self): for backend in BACKENDS: @@ -256,13 +257,13 @@ def test_quantile(self): with backend: t = math.tensor([(1, 2, 3, 4), (1, 2, 3, 4), (6, 7, 8, 9)], batch('batch'), instance('list')) q = math.quantile(t, 0.5) - math.assert_close(q, [2.5, 2.5, 7.5], msg=backend.name) + assert_close(q, [2.5, 2.5, 7.5], msg=backend.name) q = math.quantile(t, [0.5, 0.6]) - math.assert_close(q, [(2.5, 2.5, 7.5), (2.8, 2.8, 7.8)], msg=backend.name) + assert_close(q, [(2.5, 2.5, 7.5), (2.8, 2.8, 7.8)], msg=backend.name) def test_median(self): t = math.tensor([(1, 2, 3, 10), (0, 1, 3, 10)], batch('batch'), instance('list')) - math.assert_close(math.median(t), [2.5, 2]) + assert_close(math.median(t), [2.5, 2]) def test_fft(self): def get_2d_sine(grid_size, L): @@ -281,7 +282,7 @@ def get_2d_sine(grid_size, L): sine_tensor = math.tensor(sine_field, spatial('x,y')) fft_tensor = math.fft(sine_tensor) self.assertEqual(fft_tensor.dtype, math.DType(complex, 128), msg=backend.name) - math.assert_close(fft_ref_tensor, fft_tensor, abs_tolerance=1e-12, msg=backend.name) # Should usually be more precise. GitHub Actions has larger errors than usual. + assert_close(fft_ref_tensor, fft_tensor, abs_tolerance=1e-12, msg=backend.name) # Should usually be more precise. GitHub Actions has larger errors than usual. def test_ifft(self): dimensions = 'xyz' @@ -291,7 +292,7 @@ def test_ifft(self): x = math.random_normal(spatial(**{dim: 6 for dim in dimensions[:d]})) + math.tensor((0, 1), batch('batch')) k = math.fft(x) x_ = math.ifft(k) - math.assert_close(x, x_, abs_tolerance=1e-5, msg=backend.name) + assert_close(x, x_, abs_tolerance=1e-5, msg=backend.name) def test_fft_dims(self): for backend in BACKENDS: @@ -301,7 +302,7 @@ def test_fft_dims(self): k = x for dim in 'xyz': k = math.fft(k, dim) - math.assert_close(k, k3, abs_tolerance=1e-5, msg=backend.name) + assert_close(k, k3, abs_tolerance=1e-5, msg=backend.name) def test_dot_vector(self): for backend in BACKENDS: @@ -310,8 +311,8 @@ def test_dot_vector(self): b = math.ones(spatial(b=4)) dot = math.dot(a, 'a', b, 'b') self.assertEqual(0, dot.rank, msg=backend.name) - math.assert_close(dot, 4, a.a * b.b, msg=backend.name) - math.assert_close(math.dot(a, 'a', a, 'a'), 4, msg=backend.name) + assert_close(dot, 4, a.a * b.b, msg=backend.name) + assert_close(math.dot(a, 'a', a, 'a'), 4, msg=backend.name) def test_dot_matrix(self): for backend in BACKENDS: @@ -320,7 +321,7 @@ def test_dot_matrix(self): b = math.ones(spatial(y=3, b=4)) dot = math.dot(a, 'a', b, 'b') self.assertEqual(set(spatial(x=2, y=3) & batch(batch=10)), set(dot.shape), msg=backend.name) - math.assert_close(dot, 4, msg=backend.name) + assert_close(dot, 4, msg=backend.name) def test_dot_batched_vector(self): for backend in BACKENDS: @@ -329,16 +330,16 @@ def test_dot_batched_vector(self): b = math.ones(batch(batch=10) & spatial(b=4)) dot = math.dot(a, 'a', b, 'b') self.assertEqual(batch(batch=10), dot.shape, msg=backend.name) - math.assert_close(dot, 4, a.a * b.b, msg=backend.name) + assert_close(dot, 4, a.a * b.b, msg=backend.name) dot = math.dot(a, 'a', a, 'a') self.assertEqual(batch(batch=10), dot.shape, msg=backend.name) - math.assert_close(dot, 4, a.a * a.a, msg=backend.name) + assert_close(dot, 4, a.a * a.a, msg=backend.name) # more dimensions a = math.ones(batch(batch=10) & spatial(a=4, x=2)) b = math.ones(batch(batch=10) & spatial(y=3, b=4)) dot = math.dot(a, 'a', b, 'b') self.assertEqual(set(spatial(x=2, y=3) & batch(batch=10)), set(dot.shape), msg=backend.name) - math.assert_close(dot, 4, msg=backend.name) + assert_close(dot, 4, msg=backend.name) def test_dot_missing_multiply(self): w1 = math.random_uniform(channel(neurons=64, input=1), low=-1, high=1) @@ -349,16 +350,16 @@ def test_dot_missing_multiply(self): def test_range(self): for backend in BACKENDS: with backend: - math.assert_close(math.range(spatial('x'), 1, 5), [1, 2, 3, 4], msg=backend.name) - math.assert_close(math.range(spatial('x'), 1), [0], msg=backend.name) + assert_close(math.range(spatial('x'), 1, 5), [1, 2, 3, 4], msg=backend.name) + assert_close(math.range(spatial('x'), 1), [0], msg=backend.name) def test_boolean_mask_1d(self): for backend in BACKENDS: with backend: x = math.range(spatial('range'), 4) mask = math.tensor([True, False, True, False], spatial('range')) - math.assert_close(math.boolean_mask(x, 'range', mask), [0, 2], msg=backend.name) - math.assert_close(x.range[mask], [0, 2], msg=backend.name) + assert_close(math.boolean_mask(x, 'range', mask), [0, 2], msg=backend.name) + assert_close(x.range[mask], [0, 2], msg=backend.name) def test_boolean_mask_batched(self): for backend in BACKENDS: @@ -368,9 +369,9 @@ def test_boolean_mask_batched(self): selected = math.boolean_mask(x, 'x', mask) expected_0 = math.tensor([(0, -0), (2, -2)], spatial('x'), channel('vector')) expected_1 = math.tensor([(1, -1)], spatial('x'), channel('vector')) - math.assert_close(selected.batch[0], expected_0, msg=backend.name) - math.assert_close(selected.batch[1], expected_1, msg=backend.name) - math.assert_close(selected, x.x[mask], msg=backend.name) + assert_close(selected.batch[0], expected_0, msg=backend.name) + assert_close(selected.batch[1], expected_1, msg=backend.name) + assert_close(selected, x.x[mask], msg=backend.name) def test_boolean_mask_semi_batched(self): for backend in BACKENDS: @@ -395,13 +396,13 @@ def test_scatter_1d(self): indices = math.wrap([1, 2], instance('points')) values = math.wrap([11, 12], instance('points')) updated = math.scatter(base, indices, values, mode='update', outside_handling='undefined') - math.assert_close(updated, [1, 11, 12, 1]) + assert_close(updated, [1, 11, 12, 1]) updated = math.scatter(base, indices, values, mode='add', outside_handling='undefined') - math.assert_close(updated, [1, 12, 13, 1]) + assert_close(updated, [1, 12, 13, 1]) # with vector dim indices = math.expand(indices, channel(vector=1)) updated = math.scatter(base, indices, values, mode='update', outside_handling='undefined') - math.assert_close(updated, [1, 11, 12, 1]) + assert_close(updated, [1, 11, 12, 1]) def test_scatter_update_1d_batched(self): for backend in BACKENDS: @@ -411,25 +412,25 @@ def test_scatter_update_1d_batched(self): indices = math.wrap([1, 2], instance('points')) values = math.wrap([11, 12], instance('points')) updated = math.scatter(base, indices, values, mode='update', outside_handling='undefined') - math.assert_close(updated, math.tensor([(0, 1), (11, 11), (12, 12), (0, 1)], spatial('x'), channel('vector')), msg=backend.name) + assert_close(updated, math.tensor([(0, 1), (11, 11), (12, 12), (0, 1)], spatial('x'), channel('vector')), msg=backend.name) # Only values batched base = math.ones(spatial(x=4)) indices = math.wrap([1, 2], instance('points')) values = math.wrap([[11, 12], [-11, -12]], batch('batch'), instance('points')) updated = math.scatter(base, indices, values, mode='update', outside_handling='undefined') - math.assert_close(updated, math.tensor([[1, 11, 12, 1], [1, -11, -12, 1]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(updated, math.tensor([[1, 11, 12, 1], [1, -11, -12, 1]], batch('batch'), spatial('x')), msg=backend.name) # Only indices batched base = math.ones(spatial(x=4)) indices = math.wrap([[0, 1], [1, 2]], batch('batch'), instance('points')) values = math.wrap([11, 12], instance('points')) updated = math.scatter(base, indices, values, mode='update', outside_handling='undefined') - math.assert_close(updated, math.tensor([[11, 12, 1, 1], [1, 11, 12, 1]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(updated, math.tensor([[11, 12, 1, 1], [1, 11, 12, 1]], batch('batch'), spatial('x')), msg=backend.name) # Everything batched base = math.zeros(spatial(x=4)) + math.tensor([0, 1], batch('batch')) indices = math.wrap([[0, 1], [1, 2]], batch('batch'), instance('points')) values = math.wrap([[11, 12], [-11, -12]], batch('batch'), instance('points')) updated = math.scatter(base, indices, values, mode='update', outside_handling='undefined') - math.assert_close(updated, math.tensor([[11, 12, 0, 0], [1, -11, -12, 1]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(updated, math.tensor([[11, 12, 0, 0], [1, -11, -12, 1]], batch('batch'), spatial('x')), msg=backend.name) def test_scatter_update_2d(self): for backend in BACKENDS: @@ -438,7 +439,7 @@ def test_scatter_update_2d(self): indices = math.wrap([(0, 0), (0, 1), (2, 1)], instance('points'), channel('vector')) values = math.wrap([11, 12, 13], instance('points')) updated = math.scatter(base, indices, values, mode='update', outside_handling='undefined') - math.assert_close(updated, math.tensor([[11, 1, 1], [12, 1, 13]], spatial('y,x'))) + assert_close(updated, math.tensor([[11, 1, 1], [12, 1, 13]], spatial('y,x'))) def test_scatter_add_2d(self): for backend in BACKENDS: @@ -447,44 +448,44 @@ def test_scatter_add_2d(self): indices = math.wrap([(0, 0), (0, 0), (0, 1), (2, 1)], instance('points'), channel('vector')) values = math.wrap([11, 11, 12, 13], instance('points')) updated = math.scatter(base, indices, values, mode='add', outside_handling='undefined') - math.assert_close(updated, math.tensor([[23, 1, 1], [13, 1, 14]], spatial('y,x'))) + assert_close(updated, math.tensor([[23, 1, 1], [13, 1, 14]], spatial('y,x'))) def test_scatter_2d_clamp(self): base = math.ones(spatial(x=3, y=2)) indices = math.wrap([(-1, 0), (0, 2), (4, 3)], instance('points'), channel('vector')) values = math.wrap([11, 12, 13], instance('points')) updated = math.scatter(base, indices, values, mode='update', outside_handling='clamp') - math.assert_close(updated, math.tensor([[11, 1, 1], [12, 1, 13]], spatial('y,x'))) + assert_close(updated, math.tensor([[11, 1, 1], [12, 1, 13]], spatial('y,x'))) def test_scatter_2d_discard(self): base = math.ones(spatial(x=3, y=2)) indices = math.wrap([(-1, 0), (0, 1), (3, 1)], instance('points'), channel('vector')) values = math.wrap([11, 12, 13], instance('points')) updated = math.scatter(base, indices, values, mode='update', outside_handling='discard') - math.assert_close(updated, math.tensor([[1, 1, 1], [12, 1, 1]], spatial('y,x'))) + assert_close(updated, math.tensor([[1, 1, 1], [12, 1, 1]], spatial('y,x'))) def test_scatter_single(self): base = math.zeros(spatial(x=3, y=2)) indices = vec(x=1, y=0) values = 1 updated = math.scatter(base, indices, values, outside_handling='discard') - math.assert_close(updated, math.tensor([[0, 1, 0], [0, 0, 0]], spatial('y,x'))) + assert_close(updated, math.tensor([[0, 1, 0], [0, 0, 0]], spatial('y,x'))) def test_sin(self): for backend in BACKENDS: with backend: - math.assert_close(math.sin(math.zeros(spatial(x=4))), 0, abs_tolerance=1e-6, msg=backend.name) - math.assert_close(math.sin(math.tensor(math.PI / 2)), 1, abs_tolerance=1e-6, msg=backend.name) - math.assert_close(math.sin(math.tensor(math.PI)), 0, abs_tolerance=1e-6, msg=backend.name) - math.assert_close(math.sin(math.tensor(math.PI * 3 / 2)), -1, abs_tolerance=1e-6, msg=backend.name) + assert_close(math.sin(math.zeros(spatial(x=4))), 0, abs_tolerance=1e-6, msg=backend.name) + assert_close(math.sin(math.tensor(math.PI / 2)), 1, abs_tolerance=1e-6, msg=backend.name) + assert_close(math.sin(math.tensor(math.PI)), 0, abs_tolerance=1e-6, msg=backend.name) + assert_close(math.sin(math.tensor(math.PI * 3 / 2)), -1, abs_tolerance=1e-6, msg=backend.name) def test_cos(self): for backend in BACKENDS: with backend: - math.assert_close(math.cos(math.zeros(spatial(x=4))), 1, abs_tolerance=1e-6, msg=backend.name) - math.assert_close(math.cos(math.tensor(math.PI / 2)), 0, abs_tolerance=1e-6, msg=backend.name) - math.assert_close(math.cos(math.tensor(math.PI)), -1, abs_tolerance=1e-6, msg=backend.name) - math.assert_close(math.cos(math.tensor(math.PI * 3 / 2)), 0, abs_tolerance=1e-6, msg=backend.name) + assert_close(math.cos(math.zeros(spatial(x=4))), 1, abs_tolerance=1e-6, msg=backend.name) + assert_close(math.cos(math.tensor(math.PI / 2)), 0, abs_tolerance=1e-6, msg=backend.name) + assert_close(math.cos(math.tensor(math.PI)), -1, abs_tolerance=1e-6, msg=backend.name) + assert_close(math.cos(math.tensor(math.PI * 3 / 2)), 0, abs_tolerance=1e-6, msg=backend.name) def test_trigonometric_hyperbolic(self): for f in [math.sin, math.cos, math.tan, math.sinh, math.cosh, math.tanh, @@ -494,7 +495,7 @@ def test_trigonometric_hyperbolic(self): with backend: value = math.tensor(0.3421) results.append(f(value)) - math.assert_close(results, msg=f.__name__) + assert_close(results, msg=f.__name__) def test_arccosh(self): results = [] @@ -502,7 +503,7 @@ def test_arccosh(self): with backend: value = math.tensor(1.3421) results.append(math.arccosh(value)) - math.assert_close(results) + assert_close(results) def test_arctan(self): results = [] @@ -510,39 +511,39 @@ def test_arctan(self): with backend: value = math.tensor(1.3421) results.append(math.arctan(value, divide_by=0)) - math.assert_close(results) + assert_close(results) def test_any(self): for backend in BACKENDS: with backend: - math.assert_close(math.any(math.tensor([[False, True], [False, False]], spatial('y,x')), dim='x'), [True, False]) - math.assert_close(math.any(math.tensor([[False, True], [False, False]], spatial('y,x')), dim='x,y'), True) - math.assert_close(math.any(math.tensor([[False, True], [False, False]], spatial('y,x'))), True) + assert_close(math.any(math.tensor([[False, True], [False, False]], spatial('y,x')), dim='x'), [True, False]) + assert_close(math.any(math.tensor([[False, True], [False, False]], spatial('y,x')), dim='x,y'), True) + assert_close(math.any(math.tensor([[False, True], [False, False]], spatial('y,x'))), True) def test_all(self): for backend in BACKENDS: with backend: - math.assert_close(math.all(math.tensor([[False, True], [True, True]], spatial('y,x')), dim='x'), [False, True]) - math.assert_close(math.all(math.tensor([[False, True], [True, True]], spatial('y,x')), dim='x,y'), False) - math.assert_close(math.all(math.tensor([[False, True], [True, True]], spatial('y,x'))), False) + assert_close(math.all(math.tensor([[False, True], [True, True]], spatial('y,x')), dim='x'), [False, True]) + assert_close(math.all(math.tensor([[False, True], [True, True]], spatial('y,x')), dim='x,y'), False) + assert_close(math.all(math.tensor([[False, True], [True, True]], spatial('y,x'))), False) def test_imag(self): for backend in BACKENDS: with backend: - math.assert_close(math.imag(math.ones(spatial(x=4))), 0, msg=backend.name) - math.assert_close(math.imag(math.ones(spatial(x=4)) * 1j), 1, msg=backend.name) + assert_close(math.imag(math.ones(spatial(x=4))), 0, msg=backend.name) + assert_close(math.imag(math.ones(spatial(x=4)) * 1j), 1, msg=backend.name) def test_real(self): for backend in BACKENDS: with backend: - math.assert_close(math.real(math.ones(spatial(x=4))), 1, msg=backend.name) - math.assert_close(math.real(math.ones(spatial(x=4)) * 1j), 0, msg=backend.name) + assert_close(math.real(math.ones(spatial(x=4))), 1, msg=backend.name) + assert_close(math.real(math.ones(spatial(x=4)) * 1j), 0, msg=backend.name) def test_conjugate(self): for backend in BACKENDS: with backend: - math.assert_close(math.conjugate(1 + 1j), 1 - 1j, msg=backend.name) - math.assert_close(math.conjugate(1j * math.ones()), -1j, msg=backend.name) + assert_close(math.conjugate(1 + 1j), 1 - 1j, msg=backend.name) + assert_close(math.conjugate(1j * math.ones()), -1j, msg=backend.name) def test_convolution_1d_scalar(self): for backend in BACKENDS: @@ -553,20 +554,20 @@ def test_convolution_1d_scalar(self): identity_kernel3 = math.tensor([0, 1, 0], spatial('x')) shift_kernel3 = math.tensor([0, 0, 1], spatial('x')) # no padding - math.assert_close(x, math.convolve(x, identity_kernel1), msg=backend.name) - math.assert_close(x.x[1:-1], math.convolve(x, identity_kernel3), msg=backend.name) - math.assert_close(x.x[1:], math.convolve(x, identity_kernel2), msg=backend.name) - math.assert_close(x.x[2:], math.convolve(x, shift_kernel3), msg=backend.name) + assert_close(x, math.convolve(x, identity_kernel1), msg=backend.name) + assert_close(x.x[1:-1], math.convolve(x, identity_kernel3), msg=backend.name) + assert_close(x.x[1:], math.convolve(x, identity_kernel2), msg=backend.name) + assert_close(x.x[2:], math.convolve(x, shift_kernel3), msg=backend.name) # zero-padding - math.assert_close(x, math.convolve(x, identity_kernel1, math.extrapolation.ZERO), msg=backend.name) - math.assert_close(x, math.convolve(x, identity_kernel3, math.extrapolation.ZERO), msg=backend.name) - math.assert_close(x, math.convolve(x, identity_kernel2, math.extrapolation.ZERO), msg=backend.name) - math.assert_close([2, 3, 4, 0], math.convolve(x, shift_kernel3, math.extrapolation.ZERO), msg=backend.name) + assert_close(x, math.convolve(x, identity_kernel1, math.extrapolation.ZERO), msg=backend.name) + assert_close(x, math.convolve(x, identity_kernel3, math.extrapolation.ZERO), msg=backend.name) + assert_close(x, math.convolve(x, identity_kernel2, math.extrapolation.ZERO), msg=backend.name) + assert_close([2, 3, 4, 0], math.convolve(x, shift_kernel3, math.extrapolation.ZERO), msg=backend.name) # periodic padding - math.assert_close(x, math.convolve(x, identity_kernel1, math.extrapolation.PERIODIC), msg=backend.name) - math.assert_close(x, math.convolve(x, identity_kernel3, math.extrapolation.PERIODIC), msg=backend.name) - math.assert_close(x, math.convolve(x, identity_kernel2, math.extrapolation.PERIODIC), msg=backend.name) - math.assert_close([2, 3, 4, 1], math.convolve(x, shift_kernel3, math.extrapolation.PERIODIC), msg=backend.name) + assert_close(x, math.convolve(x, identity_kernel1, math.extrapolation.PERIODIC), msg=backend.name) + assert_close(x, math.convolve(x, identity_kernel3, math.extrapolation.PERIODIC), msg=backend.name) + assert_close(x, math.convolve(x, identity_kernel2, math.extrapolation.PERIODIC), msg=backend.name) + assert_close([2, 3, 4, 1], math.convolve(x, shift_kernel3, math.extrapolation.PERIODIC), msg=backend.name) def test_convolution_1d_batched(self): for backend in BACKENDS: @@ -578,24 +579,24 @@ def test_convolution_1d_batched(self): identity_kernel3 = math.tensor([0, 1, 0], spatial('x')) shift_kernel3 = math.tensor([0, 0, 1], spatial('x')) # no padding - math.assert_close(math.convolve(x, identity_kernel1), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) - math.assert_close(math.convolve(x, identity_kernel2), math.tensor([[2, 3], [12, 13]], batch('batch'), spatial('x')), msg=backend.name) - math.assert_close(math.convolve(x, identity_kernel3), math.tensor([[2], [12]], batch('batch'), spatial('x')), msg=backend.name) - math.assert_close(math.convolve(x, shift_kernel3), math.tensor([[3], [13]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, identity_kernel1), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, identity_kernel2), math.tensor([[2, 3], [12, 13]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, identity_kernel3), math.tensor([[2], [12]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, shift_kernel3), math.tensor([[3], [13]], batch('batch'), spatial('x')), msg=backend.name) # # zero-padding - math.assert_close(math.convolve(x, identity_kernel1, math.extrapolation.ZERO), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) - math.assert_close(math.convolve(x, identity_kernel2, math.extrapolation.ZERO), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) - math.assert_close(math.convolve(x, identity_kernel3, math.extrapolation.ZERO), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) - math.assert_close(math.convolve(x, shift_kernel3, math.extrapolation.ZERO), math.tensor([[2, 3, 0], [12, 13, 0]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, identity_kernel1, math.extrapolation.ZERO), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, identity_kernel2, math.extrapolation.ZERO), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, identity_kernel3, math.extrapolation.ZERO), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, shift_kernel3, math.extrapolation.ZERO), math.tensor([[2, 3, 0], [12, 13, 0]], batch('batch'), spatial('x')), msg=backend.name) # # periodic padding - math.assert_close(math.convolve(x, identity_kernel1, math.extrapolation.PERIODIC), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) - math.assert_close(math.convolve(x, identity_kernel2, math.extrapolation.PERIODIC), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) - math.assert_close(math.convolve(x, identity_kernel3, math.extrapolation.PERIODIC), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) - math.assert_close(math.convolve(x, shift_kernel3, math.extrapolation.PERIODIC), math.tensor([[2, 3, 1], [12, 13, 11]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, identity_kernel1, math.extrapolation.PERIODIC), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, identity_kernel2, math.extrapolation.PERIODIC), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, identity_kernel3, math.extrapolation.PERIODIC), math.tensor([[1, 2, 3], [11, 12, 13]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, shift_kernel3, math.extrapolation.PERIODIC), math.tensor([[2, 3, 1], [12, 13, 11]], batch('batch'), spatial('x')), msg=backend.name) # values and filters batched mixed_kernel = math.tensor([[0, 1, 0], [0, 0, 1]], batch('batch'), spatial('x')) - math.assert_close(math.convolve(x, mixed_kernel, math.extrapolation.ZERO), math.tensor([[1, 2, 3], [12, 13, 0]], batch('batch'), spatial('x')), msg=backend.name) - math.assert_close(math.convolve(x, mixed_kernel, math.extrapolation.PERIODIC), math.tensor([[1, 2, 3], [12, 13, 11]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, mixed_kernel, math.extrapolation.ZERO), math.tensor([[1, 2, 3], [12, 13, 0]], batch('batch'), spatial('x')), msg=backend.name) + assert_close(math.convolve(x, mixed_kernel, math.extrapolation.PERIODIC), math.tensor([[1, 2, 3], [12, 13, 11]], batch('batch'), spatial('x')), msg=backend.name) # with output channels out_matrix = math.tensor([[1, 0], [0, 1], [1, 1]], channel('out'), channel('vector')).out.as_channel() kernel = identity_kernel3 * out_matrix @@ -603,7 +604,7 @@ def test_convolution_1d_batched(self): [[2, 4, 6], [22, 24, 26]], [[-1, -2, -3], [-11, -12, -13]], [[1, 2, 3], [11, 12, 13]]], channel('out'), batch('batch'), spatial('x')) - math.assert_close(math.convolve(x, kernel, math.extrapolation.ZERO), expected, msg=backend.name) + assert_close(math.convolve(x, kernel, math.extrapolation.ZERO), expected, msg=backend.name) # def test_convolution_2d(self): # TODO # pass @@ -633,12 +634,12 @@ def test_reshaped_native(self): def test_native(self): nat = np.zeros(4) self.assertIs(math.native(nat), nat) - math.assert_close(math.native(math.tensor(nat)), nat) + assert_close(math.native(math.tensor(nat)), nat) def test_numpy(self): nat = np.zeros(4) self.assertIs(math.numpy(nat), nat) - math.assert_close(math.numpy(math.tensor(nat)), nat) + assert_close(math.numpy(math.tensor(nat)), nat) def test_sparse(self): i = [[0, 1, 1], @@ -667,11 +668,11 @@ def test_divide_no_nan(self): zero = math.zeros() nan = zero / zero # inf = one / zero - math.assert_close(math.divide_no_nan(zero, one), zero) - math.assert_close(math.divide_no_nan(one, zero), zero) - math.assert_close(math.divide_no_nan(zero, zero), zero) - math.assert_close(math.divide_no_nan(zero, nan), nan) - math.assert_close(math.divide_no_nan(nan, one), nan) + assert_close(math.divide_no_nan(zero, one), zero) + assert_close(math.divide_no_nan(one, zero), zero) + assert_close(math.divide_no_nan(zero, zero), zero) + assert_close(math.divide_no_nan(zero, nan), nan) + assert_close(math.divide_no_nan(nan, one), nan) def test_random_int(self): for backend in BACKENDS: @@ -692,7 +693,7 @@ def test_random_complex(self): with backend: a = math.random_uniform(instance(values=4), low=-1, high=0, dtype=(complex, 64)) self.assertEqual(a.dtype, DType(complex, 64), msg=backend.name) - math.assert_close(a.imag, 0, msg=backend.name) + assert_close(a.imag, 0, msg=backend.name) def test_cast(self): for backend in BACKENDS: @@ -734,8 +735,8 @@ def test_where_nan(self): cond = math.linspace(0, 1, channel(linspace=2)) > 0 x = math.tensor([-1, -2, -3], spatial('x')) t = math.where(cond, x, NAN) - math.assert_close(t.linspace[0], NAN) - math.assert_close(t.linspace[1], x) + assert_close(t.linspace[0], NAN) + assert_close(t.linspace[1], x) def test_fit_hyperplane(self): for backend in BACKENDS: @@ -744,5 +745,21 @@ def test_fit_hyperplane(self): y = math.stack([0.8 * x[0] - x[1] + 1, -0.8 * x[0]], channel(features='y0,y1')) from phi.math._fit import fit_hyperplane w, b = fit_hyperplane(x, y, 'batch') - math.assert_close(w, math.wrap([(0.8, -1), (-0.8, 0)], channel(y), channel(x)), abs_tolerance=1e-3) - math.assert_close(b, math.wrap((1, 0), channel(y)), abs_tolerance=1e-3) + assert_close(w, wrap([(0.8, -1), (-0.8, 0)], channel(y), channel(x)), abs_tolerance=1e-3) + assert_close(b, wrap((1, 0), channel(y)), abs_tolerance=1e-3) + + def test_map(self): + def f(x, y): + return x + y + x = wrap((0, 1), spatial('x')) + y = wrap((2, 4), spatial('y')) + math.assert_close(wrap([(2, 4), (3, 5)], spatial('x,y')), math.map(f, x, y)) + + def test_map_multi_output(self): + def f(x, y): + return x + y, x - y + x = wrap((0, 1), spatial('x')) + y = wrap((2, 4), spatial('y')) + r_x, r_y = math.map(f, x, y) + math.assert_close(wrap([(2, 4), (3, 5)], spatial('x,y')), r_x) + math.assert_close(wrap([(-2, -4), (-1, -3)], spatial('x,y')), r_y) From 4b30eb10beb4c4c4512b32682e8508499d095e25 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 15 Jan 2023 18:26:05 +0100 Subject: [PATCH 052/170] [math] Auto-convert to int in Shape creation --- phi/math/_shape.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 501d7635d..b9fb90b0f 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -1157,10 +1157,19 @@ def _construct_shape(dim_type: str, *args, **dims): elif isinstance(size, Shape): items = size.names size = size.rank + elif size is None or isinstance(size, int): + # keep size + items = None else: - from ._tensors import Tensor - assert size is None or isinstance(size, (int, Tensor)), f"Cannot construct dimension from {type(size).__name__}. Only int, tuple, list, str or Shape allowed. Got {size}" items = None + from ._tensors import Tensor + if isinstance(size, Tensor): + size = int(size) if size.shape.volume == 1 else size + else: + try: + size = int(size) + except ValueError: + raise ValueError(f"Cannot construct dimension from {type(size).__name__}. Only int, tuple, list, str or Shape allowed. Got {size}") names += (name,) sizes += (size,) item_names += (items,) From bd9cb0c00d7b2c7160939ac6f9e04c360352c611 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 15 Jan 2023 18:26:22 +0100 Subject: [PATCH 053/170] [math] Support NumPy ** for Tensors --- phi/math/_tensors.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index 1e5b2c85e..fe289b3d1 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -122,6 +122,11 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): # NumPy interface return self._op2(inputs[1], lambda x, y: x % y, lambda x, y: choose_backend(x, y).mod(x, y), 'remainder', '%') else: return self._op2(inputs[0], lambda x, y: y % x, lambda x, y: choose_backend(x, y).mod(y, x), 'r_remainder', '%') + if ufunc.__name__ == 'power': + if inputs[0] is self: + return self._op2(inputs[1], lambda x, y: x ** y, lambda x, y: choose_backend(x, y).pow(x, y), 'power', '**') + else: + return self._op2(inputs[0], lambda x, y: y ** x, lambda x, y: choose_backend(x, y).pow(y, x), 'r_power', '**') if ufunc.__name__ == 'equal': if _EQUALITY_BY_REF: return wrap(inputs[0] is inputs[1]) From 82fdd16734768b377d07bba06cecbc27660de593 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 15 Jan 2023 18:27:50 +0100 Subject: [PATCH 054/170] [math] @jit_compile(auxiliary_args=...) --- phi/math/_functional.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/phi/math/_functional.py b/phi/math/_functional.py index 26a563ce7..47502d845 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -209,7 +209,7 @@ def __name__(self): return f_name(self.f) -def jit_compile(f: Callable, auxiliary_args: str = '') -> Callable: +def jit_compile(f: Callable = None, auxiliary_args: str = '') -> Callable: """ Compiles a graph based on the function `f`. The graph compilation is performed just-in-time (jit), e.g. when the returned function is called for the first time. @@ -250,6 +250,8 @@ def my_function(x: math.Tensor) -> math.Tensor: Returns: Function with similar signature and return values as `f`. """ + if f is None: + return partial(jit_compile, auxiliary_args=auxiliary_args) auxiliary_args = set(s.strip() for s in auxiliary_args.split(',') if s.strip()) return f if isinstance(f, (JitFunction, LinearFunction)) and f.auxiliary_args == auxiliary_args else JitFunction(f, auxiliary_args) @@ -361,6 +363,8 @@ def my_linear_function(x: math.Tensor) -> math.Tensor: Returns: `LinearFunction` with similar signature and return values as `f`. """ + if f is None: + return partial(jit_compile_linear, auxiliary_args=auxiliary_args) if isinstance(f, JitFunction): f = f.f # cannot trace linear function from jitted version if isinstance(auxiliary_args, str): From a1d4b5a246b7cf898d24687ff93309212321664b Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 16 Jan 2023 10:17:57 +0100 Subject: [PATCH 055/170] [math] Add forget_traces to jit --- phi/math/_functional.py | 38 ++++++++++++++++++--------- tests/commit/math/test__functional.py | 13 +++++++++ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/phi/math/_functional.py b/phi/math/_functional.py index 47502d845..2c3c44946 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -159,10 +159,11 @@ def f_name(f): class JitFunction: - def __init__(self, f: Callable, auxiliary_args: Set[str]): + def __init__(self, f: Callable, auxiliary_args: Set[str], forget_traces: bool): self.f = f self.f_params = function_parameters(f) self.auxiliary_args = auxiliary_args + self.forget_traces = forget_traces self.traces: Dict[SignatureKey, Callable] = {} self.recorded_mappings: Dict[SignatureKey, SignatureKey] = {} self.grad_jit = GradientFunction(f.f, f.wrt, f.get_output, f.is_f_scalar, jit=True) if isinstance(f, GradientFunction) else None @@ -191,11 +192,15 @@ def __call__(self, *args, **kwargs): warnings.warn(f"jit_copmile() not supported by {key.backend}. Running function '{f_name(self.f)}' as-is.", RuntimeWarning) return self.f(*args, **kwargs) if key not in self.traces: + if self.forget_traces: + self.traces.clear() + self.recorded_mappings.clear() self.traces[key] = self._jit_compile(key) if len(self.traces) >= 10: warnings.warn(f"""Φ-lin: The jit-compiled function '{f_name(self.f)}' was traced {len(self.traces)} times. Performing many traces may be slow and cause memory leaks. -Re-tracing occurs when the number or types of arguments vary or tensor shapes vary between calls.""", RuntimeWarning) +Re-tracing occurs when the number or types of arguments vary, tensor shapes vary between calls or different auxiliary arguments are given (compared by reference). +Set forget_traces=True to avoid memory leaks when many traces are required.""", RuntimeWarning) native_result = self.traces[key](*natives) output_key = match_output_signature(key, self.recorded_mappings, self) output_tensors = assemble_tensors(native_result, output_key.shapes, output_key.native_dims) @@ -209,7 +214,7 @@ def __name__(self): return f_name(self.f) -def jit_compile(f: Callable = None, auxiliary_args: str = '') -> Callable: +def jit_compile(f: Callable = None, auxiliary_args: str = '', forget_traces: bool = None) -> Callable: """ Compiles a graph based on the function `f`. The graph compilation is performed just-in-time (jit), e.g. when the returned function is called for the first time. @@ -246,14 +251,17 @@ def my_function(x: math.Tensor) -> math.Tensor: f: Function to be traced. All positional arguments must be of type `Tensor` or `PhiTreeNode` returning a single `Tensor` or `PhiTreeNode`. auxiliary_args: Comma-separated parameter names of arguments that are not relevant to backpropagation. + forget_traces: If `True`, only remembers the most recent compiled instance of this function. + Upon tracing with new instance (due to changed shapes or auxiliary args), deletes the previous traces. Returns: Function with similar signature and return values as `f`. """ if f is None: - return partial(jit_compile, auxiliary_args=auxiliary_args) + kwargs = {k: v for k, v in locals().items() if v is not None} + return partial(jit_compile_linear, **kwargs) auxiliary_args = set(s.strip() for s in auxiliary_args.split(',') if s.strip()) - return f if isinstance(f, (JitFunction, LinearFunction)) and f.auxiliary_args == auxiliary_args else JitFunction(f, auxiliary_args) + return f if isinstance(f, (JitFunction, LinearFunction)) and f.auxiliary_args == auxiliary_args else JitFunction(f, auxiliary_args, forget_traces or False) class LinearFunction(Generic[X, Y], Callable[[X], Y]): @@ -263,12 +271,13 @@ class LinearFunction(Generic[X, Y], Callable[[X], Y]): Use `jit_compile_linear()` to create a linear function representation. """ - def __init__(self, f, auxiliary_args: Set[str]): + def __init__(self, f, auxiliary_args: Set[str], forget_traces: bool): self.f = f self.f_params = function_parameters(f) self.auxiliary_args = auxiliary_args + self.forget_traces = forget_traces self.tracers: Dict[SignatureKey, ShiftLinTracer] = {} - self.nl_jit = JitFunction(f, self.auxiliary_args) # for backends that do not support sparse matrices + self.nl_jit = JitFunction(f, self.auxiliary_args, forget_traces) # for backends that do not support sparse matrices def _trace(self, in_key: SignatureKey, prefer_numpy: bool) -> 'ShiftLinTracer': assert in_key.shapes[0].is_uniform, f"math.jit_compile_linear() only supports uniform tensors for function input and output but input shape was {in_key.shapes[0]}" @@ -287,15 +296,17 @@ def _get_or_trace(self, key: SignatureKey, prefer_numpy: bool): if not key.tracing and key in self.tracers: return self.tracers[key] else: + if self.forget_traces: + self.tracers.clear() tracer = self._trace(key, prefer_numpy=prefer_numpy) if not key.tracing: self.tracers[key] = tracer if len(self.tracers) >= 4: warnings.warn(f"""Φ-lin: The compiled linear function '{f_name(self.f)}' was traced {len(self.tracers)} times. Performing many traces may be slow and cause memory leaks. -Tensors in conditioning arguments (all except the first parameter unless specified otherwise) are compared by reference, not by tensor values. +Tensors in auxiliary arguments (all except the first parameter unless specified otherwise) are compared by reference, not by tensor values. Auxiliary arguments: {key.auxiliary_kwargs} -Multiple linear traces can be avoided by jit-compiling the code that calls the linear function.""", RuntimeWarning, stacklevel=3) +Multiple linear traces can be avoided by jit-compiling the code that calls the linear function or setting forget_traces=True.""", RuntimeWarning, stacklevel=3) return tracer def __call__(self, *args: X, **kwargs) -> Y: @@ -339,7 +350,7 @@ def __repr__(self): return f"lin({f_name(self.f)})" -def jit_compile_linear(f: Callable[[X], Y], auxiliary_args: str = None) -> 'LinearFunction[X, Y]': # TODO add cache control method, e.g. max_traces +def jit_compile_linear(f: Callable[[X], Y] = None, auxiliary_args: str = None, forget_traces: bool = None) -> 'LinearFunction[X, Y]': """ Compile an optimized representation of the linear function `f`. For backends that support sparse tensors, a sparse matrix will be constructed for `f`. @@ -359,12 +370,15 @@ def my_linear_function(x: math.Tensor) -> math.Tensor: f: Function that is linear in its positional arguments. All positional arguments must be of type `Tensor` and `f` must return a `Tensor`. auxiliary_args: Which parameters `f` is not linear in. These arguments are treated as conditioning arguments and will cause re-tracing on change. + forget_traces: If `True`, only remembers the most recent compiled instance of this function. + Upon tracing with new instance (due to changed shapes or auxiliary args), deletes the previous traces. Returns: `LinearFunction` with similar signature and return values as `f`. """ if f is None: - return partial(jit_compile_linear, auxiliary_args=auxiliary_args) + kwargs = {k: v for k, v in locals().items() if v is not None} + return partial(jit_compile_linear, **kwargs) if isinstance(f, JitFunction): f = f.f # cannot trace linear function from jitted version if isinstance(auxiliary_args, str): @@ -373,7 +387,7 @@ def my_linear_function(x: math.Tensor) -> math.Tensor: assert auxiliary_args is None f_params = function_parameters(f) auxiliary_args = f_params[1:] - return f if isinstance(f, LinearFunction) and f.auxiliary_args == auxiliary_args else LinearFunction(f, auxiliary_args) + return f if isinstance(f, LinearFunction) and f.auxiliary_args == auxiliary_args else LinearFunction(f, auxiliary_args, forget_traces or False) def simplify_wrt(f, wrt: str or int or tuple or list): diff --git a/tests/commit/math/test__functional.py b/tests/commit/math/test__functional.py index 9e68170ef..4bc83de91 100644 --- a/tests/commit/math/test__functional.py +++ b/tests/commit/math/test__functional.py @@ -353,3 +353,16 @@ def f(x, fac): self.assertEqual(4, math.iterate(f, 2, 1, f_kwargs=dict(fac=2.))) math.assert_close([1, 2, 4], math.iterate(f, batch(trajectory=2), 1, f_kwargs=dict(fac=2.))) + + def test_delayed_decorator(self): + def f(x, y): + return x + y + for jit in [math.jit_compile, math.jit_compile_linear]: + f_ = jit(auxiliary_args='y', forget_traces=True)(f) + self.assertTrue(f_.forget_traces) + f_ = jit(auxiliary_args='y')(f) + self.assertFalse(f_.forget_traces) + f_ = jit(forget_traces=True)(f) + self.assertTrue(f_.forget_traces) + f_ = jit()(f) + self.assertFalse(f_.forget_traces) From a731ac2430aad2243b17dba949fc4a40f39bafc0 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 16 Jan 2023 14:17:16 +0100 Subject: [PATCH 056/170] [tests] Fix math/test__ops.py --- tests/commit/math/test__ops.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/commit/math/test__ops.py b/tests/commit/math/test__ops.py index f932b506b..6b2698225 100644 --- a/tests/commit/math/test__ops.py +++ b/tests/commit/math/test__ops.py @@ -4,8 +4,7 @@ import phi from phi import math -from phi.field import assert_close -from phi.math import extrapolation, spatial, channel, instance, batch, DType, IncompatibleShapes, NAN, vec, non_spatial, wrap +from phi.math import extrapolation, spatial, channel, instance, batch, DType, IncompatibleShapes, NAN, vec, non_spatial, wrap, assert_close from phi.math.backend import Backend From f7d2f43a5da44086e55694e2af3dd3a3388a8c81 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 16 Jan 2023 14:23:20 +0100 Subject: [PATCH 057/170] [math] Add range argument to map() --- phi/math/_ops.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index f821147ec..edadccf66 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -373,14 +373,17 @@ def variables(obj) -> dict: print(f"{wrap(obj):full}") -def map_(function, *values, **kwargs) -> Tensor or None: +def map_(function, *values, range=range, **kwargs) -> Tensor or None: """ - Calls `function` on all elements of `value`. + Calls `function` on all elements of `values`. Args: function: Function to be called on single elements contained in `value`. Must return a value that can be stored in tensors. - values: Tensors to iterate over. Number of tensors must match `function` signature. - kwargs: Keyword arguments for `function`. + *values: `Tensors` containing positional arguments for `function`. + Number of tensors must match `function` signature. + range: Range function. Can be used to generate tqdm output by passing `trange`. + **kwargs: Non-`Tensor` keyword arguments for `function`. + Their shapes are not broadcast with the positional arguments. Returns: `Tensor` of same shape as `value`. @@ -390,7 +393,7 @@ def map_(function, *values, **kwargs) -> Tensor or None: flat = [pack_dims(expand(v, shape), shape, batch('flat')) for v in values] result = [] results = None - for items in zip(*flat): + for _, items in zip(range(flat[0].flat.size), zip(*flat)): f_output = function(*items, **kwargs) if isinstance(f_output, tuple): if results is None: From c56c4dbd441bd4d2f37c7f1a1b5360bc773a6556 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 16 Jan 2023 14:46:55 +0100 Subject: [PATCH 058/170] [math] Fix number formatting Previously, TensorFlow numbers would be printed as `tensor(...)` --- phi/math/_tensors.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index fe289b3d1..029fabace 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -2410,8 +2410,10 @@ def _format_vector(self: Tensor, options: PrintOptions) -> str: def _format_number(num, options: PrintOptions, dtype: DType): if options.float_format is not None: return format(num, options.float_format) - if dtype.kind in (bool, int): - return str(num) + if dtype.kind == int: + return format(num, 'd') + if dtype.kind == bool: + return str(bool(num)) if dtype.kind == float: return format(num, options.float_format or '.3f') return str(num) From cc8c981a7d366c492cca3100aaa9d8b5865a2638 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 16 Jan 2023 15:19:16 +0100 Subject: [PATCH 059/170] [math] Chaining BoundDims This enables the new syntax obj.dim1.dim2... --- phi/math/magic.py | 108 +++++++++++++++++++++++++-- tests/commit/math/test__magic_ops.py | 17 ++++- 2 files changed, 117 insertions(+), 8 deletions(-) diff --git a/phi/math/magic.py b/phi/math/magic.py index 89bec5cbd..95b832b65 100644 --- a/phi/math/magic.py +++ b/phi/math/magic.py @@ -18,7 +18,7 @@ import warnings from typing import Tuple, Callable -from ._shape import Shape, shape, channel, non_batch +from ._shape import Shape, shape, channel, non_batch, batch, spatial, instance, concat_shapes from .backend._dtype import DType @@ -416,8 +416,8 @@ def __getattr__(self, name: str) -> BoundDim: **Usage** - * `obj.dim.size` return the dimension size. - * `obj.dim.item_names` return the dimension item names. + * `obj.dim.size` returns the dimension size. + * `obj.dim.item_names` returns the dimension item names. * `obj.dim.exists` checks whether a dimension is listed in the shape of the bound object. * `obj.dim[0]` picks the first element along `dim`. The shape of the result will not contain `dim`. * `obj.dim[1:-1]` discards the first and last element along `dim`. @@ -425,6 +425,16 @@ def __getattr__(self, name: str) -> BoundDim: * `obj.dim.as_channel()` changes the type of `dim` to *channel*. * `obj.dim.unstack()` un-stacks the bound value along `dim`. * `for slice in obj.dim` loops over all slices of `dim`. + + Multiple dimensions can also be chained together using `obj.dim1.dim2...`. + This supports the following operations: + + * `obj.dim1.dim2...volume` returns the product of the sizes + * `obj.dim1.dim2...[0, -1]` takes the first element along `dim1` and the last element along `dim2` + * `obj.dim1.dim2...pack(new_dim)` packs the dimensions into a new dimension with size equal to their volume + * `obj.dim1.dim2...unstack()` un-stacks `obj` along multiple dimensions + * `obj.dim1.dim2...retype(type)` Changes the type of all selected dimensions + * `for slice in obj.dim1.dim2...` loops over all slices as if unstacking first """ def __init__(self, obj, name: str): @@ -465,6 +475,8 @@ def size(self): """ Length of this dimension as listed in the `Shape` of the bound object. """ return shape(self.obj).get_size(self.name) if self.exists else None + volume = size + @property def size_or_1(self): return shape(self.obj).get_size(self.name) if self.exists else 1 @@ -489,6 +501,9 @@ def __getitem__(self, item): def __setitem__(self, key, value): self.obj[{self.name: key}] = value + def __getattr__(self, item): + return _BoundDims(self.obj, (self.name, item)) + def unstack(self, size: int or None = None) -> tuple: """ Lists the slices along this dimension as a `tuple`. @@ -540,13 +555,26 @@ def retype(self, dim_type: Callable, **kwargs): See Also: `phi.math.rename_dims()` """ - if self.item_names is not None: - new_dim = dim_type(**{self.name: self.item_names}) - else: - new_dim = dim_type(**{self.name: self.size}) + new_dim = dim_type(**{self.name: self.item_names or self.size}) from ._magic_ops import rename_dims return rename_dims(self.obj, self.name, new_dim, **kwargs) + def as_batch(self, name: str = None): + """ Returns a shallow copy of the `Tensor` where the type of this dimension is *batch*. """ + return self.retype(batch) if name is None else self.replace(batch(name=self.item_names or self.size)) + + def as_spatial(self, name: str = None): + """ Returns a shallow copy of the `Tensor` where the type of this dimension is *spatial*. """ + return self.retype(spatial) if name is None else self.replace(spatial(name=self.item_names or self.size)) + + def as_channel(self, name: str = None): + """ Returns a shallow copy of the `Tensor` where the type of this dimension is *channel*. """ + return self.retype(channel) if name is None else self.replace(channel(name=self.item_names or self.size)) + + def as_instance(self, name: str = None): + """ Returns a shallow copy of the `Tensor` where the type of this dimension is *instance*. """ + return self.retype(instance) if name is None else self.replace(instance(name=self.item_names or self.size)) + def replace(self, dim: Shape, **kwargs): """ Returns a shallow copy of the `Tensor` where this dimension has been replaced by `dim`. @@ -568,6 +596,72 @@ def unpack(self, dims: Shape, **kwargs): return unpack_dim(self.obj, self.name, dims, **kwargs) +class _BoundDims: + + def __init__(self, obj, dims: Tuple[str, ...]): + self.obj = obj + self.dims = dims + + def __getitem__(self, item): + assert isinstance(item, tuple), f"A tuple of slices is required for slicing multiple dimensions at once but got {type(item)}" + assert len(item) == len(self.dims), f"Number of slices must equal number of dimensions but got {len(item)} for dims {self.dims}" + return self.obj[{dim: i for dim, i in zip(self.dims, item)}] + + def __getattr__(self, item): + return _BoundDims(self.obj, self.dims + (item,)) + + def __len__(self): + return self.volume + + @property + def size(self): + raise SyntaxError("dim.size only exists for single dimensions. Use .volume for multiple dimensions") + + @property + def volume(self): + return shape(self.obj).only(self.dims).volume + + def pack(self, packed_dim: Shape, pos=None, **kwargs): + from ._magic_ops import pack_dims + return pack_dims(self.obj, self.dims, packed_dim, pos=pos, **kwargs) + + def unstack(self) -> tuple: + from ._magic_ops import unstack + return unstack(self.obj, self.dims) + + def __iter__(self): + """ Iterate over slices along this dim """ + return iter(self.unstack()) + + def retype(self, dim_type: Callable, **kwargs): + """ + Returns a shallow copy of the `Tensor` where this dimension has the specified type. + + See Also: + `phi.math.rename_dims()` + """ + s = shape(self.obj) + new_dims = concat_shapes(*[dim_type(**{dim: s.get_item_names(dim) or s.get_size(dim)}) for dim in self.dims]) + from ._magic_ops import rename_dims + return rename_dims(self.obj, self.dims, new_dims, **kwargs) + + def as_batch(self): + """ Returns a shallow copy of the `Tensor` where the type of this dimension is *batch*. """ + return self.retype(batch) + + def as_spatial(self): + """ Returns a shallow copy of the `Tensor` where the type of this dimension is *spatial*. """ + return self.retype(spatial) + + def as_channel(self): + """ Returns a shallow copy of the `Tensor` where the type of this dimension is *channel*. """ + return self.retype(channel) + + def as_instance(self): + """ Returns a shallow copy of the `Tensor` where the type of this dimension is *instance*. """ + return self.retype(instance) + + def slicing_dict(obj, item) -> dict: """ Creates a slicing `dict` from `item` where `item` is an arbitrary value passed to `__getitem__()`. diff --git a/tests/commit/math/test__magic_ops.py b/tests/commit/math/test__magic_ops.py index ec794e245..9af12a2d2 100644 --- a/tests/commit/math/test__magic_ops.py +++ b/tests/commit/math/test__magic_ops.py @@ -2,7 +2,7 @@ from unittest import TestCase from phi.math import batch, unstack, Shape, merge_shapes, stack, concat, expand, spatial, shape, instance, rename_dims, \ - pack_dims, random_normal, flatten, unpack_dim, EMPTY_SHAPE, Tensor, Dict, channel, linspace, zeros + pack_dims, random_normal, flatten, unpack_dim, EMPTY_SHAPE, Tensor, Dict, channel, linspace, zeros, meshgrid, assert_close from phi.math.magic import BoundDim, Shaped, Sliceable, Shapable, PhiTreeNode, slicing_dict @@ -187,3 +187,18 @@ def test_phi_tree_subclasscheck(self): self.assertTrue(issubclass(list, PhiTreeNode)) self.assertTrue(issubclass(dict, PhiTreeNode)) self.assertTrue(issubclass(Dict, PhiTreeNode)) + + def test_bound_dims(self): + v = meshgrid(x=4, y=3) + assert_close(1, v.x.y.vector[1, 0, 0]) + assert ('x', 'y') == instance(v.x.y.as_instance()).names + assert instance(points=12) & channel(vector='x,y') == v.x.y.pack(instance('points')).shape + assert 12 == v.x.y.volume + assert 24 == v.x.y.vector.volume + assert 24 == len(v.x.y.vector.unstack()) + assert 24 == len(tuple(v.x.y.vector)) + try: + v.x.y.size + self.fail() + except SyntaxError: + pass From 4161f91e3109d697ecc1890fdcd6b89b4eeeabc6 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 16 Jan 2023 15:43:14 +0100 Subject: [PATCH 060/170] [vis] Matplotlib improved labels and annotation positioning --- phi/vis/_matplotlib/_matplotlib_plots.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index f31eb98a3..18d688e4f 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -50,15 +50,15 @@ def create_figure(self, else: bounds = spaces[(row, col)] if bounds.spatial_rank == 1: - axis.set_xlabel(bounds.vector.item_names[0]) + axis.set_xlabel(display_name(bounds.vector.item_names[0])) axis.set_xlim(_get_range(bounds, 0)) if bounds.vector.item_names[0] in log_dims: axis.set_xscale('log') if '_' in log_dims: axis.set_yscale('log') elif bounds.spatial_rank == 2: - axis.set_xlabel(bounds.vector.item_names[0]) - axis.set_ylabel(bounds.vector.item_names[1]) + axis.set_xlabel(display_name(bounds.vector.item_names[0])) + axis.set_ylabel(display_name(bounds.vector.item_names[1])) x_range, y_range = [_get_range(bounds, i) for i in (0, 1)] axis.set_xlim(x_range) axis.set_ylim(y_range) @@ -75,9 +75,9 @@ def create_figure(self, elif bounds.spatial_rank == 3: axis.remove() axis = axes[row, col] = figure.add_subplot(rows, cols, cols*row + col + 1, projection='3d') - axis.set_xlabel(bounds.vector.item_names[0]) - axis.set_ylabel(bounds.vector.item_names[1]) - axis.set_zlabel(bounds.vector.item_names[2]) + axis.set_xlabel(display_name(bounds.vector.item_names[0])) + axis.set_ylabel(display_name(bounds.vector.item_names[1])) + axis.set_zlabel(display_name(bounds.vector.item_names[2])) axis.set_xlim(_get_range(bounds, 0)) axis.set_ylim(_get_range(bounds, 1)) axis.set_zlim(_get_range(bounds, 2)) @@ -340,7 +340,9 @@ def _annotate_points(axis, points: math.Tensor, labelled_dim: math.Shape): x_view = axis.get_xlim()[1] - axis.get_xlim()[0] y_view = axis.get_ylim()[1] - axis.get_ylim()[0] for x_, y_, label in zip(x, y, labelled_dim.item_names[0]): - axis.annotate(label, (x_ + .01 * x_view, y_ + .01 * y_view)) + offset_x = x_ * (1 + .0003 * x_view) if axis.get_xscale() == 'log' else x_ + .01 * x_view + offset_y = y_ * (1 + .0003 * y_view) if axis.get_yscale() == 'log' else y_ + .01 * y_view + axis.text(offset_x, offset_y, label) def _rgba(col): From b2115e3a108a1749172add266a0c631bc8f25bd1 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 16 Jan 2023 17:02:31 +0100 Subject: [PATCH 061/170] [math] Fix map() for zero-dim cases --- phi/math/_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index edadccf66..9975f2e03 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -393,7 +393,7 @@ def map_(function, *values, range=range, **kwargs) -> Tensor or None: flat = [pack_dims(expand(v, shape), shape, batch('flat')) for v in values] result = [] results = None - for _, items in zip(range(flat[0].flat.size), zip(*flat)): + for _, items in zip(range(flat[0].flat.size_or_1), zip(*flat)): f_output = function(*items, **kwargs) if isinstance(f_output, tuple): if results is None: From 8d430d2eabbdd5dac69c36aad250260c94f18ddf Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 16 Jan 2023 17:30:06 +0100 Subject: [PATCH 062/170] [math] Implement pack_dims(Layout) for flatten --- phi/math/_tensors.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index 029fabace..c2569130b 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -952,10 +952,13 @@ def __replace_dims__(self, dims: Tuple[str, ...], new_dims: Shape, **kwargs) -> new_shape = self._shape.replace(dims, new_dims) return Layout(self._obj, new_shape) - def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or None, **kwargs) -> 'Shapable': - return NotImplemented + def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or None, **kwargs) -> 'Layout': + if dims == self.shape.names: + native = self._as_list() + return Layout(native, packed_dim.with_size(len(native))) + raise NotImplementedError - def __unpack_dim__(self, dim: str, unpacked_dims: Shape, **kwargs) -> 'Shapable': + def __unpack_dim__(self, dim: str, unpacked_dims: Shape, **kwargs) -> 'Layout': return NotImplemented def __cast__(self, dtype: DType): From daa06a40eab88e0e5b8d30a9f9679134b2643343 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 17 Jan 2023 16:15:32 +0100 Subject: [PATCH 063/170] [physics] No Euler integration in finite_difference functions --- phi/physics/advect.py | 17 ++++++----------- phi/physics/diffuse.py | 11 ++--------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/phi/physics/advect.py b/phi/physics/advect.py index 0c5d395d7..dce6e8dee 100644 --- a/phi/physics/advect.py +++ b/phi/physics/advect.py @@ -81,7 +81,6 @@ def advect(field: SampledField, def finite_difference(grid: Grid, velocity: Field, - dt: float or math.Tensor, scheme: Scheme = Scheme(2)) -> Field: """ @@ -90,7 +89,6 @@ def finite_difference(grid: Grid, Args: grid: Grid to be advected velocity: `Grid` that can be sampled in the elements of `grid`. - dt: Time increment scheme: finite difference `Scheme` used for differentiation supported: explicit 2/4th order - implicit 6th order @@ -100,20 +98,17 @@ def finite_difference(grid: Grid, if isinstance(grid, StaggeredGrid): field_components = unstack(grid, 'vector') - grad_list = [spatial_gradient(field_component, stack_dim=math.channel('gradient'), scheme=scheme) for - field_component in field_components] + grad_list = [spatial_gradient(field_component, stack_dim=math.channel('gradient'), scheme=scheme) for field_component in field_components] grad_grid = grid.with_values(math.stack([component.values for component in grad_list], math.channel('vector'))) velocity._scheme = True - ammounts = [grad * vel.at(grad, scheme=scheme) for grad, vel in - zip(unstack(grad_grid, dim='gradient'), unstack(velocity, dim='vector'))] - ammount = sum(ammounts) + amounts = [grad * vel.at(grad, scheme=scheme) for grad, vel in zip(unstack(grad_grid, dim='gradient'), unstack(velocity, dim='vector'))] + amount = sum(amounts) else: grad = spatial_gradient(grid, stack_dim=math.channel('gradient'), scheme=scheme) velocity = stack(unstack(velocity, dim='vector'), dim=math.channel('gradient')) - ammounts = velocity * grad - ammount = sum(unstack(ammounts, dim='gradient')) - - return grid - dt * ammount + amounts = velocity * grad + amount = sum(unstack(amounts, dim='gradient')) + return - amount def points(field: PointCloud, velocity: Field, dt: float, integrator=euler): diff --git a/phi/physics/diffuse.py b/phi/physics/diffuse.py index 76dd9397a..3cd95cbf3 100644 --- a/phi/physics/diffuse.py +++ b/phi/physics/diffuse.py @@ -66,7 +66,6 @@ def sharpen(x): def finite_difference(grid: Grid, diffusivity: float or math.Tensor or Field, - dt: float or math.Tensor, scheme: Scheme = Scheme(2)) -> FieldType: """ @@ -77,20 +76,14 @@ def finite_difference(grid: Grid, Args: grid: CenteredGrid or StaggeredGrid diffusivity: Diffusion per time. `diffusion_amount = diffusivity * dt` - dt: Time interval. `diffusion_amount = diffusivity * dt` scheme: finite difference `Scheme` used for differentiation supported: explicit 2/4th order - implicit 6th order Returns: Diffused grid of same type as `grid`. """ - - amount = diffusivity * dt - if isinstance(amount, Field): - amount = amount.at(grid) - - grid += amount * laplace(grid, scheme=scheme).with_extrapolation(grid.extrapolation) - return grid + diffusivity = diffusivity.at(grid) if isinstance(diffusivity, Field) else diffusivity + return diffusivity * laplace(grid, scheme=scheme).with_extrapolation(grid.extrapolation) def fourier(field: GridType, From ab0e727604924b2ceadbc7e6371287a3ed77f22f Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 17 Jan 2023 16:15:48 +0100 Subject: [PATCH 064/170] [physics] Add fluid.incompressible_rk4 --- phi/physics/fluid.py | 50 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index 449c45e11..158ea0768 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -3,14 +3,14 @@ The main function for incompressible fluids (Eulerian as well as FLIP / PIC) is `make_incompressible()` which removes the divergence of a velocity field. """ -from typing import Tuple +from typing import Tuple, Callable from phi import math, field -from phi.math import wrap, channel +from phi.math import wrap, channel, Solve from phi.field import SoftGeometryMask, AngularVelocity, Grid, divergence, spatial_gradient, where, CenteredGrid, PointCloud from phi.geom import union, Geometry from ..field._embed import FieldEmbedding -from ..field._grid import GridType +from ..field._grid import GridType, StaggeredGrid from ..field.numerical import Scheme from ..math import extrapolation, NUMPY, batch, shape, non_channel, expand from ..math._magic_ops import copy_with @@ -53,7 +53,7 @@ def copied_with(self, **kwargs): def make_incompressible(velocity: GridType, obstacles: tuple or list = (), - solve=math.Solve('auto', 1e-5, 1e-5, gradient_solve=math.Solve('auto', 1e-5, 1e-5)), + solve=Solve('auto', 1e-5, 1e-5, gradient_solve=Solve('auto', 1e-5, 1e-5)), active: CenteredGrid = None, scheme: Scheme = Scheme(2)) -> Tuple[GridType, CenteredGrid]: """ @@ -106,7 +106,7 @@ def make_incompressible(velocity: GridType, solve = copy_with(solve, x0=CenteredGrid(0, pressure_extrapolation, div.bounds, div.resolution)) if batch(math.merge_shapes(*obstacles)).without(batch(solve.x0)): # The initial pressure guess must contain all batch dimensions solve = copy_with(solve, x0=expand(solve.x0, batch(math.merge_shapes(*obstacles)))) - pressure = math.solve_linear(masked_laplace, f_args=[hard_bcs, active], f_kwargs={"scheme": scheme}, y=div, solve=solve) + pressure = math.solve_linear(masked_laplace, f_args=[hard_bcs, active], f_kwargs={"scheme": scheme}, y=div, solve=solve) # --- Subtract grad p --- grad_pressure = field.spatial_gradient(pressure, input_velocity.extrapolation, type=type(velocity), scheme=scheme) * hard_bcs velocity = (velocity - grad_pressure).with_extrapolation(input_velocity.extrapolation) @@ -220,3 +220,43 @@ def _accessible_extrapolation(vext: Extrapolation): return _accessible_extrapolation(vext.normal) else: raise ValueError(f"Unsupported extrapolation: {type(vext)}") + + +def incompressible_rk4(pde: Callable, velocity, pressure, dt, pressure_order=4, pressure_solve=Solve('CG', 1e-12, 1e-12)): + """ + + Args: + pde: + velocity: + pressure: + dt: + pressure_order: + pressure_solve: + + Returns: + velocity: + pressure: + """ + v_1, p_1 = velocity, pressure + # PDE at current point + rhs_1 = pde(v_1, p_1) - field.spatial_gradient(p_1, type=StaggeredGrid, scheme=Scheme(pressure_order)) + v_2_old = velocity + (dt / 2) * rhs_1 + v_2, delta_p = make_incompressible(v_2_old, solve=pressure_solve, scheme=Scheme(pressure_order)) + p_2 = p_1 + delta_p / dt + # PDE at half-point + rhs_2 = pde(v_2, p_2) - field.spatial_gradient(p_2, type=StaggeredGrid, scheme=Scheme(pressure_order)) + v_3_old = velocity + (dt / 2) * rhs_2 + v_3, delta_p = make_incompressible(v_3_old, solve=pressure_solve, scheme=Scheme(pressure_order)) + p_3 = p_2 + delta_p / dt + # PDE at corrected half-point + rhs_3 = pde(v_3, p_3) - field.spatial_gradient(p_3, type=StaggeredGrid, scheme=Scheme(pressure_order)) + v_4_old = velocity + dt * rhs_2 + v_4, delta_p = make_incompressible(v_4_old, solve=pressure_solve, scheme=Scheme(pressure_order)) + p_4 = p_3 + delta_p / dt + # PDE at RK4 point + rhs_4 = pde(v_4, p_4) - field.spatial_gradient(p_4, type=StaggeredGrid, scheme=Scheme(pressure_order)) + v_p1_old = velocity + (dt / 6) * (rhs_1 + 2 * rhs_2 + 2 * rhs_3 + rhs_4) + p_p1_old = (1 / 6) * (p_1 + 2 * p_2 + 2 * p_3 + p_4) + v_p1, delta_p = make_incompressible(v_p1_old, solve=pressure_solve, scheme=Scheme(pressure_order)) + p_p1 = p_p1_old + delta_p / dt + return v_p1, p_p1 From c100bf7ad7f5fb5cdf25934551563c2a6665a81f Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 17 Jan 2023 21:26:51 +0100 Subject: [PATCH 065/170] [math] Add measure to iterate() --- phi/math/_functional.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/phi/math/_functional.py b/phi/math/_functional.py index 2c3c44946..0a9d26fe1 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -1850,7 +1850,13 @@ def map_i2b(f: Callable) -> Callable: return map_types(f, instance, batch) -def iterate(f: Callable, iterations: int or Shape, *x0, f_kwargs: dict = None, range=range, **f_kwargs_): +def iterate(f: Callable, + iterations: int or Shape, + *x0, + f_kwargs: dict = None, + range: Callable = range, + measure: Callable = None, + **f_kwargs_): """ Repeatedly call `function`, passing the previous output as the next input. @@ -1872,20 +1878,24 @@ def iterate(f: Callable, iterations: int or Shape, *x0, f_kwargs: dict = None, r f_kwargs = {} f_kwargs.update(f_kwargs_) x = x0 + start_time = measure() if measure else None if isinstance(iterations, int): - for i in range(iterations): + for _ in range(iterations): x = f(*x, **f_kwargs) if not isinstance(x, tuple): x = (x,) assert len(x) == len(x0), f"Function to iterate must return {len(x0)} outputs to match input but got {x}" - return x[0] if len(x0) == 1 else x + result = x[0] if len(x0) == 1 else x elif isinstance(iterations, Shape): xs = [x0] - for i in range(iterations.size): + for _ in range(iterations.size): x = f(*x, **f_kwargs) if not isinstance(x, tuple): x = (x,) assert len(x) == len(x0), f"Function to iterate must return {len(x0)} outputs to match input but got {x}" xs.append(x) xs = [stack(item, iterations.with_size(None)) for item in zip(*xs)] - return xs[0] if len(x0) == 1 else xs + result = xs[0] if len(x0) == 1 else xs + else: + raise ValueError(f"iterations must be an int or Shape but got {type(iterations)}") + return (result, measure() - start_time) if measure else result From 2b16fb0572409f45ba484dc6028214e2947de3d6 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 17 Jan 2023 22:06:22 +0100 Subject: [PATCH 066/170] [demos] Replace @ with .at() --- demos/flip_liquid.py | 4 ++-- demos/fluid_logo.py | 4 ++-- demos/fog.py | 2 +- demos/moving_obstacle.py | 2 +- demos/point_cloud.py | 4 ++-- demos/smoke_embedded_mesh.py | 6 +++--- demos/smoke_plume.py | 2 +- demos/smoke_plume_3d.py | 2 +- demos/smoke_plume_advanced.py | 8 ++++---- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/demos/flip_liquid.py b/demos/flip_liquid.py index b8932e5b8..50a8405f8 100644 --- a/demos/flip_liquid.py +++ b/demos/flip_liquid.py @@ -25,8 +25,8 @@ def step(particles): occupied = CenteredGrid(particles.mask(), velocity.extrapolation.spatial_gradient(), velocity.bounds, velocity.resolution) velocity, pressure = fluid.make_incompressible(velocity + GRAVITY * DT, [OBSTACLE], active=occupied) # --- Particle Operations --- - particles += (velocity - prev_velocity) @ particles # FLIP update - # particles = velocity @ particles # PIC update + particles += (velocity - prev_velocity).at(particles) # FLIP update + # particles = velocity.at(particles) # PIC update particles = advect.points(particles, velocity * ~OBSTACLE, DT, advect.finite_rk4) particles = fluid.boundary_push(particles, [OBSTACLE, ~particles.bounds]) return particles, velocity, pressure diff --git a/demos/fluid_logo.py b/demos/fluid_logo.py index 981f52ef1..c299ae48a 100644 --- a/demos/fluid_logo.py +++ b/demos/fluid_logo.py @@ -10,7 +10,7 @@ OBSTACLE_GEOMETRIES = [Box(x=(15 + x * 7, 15 + (x + 1) * 7), y=(41, 83)) for x in range(1, 10, 2)] + [Box['x,y', 43:50, 41:48], Box['x,y', 15:43, 83:90], Box['x,y', 50:85, 83:90]] OBSTACLE = Obstacle(union(OBSTACLE_GEOMETRIES)) -OBSTACLE_MASK = HardGeometryMask(OBSTACLE.geometry) @ CenteredGrid(0, extrapolation.BOUNDARY, **DOMAIN) +OBSTACLE_MASK = HardGeometryMask(OBSTACLE.geometry).at(CenteredGrid(0, extrapolation.BOUNDARY, **DOMAIN)) INFLOW = CenteredGrid(Box['x,y', 14:21, 6:10], extrapolation.BOUNDARY, **DOMAIN) + \ CenteredGrid(Box['x,y', 81:88, 6:10], extrapolation.BOUNDARY, **DOMAIN) * 0.9 + \ @@ -20,7 +20,7 @@ for _ in view('smoke, velocity, pressure, OBSTACLE_MASK', play=False, namespace=globals()).range(warmup=1): smoke = advect.semi_lagrangian(smoke, velocity, 1) + INFLOW - buoyancy_force = smoke * (0, 0.1) @ velocity # resamples density to velocity sample points + buoyancy_force = (smoke * (0, 0.1)).at(velocity) velocity = advect.semi_lagrangian(velocity, velocity, 1) + buoyancy_force velocity, pressure = fluid.make_incompressible(velocity, (OBSTACLE,), Solve('CG-adaptive', 1e-5, 0, x0=pressure)) remaining_divergence = field.divergence(velocity) diff --git a/demos/fog.py b/demos/fog.py index 35c04e0db..ae55a59b5 100644 --- a/demos/fog.py +++ b/demos/fog.py @@ -22,7 +22,7 @@ # Physics temperature = diffuse.explicit(advect.mac_cormack(temperature, velocity, dt=1), 0.1, dt=1, substeps=2) humidity = advect.mac_cormack(humidity, velocity, dt=1) - buoyancy_force = temperature * (0, 0.1) @ velocity # resamples smoke to velocity sample points + buoyancy_force = (temperature * (0, 0.1)).at(velocity) velocity = advect.semi_lagrangian(velocity, velocity, 1) + buoyancy_force velocity, pressure = fluid.make_incompressible(velocity, (), Solve('auto', 1e-5, 0, x0=pressure)) # Compute fog diff --git a/demos/moving_obstacle.py b/demos/moving_obstacle.py index a3f7a5baa..a6956666c 100644 --- a/demos/moving_obstacle.py +++ b/demos/moving_obstacle.py @@ -23,4 +23,4 @@ def move_obstacle(obs: Obstacle): velocity = advect.mac_cormack(velocity, velocity, DT) velocity, pressure = fluid.make_incompressible(velocity, (obstacle,)) fluid.masked_laplace.tracers.clear() # we will need to retrace because the matrix changes each step. This is not needed when JIT-compiling the physics. - obstacle_mask = HardGeometryMask(obstacle.geometry) @ pressure + obstacle_mask = HardGeometryMask(obstacle.geometry).at(pressure) diff --git a/demos/point_cloud.py b/demos/point_cloud.py index d038f0a61..bdbf6061f 100644 --- a/demos/point_cloud.py +++ b/demos/point_cloud.py @@ -16,7 +16,7 @@ # Grid sampling scattered_data = field.sample(points, velocity.elements) -scattered_grid = points @ velocity -scattered_sgrid = points @ StaggeredGrid(0, 0, velocity.bounds, velocity.resolution) +scattered_grid = points.at(velocity) +scattered_sgrid = points.at(StaggeredGrid(0, 0, velocity.bounds, velocity.resolution)) view(namespace=globals()) diff --git a/demos/smoke_embedded_mesh.py b/demos/smoke_embedded_mesh.py index b54638627..3958c8d66 100644 --- a/demos/smoke_embedded_mesh.py +++ b/demos/smoke_embedded_mesh.py @@ -1,7 +1,7 @@ from phi.flow import * velocity = StaggeredGrid((0, 0), 0, x=32, y=32, bounds=Box(x=100, y=100)) # or CenteredGrid(...) -velocity_emb = velocity @ StaggeredGrid(0, velocity, x=64, y=64, bounds=Box(x=(30, 70), y=(40, 80))) +velocity_emb = velocity.at(StaggeredGrid(0, velocity, x=64, y=64, bounds=Box(x=(30, 70), y=(40, 80)))) smoke = CenteredGrid(0, extrapolation.BOUNDARY, x=200, y=200, bounds=Box(x=100, y=100)) OBSTACLE = Obstacle(Sphere(x=50, y=60, radius=5)) @@ -13,8 +13,8 @@ def step(v, v_emb, s, p, dt=1.): s = advect.mac_cormack(s, v_emb, dt) + INFLOW buoyancy = s * (0, 0.1) - v_emb = advect.semi_lagrangian(v_emb, v_emb, dt) + (buoyancy @ v_emb) * dt - v = advect.semi_lagrangian(v, v, dt) + (buoyancy @ v) * dt + v_emb = advect.semi_lagrangian(v_emb, v_emb, dt) + buoyancy.at(v_emb) * dt + v = advect.semi_lagrangian(v, v, dt) + buoyancy.at(v) * dt v, p = fluid.make_incompressible(v, [OBSTACLE], Solve('auto', 1e-5, 0, x0=p)) # Perform the embedded pressure solve p_emb_x0 = CenteredGrid(0, p, v_emb.bounds, v_emb.resolution) diff --git a/demos/smoke_plume.py b/demos/smoke_plume.py index 5a278f019..17dc3f326 100644 --- a/demos/smoke_plume.py +++ b/demos/smoke_plume.py @@ -17,7 +17,7 @@ # @jit_compile # Only for PyTorch, TensorFlow and Jax def step(v, s, p, dt=1.): s = advect.mac_cormack(s, v, dt) + INFLOW - buoyancy = s * (0, 0.1) @ v # resamples smoke to velocity sample points + buoyancy = (s * (0, 0.1)).at(v) v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt v, p = fluid.make_incompressible(v, (), Solve('auto', 1e-5, 0, x0=p)) return v, s, p diff --git a/demos/smoke_plume_3d.py b/demos/smoke_plume_3d.py index 408497e6e..7f0a1385e 100644 --- a/demos/smoke_plume_3d.py +++ b/demos/smoke_plume_3d.py @@ -17,7 +17,7 @@ # @jit_compile # Only for PyTorch, TensorFlow and Jax def step(v, s, p, dt=1.): s = advect.mac_cormack(s, v, dt) + INFLOW - buoyancy = s * (0, 0, 0.1) @ v # resamples smoke to velocity sample points + buoyancy = (s * (0, 0, 0.1)).at(v) v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt v, p = fluid.make_incompressible(v, (), Solve('auto', 1e-5, 0, x0=p)) return v, s, p diff --git a/demos/smoke_plume_advanced.py b/demos/smoke_plume_advanced.py index 4475022a6..5645fa471 100644 --- a/demos/smoke_plume_advanced.py +++ b/demos/smoke_plume_advanced.py @@ -22,12 +22,12 @@ viewer = view(smoke, velocity, namespace=globals(), play=False) for _ in viewer.range(warmup=1): # Resize grids if needed - inflow = SoftGeometryMask(INFLOW) @ CenteredGrid(0, smoke.extrapolation, x=smoke_res ** 2, y=smoke_res ** 2, bounds=BOUNDS) - smoke = smoke @ inflow - velocity = velocity @ StaggeredGrid(0, velocity.extrapolation, x=v_res ** 2, y=v_res ** 2, bounds=BOUNDS) + inflow = SoftGeometryMask(INFLOW).at(CenteredGrid(0, smoke.extrapolation, x=smoke_res ** 2, y=smoke_res ** 2, bounds=BOUNDS)) + smoke = smoke.at(inflow) + velocity = velocity.at(StaggeredGrid(0, velocity.extrapolation, x=v_res ** 2, y=v_res ** 2, bounds=BOUNDS)) # Physics step smoke = advect.mac_cormack(smoke, velocity, 1) + inflow - buoyancy_force = smoke * (0, 0.1) @ velocity # resamples smoke to velocity sample points + buoyancy_force = (smoke * (0, 0.1)).at(velocity) velocity = advect.semi_lagrangian(velocity, velocity, 1) + buoyancy_force try: with math.SolveTape() as solves: From 26d6a19df7a2e96fb69b9b8a0a8ee203c683e581 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 18 Jan 2023 13:10:08 +0100 Subject: [PATCH 067/170] [math] iterate() with measure returns Tensor --- phi/math/_functional.py | 14 +++++++++++--- tests/commit/math/test__functional.py | 10 +++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/phi/math/_functional.py b/phi/math/_functional.py index 0a9d26fe1..10fe9cb3c 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -1867,35 +1867,43 @@ def iterate(f: Callable, If `Shape`, returns the trajectory (`x0` and all outputs of `f`), stacking the values along this dimension. x0: Initial positional arguments for `f`. range: Range function. Can be used to generate tqdm output by passing `trange`. + measure: Function without arguments to call at the start and end (and in between if `isinstance(iterations, Shape)`) calls to `f`. + The measure of each call to `f` is `measure()` after minus `measure()` before the call. f_kwargs: Additional keyword arguments to be passed to `f`. These arguments can be of any type. f_kwargs_: More keyword arguments. Returns: - Trajectory of final output of `f`, depending on `iterations`. + trajectory: Trajectory of final output of `f`, depending on `iterations`. + measured: Only if `measure` was specified, returns the measured value or trajectory tensor. """ if f_kwargs is None: f_kwargs = {} f_kwargs.update(f_kwargs_) x = x0 - start_time = measure() if measure else None if isinstance(iterations, int): + start_time = measure() if measure else None for _ in range(iterations): x = f(*x, **f_kwargs) if not isinstance(x, tuple): x = (x,) assert len(x) == len(x0), f"Function to iterate must return {len(x0)} outputs to match input but got {x}" result = x[0] if len(x0) == 1 else x + return (result, measure() - start_time) if measure else result elif isinstance(iterations, Shape): xs = [x0] + ts = [measure()] if measure else None for _ in range(iterations.size): x = f(*x, **f_kwargs) if not isinstance(x, tuple): x = (x,) assert len(x) == len(x0), f"Function to iterate must return {len(x0)} outputs to match input but got {x}" xs.append(x) + if measure: + ts.append(measure()) xs = [stack(item, iterations.with_size(None)) for item in zip(*xs)] result = xs[0] if len(x0) == 1 else xs + ts = np.asarray(ts) + return (result, wrap(ts[1:] - ts[:-1], iterations.with_size(None))) if measure else result else: raise ValueError(f"iterations must be an int or Shape but got {type(iterations)}") - return (result, measure() - start_time) if measure else result diff --git a/tests/commit/math/test__functional.py b/tests/commit/math/test__functional.py index 4bc83de91..5ad3fd10d 100644 --- a/tests/commit/math/test__functional.py +++ b/tests/commit/math/test__functional.py @@ -1,5 +1,6 @@ from functools import partial from unittest import TestCase +import time import phi from phi import math @@ -352,7 +353,14 @@ def f(x, fac): return x * fac self.assertEqual(4, math.iterate(f, 2, 1, f_kwargs=dict(fac=2.))) - math.assert_close([1, 2, 4], math.iterate(f, batch(trajectory=2), 1, f_kwargs=dict(fac=2.))) + math.assert_close([1, 2, 4], math.iterate(f, batch(trajectory=2), 1, fac=2.)) + # With measure + r, t = math.iterate(f, 2, 1, fac=2., measure=time.perf_counter) + self.assertEqual(4, r) + self.assertIsInstance(t, float) + r, t = math.iterate(f, batch(trajectory=2), 1, fac=2., measure=time.perf_counter) + math.assert_close([1, 2, 4], r) + self.assertEqual(batch(trajectory=2), t.shape) def test_delayed_decorator(self): def f(x, y): From b7d5d625a92756360b01b59717534eab35b9e76b Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 18 Jan 2023 13:23:47 +0100 Subject: [PATCH 068/170] [vis] Don't annotate points if dim matches axis --- phi/vis/_matplotlib/_matplotlib_plots.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index 18d688e4f..c7fe28662 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -70,7 +70,7 @@ def create_figure(self, if bounds.vector.item_names[1] in log_dims: axis.set_yscale('log') any_log = True - if not any_log and max(x_size/y_size, y_size/x_size) < 5: + if not any_log and x_size > 0 and y_size > 0 and max(x_size/y_size, y_size/x_size) < 5: axis.set_aspect('equal', adjustable='box') elif bounds.spatial_rank == 3: axis.remove() @@ -334,6 +334,8 @@ def _plot_points(axis, data: PointCloud, dims, vector, **plt_args): def _annotate_points(axis, points: math.Tensor, labelled_dim: math.Shape): + if labelled_dim.name in points.shape.get_item_names('vector'): + return # The point labels match one of the figure axes, so they are redundant if points.shape['vector'].size == 2: x, y = math.reshaped_native(points, ['vector', points.shape.without('vector')], to_numpy=True, force_expand=True) if labelled_dim.item_names[0]: From 3c300e51b5978f8082ca91908d51d4fb7cb12a07 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 18 Jan 2023 14:23:14 +0100 Subject: [PATCH 069/170] [field,physics] Refactor higher-order * Replace Scheme class by order,implicit parameters * Add fluid.incompressible_rk4 --- demos/flip_liquid.py | 2 +- phi/field/_angular_velocity.py | 13 ++-- phi/field/_field.py | 64 ++++++++++++------ phi/field/_field_math.py | 104 ++++++++++++++---------------- phi/field/_grid.py | 33 +++++----- phi/field/_mask.py | 5 +- phi/field/_noise.py | 5 +- phi/field/_point_cloud.py | 11 ++-- phi/field/numerical.py | 45 ------------- phi/flow.py | 1 - phi/math/_nd.py | 16 ++--- phi/math/_tensors.py | 2 +- phi/physics/advect.py | 41 ++++++------ phi/physics/diffuse.py | 13 ++-- phi/physics/fluid.py | 70 +++++++++++--------- tests/commit/physics/test_flip.py | 2 +- tests/release/test_flip.py | 2 +- 17 files changed, 205 insertions(+), 224 deletions(-) delete mode 100644 phi/field/numerical.py diff --git a/demos/flip_liquid.py b/demos/flip_liquid.py index 50a8405f8..348cd89cb 100644 --- a/demos/flip_liquid.py +++ b/demos/flip_liquid.py @@ -21,7 +21,7 @@ # @jit_compile def step(particles): # --- Grid Operations --- - velocity = prev_velocity = field.finite_fill(StaggeredGrid(particles, 0, x=64, y=64, scheme=Scheme(outside_points='clamp'))) + velocity = prev_velocity = field.finite_fill(particles.at(StaggeredGrid(0, 0, x=64, y=64), outside_handling='clamp')) occupied = CenteredGrid(particles.mask(), velocity.extrapolation.spatial_gradient(), velocity.bounds, velocity.resolution) velocity, pressure = fluid.make_incompressible(velocity + GRAVITY * DT, [OBSTACLE], active=occupied) # --- Particle Operations --- diff --git a/phi/field/_angular_velocity.py b/phi/field/_angular_velocity.py index cc0c90ae9..8f6d2d4c3 100644 --- a/phi/field/_angular_velocity.py +++ b/phi/field/_angular_velocity.py @@ -4,9 +4,8 @@ from phi import math from ._field import Field -from .numerical import Scheme from ..geom import Geometry -from ..math import Shape, spatial, instance +from ..math import Shape, spatial, instance, Tensor, wrap class AngularVelocity(Field): @@ -19,12 +18,12 @@ class AngularVelocity(Field): """ def __init__(self, - location: math.Tensor or tuple or list or Number, - strength: math.Tensor or Number = 1.0, + location: Tensor or tuple or list or Number, + strength: Tensor or Number = 1.0, falloff: Callable = None, component: str = None): - location = math.wrap(location) - strength = math.wrap(strength) + location = wrap(location) + strength = wrap(strength) assert location.shape.channel.names == ('vector',), "location must have a single channel dimension called 'vector'" assert location.shape.spatial.is_empty, "location tensor cannot have any spatial dimensions" assert not instance(location), "AngularVelocity does not support instance dimensions" @@ -36,7 +35,7 @@ def __init__(self, assert spatial_names is not None, "location.vector must list spatial dimensions as item names" self._shape = location.shape & spatial(**{dim: 1 for dim in spatial_names}) - def _sample(self, geometry: Geometry, scheme: Scheme) -> math.Tensor: + def _sample(self, geometry: Geometry, **kwargs) -> Tensor: points = geometry.center distances = points - self.location strength = self.strength if self.falloff is None else self.strength * self.falloff(distances) diff --git a/phi/field/_field.py b/phi/field/_field.py index 3f5051ef0..ad002a184 100644 --- a/phi/field/_field.py +++ b/phi/field/_field.py @@ -6,7 +6,6 @@ from phi.math.extrapolation import Extrapolation from phi.geom import Geometry, Box, Point from phi.math.magic import BoundDim -from .numerical import Scheme class Field: @@ -56,11 +55,11 @@ def bounds(self) -> Box: """ raise NotImplementedError - def _sample(self, geometry: Geometry, scheme: Scheme) -> math.Tensor: + def _sample(self, geometry: Geometry, **kwargs) -> math.Tensor: """ For internal use only. Use `sample()` instead. """ raise NotImplementedError(self) - def at(self, representation: 'SampledField', keep_extrapolation=False, scheme: Scheme = Scheme()) -> 'SampledField': + def at(self, representation: 'SampledField', keep_extrapolation=False, **kwargs) -> 'SampledFieldType': """ Samples this field at the sample points of `representation`. The result will approximate the values of this field on the data structure of `representation`. @@ -78,12 +77,14 @@ def at(self, representation: 'SampledField', keep_extrapolation=False, scheme: S keep_extrapolation: Only available if `self` is a `SampledField`. If True, the resampled field will inherit the extrapolation from `self` instead of `representation`. This can result in non-compatible value tensors for staggered grids where the tensor size depends on the extrapolation type. - scheme: Numerical scheme for resampling. See `reduce_sample` + **kwargs: Sampling arguments, e.g. to specify the numerical scheme. + By default, linear interpolation is used. + Grids also support 6th order implicit sampling at mid-points. Returns: Field object of same type as `representation` """ - resampled = reduce_sample(self, representation.elements, scheme=scheme) + resampled = reduce_sample(self, representation.elements, **kwargs) extrap = self.extrapolation if isinstance(self, SampledField) and keep_extrapolation else representation.extrapolation return representation._op1(lambda old: extrap if isinstance(old, math.extrapolation.Extrapolation) else resampled) @@ -183,7 +184,7 @@ def __init__(self, elements: Geometry or Tensor, values: Tensor, extrapolation: def bounds(self) -> Box: raise NotImplementedError(self.__class__) - def _sample(self, geometry: Geometry, scheme: Scheme) -> math.Tensor: + def _sample(self, geometry: Geometry, **kwargs) -> math.Tensor: raise NotImplementedError(self.__class__) def with_values(self, values): @@ -216,7 +217,7 @@ def __concat__(self, values: tuple, dim: str, **kwargs) -> 'FieldType': @property def elements(self) -> Geometry: """ - Returns a geometrical representation of the discretized volume elements. + Returns a geometrical representation of the discrete volume elements. The result is a tuple of Geometry objects, each of which can have additional spatial (but not batch) dimensions. For grids, the geometries are boxes while particle fields may be represented as spheres. @@ -314,7 +315,9 @@ def _op2(self, other, operator) -> 'SampledField': return self.with_values(values) -def sample(field: Field, geometry: Geometry, scheme: Scheme = Scheme()) -> math.Tensor: +def sample(field: Field, + geometry: Geometry or SampledField or Tensor, + **kwargs) -> math.Tensor: """ Computes the field value inside the volume of the (batched) `geometry`. @@ -329,24 +332,32 @@ def sample(field: Field, geometry: Geometry, scheme: Scheme = Scheme()) -> math. Args: field: Source `Field` to sample. - geometry: Single or batched `phi.geom.Geometry`. - scheme: Numerical scheme. + geometry: Single or batched `phi.geom.Geometry` or `SampledField` or location `Tensor`. + When passing a `SampledField`, its `elements` are used as sample points. + When passing a vector-valued `Tensor`, a `Point` geometry will be created. + **kwargs: Sampling arguments, e.g. to specify the numerical scheme. + By default, linear interpolation is used. + Grids also support 6th order implicit sampling at mid-points. Returns: Sampled values as a `phi.math.Tensor` """ + geometry = _get_geometry(geometry) geom_ch = channel(geometry).without('vector') assert all(dim not in field.shape for dim in geom_ch) if isinstance(field, SampledField) and field.elements.shallow_equals(geometry) and not geom_ch: return field.values if geom_ch: - sampled = [field._sample(p, scheme=scheme) for p in geometry.unstack(geom_ch.name)] + sampled = [field._sample(p, **kwargs) for p in geometry.unstack(geom_ch.name)] return math.stack(sampled, geom_ch) else: - return field._sample(geometry, scheme=scheme) + return field._sample(geometry, **kwargs) -def reduce_sample(field: Field, geometry: Geometry, dim=channel('vector'), scheme: Scheme = Scheme()) -> math.Tensor: +def reduce_sample(field: Field, + geometry: Geometry or SampledField or Tensor, + dim=channel('vector'), + **kwargs) -> math.Tensor: """ Similar to `sample()`, but matches channel dimensions of `geometry` with channel dimensions of this field. Currently, `geometry` may have at most one channel dimension. @@ -356,14 +367,18 @@ def reduce_sample(field: Field, geometry: Geometry, dim=channel('vector'), schem Args: field: Source `Field` to sample. - geometry: Single or batched `phi.geom.Geometry`. + geometry: Single or batched `phi.geom.Geometry` or `SampledField` or location `Tensor`. + When passing a `SampledField`, its `elements` are used as sample points. + When passing a vector-valued `Tensor`, a `Point` geometry will be created. dim: Dimension of result, resulting from reduction of channel dimensions. - scheme: Numerical scheme. By default linear interpolation is used - supported: implicit 6th oder (only for sampling at mid-points, sampling at other locations and unsupported schemes result in automatic fallback to linear interpolation) + **kwargs: Sampling arguments, e.g. to specify the numerical scheme. + By default, linear interpolation is used. + Grids also support 6th order implicit sampling at mid-points. Returns: Sampled values as a `phi.math.Tensor` """ + geometry = _get_geometry(geometry) if isinstance(field, SampledField) and field.elements.shallow_equals(geometry): return field.values if channel(geometry).without('vector'): # Reduce this dimension @@ -372,13 +387,24 @@ def reduce_sample(field: Field, geometry: Geometry, dim=channel('vector'), schem if field.shape.channel.volume > 1: assert field.shape.channel.volume == geom_ch.volume, f"Cannot sample field with channels {field.shape.channel} at elements with channels {geometry.shape.channel}." components = math.unstack(field, field.shape.channel.name) - sampled = [c._sample(p, scheme=scheme) for c, p in zip(components, geometry.unstack(geom_ch.name))] + sampled = [c._sample(p, **kwargs) for c, p in zip(components, geometry.unstack(geom_ch.name))] else: - sampled = [field._sample(p, scheme=scheme) for p in geometry.unstack(channel(geometry).without('vector').name)] + sampled = [field._sample(p, **kwargs) for p in geometry.unstack(channel(geometry).without('vector').name)] dim = dim.with_size(geometry.shape.channel.item_names[0]) return math.stack(sampled, dim) else: # Nothing to reduce - return field._sample(geometry, scheme=scheme) + return field._sample(geometry, **kwargs) + + +def _get_geometry(geometry): + if isinstance(geometry, SampledField): + return geometry.elements + elif isinstance(geometry, Tensor) and 'vector' in geometry.shape: + return Point(geometry) + elif isinstance(geometry, Geometry): + return geometry + else: + raise ValueError(f"A Geometry, SampledField or location Tensor is required but got {geometry}") FieldType = TypeVar('FieldType', bound=Field) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 8cc2ced07..df274f233 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -4,13 +4,11 @@ from phi import geom from phi import math -from phi.geom import Box, Geometry, Sphere, Cuboid -from phi.math import Tensor, spatial, instance, tensor, masked_fill, channel, Shape, batch, unstack, wrap, vec, \ - rename_dims, solve_linear, jit_compile_linear, shape +from phi.geom import Box, Geometry, Cuboid +from phi.math import Tensor, spatial, instance, tensor, channel, Shape, unstack, wrap, solve_linear, jit_compile_linear, shape, Solve from ._field import Field, SampledField, SampledFieldType, as_extrapolation from ._grid import CenteredGrid, Grid, StaggeredGrid, GridType from ._point_cloud import PointCloud -from .numerical import Scheme from ..math.extrapolation import Extrapolation, SYMMETRIC, REFLECT, ANTIREFLECT, ANTISYMMETRIC, combine_by_direction, map @@ -40,7 +38,7 @@ def bake_extrapolation(grid: GridType) -> GridType: raise ValueError(f"Not a valid grid: {grid}") -def laplace(field: GridType, axes=spatial, scheme: Scheme = Scheme(2), weights: Tensor or Field = None) -> GridType: +def laplace(field: GridType, axes=spatial, order=2, implicit: math.Solve = None, weights: Tensor or Field = None) -> GridType: """ Spatial Laplace operator for scalar grid. If a vector grid is passed, it is assumed to be centered and the laplace is computed component-wise. @@ -48,10 +46,13 @@ def laplace(field: GridType, axes=spatial, scheme: Scheme = Scheme(2), weights: Args: field: n-dimensional `CenteredGrid` axes: The second derivative along these dimensions is summed over - scheme: finite difference `Scheme` used for differentiation - supported: explicit 2/4th order - implicit 6th order weights: (Optional) Multiply the axis terms by these factors before summation. Must be a `phi.math.Tensor` or `phi.field.Field` with a single channel dimension that lists all laplace axes by name. + order: Spatial order of accuracy. + Higher orders entail larger stencils and more computation time but result in more accurate results assuming a large enough resolution. + Supported: 2 explicit, 4 explicit, 6 implicit. + implicit: When a `Solve` object is passed, performs an implicit operation with the specified solver and tolerances. + Otherwise, an explicit stencil is used. Returns: laplacian field as `CenteredGrid` @@ -60,51 +61,39 @@ def laplace(field: GridType, axes=spatial, scheme: Scheme = Scheme(2), weights: weights = weights.at(field).values axes_names = field.shape.only(axes).names extrapol_map = {} - if not scheme.is_implicit: - if scheme.order == 2: + if not implicit: + if order == 2: values, needed_shifts = [1, -2, 1], (-1, 0, 1) - elif scheme.order == 4: + elif order == 4: values, needed_shifts = [-1/12, 4/3, -5/2, 4/3, -1/12], (-2, -1, 0, 1, 2) - else: extrapol_map_rhs = {} - if scheme.order == 6: + if order == 6: values, needed_shifts = [3/44, 12/11, -51/22, 12/11, 3/44], (-2, -1, 0, 1, 2) extrapol_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) - values_rhs, needed_shifts_rhs = [2/11, 1, 2/11], (-1, 0, 1) extrapol_map_rhs['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) - base_widths = (abs(min(needed_shifts)), max(needed_shifts)) field.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) - padded_components = [pad(field, {dim: base_widths}) for dim in axes_names] - shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, axes_names)] - result_components = [sum([value * shift for value, shift in zip(values, shifted_component)]) / field.dx.vector[dim]**2 for shifted_component, dim in zip(shifted_components, axes_names)] - - - if scheme.is_implicit: + result_components = [sum([value * shift_ for value, shift_ in zip(values, shifted_component)]) / field.dx.vector[dim]**2 for shifted_component, dim in zip(shifted_components, axes_names)] + if implicit: result_components = stack(result_components, channel('laplacian')) result_components.with_values(result_components.values._cache()) result_components = result_components.with_extrapolation(map(_ex_map_f(extrapol_map_rhs), field.extrapolation)) - scheme.solve.x0 = result_components - result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=scheme.solve, - f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, - "stack_dim": channel('laplacian')}) + implicit.x0 = result_components + result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=implicit, f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, "stack_dim": channel('laplacian')}) result_components = unstack(result_components, 'laplacian') extrapol_map = extrapol_map_rhs - result_components = [component.with_bounds(field.bounds) for component in result_components] if weights is not None: assert channel(weights).rank == 1 and channel(weights).item_names is not None, f"weights must have one channel dimension listing the laplace dims but got {shape(weights)}" assert set(channel(weights).item_names[0]) >= set(axes_names), f"the channel dim of weights must contain all laplace dims {axes_names} but only has {channel(weights).item_names}" result_components = [c * weights[ax] for c, ax in zip(result_components, axes_names)] - result = sum(result_components) result = result.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) - return result @@ -112,7 +101,8 @@ def spatial_gradient(field: CenteredGrid, gradient_extrapolation: Extrapolation = None, type: type = CenteredGrid, stack_dim: Shape = channel('vector'), - scheme: Scheme = Scheme(2)): + order=2, + implicit: Solve = None): """ Finite difference spatial_gradient. @@ -128,8 +118,11 @@ def spatial_gradient(field: CenteredGrid, type: either `CenteredGrid` or `StaggeredGrid` stack_dim: Dimension to be added. This dimension lists the spatial_gradient w.r.t. the spatial dimensions. The `field` must not have a dimension of the same name. - scheme: finite difference `Scheme` used for differentiation - supported: explicit 2/4th order - implicit 6th order + order: Spatial order of accuracy. + Higher orders entail larger stencils and more computation time but result in more accurate results assuming a large enough resolution. + Supported: 2 explicit, 4 explicit, 6 implicit. + implicit: When a `Solve` object is passed, performs an implicit operation with the specified solver and tolerances. + Otherwise, an explicit stencil is used. Returns: spatial_gradient field of type `type`. @@ -140,21 +133,21 @@ def spatial_gradient(field: CenteredGrid, gradient_extrapolation = field.extrapolation.spatial_gradient() extrapol_map = {} - if not scheme.is_implicit: - if scheme.order == 2: + if not implicit: + if order == 2: if type == CenteredGrid: values, needed_shifts = [-1/2, 1/2], (-1, 1) else: values, needed_shifts = [-1, 1], (0, 1) - elif scheme.order == 4: + elif order == 4: if type == CenteredGrid: values, needed_shifts = [1/12, -2/3, 2/3, -1/12], (-2, -1, 1, 2) else: values, needed_shifts = [1/24, -27/24, 27/24, -1/24], (-1, 0, 1, 2) else: extrapol_map_rhs = {} - if scheme.order == 6: + if order == 6: if type == CenteredGrid: values, needed_shifts = [-1/36, -14/18, 14/18, 1/36], (-2, -1, 1, 2) values_rhs, needed_shifts_rhs = [1/3, 1, 1/3], (-1, 0, 1) @@ -170,7 +163,7 @@ def spatial_gradient(field: CenteredGrid, base_widths = (abs(min(needed_shifts)), max(needed_shifts)) field.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) - if scheme.is_implicit: + if implicit: gradient_extrapolation = map(_ex_map_f(extrapol_map_rhs), gradient_extrapolation) spatial_dims = field.shape.spatial.names @@ -199,10 +192,10 @@ def spatial_gradient(field: CenteredGrid, result = result.with_extrapolation(gradient_extrapolation) - if scheme.is_implicit: - scheme.solve.x0 = result + if implicit: + implicit.x0 = result result = result - result = solve_linear(_lhs_for_implicit_scheme, result, solve=scheme.solve, + result = solve_linear(_lhs_for_implicit_scheme, result, solve=implicit, f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, "stack_dim": stack_dim, "staggered_output": type != CenteredGrid}) if type == CenteredGrid and gradient_extrapolation == math.extrapolation.NONE: @@ -223,7 +216,7 @@ def _lhs_for_implicit_scheme(x, values_rhs, needed_shifts_rhs, stack_dim, stagge result = [] for dim, component in zip(x.shape.only(math.spatial).names, unstack(x, stack_dim.name)): shifted = shift(component, needed_shifts_rhs, stack_dim=None, dims=dim) - result.append(sum([value * shift for value, shift in zip(values_rhs, shifted)])) + result.append(sum([value * shift_ for value, shift_ in zip(values_rhs, shifted)])) if staggered_output: result = x.with_values(math.stack([component.values for component in result], channel('vector'))) @@ -243,8 +236,7 @@ def pad_for_staggered_output(field: CenteredGrid, output_extrapolation: Extrapol return padded_components -def shift(grid: CenteredGrid, offsets: tuple, stack_dim: Shape = channel('shift'), dims=math.spatial, - pad: bool = True): +def shift(grid: CenteredGrid, offsets: tuple, stack_dim: Shape = channel('shift'), dims=spatial, pad=True): """ Wraps :func:`math.shift` for CenteredGrid. @@ -253,7 +245,6 @@ def shift(grid: CenteredGrid, offsets: tuple, stack_dim: Shape = channel('shift' offsets: tuple: stack_dim: (Default value = 'shift') """ - if pad: padding = grid.extrapolation new_bounds = grid.bounds @@ -324,7 +315,7 @@ def stagger(field: CenteredGrid, raise ValueError(type) -def divergence(field: Grid, scheme: Scheme = Scheme(2)) -> CenteredGrid: +def divergence(field: Grid, order=2, implicit: Solve = None) -> CenteredGrid: """ Computes the divergence of a grid using finite differences. @@ -335,29 +326,32 @@ def divergence(field: Grid, scheme: Scheme = Scheme(2)) -> CenteredGrid: Args: field: vector field as `CenteredGrid` or `StaggeredGrid` - scheme: finite difference `Scheme` used for differentiation - supported: explicit 2/4th order - implicit 6th order + order: Spatial order of accuracy. + Higher orders entail larger stencils and more computation time but result in more accurate results assuming a large enough resolution. + Supported: 2 explicit, 4 explicit, 6 implicit. + implicit: When a `Solve` object is passed, performs an implicit operation with the specified solver and tolerances. + Otherwise, an explicit stencil is used. Returns: Divergence field as `CenteredGrid` """ extrapol_map = {} - if not scheme.is_implicit: - if scheme.order == 2: + if not implicit: + if order == 2: if isinstance(field, CenteredGrid): values, needed_shifts = [-1 / 2, 1 / 2], (-1, 1) else: values, needed_shifts = [-1, 1], (0, 1) - elif scheme.order == 4: + elif order == 4: if isinstance(field, CenteredGrid): values, needed_shifts = [1 / 12, -2 / 3, 2 / 3, -1 / 12], (-2, -1, 1, 2) else: values, needed_shifts = [1 / 24, -27 / 24, 27 / 24, -1 / 24], (-1, 0, 1, 2) else: extrapol_map_rhs = {} - if scheme.order == 6: + if order == 6: extrapol_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) extrapol_map_rhs['symmetric'] = combine_by_direction(ANTIREFLECT, ANTISYMMETRIC) @@ -389,11 +383,11 @@ def divergence(field: Grid, scheme: Scheme = Scheme(2)) -> CenteredGrid: shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, spatial_dims)] result_components = [sum([value * shift for value, shift in zip(values, shifted_component)]) / field.dx.vector[dim] for shifted_component, dim in zip(shifted_components, spatial_dims)] - if scheme.is_implicit: + if implicit: result_components = stack(result_components, channel('vector')) result_components.with_values(result_components.values._cache()) - scheme.solve.x0 = field - result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=scheme.solve, + implicit.x0 = field + result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=implicit, f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, "stack_dim": channel('vector')}) result_components = unstack(result_components, 'vector') @@ -786,7 +780,7 @@ def discretize(grid: Grid, filled_fraction=0.25): return grid.with_values(filled_t) -def integrate(field: Field, region: Geometry, scheme: Scheme = Scheme()) -> Tensor: +def integrate(field: Field, region: Geometry, **kwargs) -> Tensor: """ Computes *∫R f(x) dxd* , where *f* denotes the `Field`, *R* the `region` and *d* the number of spatial dimensions (`d=field.shape.spatial_rank`). Depending on the `sample` implementation for `field`, the integral may be a rough approximation. @@ -796,14 +790,14 @@ def integrate(field: Field, region: Geometry, scheme: Scheme = Scheme()) -> Tens Args: field: `Field` to integrate. region: Region to integrate over. - scheme: Numerical scheme. + **kwargs: Specify numerical scheme. Returns: Integral as `phi.Tensor` """ if not isinstance(field, CenteredGrid): raise NotImplementedError() - return field._sample(region, scheme=scheme) * region.volume + return field._sample(region, **kwargs) * region.volume def tensor_as_field(t: Tensor): diff --git a/phi/field/_grid.py b/phi/field/_grid.py index 7e2a72be5..4d2f9c77c 100644 --- a/phi/field/_grid.py +++ b/phi/field/_grid.py @@ -1,11 +1,12 @@ from typing import TypeVar, Any, Tuple +from phi.math import Solve + from phi import math, geom from phi.geom import Box, Geometry, GridCell from . import HardGeometryMask from ._embed import FieldEmbedding from ._field import SampledField, Field, sample, reduce_sample, as_extrapolation -from .numerical import Scheme from ..geom._stack import GeometryStack from ..math import Shape, NUMPY from ..math._shape import spatial, channel, parse_dim_order @@ -48,7 +49,7 @@ def closest_values(self, points: Geometry): """ raise NotImplementedError(self) - def _sample(self, geometry: Geometry, scheme: Scheme) -> math.Tensor: + def _sample(self, geometry: Geometry, **kwargs) -> math.Tensor: raise NotImplementedError(self) def with_values(self, values): @@ -161,7 +162,6 @@ def __init__(self, extrapolation: Any = 0., bounds: Box or float = None, resolution: int or Shape = None, - scheme: Scheme = Scheme(), **resolution_: int or Tensor): """ Args: @@ -196,9 +196,9 @@ def __init__(self, if isinstance(values, math.Tensor): values = math.expand(values, resolution) elif isinstance(values, Geometry): - values = reduce_sample(HardGeometryMask(values), elements, scheme=scheme) + values = reduce_sample(HardGeometryMask(values), elements) elif isinstance(values, Field): - values = reduce_sample(values, elements, scheme=scheme) + values = reduce_sample(values, elements) elif callable(values): values = _sample_function(values, elements) else: @@ -220,11 +220,11 @@ def __getitem__(self, item): bounds = self.elements[item].bounds[{'vector': keep_dims}] return CenteredGrid(values, bounds=bounds, extrapolation=extrapolation) - def _sample(self, geometry: Geometry, scheme: Scheme) -> Tensor: + def _sample(self, geometry: Geometry, **kwargs) -> Tensor: if geometry == self.bounds: return math.mean(self._values, self._resolution) if isinstance(geometry, GeometryStack): - sampled = [self._sample(g, scheme=scheme) for g in geometry.geometries] + sampled = [self._sample(g, **kwargs) for g in geometry.geometries] return math.stack(sampled, geometry.geometries.shape) if isinstance(geometry, GridCell): if self.elements == geometry: @@ -232,9 +232,11 @@ def _sample(self, geometry: Geometry, scheme: Scheme) -> Tensor: elif math.close(self.dx, geometry.size): if all([math.close(offset, geometry.half_size) or math.close(offset, 0) for offset in math.abs(self.bounds.lower - geometry.bounds.lower)]): - dyadic_interpolated = self._dyadic_interplate(geometry.resolution, geometry.bounds, scheme) + dyadic_interpolated = self._dyadic_interplate(geometry.resolution, geometry.bounds, **kwargs) if dyadic_interpolated is not NotImplemented: return dyadic_interpolated + if 'order' in kwargs and kwargs['order'] != 2: + raise NotImplementedError(f"Only 6th-order implicit and 2nd-order resampling supported but got order={kwargs['order']}") fast_resampled = self._shift_resample(geometry.resolution, geometry.bounds) if fast_resampled is not NotImplemented: return fast_resampled @@ -246,16 +248,16 @@ def _sample(self, geometry: Geometry, scheme: Scheme) -> Tensor: # geometry is a subgrid of self return resampled_values else: # otherwise we also sample the extrapolation Field - ext_values = self._extrapolation.field._sample(geometry, scheme) + ext_values = self._extrapolation.field._sample(geometry, **kwargs) inside = self.bounds.lies_inside(points) return math.where(inside, resampled_values, ext_values) return resampled_values - def _dyadic_interplate(self, resolution: Shape, bounds: Box, scheme: Scheme): + def _dyadic_interplate(self, resolution: Shape, bounds: Box, order=2, implicit: Solve = None): from phi.math._nd import _dyadic_interpolate offsets = bounds.lower - self.bounds.lower interpolation_dirs = [0 if math.close(offset, 0) else int(math.sign(offset)) for offset in offsets] - return _dyadic_interpolate(self.values, interpolation_dirs, self.extrapolation, scheme) + return _dyadic_interpolate(self.values, interpolation_dirs, self.extrapolation, order, implicit) def _shift_resample(self, resolution: Shape, bounds: Box, threshold=1e-5, max_padding=20): assert math.all_available(bounds.lower, bounds.upper), "Shift resampling requires 'bounds' to be available." @@ -299,7 +301,6 @@ def __init__(self, extrapolation: float or Extrapolation = 0, bounds: Box or float = None, resolution: Shape or int = None, - scheme: Scheme = Scheme(), **resolution_: int or Tensor): """ Args: @@ -349,9 +350,9 @@ def __init__(self, else: # Keep dim order from data and check it matches resolution assert set(resolution_from_staggered_tensor(values, extrapolation)) == set(resolution), f"Failed to create StaggeredGrid: values {values.shape} do not match given resolution {resolution} for extrapolation {extrapolation}. See https://tum-pbs.github.io/PhiFlow/Staggered_Grids.html" elif isinstance(values, Geometry): - values = reduce_sample(HardGeometryMask(values), elements, scheme=scheme) + values = reduce_sample(HardGeometryMask(values), elements) elif isinstance(values, Field): - values = reduce_sample(values, elements, scheme=scheme) + values = reduce_sample(values, elements) elif callable(values): values = _sample_function(values, elements) if elements.shape.shape.rank > 1: # Different number of X and Y faces @@ -386,8 +387,8 @@ def with_extrapolation(self, extrapolation: Extrapolation): values = math.stack(values, channel(vector=self.resolution)) return StaggeredGrid(values, extrapolation, bounds=self.bounds) - def _sample(self, geometry: Geometry, scheme: Scheme) -> Tensor: - channels = [sample(component, geometry) for component in self.vector.unstack()] + def _sample(self, geometry: Geometry, **kwargs) -> Tensor: + channels = [sample(component, geometry, **kwargs) for component in self.vector.unstack()] return math.stack(channels, geometry.shape['vector']) def closest_values(self, points: Geometry): diff --git a/phi/field/_mask.py b/phi/field/_mask.py index 6a18a661a..4610a56d0 100644 --- a/phi/field/_mask.py +++ b/phi/field/_mask.py @@ -1,7 +1,6 @@ from phi import math from phi.geom import Geometry from ._field import Field -from .numerical import Scheme from ..math import Tensor @@ -19,7 +18,7 @@ def __init__(self, geometry: Geometry): def shape(self): return self.geometry.shape.non_channel - def _sample(self, geometry: Geometry, scheme: Scheme) -> Tensor: + def _sample(self, geometry: Geometry, **kwargs) -> Tensor: return math.to_float(self.geometry.lies_inside(geometry.center)) def __getitem__(self, item: dict): @@ -34,7 +33,7 @@ def __init__(self, geometry: Geometry, balance: Tensor or float = 0.5): super().__init__(geometry) self.balance = balance - def _sample(self, geometry: Geometry, scheme: Scheme) -> Tensor: + def _sample(self, geometry: Geometry, **kwargs) -> Tensor: return self.geometry.approximate_fraction_inside(geometry, self.balance) def __getitem__(self, item: dict): diff --git a/phi/field/_noise.py b/phi/field/_noise.py index c23193ed9..e48796d94 100644 --- a/phi/field/_noise.py +++ b/phi/field/_noise.py @@ -1,12 +1,9 @@ import warnings -import numpy as np - from phi import math from phi.geom import GridCell, Geometry from phi.math import random_normal, Tensor, channel from ._field import Field -from .numerical import Scheme class Noise(Field): @@ -33,7 +30,7 @@ def __init__(self, *shape: math.Shape, scale=10., smoothness=1.0, **channel_dims def shape(self): return self._shape - def _sample(self, geometry: Geometry, scheme: Scheme) -> Tensor: + def _sample(self, geometry: Geometry, **kwargs) -> Tensor: if isinstance(geometry, GridCell): return self.grid_sample(geometry.resolution, geometry.grid_size) raise NotImplementedError(f"{type(geometry)} not supported. Only GridCell allowed.") diff --git a/phi/field/_point_cloud.py b/phi/field/_point_cloud.py index e20655dad..7f020ebbc 100644 --- a/phi/field/_point_cloud.py +++ b/phi/field/_point_cloud.py @@ -2,9 +2,8 @@ from typing import Any, Tuple from phi import math -from phi.geom import Geometry, GridCell, Box, Point +from phi.geom import Geometry, GridCell, Box from ._field import SampledField -from .numerical import Scheme from ..geom._stack import GeometryStack from ..math import Tensor, instance, Shape from ..math.extrapolation import Extrapolation @@ -29,7 +28,7 @@ class PointCloud(SampledField): def __init__(self, elements: Tensor or Geometry, values: Any = 1., - extrapolation: float or Extrapolation = 0., + extrapolation: Extrapolation or float = 0., add_overlapping=False, bounds: Box = None, color: str or Tensor or tuple or list or None = None): @@ -126,13 +125,13 @@ def bounds(self) -> Box: def color(self) -> Tensor: return self._color - def _sample(self, geometry: Geometry, scheme: Scheme) -> Tensor: + def _sample(self, geometry: Geometry, outside_handling="discard", **kwargs) -> Tensor: if geometry == self.elements: return self.values elif isinstance(geometry, GridCell): - return self.grid_scatter(geometry.bounds, geometry.resolution, scheme.outside_points) + return self.grid_scatter(geometry.bounds, geometry.resolution, outside_handling) elif isinstance(geometry, GeometryStack): - sampled = [self._sample(g, scheme=scheme) for g in geometry.geometries] + sampled = [self._sample(g, **kwargs) for g in geometry.geometries] return math.stack(sampled, geometry.geometries.shape) else: raise NotImplementedError() diff --git a/phi/field/numerical.py b/phi/field/numerical.py deleted file mode 100644 index 74d2c2128..000000000 --- a/phi/field/numerical.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -This module contains the `Scheme` class for specifying properties of the numerical scheme of a simulation or individual operations. -""" -from ..math import Solve - - -# volume_sampling='scatter', 'exact' -# NaN to zero: (velocity - prev_velocity) @ particles for outside particles - - -class Scheme: - """ - Numerical scheme, specifying details about the numerical method being used. - - Numerical schemes are used, among others, for - - * Field resampling, such as `phi.field.resample()`, `phi.field.Field.at()` - * Finite difference operations, such as `phi.field.spatial_gradient()`, `phi.field.laplace()`. - - Schemes generally do not affect the sample point locations, extrapolations or other properties. - Consequently, simulation code should run with various schemes without additional modification. - """ - - def __init__(self, order: int = None, solve: Solve = None, outside_points: str = 'discard'): - """ - Args: - order: Minimum spatial order of the scheme. If not supported, functions may choose the next higher order. - solve: Specifies the accuracy for implicit schemes, `None` for explicit schemes. - outside_points: How to handle points lying outside the valid bounds. - Either `'discard'` to ignore them or `'clamp'` to treat them as if they lied on the domain boundary. - """ - self.order = order - """ Minimum spatial order of the scheme. If not supported, functions may choose the next higher order. """ - self.solve = solve - """ Specifies the accuracy for implicit schemes, `None` for explicit schemes. """ - self.outside_points = outside_points - """ - How to handle points lying outside the valid bounds. - Either `'discard'` to ignore them or `'clamp'` to treat them as if they lied on the domain boundary. - """ - - @property - def is_implicit(self): - """ Implicit schemes define a valid `solve` object specifying the accuracy. """ - return self.solve is not None diff --git a/phi/flow.py b/phi/flow.py index 5b9e8d36d..55536d7a2 100644 --- a/phi/flow.py +++ b/phi/flow.py @@ -21,7 +21,6 @@ from .math import Tensor, DType, Solve from .geom import Geometry, Sphere, Box, Cuboid from .field import Grid, CenteredGrid, StaggeredGrid, GeometryMask, SoftGeometryMask, HardGeometryMask, Noise, PointCloud, Scene -from .field.numerical import Scheme from .vis import Viewer from .physics.fluid import Obstacle diff --git a/phi/math/_nd.py b/phi/math/_nd.py index 0abd6fe00..e51a48d33 100644 --- a/phi/math/_nd.py +++ b/phi/math/_nd.py @@ -676,7 +676,7 @@ def sample_subgrid(grid: Tensor, start: Tensor, size: Shape) -> Tensor: return grid -def _dyadic_interpolate(grid: Tensor, interpolation_dirs: List, padding: Extrapolation, scheme): +def _dyadic_interpolate(grid: Tensor, interpolation_dirs: List, padding: Extrapolation, order: int, implicit): """ Samples a sub-grid from `grid` with an offset of half a grid cell in directions defined by `interpolation_dirs`. @@ -693,8 +693,8 @@ def _dyadic_interpolate(grid: Tensor, interpolation_dirs: List, padding: Extrapo Returns: Sub-grid as `Tensor` """ - if scheme.is_implicit: - if scheme.order == 6: + if implicit: + if order == 6: values, needed_shifts = [1 / 20, 3 / 4, 3 / 4, 1 / 20], (-1, 0, 1, 2) values_rhs, needed_shifts_rhs = [3 / 10, 1, 3 / 10], (-1, 0, 1) else: @@ -709,11 +709,11 @@ def _dyadic_interpolate(grid: Tensor, interpolation_dirs: List, padding: Extrapo current_widths = [abs(min(needed_shifts)) + is_neg_dir, max(needed_shifts) - is_neg_dir] padded = math.pad(result, {dim: tuple(current_widths)}, padding) shifted = shift(padded, needed_shifts, [dim], padding=None, stack_dim=None) - result = sum([value * shift for value, shift in zip(values, shifted)]) + result = sum([value * shift_ for value, shift_ in zip(values, shifted)]) - if scheme.is_implicit: - scheme.solve.x0 = result - result = solve_linear(dyadic_interpolate_lhs, result, solve=scheme.solve, + if implicit: + implicit.x0 = result + result = solve_linear(dyadic_interpolate_lhs, result, solve=implicit, f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, "dim": dim, "padding": padding}) return result @@ -721,7 +721,7 @@ def _dyadic_interpolate(grid: Tensor, interpolation_dirs: List, padding: Extrapo @partial(jit_compile_linear, auxiliary_args="values_rhs, needed_shifts_rhs") def dyadic_interpolate_lhs(x, values_rhs, needed_shifts_rhs, dim, padding): shifted = shift(x, needed_shifts_rhs, stack_dim=None, dims=[dim], padding=padding) - return sum([value * shift for value, shift in zip(values_rhs, shifted)]) + return sum([value * shift_ for value, shift_ in zip(values_rhs, shifted)]) # Poisson Brackets diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index c2569130b..5042acc13 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -1928,7 +1928,7 @@ def assemble_tree(obj: PhiTreeNodeType, values: List[Tensor]) -> PhiTreeNodeType return None elif obj is NATIVE_TENSOR: value = values.pop(0) - assert isinstance(value, NativeTensor) + assert isinstance(value, NativeTensor), f"Failed to assemble tree structure. Encountered {value}" return value._native elif obj is None: value = values.pop(0) diff --git a/phi/physics/advect.py b/phi/physics/advect.py index dce6e8dee..da4851c2f 100644 --- a/phi/physics/advect.py +++ b/phi/physics/advect.py @@ -7,11 +7,12 @@ * mac_cormack (grid) * runge_kutta_4 (particle) """ +from phi.math import Solve, channel + from phi import math from phi.field import SampledField, Field, PointCloud, Grid, sample, reduce_sample, spatial_gradient, unstack, stack, CenteredGrid, StaggeredGrid from phi.field._field import FieldType from phi.field._field_math import GridType -from phi.field.numerical import Scheme from phi.geom import Geometry @@ -48,8 +49,7 @@ def finite_rk4(elements: Geometry, velocity: Grid, dt: float, v0: math.Tensor = def advect(field: SampledField, velocity: Field, dt: float or math.Tensor, - integrator=euler, - scheme: Scheme = None) -> FieldType: + integrator=euler) -> FieldType: """ Advect `field` along the `velocity` vectors using the specified integrator. @@ -63,15 +63,10 @@ def advect(field: SampledField, velocity: Any `phi.field.Field` that can be sampled in the elements of `field`. dt: Time increment integrator: ODE integrator for solving the movement. - scheme: differentiation 'Scheme' if provided 'finite_difference' is used - if 'None' is given other functions are used which is the case by default Returns: Advected field of same type as `field` """ - - if scheme is not None and isinstance(field, Grid): - return finite_difference(field, velocity, dt=dt, scheme=scheme) if isinstance(field, PointCloud): return points(field, velocity, dt=dt, integrator=integrator) elif isinstance(field, Grid): @@ -81,7 +76,8 @@ def advect(field: SampledField, def finite_difference(grid: Grid, velocity: Field, - scheme: Scheme = Scheme(2)) -> Field: + order=2, + implicit: Solve = None) -> Field: """ Finite difference advection using the differentiation Scheme indicated by `scheme` and a simple Euler step @@ -89,25 +85,30 @@ def finite_difference(grid: Grid, Args: grid: Grid to be advected velocity: `Grid` that can be sampled in the elements of `grid`. - scheme: finite difference `Scheme` used for differentiation - supported: explicit 2/4th order - implicit 6th order + order: Spatial order of accuracy. + Higher orders entail larger stencils and more computation time but result in more accurate results assuming a large enough resolution. + Supported: 2 explicit, 4 explicit, 6 implicit (inherited from `phi.field.spatial_gradient()` and resampling). + Passing order=4 currently uses 2nd-order resampling. This is work-in-progress. + implicit: When a `Solve` object is passed, performs an implicit operation with the specified solver and tolerances. + Otherwise, an explicit stencil is used. Returns: Advected grid of same type as `grid` """ - if isinstance(grid, StaggeredGrid): - field_components = unstack(grid, 'vector') - grad_list = [spatial_gradient(field_component, stack_dim=math.channel('gradient'), scheme=scheme) for field_component in field_components] - grad_grid = grid.with_values(math.stack([component.values for component in grad_list], math.channel('vector'))) - velocity._scheme = True - amounts = [grad * vel.at(grad, scheme=scheme) for grad, vel in zip(unstack(grad_grid, dim='gradient'), unstack(velocity, dim='vector'))] + grad_list = [spatial_gradient(field_component, stack_dim=channel('gradient'), order=order, implicit=implicit) for field_component in grid.vector] + grad_grid = grid.with_values(math.stack([component.values for component in grad_list], channel('vector'))) + if order == 4: + amounts = [grad * vel.at(grad, order=2) for grad, vel in zip(grad_grid.gradient, velocity.vector)] # ToDo resampling does not yet support order=4 + else: + amounts = [grad * vel.at(grad, order=order, implicit=implicit) for grad, vel in zip(grad_grid.gradient, velocity.vector)] amount = sum(amounts) else: - grad = spatial_gradient(grid, stack_dim=math.channel('gradient'), scheme=scheme) - velocity = stack(unstack(velocity, dim='vector'), dim=math.channel('gradient')) + assert isinstance(grid, CenteredGrid), f"grid must be CenteredGrid or StaggeredGrid but got {type(grid)}" + grad = spatial_gradient(grid, stack_dim=channel('gradient'), order=order, implicit=implicit) + velocity = stack(unstack(velocity, dim='vector'), dim=channel('gradient')) amounts = velocity * grad - amount = sum(unstack(amounts, dim='gradient')) + amount = sum(amounts.gradient) return - amount diff --git a/phi/physics/diffuse.py b/phi/physics/diffuse.py index 3cd95cbf3..99d95f508 100644 --- a/phi/physics/diffuse.py +++ b/phi/physics/diffuse.py @@ -6,7 +6,6 @@ from phi.field._field import FieldType from phi.field._grid import GridType from phi.math import copy_with, shape -from phi.field.numerical import Scheme def explicit(field: FieldType, @@ -66,7 +65,8 @@ def sharpen(x): def finite_difference(grid: Grid, diffusivity: float or math.Tensor or Field, - scheme: Scheme = Scheme(2)) -> FieldType: + order: int, + implicit: math.Solve) -> FieldType: """ Diffusion by using a finite difference scheme. @@ -76,14 +76,17 @@ def finite_difference(grid: Grid, Args: grid: CenteredGrid or StaggeredGrid diffusivity: Diffusion per time. `diffusion_amount = diffusivity * dt` - scheme: finite difference `Scheme` used for differentiation - supported: explicit 2/4th order - implicit 6th order + order: Spatial order of accuracy. + Higher orders entail larger stencils and more computation time but result in more accurate results assuming a large enough resolution. + Supported: 2 explicit, 4 explicit, 6 implicit (inherited from `phi.field.laplace()`). + implicit: When a `Solve` object is passed, performs an implicit operation with the specified solver and tolerances. + Otherwise, an explicit stencil is used. Returns: Diffused grid of same type as `grid`. """ diffusivity = diffusivity.at(grid) if isinstance(diffusivity, Field) else diffusivity - return diffusivity * laplace(grid, scheme=scheme).with_extrapolation(grid.extrapolation) + return diffusivity * laplace(grid, order=order, implicit=implicit).with_extrapolation(grid.extrapolation) def fourier(field: GridType, diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index 158ea0768..346e52a77 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -11,7 +11,6 @@ from phi.geom import union, Geometry from ..field._embed import FieldEmbedding from ..field._grid import GridType, StaggeredGrid -from ..field.numerical import Scheme from ..math import extrapolation, NUMPY, batch, shape, non_channel, expand from ..math._magic_ops import copy_with from ..math.extrapolation import combine_sides, Extrapolation @@ -55,7 +54,7 @@ def make_incompressible(velocity: GridType, obstacles: tuple or list = (), solve=Solve('auto', 1e-5, 1e-5, gradient_solve=Solve('auto', 1e-5, 1e-5)), active: CenteredGrid = None, - scheme: Scheme = Scheme(2)) -> Tuple[GridType, CenteredGrid]: + order=2) -> Tuple[GridType, CenteredGrid]: """ Projects the given velocity field by solving for the pressure and subtracting its spatial_gradient. @@ -64,13 +63,13 @@ def make_incompressible(velocity: GridType, Args: velocity: Vector field sampled on a grid obstacles: List of Obstacles to specify boundary conditions inside the domain (Default value = ()) - solve: Parameters for the pressure solve as. + solve: `Solve` object specifying method and tolerances for the implicit pressure solve. active: (Optional) Mask for which cells the pressure should be solved. If given, the velocity may take `NaN` values where it does not contribute to the pressure. Also, the total divergence will never be subtracted if active is given, even if all values are 1. - scheme: finite difference `Scheme` used for differentiation - For Higher-order schemes the laplace operation is not conducted with a stencil exactly corresponding to the one used in divergence calculations but a smaller one instead, - while this disrupts the formal correctness of the method it only induces insignificant errors and yields considerable performance gains + order: spatial order for derivative computations. + For Higher-order schemes, the laplace operation is not conducted with a stencil exactly corresponding to the one used in divergence calculations but a smaller one instead. + While this disrupts the formal correctness of the method it only induces insignificant errors and yields considerable performance gains. supported: explicit 2/4th order - implicit 6th order (obstacles are only supported with explicit 2nd order) Returns: @@ -78,7 +77,7 @@ def make_incompressible(velocity: GridType, pressure: solved pressure field, `CenteredGrid` """ assert isinstance(obstacles, (tuple, list)), f"obstacles must be a tuple or list but got {type(obstacles)}" - assert (scheme.order == 2 and not scheme.is_implicit) or obstacles == (), f"obstacles are not supported with higher order schemes" + assert order == 2 or obstacles == (), f"obstacles are not supported with higher order schemes" obstacles = [Obstacle(o) if isinstance(o, Geometry) else o for o in obstacles] for obstacle in obstacles: assert obstacle.geometry.vector.item_names == velocity.vector.item_names, f"Obstacles must live in the same physical space as the velocity field {velocity.vector.item_names} but got {type(obstacle.geometry).__name__} obstacle with order {obstacle.geometry.vector.item_names}" @@ -95,7 +94,7 @@ def make_incompressible(velocity: GridType, active *= accessible # no pressure inside obstacles # --- Linear solve --- velocity = apply_boundary_conditions(velocity, obstacles) - div = divergence(velocity, scheme=scheme) * active + div = divergence(velocity, order=order) * active if not all_active: # NaN in velocity allowed div = field.where(field.is_finite(div), div, 0) if not input_velocity.extrapolation.is_flexible and all_active: @@ -106,15 +105,15 @@ def make_incompressible(velocity: GridType, solve = copy_with(solve, x0=CenteredGrid(0, pressure_extrapolation, div.bounds, div.resolution)) if batch(math.merge_shapes(*obstacles)).without(batch(solve.x0)): # The initial pressure guess must contain all batch dimensions solve = copy_with(solve, x0=expand(solve.x0, batch(math.merge_shapes(*obstacles)))) - pressure = math.solve_linear(masked_laplace, f_args=[hard_bcs, active], f_kwargs={"scheme": scheme}, y=div, solve=solve) + pressure = math.solve_linear(masked_laplace, f_args=[hard_bcs, active], f_kwargs=dict(order=order), y=div, solve=solve) # --- Subtract grad p --- - grad_pressure = field.spatial_gradient(pressure, input_velocity.extrapolation, type=type(velocity), scheme=scheme) * hard_bcs + grad_pressure = field.spatial_gradient(pressure, input_velocity.extrapolation, type=type(velocity), order=order) * hard_bcs velocity = (velocity - grad_pressure).with_extrapolation(input_velocity.extrapolation) return velocity, pressure @math.jit_compile_linear # jit compilation is required for boundary conditions that add a constant offset solving Ax + b = y -def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid, scheme: Scheme) -> CenteredGrid: +def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid, order=2, implicit: Solve = None) -> CenteredGrid: """ Computes the laplace of `pressure` in the presence of obstacles. @@ -126,18 +125,22 @@ def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid, active: Mask indicating for which cells the pressure value is valid. Linear solves will only determine the pressure for these cells. This is generally zero inside obstacles and in non-simulated regions. - scheme: finite difference `Scheme` used for laplace calculation + order: Spatial order of accuracy. + Higher orders entail larger stencils and more computation time but result in more accurate results assuming a large enough resolution. + Supported: 2 explicit, 4 explicit, 6 implicit (inherited from `phi.field.laplace()`). + implicit: When a `Solve` object is passed, performs an implicit operation with the specified solver and tolerances. + Otherwise, an explicit stencil is used. Returns: `CenteredGrid` """ - if scheme.order == 2 and not scheme.is_implicit: + if order == 2 and not implicit: grad = spatial_gradient(pressure, hard_bcs.extrapolation, type=type(hard_bcs)) valid_grad = grad * field.bake_extrapolation(hard_bcs).with_extrapolation(grad.extrapolation) div = divergence(valid_grad) laplace = where(active, div, pressure) else: - laplace = field.laplace(pressure, scheme=scheme) + laplace = field.laplace(pressure, order=order, implicit=implicit) return laplace @@ -222,41 +225,46 @@ def _accessible_extrapolation(vext: Extrapolation): raise ValueError(f"Unsupported extrapolation: {type(vext)}") -def incompressible_rk4(pde: Callable, velocity, pressure, dt, pressure_order=4, pressure_solve=Solve('CG', 1e-12, 1e-12)): +def incompressible_rk4(pde: Callable, velocity: GridType, pressure: CenteredGrid, dt, order=4, solve=Solve('CG', 1e-12, 1e-12)): """ + Implements the 4th-order Runge-Kutta time advancement scheme for incompressible vector fields. + This approach is inspired by [Kampanis et. al., 2006](https://www.sciencedirect.com/science/article/pii/S0021999105005061) and incorporates the pressure treatment into the time step. Args: - pde: - velocity: - pressure: - dt: - pressure_order: - pressure_solve: + pde: Momentum equation. Function that computes all PDE terms not related to pressure, e.g. diffusion, advection, external forces. + velocity: Velocity grid at time `t`. + pressure: Pressure at time `t`. + dt: Time increment to integrate. + solve: `Solve` object specifying method and tolerances for the implicit pressure solve. + order: spatial order for derivative computations. + For Higher-order schemes, the laplace operation is not conducted with a stencil exactly corresponding to the one used in divergence calculations but a smaller one instead. + While this disrupts the formal correctness of the method it only induces insignificant errors and yields considerable performance gains. + supported: explicit 2/4th order - implicit 6th order (obstacles are only supported with explicit 2nd order) Returns: - velocity: - pressure: + velocity: Velocity at time `t+dt`, same type as `velocity`. + pressure: Pressure grid at time `t+dt`, `CenteredGrid`. """ v_1, p_1 = velocity, pressure # PDE at current point - rhs_1 = pde(v_1, p_1) - field.spatial_gradient(p_1, type=StaggeredGrid, scheme=Scheme(pressure_order)) + rhs_1 = pde(v_1) - field.spatial_gradient(p_1, type=StaggeredGrid, order=order) v_2_old = velocity + (dt / 2) * rhs_1 - v_2, delta_p = make_incompressible(v_2_old, solve=pressure_solve, scheme=Scheme(pressure_order)) + v_2, delta_p = make_incompressible(v_2_old, solve=solve, order=order) p_2 = p_1 + delta_p / dt # PDE at half-point - rhs_2 = pde(v_2, p_2) - field.spatial_gradient(p_2, type=StaggeredGrid, scheme=Scheme(pressure_order)) + rhs_2 = pde(v_2) - field.spatial_gradient(p_2, type=StaggeredGrid, order=order) v_3_old = velocity + (dt / 2) * rhs_2 - v_3, delta_p = make_incompressible(v_3_old, solve=pressure_solve, scheme=Scheme(pressure_order)) + v_3, delta_p = make_incompressible(v_3_old, solve=solve, order=order) p_3 = p_2 + delta_p / dt # PDE at corrected half-point - rhs_3 = pde(v_3, p_3) - field.spatial_gradient(p_3, type=StaggeredGrid, scheme=Scheme(pressure_order)) + rhs_3 = pde(v_3) - field.spatial_gradient(p_3, type=StaggeredGrid, order=order) v_4_old = velocity + dt * rhs_2 - v_4, delta_p = make_incompressible(v_4_old, solve=pressure_solve, scheme=Scheme(pressure_order)) + v_4, delta_p = make_incompressible(v_4_old, solve=solve, order=order) p_4 = p_3 + delta_p / dt # PDE at RK4 point - rhs_4 = pde(v_4, p_4) - field.spatial_gradient(p_4, type=StaggeredGrid, scheme=Scheme(pressure_order)) + rhs_4 = pde(v_4) - field.spatial_gradient(p_4, type=StaggeredGrid, order=order) v_p1_old = velocity + (dt / 6) * (rhs_1 + 2 * rhs_2 + 2 * rhs_3 + rhs_4) p_p1_old = (1 / 6) * (p_1 + 2 * p_2 + 2 * p_3 + p_4) - v_p1, delta_p = make_incompressible(v_p1_old, solve=pressure_solve, scheme=Scheme(pressure_order)) + v_p1, delta_p = make_incompressible(v_p1_old, solve=solve, order=order) p_p1 = p_p1_old + delta_p / dt return v_p1, p_p1 diff --git a/tests/commit/physics/test_flip.py b/tests/commit/physics/test_flip.py index 81ebede92..86e22fde8 100644 --- a/tests/commit/physics/test_flip.py +++ b/tests/commit/physics/test_flip.py @@ -7,7 +7,7 @@ def step(particles: PointCloud, obstacles: list, dt: float, **grid_resolution): # --- Grid Operations --- - velocity = prev_velocity = field.finite_fill(StaggeredGrid(particles, 0, particles.bounds, scheme=Scheme(outside_points='clamp'), **grid_resolution)) + velocity = prev_velocity = field.finite_fill(particles.at(StaggeredGrid(0, 0, particles.bounds, **grid_resolution), outside_handling='clamp')) occupied = CenteredGrid(particles.mask(), velocity.extrapolation.spatial_gradient(), velocity.bounds, velocity.resolution) velocity, pressure = fluid.make_incompressible(velocity + (0, -9.81 * dt), obstacles, active=occupied) # --- Particle Operations --- diff --git a/tests/release/test_flip.py b/tests/release/test_flip.py index 381cedb40..f663c0595 100644 --- a/tests/release/test_flip.py +++ b/tests/release/test_flip.py @@ -7,7 +7,7 @@ def step(particles: PointCloud, obstacles: list, dt: float, **grid_resolution): # --- Grid Operations --- - velocity = prev_velocity = field.finite_fill(StaggeredGrid(particles, 0, particles.bounds, scheme=Scheme(outside_points='clamp'), **grid_resolution)) + velocity = prev_velocity = field.finite_fill(particles.at(StaggeredGrid(0, 0, particles.bounds, **grid_resolution), outside_handling='clamp')) occupied = CenteredGrid(particles.mask(), velocity.extrapolation.spatial_gradient(), velocity.bounds, velocity.resolution) velocity, pressure = fluid.make_incompressible(velocity + (0, -9.81 * dt), obstacles, active=occupied) # --- Particle Operations --- From 8dee6bb353dcb7b102e169d0aebf569ce40f2157 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 16 Jan 2023 19:44:45 +0100 Subject: [PATCH 070/170] [doc] Add Higher-order notebooks --- .github/workflows/update-gh-pages.yml | 4 +- docs/index.md | 17 +- docs/prerendered/HigherOrder_Demo.ipynb | 749 ++++++++++++++++++ .../prerendered/Taylor_Green_Comparison.ipynb | 564 +++++++++++++ 4 files changed, 1325 insertions(+), 9 deletions(-) create mode 100644 docs/prerendered/HigherOrder_Demo.ipynb create mode 100644 docs/prerendered/Taylor_Green_Comparison.ipynb diff --git a/.github/workflows/update-gh-pages.yml b/.github/workflows/update-gh-pages.yml index 545ec75fd..43b624cac 100644 --- a/.github/workflows/update-gh-pages.yml +++ b/.github/workflows/update-gh-pages.yml @@ -33,7 +33,9 @@ jobs: run: pdoc --html --output-dir docs --force phi - name: Build static HTML for Jupyter Notebooks - run: jupyter nbconvert --to html --execute --allow-errors docs/*.ipynb + run: | + jupyter nbconvert --to html --execute --allow-errors docs/*.ipynb + jupyter nbconvert --to html docs/prerendered/*.ipynb - name: Deploy 🚀 uses: JamesIves/github-pages-deploy-action@4.1.4 # See https://github.com/marketplace/actions/deploy-to-github-pages diff --git a/docs/index.md b/docs/index.md index fee5df019..2158db02f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,6 +28,7 @@ * [▶️ Introduction Video](https://youtu.be/YRi_c0v3HKs) * [Differentiable fluid simulations](Fluids_Tutorial.html) +* [Higher-order incompressible fluids](prerendered/HigherOrder_Demo.html) * [Batched Obstacles](Batched_Obstacles.html) #### I/O @@ -52,14 +53,14 @@ ### Module Documentation -| Module API | Documentation | -|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [phi.vis](phi/vis) | [Visualization](Visualization.md): Plotting, interactive user interfaces
[Dash](Web_Interface.md): Web interface
[Console](ConsoleUI.md): Command line interface | -| [phi.physics](phi/physics)
[phi.physics.advect](phi/physics/advect.html)
[phi.physics.fluid](phi/physics/fluid.html)
[phi.physics.diffuse](phi/physics/diffuse.html)
[phi.physics.flip](phi/physics/flip.html) | [Fluids Tutorial](Fluids_Tutorial.html): Introduction to core classes and fluid-related functions.
[Overview](Physics.md): Domains, built-in physics functions
[Functions for Fluid Simulations](Fluid_Simulation.md): Advection, projection, diffusion | -| [phi.field](phi/field) | [Overview](Fields.md): Grids, particles
[Staggered Grids](Staggered_Grids.html): Data layout, usage
[Reading and Writing Simulation Data](Reading_and_Writing_Data.md)
[Scene Format Specification](Scene_Format_Specification.md): Directory layout, file format | -| [phi.geom](phi/geom) | [Overview](Geometry.md): Differentiable Geometry | -| [phi.math](phi/math)
[phi.math.backend](phi/math/backend)
[phi.math.extrapolation](phi/math/extrapolation.html)
[phi.math.magic](phi/math/magic.html) | [Overview](Math.html): Named dimensions, backends, indexing, non-uniform tensors, precision
[Optimization and Training](Optimization.md): Automatic differentiation, neural network training
[Performance](GPU_Execution.md): GPU, JIT compilation, profiler | -| [phi.torch.nets](phi/torch/nets)
[phi.tf.nets](phi/tf/nets)
[phi.jax.stax.nets](phi/jax/stax/nets) | [Built-in Neural Networks](Network_API): Architectures, convenience functions | +| Module API | Documentation | +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [phi.vis](phi/vis) | [Visualization](Visualization.md): Plotting, interactive user interfaces
[Dash](Web_Interface.md): Web interface
[Console](ConsoleUI.md): Command line interface | +| [phi.physics](phi/physics)
[phi.physics.advect](phi/physics/advect.html)
[phi.physics.fluid](phi/physics/fluid.html)
[phi.physics.diffuse](phi/physics/diffuse.html)
[phi.physics.flip](phi/physics/flip.html) | [Fluids Tutorial](Fluids_Tutorial.html): Introduction to core classes and fluid-related functions.
[Higher-order schemes](prerendered/Taylor_Green_Comparison.html): Compares the accuracy of various numerial schemes.
[Overview](Physics.md): Domains, built-in physics functions
[Functions for Fluid Simulations](Fluid_Simulation.md): Advection, projection, diffusion | +| [phi.field](phi/field) | [Overview](Fields.md): Grids, particles
[Staggered Grids](Staggered_Grids.html): Data layout, usage
[Reading and Writing Simulation Data](Reading_and_Writing_Data.md)
[Scene Format Specification](Scene_Format_Specification.md): Directory layout, file format | +| [phi.geom](phi/geom) | [Overview](Geometry.md): Differentiable Geometry | +| [phi.math](phi/math)
[phi.math.backend](phi/math/backend)
[phi.math.extrapolation](phi/math/extrapolation.html)
[phi.math.magic](phi/math/magic.html) | [Overview](Math.html): Named dimensions, backends, indexing, non-uniform tensors, precision
[Optimization and Training](Optimization.md): Automatic differentiation, neural network training
[Performance](GPU_Execution.md): GPU, JIT compilation, profiler | +| [phi.torch.nets](phi/torch/nets)
[phi.tf.nets](phi/tf/nets)
[phi.jax.stax.nets](phi/jax/stax/nets) | [Built-in Neural Networks](Network_API): Architectures, convenience functions | ### Core Classes diff --git a/docs/prerendered/HigherOrder_Demo.ipynb b/docs/prerendered/HigherOrder_Demo.ipynb new file mode 100644 index 000000000..01f02edfe --- /dev/null +++ b/docs/prerendered/HigherOrder_Demo.ipynb @@ -0,0 +1,749 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "3m0UJN9_lZBK", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Higher-order Fluid Simulations on Periodic Boundaries with ΦFlow\n", + "\n", + "[![Google Collab Book](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/eloasdjo/PhiFlow/blob/higher_order_demo.ipynb)\n", + "\n", + "This notebook shows how to write a higher-order incompressible fluid simulation, simulating a Kolmogorov flow.\n", + "Higher-order finite difference schemes are available in ΦFlow 2.3 and newer (only with periodic boundary conditions as of 2.3.0).\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "source": [ + "## Higher-order Schemes in ΦFlow\n", + "\n", + "The spatial order of all built-in finite-difference functions is specified using the `order` parameter.\n", + "By default, an explicit finite difference scheme is used.\n", + "For implicit schemes, pass `implicit=Solve(...)` where the `Solve` specifies the algorithm and tolerances.\n", + "\n", + "The following options are implemented up to date:\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + "
Differential operation
Supported Schemes
field.spatial_gradient,
field.laplace,
field.divergence
order=2,
order=4,
order=6, implicit=Solve(...)
advect.finite_difference,
diffuse.finite_difference
order=2,
order=4,
order=6, implicit=Solve(...)
fluid.make_incompressibleorder=2,
order=4
Field.atorder=2,
order=6, implicit=Solve(...) (only available for sampling at midpoints)
" + ], + "metadata": { + "id": "aZ7ayjSaccOh", + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "If [ΦFlow](https://github.com/tum-pbs/PhiFlow) 2.3 or newer is not already installed, uncomment the first line in the cell below." + ], + "metadata": { + "id": "dwjWUYgxcaMZ", + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "-NuGPniRlX3u", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "f204a947-7a92-47b8-cbe4-40f41bafaca0", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# !pip install --upgrade git+https://github.com/tum-pbs/PhiFlow@2.3-develop\n", + "import jax\n", + "import numpy as np\n", + "\n", + "from phi.jax.flow import *\n", + "from tqdm.notebook import trange\n", + "\n", + "math.set_global_precision(64)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4NTkTx18nitZ", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Pressure Forcing\n", + "The Kolmogorov flow uses a sinusoidal forcing along x which is added to the velocity at every time step.\n", + "We add a small perturbation in the form of `Noise` to violate symmetry and trigger a turbulent time development." + ] + }, + { + "cell_type": "code", + "source": [ + "DOMAIN = dict(extrapolation=extrapolation.PERIODIC, bounds=Box(x=2*PI, y=2*PI), x=100, y=100)\n", + "FORCING = StaggeredGrid(lambda x, y: vec(x=math.sin(4 * y), y=0), **DOMAIN) + StaggeredGrid(Noise(), **DOMAIN) * 0.01\n", + "plot({'Force along X': FORCING['x'], 'Force along Y': FORCING['y']}, same_scale=False)" + ], + "metadata": { + "id": "L95qdtYBfbk1", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 386 + }, + "outputId": "b755086c-dc72-4b6b-de10-8d8bd5d79d88", + "pycharm": { + "name": "#%%\n" + } + }, + "execution_count": 2, + "outputs": [ + { + "data": { + "text/plain": "
" + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "## Simulation\n", + "Next we define the momentum equation (PDE) for the incompressible flow.\n", + "We use 6th-order implicit advection and diffusion.\n", + "The pressure solve is integrated into ΦFlow's 4th-order Runge-Kutta integrator `fluid.incompressible_rk4`. It uses a 4th-order direct scheme to avoid nested linear solves.\n", + "For all implicit operations, we use the conjugate gradient method `'CG'` since the periodic boundaries result in symmetric linear equation systems for which CG is fastest." + ], + "metadata": { + "id": "lsr6Ri_9ZPXy", + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "source": [ + "def momentum_equation(v, viscosity=0.001):\n", + " advection = advect.finite_difference(v, v, order=6, implicit=Solve('CG', 1e-5, 1e-5))\n", + " diffusion = diffuse.finite_difference(v, viscosity, order=6, implicit=Solve('CG', 1e-5, 1e-5))\n", + " return advection + diffusion + FORCING\n", + "\n", + "@jit_compile\n", + "def rk4_step(v, p, dt):\n", + " return fluid.incompressible_rk4(momentum_equation, v, p, dt, order=4, solve=Solve('CG', 1e-5, 1e-5))" + ], + "metadata": { + "id": "R-e3yX3cZ95Z", + "pycharm": { + "name": "#%%\n" + } + }, + "execution_count": 3, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Let's run the simulation!" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "source": [ + "v0 = StaggeredGrid(0, **DOMAIN)\n", + "p0 = CenteredGrid(0, **DOMAIN)\n", + "multi_step = lambda *x, **kwargs: iterate(rk4_step, 25, *x, **kwargs)\n", + "v_trj, p_trj = iterate(multi_step, batch(time=100), v0, p0, dt=0.005, range=trange)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 49, + "referenced_widgets": [ + "daa8a904b6824715890d965e9e9a1b08", + "f6898b402c62403e8c689a5e18c299d4", + "176186b2c10b4df0a6158c9a23d8758d", + "d91c4f13076b434f82f59fe6a08a6bb4", + "60db88e473014d83a4767272051961b3", + "c8fa3abfaf4d41acbe520d983d28575c", + "9975b313c00e4d998768f2513f386c14", + "02503916033d47b7b436dce40d019948", + "baf769c8c81a43989aa60693a0b65adf", + "379e91b0499c46b8a7da1f3e23d68c72", + "720685a440694d179f972eb9459eb73a" + ] + }, + "id": "z52DqqPKd8OA", + "outputId": "4af37e8b-e978-4224-ed49-34bfc84eb193", + "pycharm": { + "name": "#%%\n" + } + }, + "execution_count": 4, + "outputs": [ + { + "data": { + "text/plain": " 0%| | 0/100 [00:00", + "text/html": "" + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "## Saving the Simulation Data\n", + "\n", + "We can store the data in one of two ways: Either we use ΦFlow's built-in field I/O functions, or we store the data using NumPy.\n", + "Let's view the NumPy data first." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 6, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "float64 (101, 100, 100, 2)\n" + ] + } + ], + "source": [ + "np_velocity = v_trj.uniform_values().numpy('time,x,y,vector')\n", + "print(np_velocity.dtype, np_velocity.shape)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "We can write this array using any of NumPy's save functions, such as `np.save, np.savez, np.savez_compressed`.\n", + "Note that we called `.uniform_values()` instead of `.values` to get an array that is guaranteed to be NumPy-compatible for all possible boundary conditions.\n", + "\n", + "Alternatively, we can create a ΦFlow `Scene` object and write the data to it." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 7, + "outputs": [], + "source": [ + "scene = Scene.create('data/')\n", + "\n", + "scene.write(velocity_trj=v_trj, frame=0) # write all frames into one file\n", + "\n", + "for i, v_frame in enumerate(v_trj.time): # write each frame into one file\n", + " scene.write(velocity=v_frame, frame=i)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Comparison to Lower-Order Schemes\n", + "\n", + "An evaluation of accuracy and performance can be found [here](Taylor_Green_Comparison.html)." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "daa8a904b6824715890d965e9e9a1b08": { + "model_module": "@jupyter-widgets/controls", + "model_name": "HBoxModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_f6898b402c62403e8c689a5e18c299d4", + "IPY_MODEL_176186b2c10b4df0a6158c9a23d8758d", + "IPY_MODEL_d91c4f13076b434f82f59fe6a08a6bb4" + ], + "layout": "IPY_MODEL_60db88e473014d83a4767272051961b3" + } + }, + "f6898b402c62403e8c689a5e18c299d4": { + "model_module": "@jupyter-widgets/controls", + "model_name": "HTMLModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_c8fa3abfaf4d41acbe520d983d28575c", + "placeholder": "​", + "style": "IPY_MODEL_9975b313c00e4d998768f2513f386c14", + "value": "100%" + } + }, + "176186b2c10b4df0a6158c9a23d8758d": { + "model_module": "@jupyter-widgets/controls", + "model_name": "FloatProgressModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_02503916033d47b7b436dce40d019948", + "max": 5000, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_baf769c8c81a43989aa60693a0b65adf", + "value": 5000 + } + }, + "d91c4f13076b434f82f59fe6a08a6bb4": { + "model_module": "@jupyter-widgets/controls", + "model_name": "HTMLModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_379e91b0499c46b8a7da1f3e23d68c72", + "placeholder": "​", + "style": "IPY_MODEL_720685a440694d179f972eb9459eb73a", + "value": " 5000/5000 [10:59<00:00, 8.17it/s]" + } + }, + "60db88e473014d83a4767272051961b3": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c8fa3abfaf4d41acbe520d983d28575c": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "9975b313c00e4d998768f2513f386c14": { + "model_module": "@jupyter-widgets/controls", + "model_name": "DescriptionStyleModel", + "model_module_version": "1.5.0", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "02503916033d47b7b436dce40d019948": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "baf769c8c81a43989aa60693a0b65adf": { + "model_module": "@jupyter-widgets/controls", + "model_name": "ProgressStyleModel", + "model_module_version": "1.5.0", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "379e91b0499c46b8a7da1f3e23d68c72": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "720685a440694d179f972eb9459eb73a": { + "model_module": "@jupyter-widgets/controls", + "model_name": "DescriptionStyleModel", + "model_module_version": "1.5.0", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/docs/prerendered/Taylor_Green_Comparison.ipynb b/docs/prerendered/Taylor_Green_Comparison.ipynb new file mode 100644 index 000000000..3d6689904 --- /dev/null +++ b/docs/prerendered/Taylor_Green_Comparison.ipynb @@ -0,0 +1,564 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "3m0UJN9_lZBK", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Evaluating Higher-order Fluid Simulations on the Taylor-Green Vortex\n", + "\n", + "[![Google Collab Book](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/eloasdjo/PhiFlow/blob/Higher_order_Tutorial.ipynb)\n", + "\n", + "This notebook compares the accuracy of various numerical schemes on the [Taylor-Green Vortex](https://en.wikipedia.org/wiki/Taylor_Green_vortex),\n", + "from semi-Lagrangian advection up to 6th order compact schemes.\n", + "\n", + "If [ΦFlow](https://github.com/tum-pbs/PhiFlow) 2.3 or newer is not already installed, uncomment the first line in the cell below." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "-NuGPniRlX3u", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# !pip install --upgrade --quiet git+https://github.com/tum-pbs/PhiFlow@2.3-develop\n", + "import time\n", + "from functools import partial\n", + "from tqdm.notebook import trange\n", + "from phi.jax.flow import *\n", + "\n", + "math.set_global_precision(64) # double precision for all operations" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4NTkTx18nitZ", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Closed-Form Solution\n", + "\n", + "The Taylor–Green vortex is an unsteady incompressible flow of a decaying vortex, and has an analytic solution, given below." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "0FBmhDCAsx-T", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "def taylor_green_velocity(x, t, viscosity=0.1):\n", + " sin_x, sin_y = math.sin(x).vector\n", + " cos_x, cos_y = math.cos(x).vector\n", + " return vec(x=cos_x*sin_y, y=-sin_x*cos_y) * math.exp(-2 * viscosity * t)\n", + "\n", + "def taylor_green_pressure(x, t, viscosity=0.1):\n", + " return -1 / 4 * (math.sum(math.cos(2 * x), 'vector')) * math.exp(-4 * viscosity * t)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "STWLmyfb12eh", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "The speed of the vortex decay scales with the viscosity value. We will use a relatively low viscosity of 0.1, corresponding to a Reynolds number of 1/vis = 10.\n", + "\n", + "Let's create a square domain of size 2π with a grid resolution of 25.\n", + "We sample the velocity on a [staggered grid](https://tum-pbs.github.io/PhiFlow/Staggered_Grids.html) and the pressure at the cell centers." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "gUg4_lxJvyJj", + "outputId": "e10326c5-037f-4776-8cde-12e88a306c8a", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": "", + "text/html": "" + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "DOMAIN = dict(x=25, y=25, extrapolation=extrapolation.PERIODIC, bounds=Box(x=2*PI, y=2*PI))\n", + "TIME = math.linspace(0, 10., batch(time=200))\n", + "analytic_v = StaggeredGrid(partial(taylor_green_velocity, t=TIME), **DOMAIN)\n", + "analytic_p = CenteredGrid(partial(taylor_green_pressure, t=TIME), **DOMAIN)\n", + "plot({\"Velocity\": analytic_v, \"Pressure\": analytic_p}, animate='time')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1AHVJk0Y4Smw", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Higher-order Simulation\n", + "\n", + "Implementing a higher-order fluid solver requires higher-order spatial and temporal schemes.\n", + "We use Runge-Kutta 4 for time integration, employing the built-in `fluid.incompressible_rk4` function which takes care of the incompressibility part and lets us pass the rest of the PDE as a function.\n", + "In our case, this PDE function, `momentum_equation`, consists of an advection and diffusion term.\n", + "We parameterize the step function with the spatial order, so we can test multiple numerical schemes later, in particular the 6th order implicit scheme as well as a 4th and 2nd order explicit scheme.\n", + "For the pressure solve, 4th-order schemes are sufficient because the vortex is mainly diffusion-driven.\n", + "\n", + "For the linear solves, we use the conjugate gradient method `'CG'` since the periodic boundaries result in symmetric linear equation systems for which CG is fastest. Similar to activating double precision mode, we set the Error tolerance low to demonstrate the full capabilities of the solver." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "mDZdlZSf5OcL", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "def momentum_equation(v, order: int, implicit: Solve, viscosity=0.1):\n", + " advection = advect.finite_difference(v, v, order=order, implicit=implicit)\n", + " diffusion = diffuse.finite_difference(v, viscosity, order=order, implicit=implicit)\n", + " return advection + diffusion\n", + "\n", + "@jit_compile(auxiliary_args='order,implicit,pressure_order', forget_traces=True)\n", + "def rk4_step(v, p, dt, order=6, implicit=None, pressure_order=4):\n", + " return fluid.incompressible_rk4(partial(momentum_equation, order=order, implicit=implicit), v, p, dt, order=pressure_order, solve=Solve('CG', 1e-12, 1e-12))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WywdABcNEMtp", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Now we can simulate and visualize the time evolution in high-order accuracy." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "g0WD9yntEQzB", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "f18db300-434a-4bdc-e5f2-011afc6c4d05", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": " 0%| | 0/200 [00:00", + "text/html": "" + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "v_trj, p_trj = iterate(rk4_step, batch(time=200), analytic_v.time[0], analytic_p.time[0], dt=0.1, implicit=Solve('CG', 1e-12, 1e-12), range=trange)\n", + "vis.plot({\"Velocity\": v_trj, \"Pressure\": p_trj}, animate='time')" + ] + }, + { + "cell_type": "markdown", + "source": [ + "## Benchmark and Comparison\n", + "To compare the higher-order approach to different lower-order methods, we define two additional finite-difference schemes with lower orders and a semi-Lagrangian scheme that uses operator splitting instead of Runge-Kutta 4." + ], + "metadata": { + "id": "LI3LQpso_DvQ", + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "source": [ + "@jit_compile(forget_traces=True)\n", + "def semi_lagrangian_step(velocity, pressure, dt, viscosity=0.1):\n", + " velocity = diffuse.explicit(velocity, viscosity, dt)\n", + " velocity = advect.semi_lagrangian(velocity, velocity, dt)\n", + " return fluid.make_incompressible(velocity, (), Solve('CG', 1e-12, 1e-12, x0=pressure))\n", + "\n", + "methods = math.layout({\n", + " '6th order implicit': partial(rk4_step, order=6, implicit=Solve('CG', 1e-12, 1e-12), pressure_order=4),\n", + " '4th order': partial(rk4_step, order=4, pressure_order=4),\n", + " '2nd order': partial(rk4_step, order=2, pressure_order=2),\n", + " 'Semi-Lagrangian': semi_lagrangian_step,\n", + "}, batch('method'))" + ], + "metadata": { + "id": "1HnN9_KZuBpq", + "pycharm": { + "name": "#%%\n" + } + }, + "execution_count": 6, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "We will use a small step size of Δt = 0.001 to factor out the time advancement and put emphasis on the spatial discretization.\n", + "We target a simulation time of 0.5 seconds resulting in 500 frames.\n", + "\n", + "We define the function `eval_error` to compute the error for a given method and `resolution` at every time step.\n", + "We run it on the four methods defined above with five resolutions each.\n", + "Note that this can take 15-20 min to run." + ], + "metadata": { + "id": "ThzE3Q3SE8kl", + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "source": [ + "def eval_error(resolution, step_function, dt=0.001, time_sec=.5):\n", + " domain = dict(x=resolution, y=resolution, extrapolation=extrapolation.PERIODIC, bounds=Box(x=2*PI, y=2*PI))\n", + " times = math.linspace(0, time_sec, batch(time=math.round(time_sec/dt)+1))\n", + " analytic_v = StaggeredGrid(partial(taylor_green_velocity, t=times), **domain)\n", + " analytic_p = CenteredGrid(partial(taylor_green_pressure, t=times), **domain)\n", + " v, p = analytic_v.time[0], analytic_p.time[0]\n", + " step_function(v, p, dt) # jit-compile function before measuring the execution time\n", + " (sim_v, _), exec_times = iterate(step_function, times.shape - 1, v, p, dt=dt, measure=time.perf_counter)\n", + " rmse = math.sqrt(math.mean((analytic_v - sim_v).values**2))\n", + " relative_err = rmse / math.mean(abs(analytic_v.values))\n", + " return relative_err, exec_times.mean\n", + "\n", + "resolutions = wrap([8, 16, 32, 64, 128], batch(resolution='8, 16, 32, 64, 128'))\n", + "errors, exec_times = math.map(eval_error, resolutions, methods, range=trange)" + ], + "metadata": { + "id": "KOKuyn7FU-4c", + "pycharm": { + "name": "#%%\n" + } + }, + "execution_count": 7, + "outputs": [ + { + "data": { + "text/plain": " 0%| | 0/20 [00:00" + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Next we visualise the final error with respect to the resolution in a log-log plot. This allows us to easily see the order of accuracy.\n", + "The dark lines correspond to the errors of our four different simulation schemes and each one is accompanied by a theoretical convergence line (orders 6, 4, 2 and 1 for the four methods) in a lighter color." + ], + "metadata": { + "id": "fTrKTdDTcB3g", + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 9, + "outputs": [ + { + "data": { + "text/plain": "
" + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
", + "image/png": "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w8SJE7n66qtt4wN89NFHnHrqqUydOpV58+ZRUVEBmAju/vvvZ+bMmbz++uut7nvvvffy0EMPASYC+uUvf8mZZ57J2LFjWbduHZdffjkjR47k7rvvBiArK4sxY8Zwww03MHHiRK688kqqqqraPY991Pj8888zceJEJk2axHXXXdfqvo7+vQShHdZ288OGQXa2WfZzVQMVH2/aeLz/vikMPuMMk0ThKgoTvIIIlI84cOAAgwYN4sYbb2TKlCncfPPNVFZWcuWVV5KamspLL73E5s2bCQ8PByAmJoaNGzeSnp5uEwR7nnjiCQC2bdvGyy+/zA033GDLYFu7di3PPfccn376KRkZGURERLB161buuusuNjS3JCguLubPf/4z//vf/9i4cSOpqak8/PDDtvHDwsL48ssvueaaa5w+V0hICKtXryYtLY1LL72UJ554gu3bt7NixQpKSkoA2L17N4sWLWLr1q307duXJ5980uF4O3bsYMmSJXz66ads2bKFRx99tNVxR/9egtAhwcGmTuqMM0ybjo0bTSJF8y9jDrnwQuOq/stftiRRvPqqJFF0MiJQPqKhocEmOJs2baJPnz789a9/dXj+5ZdfDsC0adPIyspqd/zLL7+0RRdjxozBYrGwp3lz95xzzmHAgAEArF69mh//+McATJw4kYkTJwLw9ddf891333H66aczefJknnvuObKzs23jX3311W491yWXXAJASkoK48ePJz4+ntDQUIYPH86hQ4cAGDJkCKeffjoAP/7xj/nSybLJp59+ypVXXklMTAyA7TkEwSP69YOZM83+0rFjJiV91y7njQ4jI00SxbffmmjsmmvgootMNCZ0CiJQPiIpKYmkpCRmzJgBmEhg48aNDs8PDQ0FIDAwsMO9JGcF13369Gn1vqP0a60155xzDps3b2bz5s189913PPPMMw7HcDXPgIAA29fW99Z5t72/s3RwrbWkiwudg1JgsZhlv4QE2LvXWCa52j+eNs0Y0D78sDl/3DjztRf2eIXWiED5iLi4OIYMGcLu3bsB+OSTTxg3bhwAUVFRlJeXH9d4Z555Ji+99BIAe/bs4eDBg4wePdrpedu3b2fr1q0AnHLKKaxZs4Z9+/YBUFVVZYvAvM3BgwdtGYsvv/wyM2fOdHjuWWedxWuvvWZbHjzSQQHlifx7CYKN0FCTAHHaaSY54ttv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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "THEO_ORDER = wrap([6, 4, 2, 1], methods.shape)\n", + "theo_lines = vec(resolution=resolutions.resolution[(0, -1)], error=errors.time[-1].resolution[0]*.5*stack([1, (resolutions.min/resolutions.max)**THEO_ORDER], batch('resolution')))\n", + "SIM_COLOR = wrap(['#ff0000', '#00ff00', '#0000ff', '#000000'], channel('method'))\n", + "THEO_COLOR = wrap(['#ff00004d', '#00ff004d', '#0000ff4d', '#0000004d'], channel('method'))\n", + "plot(vis.overlay(\n", + " PointCloud(vec(resolution=resolutions, error=errors.time[-1]).resolution.as_spatial().method.as_channel(), color=SIM_COLOR),\n", + " PointCloud(theo_lines.resolution.as_spatial().method.as_channel(), color=THEO_COLOR)),\n", + " log_dims='resolution,error', title=\"Final Error by Resolution\", size=(6, 6))" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "Next, we plot the execution times per resolution and method." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "source": [ + "plot(PointCloud(vec(resolution=resolutions, execution_time=exec_times).resolution.as_spatial().method.as_channel(), color=SIM_COLOR), log_dims='execution_time,resolution', title=\"Execution Time per Step\")" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 824 + }, + "id": "xUbBsVIpGzm7", + "outputId": "09e8747b-a74c-494e-dd3c-4b6b261a7bbb", + "pycharm": { + "name": "#%%\n" + } + }, + "execution_count": 10, + "outputs": [ + { + "data": { + "text/plain": "
" + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
", + "image/png": "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zEmc0aoyyZ+T/LUmfZHaLjw311bKq3bhaRs76P9GERHJjLWW+fPnM27cOP744w+Cg4N5//33+b//+z8FKxGRfJLlT1drbYmZK5CU5ASf3F5b6ULbhZ/dOlztwkVEJC9Ya1m4cCHjxo3j999/p379+rzzzjvcdNNN+Pv7e7s8EZFirVj++io+HtasKZh24ZldX0ntwkVExFt+/vlnnnjiCX799Vfq1q3LG2+8wS233EIp/cckIlIgimXAWrMG2rXLuD2zduHZXYBW7cJFRKSo+PXXXxk3bhw//fQTtWvX5rXXXuO2226jdOnS3i5NRKREKZYBq359eOEFtQsXEZHi7/fff2fcuHEsWLCAmjVr8vLLL3PHHXcQoP/0RES8olgGrGrVYOhQb1chIiKSf/744w/GjRvHDz/8QPXq1XnhhRcYNWoUZcuW9XZpIiIlWrEMWCIiIsVVREQE48aN49tvv6Vq1ao899xz/Pvf/6ZcuXLeLk1ERFDAEhERKRJWrlzJ+PHjmTt3LpUrV2bSpEncddddBAYGers0ERFJQwFLRESkEFuzZg3jx49nzpw5BAUF8dRTT3H33XdToUIFb5cmIiKZUMASEREphNavX8+ECROYOXMmFSpUYPz48dxzzz0EBQV5uzQREcmGApaIiEghsnHjRiZMmIDb7SYwMJDHH3+c++67j0qVKnm7NBERyQEFLBERkUJg8+bNPPnkk3z22WeUKVOGhx9+mAceeIAqVap4uzQREckFBSwREREv+ueff3jqqaf46KOPCAgIYOzYsYwdO5Zq1ap5uzQRETkPClgiIiJesH37diZOnMj//vc//P39uffee3nooYeoUaOGt0sTEZELoIAlIiJSgHbu3MnTTz/NBx98gK+vL3fddRf/+c9/qFWrlrdLExGRPKCAJSIiUgB2797NM888w7vvvosxhlGjRvHwww9Tp04db5cmIiJ5SAFLREQkH+3du5dJkybx9ttvY63l1ltv5ZFHHqFevXreLk1ERPKBApaIiEg+2L9/P5MnT+bNN98kISGBm2++mUcffZQGDRp4uzQREclHClgiIiJ56ODBgzz33HNMmzaNuLg4brzxRh577DEaNWrk7dJERKQAKGCJiIjkgcOHD/P888/z2muvERMTw4gRI3j88cdp0qSJt0sTEZECpIAlIiJyAY4ePcoLL7zAK6+8wpkzZ7j++ut5/PHHad68ubdLExERL1DAEhEROQ/Hjh3jxRdf5KWXXuL06dMMHz6cJ554glatWnm7NBER8SIFLBERkVw4ceIEL730Ei+++CInTpxg6NChjBs3jjZt2ni7NBERKQQUsERERHLg1KlTvPLKK0yZMoXjx49z9dVXM378eNq1a+ft0kREpBBRwBIREcnG6dOnee2113j++ec5evQoAwcOZPz48YSGhnq7NBERKYQUsERERDJx5swZpk2bxnPPPcfhw4e5/PLLGT9+PBdddJG3SxMRkUJMAUtERCSN6Oho3nzzTZ599lkOHjzIpZdeyoQJE+jatau3SxMRkSLAx9sF5CVjzEBjzNsnTpzwdikiIlLExMTE8Morr9CoUSPuv/9+2rZty5IlS/jhhx8UrkREJMeKVcCy1n5trb2jYsWK3i5FRESKiNOnTzNt2jSaNGnCPffcQ/PmzVm8eDELFiygR48e3i5PRESKGE0RFBGREsFay/79+1m1alW625YtW7DW0rNnTz766CP69Onj7VJFRKQIU8ASEZFiJzExkc2bN2cIUwcPHvQc06hRI9q3b8+IESPo1asXvXr1whjjxapFRKQ4UMASEZEi7fTp06xduzZdkFq7di3R0dEAlCpVijZt2nDllVfSvn172rdvT0hICJpOLiIi+UEBS0REioRzTfEDqFSpEh06dGD06NGeMNWiRQv8/f29XL2IiJQUClgiIlLo5GSKX8OGDT1T/FLCVN26dTXNT0REvEoBS0REvConU/xat27NFVdckW6KX1BQkHcLFxERyYQCloiIFJh9+/adc4pf+/btGTVqVLopfqVKlfJy5SIiIjmjgCUiInkuMTGRLVu2ZAhTBw4c8ByTMsXvhhtu8ISpevXqaYqfiIgUaQpYIiJyQc6cOZNhit+aNWs8U/z8/f1p06YNl19+uab4iYhIsaeAJSIiOZZZF7/Nmzdrip+IlEjx8fHs3r2bmJgYb5ci+SggIIC6devmuCOtApaIiGSQmyl+119/vab4iUiJtHv3bsqXL09wcLB+9hVT1lqOHDnC7t27adiwYY7uo4AlIlLCaYqfiMj5iYmJUbgq5owxVKlShUOHDuX4PgpYIiIlyLmm+AUFBdGhQwdN8RMRySGFq+Ivt99jBSwRkWJIU/xERES8QwFLRKSIy8kUv9atW3PZZZd5glS7du00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V87fF8VLFLCkxEpKSmLr1q3p1kutWLGC48ePA86we+vWrbnyyis9bdFDQkIoV66cdwsXERERSeP06dMMGTKEl156iQoVKni2X3PNNQB07NjRs4zhl19+4e677wYgJCSEkJCQDOfbtGkTDRs2pFmzZgDcdNNNvP76656AlRKkNm7cSMOGDWnatCkAI0aM4O233wZg/vz5zJ07lylTpgBOaNu1axcA4eHhxTZcgQKWlBAJCQls3LgxXZBauXIlp0+fBqB06dKEhITgcrnSXbA3ICDAy5WLiIiIZC0+Pp4hQ4Zwww03eAJVitKlSwPg6+vruRQMcM7mWtbabPen/WVzVuey1jJ79myaN2+ebvsff/xR7H9ZrYAlxU5cXBzr169PF6ZWr17tmWNctmxZ2rdvz8iRIz1hqlWrVvj7+3u5chEREZGcs9Zy66230rJlS+6///4c3adXr1588skn9OnTh3Xr1rFmzZoMx7Ro0YIdO3awdetWmjRpwkcffcQll1yS6XHbt2/nn3/+oXHjxnz22Weeff379+fVV1/l1VdfxRjDypUr6dChw/k/2SJEAUuKtOjoaNauXZtuvdTatWuJj48HoEKFCnTo0IFRo0Z52qI3a9bsgjreiIiIiGSmRo28b9Oend9++42PPvqItm3b0r59ewCeeeYZLr/88izvM3r0aG6++WZCQkJo3749nTt3znBMQEAAH3zwAcOGDfM0uRg1alSmx7399ttcccUVVK1alZ49e7Ju3ToAHn/8ce69915CQkKw1hIcHFxi2rebcw0BFkWdOnWyERER3i5D8tjp06fTXbB3xYoVbNiwgcTERAAqV67sCVEpI1ONGjXCx8fHy5WLiIhIcfT333/TsmVLb5chBSCz77UxZrm1NsMFxDSCJYXS8ePHWblyZbowtWnTJs+c4Bo1atCxY0cGDRrkCVP169fXBXtFRERExKsUsMTrDh8+nC5ILV++nG3btnn216tXj9DQUK677jpPmKpdu7YXKxYRERERyZwClhSoffv2pVsvtWLFCiIjIz37GzVqRGhoKLfddhsdO3akQ4cOVKtWzYsVi4iIiIjknAKW5AtrLbt27Uo3MrVixQr2798POC09mzVrxsUXX5zugr2VKlXycuUiIiIiIudPAUsuWFJSEtu2bcsQpo4cOQI4115o1aoV/fv394Spdu3aUb58eS9XLiIiIiKStxSwJFcSExPZvHlzuvVSK1eu5OTJkwD4+/vTtm1bBg8e7AlTISEhlClTxsuVi4iIiIjkPwUsyVJ8fDx///13uvVSq1atIioqCnCufdCuXTtuuOEGT2v01q1bU6pUKS9XLiIiIlLwatasyYE8vBBWjRo1PMsrsvP000/z6aef4uvri4+PD2+99RZdunS5oMfu3r07v//+e4bt48ePJzAwkLFjx17Q+fPTm2++SdmyZbnxxhu98vgKWAJATEwM69atSzfFb82aNcTGxgIQGBhIhw4duP322z0jUy1atMDPT3+FRERERIA8DVc5Pd/SpUuZN28eK1asoHTp0hw+fJi4uLgLfuzMwlV+SUhIyNP3lJldFLkg6d1xCRQVFcXq1avThal169aRkJAAQFBQEKGhoYwZM8YTppo2baoL9oqIiIgUMvv27aNq1aqULl0agKpVqwKwfPly7r//fk6fPk3VqlWZPn06tWrVonfv3nTo0IHly5dz6NAhPvzwQyZNmsTatWtxuVxMnDgRcH65fvr06RzXcfXVVxMZGUlMTAz33HMPd9xxBwDvvfcekydPpnbt2jRt2pTSpUvz2muvMXLkSCpXrszKlSsJDQ3F5XJx7733Eh0dTZkyZfjggw9o3rw506dPZ+7cuURFRfHPP/8wePBgnnvuuWzPnXaU7Z133uHtt98mLi6OJk2a8NFHH1G2bFlGjhxJhQoViIiIYP/+/Tz33HMMHTo0T74nCljF3MmTJ1m1alW61ugbN24kKSkJcP4RduzYkcsuu4zQ0FA6duxIcHCwLtgrIiIiUgRceumlPPnkkzRr1oywsDBcLhfdu3dnzJgxfPXVV1SrVg23282jjz7K+++/D0CpUqX45ZdfePnllxk0aBDLly+ncuXKNG7cmPvuu48qVarkuo7333+fypUrEx0dzUUXXcSQIUOIjY3lqaeeYsWKFZQvX56+ffvSrl07z302b97MggUL8PX15eTJk/zyyy/4+fmxYMECHnnkEWbPng3AqlWrWLlyJaVLl6Z58+aMGTMGX1/fbM+d4pprruH2228H4LHHHuO9995jzJgxgBNOlyxZwsaNG7nqqqsUsCSjI0eOsHLlynQjU1u2bPHsr127NqGhoQwbNswzMlWnTh2FKREREZEiKjAwkOXLl/Prr7/y008/4XK5eOyxx1i3bh3h4eGA06SsVq1anvtcddVVALRt25bWrVt79jVq1IjIyMjzClivvPIKc+bMASAyMpItW7awf/9+LrnkEipXrgzAsGHD2Lx5s+c+w4YNw9fXF4ATJ05w0003sWXLFowxxMfHe47r168fFStWBKBVq1bs3LmTw4cPZ3vuFOvWreOxxx7j+PHjnD59mv79+3v2XX311fj4+NCqVas8nd6pgFVEHThwIENb9B07dnj2BwcHExoayk033URoaCgdOnSgZs2a3itYRERERPKFr68vvXv3pnfv3rRt25bXX3+d1q1bs3Tp0kyPT5lO6OPj4/k85euUJSMpHn30Ub755hvAGUnKzM8//8yCBQtYunQpZcuWpXfv3sTExGCtzbbucuXKeT5//PHH6dOnD3PmzGHHjh307t07Q70pzzUhIeGc504xcuRIvvzyS9q1a8f06dP5+eefMz1vTs+XEwpYhZy1lj179qRri75ixQr27t3rOaZp06Z06dKF0aNHe8LU+fzmQURERESKlk2bNuHj40PTpk0BJwS1bNmS+fPns3TpUrp160Z8fDybN2+mdevWuT7/008/zdNPP53tMSdOnKBSpUqULVuWjRs3smzZMgA6d+7Mfffdx7FjxyhfvjyzZ8+mbdu2WZ6jTp06AEyfPv2cdeX03KdOnaJWrVrEx8fzySefeB4jPylgFSLWWnbs2JGuLfqKFSs4dOgQ4PxWoUWLFvTt29ezXqp9+/ZUqFDBy5WLiIiISI0aNfK8Tfu5nD59mjFjxnD8+HH8/Pxo0qQJb7/9NnfccQd33303J06cICEhgXvvvfe8AlZmJk6cyEsvveT5+p9//uHNN98kJCSE5s2b07VrVwDq1KnDI488QpcuXahduzatWrXyTPU720MPPcRNN93E1KlT6du37zlryOm5n3rqKbp06UKDBg1o27Ytp06dOr8nnQsmL4fDCotOnTrZiIgIb5eRraSkJLZs2ZJhmt/x48cB8PPzo3Xr1p7rS6VcsDftUKqIiIiIeM/ff/9Ny5YtvV1GoXb69GkCAwNJSEhg8ODB3HLLLQwePLjQn/tsmX2vjTHLrbWdzj5WI1gFICEhgY0bN6YLUitXrvS0vixdujQhISG4XC5PmGrTpg0BAQFerlxERERE5PyNHz+eBQsWEBMTw6WXXsrVV19dJM59IRSw8lhcXBzr169Pt15q9erVxMTE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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Combining the previous two figures into one, we can plot the error against the execution time." + ], + "metadata": { + "id": "kffIKZI0zhdI", + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 11, + "outputs": [ + { + "data": { + "text/plain": "
" + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
", + "image/png": "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HDnCV199xahRo1izZg2xsbEcPXrU28acOXN4/PHHOXbsmHd26MjISFq3bs3o0aOZM2cOt912G1dddZX3nLFjxxIZGcmYMWPo06cPXbp0ITk5mT179vD2228zfvx4Vq1axbBhw3j66afZunUrAwcOpEePHvz888+0a9eOt99+u9C/F1u3bk1SUhINGzbk7bffZtKkSRhjSEhI4J133vG+b+5xeX9etWvXrqT/ar4TKPdkSQXVr1+f3r1707t3b++23HW98vZ6paSkMH/+fDIyMopsxxjjDVwREREYY9i9ezfGGIwxREdH8+677/L1119jraVjx44EBQXRunVr/u///s97Xt42cr8vuM0f95OJiIiIuGHz5s00atSIUaNGsWLFCrp168bkyZMZOnQoL774IpMmTSIx8fgEeA0bNmTZsmW8/PLLTJo0iTfffDNfey+99BIAq1atYt26dQwYMIANGzYAsGjRIlauXEn9+vV57rnniIiIYOXKlaxcudL7i/m9e/fy9NNPM3fuXOrUqcOECRN47rnneOyxxwBnzakffvih1OsKCwtjwYIFTJ48mcGDB5OcnEz9+vU57bTTuPvuuwFYv349b731FmeddRajR4/m5ZdfZsyYMUW2t3r1asaNG8ePP/5Iw4YN2bdvX779xf28Ap3+lVuNGWNo2bIlLVu25IILLvBuz8rKYuPGjaxfv57Dhw9z9OhR0tLSvF9L+/7nn3+mXr16HD161Hv+rl27+Omnn8pVX2hoaJkDWXnCW8Hvw8PDNZxRKuzAgQNcf/31pKSkYIxh6tSp9OrVC4BJkyZx7733smfPHi0cLiIigPPvrWXLljFlyhR69OjBnXfeyTPPPMNTTz1V5PG5t4d069aNzz77rND+H374gdtvvx2AmJgYTjnlFG/I6t+/P/Xr1wdgwYIF3HHHHQAkJCSQkJAAwOLFi1mzZo13AraMjAzv32NAmScxu+SSSwCIj4+nY8eONGvWDIBTTz2V7du3ExUVRcuWLb3vc/XVV/PCCy8UG7Lmz5/P0KFDvX9/5l5HVaeQVQOFhIQQGxtbaE2BE2WtJT09vVxBrbT9v//+O9u3by+0PTMz84RqrOwgl/t9IK44Lr5R3Lj67du3880339CqVSuXKxQRkUASHR1NdHS0d1bmoUOH8swzzxR7fK1atQDnvvysrKxC+0ua2KxOnTr5Xhf1y2VrLf379+eDDz4oUxul1RkUFOT9Pvd1bt0F37+kX3Zba6vlL8MVsqTCcocY+mN8bFZWls+CXO73+/btK/K8E5mlMSQkpNKDXG7vXFBQoEwOWv0VN64e4O6772bixIkMHjzYxQpFRCTQNG3alJYtW7J+/Xrat2/PvHnz6NChAwB169bl0KFD5Wqvd+/evPfee/Tt25cNGzawbds22rdvz7Jly4o87txzzyUlJYWVK1cCzrqmt956K5s2baJt27akpaWRmppKu3btfHPBeWzbto1FixbRq1cvPvjgA84+++xij+3Xrx+XXXYZd999Nw0aNGDfvn2FerNO5OflNoUsqVJCQkKoW7cudevWrdT3sdZy7Ngxn/bO7d+/n19//bXQ9uLujytNeHh4pQa5vL1z1fE3TOVR3Lj6efPm0aJFCzp16uR2iSIiUpomTXw7w2CTJqUeMmXKFEaMGEFGRgannnoq06ZNA2DkyJHcdNNN+Sa+KM0tt9zCTTfdRHx8PCEhIUyfPj1fT1Kum2++mVGjRpGQkEDnzp3p3r07AI0aNWL69OkMHz6cY8eOAfD0009XSsiKjY1lxowZ3HjjjZx++uncfPPNxR7bsWNHHn74Yc455xyCg4Pp0qWL95eauQr+vKrCxBfG12sqBaLExERbkfUBRCpTdnZ2uYJceXvq8n5/Iv+/BwcHV1qQK7gtUHvnkpKS6NmzJz/++KN3XH3ujb9z5syhXr16+WZJEhER961du9Znt0ZI2W3dupVBgwblW1e1MhX139kYk2ytdXWWDPVkibgsODiYyMhIIiMjK/V9rLXee4l8Fer++OMPdu7cWWh77m/IyqtWrVqV1iOX92tYWFi5eueKGlc/duxYtmzZ4u3FSk1NpWvXrixZsoSmZVigUkRERKovhSyRGsIYQ61atahVq1aFF6YuTW7v3In0uBW3/7fffitye05OTrnrCwoKyhe8atWqRXh4eKGveb8/duwYI0eOpFmzZixatIicnBweeugh7/777ruPJ598kuTk5ELnFvW1vEFPRESkKmjdurXferECmUKWiPicP3vnMjMzK9w7d+zYMdLT071fc++hK7j9nXfeyRfqFi5cmK+eUaNGlav+soS7E/1a1mMV9kQKGz16NLNmzaJx48befyzee++9fPnll4SFhXkXcY2KiiIzM5Prr7+eZcuWkZWVxTXXXMODDz7o8hWIiNsUskSkyjLGeGf6i4qK8tv75uTkFApgZf1anmNzZ78s7twTXdKgIDdDnsKeBKKRI0dy2223cc0113i39e/fn/HjxxMSEsL999/P+PHjmTBhAp988gnHjh1j1apVpKWl0aFDB4YPH07r1q3duwARcZ1ClohIOeUON3R7dqMTCXsnEgxzw15xx/g67LkR8hT2JK/evXuzdevWfNsGDBjg/b5nz57MnDkTcH7Zc+TIEe8SI2FhYZx00kn+LFdEApBClohIFVWVwt6JhLuCX48cOeKXsOdmyMs9V0snBLapU6cybNgwwJkI5/PPP6dZs2akpaXx/PPPF1rjR0RqHoUsERGpkKoQ9nwR8nK/D+Sw5+uAqLBX2Lhx4wgJCWHEiBEALFmyhODgYHbs2MH+/fv505/+xHnnncepp57qcqVSUFOasgvfrZPVhCb8xm+lHpednU1iYiItWrRg1qxZAEyfPp0BAwbQvHlzgEpdBqRPnz5MmjSJxERXZzSvcRSyRESkWgjksOfLkBfIYa+yegEDJezNmDGDWbNmMW/ePG8977//PgMHDiQ0NJTGjRtz1llnkZSUpJAVgHwZsMrT3uTJk4mNjeXgwYPebdOnTycuLs4bsnwlKyuLkJCK/fM+Ozub4OBgH1VUcylkiYiI+FAghr3KCHmBGPbKGub69etHfHx8ud5/9uzZTJgwge+//56IiAjv9latWjF//nyuvvpq0tLSWLx4MXfddZdPrlmqvtTUVL766isefvhhnnvuOQBmzpxJUlISI0aMoHbt2ixatAiAKVOm8OWXX5KZmcknn3xCTExMvrbS09O5+eabSUpKIiQkhOeee45zzz2X6dOn89VXX3mHVX/11VeMGjWKNWvWEBsby9GjR71tzJkzh8cff5xjx455Z8mMjIykdevWjB49mjlz5nDbbbdx1VVX+e+HVE0pZImIiFRDgRL2srOzOXbsWKWFvBMJe6+99lqJIWv48OF899137N27l+joaJ544gnGjx/PsWPH6N+/P+BMfvHqq69y6623MmrUKOLi4rDWMmrUKBISEir95ypVw1133cXEiRM5dOiQd9vQoUN58cUXCw3ha9iwIcuWLePll19m0qRJvPnmm/naeumllwBYtWoV69atY8CAAWzYsAGARYsWsXLlSurXr89zzz1HREQEK1euZOXKlXTt2hWAvXv38vTTTzN37lzq1KnDhAkTeO6553jssccA5xcZP/zwQ6X+PGoShSypUp5//nnefPNNjDHEx8czbdo0wsPD3S5LRESKERwcTERERL7eHzfkDXul/b3xwQcfFNp23XXXFXlsZGQkn3zyiU9qlOold621bt268d1335V6/JAhQwDo1q0bn332WaH9P/zwA7fffjsAMTExnHLKKd6Q1b9/f++EKwsWLOCOO+4AICEhwRv6Fy9ezJo1azjrrLMAyMjIoFevXt72cydzEd9QyJIq49dff+WFF15gzZo11K5dmyuvvJIPP/yQkSNHul2aiIgEuEAJe1Jz/Pjjj3zxxRd8/fXXpKenc/DgQa6++mrefffdIo+vVasW4HxWs7KyCu231hb7XnXq1Mn3uqh7GK219O/fv8hfIhTVhlRMkNsFiJRH7jokWVlZpKWl+fyGURERERFfGD9+PKmpqWzdupUPP/yQvn37egNW3bp18w0hLIvevXvz3nvvAbBhwwa2bdtG+/btSzwuJSWFlStXAs4Q1x9//JFNmzYBkJaW5u0JE99TyJIqo0WLFowZM4ZWrVrRrFkz6tWrl29xSBEREZHiNKFJwLQ3cuRIbrrpJjp37pxvYoqS3HLLLWRnZxMfH8+wYcOYPn26t/crr5tvvpnDhw+TkJDAxIkT6d69OwCNGjVi+vTpDB8+nISEBHr27Mm6detO+BqkZKakrsfqIjEx0SYlJbldhlTQ/v37ufzyy/noo4+IioriiiuuYOjQoVx99dVulyYiIiIBZu3atcTGxrpdhlSyov47G2OSrbWuLgymniwJCNnZUFrenzt3Lm3atKFRo0aEhoYyZMgQ/vvf//qnQBERERGRMlLIkoDw/PPQrx9s3Fj8Ma1atWLx4sWkpaVhrWXevHn6DZWIiIiIBByFLAkIDRvCsmUQHw9/+xsUtXZljx49GDp0KF27diU+Pp6cnBxuuOEG/xcrIiIiIlIC3ZMlAWPnTrjjDpg5E+Li4I03oGdPt6sSERGRqkj3ZNUMuidLpBTNmsEnn8Dnn8OBA3DmmXD77XDwoNuViYiIiIiUnUKWBJxLLoE1a+C22+Cll6BDB/jiC7erEhEREREpG4UsCUh168ILL8CiRVC/PgweDEOHOkMKRURERMqraVMwxnfPpk1Lfr/t27dz7rnnEhsbS8eOHZk8eXK5a+7Tpw++uuXlu+++Y9CgQT5pS0qnkCUBrUcPSE52JsOYNQtiY+G11yAnx+3KREREpCrZtcu/7YWEhPDss8+ydu1aFi9ezEsvvcSaNWt8W0QJsrOzK3R+VlaWjyqpmRSyJOCFhsKDD8KqVdCtG9x0E5xzDqxd63ZlIiIiIkVr1qwZXbt2BaBu3brExsby66+/Ak4P1f3330/37t1p164dCxcuBODo0aNcddVVJCQkMGzYMI4ePVpk2/PmzaNLly7Ex8czevRojh07BkDr1q158sknOfvss/nkk0+YPXs2MTExnH322Xz22Wfe848cOcLo0aM544wz6NKlC59//jkA06dP54orruDiiy9mwIABlfazqQkUsqTKOP10mDsXpk937tnq1AnGjgXPnysiIiIiAWnr1q38/PPP9OjRw7stKyuLJUuW8I9//IMnnngCgFdeeYWIiAhWrlzJww8/THJycqG20tPTGTlyJB999BGrVq0iKyuLV155xbs/PDycH374gUsvvZS//OUvfPnllyxcuJDffvvNe8y4cePo27cvS5cu5dtvv+Xee+/lyJEjACxatIgZM2Ywf/78yvpx1AgKWVKlGAPXXuv0Yl15JTzxBHTuDJ5fAFXY6NGjady4MXFxcfm2T5kyhfbt29OxY0fuu+8+37yZiIiIVHuHDx/m8ssv5x//+AcnnXSSd/uQIUMA6NatG1u3bgVgwYIFXH311QAkJCSQkJBQqL3169fTpk0b2rVrB8C1117LggULvPuHDRsGwLp162jTpg2nn346xhhvuwBz5szhmWeeoXPnzvTp04f09HS2bdsGQP/+/alfv74PfwI1k0KWVEmNG8O778Ls2ZCeDr17w403OlO/V8TIkSOZPXt2vm3ffvstn3/+OStXrmT16tWMGTOmYm8iIiIiNUJmZiaXX345I0aM8IaqXLVq1QIgODg43/1PxpgS2yxtjds6deqU2pa1lk8//ZTly5ezfPlytm3b5l1rKu/5cuIUsqRKO/98SEmBMWPgzTediTE++QROdI3t3r17F/rtzSuvvMIDDzzg/cOwcePGFS1bREREqjlrLddddx2xsbHcc889ZTqnd+/evPfeewCkpKSwcuXKQsfExMSwdetWNm3aBMA777zDOeecU+RxW7Zs4ZdffgHggw8+8O47//zzmTJlijew/fzzz+W7OCmVQpZU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88/37rv00ksJCgqiQ4cOPh/m6S+B0pO1FDjdGNPGGBMGXAV84XJNIkVq0aIFY8aMoVWrVjRr1ox69epx4YUDOOccZyjh+vXO89lnnZ6tiRPhzDOhWTMYOdKZTOPgwcqtsVatWsyfP58VK1awfPlyZs+ezeLFi+nfvz8pKSmsXLmSdu3aMX78+MotREREpAYLDg6mT58+PPHEE7z44ot8+umndOzYkeXLl7N8+XJWrVrFnDlzvMfnDi0MCgryfp/7OisrK1/bDz/8MJ07d6Zz587Fvv93333H3LlzWbRoEStWrKBLly6kp6dT2jq5derU8X7/6KOPcu6555KSksKXX35Jenp6oXpzrzUrK6vUtnONHDmSF198kVWrVvH4448X225VXdPXjSncPwAWAe2NManGmOustVnAbcB/gLXAx9ba1f6uTaQs9u/fz+eff86WLVvYsWMHR44c4d133813TLt2cM898O23ziLIH3wA/fvDF1/A0KFO+BowAF54AbZs8X2NxhjvmO3MzEwyMzMxxjBgwADvDaw9e/YkNTXV928uIiIirF+/no0bN3pfL1++nNjYWPbs2cOiRYsA5+/o1atP7J+848aN84a14vzxxx+cfPLJREREsG7dOhYvXgxA9+7d+f7779m/fz9ZWVl8+umnJbbRooUzVcL06dNLrausbR86dIhmzZqRmZnJe++9V2q7VY3fQ5a1dri1tpm1NtRaG22tfcuz/WtrbTtr7WnW2nH+rksEYB/7OEjJ3Uxz586lTZs2NGrUiNDQUIYMGVLiTD9RUXDVVfDee7B7N3z/Pdx1F2zfDnfeCaeeCh07wgMPwI8/Qna2b64lOzubzp0707hxY/r370+PHj3y7Z86dSoXXHCBb95MREQkwDVp0sSv7R0+fJhrr72WDh06kJCQwJo1a3jyySeZOXMm999/P506daJz584+nS3w6aefJjo62vscOHAgWVlZJCQk8Oijj9KzZ0/AGZXz0EMP0aNHD8477zw6dOjgHfZX0H333ceDDz7IWWedRXYZ/pFS1rafeuopevToQf/+/YmJianYhQcgU1W74MojMTHRJiUluV2GBLhjHCOBBDrSkU/5FFPkfCzw008/MXr0aJYuXUrt2rUZOXIkiYmJ3H777eV+z02bYNYs5z6uBQsgKwsaNIALL3Tu4xowAIr5M6/MDhw4wGWX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Te0yBbImNCGKKFcC2bhx4wgJCWGE537OJUuWEBwczI4dO9i/fz9/+tOfOO+88zj11FP9XltlUsgSERGp6TIyYMwYmDIFzjoLPv4Ymjd3uyqRaq02tTnF8yhNJpmlLhC9la0sYQm72U0OOYXaCCW0UAArLpA1oIFPFoieMWMGs2bNYt68eRhPj/j777/PwIEDCQ0NpXHjxpx11lkkJSUpZImIiEg1kpoKV14JixbBXXfBxIkQWr1v0hepakIJpbnnUZoccvid38s0/f1udpNBRqE2ctc/a0ITruEaxjCm3DXPnj2bCRMm8P333xMREeHd3qpVK+bPn8/VV19NWloaixcv5q677ip3+4FOIUtERKSmmj8frroK0tLgo4+csCUiVVoQQTTyPOIoeUZQi+UP/igxkJVlQefhw4fz3XffsXfvXqKjo3niiScYP348x44do3///oAz+cWrr77KrbfeyqhRo4iLi8Nay6hRo0hISPDJtQcSTeEuIiJS01jr9Fg99BC0aweffQaxJd/ULyJSVWgKdxEREfGvP/6AkSPhX/9yeq7efNOZ6EJERHxGIUtERKSmWLnSmZ5961Z4/nm4805Nzy4iUgkUskRERGqCd9+FG26AqCj49ls4+2y3KxIRqbaC3C5AREREKtGxY3DLLfDnP0P37rBsmQKWiEglU8gSERGprrZtg9694ZVXnHWw5s6Fpk3drkpEpNpTyBIREamOvvkGunYlfc0aurdtS6c5c+jYqROPP/44APfeey8xMTEkJCRw2WWXceDAAXfrFRGpRhSyREREqpOcHBg3Ds4/H5o2pdbSpcz/+WdWrFjB8uXLmT17NosXL6Z///6kpKSwcuVK2rVrx/jx492uXESk2lDIEhERqS7274fBg+GRR2D4cPjpJ0xMDJGRkQBkZmaSmZmJMYYBAwYQEuLMf9WzZ09SU1PdrFxEpFpRyBIREakOfv4ZunWD//wHpkxxZhOsUweA7OxsOnfuTOPGjenfvz89evTId+rUqVO54IIL3KhaRKRaUsgSERGp6qZNgzPPhIwM+P57uO22fOtfBQcHs3z5clJTU1myZAkpKSnefePGjSMkJIQRI0a4UbmISLWkkCUiIlJVpac7a1+NHu2ErGXLoFevYg+PioqiT58+zJ49G4AZM2Ywa9Ys3nvvPYwWJRYR8RmFLBERkapo925nvas3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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot(PointCloud(vec(time=exec_times, error=errors.time[-1]).resolution.as_spatial().method.as_channel(), color=SIM_COLOR), log_dims='error', title=\"Error vs Performance\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "As expected, higher orders are more expensive but yield better accuracy.\n", + "When computation time is limited, the highest order of accuracy is not always preferable, especially since the computational cost of the implicit 6th-order scheme is approximately constant below a resolution of 32." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file From 679e630498ae4e6b09ca53a421cf2fce3c15787f Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 18 Jan 2023 18:09:44 +0100 Subject: [PATCH 071/170] =?UTF-8?q?[=CE=A6]=20Bump=20version=20to=202.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phi/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phi/VERSION b/phi/VERSION index b539adea5..cc6612c36 100644 --- a/phi/VERSION +++ b/phi/VERSION @@ -1 +1 @@ -2.2.7 \ No newline at end of file +2.3.0 \ No newline at end of file From 0478c2256b0a7c8ba005736d5ce8a5915445e80d Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 18 Jan 2023 21:29:02 +0100 Subject: [PATCH 072/170] [math] Use correct precision for single-arg ops This affects trigonometric functions, round, ceil, floor, sqrt, exp and more --- phi/math/_ops.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 9975f2e03..f677abc31 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -1597,14 +1597,15 @@ def dot(x: Tensor, def _backend_op1(x, unbound_method) -> Tensor or PhiTreeNode: if isinstance(x, Tensor): def apply_op(native_tensor): - return getattr(choose_backend(native_tensor), unbound_method.__name__)(native_tensor) + backend = choose_backend(native_tensor) + return getattr(backend, unbound_method.__name__)(backend.auto_cast(native_tensor)[0]) apply_op.__name__ = unbound_method.__name__ return x._op1(apply_op) elif isinstance(x, PhiTreeNode): return copy_with(x, **{a: _backend_op1(getattr(x, a), unbound_method) for a in value_attributes(x)}) else: backend = choose_backend(x) - y = getattr(backend, unbound_method.__name__)(x) + y = getattr(backend, unbound_method.__name__)(backend.auto_cast(x)[0]) return y From 6cd70556ddeaac5de94addf666e45e1b30a29f1e Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Thu, 19 Jan 2023 13:32:00 +0100 Subject: [PATCH 073/170] [math] Dual dimensions Dual dimensions always have the prefix '~' and are of type DUAL_DIM. * Add Shape.dual, non_dual * Add dual(), non_dual() * Add BoundDim.as_dual() * Add BoundDim.dual to reference dual dimensions --- phi/flow.py | 3 +- phi/math/__init__.py | 4 +- phi/math/_shape.py | 146 +++++++++++++++++++++++++---- phi/math/magic.py | 19 +++- tests/commit/math/test__shape.py | 13 ++- tests/commit/math/test__tensors.py | 3 + 6 files changed, 159 insertions(+), 29 deletions(-) diff --git a/phi/flow.py b/phi/flow.py index 55536d7a2..3db611944 100644 --- a/phi/flow.py +++ b/phi/flow.py @@ -30,7 +30,8 @@ # Functions from .math import ( wrap, tensor, vec, # Tensor creation - shape, spatial, channel, batch, instance, non_spatial, non_channel, non_batch, non_instance, # Shape functions (magic) + shape, spatial, channel, batch, instance, dual, + non_spatial, non_channel, non_batch, non_instance, non_dual, # Shape functions (magic) unstack, stack, concat, expand, rename_dims, pack_dims, unpack_dim, flatten, cast, # Magic Ops jit_compile, jit_compile_linear, minimize, functional_gradient, solve_linear, solve_nonlinear, iterate, # jacobian, hessian, custom_gradient # Functional magic ) diff --git a/phi/math/__init__.py b/phi/math/__init__.py index bf14256a2..1a41889b7 100644 --- a/phi/math/__init__.py +++ b/phi/math/__init__.py @@ -18,8 +18,8 @@ from ._shape import ( shape, Shape, EMPTY_SHAPE, DimFilter, - spatial, channel, batch, instance, - non_batch, non_spatial, non_instance, non_channel, + spatial, channel, batch, instance, dual, + non_batch, non_spatial, non_instance, non_channel, non_dual, merge_shapes, concat_shapes, IncompatibleShapes ) from ._magic_ops import unstack, stack, concat, expand, rename_dims, pack_dims, unpack_dim, unpack_dim as unpack_dims, flatten, copy_with diff --git a/phi/math/_shape.py b/phi/math/_shape.py index b9fb90b0f..2395a0da7 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -1,3 +1,4 @@ +import re import warnings from numbers import Number from typing import Tuple, Callable, List, Union, Any @@ -8,19 +9,20 @@ SPATIAL_DIM = 'spatial' CHANNEL_DIM = 'channel' INSTANCE_DIM = 'înstance' -TYPE_ABBR = {SPATIAL_DIM: "ˢ", CHANNEL_DIM: "ᶜ", INSTANCE_DIM: "ⁱ", BATCH_DIM: "ᵇ", None: "⁻"} # ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻ +DUAL_DIM = 'dual' +TYPE_ABBR = {SPATIAL_DIM: "ˢ", CHANNEL_DIM: "ᶜ", INSTANCE_DIM: "ⁱ", BATCH_DIM: "ᵇ", DUAL_DIM: "ᵈ", None: "⁻"} # ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻ class Shape: """ Shapes enumerate dimensions, each consisting of a name, size and type. - There are four types of dimensions: `batch`, `spatial`, `channel`, and `instance`. + There are five types of dimensions: `batch`, `dual`, `spatial`, `channel`, and `instance`. """ def __init__(self, sizes: tuple, names: tuple, types: tuple, item_names: tuple): """ - To construct a `Shape`, use `batch`, `spatial`, `channel` or `instance`, depending on the desired dimension type. + To construct a `Shape`, use `batch`, `dual`, `spatial`, `channel` or `instance`, depending on the desired dimension type. To create a shape with multiple types, use `merge_shapes()`, `concat_shapes()` or the syntax `shape1 & shape2`. The `__init__` constructor is for internal use only. @@ -259,7 +261,7 @@ def batch(self) -> 'Shape': Filters this shape, returning only the batch dimensions as a new `Shape` object. See also: - `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`. + `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.dual`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`, `Shape.non_dual`. Returns: New `Shape` object @@ -272,7 +274,7 @@ def non_batch(self) -> 'Shape': Filters this shape, returning only the non-batch dimensions as a new `Shape` object. See also: - `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`. + `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.dual`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`, `Shape.non_dual`. Returns: New `Shape` object @@ -285,7 +287,7 @@ def spatial(self) -> 'Shape': Filters this shape, returning only the spatial dimensions as a new `Shape` object. See also: - `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`. + `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.dual`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`, `Shape.non_dual`. Returns: New `Shape` object @@ -298,7 +300,7 @@ def non_spatial(self) -> 'Shape': Filters this shape, returning only the non-spatial dimensions as a new `Shape` object. See also: - `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`. + `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.dual`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`, `Shape.non_dual`. Returns: New `Shape` object @@ -311,7 +313,7 @@ def instance(self) -> 'Shape': Filters this shape, returning only the instance dimensions as a new `Shape` object. See also: - `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`. + `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.dual`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`, `Shape.non_dual`. Returns: New `Shape` object @@ -324,7 +326,7 @@ def non_instance(self) -> 'Shape': Filters this shape, returning only the non-instance dimensions as a new `Shape` object. See also: - `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`. + `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.dual`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`, `Shape.non_dual`. Returns: New `Shape` object @@ -337,7 +339,7 @@ def channel(self) -> 'Shape': Filters this shape, returning only the channel dimensions as a new `Shape` object. See also: - `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`. + `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.dual`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`, `Shape.non_dual`. Returns: New `Shape` object @@ -350,13 +352,39 @@ def non_channel(self) -> 'Shape': Filters this shape, returning only the non-channel dimensions as a new `Shape` object. See also: - `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`. + `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.dual`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`, `Shape.non_dual`. Returns: New `Shape` object """ return self[[i for i, t in enumerate(self.types) if t != CHANNEL_DIM]] + @property + def dual(self) -> 'Shape': + """ + Filters this shape, returning only the dual dimensions as a new `Shape` object. + + See also: + `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.dual`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`, `Shape.non_dual`. + + Returns: + New `Shape` object + """ + return self[[i for i, t in enumerate(self.types) if t == DUAL_DIM]] + + @property + def non_dual(self) -> 'Shape': + """ + Filters this shape, returning only the non-dual dimensions as a new `Shape` object. + + See also: + `Shape.batch`, `Shape.spatial`, `Shape.instance`, `Shape.channel`, `Shape.dual`, `Shape.non_batch`, `Shape.non_spatial`, `Shape.non_instance`, `Shape.non_channel`, `Shape.non_dual`. + + Returns: + New `Shape` object + """ + return self[[i for i, t in enumerate(self.types) if t != DUAL_DIM]] + @property def non_singleton(self) -> 'Shape': """ @@ -1135,16 +1163,16 @@ def parse_dim_order(order: str or tuple or list or Shape or None, check_rank: in raise ValueError(order) -def _construct_shape(dim_type: str, *args, **dims): +def _construct_shape(dim_type: str, prefix: str, *args, **dims): sizes = () - names = () + names = [] item_names = () for arg in args: parts = [s.strip() for s in arg.split(',')] for name in parts: assert name not in names, f"Duplicate dimension name {name}" sizes += (None,) - names += (name,) + names.append(name) item_names += (None,) for name, size in dims.items(): assert name not in names, f"Duplicate dimension name {name}" @@ -1170,12 +1198,20 @@ def _construct_shape(dim_type: str, *args, **dims): size = int(size) except ValueError: raise ValueError(f"Cannot construct dimension from {type(size).__name__}. Only int, tuple, list, str or Shape allowed. Got {size}") - names += (name,) + names.append(name) sizes += (size,) item_names += (items,) + names = tuple(_apply_prefix(name, prefix) for name in names) return math.Shape(sizes, names, (dim_type,) * len(sizes), item_names) +def _apply_prefix(name: str, prefix: str): + match = re.search("\\w", name) + assert match, f"Dimension name must contain at least one letter or underscore but got '{name}'" + proper_name_index = match.start() + return prefix + name[proper_name_index:] + + def shape(obj) -> Shape: """ If `obj` is a `Tensor` or `phi.math.magic.Shaped`, returns its shape. @@ -1256,7 +1292,7 @@ def spatial(*args, **dims: int or str or tuple or list or Shape) -> Shape: """ from .magic import Shaped if all(isinstance(arg, str) for arg in args) or dims: - return _construct_shape(SPATIAL_DIM, *args, **dims) + return _construct_shape(SPATIAL_DIM, '', *args, **dims) elif len(args) == 1 and isinstance(args[0], Shape): return args[0].spatial elif len(args) == 1 and isinstance(args[0], Shaped): @@ -1301,7 +1337,7 @@ def channel(*args, **dims: int or str or tuple or list or Shape) -> Shape: """ from .magic import Shaped if all(isinstance(arg, str) for arg in args) or dims: - return _construct_shape(CHANNEL_DIM, *args, **dims) + return _construct_shape(CHANNEL_DIM, '', *args, **dims) elif len(args) == 1 and isinstance(args[0], Shape): return args[0].channel elif len(args) == 1 and isinstance(args[0], Shaped): @@ -1346,7 +1382,7 @@ def batch(*args, **dims: int or str or tuple or list or Shape) -> Shape: """ from .magic import Shaped if all(isinstance(arg, str) for arg in args) or dims: - return _construct_shape(BATCH_DIM, *args, **dims) + return _construct_shape(BATCH_DIM, '', *args, **dims) elif len(args) == 1 and isinstance(args[0], Shape): return args[0].batch elif len(args) == 1 and isinstance(args[0], Shaped): @@ -1391,7 +1427,7 @@ def instance(*args, **dims: int or str or tuple or list or Shape) -> Shape: """ from .magic import Shaped if all(isinstance(arg, str) for arg in args) or dims: - return _construct_shape(INSTANCE_DIM, *args, **dims) + return _construct_shape(INSTANCE_DIM, '', *args, **dims) elif len(args) == 1 and isinstance(args[0], Shape): return args[0].instance elif len(args) == 1 and isinstance(args[0], Shaped): @@ -1400,7 +1436,58 @@ def instance(*args, **dims: int or str or tuple or list or Shape) -> Shape: raise AssertionError(f"instance() must be called either as a selector instance(Shape) or instance(Tensor) or as a constructor instance(*names, **dims). Got *args={args}, **dims={dims}") -def merge_shapes(*objs: Shape or Any, order=(batch, instance, spatial, channel)): +def dual(*args, **dims: int or str or tuple or list or Shape) -> Shape: + """ + Returns the dual dimensions of an existing `Shape` or creates a new `Shape` with only dual dimensions. + + Dual dimensions are assigned the prefix `~` to distinguish them from regular dimensions. + This way, a regular and dual dimension of the same name can exist in one `Shape`. + + Dual dimensions represent the input space and are typically only present on matrices or higher-order matrices. + Dual dimensions behave like batch dimensions in regular operations, if supported. + During matrix multiplication, they are matched against their regular counterparts by name (ignoring the `~` prefix). + + Usage for filtering dual dimensions: + + >>> dual_dims = dual(shape) + >>> dual_dims = dual(tensor) + + Usage for creating a `Shape` with only dual dimensions: + + >>> dual('undef', points=2) + (~undefᵈ=None, ~pointsᵈ=2) + + Here, the dimension `undef` is created with an undefined size of `None`. + Undefined sizes are automatically filled in by `tensor`, `wrap`, `stack` and `concat`. + + To create a shape with multiple types, use `merge_shapes()`, `concat_shapes()` or the syntax `shape1 & shape2`. + + See Also: + `channel`, `batch`, `spatial` + + Args: + *args: Either + + * `Shape` or `Tensor` to filter or + * Names of dimensions with undefined sizes as `str`. + + **dims: Dimension sizes and names. Must be empty when used as a filter operation. + + Returns: + `Shape` containing only dimensions of type dual. + """ + from .magic import Shaped + if all(isinstance(arg, str) for arg in args) or dims: + return _construct_shape(DUAL_DIM, '~', *args, **dims) + elif len(args) == 1 and isinstance(args[0], Shape): + return args[0].dual + elif len(args) == 1 and isinstance(args[0], Shaped): + return shape(args[0]).dual + else: + raise AssertionError(f"dual() must be called either as a selector dual(Shape) or dual(Tensor) or as a constructor dual(*names, **dims). Got *args={args}, **dims={dims}") + + +def merge_shapes(*objs: Shape or Any, order=(batch, dual, instance, spatial, channel)): """ Combines `shapes` into a single `Shape`, grouping dimensions by type. If dimensions with equal names are present in multiple shapes, their types and sizes must match. @@ -1524,6 +1611,25 @@ def non_channel(obj) -> Shape: raise AssertionError(f"non_channel() must be called either on a Shape or an object with a 'shape' property but got {obj}") +def non_dual(obj) -> Shape: + """ + Returns the non-dual dimensions of an object. + + Args: + obj: `Shape` or object with a valid `shape` property. + + Returns: + `Shape` + """ + from .magic import Shaped + if isinstance(obj, Shape): + return obj.non_dual + elif isinstance(obj, Shaped): + return shape(obj).non_dual + else: + raise AssertionError(f"non_dual() must be called either on a Shape or an object with a 'shape' property but got {obj}") + + def _size_equal(s1, s2): if s1 is None: diff --git a/phi/math/magic.py b/phi/math/magic.py index 95b832b65..27e5652eb 100644 --- a/phi/math/magic.py +++ b/phi/math/magic.py @@ -18,7 +18,7 @@ import warnings from typing import Tuple, Callable -from ._shape import Shape, shape, channel, non_batch, batch, spatial, instance, concat_shapes +from ._shape import Shape, shape, channel, non_batch, batch, spatial, instance, concat_shapes, dual from .backend._dtype import DType @@ -451,6 +451,10 @@ def __init__(self, obj, name: str): self.obj = obj self.name = name + @property + def dual(self): + return BoundDim(self.obj, '~' + self.name) + @property def exists(self): """ Whether the dimension is listed in the `Shape` of the object. """ @@ -575,6 +579,10 @@ def as_instance(self, name: str = None): """ Returns a shallow copy of the `Tensor` where the type of this dimension is *instance*. """ return self.retype(instance) if name is None else self.replace(instance(name=self.item_names or self.size)) + def as_dual(self, name: str = None): + """ Returns a shallow copy of the `Tensor` where the type of this dimension is *instance*. """ + return self.retype(dual) if name is None else self.replace(dual(name=self.item_names or self.size)) + def replace(self, dim: Shape, **kwargs): """ Returns a shallow copy of the `Tensor` where this dimension has been replaced by `dim`. @@ -602,6 +610,11 @@ def __init__(self, obj, dims: Tuple[str, ...]): self.obj = obj self.dims = dims + @property + def dual(self): + last_dual = "~" + self.dims[-1] + return _BoundDims(self.obj, self.dims[:-1] + (last_dual,)) + def __getitem__(self, item): assert isinstance(item, tuple), f"A tuple of slices is required for slicing multiple dimensions at once but got {type(item)}" assert len(item) == len(self.dims), f"Number of slices must equal number of dimensions but got {len(item)} for dims {self.dims}" @@ -661,6 +674,10 @@ def as_instance(self): """ Returns a shallow copy of the `Tensor` where the type of this dimension is *instance*. """ return self.retype(instance) + def as_dual(self): + """ Returns a shallow copy of the `Tensor` where the type of this dimension is *instance*. """ + return self.retype(dual) + def slicing_dict(obj, item) -> dict: """ diff --git a/tests/commit/math/test__shape.py b/tests/commit/math/test__shape.py index 1701a6a98..53c2604bb 100644 --- a/tests/commit/math/test__shape.py +++ b/tests/commit/math/test__shape.py @@ -2,7 +2,7 @@ from phi import math from phi.math import spatial, channel, batch, instance, non_instance, non_channel, non_spatial, non_batch -from phi.math._shape import shape_stack, vector_add, EMPTY_SHAPE, Shape +from phi.math._shape import shape_stack, vector_add, EMPTY_SHAPE, Shape, dual class ShapedDummy: @@ -13,12 +13,12 @@ def __init__(self, shape: Shape): class TestShape(TestCase): def test_dimension_types(self): - v = math.ones(batch(batch=10) & spatial(x=4, y=3) & channel(vector=2)) - self.assertEqual(v.x.index, 1) + v = math.ones(batch(batch=10) & spatial(x=4, y=3) & channel(vector=2) & dual(d=1)) + self.assertEqual(v.x.index, 2) self.assertEqual(v.x.name, 'x') - self.assertEqual(('batch', 'spatial', 'spatial', 'channel'), v.shape.types) + self.assertEqual(('batch', 'dual', 'spatial', 'spatial', 'channel'), v.shape.types) b = v.x.as_batch() - self.assertEqual(('batch', 'batch', 'spatial', 'channel'), b.shape.types) + self.assertEqual(('batch', 'dual', 'batch', 'spatial', 'channel'), b.shape.types) def test_combine(self): self.assertEqual(batch(batch=10) & spatial(y=4, x=3) & channel(vector=2), batch(batch=10) & channel(vector=2) & spatial(y=4, x=3)) @@ -143,3 +143,6 @@ def test_with_size_item_names(self): wo = s.without_sizes() self.assertIsNone(wo.get_item_names('vector')) + def test_dual_prefix(self): + d = dual('~y,z', x=5) + self.assertEqual(('~y', '~z', '~x'), d.names) diff --git a/tests/commit/math/test__tensors.py b/tests/commit/math/test__tensors.py index 29bcde78f..a5268e75e 100644 --- a/tests/commit/math/test__tensors.py +++ b/tests/commit/math/test__tensors.py @@ -497,6 +497,9 @@ def test_change_dims(self): t = math.expand(math.random_normal(spatial(x=4)), batch(b=10)) self.assertEqual(batch(b=10, a=4), t.x.as_batch('a').shape) self.assertEqual(t.shape, t.nodim.as_batch('a').shape) + self.assertEqual(('b', '~x'), t.x.as_dual().shape.names) + self.assertEqual(('b', 'x'), t.x.as_dual().x.dual.as_spatial().shape.names) + self.assertEqual(('b', 'x'), t.x.as_dual().b.x.dual.as_spatial().shape.names) def test_device(self): for backend in BACKENDS: From 33611c196ac6ab301272662fe85d0a73493eb60d Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Thu, 19 Jan 2023 23:17:40 +0100 Subject: [PATCH 074/170] [math] Rework sparse matrices * Add matrix_from_function() * Split ._trace and ._optimize off from ._functional * Backends should now support batches of sparse matrices * Rename CompressedSparseTensor to CompressedSparseMatrix * New Tensor disassembly into natives and specs * Add matmul operator (@) to Tensors * Add SparseCoordinateMatrix.compress() * Remove join_spaces() --- demos/flip_liquid.py | 4 +- demos/moving_obstacle.py | 1 - demos/rotating_bar.py | 1 - phi/__init__.py | 6 +- phi/field/_field_math.py | 2 +- phi/math/__init__.py | 16 +- phi/math/_functional.py | 1340 +++++-------------------- phi/math/_nd.py | 3 +- phi/math/_ops.py | 82 +- phi/math/_optimize.py | 580 +++++++++++ phi/math/_shape.py | 48 +- phi/math/_sparse.py | 282 +++++- phi/math/_tensors.py | 196 ++-- phi/math/_trace.py | 305 ++++++ phi/math/backend/_backend.py | 42 +- phi/math/backend/_numpy_backend.py | 16 +- phi/math/extrapolation.py | 10 +- phi/math/magic.py | 17 +- phi/physics/fluid.py | 2 +- phi/tf/_tf_backend.py | 4 +- phi/torch/_torch_backend.py | 69 +- tests/commit/math/test__functional.py | 42 +- tests/commit/math/test__ops.py | 5 +- tests/commit/math/test__sparse.py | 19 +- tests/commit/math/test__tensors.py | 6 +- tests/commit/math/test__trace.py | 27 + tests/commit/physics/test_diffuse.py | 12 +- 27 files changed, 1762 insertions(+), 1375 deletions(-) create mode 100644 phi/math/_optimize.py create mode 100644 phi/math/_trace.py create mode 100644 tests/commit/math/test__trace.py diff --git a/demos/flip_liquid.py b/demos/flip_liquid.py index 348cd89cb..3dda354c0 100644 --- a/demos/flip_liquid.py +++ b/demos/flip_liquid.py @@ -3,8 +3,8 @@ A liquid block collides with a rotated obstacle and falls into a liquid pool. """ from phi.field._point_cloud import distribute_points -from phi.torch.flow import * -# from phi.tf.flow import * +# from phi.torch.flow import * +from phi.tf.flow import * # from phi.jax.flow import * diff --git a/demos/moving_obstacle.py b/demos/moving_obstacle.py index a6956666c..1ec33cb79 100644 --- a/demos/moving_obstacle.py +++ b/demos/moving_obstacle.py @@ -22,5 +22,4 @@ def move_obstacle(obs: Obstacle): obstacle = move_obstacle(obstacle) velocity = advect.mac_cormack(velocity, velocity, DT) velocity, pressure = fluid.make_incompressible(velocity, (obstacle,)) - fluid.masked_laplace.tracers.clear() # we will need to retrace because the matrix changes each step. This is not needed when JIT-compiling the physics. obstacle_mask = HardGeometryMask(obstacle.geometry).at(pressure) diff --git a/demos/rotating_bar.py b/demos/rotating_bar.py index 7e200fc3e..a29a9a164 100644 --- a/demos/rotating_bar.py +++ b/demos/rotating_bar.py @@ -14,5 +14,4 @@ obstacle = obstacle.copied_with(geometry=obstacle.geometry.rotated(-obstacle.angular_velocity * DT)) # rotate bar velocity = advect.mac_cormack(velocity, velocity, DT) velocity, pressure = fluid.make_incompressible(velocity, (obstacle,), Solve('CG-adaptive', 1e-5, 1e-5)) - fluid.masked_laplace.tracers.clear() # we will need to retrace because the matrix changes each step. This is not needed when JIT-compiling the physics. obstacle_mask = CenteredGrid(obstacle.geometry, extrapolation.ZERO, **DOMAIN) diff --git a/phi/__init__.py b/phi/__init__.py index 7af4b4015..8101f8a60 100644 --- a/phi/__init__.py +++ b/phi/__init__.py @@ -47,15 +47,15 @@ def detect_backends() -> tuple: `tuple` of `phi.math.backend.Backend` """ try: - from .torch import TORCH + from .jax import JAX except ImportError: pass try: - from .tf import TENSORFLOW + from .torch import TORCH except ImportError: pass try: - from .jax import JAX + from .tf import TENSORFLOW except ImportError: pass from .math.backend import BACKENDS diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index df274f233..8dda246a1 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -615,7 +615,7 @@ def concat(fields: List[SampledFieldType] or Tuple[SampledFieldType, ...], dim: values = math.concat([f.values for f in fields], dim) return fields[0].with_values(values) elif isinstance(fields[0], PointCloud): - elements = geom.concat([f.elements for f in fields], dim, sizes=[f.shape.get_size(dim) for f in fields]) + elements = geom.concat([f.elements for f in fields], dim) values = math.concat([math.expand(f.values, f.shape.only(dim)) for f in fields], dim) colors = math.concat([math.expand(f.color, f.shape.only(dim)) for f in fields], dim) return PointCloud(elements=elements, values=values, color=colors, extrapolation=fields[0].extrapolation, add_overlapping=fields[0]._add_overlapping, bounds=fields[0]._bounds) diff --git a/phi/math/__init__.py b/phi/math/__init__.py index 1a41889b7..4d0d2ce06 100644 --- a/phi/math/__init__.py +++ b/phi/math/__init__.py @@ -55,6 +55,14 @@ stop_gradient, pairwise_distances, ) +from ._trace import matrix_from_function +from ._functional import ( + LinearFunction, jit_compile_linear, jit_compile, + jacobian, jacobian as gradient, functional_gradient, custom_gradient, print_gradient, + map_types, map_s2b, map_i2b, + iterate, +) +from ._optimize import solve_linear, solve_nonlinear, minimize, Solve, SolveInfo, ConvergenceException, NotConverged, Diverged, SolveTape from ._nd import ( shift, vec, const_vec, vec_abs, vec_abs as vec_length, vec_squared, vec_normalize, cross_product, rotate_vector, dim_mask, @@ -65,14 +73,6 @@ downsample2x, upsample2x, sample_subgrid, masked_fill, finite_fill ) -from ._functional import ( - LinearFunction, jit_compile_linear, jit_compile, - jacobian, jacobian as gradient, functional_gradient, custom_gradient, print_gradient, hessian, - solve_linear, solve_nonlinear, minimize, Solve, SolveInfo, ConvergenceException, NotConverged, Diverged, SolveTape, - map_types, map_s2b, map_i2b, - iterate, -) - PI = 3.14159265358979323846 """Value of π to double precision """ diff --git a/phi/math/_functional.py b/phi/math/_functional.py index 10fe9cb3c..b64bd27de 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -1,22 +1,20 @@ import inspect -import time import types -import uuid import warnings from functools import wraps, partial from typing import Tuple, Callable, Dict, Generic, List, TypeVar, Any, Set -import numpy import numpy as np -from . import _ops as math -from ._ops import choose_backend_t, zeros_like, all_available, print_, reshaped_native, reshaped_tensor, to_float -from ._magic_ops import stack, unpack_dim, copy_with -from ._shape import EMPTY_SHAPE, Shape, parse_dim_order, vector_add, merge_shapes, spatial, instance, batch, concat_shapes, non_batch, shape -from ._tensors import Tensor, NativeTensor, disassemble_tree, assemble_tree, disassemble_tensors, assemble_tensors, variable_attributes, wrap, cached +from ._sparse import SparseCoordinateTensor, CompressedSparseMatrix +from ._trace import ShiftLinTracer, matrix_from_function +from .backend import Backend, NUMPY +from .backend._backend import get_spatial_derivative_order, functional_derivative_evaluation, PHI_LOGGER +from ._shape import EMPTY_SHAPE, Shape, vector_add, merge_shapes, spatial, instance, batch from .magic import PhiTreeNode -from .backend import choose_backend, Backend, NUMPY -from .backend._backend import SolveResult, get_spatial_derivative_order, functional_derivative_evaluation, PHI_LOGGER +from ._magic_ops import stack, unpack_dim +from ._tensors import Tensor, disassemble_tree, assemble_tree, disassemble_tensors, assemble_tensors, variable_attributes, wrap +from . import _ops as math X = TypeVar('X') Y = TypeVar('Y') @@ -28,7 +26,7 @@ def __init__(self, source_function: Callable or None, tree: Dict[str, Any], shapes: Shape or Tuple[Shape], - native_dims: Tuple[Shape] or None, + specs: Tuple[Shape] or None, backend: Backend, tracing: bool, condition: Any = None): @@ -39,7 +37,7 @@ def __init__(self, self.shapes = shapes self.backend = backend self.tracing = tracing - self.native_dims = native_dims + self.specs = specs self.auxiliary_kwargs = condition self.spatial_derivative_order = get_spatial_derivative_order() @@ -51,7 +49,8 @@ def __eq__(self, other: 'SignatureKey'): cond_equal = self.auxiliary_kwargs == other.auxiliary_kwargs if isinstance(cond_equal, Tensor): cond_equal = cond_equal.all - return self.tree == other.tree and self.shapes == other.shapes and self.backend == other.backend and self.spatial_derivative_order == other.spatial_derivative_order and cond_equal + # shapes need not be compared because they are included in specs + return self.tree == other.tree and self.specs == other.specs and self.backend == other.backend and self.spatial_derivative_order == other.spatial_derivative_order and cond_equal def __hash__(self): return hash(self.shapes) + hash(self.backend) @@ -66,7 +65,7 @@ def matches_structure_and_names(self, other: 'SignatureKey'): def extrapolate(self, rec_in: 'SignatureKey', new_in: 'SignatureKey') -> 'SignatureKey': assert self.source_function is not None, "extrapolate() must be called on output keys" shapes = [self._extrapolate_shape(s, rec_in, new_in) for s in self.shapes] - return SignatureKey(self.source_function, self.tree, shapes, self.native_dims, self.backend, self.tracing, self.auxiliary_kwargs) + return SignatureKey(self.source_function, self.tree, shapes, self.specs, self.backend, self.tracing, self.auxiliary_kwargs) @staticmethod def _extrapolate_shape(shape_: Shape, rec_in: 'SignatureKey', new_in: 'SignatureKey') -> Shape: @@ -94,7 +93,7 @@ def match_output_signature(new_in: SignatureKey, recorded_mappings: Dict[Signatu f"Registered transforms:\n{transforms_str}") # KeyError does not support \n -def key_from_args(args: tuple, kwargs: Dict[str, Any], parameters: Tuple[str, ...], cache=False, aux: Set[str] = ()) -> Tuple[SignatureKey, List[Tensor], list, Dict[str, Any]]: +def key_from_args(args: tuple, kwargs: Dict[str, Any], parameters: Tuple[str, ...], cache=False, aux: Set[str] = ()) -> Tuple[SignatureKey, List[Tensor], tuple, Dict[str, Any]]: kwargs = {**kwargs, **{parameters[i]: v for i, v in enumerate(args)}} aux_kwargs = {} if aux: @@ -103,28 +102,28 @@ def key_from_args(args: tuple, kwargs: Dict[str, Any], parameters: Tuple[str, .. aux_kwargs[param] = kwargs[param] del kwargs[param] tree, tensors = disassemble_tree(kwargs) - tracing = not all_available(*tensors) + tracing = not math.all_available(*tensors) backend = math.choose_backend_t(*tensors) - natives, shapes, native_dims = disassemble_tensors(tensors, expand=cache) - key = SignatureKey(None, tree, shapes, native_dims, backend, tracing, aux_kwargs) + natives, shapes, specs = disassemble_tensors(tensors, expand=cache) + key = SignatureKey(None, tree, shapes, specs, backend, tracing, aux_kwargs) return key, tensors, natives, kwargs -def key_from_args_pack_batch(args, kwargs, parameters: Tuple[str, ...], cache=False) -> Tuple[SignatureKey, List[Tensor], list, Dict[str, Any], Shape]: - kwargs = {**kwargs, **{parameters[i]: v for i, v in enumerate(args)}} - tree, tensors = disassemble_tree(kwargs) - tracing = not all_available(*tensors) - backend = math.choose_backend_t(*tensors) - # if tracing and cache: - # cache = False - # warnings.warn("Cannot cache a tensor while tracing.", RuntimeWarning) - batch_shape = merge_shapes(*[t.shape.batch for t in tensors]) - # tensors = [math.pack_dims(t, batch_shape, batch('batch'), pos=0) for t in tensors] - natives = [math.reshaped_native(t, [batch_shape, *t.shape.non_batch], force_expand=True) for t in tensors] - # natives, shapes, native_dims = disassemble_tensors(tensors, expand=cache) - shapes = tuple([math.concat_shapes(batch(batch=batch_shape.volume), *t.shape.non_batch) for t in tensors]) - key = SignatureKey(None, tree, shapes, None, backend, tracing, {}) - return key, tensors, natives, kwargs, batch_shape +# def key_from_args_pack_batch(args, kwargs, parameters: Tuple[str, ...], cache=False) -> Tuple[SignatureKey, List[Tensor], list, Dict[str, Any], Shape]: +# kwargs = {**kwargs, **{parameters[i]: v for i, v in enumerate(args)}} +# tree, tensors = disassemble_tree(kwargs) +# tracing = not math.all_available(*tensors) +# backend = math.choose_backend_t(*tensors) +# # if tracing and cache: +# # cache = False +# # warnings.warn("Cannot cache a tensor while tracing.", RuntimeWarning) +# batch_shape = merge_shapes(*[t.shape.batch for t in tensors]) +# # tensors = [math.pack_dims(t, batch_shape, batch('batch'), pos=0) for t in tensors] +# natives = [math.reshaped_native(t, [batch_shape, *t.shape.non_batch], force_expand=True) for t in tensors] +# natives, shapes, specs = disassemble_tensors(tensors, expand=cache) +# shapes = tuple([math.concat_shapes(batch(batch=batch_shape.volume), *t.shape.non_batch) for t in tensors]) +# key = SignatureKey(None, tree, shapes, specs, backend, tracing, {}) +# return key, tensors, natives, kwargs, batch_shape def function_parameters(f): @@ -173,12 +172,12 @@ def _jit_compile(self, in_key: SignatureKey): def jit_f_native(*natives): PHI_LOGGER.debug(f"Φ-jit: Tracing '{f_name(self.f)}'") - in_tensors = assemble_tensors(natives, in_key.shapes, in_key.native_dims) + in_tensors = assemble_tensors(natives, in_key.specs) kwargs = assemble_tree(in_key.tree, in_tensors) result = self.f(**kwargs, **in_key.auxiliary_kwargs) # Tensor or tuple/list of Tensors tree, out_tensors = disassemble_tree(result) - result_natives, result_shapes, _ = disassemble_tensors(out_tensors, expand=True) - self.recorded_mappings[in_key] = SignatureKey(jit_f_native, tree, result_shapes, None, in_key.backend, in_key.tracing) + result_natives, result_shapes, specs = disassemble_tensors(out_tensors, expand=True) + self.recorded_mappings[in_key] = SignatureKey(jit_f_native, tree, result_shapes, specs, in_key.backend, in_key.tracing) return result_natives jit_f_native.__name__ = f"native({f_name(self.f) if isinstance(self.f, types.FunctionType) else str(self.f)})" @@ -203,7 +202,7 @@ def __call__(self, *args, **kwargs): Set forget_traces=True to avoid memory leaks when many traces are required.""", RuntimeWarning) native_result = self.traces[key](*natives) output_key = match_output_signature(key, self.recorded_mappings, self) - output_tensors = assemble_tensors(native_result, output_key.shapes, output_key.native_dims) + output_tensors = assemble_tensors(native_result, output_key.specs) return assemble_tree(output_key.tree, output_tensors) def __repr__(self): @@ -259,7 +258,7 @@ def my_function(x: math.Tensor) -> math.Tensor: """ if f is None: kwargs = {k: v for k, v in locals().items() if v is not None} - return partial(jit_compile_linear, **kwargs) + return partial(jit_compile, **kwargs) auxiliary_args = set(s.strip() for s in auxiliary_args.split(',') if s.strip()) return f if isinstance(f, (JitFunction, LinearFunction)) and f.auxiliary_args == auxiliary_args else JitFunction(f, auxiliary_args, forget_traces or False) @@ -276,7 +275,7 @@ def __init__(self, f, auxiliary_args: Set[str], forget_traces: bool): self.f_params = function_parameters(f) self.auxiliary_args = auxiliary_args self.forget_traces = forget_traces - self.tracers: Dict[SignatureKey, ShiftLinTracer] = {} + self.matrices_and_biases: Dict[SignatureKey, Tuple[SparseCoordinateTensor, Tensor]] = {} self.nl_jit = JitFunction(f, self.auxiliary_args, forget_traces) # for backends that do not support sparse matrices def _trace(self, in_key: SignatureKey, prefer_numpy: bool) -> 'ShiftLinTracer': @@ -292,22 +291,22 @@ def _trace(self, in_key: SignatureKey, prefer_numpy: bool) -> 'ShiftLinTracer': assert isinstance(result_tensor, ShiftLinTracer), f"Tracing linear function '{f_name(self.f)}' failed. Make sure only linear operations are used." return result_tensor - def _get_or_trace(self, key: SignatureKey, prefer_numpy: bool): - if not key.tracing and key in self.tracers: - return self.tracers[key] + def _get_or_trace(self, key: SignatureKey, args: tuple, f_kwargs: dict): + if not key.tracing and key in self.matrices_and_biases: + return self.matrices_and_biases[key] else: if self.forget_traces: - self.tracers.clear() - tracer = self._trace(key, prefer_numpy=prefer_numpy) + self.matrices_and_biases.clear() + matrix, bias = matrix_from_function(self.f, *args, **f_kwargs) if not key.tracing: - self.tracers[key] = tracer - if len(self.tracers) >= 4: - warnings.warn(f"""Φ-lin: The compiled linear function '{f_name(self.f)}' was traced {len(self.tracers)} times. + self.matrices_and_biases[key] = matrix, bias + if len(self.matrices_and_biases) >= 4: + warnings.warn(f"""Φ-lin: The compiled linear function '{f_name(self.f)}' was traced {len(self.matrices_and_biases)} times. Performing many traces may be slow and cause memory leaks. Tensors in auxiliary arguments (all except the first parameter unless specified otherwise) are compared by reference, not by tensor values. Auxiliary arguments: {key.auxiliary_kwargs} Multiple linear traces can be avoided by jit-compiling the code that calls the linear function or setting forget_traces=True.""", RuntimeWarning, stacklevel=3) - return tracer + return matrix, bias def __call__(self, *args: X, **kwargs) -> Y: key, tensors, natives, x = key_from_args(args, kwargs, self.f_params, cache=False, aux=self.auxiliary_args) @@ -315,36 +314,50 @@ def __call__(self, *args: X, **kwargs) -> Y: if any(isinstance(t, ShiftLinTracer) for t in tensors): # TODO: if t is identity, use cached ShiftLinTracer, otherwise multiply two ShiftLinTracers return self.f(*args, **kwargs) - if not key.backend.supports(Backend.sparse_coo_tensor): + if not key.backend.supports(Backend.sparse_coo_tensor): # This might be called inside a Jax linear solve # warnings.warn(f"Sparse matrices are not supported by {backend}. Falling back to regular jit compilation.", RuntimeWarning) - if not all_available(*tensors): # avoid nested tracing, Typical case jax.scipy.sparse.cg(LinearFunction). Nested traces cannot be reused which results in lots of traces per cg. + if not math.all_available(*tensors): # avoid nested tracing, Typical case jax.scipy.sparse.cg(LinearFunction). Nested traces cannot be reused which results in lots of traces per cg. PHI_LOGGER.debug(f"Φ-lin: Running '{f_name(self.f)}' as-is with {key.backend} because it is being traced.") return self.f(*args, **kwargs) else: return self.nl_jit(*args, **kwargs) - tracer = self._get_or_trace(key, prefer_numpy=False) - return tracer.apply(tensors[0]) + matrix, bias = self._get_or_trace(key, args, kwargs) + return matrix @ tensors[0] + bias - def sparse_matrix(self, *args, format: str = None, prefer_numpy=False, **kwargs): - key, *_ = key_from_args(args, kwargs, self.f_params, cache=False, aux=self.auxiliary_args) - tracer = self._get_or_trace(key, prefer_numpy=prefer_numpy) - assert math.close(tracer.bias, 0), "This is an affine function and cannot be represented by a single matrix. Use sparse_matrix_and_bias() instead." - return tracer.get_sparse_matrix(format) + def sparse_matrix(self, *args, **kwargs): + """ + Create an explicit representation of this linear function as a sparse matrix. + + See Also: + `sparse_matrix_and_bias()`. - def sparse_matrix_and_bias(self, *args, format: str = None, prefer_numpy=False, **kwargs): + Args: + *args: Function arguments. This determines the size of the matrix. + **kwargs: Additional keyword arguments for the linear function. + + Returns: + Sparse matrix representation with `values` property and `native()` method. + """ key, *_ = key_from_args(args, kwargs, self.f_params, cache=False, aux=self.auxiliary_args) - tracer = self._get_or_trace(key, prefer_numpy=prefer_numpy) - return tracer.get_sparse_matrix(format), tracer.bias + matrix, bias = self._get_or_trace(key, args, kwargs) + assert math.close(bias, 0), "This is an affine function and cannot be represented by a single matrix. Use sparse_matrix_and_bias() instead." + return matrix - def stencil_inspector(self, *args, prefer_numpy=True, **kwargs): - key, _, _, _ = key_from_args(*args, cache=True, **kwargs) - tracer = self._get_or_trace(key, prefer_numpy=prefer_numpy) + def sparse_matrix_and_bias(self, *args, **kwargs): + """ + Create an explicit representation of this affine function as a sparse matrix and a bias vector. - def print_stencil(**indices): - pos = spatial(**indices) - print(f"{f_name(self.f)}: {pos} = {' + '.join(f'{val[indices]} * {vector_add(pos, offset)}' for offset, val in tracer.val.items() if (val[indices] != 0).all)}") + Args: + *args: Positional arguments to the linear function. + This determines the size of the matrix. + **kwargs: Additional keyword arguments for the linear function. - return print_stencil + Returns: + matrix: Sparse matrix representation with `values` property and `native()` method. + bias: `Tensor` + """ + key, *_ = key_from_args(args, kwargs, self.f_params, cache=False, aux=self.auxiliary_args) + return self._get_or_trace(key, args, kwargs) def __repr__(self): return f"lin({f_name(self.f)})" @@ -429,7 +442,7 @@ def __init__(self, f: Callable, f_params, wrt: str or Tuple[str, ...], get_outpu def _trace_grad(self, in_key: SignatureKey, wrt_natives): def f_native(*natives): PHI_LOGGER.debug(f"Φ-grad: Evaluating gradient of {f_name(self.f)}") - in_tensors = assemble_tensors(natives, in_key.shapes, in_key.native_dims) + in_tensors = assemble_tensors(natives, in_key.specs) kwargs = assemble_tree(in_key.tree, in_tensors) with functional_derivative_evaluation(order=1): result = self.f(**kwargs) # Tensor or tuple/list of Tensors @@ -443,8 +456,8 @@ def f_native(*natives): assert len( loss_shape) == 0, f"Only scalar losses are allowed when returning a native tensor but {f_name(self.f)} returned {type(loss_native).__name__} of shape {loss_shape}. For higher-dimensional values, use Φ-Tensors instead." nest, out_tensors = disassemble_tree(result) - result_natives, result_shapes, _ = disassemble_tensors(out_tensors, expand=True) - self.recorded_mappings[in_key] = SignatureKey(f_native, nest, result_shapes, None, in_key.backend, in_key.tracing) + result_natives, result_shapes, specs = disassemble_tensors(out_tensors, expand=True) + self.recorded_mappings[in_key] = SignatureKey(f_native, nest, result_shapes, specs, in_key.backend, in_key.tracing) return loss_native, result_natives if self.jit: @@ -466,15 +479,14 @@ def __call__(self, *args, **kwargs): self.traces[key] = self._trace_grad(key, wrt_natives) native_result = self.traces[key](*natives) output_key = match_output_signature(key, self.recorded_mappings, self) - jac_shape = output_key.shapes[0].non_batch - wrt_shapes = [math.concat_shapes(jac_shape, key.shapes[i]) for i in wrt_tensors] + jac_shape = output_key.shapes[0].non_batch # ToDo prepend this to all wrt shapes + wrt_specs = [key.specs[i] for i in wrt_tensors] if self.get_output: - result_shapes = list(output_key.shapes) + wrt_shapes - output_tensors = assemble_tensors(native_result, result_shapes, None) + output_tensors = assemble_tensors(native_result, list(output_key.specs) + wrt_specs) output_structure, grad_tuple = assemble_tree((output_key.tree, [key.tree[i] for i in self._wrt_tuple]), output_tensors) return output_structure, grad_tuple if isinstance(self.wrt, tuple) else grad_tuple[0] else: - output_tensors = assemble_tensors(native_result, wrt_shapes, None) + output_tensors = assemble_tensors(native_result, wrt_specs) grad_tuple = assemble_tree([key.tree[i] for i in self._wrt_tuple], output_tensors) return grad_tuple if isinstance(self.wrt, tuple) else grad_tuple[0] @@ -599,135 +611,135 @@ def __init__(self, f: Callable, f_params, wrt: tuple, get_output: bool, get_grad self.traces: Dict[SignatureKey, Callable] = {} self.recorded_mappings: Dict[SignatureKey, SignatureKey] = {} self.jit = jit - - def _trace_hessian(self, in_key: SignatureKey, wrt_natives): - def f_native(*natives): - PHI_LOGGER.debug(f"Φ-grad: Evaluating gradient of {f_name(self.f)}") - in_tensors = assemble_tensors(natives, in_key.shapes, in_key.native_dims) - kwargs = assemble_tree(in_key.tree, in_tensors) - with functional_derivative_evaluation(order=2): - result = self.f(**kwargs) - nest, out_tensors = disassemble_tree(result) - result_natives, result_shapes, _ = disassemble_tensors(out_tensors, expand=True) - self.recorded_mappings[in_key] = SignatureKey(f_native, nest, result_shapes, None, in_key.backend, in_key.tracing) - return result_natives - - hessian_generator = in_key.backend.jit_compile_hessian if self.jit else in_key.backend.hessian - return hessian_generator(f_native, wrt=wrt_natives, get_output=self.get_output, get_gradient=self.get_gradient) - - def __call__(self, *args, **kwargs): - key, tensors, natives, kwargs, batch_shape = key_from_args_pack_batch(args, kwargs, self.f_params, cache=True) - if not key.backend.supports(Backend.jacobian): - if math.default_backend().supports(Backend.jacobian): - warnings.warn(f"Using {math.default_backend()} for gradient computation because {key.backend} does not support jacobian()", RuntimeWarning) - key.backend = math.default_backend() - else: - raise AssertionError(f"jacobian() not supported by {key.backend}.") - wrt_tensors: List[int] = self._track_wrt(kwargs) - wrt_natives: List[int] = self._track_wrt_natives(wrt_tensors, disassemble_tree(kwargs)[1]) - if key not in self.traces: - self.traces[key] = self._trace_hessian(key, wrt_natives) - native_result = self.traces[key](*natives) - assert len(native_result) == 1 + int(self.get_output) + int(self.get_gradient) - output_key = match_output_signature(key, self.recorded_mappings, self) - result = () - if self.get_output: - output_tensors = assemble_tensors(native_result[0], output_key.shapes, output_key.native_dims) - output_tensors = [unpack_dim(t, 'batch', batch_shape) for t in output_tensors] - # output_tensors = [math.reshaped_tensor(n, [batch_shape, *shape.non_batch]) for n, shape in zip(native_result[0], output_key.shapes)] - result += assemble_tree(output_key.tree, output_tensors), - if self.get_gradient: - grad_tensors = assemble_tensors(native_result[int(self.get_output)], [key.shapes[i] for i in wrt_tensors], None) - grad_tensors = [unpack_dim(t, 'batch', batch_shape) for t in grad_tensors] - grads = assemble_tree([key.tree[i] for i in self._wrt_tuple], grad_tensors) - if not isinstance(self.wrt, tuple): - grads = grads[0] - result += grads, - if len(wrt_natives) == 1: - native_hessian = native_result[-1][0][0] - hessian_tensor = math.reshaped_tensor(native_hessian, [batch_shape, *self.shape_with_suffixes(key.shapes[0].non_batch, self.dim_suffixes[0]), - *self.shape_with_suffixes(key.shapes[0].non_batch, self.dim_suffixes[1])], check_sizes=True) - hessian_tree = assemble_tree(key.tree[self.wrt[0] if isinstance(self.wrt, tuple) else self.wrt], [hessian_tensor]) - result += [hessian_tree] if isinstance(self.wrt, tuple) else hessian_tree, - else: - assert all([t is None for t in key.tree]), "When computing the Hessian w.r.t. multiple tensors, all inputs must be Tensors." - raise NotImplementedError() - hessian_tree = [[] for _ in self.wrt] - for i in range(len(self.wrt)): - for j in range(len(self.wrt)): - native_hessian_ij = native_result[-1][i][j] - hessian_tensor_ij = math.reshaped_tensor(native_hessian_ij, [batch_shape, *key.shapes[i].non_batch, *self.dupli_shape(key.shapes[j].non_batch)], check_sizes=True) - hessian_tree[i].append(hessian_tensor_ij) - result += tuple([tuple(col) for col in hessian_tree]), - return result - - def shape_with_suffixes(self, shape: Shape, suffix: str): - return shape._with_names([n + suffix for n in shape.names]) - - def __repr__(self): - return f"grad({f_name(self.f)})" - - @property - def __name__(self): - return f_name(self.f) - - def _track_wrt(self, kwargs: dict): - wrt_tensors = [] - for name, arg in kwargs.items(): - _, tensors = disassemble_tree(arg) - wrt_tensors.extend([name] * len(tensors)) - return [t_i for t_i, name in enumerate(wrt_tensors) if name in self._wrt_tuple] - - @staticmethod - def _track_wrt_natives(wrt_tensors, values): - wrt_natives = [] - for i, value in enumerate(values): - wrt_natives.extend([i] * len(value._natives())) - return [n_i for n_i, t_i in enumerate(wrt_natives) if t_i in wrt_tensors] - - -def hessian(f: Callable, wrt: str, get_output=True, get_gradient=True, dim_suffixes=('', '_')) -> Callable: - """ - *Experimental. This function currently only supports PyTorch and the Hessian can only be computed w.r.t. one argument.* - - Creates a function which computes the Hessian (second derivative) of `f`. - - Example: - ```python - def loss_function(x, y): - prediction = f(x) - loss = math.l2_loss(prediction - y) - return loss, prediction - - hess, = hessian(loss_function, 'x', get_output=False, get_gradient=False)(x, y) - - (loss, prediction), (dx, dy), ((dx_dx, dx_dy), (dy_dx, dy_dy)) = hessian(loss_function, - wrt='x,y', get_output=True)(x, y) - ``` - - When the gradient function is invoked, `f` is called with tensors that track the gradient. - For PyTorch, `arg.requires_grad = True` for all positional arguments of `f`. - - Args: - f: Function to be differentiated. - `f` must return a floating point `Tensor` with rank zero. - It can return additional tensors which are treated as auxiliary data and will be returned by the gradient function if `return_values=True`. - All arguments for which the gradient is computed must be of dtype float or complex. - wrt: Comma-separated parameter names of `f` with respect to which the gradient should be computed. - If not specified, the gradient will be computed w.r.t. the first positional argument (highly discouraged). - get_output: Whether the Hessian function should also return the return values of `f`. - get_gradient: Whether the Hessian function should also return the gradient of `f`. - dim_suffixes: `tuple` containing two strings. - All Non-batch dimensions of the parameters occur twice in the corresponding Hessian. - To avoid duplicate names, suffixes are added to non-batch dimensions. - The dimensions from the first derivative computation are appended with `dim_suffixes[0]` and the second ones with `dim_suffixes[1]`. - This argument has no effect on the dimension names of the gradient if `get_gradient=True`. - - Returns: - Function with the same arguments as `f` that returns `(f(x), g(x), H(x))` or less depending on `get_output` and `get_gradient`. - """ - f_params, wrt = simplify_wrt(f, wrt) - return HessianFunction(f, f_params, wrt, get_output, get_gradient, dim_suffixes) +# +# def _trace_hessian(self, in_key: SignatureKey, wrt_natives): +# def f_native(*natives): +# PHI_LOGGER.debug(f"Φ-grad: Evaluating gradient of {f_name(self.f)}") +# in_tensors = assemble_tensors(natives, in_key.specs) +# kwargs = assemble_tree(in_key.tree, in_tensors) +# with functional_derivative_evaluation(order=2): +# result = self.f(**kwargs) +# nest, out_tensors = disassemble_tree(result) +# result_natives, result_shapes, specs = disassemble_tensors(out_tensors, expand=True) +# self.recorded_mappings[in_key] = SignatureKey(f_native, nest, result_shapes, specs, in_key.backend, in_key.tracing) +# return result_natives +# +# hessian_generator = in_key.backend.jit_compile_hessian if self.jit else in_key.backend.hessian +# return hessian_generator(f_native, wrt=wrt_natives, get_output=self.get_output, get_gradient=self.get_gradient) +# +# def __call__(self, *args, **kwargs): +# key, tensors, natives, kwargs, batch_shape = key_from_args_pack_batch(args, kwargs, self.f_params, cache=True) +# if not key.backend.supports(Backend.jacobian): +# if math.default_backend().supports(Backend.jacobian): +# warnings.warn(f"Using {math.default_backend()} for gradient computation because {key.backend} does not support jacobian()", RuntimeWarning) +# key.backend = math.default_backend() +# else: +# raise AssertionError(f"jacobian() not supported by {key.backend}.") +# wrt_tensors: List[int] = self._track_wrt(kwargs) +# wrt_natives: List[int] = self._track_wrt_natives(wrt_tensors, disassemble_tree(kwargs)[1]) +# if key not in self.traces: +# self.traces[key] = self._trace_hessian(key, wrt_natives) +# native_result = self.traces[key](*natives) +# assert len(native_result) == 1 + int(self.get_output) + int(self.get_gradient) +# output_key = match_output_signature(key, self.recorded_mappings, self) +# result = () +# if self.get_output: +# output_tensors = assemble_tensors(native_result[0], output_key.specs) +# output_tensors = [unpack_dim(t, 'batch', batch_shape) for t in output_tensors] +# # output_tensors = [math.reshaped_tensor(n, [batch_shape, *shape.non_batch]) for n, shape in zip(native_result[0], output_key.shapes)] +# result += assemble_tree(output_key.tree, output_tensors), +# if self.get_gradient: +# grad_tensors = assemble_tensors(native_result[int(self.get_output)], [key.specs[i] for i in wrt_tensors]) +# grad_tensors = [unpack_dim(t, 'batch', batch_shape) for t in grad_tensors] +# grads = assemble_tree([key.tree[i] for i in self._wrt_tuple], grad_tensors) +# if not isinstance(self.wrt, tuple): +# grads = grads[0] +# result += grads, +# if len(wrt_natives) == 1: +# native_hessian = native_result[-1][0][0] +# hessian_tensor = math.reshaped_tensor(native_hessian, [batch_shape, *self.shape_with_suffixes(key.shapes[0].non_batch, self.dim_suffixes[0]), +# *self.shape_with_suffixes(key.shapes[0].non_batch, self.dim_suffixes[1])], check_sizes=True) +# hessian_tree = assemble_tree(key.tree[self.wrt[0] if isinstance(self.wrt, tuple) else self.wrt], [hessian_tensor]) +# result += [hessian_tree] if isinstance(self.wrt, tuple) else hessian_tree, +# else: +# assert all([t is None for t in key.tree]), "When computing the Hessian w.r.t. multiple tensors, all inputs must be Tensors." +# raise NotImplementedError() +# hessian_tree = [[] for _ in self.wrt] +# for i in range(len(self.wrt)): +# for j in range(len(self.wrt)): +# native_hessian_ij = native_result[-1][i][j] +# hessian_tensor_ij = math.reshaped_tensor(native_hessian_ij, [batch_shape, *key.shapes[i].non_batch, *self.dupli_shape(key.shapes[j].non_batch)], check_sizes=True) +# hessian_tree[i].append(hessian_tensor_ij) +# result += tuple([tuple(col) for col in hessian_tree]), +# return result +# +# def shape_with_suffixes(self, shape: Shape, suffix: str): +# return shape._with_names([n + suffix for n in shape.names]) +# +# def __repr__(self): +# return f"grad({f_name(self.f)})" +# +# @property +# def __name__(self): +# return f_name(self.f) +# +# def _track_wrt(self, kwargs: dict): +# wrt_tensors = [] +# for name, arg in kwargs.items(): +# _, tensors = disassemble_tree(arg) +# wrt_tensors.extend([name] * len(tensors)) +# return [t_i for t_i, name in enumerate(wrt_tensors) if name in self._wrt_tuple] +# +# @staticmethod +# def _track_wrt_natives(wrt_tensors, values): +# wrt_natives = [] +# for i, value in enumerate(values): +# wrt_natives.extend([i] * len(value._natives())) +# return [n_i for n_i, t_i in enumerate(wrt_natives) if t_i in wrt_tensors] +# +# +# def hessian(f: Callable, wrt: str, get_output=True, get_gradient=True, dim_suffixes=('', '_')) -> Callable: +# """ +# *Experimental. This function currently only supports PyTorch and the Hessian can only be computed w.r.t. one argument.* +# +# Creates a function which computes the Hessian (second derivative) of `f`. +# +# Example: +# ```python +# def loss_function(x, y): +# prediction = f(x) +# loss = math.l2_loss(prediction - y) +# return loss, prediction +# +# hess, = hessian(loss_function, 'x', get_output=False, get_gradient=False)(x, y) +# +# (loss, prediction), (dx, dy), ((dx_dx, dx_dy), (dy_dx, dy_dy)) = hessian(loss_function, +# wrt='x,y', get_output=True)(x, y) +# ``` +# +# When the gradient function is invoked, `f` is called with tensors that track the gradient. +# For PyTorch, `arg.requires_grad = True` for all positional arguments of `f`. +# +# Args: +# f: Function to be differentiated. +# `f` must return a floating point `Tensor` with rank zero. +# It can return additional tensors which are treated as auxiliary data and will be returned by the gradient function if `return_values=True`. +# All arguments for which the gradient is computed must be of dtype float or complex. +# wrt: Comma-separated parameter names of `f` with respect to which the gradient should be computed. +# If not specified, the gradient will be computed w.r.t. the first positional argument (highly discouraged). +# get_output: Whether the Hessian function should also return the return values of `f`. +# get_gradient: Whether the Hessian function should also return the gradient of `f`. +# dim_suffixes: `tuple` containing two strings. +# All Non-batch dimensions of the parameters occur twice in the corresponding Hessian. +# To avoid duplicate names, suffixes are added to non-batch dimensions. +# The dimensions from the first derivative computation are appended with `dim_suffixes[0]` and the second ones with `dim_suffixes[1]`. +# This argument has no effect on the dimension names of the gradient if `get_gradient=True`. +# +# Returns: +# Function with the same arguments as `f` that returns `(f(x), g(x), H(x))` or less depending on `get_output` and `get_gradient`. +# """ +# f_params, wrt = simplify_wrt(f, wrt) +# return HessianFunction(f, f_params, wrt, get_output, get_gradient, dim_suffixes) class CustomGradientFunction: @@ -742,30 +754,32 @@ def __init__(self, f: Callable, gradient: Callable, auxiliary_args: Set[str]): def _trace(self, in_key: SignatureKey): def forward_native(*natives): - in_tensors = assemble_tensors(natives, in_key.shapes, in_key.native_dims) + in_tensors = assemble_tensors(natives, in_key.specs) kwargs = assemble_tree(in_key.tree, in_tensors) + PHI_LOGGER.debug(f"Running forward pass of custom op {forward_native.__name__} given args {tuple(kwargs.keys())} containing {len(natives)} native tensors") result = self.f(**kwargs, **in_key.auxiliary_kwargs) # Tensor or tuple/list of Tensors nest, out_tensors = disassemble_tree(result) - result_natives, result_shapes, _ = disassemble_tensors(out_tensors, expand=True) - self.recorded_mappings[in_key] = SignatureKey(forward_native, nest, result_shapes, None, in_key.backend, in_key.tracing) + result_natives, result_shapes, specs = disassemble_tensors(out_tensors, expand=True) + self.recorded_mappings[in_key] = SignatureKey(forward_native, nest, result_shapes, specs, in_key.backend, in_key.tracing) return result_natives def backward_native(x_natives, y_natives, dy_natives): + PHI_LOGGER.debug(f"Running backward pass of custom op {backward_native.__name__}") out_key = self.recorded_mappings[in_key] # del self.recorded_mappings[in_key] # this may be required multiple times - x_tensors = assemble_tensors(x_natives, in_key.shapes, in_key.native_dims) - y_tensors = assemble_tensors(y_natives, out_key.shapes, out_key.native_dims) - dy_tensors = assemble_tensors(dy_natives, out_key.shapes, out_key.native_dims) + x_tensors = assemble_tensors(x_natives, in_key.specs) + y_tensors = assemble_tensors(y_natives, out_key.specs) + dy_tensors = assemble_tensors(dy_natives, out_key.specs) kwargs = assemble_tree(in_key.tree, x_tensors) if in_key.auxiliary_kwargs: kwargs = {**kwargs, **in_key.auxiliary_kwargs} y = assemble_tree(out_key.tree, y_tensors) dy = assemble_tree(out_key.tree, dy_tensors) result = self.gradient(kwargs, y, dy) - assert isinstance(result, dict) and all(key in kwargs for key in - result.keys()), f"gradient function must return a dict containing only parameter names of the forward function. Forward '{f_name(self.f)}' has arguments {kwargs}." + assert isinstance(result, dict) and all(key in kwargs for key in result.keys()), f"gradient function must return a dict containing only parameter names of the forward function. Forward '{f_name(self.f)}' has arguments {kwargs}." full_result = tuple(result.get(name, None) for name in in_key.tree.keys()) result_natives = self.incomplete_tree_to_natives(full_result, tuple(in_key.tree.values()), list(in_key.shapes)) + PHI_LOGGER.debug(f"Backward pass of custom op {backward_native.__name__} returned gradients for {tuple(result.keys())} out of {tuple(in_key.tree.keys())} containing {len(result_natives)} native tensors") return result_natives forward_native.__name__ = f"forward '{f_name(self.f) if isinstance(self.f, types.FunctionType) else str(self.f)}'" @@ -789,7 +803,7 @@ def __call__(self, *args, **kwargs): """, RuntimeWarning, stacklevel=2) native_result = self.traces[key](*natives) # With PyTorch + jit, this does not call forward_native every time output_key = match_output_signature(key, self.recorded_mappings, self) - output_tensors = assemble_tensors(native_result, output_key.shapes, output_key.native_dims) + output_tensors = assemble_tensors(native_result, output_key.specs) return assemble_tree(output_key.tree, output_tensors) def __repr__(self): @@ -856,902 +870,6 @@ def custom_gradient(f: Callable, gradient: Callable, auxiliary_args: str = ''): return CustomGradientFunction(f, gradient, auxiliary_args) -def simplify_add(val: dict) -> Dict[Shape, Tensor]: - result = {} - for shift, values in val.items(): - shift = shift[[i for i, size in enumerate(shift.sizes) if size != 0]] # discard zeros - if shift in result: - result[shift] += values - else: - result[shift] = values - return result - - -class ShiftLinTracer(Tensor): - """ - Tracer object for linear and affine functions. - The sparsity pattern is assumed equal for all grid cells and is reflected in `val` (e.g. for a 5-point stencil, `val` has 5 items). - The Tensors stored in `val` include position-dependent dimensions, allowing for different stencils at different positions. - Dimensions not contained in any `val` Tensor are treated as independent (batch dimensions). - """ - - def __init__(self, source: Tensor, values_by_shift: dict, shape: Shape, bias: Tensor): - """ - Args: - source: placeholder tensor - values_by_shift: `dict` mapping relative shifts (`Shape`) to value Tensors. - Shape keys only contain non-zero shift dims. Missing dims are interpreted as independent. - shape: shape of this tensor - bias: Constant Tensor to be added to the multiplication output, A*x + b. - A bias naturally arises at boundary cells with non-trivial boundary conditions if no ghost cells are added to the matrix. - When non-zero, this tracer technically represents an affine function, not a linear one. - However, the bias can be subtracted from the solution vector when solving a linear system, allowing this function to be solved with regular linear system solvers. - """ - self.source = source - self.val: Dict[Shape, Tensor] = simplify_add(values_by_shift) - self.bias = bias - self._shape = shape - self._sparse_coo = self._sparse_csr = self._sparse_csc = None - - def native(self, order: str or tuple or list or Shape = None): - """ - Evaluates the value of the linear operation applied to the original source tensor. - - This is done by building a sparse matrix for all dimensions that are affected by the linear operation. - These dimensions are detected automatically during the creation of the linear operation. - All other dimensions (independent dimensions) are combined into a single batch dimensions for the sparse matrix multiplication. - - Args: - order: str or tuple or list: (Default value = None) - - Returns: - - """ - order = parse_dim_order(order, check_rank=self.rank) - result = self.apply(self.source) - result_order = order if order is not None else self._shape.names - return result.native(result_order) - - def apply(self, value: Tensor) -> NativeTensor: - assert value.shape == self.source.shape - mat = self.get_sparse_matrix().native() - independent_dims = self.independent_dims - # TODO slice for missing dimensions - order_src = concat_shapes(value.shape.only(independent_dims), value.shape.without(independent_dims)) - order_out = concat_shapes(self._shape.only(independent_dims), self._shape.without(independent_dims)) - native_src = value.native(order=order_src.names) - backend = choose_backend(native_src) - native_src = backend.reshape(native_src, (order_src.only(independent_dims).volume, order_src.without(independent_dims).volume)) - native_out = backend.matmul(mat, native_src) - native_out = backend.reshape(native_out, order_out.sizes) - return NativeTensor(native_out, order_out) - - def get_sparse_coordinate_matrix(self) -> 'SparseMatrixContainer': - """ - Builds a sparse matrix that represents this linear operation. - Independent dimensions, those that can be treated as batch dimensions, are recognized automatically and ignored. - """ - if self._sparse_coo is not None: - return self._sparse_coo - independent_dims = self.independent_dims - out_shape = self._shape.without(independent_dims) - src_shape = self.source.shape.without(independent_dims) - cols = [] - vals = [] - for shift, values in self.val.items(): - cells = list(cell_indices(out_shape)) - for missing_dim in src_shape.without(self._shape).names: - cells.insert(self.source.shape.index(missing_dim), np.zeros_like(cells[0])) - cells = [(cell + shift.get_size(dim) if dim in shift else cell) % src_shape.get_size(dim) for dim, cell in zip(src_shape.names, cells)] # shift & wrap - src_indices = cell_number(cells, src_shape) - cols.append(src_indices) - vals.append(reshaped_native(values, [*out_shape], force_expand=True)) - cols = np.stack(cols, -1).flatten() - backend = choose_backend(*vals) - vals = backend.flatten(backend.stack(vals, -1)) - rows = np.arange(out_shape.volume * len(self.val)) // len(self.val) - # TODO sort indices? - self._sparse_coo = SparseMatrixContainer('coo', (out_shape.volume, src_shape.volume), - set(self.val.keys()), self.dependent_dims, - NativeTensor(vals, instance(nnz=len(vals))), rows, cols) - return self._sparse_coo - - def get_sparse_csr_matrix(self) -> 'SparseMatrixContainer': - """ - Builds a sparse matrix that represents this linear operation. - Independent dimensions, those that can be treated as batch dimensions, are recognized automatically and ignored. - """ - if self._sparse_csr is not None: - return self._sparse_csr - coo = self.get_sparse_coordinate_matrix() - idx = np.arange(1, len(coo.values) + 1) # start indexing at 1 since 0 might get removed - import scipy.sparse - scipy_csr = scipy.sparse.csr_matrix((idx, (coo.rows, coo.cols)), shape=coo.shape) - col_indices = scipy_csr.indices - row_ptr = scipy_csr.indptr - if coo.values.nnz.size != len(scipy_csr.data): - warnings.warn("Failed to create CSR matrix because the CSR matrix contains fewer non-zero values than COO. This can happen when the `x` tensor is too small for the stencil.", - RuntimeWarning) - return coo - values = coo.values.nnz[wrap(scipy_csr.data - 1, instance('nnz'))] # Change order accordingly - self._sparse_csr = SparseMatrixContainer('csr', coo.shape, coo.indices_key, coo.src_shape, values, row_ptr, col_indices) - return self._sparse_csr - - def get_sparse_csc_matrix(self) -> 'SparseMatrixContainer': - """ - Builds a sparse matrix that represents this linear operation. - Independent dimensions, those that can be treated as batch dimensions, are recognized automatically and ignored. - """ - if self._sparse_csc is not None: - return self._sparse_csc - coo = self.get_sparse_coordinate_matrix() - idx = np.arange(1, len(coo.values) + 1) # start indexing at 1 since 0 might get removed - import scipy.sparse - scipy_csr = scipy.sparse.csc_matrix((idx, (coo.rows, coo.cols)), shape=coo.shape) - row_indices = scipy_csr.indices - col_ptr = scipy_csr.indptr - if coo.values.nnz.size != len(scipy_csr.data): - warnings.warn("Failed to create CSR matrix because the CSR matrix contains fewer non-zero values than COO. This can happen when the `x` tensor is too small for the stencil.", - RuntimeWarning) - return coo - values = coo.values.nnz[wrap(scipy_csr.data - 1, instance('nnz'))] # Change order accordingly - self._sparse_csc = SparseMatrixContainer('csc', coo.shape, coo.indices_key, coo.src_shape, values, row_indices, col_ptr) - return self._sparse_csc - - def get_sparse_matrix(self, matrix_format: str = None) -> 'SparseMatrixContainer': - if matrix_format is None: - if self.default_backend.supports(Backend.csc_matrix): - matrix_format = 'csc' - elif self.default_backend.supports(Backend.csr_matrix): - matrix_format = 'csr' - else: - matrix_format = 'coo' - if matrix_format == 'csc': - return self.get_sparse_csc_matrix() - if matrix_format == 'csr': - return self.get_sparse_csr_matrix() - elif matrix_format == 'coo': - return self.get_sparse_coordinate_matrix() - else: - raise NotImplementedError(f"Unsupported sparse matrix format: '{matrix_format}'") - - @property - def dependent_dims(self): - return merge_shapes(*[t.shape for t in self.val.values()]) - - @property - def independent_dims(self): - return self.source.shape.without(self.dependent_dims) - - @property - def dtype(self): - return self.source.dtype - - @property - def shape(self): - return self._shape - - def _with_shape_replaced(self, new_shape): - raise NotImplementedError() - - @property - def _is_tracer(self) -> bool: - return True - - def _getitem(self, selection: dict): - starts = {dim: (item.start or 0) if isinstance(item, slice) else item for dim, item in selection.items()} - new_shape = math.zeros(self._shape)[selection].shape - return self.shift(starts, new_shape, lambda v: v[selection], lambda b: b[selection]) - - def shift(self, shifts: dict, - new_shape: Shape, - val_fun: Callable, - bias_fun: Callable = None): - """ - Shifts all values of this tensor by `shifts`. - Values shifted outside will be mapped with periodic boundary conditions when the matrix is built. - - Args: - shifts: Offsets by dimension - new_shape: Shape of the shifted tensor, must match the shape returned by `val_fun`. - val_fun: Function to apply to the matrix values, may change the tensor shapes - bias_fun: Function to apply to the bias vector, may change the tensor shape - - Returns: - Shifted tensor, possibly with altered values. - """ - val = {} - for shift, values in self.val.items(): - assert isinstance(shift, Shape) - for dim, delta in reversed(tuple(shifts.items())): - if dim not in values.shape: - values = math.expand(values, self._shape.only(dim)) # dim order may be scrambled - if delta: - shift = shift._replace_single_size(dim, shift.get_size(dim) + delta) if dim in shift else shift._expand(spatial(**{dim: delta})) - val[shift] = val_fun(values) - bias = bias_fun(self.bias) - return ShiftLinTracer(self.source, val, new_shape, bias) - - def unstack(self, dimension): - raise NotImplementedError() - - def __neg__(self): - return ShiftLinTracer(self.source, {shift: -values for shift, values in self.val.items()}, self._shape, -self.bias) - - def _op1(self, native_function): - # __neg__ is the only proper linear op1 and is implemented above. - if native_function.__name__ == 'isfinite': - test_output = self.apply(math.ones_like(self.source)) - return math.is_finite(test_output) - else: - raise NotImplementedError('Only linear operations are supported') - - def _op2(self, other: Tensor, - operator: Callable, - native_function: Callable, - op_name: str = 'unknown', - op_symbol: str = '?') -> 'ShiftLinTracer': - """ - Tensor-tensor operation. - - Args: - other: - operator: - native_function: - """ - assert op_symbol in '+-*/', f"Unsupported operation encountered while tracing linear function: {native_function}" - zeros_for_missing_self = op_name not in ['add', 'radd', 'rsub'] # perform `operator` where `self == 0` - zeros_for_missing_other = op_name not in ['add', 'radd', 'sub'] # perform `operator` where `other == 0` - - if isinstance(other, ShiftLinTracer): - assert self.source is other.source, "Multiple linear tracers are not yet supported." - assert set(self._shape) == set(other._shape), f"Tracers have different shapes: {self._shape} and {other._shape}" - values = {} - for dim_shift in self.val.keys(): - if dim_shift in other.val: - values[dim_shift] = operator(self.val[dim_shift], other.val[dim_shift]) - else: - if zeros_for_missing_other: - values[dim_shift] = operator(self.val[dim_shift], math.zeros_like(self.val[dim_shift])) - else: - values[dim_shift] = self.val[dim_shift] - for dim_shift, other_values in other.val.items(): - if dim_shift not in self.val: - if zeros_for_missing_self: - values[dim_shift] = operator(math.zeros_like(other_values), other_values) - else: - values[dim_shift] = other_values - bias = operator(self.bias, other.bias) - return ShiftLinTracer(self.source, values, self._shape, bias) - else: - other = self._tensor(other) - if op_symbol in '*/': - values = {} - for dim_shift, val in self.val.items(): - val_, other_ = math.join_spaces(val, other) - values[dim_shift] = operator(val_, other_) - bias = operator(self.bias, other) - return ShiftLinTracer(self.source, values, self._shape & other.shape, bias) - elif op_symbol in '+-': - bias = operator(self.bias, other) - return ShiftLinTracer(self.source, self.val, self._shape & other.shape, bias) - else: - raise ValueError(f"Unsupported operation encountered while tracing linear function: {native_function}") - - def _natives(self) -> tuple: - """ - This function should only be used to determine the compatible backends, this tensor should be regarded as not available. - """ - return sum([v._natives() for v in self.val.values()], ()) + self.bias._natives() - - -def cell_indices(shape: Shape) -> tuple: - if shape.rank > 0: - return np.unravel_index(np.arange(shape.volume), shape.sizes) - else: - return 0, - - -def cell_number(cells, resolution: Shape): - if resolution.rank > 0: - return np.ravel_multi_index(cells, resolution.sizes) - else: - return 0, - - -class SparseMatrixContainer: - """ - This class holds information about a sparse matrix and can be passed as argument of JIT-compiled functions. - It is typically craeted by a PhiFlow tracer object, such as `ShiftLinTracer`. - Only the values tensor is variable, the sparsity pattern is fixed. - - TensorFlow doesn't allow native sparse tensors as arguments of JIT-compiled functions. - """ - - def __init__(self, - indexing_type: str, - shape: tuple, - indices_key, - src_shape: Shape, - values: Tensor, - rows, cols, - ): - """ - - Args: - shape: Sparse matrix shape - indices_key: Low-dimensional representation of the sparsity pattern, typically a set of offsets. - rows: Row indices - cols: Column indices - values: Values - src_shape: Non-flattened `Shape` of `x` vectors compatible with this matrix. - """ - assert indexing_type in ('coo', 'csr', 'csc') - self.indexing_type = indexing_type - self.shape = shape - self.indices_key = indices_key - self.rows = rows - self.cols = cols - self.values = values - self.src_shape = src_shape - - def __eq__(self, other): - return isinstance(other, SparseMatrixContainer) and \ - self.indexing_type == other.indexing_type and \ - self.indices_key == other.indices_key and \ - self.src_shape == other.src_shape - - def __variable_attrs__(self): - return 'values', - - def native(self): - backend = choose_backend(self.rows, self.cols, *self.values._natives()) - if self.indexing_type == 'csc': - return backend.csc_matrix(self.cols, self.rows, self.values.native(), self.shape) - if self.indexing_type == 'csr': - return backend.csr_matrix(self.cols, self.rows, self.values.native(), self.shape) - if self.indexing_type == 'coo': - return backend.sparse_coo_tensor((self.rows, self.cols), self.values.native(), self.shape) - assert False, self.indexing_type - - -class Solve(Generic[X, Y]): # TODO move to phi.math._functional, put Tensors there - """ - Specifies parameters and stopping criteria for solving a minimization problem or system of equations. - """ - - def __init__(self, - method: str, - relative_tolerance: float or Tensor, - absolute_tolerance: float or Tensor, - max_iterations: int or Tensor = 1000, - x0: X or Any = None, - suppress: tuple or list = (), - preprocess_y: Callable = None, - preprocess_y_args: tuple = (), - gradient_solve: 'Solve[Y, X]' or None = None): - assert isinstance(method, str) - self.method: str = method - """ Optimization method to use. Available solvers depend on the solve function that is used to perform the solve. """ - self.relative_tolerance: Tensor = math.to_float(wrap(relative_tolerance)) - """ Relative tolerance for linear solves only. This must be `0` for minimization problems. - For systems of equations *f(x)=y*, the final tolerance is `max(relative_tolerance * norm(y), absolute_tolerance)`. """ - self.absolute_tolerance: Tensor = math.to_float(wrap(absolute_tolerance)) - """ Absolut tolerance for optimization problems and linear solves. - For systems of equations *f(x)=y*, the final tolerance is `max(relative_tolerance * norm(y), absolute_tolerance)`. """ - self.max_iterations: Tensor = math.to_int32(wrap(max_iterations)) - """ Maximum number of iterations to perform before raising a `NotConverged` error is raised. """ - self.x0 = x0 - """ Initial guess for the method, of same type and dimensionality as the solve result. - This property must be set to a value compatible with the solution `x` before running a method. """ - self.preprocess_y: Callable = preprocess_y - """ Function to be applied to the right-hand-side vector of an equation system before solving the system. - This property is propagated to gradient solves by default. """ - self.preprocess_y_args: tuple = preprocess_y_args - assert all(issubclass(err, ConvergenceException) for err in suppress) - self.suppress: tuple = tuple(suppress) - """ Error types to suppress; `tuple` of `ConvergenceException` types. For these errors, the solve function will instead return the partial result without raising the error. """ - self._gradient_solve: Solve[Y, X] = gradient_solve - self.id = str(uuid.uuid4()) - - @property - def gradient_solve(self) -> 'Solve[Y, X]': - """ - Parameters to use for the gradient pass when an implicit gradient is computed. - If `None`, a duplicate of this `Solve` is created for the gradient solve. - - In any case, the gradient solve information will be stored in `gradient_solve.result`. - """ - if self._gradient_solve is None: - self._gradient_solve = Solve(self.method, self.relative_tolerance, self.absolute_tolerance, self.max_iterations, None, self.suppress, self.preprocess_y, self.preprocess_y_args) - return self._gradient_solve - - def __repr__(self): - return f"{self.method} with tolerance {self.relative_tolerance} (rel), {self.absolute_tolerance} (abs), max_iterations={self.max_iterations}" - - def __eq__(self, other): - if not isinstance(other, Solve): - return False - if self.method != other.method \ - or (self.absolute_tolerance != other.absolute_tolerance).any \ - or (self.relative_tolerance != other.relative_tolerance).any \ - or (self.max_iterations != other.max_iterations).any \ - or self.preprocess_y is not other.preprocess_y \ - or self.suppress != other.suppress: - return False - return self.x0 == other.x0 - - def __variable_attrs__(self): - return 'x0', 'preprocess_y_args' - - -class SolveInfo(Generic[X, Y]): - """ - Stores information about the solution or trajectory of a solve. - - When representing the full optimization trajectory, all tracked quantities will have an additional `trajectory` batch dimension. - """ - - def __init__(self, - solve: Solve, - x: X, - residual: Y or None, - iterations: Tensor or None, - function_evaluations: Tensor or None, - converged: Tensor, - diverged: Tensor, - method: str, - msg: str, - solve_time: float): - # tuple.__new__(SolveInfo, (x, residual, iterations, function_evaluations, converged, diverged)) - self.solve: Solve[X, Y] = solve - """ `Solve`, Parameters specified for the solve. """ - self.x: X = x - """ `Tensor` or `PhiTreeNode`, solution estimate. """ - self.residual: Y = residual - """ `Tensor` or `PhiTreeNode`, residual vector for systems of equations or function value for minimization problems. """ - self.iterations: Tensor = iterations - """ `Tensor`, number of performed iterations to reach this state. """ - self.function_evaluations: Tensor = function_evaluations - """ `Tensor`, how often the function (or its gradient function) was called. """ - self.converged: Tensor = converged - """ `Tensor`, whether the residual is within the specified tolerance. """ - self.diverged: Tensor = diverged - """ `Tensor`, whether the solve has diverged at this point. """ - self.method = method - """ `str`, which method and implementation that was used. """ - if not msg and all_available(diverged, converged): - if self.diverged.any: - msg = f"Solve diverged within {iterations if iterations is not None else '?'} iterations using {method}." - elif not self.converged.trajectory[-1].all: - msg = f"Solve did not converge to rel={solve.relative_tolerance}, abs={solve.absolute_tolerance} within {solve.max_iterations} iterations using {method}. Max residual: {[math.max_(t.trajectory[-1]) for t in disassemble_tree(self.residual)[1]]}" - else: - msg = f"Converged within {iterations if iterations is not None else '?'} iterations." - self.msg = msg - """ `str`, termination message """ - self.solve_time = solve_time - """ Time spent in Backend solve function (in seconds) """ - - def __repr__(self): - return self.msg - - def snapshot(self, index): - return SolveInfo(self.solve, self.x.trajectory[index], self.residual.trajectory[index], self.iterations.trajectory[index], self.function_evaluations.trajectory[index], - self.converged.trajectory[index], self.diverged.trajectory[index], self.method, self.msg, self.solve_time) - - def convergence_check(self, only_warn: bool): - if not all_available(self.diverged, self.converged): - return - if self.diverged.any: - if Diverged not in self.solve.suppress: - if only_warn: - warnings.warn(self.msg, ConvergenceWarning) - else: - raise Diverged(self) - if not self.converged.trajectory[-1].all: - if NotConverged not in self.solve.suppress: - if only_warn: - warnings.warn(self.msg, ConvergenceWarning) - else: - raise NotConverged(self) - - -class ConvergenceException(RuntimeError): - """ - Base class for exceptions raised when a solve does not converge. - - See Also: - `Diverged`, `NotConverged`. - """ - - def __init__(self, result: SolveInfo): - RuntimeError.__init__(self, result.msg) - self.result: SolveInfo = result - """ `SolveInfo` holding information about the solve. """ - - -class ConvergenceWarning(RuntimeWarning): - pass - - -class NotConverged(ConvergenceException): - """ - Raised during optimization if the desired accuracy was not reached within the maximum number of iterations. - - This exception inherits from `ConvergenceException`. - - See Also: - `Diverged`. - """ - - def __init__(self, result: SolveInfo): - ConvergenceException.__init__(self, result) - - -class Diverged(ConvergenceException): - """ - Raised if the optimization was stopped prematurely and cannot continue. - This may indicate that no solution exists. - - The values of the last estimate `x` may or may not be finite. - - This exception inherits from `ConvergenceException`. - - See Also: - `NotConverged`. - """ - - def __init__(self, result: SolveInfo): - ConvergenceException.__init__(self, result) - - -class SolveTape: - """ - Used to record additional information about solves invoked via `solve_linear()`, `solve_nonlinear()` or `minimize()`. - While a `SolveTape` is active, certain performance optimizations and algorithm implementations may be disabled. - - To access a `SolveInfo` of a recorded solve, use - ```python - solve = Solve(method, ...) - with SolveTape() as solves: - x = math.solve_linear(f, y, solve) - result: SolveInfo = solves[solve] # get by Solve - result: SolveInfo = solves[0] # get by index - ``` - """ - - def __init__(self, record_trajectories=False): - """ - Args: - record_trajectories: When enabled, the entries of `SolveInfo` will contain an additional batch dimension named `trajectory`. - """ - self.record_trajectories = record_trajectories - self.solves: List[SolveInfo] = [] - self.solve_ids: List[str] = [] - - def __enter__(self): - _SOLVE_TAPES.append(self) - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - _SOLVE_TAPES.remove(self) - - def _add(self, solve: Solve, trj: bool, result: SolveInfo): - if any(s.solve.id == solve.id for s in self.solves): - warnings.warn("SolveTape contains two results for the same solve settings. SolveTape[solve] will return the first solve result.", RuntimeWarning) - if self.record_trajectories: - assert trj, "Solve did not record a trajectory." - self.solves.append(result) - elif trj: - self.solves.append(result.snapshot(-1)) - else: - self.solves.append(result) - self.solve_ids.append(solve.id) - - def __getitem__(self, item) -> SolveInfo: - if isinstance(item, int): - return self.solves[item] - else: - assert isinstance(item, Solve) - solves = [s for s in self.solves if s.solve.id == item.id] - if len(solves) == 0: - raise KeyError(f"No solve recorded with key '{item}'.") - assert len(solves) == 1 - return solves[0] - - def __iter__(self): - return iter(self.solves) - - def __len__(self): - return len(self.solves) - - -_SOLVE_TAPES: List[SolveTape] = [] - - -def minimize(f: Callable[[X], Y], solve: Solve[X, Y]) -> X: - """ - Finds a minimum of the scalar function *f(x)*. - The `method` argument of `solve` determines which optimizer is used. - All optimizers supported by `scipy.optimize.minimize` are supported, - see https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html . - Additionally a gradient descent solver with adaptive step size can be used with `method='GD'`. - - `math.minimize()` is limited to backends that support `jacobian()`, i.e. PyTorch, TensorFlow and Jax. - - To obtain additional information about the performed solve, use a `SolveTape`. - - See Also: - `solve_nonlinear()`. - - Args: - f: Function whose output is subject to minimization. - All positional arguments of `f` are optimized and must be `Tensor` or `PhiTreeNode`. - If `solve.x0` is a `tuple` or `list`, it will be passed to *f* as varargs, `f(*x0)`. - To minimize a subset of the positional arguments, define a new (lambda) function depending only on those. - The first return value of `f` must be a scalar float `Tensor` or `PhiTreeNode`. - solve: `Solve` object to specify method type, parameters and initial guess for `x`. - - Returns: - x: solution, the minimum point `x`. - - Raises: - NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. - Diverged: If the optimization failed prematurely. - """ - assert (solve.relative_tolerance == 0).all, f"relative_tolerance must be zero for minimize() but got {solve.relative_tolerance}" - assert solve.preprocess_y is None, "minimize() does not allow preprocess_y" - x0_nest, x0_tensors = disassemble_tree(solve.x0) - x0_tensors = [to_float(t) for t in x0_tensors] - backend = choose_backend_t(*x0_tensors, prefer_default=True) - batch_dims = merge_shapes(*[t.shape for t in x0_tensors]).batch - x0_natives = [] - for t in x0_tensors: - t._expand() - assert t.shape.is_uniform - x0_natives.append(reshaped_native(t, [batch_dims, t.shape.non_batch], force_expand=True)) - x0_flat = backend.concat(x0_natives, -1) - - def unflatten_assemble(x_flat, additional_dims: Shape = EMPTY_SHAPE, convert=True): - i = 0 - x_tensors = [] - for x0_native, x0_tensor in zip(x0_natives, x0_tensors): - vol = backend.shape(x0_native)[-1] - flat_native = x_flat[..., i:i + vol] - x_tensors.append(reshaped_tensor(flat_native, [*additional_dims, batch_dims, x0_tensor.shape.non_batch], convert=convert)) - i += vol - x = assemble_tree(x0_nest, x_tensors) - return x - - def native_function(x_flat): - x = unflatten_assemble(x_flat) - if isinstance(x, (tuple, list)): - y = f(*x) - else: - y = f(x) - _, y_tensors = disassemble_tree(y) - assert not non_batch(y_tensors[0]), f"Failed to minimize '{f.__name__}' because it returned a non-scalar output {shape(y_tensors[0])}. Reduce all non-batch dimensions, e.g. using math.l2_loss()" - try: - loss_native = reshaped_native(y_tensors[0], [batch_dims]) - except AssertionError: - raise AssertionError(f"Failed to minimize '{f.__name__}' because its output loss {shape(y_tensors[0])} has more batch dimensions than the initial guess {batch_dims}.") - return y_tensors[0].sum, (loss_native,) - - atol = backend.to_float(reshaped_native(solve.absolute_tolerance, [batch_dims], force_expand=True)) - maxi = backend.to_int32(reshaped_native(solve.max_iterations, [batch_dims], force_expand=True)) - trj = _SOLVE_TAPES and any(t.record_trajectories for t in _SOLVE_TAPES) - t = time.perf_counter() - ret = backend.minimize(solve.method, native_function, x0_flat, atol, maxi, trj) - t = time.perf_counter() - t - if not trj: - assert isinstance(ret, SolveResult) - converged = reshaped_tensor(ret.converged, [batch_dims]) - diverged = reshaped_tensor(ret.diverged, [batch_dims]) - x = unflatten_assemble(ret.x) - iterations = reshaped_tensor(ret.iterations, [batch_dims]) - function_evaluations = reshaped_tensor(ret.function_evaluations, [batch_dims]) - residual = reshaped_tensor(ret.residual, [batch_dims]) - result = SolveInfo(solve, x, residual, iterations, function_evaluations, converged, diverged, ret.method, ret.message, t) - else: # trajectory - assert isinstance(ret, (tuple, list)) and all(isinstance(r, SolveResult) for r in ret) - converged = reshaped_tensor(ret[-1].converged, [batch_dims]) - diverged = reshaped_tensor(ret[-1].diverged, [batch_dims]) - x = unflatten_assemble(ret[-1].x) - x_ = unflatten_assemble(numpy.stack([r.x for r in ret]), additional_dims=batch('trajectory'), convert=False) - residual = stack([reshaped_tensor(r.residual, [batch_dims]) for r in ret], batch('trajectory')) - iterations = reshaped_tensor(ret[-1].iterations, [batch_dims]) - function_evaluations = stack([reshaped_tensor(r.function_evaluations, [batch_dims]) for r in ret], batch('trajectory')) - result = SolveInfo(solve, x_, residual, iterations, function_evaluations, converged, diverged, ret[-1].method, ret[-1].message, t) - for tape in _SOLVE_TAPES: - tape._add(solve, trj, result) - result.convergence_check(False) # raises ConvergenceException - return x - - -def solve_nonlinear(f: Callable, y, solve: Solve) -> Tensor: - """ - Solves the non-linear equation *f(x) = y* by minimizing the norm of the residual. - - This method is limited to backends that support `jacobian()`, currently PyTorch, TensorFlow and Jax. - - To obtain additional information about the performed solve, use a `SolveTape`. - - See Also: - `minimize()`, `solve_linear()`. - - Args: - f: Function whose output is optimized to match `y`. - All positional arguments of `f` are optimized and must be `Tensor` or `PhiTreeNode`. - The output of `f` must match `y`. - y: Desired output of `f(x)` as `Tensor` or `PhiTreeNode`. - solve: `Solve` object specifying optimization method, parameters and initial guess for `x`. - - Returns: - x: Solution fulfilling `f(x) = y` within specified tolerance as `Tensor` or `PhiTreeNode`. - - Raises: - NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. - Diverged: If the solve failed prematurely. - """ - from ._nd import l2_loss - - if solve.preprocess_y is not None: - y = solve.preprocess_y(y) - - def min_func(x): - diff = f(x) - y - l2 = l2_loss(diff) - return l2 - - rel_tol_to_abs = solve.relative_tolerance * l2_loss(y) - min_solve = copy_with(solve, absolute_tolerance=rel_tol_to_abs, relative_tolerance=0, preprocess_y=None) - return minimize(min_func, min_solve) - - -def solve_linear(f: Callable[[X], Y], - y: Y, solve: Solve[X, Y], - f_args: tuple or list = (), - f_kwargs: dict = None) -> X: - """ - Solves the system of linear equations *f(x) = y* and returns *x*. - For maximum performance, compile `f` using `jit_compile_linear()` beforehand. - Then, an optimized representation of `f` (such as a sparse matrix) will be used to solve the linear system. - - To obtain additional information about the performed solve, use a `SolveTape`. - - The gradient of this operation will perform another linear solve with the parameters specified by `Solve.gradient_solve`. - - See Also: - `solve_nonlinear()`, `jit_compile_linear()`. - - Args: - f: Linear function with `Tensor` or `PhiTreeNode` first parameter and return value. - `f` can have additional arguments. - y: Desired output of `f(x)` as `Tensor` or `PhiTreeNode`. - solve: `Solve` object specifying optimization method, parameters and initial guess for `x`. - f_args: Additional `Tensor` or `PhiTreeNode` arguments to be passed to `f`. - `f` need not be linear in these arguments. - Use this instead of lambda function since a lambda will not be recognized as calling a jit-compiled function. - f_kwargs: Additional keyword arguments to be passed to `f`. - These arguments are treated as auxiliary arguments and can be of any type. - - Returns: - x: solution of the linear system of equations `f(x) = y` as `Tensor` or `PhiTreeNode`. - - Raises: - NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. - Diverged: If the solve failed prematurely. - """ - y_tree, y_tensors = disassemble_tree(y) - x0_tree, x0_tensors = disassemble_tree(solve.x0) - assert len(x0_tensors) == len(y_tensors) == 1, "Only single-tensor linear solves are currently supported" - backend = choose_backend_t(*y_tensors, *x0_tensors) - - if isinstance(f, LinearFunction) and (backend.supports(Backend.sparse_coo_tensor) or backend.supports(Backend.csr_matrix)): # Matrix solve - matrix, bias = f.sparse_matrix_and_bias(solve.x0, *f_args, **(f_kwargs or {})) - - def _matrix_solve_forward(y, solve: Solve, matrix: SparseMatrixContainer, is_backprop=False): - matrix_native = matrix.native() - active_dims = matrix.src_shape - result = _linear_solve_forward(y, solve, matrix_native, active_dims=active_dims, backend=backend, is_backprop=is_backprop) - return result # must return exactly `x` so gradient isn't computed w.r.t. other quantities - - _matrix_solve = attach_gradient_solve(_matrix_solve_forward, auxiliary_args='is_backprop') - return _matrix_solve(y - bias, solve, matrix) - else: # Matrix-free solve - f_args = cached(f_args) - solve = cached(solve) - - def _function_solve_forward(y, solve: Solve, f_args: tuple, f_kwargs: dict = None, is_backprop=False): - y_nest, (y_tensor,) = disassemble_tree(y) - x0_nest, (x0_tensor,) = disassemble_tree(solve.x0) - active_dims = (y_tensor.shape & x0_tensor.shape).non_batch # assumes batch dimensions are not active - batches = (y_tensor.shape & x0_tensor.shape).batch - - def native_lin_f(native_x, batch_index=None): - if batch_index is not None and batches.volume > 1: - native_x = backend.tile(backend.expand_dims(native_x), [batches.volume, 1]) - x = assemble_tree(x0_nest, [reshaped_tensor(native_x, [batches, active_dims] if backend.ndims(native_x) >= 2 else [active_dims], convert=False)]) - y = f(x, *f_args, **f_kwargs) - _, (y_tensor,) = disassemble_tree(y) - y_native = reshaped_native(y_tensor, [batches, active_dims] if backend.ndims(native_x) >= 2 else [active_dims]) - if batch_index is not None and batches.volume > 1: - y_native = y_native[batch_index] - return y_native - - result = _linear_solve_forward(y, solve, native_lin_f, active_dims=active_dims, backend=backend, is_backprop=is_backprop) - return result # must return exactly `x` so gradient isn't computed w.r.t. other quantities - - _function_solve = attach_gradient_solve(_function_solve_forward, auxiliary_args='is_backprop,f_kwargs') - return _function_solve(y, solve, f_args, f_kwargs=f_kwargs or {}) - - -def _linear_solve_forward(y, solve: Solve, native_lin_op, - active_dims: Shape or None, backend: Backend, is_backprop: bool) -> Any: - PHI_LOGGER.debug(f"Performing linear solve {solve} with backend {backend}") - if solve.preprocess_y is not None: - y = solve.preprocess_y(y, *solve.preprocess_y_args) - y_nest, (y_tensor,) = disassemble_tree(y) - x0_nest, (x0_tensor,) = disassemble_tree(solve.x0) - batch_dims = (y_tensor.shape & x0_tensor.shape).without(active_dims) - x0_native = backend.as_tensor(reshaped_native(x0_tensor, [batch_dims, active_dims], force_expand=True)) - y_native = backend.as_tensor(reshaped_native(y_tensor, [batch_dims, active_dims], force_expand=True)) - rtol = backend.as_tensor(reshaped_native(math.to_float(solve.relative_tolerance), [batch_dims], force_expand=True)) - atol = backend.as_tensor(reshaped_native(solve.absolute_tolerance, [batch_dims], force_expand=True)) - maxi = backend.as_tensor(reshaped_native(solve.max_iterations, [batch_dims], force_expand=True)) - trj = _SOLVE_TAPES and any(t.record_trajectories for t in _SOLVE_TAPES) - if trj: - assert all_available(y_tensor, x0_tensor), "Cannot record linear solve in jit mode" - t = time.perf_counter() - ret = backend.linear_solve(solve.method, native_lin_op, y_native, x0_native, rtol, atol, maxi, trj) - t = time.perf_counter() - t - if not trj: - assert isinstance(ret, SolveResult) - converged = reshaped_tensor(ret.converged, [batch_dims]) - diverged = reshaped_tensor(ret.diverged, [batch_dims]) - x = assemble_tree(x0_nest, [reshaped_tensor(ret.x, [batch_dims, active_dims])]) - iterations = reshaped_tensor(ret.iterations, [batch_dims]) - function_evaluations = reshaped_tensor(ret.function_evaluations, [batch_dims]) - if ret.residual is not None: - residual = assemble_tree(y_nest, [reshaped_tensor(ret.residual, [batch_dims, active_dims])]) - elif _SOLVE_TAPES: - residual = backend.linear(native_lin_op, ret.x) - y_native - residual = assemble_tree(y_nest, [reshaped_tensor(residual, [batch_dims, active_dims])]) - else: - residual = None - result = SolveInfo(solve, x, residual, iterations, function_evaluations, converged, diverged, ret.method, ret.message, t) - else: # trajectory - assert isinstance(ret, (tuple, list)) and all(isinstance(r, SolveResult) for r in ret), f"Trajectory recording failed: got {type(ret)}" - converged = reshaped_tensor(ret[-1].converged, [batch_dims]) - diverged = reshaped_tensor(ret[-1].diverged, [batch_dims]) - x = assemble_tree(x0_nest, [reshaped_tensor(ret[-1].x, [batch_dims, active_dims])]) - x_ = assemble_tree(x0_nest, [stack([reshaped_tensor(r.x, [batch_dims, active_dims]) for r in ret], batch('trajectory'))]) - residual = assemble_tree(y_nest, [stack([reshaped_tensor(r.residual, [batch_dims, active_dims]) for r in ret], batch('trajectory'))]) - iterations = reshaped_tensor(ret[-1].iterations, [batch_dims]) - function_evaluations = stack([reshaped_tensor(r.function_evaluations, [batch_dims]) for r in ret], batch('trajectory')) - result = SolveInfo(solve, x_, residual, iterations, function_evaluations, converged, diverged, ret[-1].method, ret[-1].message, t) - for tape in _SOLVE_TAPES: - tape._add(solve, trj, result) - result.convergence_check(is_backprop and 'TensorFlow' in backend.name) # raises ConvergenceException - return x - - -def attach_gradient_solve(forward_solve: Callable, auxiliary_args: str): - def implicit_gradient_solve(kwargs, x, dx): - solve = kwargs['solve'] - matrix = (kwargs['matrix'],) if 'matrix' in kwargs else () - grad_solve = solve.gradient_solve - x0 = grad_solve.x0 if grad_solve.x0 is not None else zeros_like(solve.x0) - grad_solve_ = copy_with(solve.gradient_solve, x0=x0) - if 'is_backprop' in kwargs: - del kwargs['is_backprop'] - dy = solve_with_grad(dx, grad_solve_, *matrix, is_backprop=True, **kwargs) # this should hopefully result in implicit gradients for higher orders as well - return {'y': dy} - - solve_with_grad = custom_gradient(forward_solve, implicit_gradient_solve, auxiliary_args=auxiliary_args) - return solve_with_grad - - def print_gradient(value: Tensor, name="", detailed=False) -> Tensor: """ Prints the gradient vector of `value` when computed. @@ -1779,9 +897,9 @@ def f(x): def print_grad(params: dict, _y, dx): param_name, x = next(iter(params.items())) - if all_available(x, dx): + if math.all_available(x, dx): if detailed: - print_(dx, name=name) + math.print_(dx, name=name) else: print(f"{name}: \t{dx}") else: diff --git a/phi/math/_nd.py b/phi/math/_nd.py index e51a48d33..c6b56e61b 100644 --- a/phi/math/_nd.py +++ b/phi/math/_nd.py @@ -5,7 +5,8 @@ from . import _ops as math from . import extrapolation as extrapolation -from ._functional import solve_linear, jit_compile_linear +from ._functional import jit_compile_linear +from ._optimize import solve_linear from ._magic_ops import stack, rename_dims, concat, variable_values from ._shape import Shape, channel, batch, spatial, DimFilter, parse_dim_order, shape from ._tensors import Tensor, wrap diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 9975f2e03..ddf719e94 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -11,7 +11,7 @@ from ._shape import (Shape, EMPTY_SHAPE, spatial, batch, channel, instance, merge_shapes, parse_dim_order, concat_shapes, IncompatibleShapes, DimFilter, non_batch, non_channel) -from ._sparse import CompressedSparseTensor, dot_compressed_dense, dense +from ._sparse import CompressedSparseMatrix, dot_compressed_dense, dense, SparseCoordinateTensor, dot_coordinate_dense from ._tensors import Tensor, wrap, tensor, broadcastable_native_tensors, NativeTensor, TensorStack, CollapsedTensor, \ custom_op2, compatible_tensor, variable_attributes, disassemble_tree, assemble_tree, \ cached, is_scalar, Layout @@ -82,15 +82,7 @@ def all_available(*values: Tensor) -> bool: Returns: `True` if no value is a placeholder or being traced, `False` otherwise. """ - from phi.math._functional import ShiftLinTracer - for value in values: - if isinstance(value, ShiftLinTracer): - return False - natives = value._natives() - natives_available = [choose_backend(native).is_available(native) for native in natives] - if not all(natives_available): - return False - return True + return all([v.available for v in values]) def seed(seed: int): @@ -415,7 +407,7 @@ def map_(function, *values, range=range, **kwargs) -> Tensor or None: return tuple([unpack_dim(wrap(result_i, channel('_c')), '_c', shape) for result_i in results]) -def _initialize(uniform_initializer, shapes: tuple) -> Tensor: +def _initialize(uniform_initializer, shapes: Tuple[Shape]) -> Tensor: shape = concat_shapes(*shapes) if shape.is_non_uniform: stack_dim = shape.shape.without('dims')[0:1] @@ -442,7 +434,7 @@ def zeros(*shape: Shape, dtype=None) -> Tensor: Returns: `Tensor` """ - return _initialize(lambda shape: CollapsedTensor(NativeTensor(default_backend().zeros((), dtype=DType.as_dtype(dtype)), EMPTY_SHAPE), shape), shape) + return _initialize(lambda shape: expand_tensor(NativeTensor(default_backend().zeros((), dtype=DType.as_dtype(dtype)), EMPTY_SHAPE), shape), shape) def zeros_like(obj: Tensor or PhiTreeNode) -> Tensor or PhiTreeNode: @@ -472,7 +464,7 @@ def ones(*shape: Shape, dtype=None) -> Tensor: Returns: `Tensor` """ - return _initialize(lambda shape: CollapsedTensor(NativeTensor(default_backend().ones((), dtype=DType.as_dtype(dtype)), EMPTY_SHAPE), shape), shape) + return _initialize(lambda shape: expand_tensor(NativeTensor(default_backend().ones((), dtype=DType.as_dtype(dtype)), EMPTY_SHAPE), shape), shape) def ones_like(value: Tensor) -> Tensor: @@ -550,7 +542,7 @@ def transpose(x: Tensor, axes): `Tensor` or native tensor, depending on `x`. """ if isinstance(x, Tensor): - return CollapsedTensor(x, x.shape[axes]) # TODO avoid nesting + return expand(x, x.shape[axes]) else: return choose_backend(x).transpose(x, axes) @@ -732,7 +724,10 @@ def stack_tensors(values: tuple or list, dim: Shape): values = cast_same(*values) def inner_stack(*values): - return TensorStack(values, dim) + if len(values) > 1: + return TensorStack(values, dim) + else: + return CollapsedTensor(values[0], values[0].shape & dim.with_size(1)) result = broadcast_op(inner_stack, values) return result @@ -908,27 +903,11 @@ def _grid_sample(grid: Tensor, coordinates: Tensor, extrap: 'e_.Extrapolation' o neighbors = _closest_grid_values(grid, coordinates, extrap or e_.ZERO, '_closest_', pad_kwargs) binary = meshgrid(**{f'_closest_{dim}': (0, 1) for dim in grid.shape.spatial.names}, dim_type=channel, assign_item_names=False) right_weights = coordinates % 1 - binary, right_weights = join_spaces(binary, right_weights) weights = prod(binary * right_weights + (1 - binary) * (1 - right_weights), 'vector') result = sum_(neighbors * weights, dim=[f"_closest_{dim}" for dim in grid.shape.spatial.names]) return result -def join_spaces(*tensors): - """ - Adds the spatial dimensions of all tensors to all other tensors. - When spatial dimensions are present with multiple tensors, they must have the same size. - - Args: - *tensors: Sequence of `Tensor`s. - - Returns: - List of `Tensor`s with same values as `tensors` but additional spatial dimensions. - """ - spatial_dims = merge_shapes(*[t.shape.spatial for t in tensors]) - return [CollapsedTensor(t, t.shape.non_spatial & spatial_dims) for t in tensors] - - def broadcast_op(operation: Callable, tensors: tuple or list, iter_dims: set or tuple or list or Shape = None, @@ -1059,8 +1038,7 @@ def reduce_(f, value, dims, require_all_dims_present=False, required_kind: type dims = value.shape.only(dims) if require_all_dims_present and any(d not in value.shape for d in dims): raise ValueError(f"Cannot sum dimensions {dims} because tensor {value.shape} is missing at least one of them") - value = value._simplify() - return f(value, dims) + return f(value._simplify(), dims) def sum_(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: @@ -1096,7 +1074,7 @@ def _sum(value: Tensor, dims: Shape) -> Tensor: elif isinstance(value, TensorStack): reduced_inners = [_sum(t, dims.without(value.stack_dim)) for t in value._tensors] return functools.reduce(lambda x, y: x + y, reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) - elif isinstance(value, CompressedSparseTensor): + elif isinstance(value, CompressedSparseMatrix): if value.sparse_dims in dims: # reduce all sparse dims return _sum(value._values, dims.without(value.sparse_dims) & instance(value._values)) value_only_dims = dims.only(value._values.shape).without(value.sparsity_batch) @@ -1172,6 +1150,8 @@ def mean(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: def _mean(value: Tensor, dims: Shape) -> Tensor: + if not dims: + return value if isinstance(value, NativeTensor): result = value.default_backend.mean(value.native(value.shape), value.shape.indices(dims)) return NativeTensor(result, value.shape.without(dims)) @@ -1553,14 +1533,22 @@ def dot(x: Tensor, assert x_dims.volume == 1, f"Cannot compute dot product between dimensions {x_dims} on {x.shape} and {y_dims} on {y.shape}" x = x[{d: 0 for d in x_dims.names}] return x * y - if isinstance(x, CompressedSparseTensor): - if isinstance(y, CompressedSparseTensor): - raise NotImplementedError + if isinstance(x, CompressedSparseMatrix): + if isinstance(y, (CompressedSparseMatrix, SparseCoordinateTensor)): + raise NotImplementedError("sparse-sparse multiplication not yet supported") return dot_compressed_dense(x, x_dims, y, y_dims) - elif isinstance(y, CompressedSparseTensor): - if isinstance(x, CompressedSparseTensor): - raise NotImplementedError + elif isinstance(y, CompressedSparseMatrix): + if isinstance(x, (CompressedSparseMatrix, SparseCoordinateTensor)): + raise NotImplementedError("sparse-sparse multiplication not yet supported") return dot_compressed_dense(y, y_dims, x, x_dims) + if isinstance(x, SparseCoordinateTensor): + if isinstance(y, (CompressedSparseMatrix, SparseCoordinateTensor)): + raise NotImplementedError("sparse-sparse multiplication not yet supported") + return dot_coordinate_dense(x, x_dims, y, y_dims) + elif isinstance(y, SparseCoordinateTensor): + if isinstance(x, (CompressedSparseMatrix, SparseCoordinateTensor)): + raise NotImplementedError("sparse-sparse multiplication not yet supported") + return dot_coordinate_dense(y, y_dims, x, x_dims) x_native = x.native(x.shape) y_native = y.native(y.shape) backend = choose_backend(x_native, y_native) @@ -2071,6 +2059,8 @@ def scatter(base_grid: Tensor or Shape, assert isinstance(indices_gradient, bool) grid_shape = base_grid if isinstance(base_grid, Shape) else base_grid.shape assert indices.shape.channel.names == ('vector',) or (grid_shape.spatial_rank + grid_shape.instance_rank == 1 and indices.shape.channel_rank == 0) + if 'vector' in indices.shape and indices.shape.get_item_names('vector') and indices.shape.get_item_names('vector') != grid_shape.names: + indices = indices.vector[grid_shape.names] values = wrap(values) batches = values.shape.non_channel.non_instance & indices.shape.non_channel.non_instance channels = grid_shape.channel & values.shape.channel @@ -2098,7 +2088,7 @@ def scatter(base_grid: Tensor or Shape, def scatter_forward(base_grid, indices, values): indices = to_int32(round_(indices)) - native_grid = reshaped_native(base_grid, [batches, *base_grid.shape.instance, *base_grid.shape.spatial, channels], force_expand=True) + native_grid = reshaped_native(base_grid, [batches, *non_batch(base_grid).non_channel, channels], force_expand=True) native_values = reshaped_native(values, [batches, lists, channels], force_expand=True) native_indices = reshaped_native(indices, [batches, lists, 'vector'], force_expand=True) backend = choose_backend(native_indices, native_values, native_grid) @@ -2110,7 +2100,7 @@ def scatter_forward(base_grid, indices, values): count = backend.scatter(zero_grid, native_indices, backend.ones_like(native_values), mode='add') native_result = summed / backend.maximum(count, 1) native_result = backend.where(count == 0, native_grid, native_result) - return reshaped_tensor(native_result, [batches, *instance(base_grid), *spatial(base_grid), channels], check_sizes=True) + return reshaped_tensor(native_result, [batches, *non_batch(base_grid).non_channel, channels], check_sizes=True) def scatter_backward(shaped_base_grid_, shaped_indices_, shaped_values_, output, d_output): from ._nd import spatial_gradient @@ -2293,8 +2283,8 @@ def _assert_close(tensor1: Tensor, tensor2: Tensor, rel_tolerance: float, abs_to tensor1._assert_close(tensor2, rel_tolerance, abs_tolerance, msg, verbose) elif isinstance(tensor2, Layout): tensor2._assert_close(tensor1, rel_tolerance, abs_tolerance, msg, verbose) - elif isinstance(tensor1, CompressedSparseTensor): - if isinstance(tensor2, CompressedSparseTensor): + elif isinstance(tensor1, CompressedSparseMatrix): + if isinstance(tensor2, CompressedSparseMatrix): _assert_close(tensor1._values, tensor2._values, rel_tolerance, abs_tolerance, msg, verbose) _assert_close(tensor1._indices, tensor2._indices, 0, 0, msg, verbose) _assert_close(tensor1._pointers, tensor2._pointers, 0, 0, msg, verbose) @@ -2302,7 +2292,7 @@ def _assert_close(tensor1: Tensor, tensor2: Tensor, rel_tolerance: float, abs_to _assert_close(dense(tensor1), tensor2, rel_tolerance, abs_tolerance, msg, verbose) else: _assert_close(tensor1._values, tensor2._values, rel_tolerance, abs_tolerance, msg, verbose) - elif isinstance(tensor2, CompressedSparseTensor): + elif isinstance(tensor2, CompressedSparseMatrix): return _assert_close(tensor2, tensor1, rel_tolerance, abs_tolerance, msg, verbose) else: def inner_assert_close(tensor1, tensor2): @@ -2442,7 +2432,7 @@ def pairwise_distances(positions: Tensor, max_distance: float or Tensor = None, indices = wrap(indices, instance('nnz')) pointers = wrap(pointers, instance('pointers')) values = wrap(values, instance('nnz'), channel(positions)) - tensors.append(CompressedSparseTensor(indices, pointers, values, others_dims, pos_i_shape)) + tensors.append(CompressedSparseMatrix(indices, pointers, values, others_dims, pos_i_shape)) elif format == 'coo': raise NotImplementedError elif format == 'csc': diff --git a/phi/math/_optimize.py b/phi/math/_optimize.py new file mode 100644 index 000000000..69cab4a4d --- /dev/null +++ b/phi/math/_optimize.py @@ -0,0 +1,580 @@ +import time +import uuid +import warnings +from typing import Callable, Generic, List, TypeVar, Any, Tuple + +import numpy + +from ._shape import EMPTY_SHAPE, Shape, merge_shapes, batch, non_batch, shape, dual, channel, non_dual +from ._magic_ops import stack, copy_with +from ._sparse import native_matrix, SparseCoordinateTensor +from ._tensors import Tensor, disassemble_tree, assemble_tree, wrap, cached +from . import _ops as math +from ._ops import choose_backend_t, zeros_like, all_available, reshaped_native, reshaped_tensor, to_float +from ._trace import matrix_from_function +from ._functional import custom_gradient, LinearFunction +from .backend import Backend +from .backend._backend import SolveResult, PHI_LOGGER + + +X = TypeVar('X') +Y = TypeVar('Y') + + +class Solve(Generic[X, Y]): + """ + Specifies parameters and stopping criteria for solving a minimization problem or system of equations. + """ + + def __init__(self, + method: str, + relative_tolerance: float or Tensor, + absolute_tolerance: float or Tensor, + max_iterations: int or Tensor = 1000, + x0: X or Any = None, + suppress: tuple or list = (), + preprocess_y: Callable = None, + preprocess_y_args: tuple = (), + gradient_solve: 'Solve[Y, X]' or None = None): + assert isinstance(method, str) + self.method: str = method + """ Optimization method to use. Available solvers depend on the solve function that is used to perform the solve. """ + self.relative_tolerance: Tensor = math.to_float(wrap(relative_tolerance)) + """ Relative tolerance for linear solves only. This must be `0` for minimization problems. + For systems of equations *f(x)=y*, the final tolerance is `max(relative_tolerance * norm(y), absolute_tolerance)`. """ + self.absolute_tolerance: Tensor = math.to_float(wrap(absolute_tolerance)) + """ Absolut tolerance for optimization problems and linear solves. + For systems of equations *f(x)=y*, the final tolerance is `max(relative_tolerance * norm(y), absolute_tolerance)`. """ + self.max_iterations: Tensor = math.to_int32(wrap(max_iterations)) + """ Maximum number of iterations to perform before raising a `NotConverged` error is raised. """ + self.x0 = x0 + """ Initial guess for the method, of same type and dimensionality as the solve result. + This property must be set to a value compatible with the solution `x` before running a method. """ + self.preprocess_y: Callable = preprocess_y + """ Function to be applied to the right-hand-side vector of an equation system before solving the system. + This property is propagated to gradient solves by default. """ + self.preprocess_y_args: tuple = preprocess_y_args + assert all(issubclass(err, ConvergenceException) for err in suppress) + self.suppress: tuple = tuple(suppress) + """ Error types to suppress; `tuple` of `ConvergenceException` types. For these errors, the solve function will instead return the partial result without raising the error. """ + self._gradient_solve: Solve[Y, X] = gradient_solve + self.id = str(uuid.uuid4()) + + @property + def gradient_solve(self) -> 'Solve[Y, X]': + """ + Parameters to use for the gradient pass when an implicit gradient is computed. + If `None`, a duplicate of this `Solve` is created for the gradient solve. + + In any case, the gradient solve information will be stored in `gradient_solve.result`. + """ + if self._gradient_solve is None: + self._gradient_solve = Solve(self.method, self.relative_tolerance, self.absolute_tolerance, self.max_iterations, None, self.suppress, self.preprocess_y, self.preprocess_y_args) + return self._gradient_solve + + def __repr__(self): + return f"{self.method} with tolerance {self.relative_tolerance} (rel), {self.absolute_tolerance} (abs), max_iterations={self.max_iterations}" + + def __eq__(self, other): + if not isinstance(other, Solve): + return False + if self.method != other.method \ + or (self.absolute_tolerance != other.absolute_tolerance).any \ + or (self.relative_tolerance != other.relative_tolerance).any \ + or (self.max_iterations != other.max_iterations).any \ + or self.preprocess_y is not other.preprocess_y \ + or self.suppress != other.suppress: + return False + return self.x0 == other.x0 + + def __variable_attrs__(self): + return 'x0', 'preprocess_y_args' + + +class SolveInfo(Generic[X, Y]): + """ + Stores information about the solution or trajectory of a solve. + + When representing the full optimization trajectory, all tracked quantities will have an additional `trajectory` batch dimension. + """ + + def __init__(self, + solve: Solve, + x: X, + residual: Y or None, + iterations: Tensor or None, + function_evaluations: Tensor or None, + converged: Tensor, + diverged: Tensor, + method: str, + msg: str, + solve_time: float): + # tuple.__new__(SolveInfo, (x, residual, iterations, function_evaluations, converged, diverged)) + self.solve: Solve[X, Y] = solve + """ `Solve`, Parameters specified for the solve. """ + self.x: X = x + """ `Tensor` or `PhiTreeNode`, solution estimate. """ + self.residual: Y = residual + """ `Tensor` or `PhiTreeNode`, residual vector for systems of equations or function value for minimization problems. """ + self.iterations: Tensor = iterations + """ `Tensor`, number of performed iterations to reach this state. """ + self.function_evaluations: Tensor = function_evaluations + """ `Tensor`, how often the function (or its gradient function) was called. """ + self.converged: Tensor = converged + """ `Tensor`, whether the residual is within the specified tolerance. """ + self.diverged: Tensor = diverged + """ `Tensor`, whether the solve has diverged at this point. """ + self.method = method + """ `str`, which method and implementation that was used. """ + if not msg and all_available(diverged, converged): + if self.diverged.any: + msg = f"Solve diverged within {iterations if iterations is not None else '?'} iterations using {method}." + elif not self.converged.trajectory[-1].all: + msg = f"Solve did not converge to rel={solve.relative_tolerance}, abs={solve.absolute_tolerance} within {solve.max_iterations} iterations using {method}. Max residual: {[math.max_(t.trajectory[-1]) for t in disassemble_tree(self.residual)[1]]}" + else: + msg = f"Converged within {iterations if iterations is not None else '?'} iterations." + self.msg = msg + """ `str`, termination message """ + self.solve_time = solve_time + """ Time spent in Backend solve function (in seconds) """ + + def __repr__(self): + return self.msg + + def snapshot(self, index): + return SolveInfo(self.solve, self.x.trajectory[index], self.residual.trajectory[index], self.iterations.trajectory[index], self.function_evaluations.trajectory[index], + self.converged.trajectory[index], self.diverged.trajectory[index], self.method, self.msg, self.solve_time) + + def convergence_check(self, only_warn: bool): + if not all_available(self.diverged, self.converged): + return + if self.diverged.any: + if Diverged not in self.solve.suppress: + if only_warn: + warnings.warn(self.msg, ConvergenceWarning) + else: + raise Diverged(self) + if not self.converged.trajectory[-1].all: + if NotConverged not in self.solve.suppress: + if only_warn: + warnings.warn(self.msg, ConvergenceWarning) + else: + raise NotConverged(self) + + +class ConvergenceException(RuntimeError): + """ + Base class for exceptions raised when a solve does not converge. + + See Also: + `Diverged`, `NotConverged`. + """ + + def __init__(self, result: SolveInfo): + RuntimeError.__init__(self, result.msg) + self.result: SolveInfo = result + """ `SolveInfo` holding information about the solve. """ + + +class ConvergenceWarning(RuntimeWarning): + pass + + +class NotConverged(ConvergenceException): + """ + Raised during optimization if the desired accuracy was not reached within the maximum number of iterations. + + This exception inherits from `ConvergenceException`. + + See Also: + `Diverged`. + """ + + def __init__(self, result: SolveInfo): + ConvergenceException.__init__(self, result) + + +class Diverged(ConvergenceException): + """ + Raised if the optimization was stopped prematurely and cannot continue. + This may indicate that no solution exists. + + The values of the last estimate `x` may or may not be finite. + + This exception inherits from `ConvergenceException`. + + See Also: + `NotConverged`. + """ + + def __init__(self, result: SolveInfo): + ConvergenceException.__init__(self, result) + + +class SolveTape: + """ + Used to record additional information about solves invoked via `solve_linear()`, `solve_nonlinear()` or `minimize()`. + While a `SolveTape` is active, certain performance optimizations and algorithm implementations may be disabled. + + To access a `SolveInfo` of a recorded solve, use + ```python + solve = Solve(method, ...) + with SolveTape() as solves: + x = math.solve_linear(f, y, solve) + result: SolveInfo = solves[solve] # get by Solve + result: SolveInfo = solves[0] # get by index + ``` + """ + + def __init__(self, record_trajectories=False): + """ + Args: + record_trajectories: When enabled, the entries of `SolveInfo` will contain an additional batch dimension named `trajectory`. + """ + self.record_trajectories = record_trajectories + self.solves: List[SolveInfo] = [] + self.solve_ids: List[str] = [] + + def __enter__(self): + _SOLVE_TAPES.append(self) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + _SOLVE_TAPES.remove(self) + + def _add(self, solve: Solve, trj: bool, result: SolveInfo): + if any(s.solve.id == solve.id for s in self.solves): + warnings.warn("SolveTape contains two results for the same solve settings. SolveTape[solve] will return the first solve result.", RuntimeWarning) + if self.record_trajectories: + assert trj, "Solve did not record a trajectory." + self.solves.append(result) + elif trj: + self.solves.append(result.snapshot(-1)) + else: + self.solves.append(result) + self.solve_ids.append(solve.id) + + def __getitem__(self, item) -> SolveInfo: + if isinstance(item, int): + return self.solves[item] + else: + assert isinstance(item, Solve) + solves = [s for s in self.solves if s.solve.id == item.id] + if len(solves) == 0: + raise KeyError(f"No solve recorded with key '{item}'.") + assert len(solves) == 1 + return solves[0] + + def __iter__(self): + return iter(self.solves) + + def __len__(self): + return len(self.solves) + + +_SOLVE_TAPES: List[SolveTape] = [] + + +def minimize(f: Callable[[X], Y], solve: Solve[X, Y]) -> X: + """ + Finds a minimum of the scalar function *f(x)*. + The `method` argument of `solve` determines which optimizer is used. + All optimizers supported by `scipy.optimize.minimize` are supported, + see https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html . + Additionally a gradient descent solver with adaptive step size can be used with `method='GD'`. + + `math.minimize()` is limited to backends that support `jacobian()`, i.e. PyTorch, TensorFlow and Jax. + + To obtain additional information about the performed solve, use a `SolveTape`. + + See Also: + `solve_nonlinear()`. + + Args: + f: Function whose output is subject to minimization. + All positional arguments of `f` are optimized and must be `Tensor` or `PhiTreeNode`. + If `solve.x0` is a `tuple` or `list`, it will be passed to *f* as varargs, `f(*x0)`. + To minimize a subset of the positional arguments, define a new (lambda) function depending only on those. + The first return value of `f` must be a scalar float `Tensor` or `PhiTreeNode`. + solve: `Solve` object to specify method type, parameters and initial guess for `x`. + + Returns: + x: solution, the minimum point `x`. + + Raises: + NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. + Diverged: If the optimization failed prematurely. + """ + assert (solve.relative_tolerance == 0).all, f"relative_tolerance must be zero for minimize() but got {solve.relative_tolerance}" + assert solve.preprocess_y is None, "minimize() does not allow preprocess_y" + x0_nest, x0_tensors = disassemble_tree(solve.x0) + x0_tensors = [to_float(t) for t in x0_tensors] + backend = choose_backend_t(*x0_tensors, prefer_default=True) + batch_dims = merge_shapes(*[t.shape for t in x0_tensors]).batch + x0_natives = [] + for t in x0_tensors: + t._expand() + assert t.shape.is_uniform + x0_natives.append(reshaped_native(t, [batch_dims, t.shape.non_batch], force_expand=True)) + x0_flat = backend.concat(x0_natives, -1) + + def unflatten_assemble(x_flat, additional_dims: Shape = EMPTY_SHAPE, convert=True): + i = 0 + x_tensors = [] + for x0_native, x0_tensor in zip(x0_natives, x0_tensors): + vol = backend.shape(x0_native)[-1] + flat_native = x_flat[..., i:i + vol] + x_tensors.append(reshaped_tensor(flat_native, [*additional_dims, batch_dims, x0_tensor.shape.non_batch], convert=convert)) + i += vol + x = assemble_tree(x0_nest, x_tensors) + return x + + def native_function(x_flat): + x = unflatten_assemble(x_flat) + if isinstance(x, (tuple, list)): + y = f(*x) + else: + y = f(x) + _, y_tensors = disassemble_tree(y) + assert not non_batch(y_tensors[0]), f"Failed to minimize '{f.__name__}' because it returned a non-scalar output {shape(y_tensors[0])}. Reduce all non-batch dimensions, e.g. using math.l2_loss()" + try: + loss_native = reshaped_native(y_tensors[0], [batch_dims]) + except AssertionError: + raise AssertionError(f"Failed to minimize '{f.__name__}' because its output loss {shape(y_tensors[0])} has more batch dimensions than the initial guess {batch_dims}.") + return y_tensors[0].sum, (loss_native,) + + atol = backend.to_float(reshaped_native(solve.absolute_tolerance, [batch_dims], force_expand=True)) + maxi = backend.to_int32(reshaped_native(solve.max_iterations, [batch_dims], force_expand=True)) + trj = _SOLVE_TAPES and any(t.record_trajectories for t in _SOLVE_TAPES) + t = time.perf_counter() + ret = backend.minimize(solve.method, native_function, x0_flat, atol, maxi, trj) + t = time.perf_counter() - t + if not trj: + assert isinstance(ret, SolveResult) + converged = reshaped_tensor(ret.converged, [batch_dims]) + diverged = reshaped_tensor(ret.diverged, [batch_dims]) + x = unflatten_assemble(ret.x) + iterations = reshaped_tensor(ret.iterations, [batch_dims]) + function_evaluations = reshaped_tensor(ret.function_evaluations, [batch_dims]) + residual = reshaped_tensor(ret.residual, [batch_dims]) + result = SolveInfo(solve, x, residual, iterations, function_evaluations, converged, diverged, ret.method, ret.message, t) + else: # trajectory + assert isinstance(ret, (tuple, list)) and all(isinstance(r, SolveResult) for r in ret) + converged = reshaped_tensor(ret[-1].converged, [batch_dims]) + diverged = reshaped_tensor(ret[-1].diverged, [batch_dims]) + x = unflatten_assemble(ret[-1].x) + x_ = unflatten_assemble(numpy.stack([r.x for r in ret]), additional_dims=batch('trajectory'), convert=False) + residual = stack([reshaped_tensor(r.residual, [batch_dims]) for r in ret], batch('trajectory')) + iterations = reshaped_tensor(ret[-1].iterations, [batch_dims]) + function_evaluations = stack([reshaped_tensor(r.function_evaluations, [batch_dims]) for r in ret], batch('trajectory')) + result = SolveInfo(solve, x_, residual, iterations, function_evaluations, converged, diverged, ret[-1].method, ret[-1].message, t) + for tape in _SOLVE_TAPES: + tape._add(solve, trj, result) + result.convergence_check(False) # raises ConvergenceException + return x + + +def solve_nonlinear(f: Callable, y, solve: Solve) -> Tensor: + """ + Solves the non-linear equation *f(x) = y* by minimizing the norm of the residual. + + This method is limited to backends that support `jacobian()`, currently PyTorch, TensorFlow and Jax. + + To obtain additional information about the performed solve, use a `SolveTape`. + + See Also: + `minimize()`, `solve_linear()`. + + Args: + f: Function whose output is optimized to match `y`. + All positional arguments of `f` are optimized and must be `Tensor` or `PhiTreeNode`. + The output of `f` must match `y`. + y: Desired output of `f(x)` as `Tensor` or `PhiTreeNode`. + solve: `Solve` object specifying optimization method, parameters and initial guess for `x`. + + Returns: + x: Solution fulfilling `f(x) = y` within specified tolerance as `Tensor` or `PhiTreeNode`. + + Raises: + NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. + Diverged: If the solve failed prematurely. + """ + from ._nd import l2_loss + + if solve.preprocess_y is not None: + y = solve.preprocess_y(y) + + def min_func(x): + diff = f(x) - y + l2 = l2_loss(diff) + return l2 + + rel_tol_to_abs = solve.relative_tolerance * l2_loss(y) + min_solve = copy_with(solve, absolute_tolerance=rel_tol_to_abs, relative_tolerance=0, preprocess_y=None) + return minimize(min_func, min_solve) + + +def solve_linear(f: Callable[[X], Y], + y: Y, + solve: Solve[X, Y], + f_args: tuple or list = (), + f_kwargs: dict = None) -> X: + """ + Solves the system of linear equations *f(x) = y* and returns *x*. + This method will use the solver specified in `solve`. + The following method identifiers are supported by all backends: + + * `'auto'`: Automatically choose a solver + * `'CG'`: Conjugate gradient, only for symmetric and positive definite matrices. + * `'CG-adaptive'`: Conjugate gradient with adaptive step size, only for symmetric and positive definite matrices. + * `'biCG'`: Biconjugate gradient + * `'biCGstab'`: Biconjugate gradient stabilized, first order + * `'biCGstab(2)'`: Biconjugate gradient stabilized, second order + + For maximum performance, compile `f` using `jit_compile_linear()` beforehand. + Then, an optimized representation of `f` (such as a sparse matrix) will be used to solve the linear system. + + To obtain additional information about the performed solve, perform the solve within a `SolveTape` context. + The used implementation can be obtained as `SolveInfo.method`. + + The gradient of this operation will perform another linear solve with the parameters specified by `Solve.gradient_solve`. + + See Also: + `solve_nonlinear()`, `jit_compile_linear()`. + + Args: + f: Linear function with `Tensor` or `PhiTreeNode` first parameter and return value. + `f` can have additional arguments. + y: Desired output of `f(x)` as `Tensor` or `PhiTreeNode`. + solve: `Solve` object specifying optimization method, parameters and initial guess for `x`. + f_args: Additional `Tensor` or `PhiTreeNode` arguments to be passed to `f`. + `f` need not be linear in these arguments. + Use this instead of lambda function since a lambda will not be recognized as calling a jit-compiled function. + f_kwargs: Additional keyword arguments to be passed to `f`. + These arguments are treated as auxiliary arguments and can be of any type. + + Returns: + x: solution of the linear system of equations `f(x) = y` as `Tensor` or `PhiTreeNode`. + + Raises: + NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. + Diverged: If the solve failed prematurely. + """ + y_tree, y_tensors = disassemble_tree(y) + x0_tree, x0_tensors = disassemble_tree(solve.x0) + assert len(x0_tensors) == len(y_tensors) == 1, "Only single-tensor linear solves are currently supported" + backend = choose_backend_t(*y_tensors, *x0_tensors) + prefer_explicit = backend.supports(Backend.sparse_coo_tensor) or backend.supports(Backend.csr_matrix) + + if isinstance(f, LinearFunction) and prefer_explicit: # Matrix solve + matrix, bias = f.sparse_matrix_and_bias(solve.x0, *f_args, **(f_kwargs or {})) + + def _matrix_solve_forward(y, solve: Solve, matrix: Tensor, is_backprop=False): + backend_matrix = native_matrix(matrix) + pattern_dims_in = channel(**dual(matrix).untyped_dict).names + pattern_dims_out = non_dual(matrix).names # batch dims can be sparse or batched matrices + result = _linear_solve_forward(y, solve, backend_matrix, pattern_dims_in, pattern_dims_out, backend, is_backprop) + return result # must return exactly `x` so gradient isn't computed w.r.t. other quantities + + _matrix_solve = attach_gradient_solve(_matrix_solve_forward, auxiliary_args='is_backprop') + return _matrix_solve(y - bias, solve, matrix) + else: # Matrix-free solve + f_args = cached(f_args) + solve = cached(solve) + + def _function_solve_forward(y, solve: Solve, f_args: tuple, f_kwargs: dict = None, is_backprop=False): + y_nest, (y_tensor,) = disassemble_tree(y) + x0_nest, (x0_tensor,) = disassemble_tree(solve.x0) + # active_dims = (y_tensor.shape & x0_tensor.shape).non_batch # assumes batch dimensions are not active + batches = (y_tensor.shape & x0_tensor.shape).batch + + def native_lin_f(native_x, batch_index=None): + if batch_index is not None and batches.volume > 1: + native_x = backend.tile(backend.expand_dims(native_x), [batches.volume, 1]) + x = assemble_tree(x0_nest, [reshaped_tensor(native_x, [batches, non_batch(x0_tensor)] if backend.ndims(native_x) >= 2 else [non_batch(x0_tensor)], convert=False)]) + y = f(x, *f_args, **f_kwargs) + _, (y_tensor,) = disassemble_tree(y) + y_native = reshaped_native(y_tensor, [batches, non_batch(y_tensor)] if backend.ndims(native_x) >= 2 else [non_batch(y_tensor)]) + if batch_index is not None and batches.volume > 1: + y_native = y_native[batch_index] + return y_native + + result = _linear_solve_forward(y, solve, native_lin_f, pattern_dims_in=non_batch(x0_tensor).names, pattern_dims_out=non_batch(y_tensor).names, backend=backend, is_backprop=is_backprop) + return result # must return exactly `x` so gradient isn't computed w.r.t. other quantities + + _function_solve = attach_gradient_solve(_function_solve_forward, auxiliary_args='is_backprop,f_kwargs') + return _function_solve(y, solve, f_args, f_kwargs=f_kwargs or {}) + + +def _linear_solve_forward(y, + solve: Solve, + native_lin_op, + pattern_dims_in: Tuple[str, ...], + pattern_dims_out: Tuple[str, ...], + backend: Backend, + is_backprop: bool) -> Any: + PHI_LOGGER.debug(f"Performing linear solve {solve} with backend {backend}") + if solve.preprocess_y is not None: + y = solve.preprocess_y(y, *solve.preprocess_y_args) + y_nest, (y_tensor,) = disassemble_tree(y) + x0_nest, (x0_tensor,) = disassemble_tree(solve.x0) + pattern_dims_in = x0_tensor.shape.only(pattern_dims_in) + pattern_dims_out = y_tensor.shape.only(pattern_dims_out) + batch_dims = merge_shapes(y_tensor.shape.without(pattern_dims_out), x0_tensor.shape.without(pattern_dims_in)) + x0_native = backend.as_tensor(reshaped_native(x0_tensor, [batch_dims, pattern_dims_in], force_expand=True)) + y_native = backend.as_tensor(reshaped_native(y_tensor, [batch_dims, y_tensor.shape.only(pattern_dims_out)], force_expand=True)) + rtol = backend.as_tensor(reshaped_native(math.to_float(solve.relative_tolerance), [batch_dims], force_expand=True)) + atol = backend.as_tensor(reshaped_native(solve.absolute_tolerance, [batch_dims], force_expand=True)) + maxi = backend.as_tensor(reshaped_native(solve.max_iterations, [batch_dims], force_expand=True)) + trj = _SOLVE_TAPES and any(t.record_trajectories for t in _SOLVE_TAPES) + if trj: + assert all_available(y_tensor, x0_tensor), "Cannot record linear solve in jit mode" + t = time.perf_counter() + ret = backend.linear_solve(solve.method, native_lin_op, y_native, x0_native, rtol, atol, maxi, trj) + t = time.perf_counter() - t + if not trj: + assert isinstance(ret, SolveResult) + converged = reshaped_tensor(ret.converged, [batch_dims]) + diverged = reshaped_tensor(ret.diverged, [batch_dims]) + x = assemble_tree(x0_nest, [reshaped_tensor(ret.x, [batch_dims, pattern_dims_in])]) + iterations = reshaped_tensor(ret.iterations, [batch_dims]) + function_evaluations = reshaped_tensor(ret.function_evaluations, [batch_dims]) + if ret.residual is not None: + residual = assemble_tree(y_nest, [reshaped_tensor(ret.residual, [batch_dims, pattern_dims_out])]) + elif _SOLVE_TAPES: + residual = backend.linear(native_lin_op, ret.x) - y_native + residual = assemble_tree(y_nest, [reshaped_tensor(residual, [batch_dims, pattern_dims_out])]) + else: + residual = None + result = SolveInfo(solve, x, residual, iterations, function_evaluations, converged, diverged, ret.method, ret.message, t) + else: # trajectory + assert isinstance(ret, (tuple, list)) and all(isinstance(r, SolveResult) for r in ret), f"Trajectory recording failed: got {type(ret)}" + converged = reshaped_tensor(ret[-1].converged, [batch_dims]) + diverged = reshaped_tensor(ret[-1].diverged, [batch_dims]) + x = assemble_tree(x0_nest, [reshaped_tensor(ret[-1].x, [batch_dims, pattern_dims_in])]) + x_ = assemble_tree(x0_nest, [stack([reshaped_tensor(r.x, [batch_dims, pattern_dims_in]) for r in ret], batch('trajectory'))]) + residual = assemble_tree(y_nest, [stack([reshaped_tensor(r.residual, [batch_dims, pattern_dims_out]) for r in ret], batch('trajectory'))]) + iterations = reshaped_tensor(ret[-1].iterations, [batch_dims]) + function_evaluations = stack([reshaped_tensor(r.function_evaluations, [batch_dims]) for r in ret], batch('trajectory')) + result = SolveInfo(solve, x_, residual, iterations, function_evaluations, converged, diverged, ret[-1].method, ret[-1].message, t) + for tape in _SOLVE_TAPES: + tape._add(solve, trj, result) + result.convergence_check(is_backprop and 'TensorFlow' in backend.name) # raises ConvergenceException + return x + + +def attach_gradient_solve(forward_solve: Callable, auxiliary_args: str): + def implicit_gradient_solve(kwargs, x, dx): + solve = kwargs['solve'] + matrix = (kwargs['matrix'],) if 'matrix' in kwargs else () + grad_solve = solve.gradient_solve + x0 = grad_solve.x0 if grad_solve.x0 is not None else zeros_like(solve.x0) + grad_solve_ = copy_with(solve.gradient_solve, x0=x0) + if 'is_backprop' in kwargs: + del kwargs['is_backprop'] + dy = solve_with_grad(dx, grad_solve_, *matrix, is_backprop=True, **kwargs) # this should hopefully result in implicit gradients for higher orders as well + return {'y': dy} + + solve_with_grad = custom_gradient(forward_solve, implicit_gradient_solve, auxiliary_args=auxiliary_args) + return solve_with_grad + diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 2395a0da7..3427627e8 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -80,6 +80,15 @@ def _named_sizes(self): def _dimensions(self): return zip(self.sizes, self.names, self.types, self.item_names) + @property + def untyped_dict(self): + """ + Returns: + `dict` containing dimension names as keys. + The values are either the item names as `tuple` if available, otherwise the size. + """ + return {name: self.get_item_names(i) or self.get_size(i) for i, name in enumerate(self.names)} + def __len__(self): return len(self.sizes) @@ -132,20 +141,20 @@ def indices(self, dims: tuple or list or 'Shape') -> Tuple[int]: Returns: Indices as `tuple[int]`. """ - if isinstance(dims, (list, tuple)): + if isinstance(dims, (list, tuple, set)): return tuple([self.index(n) for n in dims]) elif isinstance(dims, Shape): return tuple([self.index(n) for n in dims.names]) else: raise ValueError(f"indices() requires a sequence of dimensions but got {dims}") - def get_size(self, dim: str or 'Shape'): + def get_size(self, dim: str or 'Shape' or int): """ See Also: `Shape.get_sizes()`, `Shape.size` Args: - dim: Dimension, either as name `str` or single-dimension `Shape`. + dim: Dimension, either as name `str` or single-dimension `Shape` or index `int`. Returns: Size associated with `dim` as `int` or `Tensor`. @@ -155,6 +164,8 @@ def get_size(self, dim: str or 'Shape'): elif isinstance(dim, Shape): assert dim.rank == 1, f"get_size() requires a single dimension but got {dim}. Use indices() to get multiple sizes." return self.sizes[self.names.index(dim.name)] + elif isinstance(dim, int): + return self.sizes[dim] else: raise ValueError(f"get_size() requires a single dimension but got {dim}. Use indices() to get multiple sizes.") @@ -591,7 +602,7 @@ def without(self, dims: 'DimFilter') -> 'Shape': dims = dims(self) if isinstance(dims, str): dims = parse_dim_order(dims) - if isinstance(dims, (tuple, list)): + if isinstance(dims, (tuple, list, set)): return self[[i for i in range(self.rank) if self.names[i] not in dims]] elif isinstance(dims, Shape): return self[[i for i in range(self.rank) if self.names[i] not in dims.names]] @@ -624,13 +635,13 @@ def only(self, dims: 'DimFilter', reorder=False): dims = parse_dim_order(dims) if isinstance(dims, Shape): dims = dims.names + if not isinstance(dims, (tuple, list, set)): + raise ValueError(dims) if reorder: - if isinstance(dims, (tuple, list)): - return self[[self.names.index(d) for d in dims if d in self.names]] + return self[[self.names.index(d) for d in dims if d in self.names]] else: - if isinstance(dims, (tuple, list)): - return self[[i for i in range(self.rank) if self.names[i] in dims]] - raise ValueError(dims) + return self[[i for i in range(self.rank) if self.names[i] in dims]] + @property def rank(self) -> int: @@ -778,6 +789,9 @@ def with_sizes(self, sizes: tuple or list or 'Shape', keep_item_names=True): * `tuple` / `list` of same length as `self` containing replacement sizes. * `Shape` of any rank. Replaces sizes for dimensions shared by `sizes` and `self`. + keep_item_names: If `False`, forgets all item names. + If `True`, keeps item names where the size does not change. + Returns: `Shape` with same names and types as `self`. """ @@ -901,13 +915,21 @@ def replace(self, dims: 'Shape' or str or tuple or list, new: 'Shape') -> 'Shape sizes = list(self.sizes) types = list(self.types) item_names = list(self.item_names) + if len(new) > len(dims): # Put all in one spot + assert len(dims) == 1, "Cannot replace 2+ dims by more replacements" + index = self.index(dims[0]) + return concat_shapes(self[:index], new, self[index+1:]) for old_name, new_dim in zip(dims, new): if old_name in self: names[self.index(old_name)] = new_dim.name types[self.index(old_name)] = new_dim.type item_names[self.index(old_name)] = new_dim.item_names[0] sizes[self.index(old_name)] = new_dim.size - return Shape(tuple(sizes), tuple(names), tuple(types), tuple(item_names)) + replaced = Shape(tuple(sizes), tuple(names), tuple(types), tuple(item_names)) + if len(new) == len(dims): + return replaced + to_remove = dims[len(dims) - len(new):] + return replaced.without(to_remove) def _with_types(self, types: 'Shape'): return Shape(self.sizes, self.names, tuple([types.get_type(name) if name in types else self_type for name, self_type in zip(self.names, self.types)]), self.item_names) @@ -1059,6 +1081,10 @@ def meshgrid(self, names=False): else: return + def are_adjacent(self, dims: str or tuple or list or set or 'Shape'): + indices = self.indices(dims) + return (max(indices) - min(indices)) == len(dims) - 1 + def __add__(self, other): return self._op2(other, lambda s, o: s + o, 0) @@ -1098,7 +1124,7 @@ def __hash__(self): EMPTY_SHAPE = Shape((), (), (), ()) """ Empty shape, `()` """ -DimFilter = Union[str, tuple, list, Shape, Callable] +DimFilter = Union[str, tuple, list, set, Shape, Callable] try: DimFilter.__doc__ = """Dimension filters can be used with `Shape.only()` and `Shype.without()`, making them the standard tool for specifying sets of dimensions. diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index 852f823e6..31e561498 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -1,21 +1,50 @@ import warnings from numbers import Number -from typing import List, Callable +from typing import List, Callable, Tuple -from ._shape import Shape, non_batch, merge_shapes, instance, batch, non_instance, shape, channel, spatial -from ._magic_ops import concat +import numpy as np +import scipy.sparse + +from ._shape import Shape, non_batch, merge_shapes, instance, batch, non_instance, shape, channel, spatial, DimFilter, concat_shapes, EMPTY_SHAPE, dual +from ._magic_ops import concat, pack_dims, expand, rename_dims from ._tensors import Tensor, TensorStack, CollapsedTensor, NativeTensor, cached, wrap -from .backend import choose_backend, Backend +from .backend import choose_backend, NUMPY from .backend._dtype import DType class SparseCoordinateTensor(Tensor): def __init__(self, indices: Tensor, values: Tensor, dense_shape: Shape, can_contain_double_entries: bool, indices_sorted: bool): + """ + Construct a sparse tensor with any number of sparse, dense and batch dimensions. + + Args: + indices: `Tensor` encoding the positions of stored values. It has the following dimensions: + + * One instance dimension exactly matching the instance dimension on `values`. + It enumerates the positions of stored entries. + * One channel dimension called `vector`. + Its item names must match the dimension names of `dense_shape` but the order can be arbitrary. + * Any number of batch dimensions + + values: `Tensor` containing the stored values at positions given by `indices`. It has the following dimensions: + + * One instance dimension exactly matching the instance dimension on `indices`. + It enumerates the values of stored entries. + * Any number of channel dimensions if multiple values are stored at each index. + * Any number of batch dimensions + + dense_shape: Dimensions listed in `indices`. + The order can differ from the item names of `indices`. + can_contain_double_entries: Whether some indices might occur more than once. + If so, values at the same index will be summed. + indices_sorted: Whether the indices are sorted in ascending order given the dimension order of the item names of `indices`. + """ assert instance(indices), "indices must have an instance dimension" assert 'vector' in indices.shape, "indices must have a vector dimension" - assert indices.vector.item_names is not None and len(indices.vector.item_names) == non_batch(values).non_channel.rank, "The 'vector' dimension of indices must list the dense dimensions as item names" + assert set(indices.vector.item_names) == set(dense_shape.names), "The 'vector' dimension of indices must list the dense dimensions as item names" self._shape = merge_shapes(dense_shape, batch(indices), non_instance(values)) + self._dense_shape = dense_shape self._indices = indices self._values = values self._can_contain_double_entries = can_contain_double_entries @@ -32,8 +61,108 @@ def dtype(self) -> DType: def native(self, order: str or tuple or list or Shape = None): raise RuntimeError("Sparse tensors do not have a native representation. Use math.dense(tensor).native() instead") + @property + def _is_tracer(self) -> bool: + return self._indices._is_tracer or self._values._is_tracer -class CompressedSparseTensor(Tensor): + def _natives(self) -> tuple: + indices_const = self._indices.default_backend is not self._values.default_backend + if indices_const: + return self._values._natives() # If we return NumPy arrays, they might get converted in function transformations + else: + return self._values._natives() + self._indices._natives() + + def _spec_dict(self) -> dict: + indices_const = self._indices.default_backend is not self._values.default_backend + return {'type': SparseCoordinateTensor, + 'shape': self._shape, + 'dense_shape': self._dense_shape, + 'indices': self._indices if indices_const else self._indices._spec_dict(), + 'values': self._values._spec_dict(), + 'can_contain_double_entries': self._can_contain_double_entries, + 'indices_sorted': self._indices_sorted} + + @classmethod + def _from_spec_and_natives(cls, spec: dict, natives: list): + values = spec['values']['type']._from_spec_and_natives(spec['values'], natives) + indices_or_spec = spec['indices'] + if isinstance(indices_or_spec, Tensor): + indices = indices_or_spec + else: + indices = spec['indices']['type']._from_spec_and_natives(spec['indices'], natives) + return SparseCoordinateTensor(indices, values, spec['dense_shape'], spec['can_contain_double_entries'], spec['indices_sorted']) + + def _native_coo_components(self, col_dims: DimFilter, matrix=False): + col_dims = self._shape.only(col_dims) + row_dims = self._dense_shape.without(col_dims) + row_idx_packed, col_idx_packed = self._pack_indices(row_dims, col_dims) + from phi.math import reshaped_native + ind_batch = batch(self._indices) + channels = non_instance(self._values).without(ind_batch) + if matrix: + native_indices = self.default_backend.stack([row_idx_packed, col_idx_packed], -1) + native_shape = (row_dims.volume, col_dims.volume) + else: + native_indices = reshaped_native(self._indices, [ind_batch, instance, 'vector'], force_expand=True) + native_shape = self._dense_shape.sizes + native_values = reshaped_native(self._values, [ind_batch, instance, channels]) + return ind_batch, channels, native_indices, native_values, native_shape + + def _pack_indices(self, row_dims: Shape, col_dims: Shape): + assert self._indices.default_backend is NUMPY, "Can only compress NumPy indices as of yet" + assert row_dims.without(self._dense_shape).is_empty, f"Can only compress sparse dims but got {row_dims} which contains non-sparse dims" + from ._ops import reshaped_native + row_idx = self._indices[row_dims.names] + col_idx = self._indices[self._dense_shape.without(row_dims).names] + # ToDo if not row_dims: idx = [0] + row_idx_packed = np.ravel_multi_index(reshaped_native(row_idx, [channel, batch, instance]), row_dims.sizes) + col_idx_packed = np.ravel_multi_index(reshaped_native(col_idx, [channel, batch, instance]), col_dims.sizes) + return row_idx_packed, col_idx_packed + + def compress_rows(self): + return self.compress(self._dense_shape.non_dual) + + def compress_cols(self): + return self.compress(self._dense_shape.dual) + + def compress(self, dims: DimFilter): + c_dims = self._shape.only(dims, reorder=True) + u_dims = self._dense_shape.without(c_dims) + c_idx_packed, u_idx_packed = self._pack_indices(c_dims, u_dims) + # --- Use scipy.sparse.csr_matrix to reorder values --- + idx = np.arange(1, c_idx_packed.shape[-1] + 1) # start indexing at 1 since 0 might get removed + scipy_csr = scipy.sparse.csr_matrix((idx, (c_idx_packed[0], u_idx_packed[0])), shape=(c_dims.volume, u_dims.volume)) + assert c_idx_packed.shape[1] == len(scipy_csr.data), "Failed to create CSR matrix because the CSR matrix contains fewer non-zero values than COO. This can happen when the `x` tensor is too small for the stencil." + # --- Construct CompressedSparseMatrix --- + entries_dim = instance(self._values).name + values = self._values[{entries_dim: wrap(scipy_csr.data - 1, instance(entries_dim))}] # Change order accordingly + indices = wrap(scipy_csr.indices, instance(entries_dim)) + pointers = wrap(scipy_csr.indptr, instance('pointers')) + return CompressedSparseMatrix(indices, pointers, values, u_dims, c_dims) + + def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or None, **kwargs) -> 'Tensor': + dims = self._shape.only(dims) + assert dims.without(self._dense_shape).is_empty, "Can only pack sparse dimensions on SparseCoordinateTensor" + assert self._indices.default_backend is NUMPY, "Can only pack NumPy indices as of yet" + from ._ops import reshaped_native + idx = self._indices.vector[dims.names] + idx_packed = np.ravel_multi_index(reshaped_native(idx, [channel, instance]), dims.sizes) + idx_packed = expand(wrap(idx_packed, instance(self._indices)), channel(vector=packed_dim.name)) + indices = concat([self._indices.vector[self._dense_shape.without(dims).names], idx_packed], 'vector') + dense_shape = concat_shapes(self._dense_shape.without(dims), packed_dim.with_size(dims.volume)) + idx_sorted = self._indices_sorted and False # ToDo still sorted if dims are ordered correctly and no other dim in between and inserted at right point + return SparseCoordinateTensor(indices, self._values, dense_shape, self._can_contain_double_entries, idx_sorted) + + def _with_shape_replaced(self, new_shape: Shape): + assert self._shape.rank == new_shape.rank + dense_shape = new_shape[self._shape.indices(self._dense_shape)] + new_item_names = new_shape[self._shape.indices(self._indices.shape.get_item_names('vector'))].names + indices = self._indices._with_shape_replaced(self._indices.shape.replace(self._shape, new_shape).with_dim_size('vector', new_item_names)) + values = self._values._with_shape_replaced(self._values.shape.replace(self._shape, new_shape)) + return SparseCoordinateTensor(indices, values, dense_shape, self._can_contain_double_entries, self._indices_sorted) + + +class CompressedSparseMatrix(Tensor): def __init__(self, indices: Tensor, pointers: Tensor, values: Tensor, uncompressed_dims: Shape, compressed_dims: Shape, uncompressed_offset: int = None): """ @@ -62,6 +191,7 @@ def __init__(self, indices: Tensor, pointers: Tensor, values: Tensor, uncompress assert not channel(indices) and not spatial(indices), f"channel and spatial dimensions not allowed on indices but got {shape(indices)}" assert not channel(pointers) and not spatial(pointers), f"channel and spatial dimensions not allowed on pointers but got {shape(pointers)}" assert uncompressed_dims.only(compressed_dims).is_empty, f"Dimensions cannot be compressed and uncompressed at the same time but got compressed={compressed_dims}, uncompressed={uncompressed_dims}" + assert instance(pointers).size == compressed_dims.volume + 1 self._shape = merge_shapes(compressed_dims, uncompressed_dims, batch(indices), batch(pointers), non_instance(values)) self._indices = indices self._pointers = pointers @@ -91,7 +221,41 @@ def _is_tracer(self) -> bool: return self._values._is_tracer or self._indices._is_tracer or self._pointers._is_tracer def _natives(self) -> tuple: - return self._values._natives() + self._indices._natives() + self._pointers._natives() + indices_const = self._indices.default_backend is not self._values.default_backend + pointers_const = self._pointers.default_backend is not self._values.default_backend + result = self._values._natives() + if not indices_const: + result += self._indices._natives() + if not pointers_const: + result += self._pointers._natives() + return result + + def _spec_dict(self) -> dict: + indices_const = self._indices.default_backend is not self._values.default_backend + pointers_const = self._pointers.default_backend is not self._values.default_backend + return {'type': CompressedSparseMatrix, + 'shape': self._shape, + 'values': self._values._spec_dict(), + 'indices': self._indices if indices_const else self._indices._spec_dict(), + 'pointers': self._pointers if pointers_const else self._pointers._spec_dict(), + 'uncompressed_dims': self._uncompressed_dims, + 'compressed_dims': self._compressed_dims, + 'uncompressed_offset': self._uncompressed_offset} + + @classmethod + def _from_spec_and_natives(cls, spec: dict, natives: list): + values = spec['values']['type']._from_spec_and_natives(spec['values'], natives) + indices_or_spec = spec['indices'] + if isinstance(indices_or_spec, Tensor): + indices = indices_or_spec + else: + indices = spec['indices']['type']._from_spec_and_natives(spec['indices'], natives) + pointers_or_spec = spec['pointers'] + if isinstance(pointers_or_spec, Tensor): + pointers = pointers_or_spec + else: + pointers = spec['pointers']['type']._from_spec_and_natives(spec['pointers'], natives) + return CompressedSparseMatrix(indices, pointers, values, spec['uncompressed_dims'], spec['compressed_dims'], spec['uncompressed_offset']) def _getitem(self, selection: dict) -> 'Tensor': batch_selection = {dim: selection[dim] for dim in self._shape.only(tuple(selection)).names} @@ -137,10 +301,10 @@ def _getitem(self, selection: dict) -> 'Tensor': uncompressed = uncompressed.after_gather({uncompressed.name: ind_sel}) else: raise NotImplementedError - return CompressedSparseTensor(indices, pointers, values, uncompressed, compressed, uncompressed_offset) + return CompressedSparseMatrix(indices, pointers, values, uncompressed, compressed, uncompressed_offset) - def __concat__(self, tensors: tuple, dim: str, **kwargs) -> 'CompressedSparseTensor': - if not all(isinstance(t, CompressedSparseTensor) for t in tensors): + def __concat__(self, tensors: tuple, dim: str, **kwargs) -> 'CompressedSparseMatrix': + if not all(isinstance(t, CompressedSparseMatrix) for t in tensors): return NotImplemented if dim == self._compressed_dims[0].name: indices = concat([t._indices for t in tensors], instance(self._indices), **kwargs) @@ -153,14 +317,14 @@ def __concat__(self, tensors: tuple, dim: str, **kwargs) -> 'CompressedSparseTen assert pointer_offset == instance(indices).volume pointers = concat(pointers, instance(self._pointers)) compressed = self._compressed_dims.with_dim_size(dim, sum(t.shape.get_size(dim) for t in tensors)) - return CompressedSparseTensor(indices, pointers, values, self._uncompressed_dims, compressed, self._uncompressed_offset) + return CompressedSparseMatrix(indices, pointers, values, self._uncompressed_dims, compressed, self._uncompressed_offset) elif dim == self._uncompressed_dims[0].name: if all(t._indices is self._indices and t._pointers is self._pointers for t in tensors): # ToDo test if offsets match and ordered correctly from ._ops import sum_ values = sum_([t._values for t in tensors], '0') uncompressed = self._uncompressed_dims.with_dim_size(dim, sum(t.shape.get_size(dim) for t in tensors)) - return CompressedSparseTensor(self._indices, self._pointers, values, uncompressed, self._compressed_dims, uncompressed_offset=None) + return CompressedSparseMatrix(self._indices, self._pointers, values, uncompressed, self._compressed_dims, uncompressed_offset=None) else: raise NotImplementedError("concatenating arbitrary compressed sparse tensors along uncompressed dim is not yet supported") else: @@ -174,7 +338,7 @@ def _op2(self, other, operator: Callable, native_function: Callable, op_name: st affects_only_values = self.sparse_dims not in other_shape and non_instance(self._indices).only(other_shape).is_empty if affects_only_values: return self._with_values(operator(self._values, other)) - elif isinstance(other, CompressedSparseTensor): + elif isinstance(other, CompressedSparseMatrix): if other._indices is self._indices and other._pointers is self._pointers: return self._with_values(operator(self._values, other._values)) elif op_symbol == '+': @@ -185,7 +349,11 @@ def _op2(self, other, operator: Callable, native_function: Callable, op_name: st raise NotImplementedError def _with_values(self, new_values: Tensor): - return CompressedSparseTensor(self._indices, self._pointers, new_values, self._uncompressed_dims, self._compressed_dims) + return CompressedSparseMatrix(self._indices, self._pointers, new_values, self._uncompressed_dims, self._compressed_dims) + + def _with_shape_replaced(self, new_shape: Shape): + assert self._shape.rank == new_shape.rank + raise NotImplementedError def _native_csr_components(self): from phi.math import reshaped_native @@ -203,6 +371,37 @@ def _native_csr_components(self): def native(self, order: str or tuple or list or Shape = None): raise RuntimeError("Sparse tensors do not have a native representation. Use math.dense(tensor).native() instead") + def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or None, **kwargs) -> 'Tensor': + assert all(d in self._shape for d in dims) + dims = self._shape.only(dims, reorder=True) + if dims.only(self._compressed_dims).is_empty: # pack cols + assert self._uncompressed_dims.are_adjacent(dims), f"Can only compress adjacent dims but got {dims} for matrix {self._shape}" + uncompressed_dims = self._uncompressed_dims.replace(dims, packed_dim.with_size(dims.volume)) + return CompressedSparseMatrix(self._indices, self._pointers, self._values, uncompressed_dims, self._compressed_dims, self._uncompressed_offset) + elif dims.only(self._uncompressed_dims).is_empty: # pack rows + assert self._compressed_dims.are_adjacent(dims), f"Can only compress adjacent dims but got {dims} for matrix {self._shape}" + compressed_dims = self._compressed_dims.replace(dims, packed_dim.with_size(dims.volume)) + return CompressedSparseMatrix(self._indices, self._pointers, self._values, self._uncompressed_dims, compressed_dims, self._uncompressed_offset) + else: + raise NotImplementedError(f"Cannot pack dimensions from both columns and rows with compressed sparse matrices but got {dims}") + +def sparse_dims(x: Tensor) -> Shape: + """ + Returns the dimensions of a `Tensor` that are explicitly stored in a sparse format. + + Args: + x: Any `Tensor` + + Returns: + `Shape` + """ + if isinstance(x, SparseCoordinateTensor): + return x._dense_shape + elif isinstance(x, CompressedSparseMatrix): + return concat_shapes(x._compressed_dims, x._uncompressed_dims) + else: + return EMPTY_SHAPE + def get_sparsity(x: Tensor): """ @@ -250,7 +449,7 @@ def stored_values(x: Tensor) -> List[Tensor]: return [cached(x)] if x.is_cached else stored_values(x._inner) elif isinstance(x, TensorStack): return [cached(x)] if x.is_cached else sum([stored_values(t) for t in x._tensors], []) - elif isinstance(x, CompressedSparseTensor): + elif isinstance(x, CompressedSparseMatrix): return [x._values] elif isinstance(x, SparseCoordinateTensor): if x._can_contain_double_entries: @@ -276,9 +475,12 @@ def dense(x: Tensor) -> Tensor: """ from phi.math import reshaped_tensor if isinstance(x, SparseCoordinateTensor): - raise NotImplementedError - native_dense = x.default_backend.coo_to_dense() - elif isinstance(x, CompressedSparseTensor): + from ._ops import scatter, zeros + base_grid = zeros(spatial(**x.shape.untyped_dict), dtype=x.dtype) + result_sp = scatter(base_grid, x._indices, x._values, mode='add', outside_handling='undefined') + result = rename_dims(result_sp, shape, x.shape) + return result + elif isinstance(x, CompressedSparseMatrix): ind_batch, channels, native_indices, native_pointers, native_values, native_shape = x._native_csr_components() native_dense = x.default_backend.csr_to_dense(native_indices, native_pointers, native_values, native_shape) return reshaped_tensor(native_dense, [ind_batch, x._compressed_dims, x._uncompressed_dims, channels]) @@ -290,7 +492,7 @@ def dense(x: Tensor) -> Tensor: return wrap(x) -def dot_compressed_dense(compressed: CompressedSparseTensor, cdims: Shape, dense: Tensor, ddims: Shape): +def dot_compressed_dense(compressed: CompressedSparseMatrix, cdims: Shape, dense: Tensor, ddims: Shape): from phi.math import reshaped_native, reshaped_tensor backend = choose_backend(*compressed._natives() + dense._natives()) if compressed._uncompressed_dims in cdims: # proper matrix-vector multiplication @@ -302,3 +504,45 @@ def dot_compressed_dense(compressed: CompressedSparseTensor, cdims: Shape, dense return result else: # transposed matrix vector multiplication. This is inefficient raise NotImplementedError("Transposed sparse matrix multiplication not yet implemented") + + +def dot_coordinate_dense(sparse: SparseCoordinateTensor, sdims: Shape, dense: Tensor, ddims: Shape): + from phi.math import reshaped_native, reshaped_tensor + backend = choose_backend(*sparse._natives() + dense._natives()) + ind_batch, channels, native_indices, native_values, native_shape = sparse._native_coo_components(sdims, matrix=True) + rhs_channels = shape(dense).without(ddims).without(channels) + dense_native = reshaped_native(dense, [ind_batch, ddims, channels, rhs_channels], force_expand=True) + result_native = backend.mul_coo_dense(native_indices, native_values, native_shape, dense_native) + result = reshaped_tensor(result_native, [ind_batch, channels, sparse._dense_shape.without(sdims), rhs_channels]) + return result + + +def native_matrix(value: Tensor): + cols = dual(value) + rows = non_batch(value).non_dual + if isinstance(value, SparseCoordinateTensor): + ind_batch, channels, indices, values, shape = value._native_coo_components(dual, matrix=True) + if ind_batch.volume > 1 or channels.volume > 1: + return value.default_backend.sparse_coo_tensor_batched(indices, values, shape) + else: + return value.default_backend.sparse_coo_tensor(indices[0], values[0, :, 0], shape) + elif isinstance(value, CompressedSparseMatrix): + assert not non_instance(value._values), f"native_matrix does not support vector-valued matrices. Vector dims: {non_batch(value).without(sparse_dims(value))}" + ind_batch, channels, indices, pointers, values, shape = value._native_csr_components() + if dual(value._uncompressed_dims): # CSR + assert not dual(value._compressed_dims), "Dual dimensions on both compressed and uncompressed dimensions" + if ind_batch.volume > 1 or channels.volume > 1: + return value.default_backend.csr_matrix_batched(indices, pointers, values, shape) + else: + return value.default_backend.csr_matrix(indices[0], pointers[0], values[0, :, 0], shape) + else: # CSC + assert not dual(value._uncompressed_dims) + if ind_batch.volume > 1 or channels.volume > 1: + return value.default_backend.csc_matrix_batched(pointers, indices, values, shape) + else: + return value.default_backend.csc_matrix(pointers[0], indices[0], values[0, :, 0], shape) + else: + v = pack_dims(value, rows, channel('_row')) + v = pack_dims(v, cols, channel('_col')) + from ._ops import reshaped_native + return reshaped_native(v, [batch, '_row', '_col']) \ No newline at end of file diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index 5042acc13..dcd0c377a 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -9,11 +9,11 @@ import numpy import numpy as np -from ._magic_ops import PhiTreeNodeType, variable_attributes, copy_with, stack +from ._magic_ops import PhiTreeNodeType, variable_attributes, copy_with, stack, pack_dims, expand from ._shape import (Shape, CHANNEL_DIM, BATCH_DIM, SPATIAL_DIM, EMPTY_SHAPE, parse_dim_order, shape_stack, merge_shapes, channel, concat_shapes, - TYPE_ABBR, IncompatibleShapes, INSTANCE_DIM, _construct_shape, batch) + TYPE_ABBR, IncompatibleShapes, INSTANCE_DIM, batch, spatial, dual) from .backend import NoBackendFound, choose_backend, BACKENDS, get_precision, default_backend, convert as convert_, \ Backend, ComputeDevice from .backend._dtype import DType, combine_types @@ -55,7 +55,7 @@ def native(self, order: str or tuple or list or Shape = None): Raises: ValueError if the tensor cannot be transposed to match target_shape """ - raise NotImplementedError() + raise NotImplementedError(self.__class__) def numpy(self, order: str or tuple or list or Shape = None) -> np.ndarray: """ @@ -328,8 +328,11 @@ def available(self) -> bool: See Also: `phi.math.jit_compile()`. """ - from ._ops import all_available - return all_available(self) + if self._is_tracer: + return False + natives = self._natives() + natives_available = [choose_backend(native).is_available(native) for native in natives] + return all(natives_available) @property def device(self) -> ComputeDevice or None: @@ -673,6 +676,19 @@ def __iter__(self): native = reshaped_native(self, [self.shape]) return iter(native) + def __matmul__(self, other): + assert isinstance(other, Tensor), f"Matmul '@' requires two Tensor arguments but got {type(other)}" + dims = batch(**self.shape.dual.untyped_dict).names + match = other.shape.only(dims) + assert len(dims) == match.rank, f"Dual dimensions {dual} do not match shape of second argument {other.shape}" + left_arg = pack_dims(self, dual, dual('_reduce')) if len(dims) > 1 else self + right_arg = pack_dims(other, match, channel('_reduce')) + from ._ops import dot + return dot(left_arg, dual, right_arg, '_reduce') + + # def __rmatmul__(self, other): + + def _tensor(self, other): if isinstance(other, Tensor): return other @@ -722,6 +738,13 @@ def _op2(self, other, operator: Callable, native_function: Callable, op_name: st def _natives(self) -> tuple: raise NotImplementedError(self.__class__) + def _spec_dict(self) -> dict: + raise NotImplementedError(self.__class__) + + @classmethod + def _from_spec_and_natives(cls, spec: dict, natives: list): + raise NotImplementedError(cls) + def _expand(self): """ Expands all compressed tensors to their defined size as if they were being used in `Tensor.native()`. """ warnings.warn("Tensor._expand() is deprecated, use cached(Tensor) instead.", DeprecationWarning) @@ -1070,7 +1093,7 @@ def native(self, order: str or tuple or list or Shape = None): for name in order: if name not in self.shape: native = self.default_backend.expand_dims(native, axis=-1) - shape = concat_shapes(shape, _construct_shape('tmp_perm', **{name: 1})) + shape = concat_shapes(shape, batch(**{name: 1})) # --- Transpose --- perm = shape._perm(order) native = self.default_backend.transpose(native, perm) # this will cast automatically @@ -1154,6 +1177,13 @@ def _op2(self, other, operator, native_function, op_name: str = 'unknown', op_sy def _natives(self) -> tuple: return self._native, + def _spec_dict(self) -> dict: + return {'type': NativeTensor, 'shape': self._shape} + + @classmethod + def _from_spec_and_natives(cls, spec: dict, natives: list): + return NativeTensor(natives.pop(0), spec['shape']) + def _expand(self): pass @@ -1168,20 +1198,21 @@ class CollapsedTensor(Tensor): # package-private The method `Tensor._expand()` causes a full Tensor structure to cache collapsed dimensions and must be called before gradients are recorded. """ - def __init__(self, tensor: Tensor, shape: Shape): - for name in tensor.shape.names: + def __init__(self, inner: Tensor, shape: Shape): + assert inner.shape != shape + for name in inner.shape.names: assert name in shape - for size, name, dim_type, *_ in tensor.shape._dimensions: + for size, name, dim_type, *_ in inner.shape._dimensions: assert wrap(shape.get_size(name) == size).all, f"Shape mismatch while trying to set {name}={shape.get_size(name)} but has size {size}" assert shape.get_type(name) == dim_type, f"Dimension type mismatch for dimension '{name}': {shape.get_type(name)}, {dim_type}" - if isinstance(tensor, CollapsedTensor): - if tensor.is_cached: - self._inner = tensor._cached + if isinstance(inner, CollapsedTensor): + if inner.is_cached: + self._inner = inner._cached else: - self._inner = tensor._inner + self._inner = inner._inner assert self._inner is not None else: - self._inner = tensor # this will be set to None once cached. Otherwise gradients will be incorrect. + self._inner = inner # this will be set to None once cached. Otherwise gradients will be incorrect. self._shape = shape self._cached = None # NativeTensor. Once cached, use only _cached @@ -1244,7 +1275,7 @@ def unstack(self, dimension): unstacked = self._inner.unstack(dimension) return tuple(CollapsedTensor(t, unstacked_shape) for t in unstacked) else: - return (CollapsedTensor(self._inner, unstacked_shape),) * self.shape.get_size(dimension) + return (expand(self._inner, unstacked_shape),) * self.shape.get_size(dimension) def _with_shape_replaced(self, new_shape: Shape): if self.is_cached: @@ -1270,7 +1301,7 @@ def _getitem(self, selection: dict): inner = self._inner._getitem(inner_dict) new_shape = self.shape.after_gather(selection) merge_shapes(inner.shape, new_shape) # check that sizes match - return CollapsedTensor(inner, new_shape) + return expand(inner, new_shape) def flip(self, *dims: str) -> 'Tensor': if self.is_cached: @@ -1292,12 +1323,13 @@ def _op2(self, other, operator, native_function, op_name: str = 'unknown', op_sy if isinstance(other_t, CollapsedTensor) and other_t.is_cached: other_t = other_t._cached if isinstance(other_t, NativeTensor): - if all([dim in other_t.shape for dim in self._shape.names]): # other is dense and has all dimensions + if self._shape in other_t.shape: return op2_native(self, other_t, native_function) + if isinstance(other_t, (NativeTensor, CollapsedTensor)): + if isinstance(other_t, CollapsedTensor): + other_inner = other_t._inner # case that other is cached handled above else: - other_t = CollapsedTensor(other_t, other_t.shape) - if isinstance(other_t, CollapsedTensor): - other_inner = other_t._inner # case that other is cached handled above + other_inner = other_t self_inner = self._cached if self.is_cached else self._inner inner = operator(self_inner, other_inner) if all(dim in inner.shape for dim in self.shape.names + other_t.shape.names): # shape already complete @@ -1317,6 +1349,21 @@ def _natives(self) -> tuple: else: return self._inner._natives() + def _spec_dict(self) -> dict: + if self.is_cached: + return self._cached._spec_dict() + else: + return {'type': CollapsedTensor, 'shape': self._shape, 'inner': self._inner._spec_dict()} + + @classmethod + def _from_spec_and_natives(cls, spec: dict, natives: list): + shape0 = choose_backend(natives[0]).staticshape(natives[0]) + if len(shape0) > spec['inner']['shape'].rank: # new native is expanded + assert len(shape0) == spec['shape'].rank + return NativeTensor(natives[0], spec['shape']) + inner = spec['inner']['type']._from_spec_and_natives(spec['inner'], natives) + return CollapsedTensor(inner, spec['shape']) + def _with_natives_replaced(self, natives: list): assert self.is_cached, "Cannot replace natives in uncached state. Expand tensor beforehand." return self._cached._with_natives_replaced(natives) @@ -1338,6 +1385,7 @@ class TensorStack(Tensor): def __init__(self, components: tuple or list, stack_dim: Shape): assert isinstance(stack_dim, Shape) and stack_dim.rank == 1, f"stack_dim must be a single-dimension Shape object but got {type(stack_dim)}" + # assert len(components) > 1, "Use a CollapsedTensor instead" for t in components: assert isinstance(t, Tensor) assert stack_dim.name not in t.shape, f"Cannot stack along '{stack_dim.name}' because the dimension already exists." @@ -1485,6 +1533,17 @@ def _natives(self) -> tuple: else: return sum([t._natives() for t in self._tensors], ()) + def _spec_dict(self) -> dict: + if self._cached is not None: + return self._cached._spec_dict() + else: + return {'type': TensorStack, 'stack_dim': self.stack_dim, 'tensors': [t._spec_dict() for t in self._tensors]} + + @classmethod + def _from_spec_and_natives(cls, spec: dict, natives: list): + tensors = [t['type']._from_spec_and_natives(t, natives) for t in spec['tensors']] + return TensorStack(tensors, spec['stack_dim']) + def _with_natives_replaced(self, natives: list): if self._cached is not None: return self._cached._with_natives_replaced(natives) @@ -1803,64 +1862,33 @@ def custom_op2(x: Tensor or float, y: Tensor or float, l_operator, l_native_func return result -def disassemble_tensors(obj: Tensor or Tuple[Tensor, ...] or List[Tensor], expand: bool) -> tuple: +def disassemble_tensors(tensors: Tuple[Tensor, ...] or List[Tensor], expand: bool) -> Tuple[tuple, Tuple[Shape], tuple]: """ Args: - obj: Tuple or list of Tensors. + tensors: Tuple or list of Tensors. expand: Whether to add collapsed dimensions to the native tensors. Returns: natives: tuple of native tensors - shapes: tuple of Shapes encoding the tensor dimensions including collapsed dims. - native_dims: tuple of Shapes representing the dimensions of the natives in the correct order. + specs: Identification primitives from which the tensor can be reconstructed given the natives. + One per tensor. """ - assert isinstance(obj, (Tensor, tuple, list)), f"jit-compiled function returned {type(obj)} but must return either a 'phi.math.Tensor' or tuple/list of tensors." - if isinstance(obj, Tensor): - if expand or isinstance(obj, TensorStack): - obj._expand() - if isinstance(obj, CollapsedTensor) and obj._inner is not None: - native_dims = obj._inner.shape - else: - native_dims = EMPTY_SHAPE - return obj._natives(), obj.shape, native_dims - else: - assert isinstance(obj, (tuple, list)) - dis = [disassemble_tensors(t, expand=expand) for t in obj] - return sum([i[0] for i in dis], ()), tuple(i[1] for i in dis), tuple(i[2] for i in dis) + for t in tensors: + if isinstance(t, TensorStack) or expand: + t._expand() + natives = sum([t._natives() for t in tensors], ()) + shapes = tuple([t.shape for t in tensors]) + specs = tuple([t._spec_dict() for t in tensors]) + return natives, shapes, specs -def assemble_tensors(natives: tuple, shapes: Shape or Tuple[Shape], native_dims: Tuple[Shape, ...] or None): +def assemble_tensors(natives: tuple or list, specs: Tuple[dict, ...] or List[dict]): natives = list(natives) - if isinstance(shapes, Shape): - return _assemble_pop(natives, shapes, native_dims) - else: - return [_assemble_pop(natives, shape, None if native_dims is None else native_dims[i]) for i, shape in enumerate(shapes)] - - -def _assemble_pop(natives: list, shape: Shape, native_dims: Shape or None): - if shape.is_uniform: - native = natives.pop(0) - ndim = choose_backend(native).ndims(native) - if ndim != shape.rank: - if ndim == 0 and shape.rank > 0: - inner = NativeTensor(native, EMPTY_SHAPE) - return CollapsedTensor(inner, shape) - else: - assert native_dims is not None, "Cannot restore CollapsedTensor from native and shape when native_dims are not specified." - inner = NativeTensor(native, native_dims) - return CollapsedTensor(inner, shape) - return NativeTensor(native, shape) - else: - s2 = shape.shape.without('dims') - if len(s2) > 1: - raise NotImplementedError('More than one non-uniform dimension not supported.') - shapes = shape.unstack(s2.name) - tensors = [NativeTensor(natives.pop(0), s) for s in shapes] - return TensorStack(tensors, s2) - - - - + result = [] + for spec in specs: + t = spec['type']._from_spec_and_natives(spec, natives) + result.append(t) + return result MISSING_TENSOR = 'missing' @@ -2309,7 +2337,10 @@ def is_unexpected_dtype(dtype: DType): def format_tracer(self: Tensor, options: PrintOptions) -> str: colors = options.get_colors() - return f"{colors.shape(self.shape)} {colors.dtype(self.dtype)} {colors.value(f'{self.default_backend} tracer')}" + if self._is_tracer: + return f"{colors.shape(self.shape)} {colors.dtype(self.dtype)} {colors.value(f'linear tracer for {self.default_backend}')}" + else: + return f"{colors.shape(self.shape)} {colors.dtype(self.dtype)} {colors.value(f'{self.default_backend} tracer')}" def format_full(value: Tensor, options: PrintOptions) -> str: # multi-line content @@ -2332,18 +2363,21 @@ def format_full(value: Tensor, options: PrintOptions) -> str: # multi-line cont else: text = np.array2string(value.numpy(value.shape), separator=', ', max_line_width=np.inf) lines.append(text) - elif value.shape.spatial_rank == 1: - for index_dict in value.shape.non_spatial.meshgrid(names=True): + elif value.shape.spatial_rank in (1, 2): + if value.shape.non_spatial.volume > 1: + indices = [f"{colors.shape(', '.join(f'{name}={idx}' for name, idx in index_dict.items()))}" for index_dict in value.shape.non_spatial.meshgrid(names=True)] + max_index_length = max(len(index) for index in indices) + for i, index_dict in enumerate(value.shape.non_spatial.meshgrid(names=True)): + row = "" if value.shape.non_spatial.volume > 1: - lines.append(f"--- {colors.shape(', '.join(f'{name}={idx}' for name, idx in index_dict.items()))} ---") - text = np.array2string(value[index_dict].numpy(dim_order), separator=', ', max_line_width=np.inf) - lines.append(' ' + re.sub('[\\[\\]]', '', text)) - elif value.shape.spatial_rank == 2: - for index_dict in value.shape.non_spatial.meshgrid(names=True): - if value.shape.non_spatial.volume > 1: - lines.append(f"--- {colors.shape(', '.join(f'{name}={idx}' for name, idx in index_dict.items()))} ---") - text = np.array2string(value[index_dict].numpy(dim_order)[::-1], separator=', ', max_line_width=np.inf) - lines.append(' ' + re.sub('[\\[\\]]', '', re.sub('\\],', '', text))) + row += indices[i] + " " * (max_index_length - len(indices[i]) + 2) + if value.shape.spatial_rank == 2: + row += "\n" + if value.shape.spatial_rank == 1: + text = np.array2string(value[index_dict].numpy(dim_order), separator=', ', max_line_width=np.inf) + else: + text = " " + np.array2string(value[index_dict].numpy(dim_order)[::-1], separator=', ', max_line_width=np.inf) + lines.append(row + colors.value(re.sub('[\\[\\]]', '', text)) + (f" along {colors.shape(spatial(value))}" if options.include_shape is not False else "")) else: raise NotImplementedError('Can only print tensors with up to 2 spatial dimensions.') return "\n".join(lines) @@ -2423,6 +2457,8 @@ def _format_number(num, options: PrintOptions, dtype: DType): def format_tensor(self: Tensor, options: PrintOptions) -> str: + if not self.available: + return format_tracer(self, options) from ._sparse import dense self = dense(self) if options.layout == 'auto': diff --git a/phi/math/_trace.py b/phi/math/_trace.py new file mode 100644 index 000000000..97526123a --- /dev/null +++ b/phi/math/_trace.py @@ -0,0 +1,305 @@ +from typing import Callable, Dict, Set, Tuple + +import numpy +import numpy as np + +from .backend import choose_backend, NUMPY, Backend +from ._shape import Shape, parse_dim_order, merge_shapes, spatial, instance, batch, concat_shapes, EMPTY_SHAPE, dual, channel, non_batch +from ._tensors import Tensor, wrap, disassemble_tree, disassemble_tensors, assemble_tree +from ._sparse import SparseCoordinateTensor +from . import _ops as math + + +def matrix_from_function(f: Callable, + *args, + auxiliary_args=None, + auto_compress=True, + sparsify_batch=None, + **kwargs) -> Tuple[Tensor, Tensor]: + """ + Trace a linear function and construct a (sparse) matrix. + + Args: + f: Function to trace. + *args: Arguments for `f`. + auxiliary_args: Arguments in which the function is not linear. + These parameters are not traced but passed on as given in `args` and `kwargs`. + auto_compress: If `True`, returns a compressed matrix if supported by the backend. + sparsify_batch: If `False`, the matrix will be batched. + If `True`, will create dual dimensions for the involved batch dimensions. + This will result in one large matrix instead of a batch of matrices. + **kwargs: Keyword arguments for `f`. + + Returns: + Matrix representing `f`. + """ + assert isinstance(auxiliary_args, str) or auxiliary_args is None, f"auxiliary_args must be a comma-separated str but got {auxiliary_args}" + from ._functional import function_parameters, f_name + f_params = function_parameters(f) + aux = set(s.strip() for s in auxiliary_args.split(',') if s.strip()) if isinstance(auxiliary_args, str) else f_params[1:] + all_args = {**kwargs, **{f_params[i]: v for i, v in enumerate(args)}} + aux_args = {k: v for k, v in all_args.items() if k in aux} + trace_args = {k: v for k, v in all_args.items() if k not in aux} + tree, tensors = disassemble_tree(trace_args) + # tracing = not math.all_available(*tensors) + natives, shapes, native_dims = disassemble_tensors(tensors, expand=False) + # --- Trace function --- + with NUMPY: + x = math.ones(shapes[0]) + tracer = ShiftLinTracer(x, {EMPTY_SHAPE: math.ones()}, x.shape, math.zeros(x.shape)) + x_kwargs = assemble_tree(tree, [tracer]) + result = f(**x_kwargs, **aux_args) + _, result_tensors = disassemble_tree(result) + assert len(result_tensors) == 1, f"Linear function output must be or contain a single Tensor but got {result}" + tracer_out = result_tensors[0]._simplify() + assert tracer_out._is_tracer, f"Tracing linear function '{f_name(f)}' failed. Make sure only linear operations are used. Output: {tracer_out.shape}" + assert isinstance(tracer_out, ShiftLinTracer), f"Tracing linear function '{f_name(f)}' returned a nested tracer with Shape {tracer_out.shape}. Make sure no additional dimensions get added to the output." + assert batch(tracer_out.pattern_dims).is_empty, f"Batch dimensions may not be sliced in linear operations but got pattern for {batch(tracer_out.pattern_dims)}" + # --- Convert to COO --- + if sparsify_batch is None: + if auto_compress: + sparsify_batch = not tracer_out.default_backend.supports(Backend.csr_matrix_batched) + else: + sparsify_batch = not tracer_out.default_backend.supports(Backend.sparse_coo_tensor_batched) + independent_dims = tracer_out.source.shape.without(tracer_out.dependent_dims if sparsify_batch else tracer_out.pattern_dim_names) # these will be parallelized and not added to the matrix + out_shape = tracer_out.shape.without(independent_dims) + typed_src_shape = tracer_out.source.shape.without(independent_dims) + src_shape = dual(**typed_src_shape.untyped_dict) + batch_val = merge_shapes(*tracer_out.val.values()).without(out_shape) + if non_batch(out_shape).is_empty: + assert len(tracer_out.val) == 1 and non_batch(tracer_out.val[EMPTY_SHAPE]) == EMPTY_SHAPE + return tracer_out.val[EMPTY_SHAPE], tracer_out.bias + out_indices = [] + src_indices = [] + values = [] + for shift_, shift_val in tracer_out.val.items(): + if shift_val.default_backend is NUMPY: # sparsify stencil further + native_shift_values = math.reshaped_native(shift_val, [batch_val, *out_shape], force_expand=True) + mask = np.sum(abs(native_shift_values), 0) # only 0 where no batch entry has a non-zero value + out_indices.append(numpy.nonzero(mask)) + src_indices.append([(component + shift_.get_size(dim)) % typed_src_shape.get_size(dim) if dim in shift_ else component for component, dim in zip(out_indices[-1], out_shape)]) + values.append(native_shift_values[(slice(None), *out_indices[-1])]) + else: # add full stencil tensor + all_indices = np.unravel_index(np.arange(out_shape.volume), out_shape.sizes) if out_shape else 0 + out_indices.append(all_indices) + src_indices.append([(component + shift_.get_size(dim)) % typed_src_shape.get_size(dim) if dim in shift_ else component for component, dim in zip(out_indices[-1], out_shape)]) + values.append(math.reshaped_native(shift_val, [batch_val, out_shape], force_expand=True)) + indices_np = np.concatenate([np.concatenate(src_indices, axis=1), np.concatenate(out_indices, axis=1)]).T + # _, counts = np.unique(indices_np, axis=1, return_counts=True) + # assert np.all(counts == 1) + indices = wrap(indices_np, instance('entries'), channel(vector=src_shape.names + out_shape.names)) + backend = choose_backend(*values) + values = math.reshaped_tensor(backend.concat(values, axis=-1), [batch_val, instance('entries')]) + dense_shape = concat_shapes(src_shape & out_shape) + matrix = SparseCoordinateTensor(indices, values, dense_shape, can_contain_double_entries=False, indices_sorted=False) + if not auto_compress: + return matrix, tracer_out.bias + backend = choose_backend(*values._natives()) + if backend.supports(Backend.mul_csr_dense): + return matrix.compress_rows(), tracer_out.bias + # elif backend.supports(Backend.mul_csc_dense): + # return matrix.compress_cols(), tracer_out.bias + else: + return matrix, tracer_out.bias + + +class ShiftLinTracer(Tensor): + """ + Tracer object for linear and affine functions. + The sparsity pattern is assumed equal for all grid cells and is reflected in `val` (e.g. for a 5-point stencil, `val` has 5 items). + The Tensors stored in `val` include position-dependent dimensions, allowing for different stencils at different positions. + Dimensions not contained in any `val` Tensor are treated as independent (batch dimensions). + """ + + def __init__(self, source: Tensor, values_by_shift: dict, shape: Shape, bias: Tensor): + """ + Args: + source: placeholder tensor + values_by_shift: `dict` mapping relative shifts (`Shape`) to value Tensors. + Shape keys only contain non-zero shift dims. Missing dims are interpreted as independent. + shape: shape of this tensor + bias: Constant Tensor to be added to the multiplication output, A*x + b. + A bias naturally arises at boundary cells with non-trivial boundary conditions if no ghost cells are added to the matrix. + When non-zero, this tracer technically represents an affine function, not a linear one. + However, the bias can be subtracted from the solution vector when solving a linear system, allowing this function to be solved with regular linear system solvers. + """ + self.source = source + self.val: Dict[Shape, Tensor] = simplify_add(values_by_shift) + for shift_ in self.val.keys(): + assert shift_.only(sorted(shift_.names), reorder=True) == shift_ + self.bias = bias + self._shape = shape + + def __repr__(self): + return f"Linear tracer {self._shape}" + + def native(self, order: str or tuple or list or Shape = None): + """ + Evaluates the value of the linear operation applied to the original source tensor. + + This is done by building a sparse matrix for all dimensions that are affected by the linear operation. + These dimensions are detected automatically during the creation of the linear operation. + All other dimensions (independent dimensions) are combined into a single batch dimensions for the sparse matrix multiplication. + + Args: + order: str or tuple or list: (Default value = None) + + Returns: + + """ + order = parse_dim_order(order, check_rank=self.rank) + result = self.apply(self.source) + result_order = order if order is not None else self._shape.names + return result.native(result_order) + + @property + def dependent_dims(self): + """ + Dimensions relevant to the linear operation. + This includes `pattern_dims` as well as dimensions along which only the values vary. + These dimensions cannot be parallelized trivially with a non-batched matrix. + """ + return merge_shapes(*[t.shape for t in self.val.values()]) + + @property + def pattern_dim_names(self) -> Set[str]: + """ + Dimensions along which the sparse matrix contains off-diagonal elements. + These dimensions must be part of the sparse matrix and cannot be parallelized. + """ + return set(sum([offset.names for offset in self.val], ())) + + @property + def pattern_dims(self) -> Shape: + return self.source.shape.only(self.pattern_dim_names) + + @property + def dtype(self): + return self.source.dtype + + @property + def shape(self): + return self._shape + + def _with_shape_replaced(self, new_shape): + raise NotImplementedError() + + @property + def _is_tracer(self) -> bool: + return True + + def _getitem(self, selection: dict): + starts = {dim: (item.start or 0) if isinstance(item, slice) else item for dim, item in selection.items()} + new_shape = math.zeros(self._shape)[selection].shape + return self.shift(starts, new_shape, lambda v: v[selection], lambda b: b[selection]) + + def shift(self, shifts: dict, + new_shape: Shape, + val_fun: Callable, + bias_fun: Callable = None): + """ + Shifts all values of this tensor by `shifts`. + Values shifted outside will be mapped with periodic boundary conditions when the matrix is built. + + Args: + shifts: Offsets by dimension + new_shape: Shape of the shifted tensor, must match the shape returned by `val_fun`. + val_fun: Function to apply to the matrix values, may change the tensor shapes + bias_fun: Function to apply to the bias vector, may change the tensor shape + + Returns: + Shifted tensor, possibly with altered values. + """ + val = {} + for shift, values in self.val.items(): + assert isinstance(shift, Shape) + for dim, delta in reversed(tuple(shifts.items())): + if dim not in values.shape: + values = math.expand(values, self._shape.only(dim)) # dim order may be scrambled + if delta: + shift = shift._replace_single_size(dim, shift.get_size(dim) + delta) if dim in shift else shift._expand(spatial(**{dim: delta})) + val[shift.only(sorted(shift.names), reorder=True)] = val_fun(values) + bias = bias_fun(self.bias) + return ShiftLinTracer(self.source, val, new_shape, bias) + + def unstack(self, dimension): + raise NotImplementedError() + + def __neg__(self): + return ShiftLinTracer(self.source, {shift: -values for shift, values in self.val.items()}, self._shape, -self.bias) + + def _op1(self, native_function): + # __neg__ is the only proper linear op1 and is implemented above. + if native_function.__name__ == 'isfinite': + test_output = self.apply(math.ones_like(self.source)) + return math.is_finite(test_output) + else: + raise NotImplementedError('Only linear operations are supported') + + def _op2(self, other: Tensor, + operator: Callable, + native_function: Callable, + op_name: str = 'unknown', + op_symbol: str = '?') -> 'ShiftLinTracer': + """ + Tensor-tensor operation. + + Args: + other: + operator: + native_function: + """ + assert op_symbol in '+-*/', f"Unsupported operation encountered while tracing linear function: {native_function}" + zeros_for_missing_self = op_name not in ['add', 'radd', 'rsub'] # perform `operator` where `self == 0` + zeros_for_missing_other = op_name not in ['add', 'radd', 'sub'] # perform `operator` where `other == 0` + + if isinstance(other, ShiftLinTracer): + assert self.source is other.source, "Multiple linear tracers are not yet supported." + assert set(self._shape) == set(other._shape), f"Tracers have different shapes: {self._shape} and {other._shape}" + values = {} + for dim_shift in self.val.keys(): + if dim_shift in other.val: + values[dim_shift] = operator(self.val[dim_shift], other.val[dim_shift]) + else: + if zeros_for_missing_other: + values[dim_shift] = operator(self.val[dim_shift], math.zeros_like(self.val[dim_shift])) + else: + values[dim_shift] = self.val[dim_shift] + for dim_shift, other_values in other.val.items(): + if dim_shift not in self.val: + if zeros_for_missing_self: + values[dim_shift] = operator(math.zeros_like(other_values), other_values) + else: + values[dim_shift] = other_values + bias = operator(self.bias, other.bias) + return ShiftLinTracer(self.source, values, self._shape, bias) + else: + other = self._tensor(other) + if op_symbol in '*/': + values = {} + for dim_shift, val in self.val.items(): + values[dim_shift] = operator(val, other) + bias = operator(self.bias, other) + return ShiftLinTracer(self.source, values, self._shape & other.shape, bias) + elif op_symbol in '+-': + bias = operator(self.bias, other) + return ShiftLinTracer(self.source, self.val, self._shape & other.shape, bias) + else: + raise ValueError(f"Unsupported operation encountered while tracing linear function: {native_function}") + + def _natives(self) -> tuple: + """ + This function should only be used to determine the compatible backends, this tensor should be regarded as not available. + """ + return sum([v._natives() for v in self.val.values()], ()) + self.bias._natives() + + +def simplify_add(val: dict) -> Dict[Shape, Tensor]: + result = {} + for shift, values in val.items(): + shift = shift[[i for i, size in enumerate(shift.sizes) if size != 0]] # discard zeros + if shift in result: + result[shift] += values + else: + result[shift] = values + return result diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index c373b0655..41e507e1b 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -2,8 +2,9 @@ import warnings from collections import namedtuple from contextlib import contextmanager +from dataclasses import dataclass from threading import Barrier -from typing import List, Callable, TypeVar, Tuple +from typing import List, Callable, TypeVar, Tuple, Any import logging import numpy @@ -864,7 +865,7 @@ def sparse_coo_tensor(self, indices: tuple or list, values, shape: tuple): `Backend.csr_matrix()`, `Backend.csc_matrix()`. Args: - indices: 2D tensor of shape `(2, n)` or tuple/list of two 1D tensors `(rows, cols)`. + indices: 2D tensor of shape `(nnz, dims)`. values: 1D values tensor matching `indices` shape: Shape of the sparse matrix @@ -873,6 +874,15 @@ def sparse_coo_tensor(self, indices: tuple or list, values, shape: tuple): """ raise NotImplementedError(self) + def sparse_coo_tensor_batched(self, indices: tuple or list, values, shape: tuple): + """ + Args: + indices: shape (batch_size, dims, nnz) + values: Values tensor matching `indices`, shape (batch_size, nnz) + shape: tuple of two ints representing the dense shape, (dims...) + """ + raise NotImplementedError(self) + def mul_coo_dense(self, indices, values, shape, dense): """ Multiply a batch of sparse coordinate matrices by a batch of dense matrices. @@ -904,7 +914,7 @@ def coo_to_dense(self, indices, values, shape, contains_duplicates: bool): result = self.scatter(base, indices, values, mode='add' if contains_duplicates else 'update') return result - def csr_matrix(self, column_indices, row_pointers, values, shape: tuple): + def csr_matrix(self, column_indices, row_pointers, values, shape: Tuple[int, int]): """ Create a sparse matrix in compressed sparse row (CSR) format. @@ -924,7 +934,17 @@ def csr_matrix(self, column_indices, row_pointers, values, shape: tuple): """ raise NotImplementedError(self) - def mul_csr_dense(self, column_indices, row_pointers, values, shape: tuple, dense): + def csr_matrix_batched(self, column_indices, row_pointers, values, shape: Tuple[int, int]): + """ + Args: + column_indices: Column indices corresponding to `values`, shape (batch_size, nnz) + row_pointers: Indices in `values` where any row starts, shape (batch_size, rows+1) + values: Non-zero values, shape (batch_size, nnz, channels) + shape: tuple of two ints representing the dense shape, (cols, rows) + """ + raise NotImplementedError(self) + + def mul_csr_dense(self, column_indices, row_pointers, values, shape: Tuple[int, int], dense): """ Multiply a batch of compressed sparse row matrices by a batch of dense matrices. @@ -974,11 +994,11 @@ def csr_to_coo(self, column_indices, row_pointers): row_indices = [self.repeat(self.range(row_count, dtype=self.dtype(column_indices)), repeats[b], -1) for b in range(batch_size)] return self.stack([self.stack(row_indices), column_indices], axis=-1) - def csr_to_dense(self, column_indices, row_pointers, values, shape: tuple): + def csr_to_dense(self, column_indices, row_pointers, values, shape: Tuple[int, int]): indices = self.csr_to_coo(column_indices, row_pointers) return self.coo_to_dense(indices, values, shape, contains_duplicates=False) - def csc_matrix(self, column_pointers, row_indices, values, shape: tuple): + def csc_matrix(self, column_pointers, row_indices, values, shape: Tuple[int, int]): """ Create a sparse matrix in compressed sparse column (CSC) format. @@ -998,6 +1018,16 @@ def csc_matrix(self, column_pointers, row_indices, values, shape: tuple): """ raise NotImplementedError(self) + def csc_matrix_batched(self, column_pointers, row_indices, values, shape: Tuple[int, int]): + """ + Args: + column_pointers: Indices in `values` where any row starts, shape (batch_size, cols+1) + row_indices: Row indices corresponding to `values`, shape (batch_size, nnz) + values: Non-zero values, shape (batch_size, nnz, channels) + shape: tuple of two ints representing the dense shape, (cols, rows) + """ + raise NotImplementedError(self) + def coordinates(self, tensor): """ Returns the coordinates and values of a tensor. diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index 2c87c915b..937178461 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -347,8 +347,7 @@ def indexed_segment_sum(self, x, indices, axis: int): return np.stack([np.add.reduceat(x[b], indices[b], axis-1) for b in range(x.shape[0])]) def sparse_coo_tensor(self, indices, values, shape): - if not isinstance(indices, (tuple, list)): - indices = self.unstack(indices, -1) + indices = self.unstack(indices, -1) if len(shape) == 2: return scipy.sparse.coo_matrix((values, indices), shape=shape) else: @@ -357,9 +356,6 @@ def sparse_coo_tensor(self, indices, values, shape): def csr_matrix(self, column_indices, row_pointers, values, shape: tuple): return scipy.sparse.csr_matrix((values, column_indices, row_pointers), shape=shape) - def csc_matrix(self, column_pointers, row_indices, values, shape: tuple): - return scipy.sparse.csc_matrix((values, row_indices, column_pointers), shape=shape) - def mul_csr_dense(self, column_indices, row_pointers, values, shape: tuple, dense): batch_size, nnz, channel_count = values.shape result = [] @@ -371,6 +367,9 @@ def mul_csr_dense(self, column_indices, row_pointers, values, shape: tuple, dens result.append(np.stack(b_result)) return np.stack(result) + def csc_matrix(self, column_pointers, row_indices, values, shape: tuple): + return scipy.sparse.csc_matrix((values, row_indices, column_pointers), shape=shape) + def coordinates(self, tensor): assert scipy.sparse.issparse(tensor) coo = tensor.tocoo() @@ -466,10 +465,6 @@ def scipy_iterative_sparse_solve(self, lin, y, x0, rtol, atol, max_iter, scipy_f batch_size = combined_dim(bs_y, bs_x0) # if callable(A): # A = LinearOperator(dtype=y.dtype, shape=(self.staticshape(y)[-1], self.staticshape(x0)[-1]), matvec=A) - if isinstance(lin, (tuple, list)): - assert len(lin) == batch_size - else: - lin = [lin] * batch_size def count_callback(x_n): # called after each step, not with x0 iterations[b] += 1 @@ -479,7 +474,8 @@ def count_callback(x_n): # called after each step, not with x0 converged = [] diverged = [] for b in range(batch_size): - x, ret_val = scipy_function(lin[b], y[b], x0=x0[b], tol=rtol[b], atol=atol[b], maxiter=max_iter[b], callback=count_callback) + lin_b = lin[min(b, len(lin)-1)] if isinstance(lin, (tuple, list, np.ndarray)) else lin + x, ret_val = scipy_function(lin_b, y[b], x0=x0[b], tol=rtol[b], atol=atol[b], maxiter=max_iter[b], callback=count_callback) # ret_val: 0=success, >0=not converged, <0=error xs.append(x) converged.append(ret_val == 0) diff --git a/phi/math/extrapolation.py b/phi/math/extrapolation.py index 8e5303238..5536d894f 100644 --- a/phi/math/extrapolation.py +++ b/phi/math/extrapolation.py @@ -11,7 +11,7 @@ from phi.math.backend._backend import get_spatial_derivative_order from .backend import choose_backend from ._shape import Shape, channel, spatial, EMPTY_SHAPE, merge_shapes -from ._magic_ops import concat, stack +from ._magic_ops import concat, stack, expand from ._tensors import Tensor, NativeTensor, CollapsedTensor, TensorStack, wrap from . import _ops as math # TODO this executes _ops.py, can we avoid this? @@ -76,7 +76,7 @@ def pad(self, value: Tensor, widths: dict, **kwargs) -> Tensor: Returns: Padded `Tensor` """ - from phi.math._functional import ShiftLinTracer + from phi.math._trace import ShiftLinTracer if isinstance(value, ShiftLinTracer): lower = {dim: -lo for dim, (lo, _) in widths.items()} return value.shift(lower, new_shape=value.shape.after_pad(widths), val_fun=lambda v: ZERO.pad(v, widths, **kwargs), bias_fun=lambda b: self.pad(b, widths, **kwargs)) @@ -267,7 +267,7 @@ def pad(self, value: Tensor, widths: dict, **kwargs): else: delta = sum(widths[dim]) if isinstance(widths[dim], (tuple, list)) else 2 * widths[dim] new_sizes.append(size + int(delta)) - return CollapsedTensor(value._inner, value.shape.after_pad(widths)) + return expand(value._inner, value.shape.after_pad(widths)) elif isinstance(value, TensorStack): if not value.requires_broadcast: return self.pad(value._cache(), widths) @@ -390,7 +390,7 @@ def valid_outer_faces(self, dim): def pad(self, value: Tensor, widths: dict, **kwargs) -> Tensor: value = value._simplify() - from phi.math._functional import ShiftLinTracer + from phi.math._trace import ShiftLinTracer if isinstance(value, NativeTensor): native = value._native ordered_pad_widths = order_by_shape(value.shape, widths, default=(0, 0)) @@ -403,7 +403,7 @@ def pad(self, value: Tensor, widths: dict, **kwargs) -> Tensor: inner_widths = {dim: w for dim, w in widths.items() if dim in inner.shape} if len(inner_widths) > 0: inner = self.pad(inner, widths) - return CollapsedTensor(inner, value.shape.after_pad(widths)) + return expand(inner, value.shape.after_pad(widths)) elif isinstance(value, TensorStack): if not value.requires_broadcast: return self.pad(value._cache(), widths) diff --git a/phi/math/magic.py b/phi/math/magic.py index 27e5652eb..c475c6116 100644 --- a/phi/math/magic.py +++ b/phi/math/magic.py @@ -698,23 +698,22 @@ def slicing_dict(obj, item) -> dict: if isinstance(item, tuple): if item[0] == Ellipsis: assert len(item) - 1 == shape(obj).channel_rank - item = {name: selection for name, selection in zip(channel(obj).names, item[1:])} + return {name: selection for name, selection in zip(channel(obj).names, item[1:])} elif len(item) == shape(obj).channel_rank: - warnings.warn("NumPy-style slicing for more than one channel dimension is highly discouraged. Use a dict or the special slicing syntax value.dim[slice] instead. See https://tum-pbs.github.io/PhiFlow/Math.html", SyntaxWarning, stacklevel=3) - item = {name: selection for name, selection in zip(channel(obj).names, item)} - elif len(item) == shape(obj).rank: # legacy indexing - warnings.warn("NumPy-style slicing for non-channel dimensions is highly discouraged. Use a dict or the special slicing syntax value.dim[slice] instead. See https://tum-pbs.github.io/PhiFlow/Math.html", SyntaxWarning, stacklevel=3) - item = {name: selection for name, selection in zip(obj.shape.names, item)} + if len(item) > 1: + warnings.warn("NumPy-style slicing for more than one channel dimension is highly discouraged. Use a dict or the special slicing syntax value.dim[slice] instead. See https://tum-pbs.github.io/PhiFlow/Math.html", SyntaxWarning, stacklevel=3) + return {name: selection for name, selection in zip(channel(obj).names, item)} + elif shape(obj).channel_rank == 1 and all(isinstance(e, str) for e in item): + return {channel(obj).name: item} else: raise AssertionError(f"Cannot slice {obj}[{item}]. Use a dict or the special slicing syntax value.dim[slice] instead. See https://tum-pbs.github.io/PhiFlow/Math.html") else: if shape(obj).channel_rank == 1: - item = {channel(obj).name: item} + return {channel(obj).name: item} elif non_batch(obj).rank == 1: - item = {non_batch(obj).name: item} + return {non_batch(obj).name: item} else: raise AssertionError(f"Slicing {type(obj).__name__}[{type(item).__name__}] is only supported for 1D values (excluding batch dimensions) but shape is {shape(obj)}") - return item class OtherMagicFunctions: diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index 346e52a77..44d917297 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -112,7 +112,7 @@ def make_incompressible(velocity: GridType, return velocity, pressure -@math.jit_compile_linear # jit compilation is required for boundary conditions that add a constant offset solving Ax + b = y +@math.jit_compile_linear(auxiliary_args='hard_bcs,active,order,implicit', forget_traces=True) # jit compilation is required for boundary conditions that add a constant offset solving Ax + b = y def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid, order=2, implicit: Solve = None) -> CenteredGrid: """ Computes the laplace of `pressure` in the presence of obstacles. diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index 3e4308327..c10c8acfd 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -598,9 +598,7 @@ def dtype(self, array) -> DType: def sparse_coo_tensor(self, indices, values, shape): with self._device_for(indices, values): - indices = [tf.convert_to_tensor(i, tf.int64) for i in indices] - indices = tf.cast(tf.stack(indices, axis=-1), tf.int64) - return tf.SparseTensor(indices=indices, values=values, dense_shape=shape) + return tf.SparseTensor(indices=self.to_int64(indices), values=values, dense_shape=shape) def mul_coo_dense(self, indices, values, shape, dense): values, dense = self.auto_cast(values, dense) diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index 46d02171c..44aa9fade 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -623,28 +623,65 @@ def repeat(self, x, repeats, axis: int): return torch.repeat_interleave(self.as_tensor(x), repeats, axis) def sparse_coo_tensor(self, indices, values, shape): - indices_ = self.to_int64(indices) - values_ = self.to_float(values) - if not self.is_available(values_): - # the output of torch.sparse_coo_tensor is considered constant - @torch.jit.script - def sparse_coo_tensor(values, indices, cols: int, rows: int, dtype: torch.dtype) -> torch.sparse.Tensor: - size = torch.Size([cols, rows]) - return torch.sparse_coo_tensor(indices, values, size=size, dtype=dtype) - result = sparse_coo_tensor(values_, indices_, shape[0], shape[1], to_torch_dtype(self.float_type)) + indices = self.to_int64(indices) + indices = self.transpose(indices, [1, 0]) + values = self.to_float(values) + + @torch.jit.script # the output of torch.sparse_coo_tensor is considered constant + def sparse_coo_tensor(values, indices, cols: int, rows: int, dtype: torch.dtype) -> torch.sparse.Tensor: + size = torch.Size([cols, rows]) + return torch.sparse_coo_tensor(indices, values, size=size, dtype=dtype) + + return sparse_coo_tensor(values, indices, shape[0], shape[1], to_torch_dtype(self.float_type)) + + def csr_matrix(self, column_indices, row_pointers, values, shape: Tuple[int, int]): + row_pointers = self.as_tensor(row_pointers) + column_indices = self.as_tensor(column_indices) + return torch.sparse_csr_tensor(row_pointers, column_indices, values, shape, device=values.device) + + # def csr_matrix_batched(self, column_indices, row_pointers, values, shape: Tuple[int, int]): + # batch_size, nnz, channels = values.shape + # if version.parse(torch.__version__) >= version.parse('1.13.0'): + # return torch.sparse_csr_tensor(row_pointers, column_indices, values, (batch_size, *shape, channels), device=values.device) + # else: + # warnings.warn("PyTorch >= 1.13 is required for batched CSR matrices. Visit https://pytorch.org/ to download the latest version.", RuntimeWarning) + # raise NotImplementedError + # # matrices = [] + # # for b in range(batch_size): + # # if values.shape[-1] == 1: + # # b_matrix = torch.sparse_csr_tensor(row_pointers[b], column_indices[b], values[b, :, 0], shape, device=values.device) + # # else: + # # raise NotImplementedError + # # matrices.append(b_matrix) + # # return matrices + + def csc_matrix(self, column_pointers, row_indices, values, shape: tuple): + batch_size, nnz, channels = values.shape + if version.parse(torch.__version__) >= version.parse('1.13.0'): + return torch.sparse_csc_tensor(column_pointers, row_indices, values, (batch_size, *shape, channels), device=values.device) else: - result = torch.sparse_coo_tensor(indices_, values_, shape, dtype=to_torch_dtype(self.float_type)) - return result + warnings.warn("PyTorch >= 1.13 is required for batched CSC matrices. Visit https://pytorch.org/ to download the latest version.", RuntimeWarning) + raise NotImplementedError + # batch_size, nnz, channels = values.shape + # if batch_size == channels == 1: + # return scipy.sparse.csc_matrix((values[0, :, 0], row_indices[0], column_pointers[0]), shape=shape) + # matrices = [] + # for b in range(batch_size): + # if values.shape[-1] == 1: + # b_matrix = scipy.sparse.csc_matrix((values[b, :, 0], row_indices[b], column_pointers[b]), shape=shape) + # else: + # raise NotImplementedError + # matrices.append(b_matrix) + # return matrices def mul_csr_dense(self, column_indices, row_pointers, values, shape: tuple, dense): values, dense = self.auto_cast(values, dense, bool_to_int=True, int_to_float=True) - batch_size, nnz, channel_count = values.shape + batch_size, nnz, channels = values.shape result = [] for b in range(batch_size): b_result = [] - for c in range(channel_count): + for c in range(channels): matrix = torch.sparse_csr_tensor(row_pointers[b], column_indices[b], values[b, :, c], shape, device=values.device) - # mat = scipy.sparse.csr_matrix((values[b, :, c], column_indices[b], row_pointers[b]), shape=shape) b_result.append(torch.sparse.mm(matrix, self.as_tensor(dense[b, :, c, :]))) result.append(torch.stack(b_result)) return torch.stack(result) @@ -668,7 +705,7 @@ def conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj: bool) -> Sol if callable(lin) or trj: assert self.is_available(y), "Tracing conjugate_gradient with linear operator is not yet supported." return Backend.conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj) - assert isinstance(lin, torch.Tensor) and lin.is_sparse, "Batched matrices are not yet supported" + assert isinstance(lin, torch.Tensor) and (lin.is_sparse or lin.is_sparse_csr), "Batched matrices are not yet supported" y = self.to_float(y) x0 = self.copy(self.to_float(x0)) rtol = self.as_tensor(rtol) @@ -681,7 +718,7 @@ def conjugate_gradient_adaptive(self, lin, y, x0, rtol, atol, max_iter, trj: boo if callable(lin) or trj: assert self.is_available(y), "Tracing conjugate_gradient with linear operator is not yet supported." return Backend.conjugate_gradient_adaptive(self, lin, y, x0, rtol, atol, max_iter, trj) - assert isinstance(lin, torch.Tensor) and lin.is_sparse, "Batched matrices are not yet supported" + assert isinstance(lin, torch.Tensor), "Batched matrices are not yet supported" y = self.to_float(y) x0 = self.copy(self.to_float(x0)) rtol = self.as_tensor(rtol) diff --git a/tests/commit/math/test__functional.py b/tests/commit/math/test__functional.py index 5ad3fd10d..2781a70c8 100644 --- a/tests/commit/math/test__functional.py +++ b/tests/commit/math/test__functional.py @@ -308,31 +308,23 @@ def f(x, y): assert x_.shape == x.shape math.assert_close(x, x_) - def test_hessian(self): - def f(x, y): - return math.l1_loss(x ** 2 * y), x, y - - eval_hessian = math.hessian(f, wrt='x', get_output=True, get_gradient=True, dim_suffixes=('1', '2')) - - for backend in BACKENDS: - if backend.supports(Backend.hessian): - with backend: - x = math.tensor([(0.01, 1, 2)], channel('vector', 'v')) - y = math.tensor([1., 2.], batch('batch')) - (L, x, y), g, H, = eval_hessian(x, y) - math.assert_close(L, [5.0001, 10.0002], msg=backend.name) - math.assert_close(g.batch[0].vector[0], (0.02, 2, 4), msg=backend.name) - math.assert_close(g.batch[1].vector[0], (0.04, 4, 8), msg=backend.name) - math.assert_close(2, H.v1[0].v2[0].batch[0], H.v1[1].v2[1].batch[0], H.v1[2].v2[2].batch[0], msg=backend.name) - math.assert_close(4, H.v1[0].v2[0].batch[1], H.v1[1].v2[1].batch[1], H.v1[2].v2[2].batch[1], msg=backend.name) - - def test_sparse_matrix(self): - for backend in BACKENDS: - with backend: - for f in ['csr', 'csc', 'coo']: - matrix = math.jit_compile_linear(math.laplace).sparse_matrix(math.zeros(spatial(x=5)), format=f) - self.assertEqual(f, matrix.indexing_type) - self.assertEqual((5, 5), matrix.shape) + # def test_hessian(self): + # def f(x, y): + # return math.l1_loss(x ** 2 * y), x, y + # + # eval_hessian = math.hessian(f, wrt='x', get_output=True, get_gradient=True, dim_suffixes=('1', '2')) + # + # for backend in BACKENDS: + # if backend.supports(Backend.hessian): + # with backend: + # x = math.tensor([(0.01, 1, 2)], channel('vector', 'v')) + # y = math.tensor([1., 2.], batch('batch')) + # (L, x, y), g, H, = eval_hessian(x, y) + # math.assert_close(L, [5.0001, 10.0002], msg=backend.name) + # math.assert_close(g.batch[0].vector[0], (0.02, 2, 4), msg=backend.name) + # math.assert_close(g.batch[1].vector[0], (0.04, 4, 8), msg=backend.name) + # math.assert_close(2, H.v1[0].v2[0].batch[0], H.v1[1].v2[1].batch[0], H.v1[2].v2[2].batch[0], msg=backend.name) + # math.assert_close(4, H.v1[0].v2[0].batch[1], H.v1[1].v2[1].batch[1], H.v1[2].v2[2].batch[1], msg=backend.name) def test_loss_batch_not_reduced(self): def loss_function(x): diff --git a/tests/commit/math/test__ops.py b/tests/commit/math/test__ops.py index 6b2698225..79e61eb37 100644 --- a/tests/commit/math/test__ops.py +++ b/tests/commit/math/test__ops.py @@ -641,14 +641,15 @@ def test_numpy(self): assert_close(math.numpy(math.tensor(nat)), nat) def test_sparse(self): - i = [[0, 1, 1], + idx = [[0, 1, 1], [2, 0, 2]] v = [3, 4, 5] shape = (2, 3) for backend in BACKENDS: if backend.supports(Backend.sparse_coo_tensor): with backend: - matrix = backend.sparse_coo_tensor(i, v, shape) + idx_ = backend.transpose(backend.as_tensor(idx), [1, 0]) + matrix = backend.sparse_coo_tensor(idx_, v, shape) i_, v_ = backend.coordinates(matrix) self.assertIsInstance(i_, tuple, msg=backend.name) assert len(i_) == 2 diff --git a/tests/commit/math/test__sparse.py b/tests/commit/math/test__sparse.py index 1d93e920b..61612d7d7 100644 --- a/tests/commit/math/test__sparse.py +++ b/tests/commit/math/test__sparse.py @@ -2,8 +2,8 @@ import phi from phi import math -from phi.math import batch, get_sparsity, expand, wrap, stack, zeros, channel, spatial, ones, instance, tensor, sum, pairwise_distances, vec_length, dense, assert_close -from phi.math._sparse import SparseCoordinateTensor, CompressedSparseTensor +from phi.math import batch, get_sparsity, expand, wrap, stack, zeros, channel, spatial, ones, instance, tensor, sum, pairwise_distances, vec_length, dense, assert_close, non_dual +from phi.math._sparse import SparseCoordinateTensor, CompressedSparseMatrix BACKENDS = phi.detect_backends() @@ -15,8 +15,8 @@ def test_sparsity(self): self.assertEqual(0.25, get_sparsity(expand(1., batch(b=4)))) self.assertEqual(0.25, get_sparsity(stack([zeros(batch(b=4))] * 3, channel('vector')))) self.assertEqual(0.3, get_sparsity(SparseCoordinateTensor(ones(instance(nnz=3), channel(vector='x')), ones(instance(nnz=3)), spatial(x=10), True, False))) - self.assertEqual(0.03, get_sparsity(CompressedSparseTensor(indices=ones(instance(nnz=3)), - pointers=ones(instance(y_pointers=4)), + self.assertEqual(0.03, get_sparsity(CompressedSparseMatrix(indices=ones(instance(nnz=3)), + pointers=ones(instance(y_pointers=11)), values=ones(instance(nnz=3)), uncompressed_dims=spatial(x=10), compressed_dims=spatial(y=10)))) @@ -27,7 +27,7 @@ def test_csr(self): indices = tensor([0, 1, 0], instance('nnz')) pointers = tensor([0, 2, 3, 3], instance('pointers')) values = tensor([2, 3, 4], instance('nnz')) - matrix = CompressedSparseTensor(indices, pointers, values, channel(right=3), channel(down=3)) + matrix = CompressedSparseMatrix(indices, pointers, values, channel(right=3), channel(down=3)) math.print(dense(matrix)) assert_close((2, 3, 0), dense(matrix).down[0]) assert_close((4, 0, 0), dense(matrix).down[1]) @@ -54,4 +54,13 @@ def test_csr_slice_concat(self): concat_others = math.concat([dx.others[:1], dx.others[1:]], 'others') math.assert_close(dx, concat_others) + def test_coo(self): + def f(x): + return math.laplace(x) + for backend in BACKENDS: + with backend: + x = math.ones(spatial(x=5)) + coo, bias = math.matrix_from_function(f, x, auto_compress=False) + csr = coo.compress(non_dual) + math.assert_close(f(x), coo @ x, csr @ x) diff --git a/tests/commit/math/test__tensors.py b/tests/commit/math/test__tensors.py index a5268e75e..17d7ace45 100644 --- a/tests/commit/math/test__tensors.py +++ b/tests/commit/math/test__tensors.py @@ -468,9 +468,9 @@ def test_disassemble_assemble(self): math.zeros(batch(b=2, c=2)), math.ones(batch(b=10)) * wrap((1, 2), channel('vector')), ]: - natives, shapes, native_dims = disassemble_tensors(t, expand=False) - restored = assemble_tensors(natives, shapes, native_dims) - math.assert_close(t, restored) + natives, shapes, specs = disassemble_tensors([t], expand=False) + restored = assemble_tensors(natives, specs) + math.assert_close(t, restored[0]) print(restored) def test_is_number(self): diff --git a/tests/commit/math/test__trace.py b/tests/commit/math/test__trace.py new file mode 100644 index 000000000..1bc44d1b7 --- /dev/null +++ b/tests/commit/math/test__trace.py @@ -0,0 +1,27 @@ +from unittest import TestCase + +import phi +from phi import math +from phi.math import expand, spatial, non_dual, extrapolation +from phi.math._sparse import SparseCoordinateTensor + +BACKENDS = phi.detect_backends() + + +class TestTrace(TestCase): + + def test_matrix_from_function(self): + def simple_gradient(x): + x0, x1 = math.shift(x, (0, 1), dims='x', padding=extrapolation.ZERO, stack_dim=None) + return x1 - x0 + + def diagonal(x): + return 2 * x + + for f in [simple_gradient, diagonal]: + x = expand(1, spatial(x=4)) + matrix, bias = math.matrix_from_function(f, x) + if isinstance(matrix, SparseCoordinateTensor): + matrix = matrix.compress(non_dual) + math.assert_close(f(x), matrix @ x) + diff --git a/tests/commit/physics/test_diffuse.py b/tests/commit/physics/test_diffuse.py index 7fcd7de4f..3d17f7af8 100644 --- a/tests/commit/physics/test_diffuse.py +++ b/tests/commit/physics/test_diffuse.py @@ -9,26 +9,26 @@ class TestDiffusion(TestCase): def test_diffuse_centered_batched(self): - grid = CenteredGrid(Noise(batch=2, vector=2), extrapolation.PERIODIC, x=4, y=3) + grid = CenteredGrid(Noise(batch=2, vector=2), extrapolation.PERIODIC, x=6, y=5) diffuse.explicit(grid, 1, 1, substeps=10) diffuse.implicit(grid, 1, 1, order=2) diffuse.fourier(grid, 1, 1) def test_diffuse_staggered_batched(self): for diffusivity in [1, 0.5, math.wrap([1., 0.5], batch('batch'))]: - grid = StaggeredGrid(Noise(batch(batch=2), vector=2), extrapolation.PERIODIC, x=4, y=3) + grid = StaggeredGrid(Noise(batch(batch=2), vector=2), extrapolation.PERIODIC, x=6, y=5) diffuse.explicit(grid, diffusivity, 1, substeps=10) diffuse.implicit(grid, diffusivity, 1, order=2) diffuse.fourier(grid, diffusivity, 1) - grid = StaggeredGrid(Noise(batch(batch=2), vector=2), extrapolation.ZERO, x=4, y=3) + grid = StaggeredGrid(Noise(batch(batch=2), vector=2), extrapolation.ZERO, x=6, y=5) diffuse.explicit(grid, diffusivity, 1, substeps=10) # diffuse.implicit(grid, diffusivity, 1, order=2) # not yet supported - grid = StaggeredGrid(Noise(batch(batch=2), vector=2), extrapolation.BOUNDARY, x=4, y=3) + grid = StaggeredGrid(Noise(batch(batch=2), vector=2), extrapolation.BOUNDARY, x=6, y=5) diffuse.explicit(grid, diffusivity, 1, substeps=10) # diffuse.implicit(grid, diffusivity, 1, order=2) # not yet supported def test_constant_diffusion(self): - grid = CenteredGrid(1, extrapolation.PERIODIC, x=4, y=3) + grid = CenteredGrid(1, extrapolation.PERIODIC, x=5, y=5) explicit = diffuse.explicit(grid, 1, 1, substeps=10) implicit = diffuse.implicit(grid, 1, 1, order=2) fourier = diffuse.fourier(grid, 1, 1) @@ -65,7 +65,7 @@ def test_consistency_implicit(self): def test_implicit_stability(self): DIFFUSIVITY = 10 - grid = CenteredGrid((1,) * 3 + (0,) * 3, extrapolation.PERIODIC, x=6) + grid = CenteredGrid((1,) * 3 + (0,) * 3, extrapolation.PERIODIC, x=21) try: implicit = diffuse.implicit(grid, DIFFUSIVITY, 1, order=10) print(implicit.values) From a9ab831e6964c16e00bb922d63b8b947ba30efb8 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 24 Jan 2023 20:05:10 +0100 Subject: [PATCH 075/170] [math] Add identity() --- phi/flow.py | 2 +- phi/math/__init__.py | 1 + phi/math/_functional.py | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/phi/flow.py b/phi/flow.py index 3db611944..ce6e56a8f 100644 --- a/phi/flow.py +++ b/phi/flow.py @@ -33,7 +33,7 @@ shape, spatial, channel, batch, instance, dual, non_spatial, non_channel, non_batch, non_instance, non_dual, # Shape functions (magic) unstack, stack, concat, expand, rename_dims, pack_dims, unpack_dim, flatten, cast, # Magic Ops - jit_compile, jit_compile_linear, minimize, functional_gradient, solve_linear, solve_nonlinear, iterate, # jacobian, hessian, custom_gradient # Functional magic + jit_compile, jit_compile_linear, minimize, functional_gradient, solve_linear, solve_nonlinear, iterate, identity, # jacobian, hessian, custom_gradient # Functional magic ) from .geom import union from .vis import show, view, control, plot diff --git a/phi/math/__init__.py b/phi/math/__init__.py index 4d0d2ce06..ae54f3683 100644 --- a/phi/math/__init__.py +++ b/phi/math/__init__.py @@ -61,6 +61,7 @@ jacobian, jacobian as gradient, functional_gradient, custom_gradient, print_gradient, map_types, map_s2b, map_i2b, iterate, + identity, ) from ._optimize import solve_linear, solve_nonlinear, minimize, Solve, SolveInfo, ConvergenceException, NotConverged, Diverged, SolveTape from ._nd import ( diff --git a/phi/math/_functional.py b/phi/math/_functional.py index b64bd27de..013d39ed3 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -1025,3 +1025,16 @@ def iterate(f: Callable, return (result, wrap(ts[1:] - ts[:-1], iterations.with_size(None))) if measure else result else: raise ValueError(f"iterations must be an int or Shape but got {type(iterations)}") + + +def identity(*args): + """ + Identity function without keyword arguments. + + Args: + *args: Positional arguments. + + Returns: + `args` + """ + return args From ca6fe687489ca089903ac9600a757a817d107712 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 24 Jan 2023 20:22:31 +0100 Subject: [PATCH 076/170] [doc] Use >>> for example usage --- phi/geom/_box.py | 17 +++++-------- phi/math/_magic_ops.py | 53 ++++++++++++++++------------------------- phi/math/_nd.py | 26 ++++++++++---------- phi/math/_ops.py | 30 ++++++++++------------- phi/math/_optimize.py | 12 ++++------ phi/math/_shape.py | 52 ++++++++++++++++------------------------ phi/math/_tensors.py | 29 ++++++++++------------ phi/physics/__init__.py | 8 +++---- phi/vis/_viewer.py | 6 ++--- phi/vis/_vis.py | 8 ++----- 10 files changed, 96 insertions(+), 145 deletions(-) diff --git a/phi/geom/_box.py b/phi/geom/_box.py index d980b4476..bfab67a10 100644 --- a/phi/geom/_box.py +++ b/phi/geom/_box.py @@ -168,19 +168,14 @@ class Box(BaseBox, metaclass=BoxType): Boxes can be constructed either from two positional vector arguments `(lower, upper)` or by specifying the limits by dimension name as `kwargs`. - **Examples**: + Examples: + >>> Box(x=1, y=1) # creates a two-dimensional unit box with `lower=(0, 0)` and `upper=(1, 1)`. + >>> Box(x=(None, 1), y=(0, None) # creates a Box with `lower=(-inf, 0)` and `upper=(1, inf)`. - ```python - Box(x=1, y=1) # creates a two-dimensional unit box with `lower=(0, 0)` and `upper=(1, 1)`. - Box(x=(None, 1), y=(0, None) # creates a Box with `lower=(-inf, 0)` and `upper=(1, inf)`. - ``` + The slicing constructor was updated in version 2.2 and now requires the dimension order as the first argument. - The slicing constructor was updated in version 2.2 and now requires the dimension order as the first argument. - - ```python - Box['x,y', 0:1, 0:1] # creates a two-dimensional unit box with `lower=(0, 0)` and `upper=(1, 1)`. - Box['x,y', :1, 0:] # creates a Box with `lower=(-inf, 0)` and `upper=(1, inf)`. - ``` + >>> Box['x,y', 0:1, 0:1] # creates a two-dimensional unit box with `lower=(0, 0)` and `upper=(1, 1)`. + >>> Box['x,y', :1, 0:] # creates a Box with `lower=(-inf, 0)` and `upper=(1, inf)`. """ def __init__(self, lower: Tensor = None, upper: Tensor = None, **size: int or Tensor): diff --git a/phi/math/_magic_ops.py b/phi/math/_magic_ops.py index 0a4ba59bd..4e04db4d8 100644 --- a/phi/math/_magic_ops.py +++ b/phi/math/_magic_ops.py @@ -3,9 +3,10 @@ from numbers import Number from typing import TypeVar, Tuple, Set +from . import channel from .backend import choose_backend, NoBackendFound from .backend._dtype import DType -from ._shape import Shape, DimFilter, batch, instance, shape, non_batch, merge_shapes, concat_shapes +from ._shape import Shape, DimFilter, batch, instance, shape, non_batch, merge_shapes, concat_shapes, spatial from .magic import Sliceable, Shaped, Shapable, PhiTreeNode @@ -26,10 +27,8 @@ def unstack(value, dim: DimFilter): `tuple` of `Tensor` objects. Examples: - ```python - unstack(math.zeros(spatial(x=5)), 'x') - # Out: (0.0, 0.0, 0.0, 0.0, 0.0) - ``` + >>> unstack(expand(0, spatial(x=5)), 'x') + (0.0, 0.0, 0.0, 0.0, 0.0) """ assert isinstance(value, Sliceable) and isinstance(value, Shaped), f"Cannot unstack {type(value).__name__}. Must be Sliceable and Shaped, see https://tum-pbs.github.io/PhiFlow/phi/math/magic.html" dims = shape(value).only(dim) @@ -79,17 +78,14 @@ def stack(values: tuple or list or dict, dim: Shape, expand_values=False, **kwar `Tensor` containing `values` stacked along `dim`. Examples: + >>> stack({'x': 0, 'y': 1}, channel('vector')) + (x=0, y=1) - ```python - stack({'x': 0, 'y': 1}, channel('vector')) - # Out: (x=0, y=1) + >>> stack([math.zeros(batch(b=2)), math.ones(batch(b=2))], channel(c='x,y')) + (x=0.000, y=1.000); (x=0.000, y=1.000) (bᵇ=2, cᶜ=x,y) - stack([math.zeros(batch(b=2)), math.ones(batch(b=2))], channel(c='x,y')) - # Out: (x=0.000, y=1.000); (x=0.000, y=1.000) (bᵇ=2, cᶜ=x,y) - - stack([vec(x=1, y=0), vec(x=2, y=3.)], batch('b')) - # Out: (x=1.000, y=0.000); (x=2.000, y=3.000) (bᵇ=2, vectorᶜ=x,y) - ``` + >>> stack([vec(x=1, y=0), vec(x=2, y=3.)], batch('b')) + (x=1.000, y=0.000); (x=2.000, y=3.000) (bᵇ=2, vectorᶜ=x,y) """ assert len(values) > 0, f"stack() got empty sequence {values}" assert isinstance(dim, Shape) @@ -196,14 +192,11 @@ def concat(values: tuple or list, dim: str or Shape, **kwargs): Concatenated `Tensor` Examples: + >>> concat([math.zeros(batch(b=10)), math.ones(batch(b=10))], 'b') + (bᵇ=20) 0.500 ± 0.500 (0e+00...1e+00) - ```python - concat([math.zeros(batch(b=10)), math.ones(batch(b=10))], 'b') - # Out: (bᵇ=20) 0.500 ± 0.500 (0e+00...1e+00) - - concat([vec(x=1, y=0), vec(z=2.)], 'vector') - # Out: (x=1.000, y=0.000, z=2.000) float64 - ``` + >>> concat([vec(x=1, y=0), vec(z=2.)], 'vector') + (x=1.000, y=0.000, z=2.000) float64 """ assert len(values) > 0, f"concat() got empty sequence {values}" if isinstance(dim, Shape): @@ -378,10 +371,8 @@ def pack_dims(value, dims: DimFilter, packed_dim: Shape, pos: int or None = None Same type as `value`. Examples: - ```python - pack_dims(math.zeros(spatial(x=4, y=3)), spatial, instance('points')) - # Out: (pointsⁱ=12) const 0.0 - ``` + >>> pack_dims(math.zeros(spatial(x=4, y=3)), spatial, instance('points')) + (pointsⁱ=12) const 0.0 """ assert isinstance(value, Shapable) and isinstance(value, Sliceable) and isinstance(value, Shaped), f"value must be Shapable but got {type(value)}" dims = shape(value).only(dims, reorder=True) @@ -429,10 +420,8 @@ def unpack_dim(value, dim: str or Shape, unpacked_dims: Shape, **kwargs): Same type as `value`. Examples: - ```python - unpack_dim(math.zeros(instance(points=12)), 'points', spatial(x=4, y=3)) - # Out: (xˢ=4, yˢ=3) const 0.0 - ``` + >>> unpack_dim(math.zeros(instance(points=12)), 'points', spatial(x=4, y=3)) + (xˢ=4, yˢ=3) const 0.0 """ assert isinstance(value, Shapable) and isinstance(value, Sliceable) and isinstance(value, Shaped), f"value must be Shapable but got {type(value)}" if isinstance(dim, Shape): @@ -480,10 +469,8 @@ def flatten(value, flat_dim: Shape = instance('flat'), flatten_batch=False, **kw Same type as `value`. Examples: - ```python - flatten(math.zeros(spatial(x=4, y=3))) - # Out: (flatⁱ=12) const 0.0 - ``` + >>> flatten(math.zeros(spatial(x=4, y=3))) + (flatⁱ=12) const 0.0 """ assert isinstance(flat_dim, Shape) and flat_dim.rank == 1, flat_dim assert isinstance(value, Shapable) and isinstance(value, Shaped), f"value must be Shapable but got {type(value)}" diff --git a/phi/math/_nd.py b/phi/math/_nd.py index c6b56e61b..f229d34dd 100644 --- a/phi/math/_nd.py +++ b/phi/math/_nd.py @@ -3,15 +3,15 @@ import numpy as np -from . import _ops as math +from ._shape import Shape, channel, batch, spatial, DimFilter, parse_dim_order, shape, instance +from .magic import PhiTreeNode +from ._magic_ops import stack, rename_dims, concat, variable_values +from ._tensors import Tensor, wrap, tensor from . import extrapolation as extrapolation +from .extrapolation import Extrapolation +from . import _ops as math from ._functional import jit_compile_linear from ._optimize import solve_linear -from ._magic_ops import stack, rename_dims, concat, variable_values -from ._shape import Shape, channel, batch, spatial, DimFilter, parse_dim_order, shape -from ._tensors import Tensor, wrap -from .extrapolation import Extrapolation -from .magic import PhiTreeNode def vec(name='vector', **components) -> Tensor: @@ -26,16 +26,14 @@ def vec(name='vector', **components) -> Tensor: `Tensor` Examples: - ```python - vec(x=1, y=0, z=-1) - # Out: (x=1, y=0, z=-1) + >>> vec(x=1, y=0, z=-1) + (x=1, y=0, z=-1) - vec(x=1., z=0) - # Out: (x=1.000, z=0.000) + >>> vec(x=1., z=0) + (x=1.000, z=0.000) - vec(x=tensor([1, 2, 3], instance('particles')), y=0) - # Out: (x=1, y=0); (x=2, y=0); (x=3, y=0) (particlesⁱ=3, vectorᶜ=x,y) - ``` + >>> vec(x=tensor([1, 2, 3], instance('particles')), y=0) + (x=1, y=0); (x=2, y=0); (x=3, y=0) (particlesⁱ=3, vectorᶜ=x,y) """ return stack(components, channel(name), expand_values=True) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index f3c402b0b..3e67ea110 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -649,13 +649,11 @@ def linspace(start: int or Tensor, stop, dim: Shape) -> Tensor: `Tensor` Examples: - ```python - math.linspace(0, 1, spatial(x=5)) - # Out: (0.000, 0.250, 0.500, 0.750, 1.000) along xˢ + >>> math.linspace(0, 1, spatial(x=5)) + (0.000, 0.250, 0.500, 0.750, 1.000) along xˢ - math.linspace(0, (-1, 1), spatial(x=3)) - # Out: (0.000, 0.000); (-0.500, 0.500); (-1.000, 1.000) (xˢ=3, vectorᶜ=2) - ``` + >>> math.linspace(0, (-1, 1), spatial(x=3)) + (0.000, 0.000); (-0.500, 0.500); (-1.000, 1.000) (xˢ=3, vectorᶜ=2) """ assert isinstance(dim, Shape) and dim.rank == 1, f"dim must be a single-dimension Shape but got {dim}" if is_scalar(start) and is_scalar(stop): @@ -771,13 +769,11 @@ def pad(value: Tensor, widths: dict, mode: 'e_.Extrapolation' or Tensor or Numbe Padded `Tensor` Examples: - ```python - math.pad(math.ones(spatial(x=10, y=10)), {'x': (1, 1), 'y': (2, 1)}, 0) - # Out: (xˢ=12, yˢ=13) 0.641 ± 0.480 (0e+00...1e+00) + >>> math.pad(math.ones(spatial(x=10, y=10)), {'x': (1, 1), 'y': (2, 1)}, 0) + (xˢ=12, yˢ=13) 0.641 ± 0.480 (0e+00...1e+00) - math.pad(math.ones(spatial(x=10, y=10)), {'x': (1, -1)}, 0) - # Out: (xˢ=10, yˢ=10) 0.900 ± 0.300 (0e+00...1e+00) - ``` + >>> math.pad(math.ones(spatial(x=10, y=10)), {'x': (1, -1)}, 0) + (xˢ=10, yˢ=10) 0.900 ± 0.300 (0e+00...1e+00) """ mode = mode if isinstance(mode, e_.Extrapolation) else e_.ConstantExtrapolation(mode) has_negative_widths = any(w0 < 0 or w1 < 0 for w0, w1 in widths.values()) @@ -2392,12 +2388,10 @@ def pairwise_distances(positions: Tensor, max_distance: float or Tensor = None, `Tensor` Examples: - ```python - pos = vec(x=0, y=tensor([0, 1, 2.5], instance('particles'))) - dx = math.pairwise_distances(pos, format='dense', max_distance=2) - dx.particles[0] - # Out: (x=0.000, y=0.000); (x=0.000, y=1.000); (x=0.000, y=0.000) (othersⁱ=3, vectorᶜ=x,y) - ``` + >>> pos = vec(x=0, y=tensor([0, 1, 2.5], instance('particles'))) + >>> dx = pairwise_distances(pos, format='dense', max_distance=2) + >>> dx.particles[0] + (x=0.000, y=0.000); (x=0.000, y=1.000); (x=0.000, y=0.000) (othersⁱ=3, vectorᶜ=x,y) """ if format == 'dense': # if not count_self: diff --git a/phi/math/_optimize.py b/phi/math/_optimize.py index 69cab4a4d..f9b92e4d6 100644 --- a/phi/math/_optimize.py +++ b/phi/math/_optimize.py @@ -217,13 +217,11 @@ class SolveTape: While a `SolveTape` is active, certain performance optimizations and algorithm implementations may be disabled. To access a `SolveInfo` of a recorded solve, use - ```python - solve = Solve(method, ...) - with SolveTape() as solves: - x = math.solve_linear(f, y, solve) - result: SolveInfo = solves[solve] # get by Solve - result: SolveInfo = solves[0] # get by index - ``` + >>> solve = Solve(method, ...) + >>> with SolveTape() as solves: + >>> x = math.solve_linear(f, y, solve) + >>> result: SolveInfo = solves[solve] # get by Solve + >>> result: SolveInfo = solves[0] # get by index """ def __init__(self, record_trajectories=False): diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 3427627e8..9bb7aca85 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -1287,16 +1287,13 @@ def spatial(*args, **dims: int or str or tuple or list or Shape) -> Shape: Returns the spatial dimensions of an existing `Shape` or creates a new `Shape` with only spatial dimensions. Usage for filtering spatial dimensions: - ```python - spatial_dims = spatial(shape) - spatial_dims = spatial(tensor) - ``` + >>> spatial_dims = spatial(shape) + >>> spatial_dims = spatial(tensor) Usage for creating a `Shape` with only spatial dimensions: - ```python - spatial_shape = spatial('undef', x=2, y=3) - # Out: (x=2, y=3, undef=None) - ``` + >>> spatial_shape = spatial('undef', x=2, y=3) + (x=2, y=3, undef=None) + Here, the dimension `undef` is created with an undefined size of `None`. Undefined sizes are automatically filled in by `tensor`, `wrap`, `stack` and `concat`. @@ -1332,16 +1329,13 @@ def channel(*args, **dims: int or str or tuple or list or Shape) -> Shape: Returns the channel dimensions of an existing `Shape` or creates a new `Shape` with only channel dimensions. Usage for filtering channel dimensions: - ```python - channel_dims = channel(shape) - channel_dims = channel(tensor) - ``` + >>> channel_dims = channel(shape) + >>> channel_dims = channel(tensor) Usage for creating a `Shape` with only channel dimensions: - ```python - channel_shape = channel('undef', vector=2) - # Out: (vector=2, undef=None) - ``` + >>> channel_shape = channel('undef', vector=2) + (vector=2, undef=None) + Here, the dimension `undef` is created with an undefined size of `None`. Undefined sizes are automatically filled in by `tensor`, `wrap`, `stack` and `concat`. @@ -1377,16 +1371,13 @@ def batch(*args, **dims: int or str or tuple or list or Shape) -> Shape: Returns the batch dimensions of an existing `Shape` or creates a new `Shape` with only batch dimensions. Usage for filtering batch dimensions: - ```python - batch_dims = batch(shape) - batch_dims = batch(tensor) - ``` + >>> batch_dims = batch(shape) + >>> batch_dims = batch(tensor) Usage for creating a `Shape` with only batch dimensions: - ```python - batch_shape = batch('undef', batch=2) - # Out: (batch=2, undef=None) - ``` + >>> batch_shape = batch('undef', batch=2) + (batch=2, undef=None) + Here, the dimension `undef` is created with an undefined size of `None`. Undefined sizes are automatically filled in by `tensor`, `wrap`, `stack` and `concat`. @@ -1422,16 +1413,13 @@ def instance(*args, **dims: int or str or tuple or list or Shape) -> Shape: Returns the instance dimensions of an existing `Shape` or creates a new `Shape` with only instance dimensions. Usage for filtering instance dimensions: - ```python - instance_dims = instance(shape) - instance_dims = instance(tensor) - ``` + >>> instance_dims = instance(shape) + >>> instance_dims = instance(tensor) Usage for creating a `Shape` with only instance dimensions: - ```python - instance_shape = instance('undef', points=2) - # Out: (points=2, undef=None) - ``` + >>> instance_shape = instance('undef', points=2) + (points=2, undef=None) + Here, the dimension `undef` is created with an undefined size of `None`. Undefined sizes are automatically filled in by `tensor`, `wrap`, `stack` and `concat`. diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index dcd0c377a..974de49d4 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -1613,22 +1613,20 @@ def tensor(data: Tensor or Shape or tuple or list or numbers.Number, Tensor containing same values as data Examples: - ```python - tensor([1, 2, 3], channel(vector='x,y,z')) - # Out: (x=1, y=2, z=3) + >>> tensor([1, 2, 3], channel(vector='x,y,z')) + (x=1, y=2, z=3) - tensor([1., 2, 3], channel(vector='x,y,z')) - # Out: (x=1.000, y=2.000, z=3.000) float64 + >>> tensor([1., 2, 3], channel(vector='x,y,z')) + (x=1.000, y=2.000, z=3.000) float64 - tensor(numpy.zeros([10, 8, 6, 2]), batch('batch'), spatial('x,y'), channel(vector='x,y')) - # Out: (batchᵇ=10, xˢ=8, yˢ=6, vectorᶜ=x,y) float64 const 0.0 + >>> tensor(numpy.zeros([10, 8, 6, 2]), batch('batch'), spatial('x,y'), channel(vector='x,y')) + (batchᵇ=10, xˢ=8, yˢ=6, vectorᶜ=x,y) float64 const 0.0 - tensor([(0, 1), (0, 2), (1, 3)], instance('particles'), channel(vector='x,y')) - # Out: (x=0, y=1); (x=0, y=2); (x=1, y=3) (particlesⁱ=3, vectorᶜ=x,y) + >>> tensor([(0, 1), (0, 2), (1, 3)], instance('particles'), channel(vector='x,y')) + (x=0, y=1); (x=0, y=2); (x=1, y=3) (particlesⁱ=3, vectorᶜ=x,y) - tensor(numpy.random.randn(10)) - # Out: (vectorᶜ=10) float64 -0.128 ± 1.197 (-2e+00...2e+00) - ``` + >>> tensor(numpy.random.randn(10)) + (vectorᶜ=10) float64 -0.128 ± 1.197 (-2e+00...2e+00) """ assert all(isinstance(s, Shape) for s in shape), f"Cannot create tensor because shape needs to be one or multiple Shape instances but got {shape}" shape = None if len(shape) == 0 else concat_shapes(*shape) @@ -1717,10 +1715,9 @@ def layout(objects, *shape: Shape) -> Tensor: Strings may also be used as containers. Example: - ```python - t = layout({'a': 'text', 'b': [0, 1]}, channel('dict,inner')) - t.inner[1].dict['a'].native() # returns 'e' - ``` + >>> t = layout({'a': 'text', 'b': [0, 1]}, channel('dict,inner')) + >>> t.inner[1].dict['a'].native() + 'e' See Also: `tensor()`, `wrap()`. diff --git a/phi/physics/__init__.py b/phi/physics/__init__.py index 70d395cf5..cb3de5878 100644 --- a/phi/physics/__init__.py +++ b/phi/physics/__init__.py @@ -1,9 +1,9 @@ """ -Contains built-in physics functions, e.g. for fluids. -The actual physics functions are located in the sub-modules of `phi.physics`. -A common trait of many physics functions is the time increment (`dt`) argument. +Contains built-in physics functions, mainly for partial differential equations, such as incompressible fluids. +The actual physics functions are located in the submodules of `phi.physics`. -Main class: `Domain` +Some physics functions have built-in time advancement while others return the PDE term, i.e. the derivative. +The time-advancing functions always take a time increment argument called `dt`. See the `phi.physics` module documentation at https://tum-pbs.github.io/PhiFlow/Physics.html """ diff --git a/phi/vis/_viewer.py b/phi/vis/_viewer.py index 80445fd35..f6baada82 100644 --- a/phi/vis/_viewer.py +++ b/phi/vis/_viewer.py @@ -142,10 +142,8 @@ def range(self, *args, warmup=0, **rec_dim): """ Similarly to `range()`, returns a generator that can be used in a `for` loop. - ```python - for step in ModuleViewer().range(100): - print(f'Running step {step}') - ``` + >>> for step in ModuleViewer().range(100): + >>> print(f'Running step {step}') However, `Viewer.range()` enables controlling the flow via the user interface. Each element returned by the generator waits for `progress` to be invoked once. diff --git a/phi/vis/_vis.py b/phi/vis/_vis.py index 5634d284c..3ea73bb88 100644 --- a/phi/vis/_vis.py +++ b/phi/vis/_vis.py @@ -218,9 +218,7 @@ def control(value, range: tuple = None, description="", **kwargs): Mark a variable as controllable by any GUI created via `view()`. Example: - ```python - dt = control(1.0, (0.1, 10), name="Time increment (dt)") - ``` + >>> dt = control(1.0, (0.1, 10), name="Time increment (dt)") This will cause a control component (slider, checkbox, text field, drop-down, etc.) to be generated in the user interface. Changes to that component will immediately be reflected in the Python variable assigned to the control. @@ -446,9 +444,7 @@ def overlay(*fields: SampledField or Tensor) -> Tensor: Specify that multiple fields should be drawn on top of one another in the same figure. The fields will be plotted in the order they are given, i.e. the last field on top. - ```python - vis.plot(vis.overlay(heatmap, points, velocity)) - ``` + >>> plot(vis.overlay(heatmap, points, velocity)) Args: *fields: `SampledField` or `Tensor` instances From 278a731139e57ae56e7d6745dd7429ad417078ef Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 24 Jan 2023 20:24:25 +0100 Subject: [PATCH 077/170] [math] Rename BOUNDARY to ZERO_GRADIENT --- phi/math/extrapolation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phi/math/extrapolation.py b/phi/math/extrapolation.py index 5536d894f..806691c39 100644 --- a/phi/math/extrapolation.py +++ b/phi/math/extrapolation.py @@ -964,7 +964,7 @@ def __rtruediv__(self, other): """ Extrapolates with the constant value 1 (Dirichlet boundary condition). """ PERIODIC = _PeriodicExtrapolation(1) """ Extends a grid by tiling it (Periodic boundary condition). """ -BOUNDARY = _BoundaryExtrapolation(2) +ZERO_GRADIENT = BOUNDARY = _BoundaryExtrapolation(2) """ Extends a grid with its edge values (Neumann boundary condition). The value of a point lying outside the grid is determined by the closest grid value(s). """ SYMMETRIC = _SymmetricExtrapolation(3) """ Extends a grid by tiling it. Every other copy of the grid is flipped. Edge values occur twice per seam. """ From 8b09a2424a948c7637e3d465b24b6bd335c8f8c7 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 24 Jan 2023 20:42:23 +0100 Subject: [PATCH 078/170] [math] Support varargs, kwargs in solve_linear() --- phi/field/_field_math.py | 12 ++++-------- phi/math/_optimize.py | 19 ++++++++++++------- phi/physics/fluid.py | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 8dda246a1..82609c43f 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -84,7 +84,7 @@ def laplace(field: GridType, axes=spatial, order=2, implicit: math.Solve = None, result_components.with_values(result_components.values._cache()) result_components = result_components.with_extrapolation(map(_ex_map_f(extrapol_map_rhs), field.extrapolation)) implicit.x0 = result_components - result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=implicit, f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, "stack_dim": channel('laplacian')}) + result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=implicit, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=channel('laplacian')) result_components = unstack(result_components, 'laplacian') extrapol_map = extrapol_map_rhs result_components = [component.with_bounds(field.bounds) for component in result_components] @@ -195,9 +195,7 @@ def spatial_gradient(field: CenteredGrid, if implicit: implicit.x0 = result result = result - result = solve_linear(_lhs_for_implicit_scheme, result, solve=implicit, - f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, - "stack_dim": stack_dim, "staggered_output": type != CenteredGrid}) + result = solve_linear(_lhs_for_implicit_scheme, result, solve=implicit, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=stack_dim, staggered_output=type != CenteredGrid) if type == CenteredGrid and gradient_extrapolation == math.extrapolation.NONE: result = result.with_bounds(Box(field.bounds.lower - field.dx, field.bounds.upper + field.dx)) else: @@ -211,7 +209,7 @@ def f(ext: Extrapolation): return f -@partial(jit_compile_linear, auxiliary_args="values_rhs, needed_shifts_rhs, stack_dim, staggered_output") +@jit_compile_linear(auxiliary_args="values_rhs, needed_shifts_rhs, stack_dim, staggered_output") def _lhs_for_implicit_scheme(x, values_rhs, needed_shifts_rhs, stack_dim, staggered_output=False): result = [] for dim, component in zip(x.shape.only(math.spatial).names, unstack(x, stack_dim.name)): @@ -387,9 +385,7 @@ def divergence(field: Grid, order=2, implicit: Solve = None) -> CenteredGrid: result_components = stack(result_components, channel('vector')) result_components.with_values(result_components.values._cache()) implicit.x0 = field - result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=implicit, - f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, - "stack_dim": channel('vector')}) + result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=implicit, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=channel('vector')) result_components = unstack(result_components, 'vector') result_components = [component.with_bounds(field.bounds) for component in result_components] diff --git a/phi/math/_optimize.py b/phi/math/_optimize.py index f9b92e4d6..c4e4894a1 100644 --- a/phi/math/_optimize.py +++ b/phi/math/_optimize.py @@ -415,8 +415,9 @@ def min_func(x): def solve_linear(f: Callable[[X], Y], y: Y, solve: Solve[X, Y], - f_args: tuple or list = (), - f_kwargs: dict = None) -> X: + *f_args, + f_kwargs: dict = None, + **f_kwargs_) -> X: """ Solves the system of linear equations *f(x) = y* and returns *x*. This method will use the solver specified in `solve`. @@ -445,9 +446,8 @@ def solve_linear(f: Callable[[X], Y], `f` can have additional arguments. y: Desired output of `f(x)` as `Tensor` or `PhiTreeNode`. solve: `Solve` object specifying optimization method, parameters and initial guess for `x`. - f_args: Additional `Tensor` or `PhiTreeNode` arguments to be passed to `f`. - `f` need not be linear in these arguments. - Use this instead of lambda function since a lambda will not be recognized as calling a jit-compiled function. + *f_args: Positional arguments to be passed to `f` after `solve.x0`. These arguments will not be solved for. + Supports vararg mode or pass all arguments as a `tuple`. f_kwargs: Additional keyword arguments to be passed to `f`. These arguments are treated as auxiliary arguments and can be of any type. @@ -458,6 +458,11 @@ def solve_linear(f: Callable[[X], Y], NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. Diverged: If the solve failed prematurely. """ + # --- Handle parameters --- + f_kwargs = f_kwargs or {} + f_kwargs.update(f_kwargs_) + f_args = f_args[0] if len(f_args) == 1 and isinstance(f_args[0], tuple) else f_args + # --- Get input and output tensors --- y_tree, y_tensors = disassemble_tree(y) x0_tree, x0_tensors = disassemble_tree(solve.x0) assert len(x0_tensors) == len(y_tensors) == 1, "Only single-tensor linear solves are currently supported" @@ -465,7 +470,7 @@ def solve_linear(f: Callable[[X], Y], prefer_explicit = backend.supports(Backend.sparse_coo_tensor) or backend.supports(Backend.csr_matrix) if isinstance(f, LinearFunction) and prefer_explicit: # Matrix solve - matrix, bias = f.sparse_matrix_and_bias(solve.x0, *f_args, **(f_kwargs or {})) + matrix, bias = f.sparse_matrix_and_bias(solve.x0, *f_args, **f_kwargs) def _matrix_solve_forward(y, solve: Solve, matrix: Tensor, is_backprop=False): backend_matrix = native_matrix(matrix) @@ -501,7 +506,7 @@ def native_lin_f(native_x, batch_index=None): return result # must return exactly `x` so gradient isn't computed w.r.t. other quantities _function_solve = attach_gradient_solve(_function_solve_forward, auxiliary_args='is_backprop,f_kwargs') - return _function_solve(y, solve, f_args, f_kwargs=f_kwargs or {}) + return _function_solve(y, solve, f_args, f_kwargs=f_kwargs) def _linear_solve_forward(y, diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index 44d917297..d23c0dfec 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -105,7 +105,7 @@ def make_incompressible(velocity: GridType, solve = copy_with(solve, x0=CenteredGrid(0, pressure_extrapolation, div.bounds, div.resolution)) if batch(math.merge_shapes(*obstacles)).without(batch(solve.x0)): # The initial pressure guess must contain all batch dimensions solve = copy_with(solve, x0=expand(solve.x0, batch(math.merge_shapes(*obstacles)))) - pressure = math.solve_linear(masked_laplace, f_args=[hard_bcs, active], f_kwargs=dict(order=order), y=div, solve=solve) + pressure = math.solve_linear(masked_laplace, div, solve, hard_bcs, active, order=order) # --- Subtract grad p --- grad_pressure = field.spatial_gradient(pressure, input_velocity.extrapolation, type=type(velocity), order=order) * hard_bcs velocity = (velocity - grad_pressure).with_extrapolation(input_velocity.extrapolation) From 2ba4af737132e4e351f2f01a2294536554978c67 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 24 Jan 2023 20:43:01 +0100 Subject: [PATCH 079/170] [field] Move dyadic interpolation to _grid.py --- phi/field/_grid.py | 50 +++++++++++++++++++++++++++++++++++++++++++--- phi/math/_nd.py | 48 -------------------------------------------- 2 files changed, 47 insertions(+), 51 deletions(-) diff --git a/phi/field/_grid.py b/phi/field/_grid.py index 4d2f9c77c..335f9c17e 100644 --- a/phi/field/_grid.py +++ b/phi/field/_grid.py @@ -1,4 +1,4 @@ -from typing import TypeVar, Any, Tuple +from typing import TypeVar, Any, Tuple, List from phi.math import Solve @@ -254,7 +254,6 @@ def _sample(self, geometry: Geometry, **kwargs) -> Tensor: return resampled_values def _dyadic_interplate(self, resolution: Shape, bounds: Box, order=2, implicit: Solve = None): - from phi.math._nd import _dyadic_interpolate offsets = bounds.lower - self.bounds.lower interpolation_dirs = [0 if math.close(offset, 0) else int(math.sign(offset)) for offset in offsets] return _dyadic_interpolate(self.values, interpolation_dirs, self.extrapolation, order, implicit) @@ -567,4 +566,49 @@ def _get_resolution(resolution: Shape, resolution_: dict, bounds: Box): resolution_ = spatial(**resolution_) except AssertionError as err: raise ValueError(f"Invalid grid resolution: {', '.join(f'{dim}={size}' for dim, size in resolution_.items())}. Pass an int for all sizes.") from err - return (resolution or math.EMPTY_SHAPE) & resolution_ \ No newline at end of file + return (resolution or math.EMPTY_SHAPE) & resolution_ + + +def _dyadic_interpolate(grid: Tensor, interpolation_dirs: List, padding: Extrapolation, order: int, implicit: Solve): + """ + Samples a sub-grid from `grid` with an offset of half a grid cell in directions defined by `interpolation_dirs`. + + Args: + grid: `Tensor` to be resampled. + interpolation_dirs: List which defines for every spatial dimension of `grid` if interpolation should be performed, + in positive direction `1` / negative direction `-1` / no interpolation`0` + len(interpolation_dirs) == len(grid.shape.spatial.names) is assumed + Example: With `grid.shape.spatial.names=['x', 'y']` and `interpolation_dirs: [1, -1]` + grid will be interpolated half a grid cell in positive x direction and half a grid cell in negative y direction + padding: Extrapolation used for the needed out of Domain values + order: finite difference `Scheme` used for interpolation + + Returns: + Sub-grid as `Tensor` + """ + if implicit: + if order == 6: + values, needed_shifts = [1 / 20, 3 / 4, 3 / 4, 1 / 20], (-1, 0, 1, 2) + values_rhs, needed_shifts_rhs = [3 / 10, 1, 3 / 10], (-1, 0, 1) + else: + return NotImplemented + else: + return NotImplemented + result = grid + for dim, dir in zip(grid.shape.spatial.names, interpolation_dirs): + if dir == 0: continue + is_neg_dir = dir == -1 + current_widths = [abs(min(needed_shifts)) + is_neg_dir, max(needed_shifts) - is_neg_dir] + padded = math.pad(result, {dim: tuple(current_widths)}, padding) + shifted = math.shift(padded, needed_shifts, [dim], padding=None, stack_dim=None) + result = sum([value * shift_ for value, shift_ in zip(values, shifted)]) + if implicit: + implicit.x0 = result + result = math.solve_linear(dyadic_interpolate_lhs, result, implicit, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, dim=dim, padding=padding) + return result + + +@math.jit_compile_linear(auxiliary_args="values_rhs, needed_shifts_rhs") +def dyadic_interpolate_lhs(x, values_rhs, needed_shifts_rhs, dim, padding): + shifted = math.shift(x, needed_shifts_rhs, stack_dim=None, dims=[dim], padding=padding) + return sum([value * shift_ for value, shift_ in zip(values_rhs, shifted)]) diff --git a/phi/math/_nd.py b/phi/math/_nd.py index f229d34dd..fe85b94a5 100644 --- a/phi/math/_nd.py +++ b/phi/math/_nd.py @@ -675,54 +675,6 @@ def sample_subgrid(grid: Tensor, start: Tensor, size: Shape) -> Tensor: return grid -def _dyadic_interpolate(grid: Tensor, interpolation_dirs: List, padding: Extrapolation, order: int, implicit): - """ - Samples a sub-grid from `grid` with an offset of half a grid cell in directions defined by `interpolation_dirs`. - - Args: - grid: `Tensor` to be resampled. - interpolation_dirs: List which defines for every spatial dimension of `grid` if interpolation should be performed, - in positive direction `1` / negative direction `-1` / no interpolation`0` - len(interpolation_dirs) == len(grid.shape.spatial.names) is assumed - Example: With `grid.shape.spatial.names=['x', 'y']` and `interpolation_dirs: [1, -1]` - grid will be interpolated half a grid cell in positive x direction and half a grid cell in negative y direction - padding: Extrapolation used for the needed out of Domain values - scheme: finite difference `Scheme` used for interpolation - - Returns: - Sub-grid as `Tensor` - """ - if implicit: - if order == 6: - values, needed_shifts = [1 / 20, 3 / 4, 3 / 4, 1 / 20], (-1, 0, 1, 2) - values_rhs, needed_shifts_rhs = [3 / 10, 1, 3 / 10], (-1, 0, 1) - else: - return NotImplemented - else: - return NotImplemented - - result = grid - for dim, dir in zip(grid.shape.spatial.names, interpolation_dirs): - if dir == 0: continue - is_neg_dir = dir == -1 - current_widths = [abs(min(needed_shifts)) + is_neg_dir, max(needed_shifts) - is_neg_dir] - padded = math.pad(result, {dim: tuple(current_widths)}, padding) - shifted = shift(padded, needed_shifts, [dim], padding=None, stack_dim=None) - result = sum([value * shift_ for value, shift_ in zip(values, shifted)]) - - if implicit: - implicit.x0 = result - result = solve_linear(dyadic_interpolate_lhs, result, solve=implicit, - f_kwargs={"values_rhs": values_rhs, "needed_shifts_rhs": needed_shifts_rhs, - "dim": dim, "padding": padding}) - return result - -@partial(jit_compile_linear, auxiliary_args="values_rhs, needed_shifts_rhs") -def dyadic_interpolate_lhs(x, values_rhs, needed_shifts_rhs, dim, padding): - shifted = shift(x, needed_shifts_rhs, stack_dim=None, dims=[dim], padding=padding) - return sum([value * shift_ for value, shift_ in zip(values_rhs, shifted)]) - - # Poisson Brackets From 7279cc4a5ad4207107d2f6012b0420157cdee9ce Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 28 Jan 2023 21:08:21 +0100 Subject: [PATCH 080/170] [math] Make TensorStack.stack_dim private, add Shape.isdisjoint --- phi/math/_magic_ops.py | 2 +- phi/math/_ops.py | 34 ++++++++++---------- phi/math/_shape.py | 5 +++ phi/math/_sparse.py | 8 ++--- phi/math/_tensors.py | 65 +++++++++++++++++++++------------------ phi/math/extrapolation.py | 12 ++++---- 6 files changed, 68 insertions(+), 58 deletions(-) diff --git a/phi/math/_magic_ops.py b/phi/math/_magic_ops.py index 4e04db4d8..fccbf2764 100644 --- a/phi/math/_magic_ops.py +++ b/phi/math/_magic_ops.py @@ -127,7 +127,7 @@ def stack(values: tuple or list or dict, dim: Shape, expand_values=False, **kwar if attributes and all(all_attributes(v) == attributes for v in values): new_attrs = {} for a in attributes: - assert all(shape(getattr(v, a)).only(dim).is_empty for v in values), f"Cannot stack attribute {a} because one values contains the stack dimension {dim}." + assert all(dim not in shape(getattr(v, a)) for v in values), f"Cannot stack attribute {a} because one values contains the stack dimension {dim}." a_values = [getattr(v, a) for v in values] new_attrs[a] = stack(a_values, dim, expand_values=expand_values, **kwargs) return copy_with(values[0], **new_attrs) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 3e67ea110..33037f1e5 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -912,7 +912,7 @@ def broadcast_op(operation: Callable, iter_dims = set() for tensor in tensors: if isinstance(tensor, TensorStack) and tensor.requires_broadcast: - iter_dims.add(tensor.stack_dim.name) + iter_dims.add(tensor._stack_dim.name) if len(iter_dims) == 0: return operation(*tensors) else: @@ -1068,8 +1068,8 @@ def _sum(value: Tensor, dims: Shape) -> Tensor: result = _sum(value._inner, dims.only(value._inner.shape)) * value.collapsed_dims.only(dims).volume return expand_tensor(result, value.shape.without(dims)) elif isinstance(value, TensorStack): - reduced_inners = [_sum(t, dims.without(value.stack_dim)) for t in value._tensors] - return functools.reduce(lambda x, y: x + y, reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + reduced_inners = [_sum(t, dims.without(value._stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: x + y, reduced_inners) if value._stack_dim in dims else TensorStack(reduced_inners, value._stack_dim) elif isinstance(value, CompressedSparseMatrix): if value.sparse_dims in dims: # reduce all sparse dims return _sum(value._values, dims.without(value.sparse_dims) & instance(value._values)) @@ -1077,7 +1077,7 @@ def _sum(value: Tensor, dims: Shape) -> Tensor: if value_only_dims: value = value._with_values(_sum(value._values, value_only_dims)) dims = dims.without(value_only_dims) - if value._compressed_dims in dims and value._uncompressed_dims.only(dims).is_empty: + if value._compressed_dims in dims and value._uncompressed_dims.isdisjoint(dims): # We can ignore the pointers result_base = zeros(value.shape.without(value._compressed_dims)) return scatter(result_base, value._indices, value._values, mode='add', outside_handling='undefined') @@ -1118,8 +1118,8 @@ def _prod(value: Tensor, dims: Shape) -> Tensor: result = _prod(value._inner, dims.only(value._inner.shape)) ** value.collapsed_dims.only(dims).volume return expand_tensor(result, value.shape.without(dims)) elif isinstance(value, TensorStack): - reduced_inners = [_prod(t, dims.without(value.stack_dim)) for t in value._tensors] - return functools.reduce(lambda x, y: x * y, reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + reduced_inners = [_prod(t, dims.without(value._stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: x * y, reduced_inners) if value._stack_dim in dims else TensorStack(reduced_inners, value._stack_dim) else: raise ValueError(type(value)) @@ -1155,8 +1155,8 @@ def _mean(value: Tensor, dims: Shape) -> Tensor: result = _mean(value._inner, dims.only(value._inner.shape)) return expand_tensor(result, value.shape.without(dims)) elif isinstance(value, TensorStack): - reduced_inners = [_mean(t, dims.without(value.stack_dim)) for t in value._tensors] - return functools.reduce(lambda x, y: x + y, reduced_inners) / len(reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + reduced_inners = [_mean(t, dims.without(value._stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: x + y, reduced_inners) / len(reduced_inners) if value._stack_dim in dims else TensorStack(reduced_inners, value._stack_dim) else: raise ValueError(type(value)) @@ -1218,8 +1218,8 @@ def _any(value: Tensor, dims: Shape) -> Tensor: result = _any(value._inner, dims.only(value._inner.shape)) return expand_tensor(result, value.shape.without(dims)) elif isinstance(value, TensorStack): - reduced_inners = [_any(t, dims.without(value.stack_dim)) for t in value._tensors] - return functools.reduce(lambda x, y: x | y, reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + reduced_inners = [_any(t, dims.without(value._stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: x | y, reduced_inners) if value._stack_dim in dims else TensorStack(reduced_inners, value._stack_dim) else: raise ValueError(type(value)) @@ -1253,8 +1253,8 @@ def _all(value: Tensor, dims: Shape) -> Tensor: result = _all(value._inner, dims.only(value._inner.shape)) return expand_tensor(result, value.shape.without(dims)) elif isinstance(value, TensorStack): - reduced_inners = [_all(t, dims.without(value.stack_dim)) for t in value._tensors] - return functools.reduce(lambda x, y: x & y, reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + reduced_inners = [_all(t, dims.without(value._stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: x & y, reduced_inners) if value._stack_dim in dims else TensorStack(reduced_inners, value._stack_dim) else: raise ValueError(type(value)) @@ -1288,8 +1288,8 @@ def _max(value: Tensor, dims: Shape) -> Tensor: result = _max(value._inner, dims.only(value._inner.shape)) return expand_tensor(result, value.shape.without(dims)) elif isinstance(value, TensorStack): - reduced_inners = [_max(t, dims.without(value.stack_dim)) for t in value._tensors] - return functools.reduce(lambda x, y: maximum(x, y), reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + reduced_inners = [_max(t, dims.without(value._stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: maximum(x, y), reduced_inners) if value._stack_dim in dims else TensorStack(reduced_inners, value._stack_dim) else: raise ValueError(type(value)) @@ -1323,8 +1323,8 @@ def _min(value: Tensor, dims: Shape) -> Tensor: result = _min(value._inner, dims.only(value._inner.shape)) return expand_tensor(result, value.shape.without(dims)) elif isinstance(value, TensorStack): - reduced_inners = [_min(t, dims.without(value.stack_dim)) for t in value._tensors] - return functools.reduce(lambda x, y: minimum(x, y), reduced_inners) if value.stack_dim in dims else TensorStack(reduced_inners, value.stack_dim) + reduced_inners = [_min(t, dims.without(value._stack_dim)) for t in value._tensors] + return functools.reduce(lambda x, y: minimum(x, y), reduced_inners) if value._stack_dim in dims else TensorStack(reduced_inners, value._stack_dim) else: raise ValueError(type(value)) @@ -1551,7 +1551,7 @@ def dot(x: Tensor, remaining_shape_x = x.shape.without(x_dims) remaining_shape_y = y.shape.without(y_dims) assert x_dims.volume == y_dims.volume, f"Failed to reduce {x_dims} against {y_dims} in dot product of {x.shape} and {y.shape}. Sizes do not match." - if remaining_shape_y.only(remaining_shape_x).is_empty: # no shared batch dimensions -> tensordot + if remaining_shape_y.isdisjoint(remaining_shape_x): # no shared batch dimensions -> tensordot result_native = backend.tensordot(x_native, x.shape.indices(x_dims), y_native, y.shape.indices(y_dims)) result_shape = concat_shapes(remaining_shape_x, remaining_shape_y) else: # shared batch dimensions -> einsum diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 9bb7aca85..356daa4f9 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -100,6 +100,11 @@ def __contains__(self, item): else: raise ValueError(item) + def isdisjoint(self, other: 'Shape' or tuple or list or str): + """ Shapes are disjoint if all dimension names of one shape do not occur in the other shape. """ + other = parse_dim_order(other) + return not any(dim in self.names for dim in other) + def __iter__(self): return iter(self[i] for i in range(self.rank)) diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index 31e561498..7a7aaa02b 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -110,7 +110,7 @@ def _native_coo_components(self, col_dims: DimFilter, matrix=False): def _pack_indices(self, row_dims: Shape, col_dims: Shape): assert self._indices.default_backend is NUMPY, "Can only compress NumPy indices as of yet" - assert row_dims.without(self._dense_shape).is_empty, f"Can only compress sparse dims but got {row_dims} which contains non-sparse dims" + assert self._dense_shape in row_dims, f"Can only compress sparse dims but got {row_dims} which contains non-sparse dims" from ._ops import reshaped_native row_idx = self._indices[row_dims.names] col_idx = self._indices[self._dense_shape.without(row_dims).names] @@ -142,7 +142,7 @@ def compress(self, dims: DimFilter): def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or None, **kwargs) -> 'Tensor': dims = self._shape.only(dims) - assert dims.without(self._dense_shape).is_empty, "Can only pack sparse dimensions on SparseCoordinateTensor" + assert self._dense_shape in dims, "Can only pack sparse dimensions on SparseCoordinateTensor" assert self._indices.default_backend is NUMPY, "Can only pack NumPy indices as of yet" from ._ops import reshaped_native idx = self._indices.vector[dims.names] @@ -190,7 +190,7 @@ def __init__(self, indices: Tensor, pointers: Tensor, values: Tensor, uncompress assert instance(values) == instance(indices), "Instance dimensions of values and indices must match exactly" assert not channel(indices) and not spatial(indices), f"channel and spatial dimensions not allowed on indices but got {shape(indices)}" assert not channel(pointers) and not spatial(pointers), f"channel and spatial dimensions not allowed on pointers but got {shape(pointers)}" - assert uncompressed_dims.only(compressed_dims).is_empty, f"Dimensions cannot be compressed and uncompressed at the same time but got compressed={compressed_dims}, uncompressed={uncompressed_dims}" + assert uncompressed_dims.isdisjoint(compressed_dims), f"Dimensions cannot be compressed and uncompressed at the same time but got compressed={compressed_dims}, uncompressed={uncompressed_dims}" assert instance(pointers).size == compressed_dims.volume + 1 self._shape = merge_shapes(compressed_dims, uncompressed_dims, batch(indices), batch(pointers), non_instance(values)) self._indices = indices @@ -335,7 +335,7 @@ def _op1(self, native_function): def _op2(self, other, operator: Callable, native_function: Callable, op_name: str = 'unknown', op_symbol: str = '?') -> 'Tensor': other_shape = shape(other) - affects_only_values = self.sparse_dims not in other_shape and non_instance(self._indices).only(other_shape).is_empty + affects_only_values = self.sparse_dims not in other_shape and non_instance(self._indices).isdisjoint(other_shape) if affects_only_values: return self._with_values(operator(self._values, other)) elif isinstance(other, CompressedSparseMatrix): diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index 974de49d4..8c7ac4503 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -507,10 +507,10 @@ def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or No from ._magic_ops import pack_dims value = cached(self) assert isinstance(value, TensorStack) - assert value.stack_dim.name in dims - concat_dim = (value.shape.without(value.stack_dim).non_uniform or value.shape.without(value.stack_dim))[0] + assert value._stack_dim.name in dims + concat_dim = (value.shape.without(value._stack_dim).non_uniform or value.shape.without(value._stack_dim))[0] c = concat_tensor(value._tensors, concat_dim.name) - return pack_dims(c, [d for d in dims if d != value.stack_dim.name], packed_dim, pos=pos) + return pack_dims(c, [d for d in dims if d != value._stack_dim.name], packed_dim, pos=pos) def __cast__(self, dtype: DType): return self._op1(lambda native: choose_backend(native).cast(native, dtype=dtype)) @@ -1390,13 +1390,13 @@ def __init__(self, components: tuple or list, stack_dim: Shape): assert isinstance(t, Tensor) assert stack_dim.name not in t.shape, f"Cannot stack along '{stack_dim.name}' because the dimension already exists." self._tensors = tuple(components) - self.stack_dim = stack_dim.with_sizes([len(components)], keep_item_names=True) + self._stack_dim = stack_dim.with_sizes([len(components)], keep_item_names=True) try: merge_shapes(*self._tensors) self._varying_shapes = False except IncompatibleShapes: self._varying_shapes = True - self._shape = shape_stack(self.stack_dim, *[t.shape for t in self._tensors]) + self._shape = shape_stack(self._stack_dim, *[t.shape for t in self._tensors]) self._cached = None @property @@ -1406,6 +1406,11 @@ def _is_tracer(self) -> bool: @property def requires_broadcast(self): return self._varying_shapes or not self._shape.well_defined or self._is_tracer + + @property + def stack_dim(self): + warnings.warn("TensorStack.stack_dim is deprecated", DeprecationWarning, stacklevel=2) + return self._stack_dim def _cache(self): if self._cached is None: @@ -1413,14 +1418,14 @@ def _cache(self): return None elif all([t.shape.is_uniform for t in self._tensors]): natives = [t.native(order=self._shape.names) for t in self._tensors] - native = choose_backend(*natives).concat(natives, axis=self.shape.index(self.stack_dim.name)) + native = choose_backend(*natives).concat(natives, axis=self.shape.index(self._stack_dim.name)) self._cached = NativeTensor(native, self._shape) else: # cache stack_dim on inner tensors non_uniform_dim = self._tensors[0].shape.shape.without('dims') unstacked = [t.unstack(non_uniform_dim.name) for t in self._tensors] stacked = [] for to_stack in zip(*unstacked): - tensor = TensorStack(to_stack, self.stack_dim)._cache() + tensor = TensorStack(to_stack, self._stack_dim)._cache() stacked.append(tensor) self._cached = TensorStack(stacked, non_uniform_dim) return self._cached @@ -1439,11 +1444,11 @@ def native(self, order: str or tuple or list or Shape = None): else: order = parse_dim_order(order, check_rank=self.rank) # Is only the stack dimension shifted? - if order is not None and self._shape.without(self.stack_dim).names == tuple(filter(lambda name: name != self.stack_dim.name, order)): - inner_order = [dim for dim in order if dim != self.stack_dim.name] + if order is not None and self._shape.without(self._stack_dim).names == tuple(filter(lambda name: name != self._stack_dim.name, order)): + inner_order = [dim for dim in order if dim != self._stack_dim.name] natives = [t.native(inner_order) for t in self._tensors] - assert self.stack_dim.name in order, f"Dimension {self.stack_dim} missing from 'order'. Got {order} but tensor has shape {self.shape}." - native = choose_backend(*natives).stack(natives, axis=order.index(self.stack_dim.name)) + assert self._stack_dim.name in order, f"Dimension {self._stack_dim} missing from 'order'. Got {order} but tensor has shape {self.shape}." + native = choose_backend(*natives).stack(natives, axis=order.index(self._stack_dim.name)) return native assert not self.shape.is_non_uniform, f"Cannot convert non-uniform tensor with shape {self.shape} to native tensor." return self._cache().native(order=order) @@ -1452,7 +1457,7 @@ def _with_shape_replaced(self, new_shape: Shape): if self._cached is not None: return self._cached._with_shape_replaced(new_shape) else: - new_stack_dim = new_shape[self._shape.index(self.stack_dim.name)] + new_stack_dim = new_shape[self._shape.index(self._stack_dim.name)] new_tensors = [] for t in self._tensors: inner_indices = [self.shape.index(d) for d in t.shape.names] @@ -1463,43 +1468,43 @@ def _with_shape_replaced(self, new_shape: Shape): def _getitem(self, selection: dict): if self._cached is not None: return self._cached._getitem(selection) - if (self.stack_dim.name not in selection or len(selection) != 1) and not self.requires_broadcast: + if (self._stack_dim.name not in selection or len(selection) != 1) and not self.requires_broadcast: return self._cache()._getitem(selection) # --- Inner dims --- - inner_dict = {dim: sel for dim, sel in selection.items() if dim != self.stack_dim.name} + inner_dict = {dim: sel for dim, sel in selection.items() if dim != self._stack_dim.name} tensors = self._tensors if len(inner_dict) > 0: tensors = [t[inner_dict] for t in tensors] # --- stack dimension --- - if self.stack_dim.name in selection: - selection = selection[self.stack_dim.name] + if self._stack_dim.name in selection: + selection = selection[self._stack_dim.name] if isinstance(selection, int): return self._tensors[selection] elif isinstance(selection, slice): - return TensorStack(tensors[selection], self.stack_dim) + return TensorStack(tensors[selection], self._stack_dim) else: raise NotImplementedError(f"{type(selection)} not supported. Only (int, slice) allwoed") else: - return TensorStack(tensors, self.stack_dim) + return TensorStack(tensors, self._stack_dim) def flip(self, *dims: str) -> 'Tensor': if self._cached is not None: return self._cached.flip(*dims) else: tensors = [t.flip(*dims) for t in self._tensors] - if self.stack_dim.name in dims: + if self._stack_dim.name in dims: tensors = tensors[::-1] - return TensorStack(tensors, self.stack_dim) + return TensorStack(tensors, self._stack_dim) def unstack(self, dimension): if self._cached is not None: return self._cached.unstack(dimension) - if dimension == self.stack_dim.name: + if dimension == self._stack_dim.name: return self._tensors else: if self.requires_broadcast: unstacked = [t.unstack(dimension) for t in self._tensors] - result = [TensorStack(items, self.stack_dim) for items in zip(*unstacked)] + result = [TensorStack(items, self._stack_dim) for items in zip(*unstacked)] return result else: return self._cache().unstack(dimension=dimension) @@ -1507,19 +1512,19 @@ def unstack(self, dimension): def _op1(self, native_function): if self.requires_broadcast: tensors = [t._op1(native_function) for t in self._tensors] - return TensorStack(tensors, self.stack_dim) + return TensorStack(tensors, self._stack_dim) else: return self._cache()._op1(native_function) def _op2(self, other, operator, native_function, op_name: str = 'unknown', op_symbol: str = '?'): other = self._tensor(other) if self.requires_broadcast: - if self.stack_dim.name in other.shape: - other = other.unstack(self.stack_dim.name) + if self._stack_dim.name in other.shape: + other = other.unstack(self._stack_dim.name) tensors = [operator(t1, t2) for t1, t2 in zip(self._tensors, other)] else: tensors = [operator(t, other) for t in self._tensors] - return TensorStack(tensors, self.stack_dim) + return TensorStack(tensors, self._stack_dim) elif isinstance(other, (CollapsedTensor, NativeTensor)): return op2_native(self, other, native_function) elif isinstance(other, TensorStack) and not other.requires_broadcast: @@ -1537,7 +1542,7 @@ def _spec_dict(self) -> dict: if self._cached is not None: return self._cached._spec_dict() else: - return {'type': TensorStack, 'stack_dim': self.stack_dim, 'tensors': [t._spec_dict() for t in self._tensors]} + return {'type': TensorStack, 'stack_dim': self._stack_dim, 'tensors': [t._spec_dict() for t in self._tensors]} @classmethod def _from_spec_and_natives(cls, spec: dict, natives: list): @@ -1549,7 +1554,7 @@ def _with_natives_replaced(self, natives: list): return self._cached._with_natives_replaced(natives) else: tensors = [t._with_natives_replaced(natives) for t in self._tensors] - return TensorStack(tensors, self.stack_dim) + return TensorStack(tensors, self._stack_dim) def _expand(self): if self.requires_broadcast: @@ -1995,10 +2000,10 @@ def cached(t: Tensor or 'PhiTreeNode') -> Tensor or 'PhiTreeNode': return t._cached inners = cached(t._tensors) if t.requires_broadcast: - return TensorStack(inners, t.stack_dim) + return TensorStack(inners, t._stack_dim) else: natives = [t.native(order=t.shape.names) for t in inners] - native = choose_backend(*natives).stack(natives, axis=t.shape.index(t.stack_dim.name)) + native = choose_backend(*natives).stack(natives, axis=t.shape.index(t._stack_dim.name)) return NativeTensor(native, t.shape) elif isinstance(t, Layout): return t diff --git a/phi/math/extrapolation.py b/phi/math/extrapolation.py index 806691c39..26762e11d 100644 --- a/phi/math/extrapolation.py +++ b/phi/math/extrapolation.py @@ -271,9 +271,9 @@ def pad(self, value: Tensor, widths: dict, **kwargs): elif isinstance(value, TensorStack): if not value.requires_broadcast: return self.pad(value._cache(), widths) - inner_widths = {dim: w for dim, w in widths.items() if dim != value.stack_dim.name} - tensors = [self[{value.stack_dim.name: i}].pad(t, inner_widths) for i, t in enumerate(value.dimension(value.stack_dim.name))] - return TensorStack(tensors, value.stack_dim) + inner_widths = {dim: w for dim, w in widths.items() if dim != value._stack_dim.name} + tensors = [self[{value._stack_dim.name: i}].pad(t, inner_widths) for i, t in enumerate(value.dimension(value._stack_dim.name))] + return TensorStack(tensors, value._stack_dim) else: return Extrapolation.pad(self, value, widths, **kwargs) @@ -407,9 +407,9 @@ def pad(self, value: Tensor, widths: dict, **kwargs) -> Tensor: elif isinstance(value, TensorStack): if not value.requires_broadcast: return self.pad(value._cache(), widths) - inner_widths = {dim: w for dim, w in widths.items() if dim != value.stack_dim_name} - tensors = [self.pad(t, inner_widths) for t in value.dimension(value.stack_dim.name)] - return TensorStack(tensors, value.stack_dim) + inner_widths = {dim: w for dim, w in widths.items() if dim != value._stack_dim_name} + tensors = [self.pad(t, inner_widths) for t in value.dimension(value._stack_dim.name)] + return TensorStack(tensors, value._stack_dim) elif isinstance(value, ShiftLinTracer): return self._pad_linear_tracer(value, widths) else: From b1da81f7bccc3b95360573c82c5c484b100c1eb9 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 31 Jan 2023 18:43:20 +0100 Subject: [PATCH 081/170] [doc] Update Planets_Tutorial.ipynb * Use pairwise_distances, iterate --- docs/Planets_Tutorial.ipynb | 76 ++++++++++++------------------------- 1 file changed, 24 insertions(+), 52 deletions(-) diff --git a/docs/Planets_Tutorial.ipynb b/docs/Planets_Tutorial.ipynb index 63ec73428..b69eaeb9b 100644 --- a/docs/Planets_Tutorial.ipynb +++ b/docs/Planets_Tutorial.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "pycharm": { "name": "#%%\n" @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": { "pycharm": { "name": "#%%\n" @@ -127,7 +127,7 @@ { "data": { "text/plain": "
", - "image/png": "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\n" + "image/png": "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o4i4pYI5JKl4dY90Zl6WmcMzswWYADyYmV+o9xyS1Ah8nrQkFaypyoNn5kPAQ1XOIEkl80xakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpmpCWpYEZakgpW90hHxOERMScilkTEUxExtd4zSFKjaKrgmF3A32Xmgog4EJgfEQ9k5uIKZpGkotX9TDozV2fmgtrXrwNLgGH1nkOSGkGla9IR0QKMBh7bwWVTIqIjIjo6OzvrPpsklaCySEfEAcAdwFcy87XtL8/M6ZnZlpltzc3N9R9QkgpQSaQj4j10B/rWzPxxFTNIUiOo4tkdAXwfWJKZ19f7+JLUSKo4kz4JmAj8UUQsrH2cWcEcklS8uj8FLzP/FYh6H1eSGpGvOJSkghlpSSqYkZakghlpSSqYkZakghlpSSqYkZakghlpSSqYkZakghlpSSqYkZakghlpSSqYkZakghlpSSqYkZakghlpSSqYkZakghlpSSqYkZakghlpSSqYkZakghlpSSqYkZakghlpSSqYkZakghlpSSqYkZakghlpSSqYka6Tb37zmxx33HGMGjWK1tZWHnvssapHkvZIgwYNorW1dfPHtdde26fb33nnnSxevHjzdnt7Ox0dHf095i5rquzIe5FHH32Uu+++mwULFjB48GBeeukl3n777arHkvZI++23HwsXLtyt23Z1dXHnnXcyduxYjj322P4dbDd5Jl0Hq1evZujQoQwePBiAoUOHcthhh9HS0sJLL70EQEdHB+3t7QBcddVVTJ48mfb2do488ki+853vVDW6tMe45pprOP744xkxYgRTpkwhM4HuM+XLL7+cT3/603zrW9/irrvu4uKLL6a1tZVf//rXANx+++2ccMIJHHXUUcydO7eucxvpOjjttNNYuXIlRx11FBdeeCEPP/zwTm+zdOlSfv7znzNv3jyuvvpqNmzYUIdJpca3bt26bZY7fvjDHwJw0UUX8fjjj7No0SLWrVvH3Xffvfk2r776Kg8//DBXXHEF48aNY9q0aSxcuJAPf/jDQPcZ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\n" }, "metadata": { "needs_background": "light" @@ -171,7 +171,7 @@ { "data": { "text/plain": "
", - "image/png": "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\n" + "image/png": "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\n" }, "metadata": { "needs_background": "light" @@ -217,7 +217,7 @@ { "data": { "text/plain": "
", - "image/png": "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\n" + "image/png": "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\n" }, "metadata": { "needs_background": "light" @@ -238,10 +238,11 @@ } }, "source": [ - "To Simulate our system, we define a simple update step and run it repeatedly, collecting all intermediate states in the lists `xs` and `vs`.\n", + "To Simulate our system, we define a simple update step, `simulation`, and iterate it to produce the position and velocity trajectories `xs` and `vs`.\n", + "We use `iterate` instead of a `for`-loop because `iterate` can also stack all intermediate values to give us the full trajectory along the newly-defined `time` axis.\n", + "This saves us from creating two lists, adding every element and manually stacking the values.\n", "\n", - "For the pair-wise distances between our planets, we use `math.rename_dims` to rename our *planets* dimension to *others*. Our previous tensors are constant along *others* and the renamed tensor is constant along *planets*.\n", - "When combining tensors with different dimensions in an operation, they will be reshaped automatically." + "Inside the simulation step, `math.pairwise_distances` computes all body-body distances, adding a new instance dimension with the name `others` by default." ] }, { @@ -252,53 +253,23 @@ "name": "#%%\n" } }, - "outputs": [], - "source": [ - "def simulate(x, v, dt=.5):\n", - " dx = x - math.rename_dims(x, 'planets', 'others')\n", - " a = - .01 * math.sum(math.divide_no_nan(math.rename_dims(masses, 'planets', 'others') * dx, math.vec_squared(dx) ** 1.5), 'others')\n", - " return x + v * dt, v + a * dt\n", - "\n", - "xs, vs = [x], [v]\n", - "for i in range(100):\n", - " x, v = simulate(x, v)\n", - " xs.append(x)\n", - " vs.append(v)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "Finally, let's plot the system trajectory as an animation!\n", - "First, we need to define a dimension which will list our animation frames." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, "outputs": [ { "data": { - "text/plain": "\u001B[92m(timeᵇ=101, planetsⁱ=Sun,Earth,Mars, vectorᶜ=x,y)\u001B[0m \u001B[94m-2.464 ± 7.704\u001B[0m \u001B[37m(-2e+01...1e+01)\u001B[0m" + "text/plain": "\u001B[92m(timeᵇ=101, planetsⁱ=Sun,Earth,Mars, vectorᶜ=x,y)\u001B[0m \u001B[94m8.559 ± 24.659\u001B[0m \u001B[37m(-6e+01...7e+01)\u001B[0m" }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "xs = math.stack(xs, batch('time'))\n", - "vs = math.stack(vs, batch('time'))\n", + "def simulate(x, v, dt=.5):\n", + " dx = math.pairwise_distances(x)\n", + " a = - .01 * math.sum(math.divide_no_nan(math.rename_dims(masses, 'planets', 'others') * dx, math.vec_squared(dx) ** 1.5), 'others')\n", + " return x + v * dt, v + a * dt\n", + "\n", + "xs, vs = iterate(simulate, batch(time=100), x, v)\n", "xs" ] }, @@ -310,13 +281,14 @@ } }, "source": [ - "Now we can specify this dimension via the `animate` argument.\n", - "Using `vis.overlay` additionally allows us to plot multiple fields in one figure." + "Let's plot the system trajectory as an animation!\n", + "We can use the `time` dimension created above for the animation.\n", + "Using `vis.overlay` allows us to plot multiple fields in one figure." ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "pycharm": { "name": "#%%\n" @@ -325,10 +297,10 @@ "outputs": [ { "data": { - "text/plain": "", - "text/html": "" + "text/plain": "", + "text/html": "" }, - "execution_count": 9, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" }, From 89acf87a0bfd4b763ddfd30e362496f2f99e796d Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 31 Jan 2023 18:45:01 +0100 Subject: [PATCH 082/170] [physics] Rename arguments in incompressible_rk4 --- docs/prerendered/HigherOrder_Demo.ipynb | 48 +------------------ .../prerendered/Taylor_Green_Comparison.ipynb | 32 ++++++------- phi/physics/fluid.py | 23 ++++----- 3 files changed, 30 insertions(+), 73 deletions(-) diff --git a/docs/prerendered/HigherOrder_Demo.ipynb b/docs/prerendered/HigherOrder_Demo.ipynb index 01f02edfe..e7ecdbb6a 100644 --- a/docs/prerendered/HigherOrder_Demo.ipynb +++ b/docs/prerendered/HigherOrder_Demo.ipynb @@ -182,7 +182,7 @@ "\n", "@jit_compile\n", "def rk4_step(v, p, dt):\n", - " return fluid.incompressible_rk4(momentum_equation, v, p, dt, order=4, solve=Solve('CG', 1e-5, 1e-5))" + " return fluid.incompressible_rk4(momentum_equation, v, p, dt, pressure_order=4, pressure_solve=Solve('CG', 1e-5, 1e-5))" ], "metadata": { "id": "R-e3yX3cZ95Z", @@ -211,51 +211,7 @@ "v0 = StaggeredGrid(0, **DOMAIN)\n", "p0 = CenteredGrid(0, **DOMAIN)\n", "multi_step = lambda *x, **kwargs: iterate(rk4_step, 25, *x, **kwargs)\n", - "v_trj, p_trj = iterate(multi_step, batch(time=100), v0, p0, dt=0.005, range=trange)" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 49, - "referenced_widgets": [ - "daa8a904b6824715890d965e9e9a1b08", - "f6898b402c62403e8c689a5e18c299d4", - "176186b2c10b4df0a6158c9a23d8758d", - "d91c4f13076b434f82f59fe6a08a6bb4", - "60db88e473014d83a4767272051961b3", - "c8fa3abfaf4d41acbe520d983d28575c", - "9975b313c00e4d998768f2513f386c14", - "02503916033d47b7b436dce40d019948", - "baf769c8c81a43989aa60693a0b65adf", - "379e91b0499c46b8a7da1f3e23d68c72", - "720685a440694d179f972eb9459eb73a" - ] - }, - "id": "z52DqqPKd8OA", - "outputId": "4af37e8b-e978-4224-ed49-34bfc84eb193", - "pycharm": { - "name": "#%%\n" - } - }, - "execution_count": 4, - "outputs": [ - { - "data": { - "text/plain": " 0%| | 0/100 [00:00", + "text/plain": "", "text/html": "" }, "execution_count": 3, @@ -166,7 +166,7 @@ "\n", "@jit_compile(auxiliary_args='order,implicit,pressure_order', forget_traces=True)\n", "def rk4_step(v, p, dt, order=6, implicit=None, pressure_order=4):\n", - " return fluid.incompressible_rk4(partial(momentum_equation, order=order, implicit=implicit), v, p, dt, order=pressure_order, solve=Solve('CG', 1e-12, 1e-12))" + " return fluid.incompressible_rk4(momentum_equation, v, p, dt, order=order, implicit=implicit, pressure_order=pressure_order, pressure_solve=Solve('CG', 1e-12, 1e-12))" ] }, { @@ -201,7 +201,7 @@ "application/vnd.jupyter.widget-view+json": { "version_major": 2, "version_minor": 0, - "model_id": "a5a3e575ce9940798980dff28c2da3a0" + "model_id": "b4a16f86960f41789de3161f37bde297" } }, "metadata": {}, @@ -209,7 +209,7 @@ }, { "data": { - "text/plain": "", + "text/plain": "", "text/html": "" }, "execution_count": 5, @@ -316,7 +316,7 @@ "application/vnd.jupyter.widget-view+json": { "version_major": 2, "version_minor": 0, - "model_id": "8e0b3151a3504c378b3ef5083c4014fb" + "model_id": "bef013bd9552498c97a2a600cc3d78c7" } }, "metadata": {}, @@ -353,13 +353,13 @@ "name": "#%%\n" } }, - "execution_count": 8, + "execution_count": 12, "outputs": [ { "data": { "text/plain": "
" }, - "execution_count": 8, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" }, @@ -390,13 +390,13 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 13, "outputs": [ { "data": { "text/plain": "
" }, - "execution_count": 9, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" }, @@ -456,20 +456,20 @@ "name": "#%%\n" } }, - "execution_count": 10, + "execution_count": 14, "outputs": [ { "data": { "text/plain": "
" }, - "execution_count": 10, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": "
", - "image/png": "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zEmc0aoyyZ+T/LUmfZHaLjw311bKq3bhaRs76P9GERHJjLWW+fPnM27cOP744w+Cg4N5//33+b//+z8FKxGRfJLlT1drbYmZK5CU5ASf3F5b6ULbhZ/dOlztwkVEJC9Ya1m4cCHjxo3j999/p379+rzzzjvcdNNN+Pv7e7s8EZFirVj++io+HtasKZh24ZldX0ntwkVExFt+/vlnnnjiCX799Vfq1q3LG2+8wS233EIp/cckIlIgimXAWrMG2rXLuD2zduHZXYBW7cJFRKSo+PXXXxk3bhw//fQTtWvX5rXXXuO2226jdOnS3i5NRKREKZYBq359eOEFtQsXEZHi7/fff2fcuHEsWLCAmjVr8vLLL3PHHXcQoP/0RES8olgGrGrVYOhQb1chIiKSf/744w/GjRvHDz/8QPXq1XnhhRcYNWoUZcuW9XZpIiIlWrEMWCIiIsVVREQE48aN49tvv6Vq1ao899xz/Pvf/6ZcuXLeLk1ERFDAEhERKRJWrlzJ+PHjmTt3LpUrV2bSpEncddddBAYGers0ERFJQwFLRESkEFuzZg3jx49nzpw5BAUF8dRTT3H33XdToUIFb5cmIiKZUMASEREphNavX8+ECROYOXMmFSpUYPz48dxzzz0EBQV5uzQREcmGApaIiEghsnHjRiZMmIDb7SYwMJDHH3+c++67j0qVKnm7NBERyQEFLBERkUJg8+bNPPnkk3z22WeUKVOGhx9+mAceeIAqVap4uzQREckFBSwREREv+ueff3jqqaf46KOPCAgIYOzYsYwdO5Zq1ap5uzQRETkPClgiIiJesH37diZOnMj//vc//P39uffee3nooYeoUaOGt0sTEZELoIAlIiJSgHbu3MnTTz/NBx98gK+vL3fddRf/+c9/qFWrlrdLExGRPKCAJSIiUgB2797NM888w7vvvosxhlGjRvHwww9Tp04db5cmIiJ5SAFLREQkH+3du5dJkybx9ttvY63l1ltv5ZFHHqFevXreLk1ERPKBApaIiEg+2L9/P5MnT+bNN98kISGBm2++mUcffZQGDRp4uzQREclHClgiIiJ56ODBgzz33HNMmzaNuLg4brzxRh577DEaNWrk7dJERKQAKGCJiIjkgcOHD/P888/z2muvERMTw4gRI3j88cdp0qSJt0sTEZECpIAlIiJyAY4ePcoLL7zAK6+8wpkzZ7j++ut5/PHHad68ubdLExERL1DAEhEROQ/Hjh3jxRdf5KWXXuL06dMMHz6cJ554glatWnm7NBER8SIFLBERkVw4ceIEL730Ei+++CInTpxg6NChjBs3jjZt2ni7NBERKQQUsERERHLg1KlTvPLKK0yZMoXjx49z9dVXM378eNq1a+ft0kREpBBRwBIREcnG6dOnee2113j++ec5evQoAwcOZPz48YSGhnq7NBERKYQUsERERDJx5swZpk2bxnPPPcfhw4e5/PLLGT9+PBdddJG3SxMRkUJMAUtERCSN6Oho3nzzTZ599lkOHjzIpZdeyoQJE+jatau3SxMRkSLAx9sF5CVjzEBjzNsnTpzwdikiIlLExMTE8Morr9CoUSPuv/9+2rZty5IlS/jhhx8UrkREJMeKVcCy1n5trb2jYsWK3i5FRESKiNOnTzNt2jSaNGnCPffcQ/PmzVm8eDELFiygR48e3i5PRESKGE0RFBGREsFay/79+1m1alW625YtW7DW0rNnTz766CP69Onj7VJFRKQIU8ASEZFiJzExkc2bN2cIUwcPHvQc06hRI9q3b8+IESPo1asXvXr1whjjxapFRKQ4UMASEZEi7fTp06xduzZdkFq7di3R0dEAlCpVijZt2nDllVfSvn172rdvT0hICJpOLiIi+UEBS0REioRzTfEDqFSpEh06dGD06NGeMNWiRQv8/f29XL2IiJQUClgiIlLo5GSKX8OGDT1T/FLCVN26dTXNT0REvEoBS0REvConU/xat27NFVdckW6KX1BQkHcLFxERyYQCloiIFJh9+/adc4pf+/btGTVqVLopfqVKlfJy5SIiIjmjgCUiInkuMTGRLVu2ZAhTBw4c8ByTMsXvhhtu8ISpevXqaYqfiIgUaQpYIiJyQc6cOZNhit+aNWs8U/z8/f1p06YNl19+uab4iYhIsaeAJSIiOZZZF7/Nmzdrip+IlEjx8fHs3r2bmJgYb5ci+SggIIC6devmuCOtApaIiGSQmyl+119/vab4iUiJtHv3bsqXL09wcLB+9hVT1lqOHDnC7t27adiwYY7uo4AlIlLCaYqfiMj5iYmJUbgq5owxVKlShUOHDuX4PgpYIiIlyLmm+AUFBdGhQwdN8RMRySGFq+Ivt99jBSwRkWJIU/xERES8QwFLRKSIy8kUv9atW3PZZZd5glS7du00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V87fF8VLFLCkxEpKSmLr1q3p1kutWLGC48ePA86we+vWrbnyyis9bdFDQkIoV66cdwsXERERSeP06dMMGTKEl156iQoVKni2X3PNNQB07NjRs4zhl19+4e677wYgJCSEkJCQDOfbtGkTDRs2pFmzZgDcdNNNvP76656AlRKkNm7cSMOGDWnatCkAI0aM4O233wZg/vz5zJ07lylTpgBOaNu1axcA4eHhxTZcgQKWlBAJCQls3LgxXZBauXIlp0+fBqB06dKEhITgcrnSXbA3ICDAy5WLiIiIZC0+Pp4hQ4Zwww03eAJVitKlSwPg6+vruRQMcM7mWtbabPen/WVzVuey1jJ79myaN2+ebvsff/xR7H9ZrYAlxU5cXBzr169PF6ZWr17tmWNctmxZ2rdvz8iRIz1hqlWrVvj7+3u5chEREZGcs9Zy66230rJlS+6///4c3adXr1588skn9OnTh3Xr1rFmzZoMx7Ro0YIdO3awdetWmjRpwkcffcQll1yS6XHbt2/nn3/+oXHjxnz22Weeff379+fVV1/l1VdfxRjDypUr6dChw/k/2SJEAUuKtOjoaNauXZtuvdTatWuJj48HoEKFCnTo0IFRo0Z52qI3a9bsgjreiIiIiGSmRo28b9Oend9++42PPvqItm3b0r59ewCeeeYZLr/88izvM3r0aG6++WZCQkJo3749nTt3znBMQEAAH3zwAcOGDfM0uRg1alSmx7399ttcccUVVK1alZ49e7Ju3ToAHn/8ce69915CQkKw1hIcHFxi2rebcw0BFkWdOnWyERER3i5D8tjp06fTXbB3xYoVbNiwgcTERAAqV67sCVEpI1ONGjXCx8fHy5WLiIhIcfT333/TsmVLb5chBSCz77UxZrm1NsMFxDSCJYXS8ePHWblyZbowtWnTJs+c4Bo1atCxY0cGDRrkCVP169fXBXtFRERExKsUsMTrDh8+nC5ILV++nG3btnn216tXj9DQUK677jpPmKpdu7YXKxYRERERyZwClhSoffv2pVsvtWLFCiIjIz37GzVqRGhoKLfddhsdO3akQ4cOVKtWzYsVi4iIiIjknAKW5AtrLbt27Uo3MrVixQr2798POC09mzVrxsUXX5zugr2VKlXycuUiIiIiIudPAUsuWFJSEtu2bcsQpo4cOQI4115o1aoV/fv394Spdu3aUb58eS9XLiIiIiKStxSwJFcSExPZvHlzuvVSK1eu5OTJkwD4+/vTtm1bBg8e7AlTISEhlClTxsuVi4iIiIjkPwUsyVJ8fDx///13uvVSq1atIioqCnCufdCuXTtuuOEGT2v01q1bU6pUKS9XLiIiIlLwatasyYE8vBBWjRo1PMsrsvP000/z6aef4uvri4+PD2+99RZdunS5oMfu3r07v//+e4bt48ePJzAwkLFjx17Q+fPTm2++SdmyZbnxxhu98vgKWAJATEwM69atSzfFb82aNcTGxgIQGBhIhw4duP322z0jUy1atMDPT3+FRERERIA8DVc5Pd/SpUuZN28eK1asoHTp0hw+fJi4uLgLfuzMwlV+SUhIyNP3lJldFLkg6d1xCRQVFcXq1avThal169aRkJAAQFBQEKGhoYwZM8YTppo2baoL9oqIiIgUMvv27aNq1aqULl0agKpVqwKwfPly7r//fk6fPk3VqlWZPn06tWrVonfv3nTo0IHly5dz6NAhPvzwQyZNmsTatWtxuVxMnDgRcH65fvr06RzXcfXVVxMZGUlMTAz33HMPd9xxBwDvvfcekydPpnbt2jRt2pTSpUvz2muvMXLkSCpXrszKlSsJDQ3F5XJx7733Eh0dTZkyZfjggw9o3rw506dPZ+7cuURFRfHPP/8wePBgnnvuuWzPnXaU7Z133uHtt98mLi6OJk2a8NFHH1G2bFlGjhxJhQoViIiIYP/+/Tz33HMMHTo0T74nCljF3MmTJ1m1alW61ugbN24kKSkJcP4RduzYkcsuu4zQ0FA6duxIcHCwLtgrIiIiUgRceumlPPnkkzRr1oywsDBcLhfdu3dnzJgxfPXVV1SrVg23282jjz7K+++/D0CpUqX45ZdfePnllxk0aBDLly+ncuXKNG7cmPvuu48qVarkuo7333+fypUrEx0dzUUXXcSQIUOIjY3lqaeeYsWKFZQvX56+ffvSrl07z302b97MggUL8PX15eTJk/zyyy/4+fmxYMECHnnkEWbPng3AqlWrWLlyJaVLl6Z58+aMGTMGX1/fbM+d4pprruH2228H4LHHHuO9995jzJgxgBNOlyxZwsaNG7nqqqsUsCSjI0eOsHLlynQjU1u2bPHsr127NqGhoQwbNswzMlWnTh2FKREREZEiKjAwkOXLl/Prr7/y008/4XK5eOyxx1i3bh3h4eGA06SsVq1anvtcddVVALRt25bWrVt79jVq1IjIyMjzClivvPIKc+bMASAyMpItW7awf/9+LrnkEipXrgzAsGHD2Lx5s+c+w4YNw9fXF4ATJ05w0003sWXLFowxxMfHe47r168fFStWBKBVq1bs3LmTw4cPZ3vuFOvWreOxxx7j+PHjnD59mv79+3v2XX311fj4+NCqVas8nd6pgFVEHThwIENb9B07dnj2BwcHExoayk033URoaCgdOnSgZs2a3itYRERERPKFr68vvXv3pnfv3rRt25bXX3+d1q1bs3Tp0kyPT5lO6OPj4/k85euUJSMpHn30Ub755hvAGUnKzM8//8yCBQtYunQpZcuWpXfv3sTExGCtzbbucuXKeT5//PHH6dOnD3PmzGHHjh307t07Q70pzzUhIeGc504xcuRIvvzyS9q1a8f06dP5+eefMz1vTs+XEwpYhZy1lj179qRri75ixQr27t3rOaZp06Z06dKF0aNHe8LU+fzmQURERESKlk2bNuHj40PTpk0BJwS1bNmS+fPns3TpUrp160Z8fDybN2+mdevWuT7/008/zdNPP53tMSdOnKBSpUqULVuWjRs3smzZMgA6d+7Mfffdx7FjxyhfvjyzZ8+mbdu2WZ6jTp06AEyfPv2cdeX03KdOnaJWrVrEx8fzySefeB4jPylgFSLWWnbs2JGuLfqKFSs4dOgQ4PxWoUWLFvTt29ezXqp9+/ZUqFDBy5WLiIiISI0aNfK8Tfu5nD59mjFjxnD8+HH8/Pxo0qQJb7/9NnfccQd33303J06cICEhgXvvvfe8AlZmJk6cyEsvveT5+p9//uHNN98kJCSE5s2b07VrVwDq1KnDI488QpcuXahduzatWrXyTPU720MPPcRNN93E1KlT6du37zlryOm5n3rqKbp06UKDBg1o27Ytp06dOr8nnQsmL4fDCotOnTrZiIgIb5eRraSkJLZs2ZJhmt/x48cB8PPzo3Xr1p7rS6VcsDftUKqIiIiIeM/ff/9Ny5YtvV1GoXb69GkCAwNJSEhg8ODB3HLLLQwePLjQn/tsmX2vjTHLrbWdzj5WI1gFICEhgY0bN6YLUitXrvS0vixdujQhISG4XC5PmGrTpg0BAQFerlxERERE5PyNHz+eBQsWEBMTw6WXXsrVV19dJM59IRSw8lhcXBzr169Pt15q9erVxMTE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\n" + "image/png": "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IsnJyQQFBTFq1Cj69+/vfsz27dtZunQpvr6+nD17lmXLluHn58fSpUv5j//4Dz788EMA1q5dy5o1awgICKBHjx7Mnj0bX1/fco9dYMqUKdxzzz0APPvss7z77rvMnj0bcMLp8uXL2bp1KzfffLMCllRednY269at8whUBw4cAKBRo0YMHjyYp556ipiYGIYOHer+FxARERERqZmaNGlCUlIS//73v/nmm2+IjY3l2WefZePGjYwdOxaA3Nxc2rRp437MzTffDEC/fv3o06eP+74uXbpw4MCBSwpYv//97/noo48AOHDgADt27ODIkSNcd911hISEADBt2jS2b9/ufsy0adPck6RTU1O544472LFjB8YYsrOz3fuNHj3afQmf3r17s2/fPk6cOFHusQts3LiRZ599ljNnzpCWlsb111/vvu+WW27Bx8eH3r17V+nyTgWsOuzEiRMewyhWrVpFeno6AB06dHB3pmJiYujfvz8NGjTwcsUiIiIiUlm+vr6MGDGCESNG0K9fP9555x369OnDihUrSt2/YDmhj4+PxzAyHx8f90qmAs888wyLFy8GnE5Sab799luWLl3KihUrCAwMZMSIEWRkZGCtLbfuxo0bu28/99xzjBw5ko8++oi9e/cyYsSIEvUWvNacnJyLHrvArFmz+Pjjj+nfvz9z5szh22+/LfW4FT1eRShg1RF5eXls3brVozu1bds2APz8/Bg4cCD33nuve1R6hw4dvFyxiIiIiFyubdu24ePjQ7du3QAnBPXq1YslS5awYsUKhg0bRnZ2Ntu3b6dPnz6VPv7LL7/Myy+/XO4+qampBAcHExgYyNatW1m5ciUAgwcP5vHHH+f06dMEBQXx4Ycf0q9fvzKP0a5dOwDmzJlz0boqeuxz587Rpk0bsrOz+ec//+l+juqkgFVLpaWlsWrVKneYWrFiBWfOnAGgRYsWxMTEMGvWLGJiYoiOjiYwMNC7BYuIiIjUca1ataryMe0Xk5aWxuzZszlz5gx+fn6Eh4fz5z//mXvvvZdHHnmE1NRUcnJyeOyxxy4pYJXmpZde4ne/+5376127dvGnP/2JiIgIevTowdChQwFo164d//Ef/8GQIUNo27YtvXv3di/1K+4Xv/gFd9xxB2+88QajRo26aA0VPfaLL77IkCFDCAsLo1+/fpw7d+7SXnQlmKpsh9Ul0dHRNjEx0dtlAE7Lct++fR7dqXXr1pGXlwdAnz59PJb7devWTaPSRURERKrZli1b6NWrl7fLqNHS0tJo0qQJOTk5TJ48mbvuuovJkyfX+GMXV9rP2hiTZK2NLr6vOlg1UGZmJmvWrPEIVIcPHwacExmHDBnCM8884x5G0bx5c+8WLCIiIiJSihdeeIGlS5eSkZHBuHHjuOWWW2rFsS+HAlYNcPToUVasWOEOU4mJiWRmZgLQuXNnRo0a5e5O9e3bt0ovxCYiIiIiUl1ef/31Wnnsy6F36ldYbm4umzZt8uhO7dq1C3CuSRAVFcXDDz/sHkZRdKSmiIiIiNQs1lqdmlHHVfaUKgWsapaamuoelb5ixQpWrlzpPrmuVatWXH311TzwwAPExMQQGRnpMS5SRERERGquhg0bcvLkSVq0aKGQVUdZazl58iQNGzas8GMUsKqQtZadO3d6dKc2bdqEtRYfHx8iIiL46U9/6l7u16lTJ/1lFBEREaml2rdvT0pKCsePH/d2KVKNGjZsSPv27Su8vwLWZUhPTycxMdEjUJ04cQKAZs2aMWzYMKZPn05MTAyDBw8mKCjIyxWLiIiISFVp0KABnTt39nYZUsMoYFXCwYMHPcLUmjVryM7OBqB79+5MmDDB3Z3q1asXPj4+Xq5YRERERESuJAWsMlhrSUpK8ghU+/fvB5w24eDBg/n5z3/uHpUeGhrq5YpFRERERMTbdKHhMvj6+tqCC/m2a9eOq6++2t2d6t+/P/7+/l6uUEREREREvKWsCw0rYJXBGJMK7PByGc2A1Cv8nC2BE1f4OUVqIm/8/ZOqp5/j5avP38O69Npr22upifXWlJr0/rDmCLPWlljGpiWCZXNZa+/1ZgHGmD9f6RqMMYmlJXGR+sYbf/+k6unnePnq8/ewLr322vZaamK9NaUmvT+s+TSFoWwLvV0ANaMGkfpKf//qBv0cL199/h7Wpdde215LTay3ptRUU+qQMmiJoHjQv1CIiIiISFF6f1g56mBJcX/2dgEiIiIiUqPo/WElqIMlIiIiIiJSRdTBEhERERERqSIKWCIiIiIiIlVEAUtERERERKSKKGCJiIiIiIhUEQUsKZcxposx5l1jzHxv1yIiIiIi3meMucUY8xdjzL+MMeO8XU9No4BVDxlj3jPGHDPGbCy2/QZjzDZjzE5jzNMA1trd1tq7vVOpiIiIiFwJlXx/+LG19h5gFhDrhXJrNAWs+mkOcEPRDcYYX+Ad4EagNxBnjOl95UsTERERES+YQ+XfHz6bf78UoYBVD1lrlwGnim0eDOzM71hlAR8Ak654cSIiIiJyxVXm/aFx/Ab4zFqbfKVrrekUsKRAO+BAka9TgHbGmBbGmD8BA40xv/ROaSIiIiLiBaW+PwRmA2OAqcaY+71RWE3m5+0CpMYwpWyz1tqTgP7iiIiIiNQ/Zb0//D3w+ytdTG2hDpYUSAE6FPm6PXDIS7WIiIiIiPfp/eElUMCSAquBbsaYzsYYf2AG8ImXaxIRERER79H7w0uggFUPGWMSgBVAD2NMijHmbmttDvAw8AWwBZhrrd3kzTpFRERE5MrQ+8OqY6y13q5BRERERESkTlAHS0REREREpIooYImIiIiIiFQRBSwREREREZEqooAlIiIiIiJSRRSwREREREREqogCloiIiIiISBVRwBIRkVrPGJNrjFlrjNlojFlojGlehcceYYxZdJF9mhtjHizydVtjzPyqqkFERGoPBSwREakL0q21A6y1fYFTwENX+PmbA+6AZa09ZK2deoVrEBGRGkABS0RE6poVQDsAY0xXY8znxpgkY8y/jTE987dPy+92rTPGLMvf1tAY874xZoMxZo0xZmTxAxtjXjDGPFnk643GmE7Aq0DX/C7afxtjOhljNpZ3XGPMLGPMgvz6dhhjXqvub4yIiFQ/P28XICIiUlWMMb7AaODd/E1/Bu631u4wxgwB/gCMAn4FXG+tPVhkOeFDANbafvlBbIkxpnsFn/ppoK+1dkB+HZ2K3FfecQcAA4FMYJsx5i1r7YHKvWoREalJ1MESEZG6oJExZi1wEggBvjTGNAFigHn59/0v0CZ//++BOcaYewDf/G3Dgb8DWGu3AvuAigas8pR33K+stanW2gxgMxBWBc8nIiJepIAlIiJ1QXp+9ygM8MfpGvkAZ/LPzSr46AVgrb0feBboAKw1xrQATAWeJwfP350NK/CY8o6bWeR2LlpZIiJS6ylgiYhInWGtTQUeAZ4E0oE9xphpAMbRP/92V2vtj9baXwEncILWMuAn+fd3BzoC24o9xV4gMn+fSKBz/vZzQFAZZVXkuCIiUkcoYImISJ1irV0DrANm4ASbu40x64BNwKT83f47f+jERpwAtA7n/CxfY8wGwAXMstZmFjv8h0BI/pLDB4Dt+c95Evg+f+jFfxd7TEWOKyIidYSx1nq7BhERERERkTpBHSwREREREZEqooAlIiIiIiJSRRSwREREREREqogCloiIiIiISBVRwBIREREREakiClgiIiIiIiJVRAFLRERERESkivx//givyMcZp4UAAAAASUVORK5CYII=\n" }, "metadata": { "needs_background": "light" @@ -492,20 +492,20 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 15, "outputs": [ { "data": { "text/plain": "
" }, - "execution_count": 11, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": "
", - "image/png": "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HDnCV199xahRo1izZg2xsbEcPXrU28acOXN4/PHHOXbsmHd26MjISFq3bs3o0aOZM2cOt912G1dddZX3nLFjxxIZGcmYMWPo06cPXbp0ITk5mT179vD2228zfvx4Vq1axbBhw3j66afZunUrAwcOpEePHvz888+0a9eOt99+u9C/F1u3bk1SUhINGzbk7bffZtKkSRhjSEhI4J133vG+b+5xeX9etWvXrqT/ar4TKPdkSQXVr1+f3r1707t3b++23HW98vZ6paSkMH/+fDIyMopsxxjjDVwREREYY9i9ezfGGIwxREdH8+677/L1119jraVjx44EBQXRunVr/u///s97Xt42cr8vuM0f95OJiIiIuGHz5s00atSIUaNGsWLFCrp168bkyZMZOnQoL774IpMmTSIx8fgEeA0bNmTZsmW8/PLLTJo0iTfffDNfey+99BIAq1atYt26dQwYMIANGzYAsGjRIlauXEn9+vV57rnniIiIYOXKlaxcudL7i/m9e/fy9NNPM3fuXOrUqcOECRN47rnneOyxxwBnzakffvih1OsKCwtjwYIFTJ48mcGDB5OcnEz9+vU57bTTuPvuuwFYv349b731FmeddRajR4/m5ZdfZsyYMUW2t3r1asaNG8ePP/5Iw4YN2bdvX779xf28Ap3+lVuNGWNo2bIlLVu25IILLvBuz8rKYuPGjaxfv57Dhw9z9OhR0tLSvF9L+/7nn3+mXr16HD161Hv+rl27+Omnn8pVX2hoaJkDWXnCW8Hvw8PDNZxRKuzAgQNcf/31pKSkYIxh6tSp9OrVC4BJkyZx7733smfPHi0cLiIigPPvrWXLljFlyhR69OjBnXfeyTPPPMNTTz1V5PG5t4d069aNzz77rND+H374gdtvvx2AmJgYTjnlFG/I6t+/P/Xr1wdgwYIF3HHHHQAkJCSQkJAAwOLFi1mzZo13AraMjAzv32NAmScxu+SSSwCIj4+nY8eONGvWDIBTTz2V7du3ExUVRcuWLb3vc/XVV/PCCy8UG7Lmz5/P0KFDvX9/5l5HVaeQVQOFhIQQGxtbaE2BE2WtJT09vVxBrbT9v//+O9u3by+0PTMz84RqrOwgl/t9IK44Lr5R3Lj67du3880339CqVSuXKxQRkUASHR1NdHS0d1bmoUOH8swzzxR7fK1atQDnvvysrKxC+0ua2KxOnTr5Xhf1y2VrLf379+eDDz4oUxul1RkUFOT9Pvd1bt0F37+kX3Zba6vlL8MVsqTCcocY+mN8bFZWls+CXO73+/btK/K8E5mlMSQkpNKDXG7vXFBQoEwOWv0VN64e4O6772bixIkMHjzYxQpFRCTQNG3alJYtW7J+/Xrat2/PvHnz6NChAwB169bl0KFD5Wqvd+/evPfee/Tt25cNGzawbds22rdvz7Jly4o87txzzyUlJYWVK1cCzrqmt956K5s2baJt27akpaWRmppKu3btfHPBeWzbto1FixbRq1cvPvjgA84+++xij+3Xrx+XXXYZd999Nw0aNGDfvn2FerNO5OflNoUsqVJCQkKoW7cudevWrdT3sdZy7Ngxn/bO7d+/n19//bXQ9uLujytNeHh4pQa5vL1z1fE3TOVR3Lj6efPm0aJFCzp16uR2iSIiUpomTXw7w2CTJqUeMmXKFEaMGEFGRgannnoq06ZNA2DkyJHcdNNN+Sa+KM0tt9zCTTfdRHx8PCEhIUyfPj1fT1Kum2++mVGjRpGQkEDnzp3p3r07AI0aNWL69OkMHz6cY8eOAfD0009XSsiKjY1lxowZ3HjjjZx++uncfPPNxR7bsWNHHn74Yc455xyCg4Pp0qWL95eauQr+vKrCxBfG12sqBaLExERbkfUBRCpTdnZ2uYJceXvq8n5/Iv+/BwcHV1qQK7gtUHvnkpKS6NmzJz/++KN3XH3ujb9z5syhXr16+WZJEhER961du9Znt0ZI2W3dupVBgwblW1e1MhX139kYk2ytdXWWDPVkibgsODiYyMhIIiMjK/V9rLXee4l8Fer++OMPdu7cWWh77m/IyqtWrVqV1iOX92tYWFi5eueKGlc/duxYtmzZ4u3FSk1NpWvXrixZsoSmZVigUkRERKovhSyRGsIYQ61atahVq1aFF6YuTW7v3In0uBW3/7fffitye05OTrnrCwoKyhe8atWqRXh4eKGveb8/duwYI0eOpFmzZixatIicnBweeugh7/777ruPJ598kuTk5ELnFvW1vEFPRESkKmjdurXferECmUKWiPicP3vnMjMzK9w7d+zYMdLT071fc++hK7j9nXfeyRfqFi5cmK+eUaNGlav+soS7E/1a1mMV9kQKGz16NLNmzaJx48befyzee++9fPnll4SFhXkXcY2KiiIzM5Prr7+eZcuWkZWVxTXXXMODDz7o8hWIiNsUskSkyjLGeGf6i4qK8tv75uTkFApgZf1anmNzZ78s7twTXdKgIDdDnsKeBKKRI0dy2223cc0113i39e/fn/HjxxMSEsL999/P+PHjmTBhAp988gnHjh1j1apVpKWl0aFDB4YPH07r1q3duwARcZ1ClohIOeUON3R7dqMTCXsnEgxzw15xx/g67LkR8hT2JK/evXuzdevWfNsGDBjg/b5nz57MnDkTcH7Zc+TIEe8SI2FhYZx00kn+LFdEApBClohIFVWVwt6JhLuCX48cOeKXsOdmyMs9V0snBLapU6cybNgwwJkI5/PPP6dZs2akpaXx/PPPF1rjR0RqHoUsERGpkKoQ9nwR8nK/D+Sw5+uAqLBX2Lhx4wgJCWHEiBEALFmyhODgYHbs2MH+/fv505/+xHnnncepp57qcqVSUFOasgvfrZPVhCb8xm+lHpednU1iYiItWrRg1qxZAEyfPp0BAwbQvHlzgEpdBqRPnz5MmjSJxERXZzSvcRSyRESkWgjksOfLkBfIYa+yegEDJezNmDGDWbNmMW/ePG8977//PgMHDiQ0NJTGjRtz1llnkZSUpJAVgHwZsMrT3uTJk4mNjeXgwYPebdOnTycuLs4bsnwlKyuLkJCK/fM+Ozub4OBgH1VUcylkiYiI+FAghr3KCHmBGPbKGub69etHfHx8ud5/9uzZTJgwge+//56IiAjv9latWjF//nyuvvpq0tLSWLx4MXfddZdPrlmqvtTUVL766isefvhhnnvuOQBmzpxJUlISI0aMoHbt2ixatAiAKVOm8OWXX5KZmcknn3xCTExMvrbS09O5+eabSUpKIiQkhOeee45zzz2X6dOn89VXX3mHVX/11VeMGjWKNWvWEBsby9GjR71tzJkzh8cff5xjx455Z8mMjIykdevWjB49mjlz5nDbbbdx1VVX+e+HVE0pZImIiFRDgRL2srOzOXbsWKWFvBMJe6+99lqJIWv48OF899137N27l+joaJ544gnGjx/PsWPH6N+/P+BMfvHqq69y6623MmrUKOLi4rDWMmrUKBISEir95ypVw1133cXEiRM5dOiQd9vQoUN58cUXCw3ha9iwIcuWLePll19m0qRJvPnmm/naeumllwBYtWoV69atY8CAAWzYsAGARYsWsXLlSurXr89zzz1HREQEK1euZOXKlXTt2hWAvXv38vTTTzN37lzq1KnDhAkTeO6553jssccA5xcZP/zwQ6X+PGoShSypUp5//nnefPNNjDHEx8czbdo0wsPD3S5LRESKERwcTERERL7eHzfkDXul/b3xwQcfFNp23XXXFXlsZGQkn3zyiU9qlOold621bt268d1335V6/JAhQwDo1q0bn332WaH9P/zwA7fffjsAMTExnHLKKd6Q1b9/f++EKwsWLOCOO+4AICEhwRv6Fy9ezJo1azjrrLMAyMjIoFevXt72cydzEd9QyJIq49dff+WFF15gzZo11K5dmyuvvJIPP/yQkSNHul2aiIgEuEAJe1Jz/Pjjj3zxxRd8/fXXpKenc/DgQa6++mrefffdIo+vVasW4HxWs7KyCu231hb7XnXq1Mn3uqh7GK219O/fv8hfIhTVhlRMkNsFiJRH7jokWVlZpKWl+fyGURERERFfGD9+PKmpqWzdupUPP/yQvn37egNW3bp18w0hLIvevXvz3nvvAbBhwwa2bdtG+/btSzwuJSWFlStXAs4Q1x9//JFNmzYBkJaW5u0JE99TyJIqo0WLFowZM4ZWrVrRrFkz6tWrl29xSBEREZHiNKFJwLQ3cuRIbrrpJjp37pxvYoqS3HLLLWRnZxMfH8+wYcOYPn26t/crr5tvvpnDhw+TkJDAxIkT6d69OwCNGjVi+vTpDB8+nISEBHr27Mm6detO+BqkZKakrsfqIjEx0SYlJbldhlTQ/v37ufzyy/noo4+IioriiiuuYOjQoVx99dVulyYiIiIBZu3atcTGxrpdhlSyov47G2OSrbWuLgymniwJCNnZUFrenzt3Lm3atKFRo0aEhoYyZMgQ/vvf//qnQBERERGRMlLIkoDw/PPQrx9s3Fj8Ma1atWLx4sWkpaVhrWXevHn6DZWIiIiIBByFLAkIDRvCsmUQHw9/+xsUtXZljx49GDp0KF27diU+Pp6cnBxuuOEG/xcrIiIiIlIC3ZMlAWPnTrjjDpg5E+Li4I03oGdPt6sSERGRqkj3ZNUMuidLpBTNmsEnn8Dnn8OBA3DmmXD77XDwoNuViYiIiIiUnUKWBJxLLoE1a+C22+Cll6BDB/jiC7erEhEREREpG4UsCUh168ILL8CiRVC/PgweDEOHOkMKRURERMqraVMwxnfPpk1Lfr/t27dz7rnnEhsbS8eOHZk8eXK5a+7Tpw++uuXlu+++Y9CgQT5pS0qnkCUBrUcPSE52JsOYNQtiY+G11yAnx+3KREREpCrZtcu/7YWEhPDss8+ydu1aFi9ezEsvvcSaNWt8W0QJsrOzK3R+VlaWjyqpmRSyJOCFhsKDD8KqVdCtG9x0E5xzDqxd63ZlIiIiIkVr1qwZXbt2BaBu3brExsby66+/Ak4P1f3330/37t1p164dCxcuBODo0aNcddVVJCQkMGzYMI4ePVpk2/PmzaNLly7Ex8czevRojh07BkDr1q158sknOfvss/nkk0+YPXs2MTExnH322Xz22Wfe848cOcLo0aM544wz6NKlC59//jkA06dP54orruDiiy9mwIABlfazqQkUsqTKOP10mDsXpk937tnq1AnGjgXPnysiIiIiAWnr1q38/PPP9OjRw7stKyuLJUuW8I9//IMnnngCgFdeeYWIiAhWrlzJww8/THJycqG20tPTGTlyJB999BGrVq0iKyuLV155xbs/PDycH374gUsvvZS//OUvfPnllyxcuJDffvvNe8y4cePo27cvS5cu5dtvv+Xee+/lyJEjACxatIgZM2Ywf/78yvpx1AgKWVKlGAPXXuv0Yl15JTzxBHTuDJ5fAFXY6NGjady4MXFxcfm2T5kyhfbt29OxY0fuu+8+37yZiIiIVHuHDx/m8ssv5x//+AcnnXSSd/uQIUMA6NatG1u3bgVgwYIFXH311QAkJCSQkJBQqL3169fTpk0b2rVrB8C1117LggULvPuHDRsGwLp162jTpg2nn346xhhvuwBz5szhmWeeoXPnzvTp04f09HS2bdsGQP/+/alfv74PfwI1k0KWVEmNG8O778Ls2ZCeDr17w403OlO/V8TIkSOZPXt2vm3ffvstn3/+OStXrmT16tWMGTOmYm8iIiIiNUJmZiaXX345I0aM8IaqXLVq1QIgODg43/1PxpgS2yxtjds6deqU2pa1lk8//ZTly5ezfPlytm3b5l1rKu/5cuIUsqRKO/98SEmBMWPgzTediTE++QROdI3t3r17F/rtzSuvvMIDDzzg/cOwcePGFS1bREREqjlrLddddx2xsbHcc889ZTqnd+/evPfeewCkpKSwcuXKQsfExMSwdetWNm3aBMA777zDOeecU+RxW7Zs4ZdffgHggw8+8O47//zzmTJlijew/fzzz+W7OCmVQpZU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88/37rv00ksJCgqiQ4cOPh/m6S+B0pO1FDjdGNPGGBMGXAV84XJNIkVq0aIFY8aMoVWrVjRr1ox69epx4YUDOOccZyjh+vXO89lnnZ6tiRPhzDOhWTMYOdKZTOPgwcqtsVatWsyfP58VK1awfPlyZs+ezeLFi+nfvz8pKSmsXLmSdu3aMX78+MotREREpAYLDg6mT58+PPHEE7z44ot8+umndOzYkeXLl7N8+XJWrVrFnDlzvMfnDi0MCgryfp/7OisrK1/bDz/8MJ07d6Zz587Fvv93333H3LlzWbRoEStWrKBLly6kp6dT2jq5derU8X7/6KOPcu6555KSksKXX35Jenp6oXpzrzUrK6vUtnONHDmSF198kVWrVvH4448X225VXdPXjSncPwAWAe2NManGmOustVnAbcB/gLXAx9ba1f6uTaQs9u/fz+eff86WLVvYsWMHR44c4d133813TLt2cM898O23ziLIH3wA/fvDF1/A0KFO+BowAF54AbZs8X2NxhjvmO3MzEwyMzMxxjBgwADvDaw9e/YkNTXV928uIiIirF+/no0bN3pfL1++nNjYWPbs2cOiRYsA5+/o1atP7J+848aN84a14vzxxx+cfPLJREREsG7dOhYvXgxA9+7d+f7779m/fz9ZWVl8+umnJbbRooUzVcL06dNLrausbR86dIhmzZqRmZnJe++9V2q7VY3fQ5a1dri1tpm1NtRaG22tfcuz/WtrbTtr7WnW2nH+rksEYB/7OEjJ3Uxz586lTZs2NGrUiNDQUIYMGVLiTD9RUXDVVfDee7B7N3z/Pdx1F2zfDnfeCaeeCh07wgMPwI8/Qna2b64lOzubzp0707hxY/r370+PHj3y7Z86dSoXXHCBb95MREQkwDVp0sSv7R0+fJhrr72WDh06kJCQwJo1a3jyySeZOXMm999/P506daJz584+nS3w6aefJjo62vscOHAgWVlZJCQk8Oijj9KzZ0/AGZXz0EMP0aNHD8477zw6dOjgHfZX0H333ceDDz7IWWedRXYZ/pFS1rafeuopevToQf/+/YmJianYhQcgU1W74MojMTHRJiUluV2GBLhjHCOBBDrSkU/5FFPkfCzw008/MXr0aJYuXUrt2rUZOXIkiYmJ3H777eV+z02bYNYs5z6uBQsgKwsaNIALL3Tu4xowAIr5M6/MDhw4wGWX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Te0yBbImNCGKKFcC2bhx4wgJCWGE537OJUuWEBwczI4dO9i/fz9/+tOfOO+88zj11FP9XltlUsgSERGp6TIyYMwYmDIFzjoLPv4Ymjd3uyqRaq02tTnF8yhNJpmlLhC9la0sYQm72U0OOYXaCCW0UAArLpA1oIFPFoieMWMGs2bNYt68eRhPj/j777/PwIEDCQ0NpXHjxpx11lkkJSUpZImIiEg1kpoKV14JixbBXXfBxIkQWr1v0hepakIJpbnnUZoccvid38s0/f1udpNBRqE2ctc/a0ITruEaxjCm3DXPnj2bCRMm8P333xMREeHd3qpVK+bPn8/VV19NWloaixcv5q677ip3+4FOIUtERKSmmj8frroK0tLgo4+csCUiVVoQQTTyPOIoeUZQi+UP/igxkJVlQefhw4fz3XffsXfvXqKjo3niiScYP348x44do3///oAz+cWrr77KrbfeyqhRo4iLi8Nay6hRo0hISPDJtQcSTeEuIiJS01jr9Fg99BC0aweffQaxJd/ULyJSVWgKdxEREfGvP/6AkSPhX/9yeq7efNOZ6EJERHxGIUtERKSmWLnSmZ5961Z4/nm4805Nzy4iUgkUskRERGqCd9+FG26AqCj49ls4+2y3KxIRqbaC3C5AREREKtGxY3DLLfDnP0P37rBsmQKWiEglU8gSERGprrZtg9694ZVXnHWw5s6Fpk3drkpEpNpTyBIREamOvvkGunYlfc0aurdtS6c5c+jYqROPP/44APfeey8xMTEkJCRw2WWXceDAAXfrFRGpRhSyREREqpOcHBg3Ds4/H5o2pdbSpcz/+WdWrFjB8uXLmT17NosXL6Z///6kpKSwcuVK2rVrx/jx492uXESk2lDIEhERqS7274fBg+GRR2D4cPjpJ0xMDJGRkQBkZmaSmZmJMYYBAwYQEuLMf9WzZ09SU1PdrFxEpFpRyBIREakOfv4ZunWD//wHpkxxZhOsUweA7OxsOnfuTOPGjenfvz89evTId+rUqVO54IIL3KhaRKRaUsgSERGp6qZNgzPPhIwM+P57uO22fOtfBQcHs3z5clJTU1myZAkpKSnefePGjSMkJIQRI0a4UbmISLWkkCUiIlJVpac7a1+NHu2ErGXLoFevYg+PioqiT58+zJ49G4AZM2Ywa9Ys3nvvPYwWJRYR8RmFLBERkapo925nvas3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\n" + "image/png": "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EOjO7H/++NPa80iSSCK72JVqgeT/8T8+5EPA6ZbYgAaXdTGsQIVsX3f69OnMnz+fJUuWYDytdp9++ik9evTA19eXihUr0qlTJyIiIpRgiYiIiIhIJrZuhZtvhsqVYcECKFPG7YhS8cabhp7HAAYAzmQa0USnaun6iZ+Yyczk86pT/bKkqwY1Lqt/wYIFjB07lh9++IESJUok769RowZLly5l8ODBxMTEsHr1ap588sk8v9/8pFkERURERERy0/790KkTxMfDjz9C3bpuR3RVjnP8j0WSPY8d7OASl6hFLToM7MCyZcs4duwYlSpVYtSoUYwZM4aLFy9Svnx5wJnoYvLkyZw7d46hQ4eydetWrLUMHTqUESNG5Mt95NcsgkqwRERERERyy9Gjznirw4fhhx+gRQu3I8oT5zlPJJGc4hQ96el2ONmiadpFRERERAqTs2edboH79jmTWxTR5AqgJCXpSEe3wyiQlGCJiIiIiFytixehb1/YsAG++sppxZJiSQmWiIiIiMjVSEyEu++GxYth2jS49Va3IxIX5e4yziIiIiIixYm1ziLCs2bB+PFw771uRyQuU4IlIiIiInKlXnkFJk+G556Dv/7V7WikAFCCJSIiIiJyJd56C/7xD7jvPhgzxu1opIDQGCwRERERkWyIjY2lc+fOXLx4kYTjx+l38CCj+vRhRJkyzAsLw8/Pj7p16zJ16lSCg4PdDldcohYsEREREZFs8Pf3Z+nSpWx6/XU2Hj7MglKlWP3kk3S/6SaioqKIjIykQYMGjFFrVrGmBEtEREREJBuMMQRt3gx33EF8kybE166NCQggPDwcHx+nY1iHDh2Ijo52OVJxkxIsEREREZHs2LKFxJtvpmViIhV37aJ7jx60b98+VZEpU6bQs2dPlwKUgkAJloiIiIhIVvbtg5tuwjswkI3btxN98CBr1qwhKioqucjo0aPx8fFh0KBBLgYqblOCJSIiIiKSmaNHITwczp+H776D2rUJDg6ma9euLFiwAIDp06czf/58PvnkE4wxLgcsblKCJSIiIiKSmb/8BXbu5OiMGZyqXh2ACxcusHjxYho1asSCBQsYO3Ysc+fOpUSJEi4HK27TNO0iIiIiIpm58Ub46it+mzmTe//+dxITE7l06RJ33XUXvXr1ol69ely8eJHu3bsDzkQXkydPdjlocYsSLBERERGR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IxqITtESPRsn4UpFnx0prpRgiUi2eCYq+BDYZq19I4MyrYD/AL2BoUA5Y8xrObjMWqC+Maa2Z9D0AGDu1UUubjPGhHharjDGBOJ049mepow+O/nMWvuCtTbUWlsL5/1aaq0dnLKMfi6SHmvtYeCAMaahZ9efgK0py+izI8WZEiwRya5OwN0433Jv9Gw3pylTArjTWvurtfYScC+wL21FxpjPgFVAQ2NMtDHmPgBrbQIwHPgOZxKNz621W/LuliSfVAG+N8ZE4vzRtMham3ZKcH12Cib9XCQjjwGfeP67bgn8M81xfXak2NI07SIiIiIiIrlELVgiIiIiIiK5RAmWiIiIiIhILlGCJSIiIiIikkuUYImIiIiIiOQSJVgiIiIiIiK5RAmWiIiIiIhILlGCJSIihZIxpnyKNdkOG2MOep6fM8a863Z8IiJSPGkdLBERKfSMMSOBc9ba8W7HIiIixZtasEREpEgxxnQ1xsz3PB9pjJlujFlojNlrjOlrjBlnjNlsjFlgjPH1lGtjjPnBGLPOGPOdMaaKu3chIiKFlRIsEREp6uoCtwC9gY+B7621zYALwC2eJGsS0M9a2waYAox2K1gRESncfNwOQEREJI99a62NN8ZsBryBBZ79m4FaQEOgKbDIGIOnzG8uxCkiIkWAEiwRESnqLgJYay8ZY+LtH4OPL+H8HjTAFmttR7cCFBGRokNdBEVEpLjbAYQYYzoCGGN8jTFNXI5JREQKKSVYIiJSrFlr44B+wFhjzCZgI3Ctq0GJiEihpWnaRUREREREcolasERERERERHKJEiwREREREZFcogRLREREREQklyjBEhERERERySVKsERERERERHKJEiwREREREZFcogRLREREREQkl/w/nOcQ8AhhzJwAAAAASUVORK5CYII=\n" }, "metadata": { "needs_background": "light" @@ -514,7 +514,7 @@ } ], "source": [ - "plot(PointCloud(vec(time=exec_times, error=errors.time[-1]).resolution.as_spatial().method.as_channel(), color=SIM_COLOR), log_dims='error', title=\"Error vs Performance\")" + "plot(PointCloud(vec(time=exec_times, error=errors.time[-1]).resolution.as_spatial().method.as_channel(), color=SIM_COLOR), log_dims='error,time', title=\"Error vs Performance\")" ], "metadata": { "collapsed": false, diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index d23c0dfec..618019ea1 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -225,7 +225,7 @@ def _accessible_extrapolation(vext: Extrapolation): raise ValueError(f"Unsupported extrapolation: {type(vext)}") -def incompressible_rk4(pde: Callable, velocity: GridType, pressure: CenteredGrid, dt, order=4, solve=Solve('CG', 1e-12, 1e-12)): +def incompressible_rk4(pde: Callable, velocity: GridType, pressure: CenteredGrid, dt, pressure_order=4, pressure_solve=Solve('CG', 1e-12, 1e-12), **pde_aux_kwargs): """ Implements the 4th-order Runge-Kutta time advancement scheme for incompressible vector fields. This approach is inspired by [Kampanis et. al., 2006](https://www.sciencedirect.com/science/article/pii/S0021999105005061) and incorporates the pressure treatment into the time step. @@ -235,11 +235,12 @@ def incompressible_rk4(pde: Callable, velocity: GridType, pressure: CenteredGrid velocity: Velocity grid at time `t`. pressure: Pressure at time `t`. dt: Time increment to integrate. - solve: `Solve` object specifying method and tolerances for the implicit pressure solve. - order: spatial order for derivative computations. + pressure_order: spatial order for derivative computations. For Higher-order schemes, the laplace operation is not conducted with a stencil exactly corresponding to the one used in divergence calculations but a smaller one instead. While this disrupts the formal correctness of the method it only induces insignificant errors and yields considerable performance gains. supported: explicit 2/4th order - implicit 6th order (obstacles are only supported with explicit 2nd order) + pressure_solve: `Solve` object specifying method and tolerances for the implicit pressure solve. + **pde_aux_kwargs: Auxiliary arguments for `pde`. These are considered constant over time. Returns: velocity: Velocity at time `t+dt`, same type as `velocity`. @@ -247,24 +248,24 @@ def incompressible_rk4(pde: Callable, velocity: GridType, pressure: CenteredGrid """ v_1, p_1 = velocity, pressure # PDE at current point - rhs_1 = pde(v_1) - field.spatial_gradient(p_1, type=StaggeredGrid, order=order) + rhs_1 = pde(v_1, **pde_aux_kwargs) - field.spatial_gradient(p_1, type=StaggeredGrid, order=pressure_order) v_2_old = velocity + (dt / 2) * rhs_1 - v_2, delta_p = make_incompressible(v_2_old, solve=solve, order=order) + v_2, delta_p = make_incompressible(v_2_old, solve=pressure_solve, order=pressure_order) p_2 = p_1 + delta_p / dt # PDE at half-point - rhs_2 = pde(v_2) - field.spatial_gradient(p_2, type=StaggeredGrid, order=order) + rhs_2 = pde(v_2, **pde_aux_kwargs) - field.spatial_gradient(p_2, type=StaggeredGrid, order=pressure_order) v_3_old = velocity + (dt / 2) * rhs_2 - v_3, delta_p = make_incompressible(v_3_old, solve=solve, order=order) + v_3, delta_p = make_incompressible(v_3_old, solve=pressure_solve, order=pressure_order) p_3 = p_2 + delta_p / dt # PDE at corrected half-point - rhs_3 = pde(v_3) - field.spatial_gradient(p_3, type=StaggeredGrid, order=order) + rhs_3 = pde(v_3, **pde_aux_kwargs) - field.spatial_gradient(p_3, type=StaggeredGrid, order=pressure_order) v_4_old = velocity + dt * rhs_2 - v_4, delta_p = make_incompressible(v_4_old, solve=solve, order=order) + v_4, delta_p = make_incompressible(v_4_old, solve=pressure_solve, order=pressure_order) p_4 = p_3 + delta_p / dt # PDE at RK4 point - rhs_4 = pde(v_4) - field.spatial_gradient(p_4, type=StaggeredGrid, order=order) + rhs_4 = pde(v_4, **pde_aux_kwargs) - field.spatial_gradient(p_4, type=StaggeredGrid, order=pressure_order) v_p1_old = velocity + (dt / 6) * (rhs_1 + 2 * rhs_2 + 2 * rhs_3 + rhs_4) p_p1_old = (1 / 6) * (p_1 + 2 * p_2 + 2 * p_3 + p_4) - v_p1, delta_p = make_incompressible(v_p1_old, solve=solve, order=order) + v_p1, delta_p = make_incompressible(v_p1_old, solve=pressure_solve, order=pressure_order) p_p1 = p_p1_old + delta_p / dt return v_p1, p_p1 From b56a21f6183a9ae3f77a6998ce25be28fda92e8c Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 31 Jan 2023 21:42:39 +0100 Subject: [PATCH 083/170] [math] Fix matrix_from_function for stacked outputs * Add default value to Shape.get_size(dim) * Improve sparse tensor formatting --- phi/math/_shape.py | 38 +++-- phi/math/_sparse.py | 9 +- phi/math/_tensors.py | 52 ++++-- phi/math/_trace.py | 234 ++++++++++++++++----------- phi/math/extrapolation.py | 2 +- tests/commit/math/test__sparse.py | 6 +- tests/commit/physics/test_diffuse.py | 10 +- 7 files changed, 216 insertions(+), 135 deletions(-) diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 356daa4f9..4c225438f 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -153,24 +153,31 @@ def indices(self, dims: tuple or list or 'Shape') -> Tuple[int]: else: raise ValueError(f"indices() requires a sequence of dimensions but got {dims}") - def get_size(self, dim: str or 'Shape' or int): + def get_size(self, dim: str or 'Shape' or int, default=None): """ See Also: `Shape.get_sizes()`, `Shape.size` Args: dim: Dimension, either as name `str` or single-dimension `Shape` or index `int`. + default: (Optional) If the dim does not exist, return this value instead of raising an error. Returns: Size associated with `dim` as `int` or `Tensor`. """ + if isinstance(dim, int): + assert default is None, "Cannot use a default value when passing an int for dim" + return self.sizes[dim] + if isinstance(dim, Shape): + assert dim.rank == 1, f"get_size() requires a single dimension but got {dim}. Use indices() to get multiple sizes." + dim = dim.name if isinstance(dim, str): + if dim not in self.names: + if default is None: + raise KeyError(f"get_size() failed because '{dim}' is not part of Shape {self} and no default value was provided") + else: + return default return self.sizes[self.names.index(dim)] - elif isinstance(dim, Shape): - assert dim.rank == 1, f"get_size() requires a single dimension but got {dim}. Use indices() to get multiple sizes." - return self.sizes[self.names.index(dim.name)] - elif isinstance(dim, int): - return self.sizes[dim] else: raise ValueError(f"get_size() requires a single dimension but got {dim}. Use indices() to get multiple sizes.") @@ -781,7 +788,7 @@ def with_size(self, size: int or None): assert self.rank == 1, "Shape.with_size() is only defined for shapes of rank 1." return self.with_sizes([size]) - def with_sizes(self, sizes: tuple or list or 'Shape', keep_item_names=True): + def with_sizes(self, sizes: tuple or list or 'Shape' or int, keep_item_names=True): """ Returns a new `Shape` matching the dimension names and types of `self` but with different sizes. @@ -800,6 +807,8 @@ def with_sizes(self, sizes: tuple or list or 'Shape', keep_item_names=True): Returns: `Shape` with same names and types as `self`. """ + if isinstance(sizes, int): + sizes = [sizes] * len(self.sizes) if isinstance(sizes, Shape): item_names = [sizes.get_item_names(dim) if dim in sizes else self.get_item_names(dim) for dim in self.names] sizes = [sizes.get_size(dim) if dim in sizes else s for dim, s in self._named_sizes] @@ -933,11 +942,20 @@ def replace(self, dims: 'Shape' or str or tuple or list, new: 'Shape') -> 'Shape replaced = Shape(tuple(sizes), tuple(names), tuple(types), tuple(item_names)) if len(new) == len(dims): return replaced - to_remove = dims[len(dims) - len(new):] + to_remove = dims[-(len(dims) - len(new)):] return replaced.without(to_remove) - def _with_types(self, types: 'Shape'): - return Shape(self.sizes, self.names, tuple([types.get_type(name) if name in types else self_type for name, self_type in zip(self.names, self.types)]), self.item_names) + def _with_types(self, types: 'Shape' or str): + """ + Only for internal use. + Note: This method does not rename dimensions to comply with type requirements (e.g. ~ for dual dims). + """ + if isinstance(types, Shape): + return Shape(self.sizes, self.names, tuple([types.get_type(name) if name in types else self_type for name, self_type in zip(self.names, self.types)]), self.item_names) + elif isinstance(types, str): + return Shape(self.sizes, self.names, (types,) * self.rank, self.item_names) + else: + raise ValueError(types) def _with_item_names(self, item_names: tuple): return Shape(self.sizes, self.names, self.types, item_names) diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index 7a7aaa02b..bbaecc807 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -5,7 +5,7 @@ import numpy as np import scipy.sparse -from ._shape import Shape, non_batch, merge_shapes, instance, batch, non_instance, shape, channel, spatial, DimFilter, concat_shapes, EMPTY_SHAPE, dual +from ._shape import Shape, non_batch, merge_shapes, instance, batch, non_instance, shape, channel, spatial, DimFilter, concat_shapes, EMPTY_SHAPE, dual, DUAL_DIM, SPATIAL_DIM from ._magic_ops import concat, pack_dims, expand, rename_dims from ._tensors import Tensor, TensorStack, CollapsedTensor, NativeTensor, cached, wrap from .backend import choose_backend, NUMPY @@ -43,6 +43,7 @@ def __init__(self, indices: Tensor, values: Tensor, dense_shape: Shape, can_cont assert instance(indices), "indices must have an instance dimension" assert 'vector' in indices.shape, "indices must have a vector dimension" assert set(indices.vector.item_names) == set(dense_shape.names), "The 'vector' dimension of indices must list the dense dimensions as item names" + assert indices.dtype.kind == int, f"indices must have dtype=int but got {indices.dtype}" self._shape = merge_shapes(dense_shape, batch(indices), non_instance(values)) self._dense_shape = dense_shape self._indices = indices @@ -110,7 +111,7 @@ def _native_coo_components(self, col_dims: DimFilter, matrix=False): def _pack_indices(self, row_dims: Shape, col_dims: Shape): assert self._indices.default_backend is NUMPY, "Can only compress NumPy indices as of yet" - assert self._dense_shape in row_dims, f"Can only compress sparse dims but got {row_dims} which contains non-sparse dims" + assert row_dims in self._dense_shape, f"Can only compress sparse dims but got {row_dims} which contains non-sparse dims" from ._ops import reshaped_native row_idx = self._indices[row_dims.names] col_idx = self._indices[self._dense_shape.without(row_dims).names] @@ -357,7 +358,7 @@ def _with_shape_replaced(self, new_shape: Shape): def _native_csr_components(self): from phi.math import reshaped_native - ind_batch = batch(self._indices & self._pointers) + ind_batch = batch(self._indices) & batch(self._pointers) channels = non_instance(self._values).without(ind_batch) native_indices = reshaped_native(self._indices, [ind_batch, instance], force_expand=True) native_pointers = reshaped_native(self._pointers, [ind_batch, instance], force_expand=True) @@ -476,7 +477,7 @@ def dense(x: Tensor) -> Tensor: from phi.math import reshaped_tensor if isinstance(x, SparseCoordinateTensor): from ._ops import scatter, zeros - base_grid = zeros(spatial(**x.shape.untyped_dict), dtype=x.dtype) + base_grid = zeros(x.shape._with_types(SPATIAL_DIM), dtype=x.dtype) result_sp = scatter(base_grid, x._indices, x._values, mode='add', outside_handling='undefined') result = rename_dims(result_sp, shape, x.shape) return result diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index 8c7ac4503..b14ca198b 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -13,7 +13,7 @@ from ._shape import (Shape, CHANNEL_DIM, BATCH_DIM, SPATIAL_DIM, EMPTY_SHAPE, parse_dim_order, shape_stack, merge_shapes, channel, concat_shapes, - TYPE_ABBR, IncompatibleShapes, INSTANCE_DIM, batch, spatial, dual) + TYPE_ABBR, IncompatibleShapes, INSTANCE_DIM, batch, spatial, dual, instance) from .backend import NoBackendFound, choose_backend, BACKENDS, get_precision, default_backend, convert as convert_, \ Backend, ComputeDevice from .backend._dtype import DType, combine_types @@ -679,7 +679,7 @@ def __iter__(self): def __matmul__(self, other): assert isinstance(other, Tensor), f"Matmul '@' requires two Tensor arguments but got {type(other)}" dims = batch(**self.shape.dual.untyped_dict).names - match = other.shape.only(dims) + match = other.shape.only(dims, reorder=True) assert len(dims) == match.rank, f"Dual dimensions {dual} do not match shape of second argument {other.shape}" left_arg = pack_dims(self, dual, dual('_reduce')) if len(dims) > 1 else self right_arg = pack_dims(other, match, channel('_reduce')) @@ -2301,32 +2301,48 @@ def format_summary(self: Tensor, options: PrintOptions) -> str: """ if not self.available: return format_tracer(self, options) + from ._sparse import SparseCoordinateTensor, CompressedSparseMatrix + if isinstance(self, (SparseCoordinateTensor, CompressedSparseMatrix)): + return sparse_summary(self, options) colors = options.get_colors() - result = [] + tokens = [] if self.shape if options.include_shape is None else options.include_shape: - result.append(f"{colors.shape(self.shape)}") + tokens.append(f"{colors.shape(self.shape)}") if is_unexpected_dtype(self.dtype) if options.include_dtype is None else options.include_dtype: - result.append(f"{colors.dtype(self.dtype)}") + tokens.append(f"{colors.dtype(self.dtype)}") try: if self.rank == 0: - result.append(colors.value(self.numpy())) + tokens.append(colors.value(self.numpy())) elif self.dtype.kind == bool: - result.append(colors.value(f"{self.sum} / {self.shape.volume} True")) + tokens.append(colors.value(f"{self.sum} / {self.shape.volume} True")) elif self.dtype.kind in (float, int): min_val, max_val, mean, std = [float(f) for f in [self.finite_min, self.finite_max, self.finite_mean, self.std]] if std == 0: - result.append(colors.value(f"const {mean:{options.float_format or ''}}")) + tokens.append(colors.value(f"const {mean:{options.float_format or ''}}")) else: if any([abs(val) < 0.001 or abs(val) > 1000 for val in [mean, std]]): - result.append(colors.value(f"{mean:{options.float_format or '.2e'}} ± {std:{options.float_format or '.1e'}}")) + tokens.append(colors.value(f"{mean:{options.float_format or '.2e'}} ± {std:{options.float_format or '.1e'}}")) else: - result.append(colors.value(f"{mean:{options.float_format or '.3f'}} ± {std:{options.float_format or '.3f'}}")) - result.append(colors.fine(f"({min_val:{options.float_format or '.0e'}}...{max_val:{options.float_format or '.0e'}})")) + tokens.append(colors.value(f"{mean:{options.float_format or '.3f'}} ± {std:{options.float_format or '.3f'}}")) + tokens.append(colors.fine(f"({min_val:{options.float_format or '.0e'}}...{max_val:{options.float_format or '.0e'}})")) elif self.dtype.kind == complex: - result.append(colors.value(f"|...| < {abs(self).max}")) + tokens.append(colors.value(f"|...| < {abs(self).max}")) except BaseException as err: - result.append(f"failed to fetch values: {err}") - return " ".join(result) + tokens.append(f"failed to fetch values: {err}") + return " ".join(tokens) + + +def sparse_summary(value: Tensor, options: PrintOptions) -> str: + colors = options.get_colors() + from ._sparse import SparseCoordinateTensor, CompressedSparseMatrix + tokens = [] + if is_unexpected_dtype(value.dtype) if options.include_dtype is None else options.include_dtype: + tokens.append(f"{colors.dtype(value.dtype)}") + tokens.append("sparse" if isinstance(value, SparseCoordinateTensor) else "compressed sparse") + if options.include_shape is not False: + tokens.append(f"{colors.shape(value.shape)}") + tokens.append(f"with {instance(value._values).volume} entries") + return " ".join(tokens) def is_unexpected_dtype(dtype: DType): @@ -2348,6 +2364,8 @@ def format_tracer(self: Tensor, options: PrintOptions) -> str: def format_full(value: Tensor, options: PrintOptions) -> str: # multi-line content if not value.available: return format_tracer(value, options) + from ._sparse import dense + value = dense(value) import re colors = options.get_colors() dim_order = tuple(sorted(value.shape.spatial.names, reverse=True)) @@ -2400,6 +2418,8 @@ def format_row(self: Tensor, options: PrintOptions) -> str: # all values in a s """ if not self.available: return format_tracer(self, options) + from ._sparse import dense + self = dense(self) colors = options.get_colors() if self.shape.rank == 1: content = _format_vector(self, options) @@ -2419,6 +2439,8 @@ def format_row(self: Tensor, options: PrintOptions) -> str: # all values in a s def format_numpy(self: Tensor, options: PrintOptions) -> str: + from ._sparse import dense + self = dense(self) header = [] colors = options.get_colors() if options.include_shape: @@ -2461,8 +2483,6 @@ def _format_number(num, options: PrintOptions, dtype: DType): def format_tensor(self: Tensor, options: PrintOptions) -> str: if not self.available: return format_tracer(self, options) - from ._sparse import dense - self = dense(self) if options.layout == 'auto': if not self.shape: return format_summary(self, options) diff --git a/phi/math/_trace.py b/phi/math/_trace.py index 97526123a..ef566e93e 100644 --- a/phi/math/_trace.py +++ b/phi/math/_trace.py @@ -1,3 +1,4 @@ +from collections import namedtuple from typing import Callable, Dict, Set, Tuple import numpy @@ -5,102 +6,13 @@ from .backend import choose_backend, NUMPY, Backend from ._shape import Shape, parse_dim_order, merge_shapes, spatial, instance, batch, concat_shapes, EMPTY_SHAPE, dual, channel, non_batch -from ._tensors import Tensor, wrap, disassemble_tree, disassemble_tensors, assemble_tree +from ._magic_ops import stack, expand +from ._tensors import Tensor, wrap, disassemble_tree, disassemble_tensors, assemble_tree, CollapsedTensor, TensorStack from ._sparse import SparseCoordinateTensor from . import _ops as math -def matrix_from_function(f: Callable, - *args, - auxiliary_args=None, - auto_compress=True, - sparsify_batch=None, - **kwargs) -> Tuple[Tensor, Tensor]: - """ - Trace a linear function and construct a (sparse) matrix. - - Args: - f: Function to trace. - *args: Arguments for `f`. - auxiliary_args: Arguments in which the function is not linear. - These parameters are not traced but passed on as given in `args` and `kwargs`. - auto_compress: If `True`, returns a compressed matrix if supported by the backend. - sparsify_batch: If `False`, the matrix will be batched. - If `True`, will create dual dimensions for the involved batch dimensions. - This will result in one large matrix instead of a batch of matrices. - **kwargs: Keyword arguments for `f`. - - Returns: - Matrix representing `f`. - """ - assert isinstance(auxiliary_args, str) or auxiliary_args is None, f"auxiliary_args must be a comma-separated str but got {auxiliary_args}" - from ._functional import function_parameters, f_name - f_params = function_parameters(f) - aux = set(s.strip() for s in auxiliary_args.split(',') if s.strip()) if isinstance(auxiliary_args, str) else f_params[1:] - all_args = {**kwargs, **{f_params[i]: v for i, v in enumerate(args)}} - aux_args = {k: v for k, v in all_args.items() if k in aux} - trace_args = {k: v for k, v in all_args.items() if k not in aux} - tree, tensors = disassemble_tree(trace_args) - # tracing = not math.all_available(*tensors) - natives, shapes, native_dims = disassemble_tensors(tensors, expand=False) - # --- Trace function --- - with NUMPY: - x = math.ones(shapes[0]) - tracer = ShiftLinTracer(x, {EMPTY_SHAPE: math.ones()}, x.shape, math.zeros(x.shape)) - x_kwargs = assemble_tree(tree, [tracer]) - result = f(**x_kwargs, **aux_args) - _, result_tensors = disassemble_tree(result) - assert len(result_tensors) == 1, f"Linear function output must be or contain a single Tensor but got {result}" - tracer_out = result_tensors[0]._simplify() - assert tracer_out._is_tracer, f"Tracing linear function '{f_name(f)}' failed. Make sure only linear operations are used. Output: {tracer_out.shape}" - assert isinstance(tracer_out, ShiftLinTracer), f"Tracing linear function '{f_name(f)}' returned a nested tracer with Shape {tracer_out.shape}. Make sure no additional dimensions get added to the output." - assert batch(tracer_out.pattern_dims).is_empty, f"Batch dimensions may not be sliced in linear operations but got pattern for {batch(tracer_out.pattern_dims)}" - # --- Convert to COO --- - if sparsify_batch is None: - if auto_compress: - sparsify_batch = not tracer_out.default_backend.supports(Backend.csr_matrix_batched) - else: - sparsify_batch = not tracer_out.default_backend.supports(Backend.sparse_coo_tensor_batched) - independent_dims = tracer_out.source.shape.without(tracer_out.dependent_dims if sparsify_batch else tracer_out.pattern_dim_names) # these will be parallelized and not added to the matrix - out_shape = tracer_out.shape.without(independent_dims) - typed_src_shape = tracer_out.source.shape.without(independent_dims) - src_shape = dual(**typed_src_shape.untyped_dict) - batch_val = merge_shapes(*tracer_out.val.values()).without(out_shape) - if non_batch(out_shape).is_empty: - assert len(tracer_out.val) == 1 and non_batch(tracer_out.val[EMPTY_SHAPE]) == EMPTY_SHAPE - return tracer_out.val[EMPTY_SHAPE], tracer_out.bias - out_indices = [] - src_indices = [] - values = [] - for shift_, shift_val in tracer_out.val.items(): - if shift_val.default_backend is NUMPY: # sparsify stencil further - native_shift_values = math.reshaped_native(shift_val, [batch_val, *out_shape], force_expand=True) - mask = np.sum(abs(native_shift_values), 0) # only 0 where no batch entry has a non-zero value - out_indices.append(numpy.nonzero(mask)) - src_indices.append([(component + shift_.get_size(dim)) % typed_src_shape.get_size(dim) if dim in shift_ else component for component, dim in zip(out_indices[-1], out_shape)]) - values.append(native_shift_values[(slice(None), *out_indices[-1])]) - else: # add full stencil tensor - all_indices = np.unravel_index(np.arange(out_shape.volume), out_shape.sizes) if out_shape else 0 - out_indices.append(all_indices) - src_indices.append([(component + shift_.get_size(dim)) % typed_src_shape.get_size(dim) if dim in shift_ else component for component, dim in zip(out_indices[-1], out_shape)]) - values.append(math.reshaped_native(shift_val, [batch_val, out_shape], force_expand=True)) - indices_np = np.concatenate([np.concatenate(src_indices, axis=1), np.concatenate(out_indices, axis=1)]).T - # _, counts = np.unique(indices_np, axis=1, return_counts=True) - # assert np.all(counts == 1) - indices = wrap(indices_np, instance('entries'), channel(vector=src_shape.names + out_shape.names)) - backend = choose_backend(*values) - values = math.reshaped_tensor(backend.concat(values, axis=-1), [batch_val, instance('entries')]) - dense_shape = concat_shapes(src_shape & out_shape) - matrix = SparseCoordinateTensor(indices, values, dense_shape, can_contain_double_entries=False, indices_sorted=False) - if not auto_compress: - return matrix, tracer_out.bias - backend = choose_backend(*values._natives()) - if backend.supports(Backend.mul_csr_dense): - return matrix.compress_rows(), tracer_out.bias - # elif backend.supports(Backend.mul_csc_dense): - # return matrix.compress_cols(), tracer_out.bias - else: - return matrix, tracer_out.bias +TracerSource = namedtuple('TracerSource', ['shape', 'dtype', 'name', 'index']) class ShiftLinTracer(Tensor): @@ -111,7 +23,7 @@ class ShiftLinTracer(Tensor): Dimensions not contained in any `val` Tensor are treated as independent (batch dimensions). """ - def __init__(self, source: Tensor, values_by_shift: dict, shape: Shape, bias: Tensor): + def __init__(self, source: TracerSource, values_by_shift: dict, shape: Shape, bias: Tensor): """ Args: source: placeholder tensor @@ -123,6 +35,7 @@ def __init__(self, source: Tensor, values_by_shift: dict, shape: Shape, bias: Te When non-zero, this tracer technically represents an affine function, not a linear one. However, the bias can be subtracted from the solution vector when solving a linear system, allowing this function to be solved with regular linear system solvers. """ + assert isinstance(source, TracerSource) self.source = source self.val: Dict[Shape, Tensor] = simplify_add(values_by_shift) for shift_ in self.val.keys(): @@ -153,13 +66,13 @@ def native(self, order: str or tuple or list or Shape = None): return result.native(result_order) @property - def dependent_dims(self): + def dependent_dims(self) -> Set[str]: """ Dimensions relevant to the linear operation. This includes `pattern_dims` as well as dimensions along which only the values vary. These dimensions cannot be parallelized trivially with a non-batched matrix. """ - return merge_shapes(*[t.shape for t in self.val.values()]) + return self.pattern_dim_names | set(sum([t.shape.names for t in self.val.values()], ())) | set(self.bias.shape.names) @property def pattern_dim_names(self) -> Set[str]: @@ -231,7 +144,7 @@ def __neg__(self): def _op1(self, native_function): # __neg__ is the only proper linear op1 and is implemented above. if native_function.__name__ == 'isfinite': - test_output = self.apply(math.ones_like(self.source)) + test_output = self.apply(math.ones(self.source.shape, dtype=self.source.dtype)) return math.is_finite(test_output) else: raise NotImplementedError('Only linear operations are supported') @@ -303,3 +216,132 @@ def simplify_add(val: dict) -> Dict[Shape, Tensor]: else: result[shift] = values return result + + +def matrix_from_function(f: Callable, + *args, + auxiliary_args=None, + auto_compress=True, + sparsify_batch=None, + separate_independent=False, # not fully implemented, requires auto_compress=False + **kwargs) -> Tuple[Tensor, Tensor]: + """ + Trace a linear function and construct a matrix. + Depending on the functional form of `f`, the returned matrix may be dense or sparse. + + Args: + f: Function to trace. + *args: Arguments for `f`. + auxiliary_args: Arguments in which the function is not linear. + These parameters are not traced but passed on as given in `args` and `kwargs`. + auto_compress: If `True`, returns a compressed matrix if supported by the backend. + sparsify_batch: If `False`, the matrix will be batched. + If `True`, will create dual dimensions for the involved batch dimensions. + This will result in one large matrix instead of a batch of matrices. + **kwargs: Keyword arguments for `f`. + + Returns: + matrix: Matrix representing the linear dependency of the output `f` on the input of `f`. + Input dimensions will be `dual` dimensions of the matrix while output dimensions will be regular. + bias: Bias for affine functions or zero-vector if the function is purely linear. + """ + assert isinstance(auxiliary_args, str) or auxiliary_args is None, f"auxiliary_args must be a comma-separated str but got {auxiliary_args}" + from ._functional import function_parameters, f_name + f_params = function_parameters(f) + aux = set(s.strip() for s in auxiliary_args.split(',') if s.strip()) if isinstance(auxiliary_args, str) else f_params[1:] + all_args = {**kwargs, **{f_params[i]: v for i, v in enumerate(args)}} + aux_args = {k: v for k, v in all_args.items() if k in aux} + trace_args = {k: v for k, v in all_args.items() if k not in aux} + tree, tensors = disassemble_tree(trace_args) + # tracing = not math.all_available(*tensors) + natives, shapes, native_dims = disassemble_tensors(tensors, expand=False) + # --- Trace function --- + with NUMPY: + src = TracerSource(tensors[0].shape, tensors[0].dtype, tuple(trace_args.keys())[0], 0) + tracer = ShiftLinTracer(src, {EMPTY_SHAPE: math.ones()}, tensors[0].shape, math.zeros(tensors[0].shape, dtype=tensors[0].dtype)) + x_kwargs = assemble_tree(tree, [tracer]) + result = f(**x_kwargs, **aux_args) + _, result_tensors = disassemble_tree(result) + assert len(result_tensors) == 1, f"Linear function output must be or contain a single Tensor but got {result}" + tracer = result_tensors[0]._simplify() + assert tracer._is_tracer, f"Tracing linear function '{f_name(f)}' failed. Make sure only linear operations are used. Output: {tracer.shape}" + # --- Convert to COO --- + if sparsify_batch is None: + if auto_compress: + sparsify_batch = not tracer.default_backend.supports(Backend.csr_matrix_batched) + else: + sparsify_batch = not tracer.default_backend.supports(Backend.sparse_coo_tensor_batched) + matrix, bias = tracer_to_coo(tracer, sparsify_batch, separate_independent) + # --- Compress --- + if not auto_compress: + return matrix, bias + if matrix.default_backend.supports(Backend.mul_csr_dense): + return matrix.compress_rows(), bias + # elif backend.supports(Backend.mul_csc_dense): + # return matrix.compress_cols(), tracer.bias + else: + return matrix, bias + + +def tracer_to_coo(tracer: Tensor, sparsify_batch: bool, separate_independent: bool): + if isinstance(tracer, CollapsedTensor): + tracer = tracer._cached if tracer.is_cached else tracer._inner # ignore collapsed dimensions. Alternatively, we could expand the result + return tracer_to_coo(tracer, sparsify_batch, separate_independent) + elif isinstance(tracer, TensorStack): # This indicates separable solves + matrices, biases = zip(*[tracer_to_coo(t, sparsify_batch, separate_independent) for t in tracer._tensors]) + bias = stack(biases, tracer._stack_dim) + if not separate_independent: + indices = [math.concat_tensor([m._indices, expand(i, instance(m._indices), channel(vector=tracer._stack_dim.name))], 'vector') for i, m in enumerate(matrices)] + indices = math.concat_tensor(indices, 'entries') + values = math.concat_tensor([m._values for m in matrices], 'entries') + # matrix = stack(matrices, tracer._stack_dim) + dense_shape = concat_shapes(matrices[0]._dense_shape, tracer._stack_dim) + matrix = SparseCoordinateTensor(indices, values, dense_shape, can_contain_double_entries=False, indices_sorted=False) + else: + matrix = stack(matrices, tracer._stack_dim) + return matrix, bias + elif not tracer._is_tracer: # This part of the output is independent of the input + return expand(0, tracer.shape), tracer + assert isinstance(tracer, ShiftLinTracer), f"Tracing linear function returned an unsupported construct: {type(tracer)}" + assert batch(tracer.pattern_dims).is_empty, f"Batch dimensions may not be sliced in linear operations but got pattern for {batch(tracer.pattern_dims)}" + missing_dims = tracer.source.shape.without(tracer.shape) # these were sliced off + ignored_dims = tracer.source.shape.without(tracer.shape.only(tracer.dependent_dims) if sparsify_batch else tracer.pattern_dim_names).without(missing_dims) # these will be parallelized and not added to the matrix + out_shape = tracer.shape.without(ignored_dims) + typed_src_shape = tracer.source.shape.without(ignored_dims) + src_shape = dual(**typed_src_shape.untyped_dict) + sliced_src_shape = src_shape.without(dual(**missing_dims.untyped_dict)) + batch_val = merge_shapes(*tracer.val.values()).without(out_shape) + if non_batch(out_shape).is_empty: + assert len(tracer.val) == 1 and non_batch(tracer.val[EMPTY_SHAPE]) == EMPTY_SHAPE + return tracer.val[EMPTY_SHAPE], tracer.bias + out_indices = [] + src_indices = [] + values = [] + for shift_, shift_val in tracer.val.items(): + if shift_val.default_backend is NUMPY: # sparsify stencil further + native_shift_values = math.reshaped_native(shift_val, [batch_val, *out_shape], force_expand=True) + mask = np.sum(abs(native_shift_values), 0) # only 0 where no batch entry has a non-zero value + out_idx = numpy.nonzero(mask) + src_idx = [(component + shift_.get_size(dim)) % typed_src_shape.get_size(dim) if dim in shift_ else component for component, dim in zip(out_idx, out_shape)] + values.append(native_shift_values[(slice(None), *out_idx)]) + else: # add full stencil tensor + out_idx = np.unravel_index(np.arange(out_shape.volume), out_shape.sizes) if out_shape else 0 + src_idx = [(component + shift_.get_size(dim)) % typed_src_shape.get_size(dim) if dim in shift_ else component for component, dim in zip(out_idx, out_shape)] + values.append(math.reshaped_native(shift_val, [batch_val, out_shape], force_expand=True)) + out_indices.append(out_idx) + src_idx_all = [] + for dim in typed_src_shape: + if dim in missing_dims: + if not separate_independent: + offset = shift_.get_size(dim, default=0) + src_idx_all.append(np.zeros(out_shape.volume, dtype=np.int32) + offset) + else: + src_idx_all.append(src_idx[out_shape.index(dim)]) + src_indices.append(src_idx_all) + indices_np = np.concatenate([np.concatenate(src_indices, axis=1), np.concatenate(out_indices, axis=1)]).T + indices = wrap(indices_np, instance('entries'), channel(vector=(sliced_src_shape if separate_independent else src_shape).names + out_shape.names)) + backend = choose_backend(*values) + values = math.reshaped_tensor(backend.concat(values, axis=-1), [batch_val, instance('entries')]) + dense_shape = concat_shapes((sliced_src_shape if separate_independent else src_shape) & out_shape) + matrix = SparseCoordinateTensor(indices, values, dense_shape, can_contain_double_entries=False, indices_sorted=False) + return matrix, tracer.bias diff --git a/phi/math/extrapolation.py b/phi/math/extrapolation.py index 26762e11d..51737b17c 100644 --- a/phi/math/extrapolation.py +++ b/phi/math/extrapolation.py @@ -407,7 +407,7 @@ def pad(self, value: Tensor, widths: dict, **kwargs) -> Tensor: elif isinstance(value, TensorStack): if not value.requires_broadcast: return self.pad(value._cache(), widths) - inner_widths = {dim: w for dim, w in widths.items() if dim != value._stack_dim_name} + inner_widths = {dim: w for dim, w in widths.items() if dim != value._stack_dim.name} tensors = [self.pad(t, inner_widths) for t in value.dimension(value._stack_dim.name)] return TensorStack(tensors, value._stack_dim) elif isinstance(value, ShiftLinTracer): diff --git a/tests/commit/math/test__sparse.py b/tests/commit/math/test__sparse.py index 61612d7d7..9a842d0eb 100644 --- a/tests/commit/math/test__sparse.py +++ b/tests/commit/math/test__sparse.py @@ -14,9 +14,9 @@ def test_sparsity(self): self.assertEqual(1, get_sparsity(wrap(1))) self.assertEqual(0.25, get_sparsity(expand(1., batch(b=4)))) self.assertEqual(0.25, get_sparsity(stack([zeros(batch(b=4))] * 3, channel('vector')))) - self.assertEqual(0.3, get_sparsity(SparseCoordinateTensor(ones(instance(nnz=3), channel(vector='x')), ones(instance(nnz=3)), spatial(x=10), True, False))) - self.assertEqual(0.03, get_sparsity(CompressedSparseMatrix(indices=ones(instance(nnz=3)), - pointers=ones(instance(y_pointers=11)), + self.assertEqual(0.3, get_sparsity(SparseCoordinateTensor(ones(instance(nnz=3), channel(vector='x'), dtype=int), ones(instance(nnz=3)), spatial(x=10), True, False))) + self.assertEqual(0.03, get_sparsity(CompressedSparseMatrix(indices=ones(instance(nnz=3), dtype=int), + pointers=ones(instance(y_pointers=11), dtype=int), values=ones(instance(nnz=3)), uncompressed_dims=spatial(x=10), compressed_dims=spatial(y=10)))) diff --git a/tests/commit/physics/test_diffuse.py b/tests/commit/physics/test_diffuse.py index 3d17f7af8..091f30a71 100644 --- a/tests/commit/physics/test_diffuse.py +++ b/tests/commit/physics/test_diffuse.py @@ -36,9 +36,9 @@ def test_constant_diffusion(self): def test_equality_1d_periodic(self): DIFFUSIVITY = 0.5 - grid = CenteredGrid((1,) * 100 + (0,) * 100, extrapolation.PERIODIC, x=200) - explicit = diffuse.explicit(grid, DIFFUSIVITY, 1, substeps=1000) - implicit = diffuse.implicit(grid, DIFFUSIVITY, 1, order=10) + grid = CenteredGrid((1,) * 100 + (0,) * 100, extrapolation.PERIODIC, x=100) + explicit = diffuse.explicit(grid, DIFFUSIVITY, 1, substeps=10) + implicit = diffuse.implicit(grid, DIFFUSIVITY, 1, order=1) fourier = diffuse.fourier(grid, DIFFUSIVITY, 1) field.assert_close(explicit, implicit, rel_tolerance=0, abs_tolerance=0.01) field.assert_close(explicit, implicit, fourier, rel_tolerance=0, abs_tolerance=0.1) @@ -46,8 +46,8 @@ def test_equality_1d_periodic(self): # print(f"{implicit.values[:6]} Implicit") # print(f"{fourier.values[:6]} Fourier") # print() - back_explicit = diffuse.explicit(explicit, DIFFUSIVITY, -1, substeps=1000) - back_implicit = diffuse.implicit(implicit, DIFFUSIVITY, -1, order=10) + back_explicit = diffuse.explicit(explicit, DIFFUSIVITY, -1, substeps=10) + back_implicit = diffuse.implicit(implicit, DIFFUSIVITY, -1, order=1) back_fourier = diffuse.fourier(fourier, DIFFUSIVITY, -1) # print(f"{back_explicit.values[:6]} Explicit") # print(f"{back_implicit.values[:6]} Implicit") From a95fe473e72c65ae753344a0d0107d8397af860c Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 31 Jan 2023 21:51:28 +0100 Subject: [PATCH 084/170] [field] Style pass: higher-order --- phi/field/_field_math.py | 81 +++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 46 deletions(-) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 82609c43f..2257f92b9 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -1,4 +1,3 @@ -from functools import partial from numbers import Number from typing import Callable, List, Tuple @@ -38,7 +37,11 @@ def bake_extrapolation(grid: GridType) -> GridType: raise ValueError(f"Not a valid grid: {grid}") -def laplace(field: GridType, axes=spatial, order=2, implicit: math.Solve = None, weights: Tensor or Field = None) -> GridType: +def laplace(field: GridType, + axes=spatial, + order=2, + implicit: math.Solve = None, + weights: Tensor or Field = None) -> GridType: """ Spatial Laplace operator for scalar grid. If a vector grid is passed, it is assumed to be centered and the laplace is computed component-wise. @@ -60,7 +63,7 @@ def laplace(field: GridType, axes=spatial, order=2, implicit: math.Solve = None, if isinstance(weights, Field): weights = weights.at(field).values axes_names = field.shape.only(axes).names - extrapol_map = {} + extrap_map = {} if not implicit: if order == 2: values, needed_shifts = [1, -2, 1], (-1, 0, 1) @@ -68,32 +71,32 @@ def laplace(field: GridType, axes=spatial, order=2, implicit: math.Solve = None, elif order == 4: values, needed_shifts = [-1/12, 4/3, -5/2, 4/3, -1/12], (-2, -1, 0, 1, 2) else: - extrapol_map_rhs = {} + extrap_map_rhs = {} if order == 6: values, needed_shifts = [3/44, 12/11, -51/22, 12/11, 3/44], (-2, -1, 0, 1, 2) - extrapol_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) + extrap_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) values_rhs, needed_shifts_rhs = [2/11, 1, 2/11], (-1, 0, 1) - extrapol_map_rhs['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) + extrap_map_rhs['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) base_widths = (abs(min(needed_shifts)), max(needed_shifts)) - field.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) + field.with_extrapolation(map(_ex_map_f(extrap_map), field.extrapolation)) padded_components = [pad(field, {dim: base_widths}) for dim in axes_names] shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, axes_names)] result_components = [sum([value * shift_ for value, shift_ in zip(values, shifted_component)]) / field.dx.vector[dim]**2 for shifted_component, dim in zip(shifted_components, axes_names)] if implicit: result_components = stack(result_components, channel('laplacian')) result_components.with_values(result_components.values._cache()) - result_components = result_components.with_extrapolation(map(_ex_map_f(extrapol_map_rhs), field.extrapolation)) + result_components = result_components.with_extrapolation(map(_ex_map_f(extrap_map_rhs), field.extrapolation)) implicit.x0 = result_components result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=implicit, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=channel('laplacian')) result_components = unstack(result_components, 'laplacian') - extrapol_map = extrapol_map_rhs + extrap_map = extrap_map_rhs result_components = [component.with_bounds(field.bounds) for component in result_components] if weights is not None: assert channel(weights).rank == 1 and channel(weights).item_names is not None, f"weights must have one channel dimension listing the laplace dims but got {shape(weights)}" assert set(channel(weights).item_names[0]) >= set(axes_names), f"the channel dim of weights must contain all laplace dims {axes_names} but only has {channel(weights).item_names}" result_components = [c * weights[ax] for c, ax in zip(result_components, axes_names)] result = sum(result_components) - result = result.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) + result = result.with_extrapolation(map(_ex_map_f(extrap_map), field.extrapolation)) return result @@ -128,44 +131,39 @@ def spatial_gradient(field: CenteredGrid, spatial_gradient field of type `type`. """ - if gradient_extrapolation is None: gradient_extrapolation = field.extrapolation.spatial_gradient() - - extrapol_map = {} + extrap_map = {} if not implicit: if order == 2: if type == CenteredGrid: values, needed_shifts = [-1/2, 1/2], (-1, 1) else: values, needed_shifts = [-1, 1], (0, 1) - elif order == 4: if type == CenteredGrid: values, needed_shifts = [1/12, -2/3, 2/3, -1/12], (-2, -1, 1, 2) else: values, needed_shifts = [1/24, -27/24, 27/24, -1/24], (-1, 0, 1, 2) + else: + raise NotImplementedError(f"explicit {order}th-order not supported") else: - extrapol_map_rhs = {} + extrap_map_rhs = {} if order == 6: if type == CenteredGrid: values, needed_shifts = [-1/36, -14/18, 14/18, 1/36], (-2, -1, 1, 2) values_rhs, needed_shifts_rhs = [1/3, 1, 1/3], (-1, 0, 1) - else: values, needed_shifts = [-17/186, -63/62, 63/62, 17/186], (-1, 0, 1, 2) - extrapol_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) - + extrap_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) values_rhs, needed_shifts_rhs = [9/62, 1, 9/62], (-1, 0, 1) - extrapol_map_rhs['symmetric'] = combine_by_direction(ANTIREFLECT, ANTISYMMETRIC) - - + extrap_map_rhs['symmetric'] = combine_by_direction(ANTIREFLECT, ANTISYMMETRIC) + else: + raise NotImplementedError(f"implicit {order}th-order not supported") base_widths = (abs(min(needed_shifts)), max(needed_shifts)) - field.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) - + field.with_extrapolation(map(_ex_map_f(extrap_map), field.extrapolation)) # ToDo does this line do anything? if implicit: - gradient_extrapolation = map(_ex_map_f(extrapol_map_rhs), gradient_extrapolation) - + gradient_extrapolation = map(_ex_map_f(extrap_map_rhs), gradient_extrapolation) spatial_dims = field.shape.spatial.names if type == CenteredGrid: # ToDo if extrapolation == math.extrapolation.NONE, extend size by 1 @@ -176,22 +174,18 @@ def spatial_gradient(field: CenteredGrid, base_widths = (abs(min(needed_shifts))+1, max(needed_shifts)+1) std_widths = (1, 1) padded_components = [pad(field, {dim_: base_widths if dim_ == dim else std_widths for dim_ in spatial_dims}) for dim in spatial_dims] - else: + elif type == StaggeredGrid: base_widths = (base_widths[0], base_widths[1]-1) - padded_components = pad_for_staggered_output(field, gradient_extrapolation, - field.shape.spatial.names, base_widths) - + padded_components = pad_for_staggered_output(field, gradient_extrapolation, field.shape.spatial.names, base_widths) + else: + raise ValueError(type) shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, spatial_dims)] result_components = [sum([value * shift for value, shift in zip(values, shifted_component)]) / field.dx.vector[dim] for shifted_component, dim in zip(shifted_components, field.shape.spatial.names)] - if type == CenteredGrid: result = stack(result_components, stack_dim) else: - result = StaggeredGrid(math.stack([component.values for component in result_components], channel('vector')), - bounds=field.bounds, extrapolation=gradient_extrapolation) - + result = StaggeredGrid(math.stack([component.values for component in result_components], channel('vector')), bounds=field.bounds, extrapolation=gradient_extrapolation) result = result.with_extrapolation(gradient_extrapolation) - if implicit: implicit.x0 = result result = result @@ -200,9 +194,9 @@ def spatial_gradient(field: CenteredGrid, result = result.with_bounds(Box(field.bounds.lower - field.dx, field.bounds.upper + field.dx)) else: result = result.with_bounds(field.bounds) - return result + def _ex_map_f(ext_dict: dict): def f(ext: Extrapolation): return ext_dict[ext.__repr__()] if ext.__repr__() in ext_dict else ext @@ -334,7 +328,7 @@ def divergence(field: Grid, order=2, implicit: Solve = None) -> CenteredGrid: Divergence field as `CenteredGrid` """ - extrapol_map = {} + extrap_map = {} if not implicit: if order == 2: if isinstance(field, CenteredGrid): @@ -348,10 +342,10 @@ def divergence(field: Grid, order=2, implicit: Solve = None) -> CenteredGrid: else: values, needed_shifts = [1 / 24, -27 / 24, 27 / 24, -1 / 24], (-1, 0, 1, 2) else: - extrapol_map_rhs = {} + extrap_map_rhs = {} if order == 6: - extrapol_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) - extrapol_map_rhs['symmetric'] = combine_by_direction(ANTIREFLECT, ANTISYMMETRIC) + extrap_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) + extrap_map_rhs['symmetric'] = combine_by_direction(ANTIREFLECT, ANTISYMMETRIC) if isinstance(field, CenteredGrid): values, needed_shifts = [-1 / 36, -14 / 18, 14 / 18, 1 / 36], (-2, -1, 1, 2) @@ -360,11 +354,8 @@ def divergence(field: Grid, order=2, implicit: Solve = None) -> CenteredGrid: else: values, needed_shifts = [-17 / 186, -63 / 62, 63 / 62, 17 / 186], (-1, 0, 1, 2) values_rhs, needed_shifts_rhs = [9 / 62, 1, 9 / 62], (-1, 0, 1) - - base_widths = (abs(min(needed_shifts)), max(needed_shifts)) - field.with_extrapolation(map(_ex_map_f(extrapol_map), field.extrapolation)) - + field.with_extrapolation(map(_ex_map_f(extrap_map), field.extrapolation)) # ToDo does this line do anything? spatial_dims = field.shape.spatial.names if isinstance(field, StaggeredGrid): base_widths = (base_widths[0]+1, base_widths[1]) @@ -377,17 +368,14 @@ def divergence(field: Grid, order=2, implicit: Solve = None) -> CenteredGrid: padded_components = [pad(component, {dim: base_widths}) for dim, component in zip(spatial_dims, unstack(field, 'vector'))] if field.extrapolation == math.extrapolation.NONE: padded_components = [pad(component, {dim_: (0, 0) if dim_ == dim else (-1, -1) for dim_ in spatial_dims}) for dim, component in zip(spatial_dims, padded_components)] - shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, spatial_dims)] result_components = [sum([value * shift for value, shift in zip(values, shifted_component)]) / field.dx.vector[dim] for shifted_component, dim in zip(shifted_components, spatial_dims)] - if implicit: result_components = stack(result_components, channel('vector')) result_components.with_values(result_components.values._cache()) implicit.x0 = field result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=implicit, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=channel('vector')) result_components = unstack(result_components, 'vector') - result_components = [component.with_bounds(field.bounds) for component in result_components] result = sum(result_components) if field.extrapolation == math.extrapolation.NONE and isinstance(field, CenteredGrid): @@ -628,6 +616,7 @@ def stack(fields, dim: Shape, dim_bounds: Box = None): Args: fields: List of matching `SampledField` instances. dim: Stack dimension as `Shape`. Size is ignored. + dim_bounds: `Box` defining the physical size for `dim`. Returns: `SampledField` matching stacked fields. From 67a7186b793882a14fbbb7aa94c878da75ccfebf Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 31 Jan 2023 22:28:44 +0100 Subject: [PATCH 085/170] [physics] Allow single Obstacle/Geometry in fluid functions * Add type checking --- phi/physics/fluid.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index 618019ea1..735388472 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -7,7 +7,7 @@ from phi import math, field from phi.math import wrap, channel, Solve -from phi.field import SoftGeometryMask, AngularVelocity, Grid, divergence, spatial_gradient, where, CenteredGrid, PointCloud +from phi.field import SoftGeometryMask, AngularVelocity, Grid, divergence, spatial_gradient, where, CenteredGrid, PointCloud, Field from phi.geom import union, Geometry from ..field._embed import FieldEmbedding from ..field._grid import GridType, StaggeredGrid @@ -50,8 +50,17 @@ def copied_with(self, **kwargs): return Obstacle(geometry, velocity, angular_velocity) +def _get_obstacles_for(obstacles, space: Field): + obstacles = [obstacles] if isinstance(obstacles, (Obstacle, Geometry)) else obstacles + assert isinstance(obstacles, (tuple, list)), f"obstacles must be an Obstacle or Geometry or a tuple/list thereof but got {type(obstacles)}" + obstacles = [Obstacle(o) if isinstance(o, Geometry) else o for o in obstacles] + for obstacle in obstacles: + assert obstacle.geometry.vector.item_names == space.vector.item_names, f"Obstacles must live in the same physical space as the velocity field {space.vector.item_names} but got {type(obstacle.geometry).__name__} obstacle with order {obstacle.geometry.vector.item_names}" + return obstacles + + def make_incompressible(velocity: GridType, - obstacles: tuple or list = (), + obstacles: Obstacle or Geometry or tuple or list = (), solve=Solve('auto', 1e-5, 1e-5, gradient_solve=Solve('auto', 1e-5, 1e-5)), active: CenteredGrid = None, order=2) -> Tuple[GridType, CenteredGrid]: @@ -61,8 +70,8 @@ def make_incompressible(velocity: GridType, This method is similar to :func:`field.divergence_free()` but differs in how the boundary conditions are specified. Args: - velocity: Vector field sampled on a grid - obstacles: List of Obstacles to specify boundary conditions inside the domain (Default value = ()) + velocity: Vector field sampled on a grid. + obstacles: `Obstacle` or `phi.geom.Geometry` or tuple/list thereof to specify boundary conditions inside the domain. solve: `Solve` object specifying method and tolerances for the implicit pressure solve. active: (Optional) Mask for which cells the pressure should be solved. If given, the velocity may take `NaN` values where it does not contribute to the pressure. @@ -76,11 +85,8 @@ def make_incompressible(velocity: GridType, velocity: divergence-free velocity of type `type(velocity)` pressure: solved pressure field, `CenteredGrid` """ - assert isinstance(obstacles, (tuple, list)), f"obstacles must be a tuple or list but got {type(obstacles)}" - assert order == 2 or obstacles == (), f"obstacles are not supported with higher order schemes" - obstacles = [Obstacle(o) if isinstance(o, Geometry) else o for o in obstacles] - for obstacle in obstacles: - assert obstacle.geometry.vector.item_names == velocity.vector.item_names, f"Obstacles must live in the same physical space as the velocity field {velocity.vector.item_names} but got {type(obstacle.geometry).__name__} obstacle with order {obstacle.geometry.vector.item_names}" + obstacles = _get_obstacles_for(obstacles, velocity) + assert order == 2 or len(obstacles) == 0, f"obstacles are not supported with higher order schemes" input_velocity = velocity # --- Create masks --- accessible_extrapolation = _accessible_extrapolation(input_velocity.extrapolation) @@ -148,7 +154,7 @@ def _balance_divergence(div, active): return div - active * (field.mean(div) / field.mean(active)) -def apply_boundary_conditions(velocity: Grid or PointCloud, obstacles: tuple or list): +def apply_boundary_conditions(velocity: Grid or PointCloud, obstacles: Obstacle or Geometry or tuple or list): """ Enforces velocities boundary conditions on a velocity grid. Cells inside obstacles will get their velocity from the obstacle movement. @@ -156,11 +162,12 @@ def apply_boundary_conditions(velocity: Grid or PointCloud, obstacles: tuple or Args: velocity: Velocity `Grid`. - obstacles: Obstacles as `tuple` or `list` + obstacles: `Obstacle` or `phi.geom.Geometry` or tuple/list thereof to specify boundary conditions inside the domain. Returns: Velocity of same type as `velocity` """ + obstacles = _get_obstacles_for(obstacles, velocity) # velocity = field.bake_extrapolation(velocity) # TODO we should bake only for divergence but keep correct extrapolation for velocity. However, obstacles should override extrapolation. for obstacle in obstacles: if isinstance(obstacle, Geometry): From 128d0809675b0be370c37d260ad19dae64a7d22e Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 1 Feb 2023 21:24:27 +0100 Subject: [PATCH 086/170] [field] Add dims argument to spatial_gradient --- phi/field/_field_math.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 2257f92b9..9278412e1 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -1,5 +1,5 @@ from numbers import Number -from typing import Callable, List, Tuple +from typing import Callable, List, Tuple, Optional from phi import geom from phi import math @@ -103,6 +103,7 @@ def laplace(field: GridType, def spatial_gradient(field: CenteredGrid, gradient_extrapolation: Extrapolation = None, type: type = CenteredGrid, + dims: math.DimFilter = spatial, stack_dim: Shape = channel('vector'), order=2, implicit: Solve = None): @@ -119,6 +120,7 @@ def spatial_gradient(field: CenteredGrid, field: centered grid of any number of dimensions (scalar field, vector field, tensor field) gradient_extrapolation: Extrapolation of the output type: either `CenteredGrid` or `StaggeredGrid` + dims: Along which dimensions to compute the spatial gradient. Only supported when `type==CenteredGrid`. stack_dim: Dimension to be added. This dimension lists the spatial_gradient w.r.t. the spatial dimensions. The `field` must not have a dimension of the same name. order: Spatial order of accuracy. @@ -164,7 +166,8 @@ def spatial_gradient(field: CenteredGrid, field.with_extrapolation(map(_ex_map_f(extrap_map), field.extrapolation)) # ToDo does this line do anything? if implicit: gradient_extrapolation = map(_ex_map_f(extrap_map_rhs), gradient_extrapolation) - spatial_dims = field.shape.spatial.names + spatial_dims = field.shape.only(dims).names + stack_dim = stack_dim._with_item_names((spatial_dims,)) if type == CenteredGrid: # ToDo if extrapolation == math.extrapolation.NONE, extend size by 1 # pad = 1 if extrapolation == math.extrapolation.NONE else 0 @@ -175,16 +178,18 @@ def spatial_gradient(field: CenteredGrid, std_widths = (1, 1) padded_components = [pad(field, {dim_: base_widths if dim_ == dim else std_widths for dim_ in spatial_dims}) for dim in spatial_dims] elif type == StaggeredGrid: + assert spatial_dims == field.shape.spatial.names, f"spatial_gradient with type=StaggeredGrid requires dims=spatial, i.e. dims='{','.join(field.shape.spatial.names)}'" base_widths = (base_widths[0], base_widths[1]-1) padded_components = pad_for_staggered_output(field, gradient_extrapolation, field.shape.spatial.names, base_widths) else: raise ValueError(type) - shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, spatial_dims)] - result_components = [sum([value * shift for value, shift in zip(values, shifted_component)]) / field.dx.vector[dim] for shifted_component, dim in zip(shifted_components, field.shape.spatial.names)] + shifted_components = [shift(padded_component, needed_shifts, stack_dim=None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, spatial_dims)] + result_components = [sum([value * shift_ for value, shift_ in zip(values, shifted_component)]) / field.dx.vector[dim] for shifted_component, dim in zip(shifted_components, field.shape.spatial.names)] if type == CenteredGrid: result = stack(result_components, stack_dim) else: - result = StaggeredGrid(math.stack([component.values for component in result_components], channel('vector')), bounds=field.bounds, extrapolation=gradient_extrapolation) + assert stack_dim.name == 'vector', f"spatial_gradient with type=StaggeredGrid requires stack_dim.name == 'vector' but got '{stack_dim.name}'" + result = StaggeredGrid(math.stack([component.values for component in result_components], channel(vector=spatial_dims)), bounds=field.bounds, extrapolation=gradient_extrapolation) result = result.with_extrapolation(gradient_extrapolation) if implicit: implicit.x0 = result @@ -228,7 +233,7 @@ def pad_for_staggered_output(field: CenteredGrid, output_extrapolation: Extrapol return padded_components -def shift(grid: CenteredGrid, offsets: tuple, stack_dim: Shape = channel('shift'), dims=spatial, pad=True): +def shift(grid: CenteredGrid, offsets: tuple, stack_dim: Optional[Shape] = channel('shift'), dims=spatial, pad=True): """ Wraps :func:`math.shift` for CenteredGrid. From 9f927f0ac94b94b50ed314c05fb6788baa0dfe9e Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 3 Feb 2023 13:03:40 +0100 Subject: [PATCH 087/170] [doc] Update Cookbook.ipynb --- docs/Cookbook.ipynb | 48 +++++++++++++++++----------------------- phi/field/_field_math.py | 2 -- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/docs/Cookbook.ipynb b/docs/Cookbook.ipynb index e7acf118d..a082fccd4 100644 --- a/docs/Cookbook.ipynb +++ b/docs/Cookbook.ipynb @@ -28,27 +28,8 @@ }, { "cell_type": "code", - "execution_count": 2, - "outputs": [ - { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'jaxlib'", - "output_type": "error", - "traceback": [ - "\u001B[1;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[1;31mModuleNotFoundError\u001B[0m Traceback (most recent call last)", - "\u001B[1;32m\u001B[0m in \u001B[0;36m\u001B[1;34m\u001B[0m\n\u001B[0;32m 1\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[0mphi\u001B[0m\u001B[1;33m.\u001B[0m\u001B[0mflow\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[1;33m*\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 2\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[0mphi\u001B[0m\u001B[1;33m.\u001B[0m\u001B[0mtf\u001B[0m\u001B[1;33m.\u001B[0m\u001B[0mflow\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[1;33m*\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[1;32m----> 3\u001B[1;33m \u001B[1;32mfrom\u001B[0m \u001B[0mphi\u001B[0m\u001B[1;33m.\u001B[0m\u001B[0mjax\u001B[0m\u001B[1;33m.\u001B[0m\u001B[0mstax\u001B[0m\u001B[1;33m.\u001B[0m\u001B[0mflow\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[1;33m*\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0m\u001B[0;32m 4\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[0mphi\u001B[0m\u001B[1;33m.\u001B[0m\u001B[0mtorch\u001B[0m\u001B[1;33m.\u001B[0m\u001B[0mflow\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[1;33m*\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n", - "\u001B[1;32mC:\\PhD\\phiflow2\\phi\\jax\\__init__.py\u001B[0m in \u001B[0;36m\u001B[1;34m\u001B[0m\n\u001B[0;32m 9\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[0mphi\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mmath\u001B[0m \u001B[1;32mas\u001B[0m \u001B[0m_math\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 10\u001B[0m \u001B[1;33m\u001B[0m\u001B[0m\n\u001B[1;32m---> 11\u001B[1;33m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m\u001B[0m_jax_backend\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mJaxBackend\u001B[0m \u001B[1;32mas\u001B[0m \u001B[0m_JaxBackend\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0m\u001B[0;32m 12\u001B[0m \u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 13\u001B[0m \u001B[0mJAX\u001B[0m \u001B[1;33m=\u001B[0m \u001B[0m_JaxBackend\u001B[0m\u001B[1;33m(\u001B[0m\u001B[1;33m)\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n", - "\u001B[1;32mC:\\PhD\\phiflow2\\phi\\jax\\_jax_backend.py\u001B[0m in \u001B[0;36m\u001B[1;34m\u001B[0m\n\u001B[0;32m 4\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[0mtyping\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mList\u001B[0m\u001B[1;33m,\u001B[0m \u001B[0mCallable\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 5\u001B[0m \u001B[1;33m\u001B[0m\u001B[0m\n\u001B[1;32m----> 6\u001B[1;33m \u001B[1;32mimport\u001B[0m \u001B[0mjax\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0m\u001B[0;32m 7\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mjax\u001B[0m\u001B[1;33m.\u001B[0m\u001B[0mnumpy\u001B[0m \u001B[1;32mas\u001B[0m \u001B[0mjnp\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 8\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mjax\u001B[0m\u001B[1;33m.\u001B[0m\u001B[0mscipy\u001B[0m \u001B[1;32mas\u001B[0m \u001B[0mscipy\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n", - "\u001B[1;32mC:\\ProgramData\\Anaconda3\\envs\\phiflow2\\lib\\site-packages\\jax\\__init__.py\u001B[0m in \u001B[0;36m\u001B[1;34m\u001B[0m\n\u001B[0;32m 20\u001B[0m \u001B[1;31m# flake8: noqa: F401\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 21\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m\u001B[0mconfig\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mconfig\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[1;32m---> 22\u001B[1;33m from .api import (\n\u001B[0m\u001B[0;32m 23\u001B[0m \u001B[0mad\u001B[0m\u001B[1;33m,\u001B[0m \u001B[1;31m# TODO(phawkins): update users to avoid this.\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 24\u001B[0m \u001B[0margnums_partial\u001B[0m\u001B[1;33m,\u001B[0m \u001B[1;31m# TODO(phawkins): update Haiku to not use this.\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n", - "\u001B[1;32mC:\\ProgramData\\Anaconda3\\envs\\phiflow2\\lib\\site-packages\\jax\\api.py\u001B[0m in \u001B[0;36m\u001B[1;34m\u001B[0m\n\u001B[0;32m 37\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[0mcontextlib\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mcontextmanager\u001B[0m\u001B[1;33m,\u001B[0m \u001B[0mExitStack\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 38\u001B[0m \u001B[1;33m\u001B[0m\u001B[0m\n\u001B[1;32m---> 39\u001B[1;33m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mcore\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0m\u001B[0;32m 40\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mlib\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 41\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mlinear_util\u001B[0m \u001B[1;32mas\u001B[0m \u001B[0mlu\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n", - "\u001B[1;32mC:\\ProgramData\\Anaconda3\\envs\\phiflow2\\lib\\site-packages\\jax\\core.py\u001B[0m in \u001B[0;36m\u001B[1;34m\u001B[0m\n\u001B[0;32m 29\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mnumpy\u001B[0m \u001B[1;32mas\u001B[0m \u001B[0mnp\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 30\u001B[0m \u001B[1;33m\u001B[0m\u001B[0m\n\u001B[1;32m---> 31\u001B[1;33m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mdtypes\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0m\u001B[0;32m 32\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m\u001B[0mconfig\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mFLAGS\u001B[0m\u001B[1;33m,\u001B[0m \u001B[0mconfig\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 33\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mlinear_util\u001B[0m \u001B[1;32mas\u001B[0m \u001B[0mlu\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n", - "\u001B[1;32mC:\\ProgramData\\Anaconda3\\envs\\phiflow2\\lib\\site-packages\\jax\\dtypes.py\u001B[0m in \u001B[0;36m\u001B[1;34m\u001B[0m\n\u001B[0;32m 30\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m\u001B[0m_src\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mutil\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 31\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m\u001B[0mconfig\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mflags\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[1;32m---> 32\u001B[1;33m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m\u001B[0mlib\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mxla_client\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0m\u001B[0;32m 33\u001B[0m \u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 34\u001B[0m \u001B[1;32mfrom\u001B[0m \u001B[1;33m.\u001B[0m\u001B[0m_src\u001B[0m \u001B[1;32mimport\u001B[0m \u001B[0mtraceback_util\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n", - "\u001B[1;32mC:\\ProgramData\\Anaconda3\\envs\\phiflow2\\lib\\site-packages\\jax\\lib\\__init__.py\u001B[0m in \u001B[0;36m\u001B[1;34m\u001B[0m\n\u001B[0;32m 21\u001B[0m ]\n\u001B[0;32m 22\u001B[0m \u001B[1;33m\u001B[0m\u001B[0m\n\u001B[1;32m---> 23\u001B[1;33m \u001B[1;32mimport\u001B[0m \u001B[0mjaxlib\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0m\u001B[0;32m 24\u001B[0m \u001B[1;33m\u001B[0m\u001B[0m\n\u001B[0;32m 25\u001B[0m \u001B[1;31m# Must be kept in sync with the jaxlib version in build/test-requirements.txt\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[1;33m\u001B[0m\u001B[0m\n", - "\u001B[1;31mModuleNotFoundError\u001B[0m: No module named 'jaxlib'" - ] - } - ], + "execution_count": 1, + "outputs": [], "source": [ "from phi.flow import *\n", "from phi.tf.flow import *\n", @@ -453,7 +434,7 @@ "noise_grid = StaggeredGrid(Noise(), extrapolation.PERIODIC, x=32, y=32)\n", "sin_curve = StaggeredGrid(lambda x: math.sin(x), extrapolation.PERIODIC, x=100, bounds=Box(x=2 * PI))\n", "\n", - "vis.plot(zero_grid, y_grid, noise_grid, sin_curve, size=(12, 3))\n" + "vis.plot(zero_grid, y_grid, noise_grid, sin_curve, size=(12, 3))" ], "metadata": { "collapsed": false, @@ -498,7 +479,7 @@ "\n", "vx = math.tensor(np.zeros([31, 32]), spatial('x,y'))\n", "vy = math.tensor(np.zeros([32, 31]), spatial('x,y'))\n", - "StaggeredGrid(math.stack([vx, vy], channel('vector')), 0)\n" + "StaggeredGrid(math.stack([vx, vy], channel('vector')), 0)" ] }, { @@ -575,14 +556,25 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], + "execution_count": 4, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001B[92m~x=0\u001B[0m \u001B[94m-2., 1., 1.\u001B[0m along \u001B[92m(xˢ=3)\u001B[0m\n", + "\u001B[92m~x=1\u001B[0m \u001B[94m 1., -2., 1.\u001B[0m along \u001B[92m(xˢ=3)\u001B[0m\n", + "\u001B[92m~x=2\u001B[0m \u001B[94m 1., 1., -2.\u001B[0m along \u001B[92m(xˢ=3)\u001B[0m\n" + ] + } + ], "source": [ "from functools import partial\n", "\n", "periodic_laplace = partial(math.laplace, padding=extrapolation.PERIODIC)\n", - "matrix = math.jit_compile_linear(periodic_laplace).sparse_matrix(math.zeros(spatial(x=5)), format='coo') # csr, csc, coo\n", - "math.print(matrix.values)\n" + "example_input = math.ones(spatial(x=3))\n", + "matrix, bias = math.matrix_from_function(periodic_laplace, example_input)\n", + "math.print(matrix)" ], "metadata": { "collapsed": false, @@ -729,7 +721,7 @@ "execution_count": null, "outputs": [], "source": [ - "parameter_count(net)\n" + "parameter_count(net)" ], "metadata": { "collapsed": false, diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 9278412e1..54e9c8c42 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -107,7 +107,6 @@ def spatial_gradient(field: CenteredGrid, stack_dim: Shape = channel('vector'), order=2, implicit: Solve = None): - """ Finite difference spatial_gradient. @@ -131,7 +130,6 @@ def spatial_gradient(field: CenteredGrid, Returns: spatial_gradient field of type `type`. - """ if gradient_extrapolation is None: gradient_extrapolation = field.extrapolation.spatial_gradient() From c2eb441254ca49e9e9c87bd7f7bd187852f83084 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 3 Feb 2023 13:28:11 +0100 Subject: [PATCH 088/170] [math] Allow __stack__, __concat__ as staticmethod --- phi/field/_embed.py | 3 ++- phi/field/_field.py | 6 ++++-- phi/geom/_box.py | 13 ++++++++----- phi/geom/_geom.py | 5 +++-- phi/math/_tensors.py | 12 ++++++++---- phi/math/extrapolation.py | 3 ++- phi/math/magic.py | 11 ++++++++--- tests/commit/math/test__magic_ops.py | 6 ++++-- 8 files changed, 39 insertions(+), 20 deletions(-) diff --git a/phi/field/_embed.py b/phi/field/_embed.py index a4f479972..3191df88a 100644 --- a/phi/field/_embed.py +++ b/phi/field/_embed.py @@ -19,7 +19,8 @@ def __value_attrs__(self): def __getitem__(self, item): return FieldEmbedding(self.field[item]) - def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'ConstantExtrapolation': + @staticmethod + def __stack__(values: tuple, dim: Shape, **kwargs) -> 'ConstantExtrapolation': if all(isinstance(v, FieldEmbedding) for v in values): return ConstantExtrapolation(stack([v.field for v in values], dim, **kwargs)) else: diff --git a/phi/field/_field.py b/phi/field/_field.py index ad002a184..9831330d2 100644 --- a/phi/field/_field.py +++ b/phi/field/_field.py @@ -206,11 +206,13 @@ def spatial_rank(self) -> int: def __getitem__(self: 'FieldType', item) -> 'FieldType': raise NotImplementedError(self) - def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'FieldType': + @staticmethod + def __stack__(values: tuple, dim: Shape, **kwargs) -> 'FieldType': from ._field_math import stack return stack(values, dim, kwargs.get('bounds', None)) - def __concat__(self, values: tuple, dim: str, **kwargs) -> 'FieldType': + @staticmethod + def __concat__(values: tuple, dim: str, **kwargs) -> 'FieldType': from ._field_math import concat return concat(values, dim) diff --git a/phi/geom/_box.py b/phi/geom/_box.py index bfab67a10..94ba1fae4 100644 --- a/phi/geom/_box.py +++ b/phi/geom/_box.py @@ -224,11 +224,12 @@ def __getitem__(self, item): item = _keep_vector(slicing_dict(self, item)) return Box(self._lower[item], self._upper[item]) - def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'Geometry': + @staticmethod + def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Geometry': if all(isinstance(v, Box) for v in values): return NotImplemented # stack attributes else: - return Geometry.__stack__(self, values, dim, **kwargs) + return Geometry.__stack__(values, dim, **kwargs) def __eq__(self, other): return isinstance(other, BaseBox)\ @@ -335,11 +336,12 @@ def __getitem__(self, item): item = _keep_vector(slicing_dict(self, item)) return Cuboid(self._center[item], self._half_size[item]) - def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'Geometry': + @staticmethod + def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Geometry': if all(isinstance(v, Cuboid) for v in values): return Cuboid(math.stack([v.center for v in values], dim, **kwargs), math.stack([v.half_size for v in values], dim, **kwargs)) else: - return Geometry.__stack__(self, values, dim, **kwargs) + return Geometry.__stack__(values, dim, **kwargs) def __variable_attrs__(self): return '_center', '_half_size' @@ -465,7 +467,8 @@ def __getitem__(self, item): def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or None, **kwargs) -> 'Cuboid': return math.pack_dims(self.center_representation(), dims, packed_dim, pos, **kwargs) - def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'Geometry': + @staticmethod + def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Geometry': from ._stack import GeometryStack return GeometryStack(math.layout(values, dim)) diff --git a/phi/geom/_geom.py b/phi/geom/_geom.py index 03789f13f..2175284d1 100644 --- a/phi/geom/_geom.py +++ b/phi/geom/_geom.py @@ -312,8 +312,9 @@ def shallow_equals(self, other): return False return True - def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'Geometry': - if all(type(v) == type(self) for v in values): + @staticmethod + def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Geometry': + if all(type(v) == type(values[0]) for v in values): return NotImplemented # let attributes be stacked else: from ._stack import GeometryStack diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index b14ca198b..efac69f5d 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -9,6 +9,7 @@ import numpy import numpy as np +from .magic import Shapable from ._magic_ops import PhiTreeNodeType, variable_attributes, copy_with, stack, pack_dims, expand from ._shape import (Shape, CHANNEL_DIM, BATCH_DIM, SPATIAL_DIM, EMPTY_SHAPE, @@ -467,7 +468,8 @@ def unstack(self, dimension: str): """ raise NotImplementedError() - def __stack__(self, values: tuple, dim: Shape, **_kwargs) -> 'Tensor': + @staticmethod + def __stack__(values: tuple, dim: Shape, **_kwargs) -> 'Tensor': from ._ops import stack_tensors return stack_tensors(values, dim) @@ -475,7 +477,8 @@ def __expand__(self, dims: Shape, **kwargs) -> 'Tensor': from ._ops import expand_tensor return expand_tensor(self, dims) - def __concat__(self, values: tuple, dim: str, **kwargs) -> 'Tensor': + @staticmethod + def __concat__(values: tuple, dim: str, **kwargs) -> 'Tensor': from ._ops import concat_tensor return concat_tensor(values, dim) @@ -948,12 +951,13 @@ def __bool__(self): assert self.rank == 0, f"Cannot convert tensor with non-empty shape {self.shape} to bool. Use tensor.any or tensor.all instead." return bool(self._obj) - def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'Shapable': + def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'Layout': obj = [v.native(self._shape) for v in values] new_shape = concat_shapes(dim, self._shape) return Layout(obj, new_shape) - def __concat__(self, values: tuple, dim: str, **kwargs) -> 'Shapable': + @staticmethod + def __concat__(values: tuple, dim: str, **kwargs) -> 'Shapable': return NotImplemented def __flatten__(self, flat_dim: Shape, flatten_batch: bool): diff --git a/phi/math/extrapolation.py b/phi/math/extrapolation.py index 51737b17c..f3ff7c23e 100644 --- a/phi/math/extrapolation.py +++ b/phi/math/extrapolation.py @@ -212,7 +212,8 @@ def __value_attrs__(self): def __getitem__(self, item): return ConstantExtrapolation(self.value[item]) - def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'ConstantExtrapolation': + @staticmethod + def __stack__(values: tuple, dim: Shape, **kwargs) -> 'ConstantExtrapolation': if all(isinstance(v, ConstantExtrapolation) for v in values): return ConstantExtrapolation(stack([v.value for v in values], dim, **kwargs)) else: diff --git a/phi/math/magic.py b/phi/math/magic.py index c475c6116..f186efbd8 100644 --- a/phi/math/magic.py +++ b/phi/math/magic.py @@ -163,11 +163,13 @@ class Shapable(metaclass=_ShapableType): Additionally, the `phi.math.BoundDim` syntax for dimension renaming and retyping is enabled, e.g. `obj.dim.as_channel('vector')`. """ - - def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'Shapable': + @staticmethod + def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Shapable': """ Stack all `values` into a single instance along the new dimension `dim`. + This method can be implemented as a bound method or as a `staticmethod` (without the `self` argument). + Args: values: `tuple` of `Shapable` objects to be stacked. `self` is included in that list at least once. dim: Single-dimension `Shape`. This dimension must not be present with any of the `values`. @@ -183,10 +185,13 @@ def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'Shapable': """ raise NotImplementedError - def __concat__(self, values: tuple, dim: str, **kwargs) -> 'Shapable': + @staticmethod + def __concat__(values: tuple, dim: str, **kwargs) -> 'Shapable': """ Concatenate `values` along `dim`. + This method can be implemented as a bound method or as a `staticmethod` (without the `self` argument). + Args: values: Values to concatenate. `self` is included in that list at least once. dim: Dimension nams as `str`, must be present in all `values`. diff --git a/tests/commit/math/test__magic_ops.py b/tests/commit/math/test__magic_ops.py index 9af12a2d2..d480ad316 100644 --- a/tests/commit/math/test__magic_ops.py +++ b/tests/commit/math/test__magic_ops.py @@ -14,7 +14,8 @@ def __init__(self, shape: Shape): def __getitem__(self, item: dict): return Stackable(self.shape.after_gather(slicing_dict(self, item))) - def __stack__(self, values: tuple, dim: Shape, **kwargs) -> 'Stackable': + @staticmethod + def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Stackable': return Stackable(merge_shapes(dim, *[v.shape for v in values])) @@ -26,7 +27,8 @@ def __init__(self, shape: Shape): def __getitem__(self, item: dict): return ConcatExpandable(self.shape.after_gather(slicing_dict(self, item))) - def __concat__(self, values: tuple, dim: str, **kwargs) -> 'ConcatExpandable': + @staticmethod + def __concat__(values: tuple, dim: str, **kwargs) -> 'ConcatExpandable': try: new_size = sum([v.shape.get_item_names(dim) for v in values], ()) except: From 0bf07fbaa09daecaf8dc3b45be44d1d2da73942f Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 3 Feb 2023 13:33:07 +0100 Subject: [PATCH 089/170] [math] Add enable_debug_checks() --- phi/math/__init__.py | 3 ++- phi/math/_shape.py | 33 +++++++++++++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/phi/math/__init__.py b/phi/math/__init__.py index ae54f3683..00d5029ba 100644 --- a/phi/math/__init__.py +++ b/phi/math/__init__.py @@ -20,7 +20,8 @@ shape, Shape, EMPTY_SHAPE, DimFilter, spatial, channel, batch, instance, dual, non_batch, non_spatial, non_instance, non_channel, non_dual, - merge_shapes, concat_shapes, IncompatibleShapes + merge_shapes, concat_shapes, IncompatibleShapes, + enable_debug_checks, ) from ._magic_ops import unstack, stack, concat, expand, rename_dims, pack_dims, unpack_dim, unpack_dim as unpack_dims, flatten, copy_with from ._tensors import wrap, tensor, layout, Tensor, Dict, to_dict, from_dict, is_scalar diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 4c225438f..50b5c8f24 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -5,13 +5,26 @@ from phi import math + BATCH_DIM = 'batch' SPATIAL_DIM = 'spatial' CHANNEL_DIM = 'channel' INSTANCE_DIM = 'înstance' DUAL_DIM = 'dual' + TYPE_ABBR = {SPATIAL_DIM: "ˢ", CHANNEL_DIM: "ᶜ", INSTANCE_DIM: "ⁱ", BATCH_DIM: "ᵇ", DUAL_DIM: "ᵈ", None: "⁻"} # ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻ +DEBUG_CHECKS = False + + +def enable_debug_checks(): + """ + Once called, additional type checks are enabled. + This may result in a noticeable drop in performance. + """ + global DEBUG_CHECKS + DEBUG_CHECKS = True + class Shape: """ @@ -46,16 +59,16 @@ def __init__(self, sizes: tuple, names: tuple, types: tuple, item_names: tuple): `Shape.name`. """ self.types: Tuple[str] = types # undocumented, may be private - self.item_names: Tuple[str or 'Shape'] = (None,) * len(sizes) if item_names is None else item_names - # Debug asserts - # assert len(sizes) == len(names) == len(types) == len(item_names), f"sizes={sizes}, names={names}, types={types}, item_names={item_names}" - # assert all(isinstance(n, str) for n in names), f"All names must be of type string but got {names}" - # assert isinstance(self.item_names, tuple) - # assert all([items is None or isinstance(items, tuple) for items in self.item_names]) - # assert all([items is None or all([isinstance(n, str) for n in items]) for items in self.item_names]) - # for size in sizes: - # if size is not None and not isinstance(size, int): - # assert size.rank > 0 + self.item_names: Tuple[str or 'Shape'] = (None,) * len(sizes) if item_names is None else item_names # undocumented + if DEBUG_CHECKS: + assert len(sizes) == len(names) == len(types) == len(item_names), f"sizes={sizes}, names={names}, types={types}, item_names={item_names}" + assert all(isinstance(n, str) for n in names), f"All names must be of type string but got {names}" + assert isinstance(self.item_names, tuple) + assert all([items is None or isinstance(items, tuple) for items in self.item_names]) + assert all([items is None or all([isinstance(n, str) for n in items]) for items in self.item_names]) + for size in sizes: + if size is not None and not isinstance(size, int): + assert size.rank > 0 def _to_dict(self, include_sizes=True): result = dict(names=self.names, types=self.types, item_names=self.item_names) From f8978f28012b7da5423438a03d65991127003cd8 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 3 Feb 2023 22:26:51 +0100 Subject: [PATCH 090/170] [math] Fix magic ops for primitives --- phi/math/_magic_ops.py | 31 ++++++++++++++++++++++++------- phi/math/_shape.py | 26 +++++++++++--------------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/phi/math/_magic_ops.py b/phi/math/_magic_ops.py index fccbf2764..633a198c5 100644 --- a/phi/math/_magic_ops.py +++ b/phi/math/_magic_ops.py @@ -306,6 +306,8 @@ def rename_dims(value, """ Change the name and optionally the type of some dimensions of `value`. + Dimensions that are not present on value will be ignored. The corresponding new dimensions given by `names` will not be added. + Args: value: `Shape` or `Tensor` or `Shapable`. dims: Existing dimensions of `value`. @@ -323,22 +325,31 @@ def rename_dims(value, """ if isinstance(value, Shape): return value._replace_names_and_types(dims, names) + elif isinstance(value, (Number, bool)): + return value assert isinstance(value, Shapable) and isinstance(value, Shaped), f"value must be a Shape or Shapable but got {type(value).__name__}" - dims = shape(value).only(dims) - names = dims._replace_names_and_types(dims, names) + dims = parse_dim_order(dims) + if isinstance(names, str): + names = parse_dim_order(names) + assert len(dims) == len(names), f"names and dims must be of equal length but got #dims={len(dims)} and #names={len(names)}" + existing_dims = shape(value).only(dims, reorder=True) + if not existing_dims: + return value + existing_names = [n for i, n in enumerate(names) if dims[i] in existing_dims] + existing_names = existing_dims._replace_names_and_types(existing_dims, existing_names) # --- First try __replace_dims__ --- if hasattr(value, '__replace_dims__'): - result = value.__replace_dims__(dims.names, names, **kwargs) + result = value.__replace_dims__(existing_dims.names, existing_names, **kwargs) if result is not NotImplemented: return result # --- Next try Tree Node --- if isinstance(value, PhiTreeNode): - new_attributes = {a: rename_dims(getattr(value, a), dims, names, **kwargs) for a in all_attributes(value)} + new_attributes = {a: rename_dims(getattr(value, a), existing_dims, existing_names, **kwargs) for a in all_attributes(value)} return copy_with(value, **new_attributes) # --- Fallback: unstack and stack --- - if shape(value).only(dims).volume > 8: + if shape(value).only(existing_dims).volume > 8: warnings.warn(f"rename_dims() default implementation is slow on large dimensions ({shape(value).only(dims)}). Please implement __replace_dims__() for {type(value).__name__} as defined in phi.math.magic", RuntimeWarning, stacklevel=2) - for old_name, new_dim in zip(dims.names, names): + for old_name, new_dim in zip(existing_dims.names, existing_names): value = stack(unstack(value, old_name), new_dim, **kwargs) return value @@ -353,7 +364,7 @@ def pack_dims(value, dims: DimFilter, packed_dim: Shape, pos: int or None = None The type of the new dimension will be equal to the types of `dims`. If `dims` have varying types, the new dimension will be a batch dimension. - If none of `dims` exist on `value`, `packed_dim` will be added only if it is given with a definite size. + If none of `dims` exist on `value`, `packed_dim` will be added only if it is given with a definite size and `value` is not a primitive type. See Also: `unpack_dim()` @@ -374,6 +385,8 @@ def pack_dims(value, dims: DimFilter, packed_dim: Shape, pos: int or None = None >>> pack_dims(math.zeros(spatial(x=4, y=3)), spatial, instance('points')) (pointsⁱ=12) const 0.0 """ + if isinstance(value, (Number, bool)): + return value assert isinstance(value, Shapable) and isinstance(value, Sliceable) and isinstance(value, Shaped), f"value must be Shapable but got {type(value)}" dims = shape(value).only(dims, reorder=True) if packed_dim in shape(value): @@ -405,6 +418,8 @@ def unpack_dim(value, dim: str or Shape, unpacked_dims: Shape, **kwargs): This function replaces the traditional `reshape` for these cases. The compressed dimension `dim` is assumed to contain elements laid out according to the order of `unpacked_dims`. + If `dim` does not exist on `value`, this function will return `value` as-is. This includes primitive types. + See Also: `pack_dims()` @@ -423,6 +438,8 @@ def unpack_dim(value, dim: str or Shape, unpacked_dims: Shape, **kwargs): >>> unpack_dim(math.zeros(instance(points=12)), 'points', spatial(x=4, y=3)) (xˢ=4, yˢ=3) const 0.0 """ + if isinstance(value, (Number, bool)): + return value assert isinstance(value, Shapable) and isinstance(value, Sliceable) and isinstance(value, Shaped), f"value must be Shapable but got {type(value)}" if isinstance(dim, Shape): dim = dim.name diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 50b5c8f24..cf71f6cd5 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -903,23 +903,19 @@ def _replace_names_and_types(self, """ dims = parse_dim_order(dims) sizes = [math.rename_dims(s, dims, new) if isinstance(s, math.Tensor) else s for s in self.sizes] - if isinstance(new, Shape): # replace names and types - names = list(self.names) - types = list(self.types) - item_names = list(self.item_names) - for old_name, new_dim in zip(dims, new): - if old_name in self: + new = parse_dim_order(new) if isinstance(new, str) else new + names = list(self.names) + types = list(self.types) + item_names = list(self.item_names) + for old_name, new_dim in zip(dims, new): + if old_name in self: + if isinstance(new_dim, Shape): names[self.index(old_name)] = new_dim.name types[self.index(old_name)] = new_dim.type item_names[self.index(old_name)] = new_dim.item_names[0] - return Shape(tuple(sizes), tuple(names), tuple(types), tuple(item_names)) - else: # replace only names - new = parse_dim_order(new) - names = list(self.names) - for old_name, new_name in zip(dims, new): - if old_name in self: - names[self.index(old_name)] = new_name - return Shape(tuple(sizes), tuple(names), self.types, self.item_names) + else: + names[self.index(old_name)] = new_dim + return Shape(tuple(sizes), tuple(names), tuple(types), tuple(item_names)) def replace(self, dims: 'Shape' or str or tuple or list, new: 'Shape') -> 'Shape': """ @@ -1302,7 +1298,7 @@ def shape(obj) -> Shape: return EMPTY_SHAPE elif isinstance(obj, PhiTreeNode): from phi.math._magic_ops import all_attributes - return merge_shapes(*[getattr(obj, a) for a in all_attributes(obj)]) + return merge_shapes(*[getattr(obj, a) for a in all_attributes(obj, assert_any=True)]) else: from .backend import choose_backend, NoBackendFound try: From f8b4eb3019080d07f93713c7cd4086cdec048c8d Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 3 Feb 2023 22:28:32 +0100 Subject: [PATCH 091/170] [math] dataclasses as PhiTreeNodes --- phi/math/__init__.py | 2 +- phi/math/_magic_ops.py | 47 ++++++++++++++++++---------- phi/math/magic.py | 11 ++++++- tests/commit/math/test__magic_ops.py | 22 +++++++++++-- tests/commit/math/test__tensors.py | 2 +- 5 files changed, 62 insertions(+), 22 deletions(-) diff --git a/phi/math/__init__.py b/phi/math/__init__.py index 00d5029ba..afa9439d5 100644 --- a/phi/math/__init__.py +++ b/phi/math/__init__.py @@ -23,7 +23,7 @@ merge_shapes, concat_shapes, IncompatibleShapes, enable_debug_checks, ) -from ._magic_ops import unstack, stack, concat, expand, rename_dims, pack_dims, unpack_dim, unpack_dim as unpack_dims, flatten, copy_with +from ._magic_ops import unstack, stack, concat, expand, rename_dims, pack_dims, unpack_dim, unpack_dim as unpack_dims, flatten, copy_with, replace from ._tensors import wrap, tensor, layout, Tensor, Dict, to_dict, from_dict, is_scalar from ._sparse import dense, get_sparsity from .extrapolation import Extrapolation diff --git a/phi/math/_magic_ops.py b/phi/math/_magic_ops.py index 633a198c5..41066f6d9 100644 --- a/phi/math/_magic_ops.py +++ b/phi/math/_magic_ops.py @@ -3,10 +3,12 @@ from numbers import Number from typing import TypeVar, Tuple, Set +import dataclasses + from . import channel from .backend import choose_backend, NoBackendFound from .backend._dtype import DType -from ._shape import Shape, DimFilter, batch, instance, shape, non_batch, merge_shapes, concat_shapes, spatial +from ._shape import Shape, DimFilter, batch, instance, shape, non_batch, merge_shapes, concat_shapes, spatial, parse_dim_order from .magic import Sliceable, Shaped, Shapable, PhiTreeNode @@ -511,26 +513,45 @@ def variable_attributes(obj) -> Tuple[str]: return obj.__variable_attrs__() elif hasattr(obj, '__value_attrs__'): return obj.__value_attrs__() + elif dataclasses.is_dataclass(obj): + return tuple([f.name for f in dataclasses.fields(obj)]) else: raise ValueError(f"Not a PhiTreeNode: {type(obj).__name__}") def value_attributes(obj) -> Tuple[str, ...]: - assert hasattr(obj, '__value_attrs__'), f"{type(obj).__name__} must implement '__value_attrs__()' to be used with value functions." - return obj.__value_attrs__() + if hasattr(obj, '__value_attrs__'): + return obj.__value_attrs__() + if dataclasses.is_dataclass(obj): + return tuple([f.name for f in dataclasses.fields(obj)]) + raise ValueError(f"{type(obj).__name__} must implement '__value_attrs__()' or be a dataclass to be used with value functions.") def variable_values(obj) -> Tuple[str, ...]: - assert hasattr(obj, '__value_attrs__'), f"{type(obj).__name__} must implement '__value_attrs__()' to be used with value functions." if hasattr(obj, '__variable_attrs__'): values = obj.__value_attrs__() variables = obj.__variable_attrs__() return tuple([a for a in values if a in variables]) else: - return obj.__value_attrs__() + return obj.__value_attrs__() # this takes care of dataclasses as well + + +def all_attributes(obj, assert_any=False) -> Set[str]: + if not isinstance(obj, PhiTreeNode): + raise ValueError(f"Not a PhiTreeNode: {type(obj).__name__}") + result = set() + if hasattr(obj, '__variable_attrs__'): + result.update(obj.__variable_attrs__()) + if hasattr(obj, '__value_attrs__'): + result.update(obj.__value_attrs__()) + if dataclasses.is_dataclass(obj) and not hasattr(obj, '__variable_attrs__') and not hasattr(obj, '__value_attrs__'): + result.update([f.name for f in dataclasses.fields(obj)]) + if assert_any: + assert result, f"{type(obj).__name__} is not a valid tree node because it has no tensor-like attributes." + return result -def copy_with(obj: PhiTreeNodeType, **updates) -> PhiTreeNodeType: +def replace(obj: PhiTreeNodeType, **updates) -> PhiTreeNodeType: """ Creates a copy of the given `PhiTreeNode` with updated values as specified in `updates`. @@ -548,6 +569,8 @@ def copy_with(obj: PhiTreeNodeType, **updates) -> PhiTreeNodeType: return obj.__with_attrs__(**updates) elif isinstance(obj, (Number, bool)): return obj + elif dataclasses.is_dataclass(obj): + return dataclasses.replace(obj, **updates) else: cpy = copy.copy(obj) for attr, value in updates.items(): @@ -555,17 +578,7 @@ def copy_with(obj: PhiTreeNodeType, **updates) -> PhiTreeNodeType: return cpy -def all_attributes(obj, assert_any=False) -> Set[str]: - if not isinstance(obj, PhiTreeNode): - raise ValueError(f"Not a PhiTreeNode: {type(obj).__name__}") - result = set() - if hasattr(obj, '__variable_attrs__'): - result.update(obj.__variable_attrs__()) - if hasattr(obj, '__value_attrs__'): - result.update(obj.__value_attrs__()) - if assert_any: - assert result, f"{type(obj).__name__} is not a valid tree node because it has no tensor-like attributes." - return result +copy_with = replace # Other Ops diff --git a/phi/math/magic.py b/phi/math/magic.py index f186efbd8..615509b6f 100644 --- a/phi/math/magic.py +++ b/phi/math/magic.py @@ -18,6 +18,8 @@ import warnings from typing import Tuple, Callable +import dataclasses + from ._shape import Shape, shape, channel, non_batch, batch, spatial, instance, concat_shapes, dual from .backend._dtype import DType @@ -313,6 +315,8 @@ def __instancecheck__(self, instance): return True elif isinstance(instance, dict): return all(isinstance(name, str) for name in instance.keys()) and all(isinstance(val, PhiTreeNode) for val in instance.values()) + elif dataclasses.is_dataclass(instance): + return True else: return hasattr(instance, '__variable_attrs__') or hasattr(instance, '__value_attrs__') @@ -324,6 +328,8 @@ def __subclasscheck__(self, subclass): return True elif issubclass(subclass, Dict): return True + elif dataclasses.is_dataclass(subclass): + return True else: return hasattr(subclass, '__variable_attrs__') or hasattr(subclass, '__value_attrs__') @@ -332,12 +338,15 @@ class PhiTreeNode(metaclass=_PhiTreeNodeType): """ Φ-tree nodes can be iterated over and disassembled or flattened into elementary objects, such as tensors. `phi.math.Tensor` instances as well as PyTree nodes (`tuple`, `list`, `dict` with `str` keys) are Φ-tree nodes. + All data classes are also considered PhiTreeNodes as of version 2.3. - For custom classes to be considered Φ-tree nodes, they have to implement one of the following magic methods: + For custom classes to be considered Φ-tree nodes, they have to be a dataclass or implement one of the following magic methods: * `__variable_attrs__()` * `__value_attrs__()` + Dataclasses may also implement these functions to specify which attributes should be considered value / variable properties. + Additionally, Φ-tree nodes must override `__eq__()` to allow comparison of data-stripped (key) instances. To check whether an object is a Φ-tree node, use `isinstance(obj, PhiTreeNode)`. diff --git a/tests/commit/math/test__magic_ops.py b/tests/commit/math/test__magic_ops.py index d480ad316..058191647 100644 --- a/tests/commit/math/test__magic_ops.py +++ b/tests/commit/math/test__magic_ops.py @@ -1,6 +1,8 @@ -from typing import Tuple +from typing import Tuple, Union from unittest import TestCase +import dataclasses + from phi.math import batch, unstack, Shape, merge_shapes, stack, concat, expand, spatial, shape, instance, rename_dims, \ pack_dims, random_normal, flatten, unpack_dim, EMPTY_SHAPE, Tensor, Dict, channel, linspace, zeros, meshgrid, assert_close from phi.math.magic import BoundDim, Shaped, Sliceable, Shapable, PhiTreeNode, slicing_dict @@ -51,7 +53,17 @@ def __getitem__(self, item: dict): return ValuedPhiTreeNode(self.tensor[item].shape) -TEST_CLASSES = [Stackable, ConcatExpandable, random_normal, ValuedPhiTreeNode] +@dataclasses.dataclass +class MyPoint: + x: Tensor or float + y: Union[Tensor, float] + is_normalized: bool = dataclasses.field(compare=False) + + def __getitem__(self, item): + return MyPoint(self.x[item], self.y[item], is_normalized=self.is_normalized) + + +TEST_CLASSES = [Stackable, ConcatExpandable, random_normal, ValuedPhiTreeNode, lambda shape: MyPoint(zeros(shape), zeros(shape), is_normalized=False)] class TestMagicOps(TestCase): @@ -89,6 +101,12 @@ def test_subclasscheck(self): self.assertTrue(issubclass(ConcatExpandable, Shaped)) self.assertTrue(issubclass(ConcatExpandable, Sliceable)) self.assertTrue(issubclass(ConcatExpandable, Shapable)) + self.assertTrue(issubclass(ValuedPhiTreeNode, Shaped)) + self.assertTrue(issubclass(ValuedPhiTreeNode, Sliceable)) + self.assertTrue(issubclass(ValuedPhiTreeNode, Sliceable)) + self.assertTrue(issubclass(MyPoint, Shaped)) + self.assertTrue(issubclass(MyPoint, Sliceable)) + self.assertTrue(issubclass(MyPoint, Sliceable)) def test_instancecheck(self): for test_class in TEST_CLASSES: diff --git a/tests/commit/math/test__tensors.py b/tests/commit/math/test__tensors.py index 17d7ace45..581d8c23c 100644 --- a/tests/commit/math/test__tensors.py +++ b/tests/commit/math/test__tensors.py @@ -269,7 +269,7 @@ def __variable_attrs__(self): pass try: math.cos(t) - except AssertionError: + except ValueError: pass def test_Dict(self): From bf794510adaddfbbeefb18636eb981eb17e7231a Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 3 Feb 2023 22:29:25 +0100 Subject: [PATCH 092/170] [math] Remove unpack_dims alias --- phi/math/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phi/math/__init__.py b/phi/math/__init__.py index afa9439d5..1bd1f5b26 100644 --- a/phi/math/__init__.py +++ b/phi/math/__init__.py @@ -23,7 +23,7 @@ merge_shapes, concat_shapes, IncompatibleShapes, enable_debug_checks, ) -from ._magic_ops import unstack, stack, concat, expand, rename_dims, pack_dims, unpack_dim, unpack_dim as unpack_dims, flatten, copy_with, replace +from ._magic_ops import unstack, stack, concat, expand, rename_dims, pack_dims, unpack_dim, flatten, copy_with, replace from ._tensors import wrap, tensor, layout, Tensor, Dict, to_dict, from_dict, is_scalar from ._sparse import dense, get_sparsity from .extrapolation import Extrapolation From 0747705ce8edb59b2a92c4206e769e03798d14be Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 3 Feb 2023 22:45:51 +0100 Subject: [PATCH 093/170] [math] Fix nd convolution --- phi/math/_ops.py | 3 +-- tests/commit/math/test__ops.py | 8 ++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 33037f1e5..ae847f835 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -1925,8 +1925,7 @@ def convolve(value: Tensor, out_channels = kernel.shape.channel.without(in_channels) batch = value.shape.batch & kernel.shape.batch if extrapolation is not None and extrapolation != e_.ZERO: - value = pad(value, {dim: (kernel.shape.get_size(dim) // 2, (kernel.shape.get_size(dim) - 1) // 2) - for dim in conv_shape.name}, extrapolation) + value = pad(value, {dim: (kernel.shape.get_size(dim) // 2, (kernel.shape.get_size(dim) - 1) // 2) for dim in conv_shape.names}, extrapolation) native_kernel = reshaped_native(kernel, (batch, out_channels, in_channels, *conv_shape.names), force_expand=in_channels) native_value = reshaped_native(value, (batch, in_channels, *conv_shape.names), force_expand=batch) backend = choose_backend(native_value, native_kernel) diff --git a/tests/commit/math/test__ops.py b/tests/commit/math/test__ops.py index 79e61eb37..13c2523c7 100644 --- a/tests/commit/math/test__ops.py +++ b/tests/commit/math/test__ops.py @@ -605,8 +605,12 @@ def test_convolution_1d_batched(self): [[1, 2, 3], [11, 12, 13]]], channel('out'), batch('batch'), spatial('x')) assert_close(math.convolve(x, kernel, math.extrapolation.ZERO), expected, msg=backend.name) - # def test_convolution_2d(self): # TODO - # pass + def test_convolution_2d(self): + for backend in BACKENDS: + with backend: + values = math.random_normal(spatial(x=64, y=64)) + kernel = math.random_normal(spatial(x=5, y=5)) + values_conv = math.convolve(values, kernel, extrapolation.PERIODIC) def test_reshaped_native(self): a = math.random_uniform(channel(vector=2) & spatial(x=4, y=3)) From 4bb3867e329b6fabb4fe4740e59b062fadc964da Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 3 Feb 2023 23:01:44 +0100 Subject: [PATCH 094/170] [physics] Pressure solve boundary handling This reverts the boundary handling back to the old method but with inflow fixed. This also fixes the outermost layer of CenteredGrid pressure solves. --- demos/pipe.py | 2 +- phi/physics/fluid.py | 3 ++- tests/commit/physics/test_fluid.py | 5 +---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/demos/pipe.py b/demos/pipe.py index 17bcec56f..88bb009ad 100644 --- a/demos/pipe.py +++ b/demos/pipe.py @@ -9,5 +9,5 @@ for _ in view('velocity, pressure', namespace=globals()).range(): velocity = advect.semi_lagrangian(velocity, velocity, DT) - velocity, pressure = fluid.make_incompressible(velocity, solve=Solve('CG-adaptive', 1e-5, 0, x0=pressure)) velocity = diffuse.explicit(velocity, 0.1, DT) + velocity, pressure = fluid.make_incompressible(velocity, solve=Solve('CG-adaptive', 1e-5, 0, x0=pressure)) diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index 735388472..ad92ac6de 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -142,7 +142,8 @@ def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid, """ if order == 2 and not implicit: grad = spatial_gradient(pressure, hard_bcs.extrapolation, type=type(hard_bcs)) - valid_grad = grad * field.bake_extrapolation(hard_bcs).with_extrapolation(grad.extrapolation) + valid_grad = grad * hard_bcs + valid_grad = valid_grad.with_extrapolation(valid_grad.extrapolation - valid_grad.extrapolation) div = divergence(valid_grad) laplace = where(active, div, pressure) else: diff --git a/tests/commit/physics/test_fluid.py b/tests/commit/physics/test_fluid.py index 25517f435..b02679f54 100644 --- a/tests/commit/physics/test_fluid.py +++ b/tests/commit/physics/test_fluid.py @@ -24,10 +24,7 @@ def _test_make_incompressible(self, grid_type: type, extrapolation: math.Extrapo for _ in range(2): velocity += smoke * (0, 0.1) @ velocity velocity, _ = fluid.make_incompressible(velocity) - if grid_type == StaggeredGrid: - math.assert_close(0, divergence(velocity).values, abs_tolerance=2e-5) - else: - math.assert_close(0, field.pad(divergence(velocity), -1).values, abs_tolerance=2e-5) + math.assert_close(0, divergence(velocity).values, abs_tolerance=2e-5) if result is None: result = velocity else: From 09f5101c02ba90b0dc3db4037e7e9cc190773c99 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 4 Feb 2023 13:17:33 +0100 Subject: [PATCH 095/170] [vis] Improved annotations Annotations now support multiple named dimensions --- phi/vis/_matplotlib/_matplotlib_plots.py | 40 +++++++++++++++++------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index c7fe28662..c0bae2bcb 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -328,23 +328,39 @@ def _plot_points(axis, data: PointCloud, dims, vector, **plt_args): if spatial(data.points): # Connect by line x, y = math.reshaped_numpy(data.points.vector[dims], [vector, spatial(data), instance(data)]) axis.plot(x, y, color=color[0]) - if non_channel(data).rank == 1 and non_channel(data).item_names[0]: - _annotate_points(axis, data.points, non_channel(data)) + if any(non_channel(data).item_names): + _annotate_points(axis, data.points) return color[0] -def _annotate_points(axis, points: math.Tensor, labelled_dim: math.Shape): - if labelled_dim.name in points.shape.get_item_names('vector'): +def _annotate_points(axis, points: math.Tensor): + labelled_dims = non_channel(points) + labelled_dims = math.concat_shapes(*[d for d in labelled_dims if d.item_names[0]]) + if not labelled_dims: + return + if all(dim.name in points.shape.get_item_names('vector') for dim in labelled_dims): return # The point labels match one of the figure axes, so they are redundant if points.shape['vector'].size == 2: - x, y = math.reshaped_native(points, ['vector', points.shape.without('vector')], to_numpy=True, force_expand=True) - if labelled_dim.item_names[0]: - x_view = axis.get_xlim()[1] - axis.get_xlim()[0] - y_view = axis.get_ylim()[1] - axis.get_ylim()[0] - for x_, y_, label in zip(x, y, labelled_dim.item_names[0]): - offset_x = x_ * (1 + .0003 * x_view) if axis.get_xscale() == 'log' else x_ + .01 * x_view - offset_y = y_ * (1 + .0003 * y_view) if axis.get_yscale() == 'log' else y_ + .01 * y_view - axis.text(offset_x, offset_y, label) + xs, ys = math.reshaped_numpy(points, ['vector', points.shape.without('vector')], force_expand=True) + if labelled_dims.rank == 1: + labels = labelled_dims.item_names[0] + else: + labels = labelled_dims.meshgrid(names=True) + labels = [" ".join(index_dict.values()) for index_dict in labels] + x_view = axis.get_xlim()[1] - axis.get_xlim()[0] + y_view = axis.get_ylim()[1] - axis.get_ylim()[0] + x_c = .95 * axis.get_xlim()[1] + .1 * axis.get_xlim()[0] + y_c = .95 * axis.get_ylim()[1] + .1 * axis.get_ylim()[0] + for x, y, label in zip(xs, ys, labels): + if axis.get_xscale() == 'log': + offset_x = x * (1 + .0003 * x_view) if x < x_c else x * (1 - .0003 * x_view) + else: + offset_x = x + .01 * x_view if x < x_c else x - .01 * x_view + if axis.get_yscale() == 'log': + offset_y = y * (1 + .0003 * y_view) if y < y_c else y * (1 - .0003 * y_view) + else: + offset_y = y + .01 * y_view if y < y_c else y - .01 * y_view + axis.text(offset_x, offset_y, label) def _rgba(col): From 94e982f6b191e5a76819e9adecc02e9ac727f90a Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 4 Feb 2023 13:45:38 +0100 Subject: [PATCH 096/170] [learning] Initialize PyTorch biases with 0 --- phi/torch/nets.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/phi/torch/nets.py b/phi/torch/nets.py index 7a073f71f..e1e25ea60 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -146,7 +146,15 @@ def rmsprop(net: nn.Module, learning_rate: float = 1e-3, alpha=0.99, eps=1e-08, return optim.RMSprop(net.parameters(), learning_rate, alpha, eps, weight_decay, momentum, centered) -CONV = [None, nn.Conv1d, nn.Conv2d, nn.Conv3d] +def _bias0(conv): + def initialize(*args, **kwargs): + module = conv(*args, **kwargs) + module.bias.data.fill_(0) + return module + return initialize + + +CONV = [None, _bias0(nn.Conv1d), _bias0(nn.Conv2d), _bias0(nn.Conv3d)] NORM = [None, nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d] ACTIVATIONS = {'ReLU': nn.ReLU, 'Sigmoid': nn.Sigmoid, 'tanh': nn.Tanh, 'SiLU': nn.SiLU, 'GeLU': nn.GELU} @@ -187,7 +195,7 @@ def __init__(self, self._activation = activation self._batch_norm = batch_norm for i, (s1, s2) in enumerate(zip(layers[:-1], layers[1:])): - self.add_module(f'linear{i}', nn.Linear(s1, s2, bias=True)) + self.add_module(f'linear{i}', _bias0(nn.Linear)(s1, s2, bias=True)) if batch_norm: self.add_module(f'norm{i}', nn.BatchNorm1d(s2)) self.softmax = nn.Softmax() if use_softmax else None @@ -444,9 +452,9 @@ def __init__(self, in_channels, mid_channels, batch_norm, activation): super(Dense_resnet_block, self).__init__() self.activation = activation self.bn1 = NORM[1](in_channels) if batch_norm else nn.Identity() - self.linear1 = nn.Linear(in_channels, mid_channels) + self.linear1 = _bias0(nn.Linear)(in_channels, mid_channels) self.bn2 = NORM[1](mid_channels) if batch_norm else nn.Identity() - self.linear2 = nn.Linear(mid_channels, in_channels) + self.linear2 = _bias0(nn.Linear)(mid_channels, in_channels) def forward(self, x): x = TORCH.as_tensor(x) @@ -821,15 +829,15 @@ def __init__(self, in_channels, out_channels, width, modes, activation, batch_no self.width = width self.in_spatial = in_spatial - self.fc0 = nn.Linear(in_channels + in_spatial, self.width) + self.fc0 = _bias0(nn.Linear)(in_channels + in_spatial, self.width) for i in range(4): self.add_module(f'conv{i}', SpectralConv(self.width, self.width, modes, in_spatial)) self.add_module(f'w{i}', CONV[in_spatial](self.width, self.width, kernel_size=1)) self.add_module(f'bn{i}', NORM[in_spatial](self.width) if batch_norm else nn.Identity()) - self.fc1 = nn.Linear(self.width, 128) - self.fc2 = nn.Linear(128, out_channels) + self.fc1 = _bias0(nn.Linear)(self.width, 128) + self.fc2 = _bias0(nn.Linear)(128, out_channels) # Adding extra spatial channels eg. x, y, z, .... to input x def get_grid(self, shape, device): From 447c089dc5142b8093a3c53b41ee7174422c7bd0 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 4 Feb 2023 13:47:56 +0100 Subject: [PATCH 097/170] [learning] Remove unused PyTorch code --- phi/torch/nets.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/phi/torch/nets.py b/phi/torch/nets.py index e1e25ea60..2f353c680 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -268,8 +268,7 @@ def __init__(self, d: int, in_channels: int, out_channels: int, filters: tuple, self.add_module('inc', DoubleConv(d, in_channels, filters[0], filters[0], batch_norm, activation, periodic)) for i in range(1, self._levels): self.add_module(f'down{i}', Down(d, filters[i - 1], filters[i], batch_norm, activation, periodic, use_res_blocks)) - self.add_module(f'up{i}', Up(d, filters[i] + filters[i - 1], filters[i - 1], batch_norm, activation, periodic, - use_res_blocks=use_res_blocks)) + self.add_module(f'up{i}', Up(d, filters[i] + filters[i - 1], filters[i - 1], batch_norm, activation, periodic, use_res_blocks)) self.add_module('outc', CONV[d](filters[0], out_channels, kernel_size=1)) def forward(self, x): @@ -328,21 +327,13 @@ class Up(nn.Module): _MODES = [None, 'linear', 'bilinear', 'trilinear'] - def __init__(self, d: int, in_channels: int, out_channels: int, batch_norm: bool, activation: type, periodic: bool, linear=True, use_res_blocks: bool = False): + def __init__(self, d: int, in_channels: int, out_channels: int, batch_norm: bool, activation: type, periodic: bool, use_res_blocks: bool): super().__init__() - if linear: - # if bilinear, use the normal convolutions to reduce the number of channels - up = nn.Upsample(scale_factor=2, mode=Up._MODES[d]) - if use_res_blocks: - conv = resnet_block(d, in_channels, out_channels, batch_norm, activation, periodic) - else: - conv = DoubleConv(d, in_channels, out_channels, in_channels // 2, batch_norm, activation, periodic) + up = nn.Upsample(scale_factor=2, mode=Up._MODES[d]) + if use_res_blocks: + conv = resnet_block(d, in_channels, out_channels, batch_norm, activation, periodic) else: - up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2) - if use_res_blocks: - conv = resnet_block(d, in_channels, out_channels, batch_norm, activation, periodic) - else: - conv = DoubleConv(d, in_channels, out_channels, out_channels, batch_norm, activation, periodic) + conv = DoubleConv(d, in_channels, out_channels, in_channels // 2, batch_norm, activation, periodic) self.add_module('up', up) self.add_module('conv', conv) From f03e6bd8019253604e855e1edbf058cfa2a6f50e Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 4 Feb 2023 17:22:03 +0100 Subject: [PATCH 098/170] [vis] Refactor plotting * Add Recipe class * Remove PointCloud.color * Add color argument to plot() --- phi/field/_field.py | 7 +- phi/field/_field_math.py | 6 +- phi/field/_point_cloud.py | 47 +- phi/math/_shape.py | 4 +- phi/math/_tensors.py | 2 +- phi/vis/_dash/_plotly_plots.py | 236 ++++++---- phi/vis/_dash/viewer.py | 2 +- phi/vis/_matplotlib/__init__.py | 3 +- phi/vis/_matplotlib/_matplotlib_plots.py | 534 +++++++++-------------- phi/vis/_matplotlib/_scalars.py | 178 ++++++++ phi/vis/_vis.py | 16 +- phi/vis/_vis_base.py | 63 ++- tests/commit/vis/test__plots.py | 8 +- 13 files changed, 639 insertions(+), 467 deletions(-) create mode 100644 phi/vis/_matplotlib/_scalars.py diff --git a/phi/field/_field.py b/phi/field/_field.py index 9831330d2..2caf2352f 100644 --- a/phi/field/_field.py +++ b/phi/field/_field.py @@ -1,5 +1,5 @@ import warnings -from typing import TypeVar, Callable +from typing import TypeVar, Callable, Union from phi import math from phi.math import Shape, Tensor, channel @@ -163,7 +163,10 @@ class SampledField(Field): Base class for fields that are sampled at specific locations such as grids or point clouds. """ - def __init__(self, elements: Geometry or Tensor, values: Tensor, extrapolation: float or Extrapolation or Field or None, bounds: Box or None): + def __init__(self, elements: Union[Geometry, Tensor], + values: Tensor, + extrapolation: float or Extrapolation or Field or None, + bounds: Box or None): """ Args: elements: Geometry object specifying the sample points and sizes diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 54e9c8c42..b4125a621 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -604,8 +604,7 @@ def concat(fields: List[SampledFieldType] or Tuple[SampledFieldType, ...], dim: elif isinstance(fields[0], PointCloud): elements = geom.concat([f.elements for f in fields], dim) values = math.concat([math.expand(f.values, f.shape.only(dim)) for f in fields], dim) - colors = math.concat([math.expand(f.color, f.shape.only(dim)) for f in fields], dim) - return PointCloud(elements=elements, values=values, color=colors, extrapolation=fields[0].extrapolation, add_overlapping=fields[0]._add_overlapping, bounds=fields[0]._bounds) + return PointCloud(elements=elements, values=values, extrapolation=fields[0].extrapolation, add_overlapping=fields[0]._add_overlapping, bounds=fields[0]._bounds) raise NotImplementedError(type(fields[0])) @@ -639,8 +638,7 @@ def stack(fields, dim: Shape, dim_bounds: Box = None): elif isinstance(fields[0], PointCloud): elements = geom.stack([f.elements for f in fields], dim=dim) values = math.stack([f.values for f in fields], dim=dim) - colors = math.stack([f.color for f in fields], dim=dim) - return PointCloud(elements=elements, values=values, color=colors, extrapolation=fields[0].extrapolation, add_overlapping=fields[0]._add_overlapping, bounds=fields[0]._bounds) + return PointCloud(elements=elements, values=values, extrapolation=fields[0].extrapolation, add_overlapping=fields[0]._add_overlapping, bounds=fields[0]._bounds) raise NotImplementedError(type(fields[0])) diff --git a/phi/field/_point_cloud.py b/phi/field/_point_cloud.py index 7f020ebbc..ccada61bd 100644 --- a/phi/field/_point_cloud.py +++ b/phi/field/_point_cloud.py @@ -1,12 +1,12 @@ import warnings -from typing import Any, Tuple +from typing import Any, Tuple, Union from phi import math from phi.geom import Geometry, GridCell, Box from ._field import SampledField from ..geom._stack import GeometryStack from ..math import Tensor, instance, Shape -from ..math.extrapolation import Extrapolation +from ..math.extrapolation import Extrapolation, ConstantExtrapolation from ..math.magic import slicing_dict @@ -26,12 +26,12 @@ class PointCloud(SampledField): """ def __init__(self, - elements: Tensor or Geometry, + elements: Union[Tensor, Geometry], values: Any = 1., - extrapolation: Extrapolation or float = 0., + extrapolation: Union[Extrapolation, float] = 0., add_overlapping=False, bounds: Box = None, - color: str or Tensor or tuple or list or None = None): + color: Any = None): """ Args: elements: `Tensor` or `Geometry` object specifying the sample points and sizes @@ -39,12 +39,11 @@ def __init__(self, extrapolation: values outside elements add_overlapping: True: values of overlapping geometries are summed. False: values between overlapping geometries are interpolated bounds: (optional) size of the fixed domain in which the points should get visualized. None results in max and min coordinates of points. - color: (optional) hex code for color or tensor of colors (same length as elements) in which points should get plotted. """ SampledField.__init__(self, elements, math.wrap(values), extrapolation, bounds) self._add_overlapping = add_overlapping - color = '#0060ff' if color is None else color - self._color = math.wrap(color, instance('points')) if isinstance(color, (tuple, list)) else math.wrap(color) + if color is not None: + warnings.warn("PointCloud.color is no longer in use. Use plot(data, color=...) instead.", SyntaxWarning) @property def shape(self): @@ -56,24 +55,20 @@ def __getitem__(self, item): return self elements = self.elements[{dim: selection for dim, selection in item.items() if dim != 'vector'}] values = self._values[item] - color = self._color[item] extrapolation = self._extrapolation[item] - return PointCloud(elements, values, extrapolation, self._add_overlapping, self._bounds, color) + return PointCloud(elements, values, extrapolation, self._add_overlapping, self._bounds) def with_elements(self, elements: Geometry): - return PointCloud(elements=elements, values=self.values, extrapolation=self.extrapolation, add_overlapping=self._add_overlapping, bounds=self._bounds, color=self._color) + return PointCloud(elements=elements, values=self.values, extrapolation=self.extrapolation, add_overlapping=self._add_overlapping, bounds=self._bounds) def with_values(self, values): - return PointCloud(elements=self.elements, values=values, extrapolation=self.extrapolation, add_overlapping=self._add_overlapping, bounds=self._bounds, color=self._color) + return PointCloud(elements=self.elements, values=values, extrapolation=self.extrapolation, add_overlapping=self._add_overlapping, bounds=self._bounds) def with_extrapolation(self, extrapolation: Extrapolation): - return PointCloud(elements=self.elements, values=self.values, extrapolation=extrapolation, add_overlapping=self._add_overlapping, bounds=self._bounds, color=self._color) - - def with_color(self, color: str or Tensor or tuple or list): - return PointCloud(elements=self.elements, values=self.values, extrapolation=self.extrapolation, add_overlapping=self._add_overlapping, bounds=self._bounds, color=color) + return PointCloud(elements=self.elements, values=self.values, extrapolation=extrapolation, add_overlapping=self._add_overlapping, bounds=self._bounds) def with_bounds(self, bounds: Box): - return PointCloud(elements=self.elements, values=self.values, extrapolation=self.extrapolation, add_overlapping=self._add_overlapping, bounds=bounds, color=self._color) + return PointCloud(elements=self.elements, values=self.values, extrapolation=self.extrapolation, add_overlapping=self._add_overlapping, bounds=bounds) def __value_attrs__(self): return '_values', '_extrapolation' @@ -88,7 +83,7 @@ def __replace_dims__(self, dims: Tuple[str, ...], new_dims: Shape, **kwargs) -> elements = math.rename_dims(self.elements, dims, new_dims) values = math.rename_dims(self.values, dims, new_dims) extrapolation = math.rename_dims(self.extrapolation, dims, new_dims, **kwargs) - return PointCloud(elements, values, extrapolation, self._add_overlapping, self._bounds, self._color) + return PointCloud(elements, values, extrapolation, self._add_overlapping, self._bounds) def __eq__(self, other): if not type(self) == type(other): @@ -121,10 +116,6 @@ def bounds(self) -> Box: radius = math.max(self.elements.bounding_radius()) return Box(bounds.lower - radius, bounds.upper + radius) - @property - def color(self) -> Tensor: - return self._color - def _sample(self, geometry: Geometry, outside_handling="discard", **kwargs) -> Tensor: if geometry == self.elements: return self.values @@ -152,8 +143,8 @@ def grid_scatter(self, bounds: Box, resolution: math.Shape, outside_handling: st closest_index = bounds.global_to_local(self.points) * resolution - 0.5 mode = 'add' if self._add_overlapping else 'mean' base = math.zeros(resolution) - if isinstance(self.extrapolation, math.extrapolation.ConstantExtrapolation): - base += self.extrapolation.value + if isinstance(self._extrapolation, ConstantExtrapolation): + base += self._extrapolation.value scattered = math.scatter(base, closest_index, self.values, mode=mode, outside_handling=outside_handling) return scattered @@ -164,7 +155,7 @@ def mask(self): Returns: `PointCloud` """ - return PointCloud(self.elements, bounds=self.bounds, color=self.color) + return PointCloud(self.elements, bounds=self.bounds) def __repr__(self): return "PointCloud[%s]" % (self.shape,) @@ -179,13 +170,12 @@ def __and__(self, other): def nonzero(field: SampledField): indices = math.nonzero(field.values, list_dim=instance('points')) elements = field.elements[indices] - return PointCloud(elements, values=math.tensor(1.), extrapolation=math.extrapolation.ZERO, add_overlapping=False, bounds=field.bounds, color=None) + return PointCloud(elements, values=math.tensor(1.), extrapolation=math.extrapolation.ZERO, add_overlapping=False, bounds=field.bounds) def distribute_points(geometries: tuple or list or Geometry or float, dim: Shape = instance('points'), points_per_cell: int = 8, - color: str = None, center: bool = False, radius: float = None, extrapolation: float or Extrapolation = math.NAN, @@ -197,7 +187,6 @@ def distribute_points(geometries: tuple or list or Geometry or float, geometries: Geometry objects marking the cells which should contain points dim: Dimension along which the points are listed. points_per_cell: Number of points for each cell of `geometries` - color (Optional): Color of PointCloud center: Set all points to the center of the grid cells. radius: Sphere radius. extrapolation: Extrapolation for the `PointCloud`, default `NaN` used for FLIP. @@ -216,7 +205,7 @@ def distribute_points(geometries: tuple or list or Geometry or float, from phi.field._field_math import data_bounds radius = math.mean(data_bounds(initial_points).size) * 0.005 from phi.geom import Sphere - return PointCloud(Sphere(initial_points, radius=radius), extrapolation=geometries.extrapolation, color=color, bounds=geometries.bounds) + return PointCloud(Sphere(initial_points, radius=radius), extrapolation=geometries.extrapolation, bounds=geometries.bounds) def _distribute_points(mask: math.Tensor, dim: Shape, points_per_cell: int = 1, center: bool = False) -> math.Tensor: diff --git a/phi/math/_shape.py b/phi/math/_shape.py index cf71f6cd5..ea42afd4f 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -667,7 +667,6 @@ def only(self, dims: 'DimFilter', reorder=False): else: return self[[i for i in range(self.rank) if self.names[i] in dims]] - @property def rank(self) -> int: """ @@ -1113,6 +1112,9 @@ def meshgrid(self, names=False): else: return + def first_index(self, names=False): + return next(iter(self.meshgrid(names=names))) + def are_adjacent(self, dims: str or tuple or list or set or 'Shape'): indices = self.indices(dims) return (max(indices) - min(indices)) == len(dims) - 1 diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index efac69f5d..73f7ecc91 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -1657,7 +1657,7 @@ def tensor(data: Tensor or Shape or tuple or list or numbers.Number, assert shape.rank == 1, "Can only convert 1D shapes to Tensors" shape = shape.with_size(data.names) data = data.sizes - elif isinstance(data, str): + elif isinstance(data, str) or data is None: return layout(data) elif isinstance(data, (numbers.Number, bool)): assert not shape, f"Trying to create a zero-dimensional Tensor from value '{data}' but shape={shape}" diff --git a/phi/vis/_dash/_plotly_plots.py b/phi/vis/_dash/_plotly_plots.py index 392cf8ee5..6936b6223 100644 --- a/phi/vis/_dash/_plotly_plots.py +++ b/phi/vis/_dash/_plotly_plots.py @@ -1,5 +1,5 @@ import warnings -from typing import Tuple, Any, Dict, Optional, List, Callable +from typing import Tuple, Any, Dict, List, Callable import numpy import numpy as np @@ -10,10 +10,11 @@ from phi import math, field from phi.field import SampledField, PointCloud, Grid, StaggeredGrid from phi.geom import Sphere, BaseBox, Point, Box -from phi.math import instance, Tensor, spatial, channel +from phi.geom._stack import GeometryStack +from phi.math import Tensor, spatial, channel, non_channel from phi.vis._dash.colormaps import COLORMAPS from phi.vis._plot_util import smooth_uniform_curve, down_sample_curve -from phi.vis._vis_base import PlottingLibrary +from phi.vis._vis_base import PlottingLibrary, Recipe class PlotlyPlots(PlottingLibrary): @@ -48,9 +49,8 @@ def create_figure(self, def animate(self, fig, frames: int, plot_frame_function: Callable, interval: float, repeat: bool): raise NotImplementedError() - def plot(self, data: SampledField, figure: graph_objects.Figure, subplot, space: Box, min_val: float = None, max_val: float = None, - show_color_bar: bool = True, **plt_args): - _plot(data, figure, row=subplot[0], col=subplot[1], size=(800, 600), colormap=None, show_color_bar=show_color_bar, vmin=min_val, vmax=max_val) + def finalize(self, figure): + pass def close(self, figure): pass @@ -65,57 +65,60 @@ def save(self, figure: graph_objects.Figure, path: str, dpi: float): figure.write_image(path, width=width * dpi / scale, height=height * dpi / scale, scale=scale) +class LinePlot(Recipe): -PLOTLY = PlotlyPlots() - + def can_plot(self, data: SampledField, space: Box) -> bool: + return data.spatial_rank == 1 and isinstance(data, Grid) -def _get_range(bounds: Box, index: int): - lower = bounds.lower.vector[index].numpy() - upper = bounds.upper.vector[index].numpy() - return lower, upper - - -def _plot(data: SampledField, - fig: graph_objects.Figure, - size: tuple, - colormap: str or None, - show_color_bar: bool, - vmin, - vmax, - row: int = None, - col: int = None): - subplot = fig.get_subplot(row, col) - dims = data.bounds.vector.item_names - vector = data.bounds.shape['vector'] - extra_channels = data.shape.channel.without('vector') - if data.spatial_rank == 1 and isinstance(data, Grid): # Line plot + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + row, col = subplot + subplot = figure.get_subplot(row, col) x = data.points.vector[0].numpy().flatten() channels = data.values.shape.channel if channels.rank == 1 and channels.get_item_names(0) is not None: for i, name in enumerate(channels.get_item_names(0)): y = math.reshaped_native(real_values(data[{channels.name: i}]), [data.shape.spatial], to_numpy=True) - fig.add_trace(graph_objects.Scatter(x=x, y=y, mode='lines+markers', name=name), row=row, col=col) - fig.update_layout(showlegend=True) + figure.add_trace(graph_objects.Scatter(x=x, y=y, mode='lines+markers', name=name), row=row, col=col) + figure.update_layout(showlegend=True) else: for ch_idx in channels.meshgrid(): y = math.reshaped_native(real_values(data[ch_idx]), [data.shape.spatial], to_numpy=True) - fig.add_trace(graph_objects.Scatter(x=x, y=y, mode='lines+markers', name='Multi-channel'), row=row, col=col) - fig.update_layout(showlegend=False) - if vmin is not None and vmax is not None: - subplot.yaxis.update(range=(vmin - .02 * (vmax - vmin), vmax + .02 * (vmax - vmin))) - elif data.spatial_rank == 2 and isinstance(data, Grid) and 'vector' not in data.shape: # heatmap + figure.add_trace(graph_objects.Scatter(x=x, y=y, mode='lines+markers', name='Multi-channel'), row=row, col=col) + figure.update_layout(showlegend=False) + if min_val is not None and max_val is not None: + subplot.yaxis.update(range=(min_val - .02 * (max_val - min_val), max_val + .02 * (max_val - min_val))) + + +class Heatmap2D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return data.spatial_rank == 2 and isinstance(data, Grid) and 'vector' not in data.shape + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + row, col = subplot dims = spatial(data) values = real_values(data).numpy(dims.reversed) x = data.points.vector[dims[0].name].dimension(dims[1].name)[0].numpy() y = data.points.vector[dims[1].name].dimension(dims[0].name)[0].numpy() min_val, max_val = numpy.nanmin(values), numpy.nanmax(values) min_val, max_val = min_val if numpy.isfinite(min_val) else 0, max_val if numpy.isfinite(max_val) else 0 - color_scale = get_div_map(min_val, max_val, equal_scale=True, colormap=colormap) + color_scale = get_div_map(min_val, max_val, equal_scale=True) # color_bar = graph_objects.heatmap.ColorBar(x=1.15) , colorbar=color_bar - fig.add_heatmap(row=row, col=col, x=x, y=y, z=values, zauto=False, zmin=min_val, zmax=max_val, colorscale=color_scale, showscale=show_color_bar) - elif data.spatial_rank == 2 and isinstance(data, Grid): # vector field + figure.add_heatmap(row=row, col=col, x=x, y=y, z=values, zauto=False, zmin=min_val, zmax=max_val, colorscale=color_scale, showscale=show_color_bar) + + +class VectorField2D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return data.spatial_rank == 2 and isinstance(data, Grid) + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): if isinstance(data, StaggeredGrid): data = data.at_centers() + row, col = subplot + dims = data.bounds.vector.item_names + vector = data.bounds.shape['vector'] + extra_channels = data.shape.channel.without('vector') x, y = math.reshaped_numpy(data.points.vector[dims], [vector, data.shape.non_channel], force_expand=True) u, v = math.reshaped_numpy(data.values.vector[dims], [vector, extra_channels, data.shape.without(vector)], force_expand=True) for ch in range(u.shape[0]): @@ -128,32 +131,62 @@ def _plot(data: SampledField, lines_x = numpy.stack([x, x + u_ch, [None] * len(x)], -1).flatten() lines_y = numpy.stack([y, y + v_ch, [None] * len(x)], -1).flatten() # 3 points per arrow name = extra_channels.get_item_names(0)[ch] if extra_channels.rank == 1 and extra_channels.get_item_names(0) is not None else None - fig.add_scatter(x=lines_x, y=lines_y, mode='lines', row=row, col=col, name=name) + figure.add_scatter(x=lines_x, y=lines_y, mode='lines', row=row, col=col, name=name) if u.shape[0] == 1: - fig.update_layout(showlegend=False) - elif data.spatial_rank == 3 and isinstance(data, Grid) and data.shape.channel.volume == 1: # 3D heatmap + figure.update_layout(showlegend=False) + + +class Heatmap3D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return data.spatial_rank == 3 and isinstance(data, Grid) and data.shape.channel.volume == 1 + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + row, col = subplot + dims = data.bounds.vector.item_names + vector = data.bounds.shape['vector'] values = real_values(data).numpy(dims) x, y, z = math.reshaped_numpy(data.points.vector[dims], [vector, *data.points.shape.spatial]) min_val, max_val = numpy.nanmin(values), numpy.nanmax(values) min_val, max_val = min_val if numpy.isfinite(min_val) else 0, max_val if numpy.isfinite(max_val) else 0 - color_scale = get_div_map(min_val, max_val, equal_scale=True, colormap=colormap) - fig.add_volume(x=x.flatten(), y=y.flatten(), z=z.flatten(), value=values.flatten(), - showscale=show_color_bar, colorscale=color_scale, cmin=min_val, cmax=max_val, cauto=False, - isomin=0.1, isomax=0.8, - opacity=0.1, # needs to be small to see through all surfaces - surface_count=17, # needs to be a large number for good volume rendering - row=row, col=col) - fig.update_layout(uirevision=True) - elif isinstance(data, Grid) and data.spatial_rank == 3: # 3D vector field + color_scale = get_div_map(min_val, max_val, equal_scale=True) + figure.add_volume(x=x.flatten(), y=y.flatten(), z=z.flatten(), value=values.flatten(), + showscale=show_color_bar, colorscale=color_scale, cmin=min_val, cmax=max_val, cauto=False, + isomin=0.1, isomax=0.8, + opacity=0.1, # needs to be small to see through all surfaces + surface_count=17, # needs to be a large number for good volume rendering + row=row, col=col) + figure.update_layout(uirevision=True) + + +class VectorField3D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, Grid) and data.spatial_rank == 3 + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + row, col = subplot + dims = data.bounds.vector.item_names + vector = data.bounds.shape['vector'] + extra_channels = data.shape.channel.without('vector') if isinstance(data, StaggeredGrid): data = data.at_centers() x, y, z = math.reshaped_numpy(data.points.vector[dims], [vector, data.shape.non_channel]) u, v, w = math.reshaped_numpy(data.values.vector[dims], [vector, extra_channels, data.shape.non_channel], force_expand=True) - fig.add_cone(x=x.flatten(), y=y.flatten(), z=z.flatten(), u=u.flatten(), v=v.flatten(), w=w.flatten(), - colorscale='Blues', - sizemode="absolute", sizeref=1, - row=row, col=col) - elif isinstance(data, PointCloud) and data.spatial_rank == 2 and 'vector' in channel(data): + figure.add_cone(x=x.flatten(), y=y.flatten(), z=z.flatten(), u=u.flatten(), v=v.flatten(), w=w.flatten(), + colorscale='Blues', + sizemode="absolute", sizeref=1, + row=row, col=col) + + +class VectorCloud2D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, PointCloud) and data.spatial_rank == 2 and 'vector' in channel(data) + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + row, col = subplot + vector = data.bounds.shape['vector'] x, y = math.reshaped_numpy(data.points, [vector, data.shape.without('vector')]) u, v = math.reshaped_numpy(data.values, [vector, data.shape.without('vector')], force_expand=True) quiver = figure_factory.create_quiver(x, y, u, v, scale=1.0).data[0] # 7 points per arrow @@ -163,25 +196,37 @@ def _plot(data: SampledField, else: color = data.color.native() quiver.line.update(color=color) - fig.add_trace(quiver, row=row, col=col) - elif isinstance(data, PointCloud) and data.spatial_rank == 2: + figure.add_trace(quiver, row=row, col=col) + + +class PointCloud2D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, PointCloud) and data.spatial_rank == 2 + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + if isinstance(data.elements, GeometryStack): + for idx in data.elements.geometries.shape[0].meshgrid(): + self.plot(data[idx], figure, subplot, space, min_val, max_val, show_color_bar, color[idx]) + return + row, col = subplot + subplot = figure.get_subplot(row, col) + dims = data.bounds.vector.item_names + vector = data.bounds.shape['vector'] + size = figure._phi_size yrange = subplot.yaxis.range - if data.points.shape.non_channel.rank > 1: - data_list = field.unstack(data, data.points.shape.non_channel[0].name) - for d in data_list: - _plot(d, fig, size, colormap, show_color_bar, vmin, vmax, row=row, col=col) - else: - if spatial(data): - raise NotImplementedError("Plotly does not yet support plotting point clouds with spatial dimensions") - x, y = math.reshaped_numpy(data.points.vector[dims], [vector, data.shape.non_channel]) - color = data.color.native() - subplot_height = (subplot.yaxis.domain[1] - subplot.yaxis.domain[0]) * size[1] + if spatial(data): + raise NotImplementedError("Plotly does not yet support plotting point clouds with spatial dimensions") + for idx in non_channel(data.points).meshgrid(names=True): + x, y = math.reshaped_numpy(data[idx].points.vector[dims], [vector, data.shape.non_channel]) + hex_color = color[idx].native() + subplot_height = (subplot.yaxis.domain[1] - subplot.yaxis.domain[0]) * size[1] * 100 if isinstance(data.elements, Sphere): symbol = 'circle' marker_size = data.elements.bounding_radius().numpy() * 1.9 elif isinstance(data.elements, BaseBox): symbol = 'square' - marker_size = math.mean(data.elements.bounding_half_extent(), 'vector').numpy() * 1 + marker_size = math.mean(data.elements.bounding_half_extent(), 'vector').numpy() * 2 elif isinstance(data.elements, Point): symbol = 'x' marker_size = 12 / (subplot_height / (yrange[1] - yrange[0])) @@ -189,23 +234,31 @@ def _plot(data: SampledField, symbol = 'asterisk' marker_size = data.elements.bounding_radius().numpy() marker_size *= subplot_height / (yrange[1] - yrange[0]) - marker = graph_objects.scatter.Marker(size=marker_size, color=color, sizemode='diameter', symbol=symbol) - fig.add_scatter(mode='markers', x=x, y=y, marker=marker, row=row, col=col) - fig.update_layout(showlegend=False) - elif isinstance(data, PointCloud) and data.spatial_rank == 3: + marker = graph_objects.scatter.Marker(size=marker_size, color=hex_color, sizemode='diameter', symbol=symbol) + figure.add_scatter(mode='markers', x=x, y=y, marker=marker, row=row, col=col) + figure.update_layout(showlegend=False) + + +class PointCloud3D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, PointCloud) and data.spatial_rank == 3 + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + row, col = subplot + subplot = figure.get_subplot(row, col) + dims = data.bounds.vector.item_names + vector = data.bounds.shape['vector'] + size = figure._phi_size yrange = subplot.yaxis.range if data.points.shape.non_channel.rank > 1: data_list = field.unstack(data, data.points.shape.non_channel[0].name) for d in data_list: - _plot(d, fig, size, colormap, show_color_bar, vmin, vmax, row=row, col=col) + self.plot(d, figure, (row, col), space, min_val, max_val, show_color_bar, color) else: x, y, z = math.reshaped_numpy(data.points.vector[dims], [vector, data.shape.non_channel]) - color = data.color.native() - # if data.color.shape.instance_rank == 0: - # color = str(data.color) - # else: - # color = [str(d) for d in math.unstack(data.color, instance)] - domain_y = fig.layout[subplot.plotly_name].domain.y + color = color.native() + domain_y = figure.layout[subplot.plotly_name].domain.y if isinstance(data.elements, Sphere): symbol = 'circle' marker_size = data.elements.bounding_radius().numpy() * 2 @@ -220,10 +273,14 @@ def _plot(data: SampledField, marker_size = data.elements.bounding_radius().numpy() marker_size *= size[1] * (domain_y[1] - domain_y[0]) / (yrange[1] - yrange[0]) * 0.5 marker = graph_objects.scatter3d.Marker(size=marker_size, color=color, sizemode='diameter', symbol=symbol) - fig.add_scatter3d(mode='markers', x=x, y=y, z=z, marker=marker, row=row, col=col) - fig.update_layout(showlegend=False) - else: - raise NotImplementedError(f"No figure recipe for {data}") + figure.add_scatter3d(mode='markers', x=x, y=y, z=z, marker=marker, row=row, col=col) + figure.update_layout(showlegend=False) + + +def _get_range(bounds: Box, index: int): + lower = bounds.lower.vector[index].numpy() + upper = bounds.upper.vector[index].numpy() + return lower, upper def real_values(field: SampledField): @@ -376,3 +433,16 @@ def split_curve(curve: np.ndarray) -> List[np.ndarray]: def join_curves(curves: List[np.ndarray]) -> np.ndarray: curves = [np.append(np.array(c, numpy.float), [[numpy.nan, numpy.nan]], -2) for c in curves[:-1]] + [curves[-1]] return np.concatenate(curves, -2) + + +PLOTLY = PlotlyPlots() +PLOTLY.recipes.extend([ + LinePlot(), + Heatmap2D(), + VectorField2D(), + VectorField3D(), + VectorCloud2D(), + Heatmap3D(), + PointCloud2D(), + PointCloud3D(), + ]) \ No newline at end of file diff --git a/phi/vis/_dash/viewer.py b/phi/vis/_dash/viewer.py index 285b90064..251af4783 100644 --- a/phi/vis/_dash/viewer.py +++ b/phi/vis/_dash/viewer.py @@ -46,7 +46,7 @@ def update_figure(field, _0, _1, *settings): value = app.model.get_field(field, selection['select']) try: value = select_channel(value, selection.get('component', None)) - return plot(value, lib='plotly', size=(height, height), same_scale=False, colormap=app.config.get('colormap', None)).native() + return plot(value, lib='plotly', size=(height, height), same_scale=False).native() except ValueError as err: fig = graph_objects.Figure() fig.update_layout(title_text=str(value), paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)') diff --git a/phi/vis/_matplotlib/__init__.py b/phi/vis/_matplotlib/__init__.py index 55983fa29..74fa454bf 100644 --- a/phi/vis/_matplotlib/__init__.py +++ b/phi/vis/_matplotlib/__init__.py @@ -1 +1,2 @@ -from ._matplotlib_plots import MATPLOTLIB, plot_scalars, smooth_uniform_curve, savefig +from ._matplotlib_plots import MATPLOTLIB, savefig +from ._scalars import plot_scalars, smooth_uniform_curve diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index c0bae2bcb..357e0f020 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -1,29 +1,21 @@ -import logging -import os import sys import warnings -from numbers import Number from typing import Callable, Tuple, Any, Dict import matplotlib import matplotlib.pyplot as plt -import numpy import numpy as np -from matplotlib import animation, cbook +from matplotlib import animation from matplotlib import rc from matplotlib.patches import Patch from matplotlib.transforms import Bbox -from mpl_toolkits.mplot3d import Axes3D from phi import math, field -from phi.field import Grid, StaggeredGrid, PointCloud, Scene, SampledField -from phi.field._scene import _str +from phi.field import Grid, StaggeredGrid, PointCloud, SampledField from phi.geom import Sphere, BaseBox, Point, Box from phi.geom._stack import GeometryStack -from phi.math import Tensor, batch, channel, spatial, instance, non_channel -from phi.math.backend import PHI_LOGGER -from phi.vis._plot_util import smooth_uniform_curve -from phi.vis._vis_base import display_name, PlottingLibrary +from phi.math import Tensor, channel, spatial, instance, non_channel, Shape +from phi.vis._vis_base import display_name, PlottingLibrary, Recipe, index_label class MatplotlibPlots(PlottingLibrary): @@ -83,10 +75,10 @@ def create_figure(self, axis.set_zlim(_get_range(bounds, 2)) if bounds.vector.item_names[0] in log_dims: warnings.warn("Only z axis can be log scaled in 3D plot with Matplotlib. Please reorder the dimensions.", RuntimeWarning) - # axis.set_xscale('log') + # subplot.set_xscale('log') if bounds.vector.item_names[1] in log_dims: warnings.warn("Only z axis can be log scaled in 3D plot with Matplotlib. Please reorder the dimensions.", RuntimeWarning) - # axis.set_yscale('log') + # subplot.set_yscale('log') if bounds.vector.item_names[2] in log_dims: axis.set_zscale('log') axis.set_title(titles.get((row, col), None)) @@ -108,7 +100,7 @@ def clear_and_plot(frame: int): if axis not in base_axes: # colorbar etc. axis.remove() else: - # axis.cla() # this also clears titles and axis labels + # subplot.cla() # this also clears titles and subplot labels axis.lines.clear() axis.patches.clear() axis.texts.clear() @@ -120,29 +112,14 @@ def clear_and_plot(frame: int): box = Bbox(positions[axis]) axis.set_position(box, which='active') axis.set_subplotspec(specs[axis]) - # axis.set_title(titles[axis]) + # subplot.set_title(titles[subplot]) # plt.tight_layout() plot_frame_function(frame) return animation.FuncAnimation(fig, clear_and_plot, repeat=repeat, frames=frames, interval=interval) - def plot(self, - data: SampledField, - figure, - subplot, - space: Box, - min_val: float = None, - max_val: float = None, - show_color_bar: bool = True, - **plt_args): - """ - Returns: - [Matplotlib figure](https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure). - """ - # plt.tight_layout() - _plot(subplot, data, space, show_color_bar=show_color_bar, vmin=min_val, vmax=max_val, **plt_args) + def finalize(self, figure): plt.tight_layout() - return figure def close(self, figure): if isinstance(figure, plt.Figure): @@ -171,9 +148,6 @@ def save(self, figure, path: str, dpi: float): raise ValueError(figure) -MATPLOTLIB = MatplotlibPlots() - - def _get_range(bounds: Box, index: int): lower = float(bounds.lower.vector[index].min) upper = float(bounds.upper.vector[index].max) @@ -185,31 +159,40 @@ def _default_color(i: int): return default_colors[i % len(default_colors)] -def _plot(axis, data: SampledField, space: Box, show_color_bar, vmin, vmax, **plt_args): - dims = space.vector.item_names - # dims = data.bounds.vector.item_names - vector = data.bounds.shape['vector'] - extra_channels = data.shape.channel.without('vector') - if isinstance(data, Grid) and data.spatial_rank == 1: # Line plot +class LinePlot(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, Grid) and data.spatial_rank == 1 and not instance(data) + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): x = data.points.staggered_direction[0].vector[0].numpy() requires_legend = False for c in channel(data).meshgrid(names=True): label = ", ".join([i for dim, i in c.items() if isinstance(i, str)]) values = data.values[c].numpy() - color = _default_color(len(axis.lines)) + color = _default_color(len(subplot.lines)) if values.dtype in (np.complex64, np.complex128): - axis.plot(x, values.real, label=f"real({label})" if label else "real", color=color) - axis.plot(x, values.imag, '--', label=f"imag({label})" if label else "imag", color=color) + subplot.plot(x, values.real, label=f"real({label})" if label else "real", color=color) + subplot.plot(x, values.imag, '--', label=f"imag({label})" if label else "imag", color=color) requires_legend = True else: - axis.plot(x, values, label=label, color=color) + subplot.plot(x, values, label=label, color=color) requires_legend = requires_legend or label if requires_legend: - axis.legend() - elif vmin is not None and vmax is not None: - axis.set_ylim((vmin - .02 * (vmax - vmin), vmax + .02 * (vmax - vmin))) - elif isinstance(data, Grid) and channel(data).volume == 1 and data.spatial_rank == 2: + subplot.legend() + elif min_val is not None and max_val is not None: + subplot.set_ylim((min_val - .02 * (max_val - min_val), max_val + .02 * (max_val - min_val))) + return True + + +class Heatmap2D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, Grid) and channel(data).volume == 1 and data.spatial_rank == 2 and not instance(data) + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): dims = spatial(data) + vector = data.bounds.shape['vector'] if data.bounds.upper.vector.item_names is not None: left, bottom = data.bounds.lower.vector[dims] right, top = data.bounds.upper.vector[dims] @@ -221,64 +204,172 @@ def _plot(axis, data: SampledField, space: Box, show_color_bar, vmin, vmax, **pl if space.spatial_rank == 3: # surface plot z = data.values.numpy(dims) x, y = math.reshaped_numpy(data.points, [vector, *spatial(data)]) - im = axis.plot_surface(x, y, z, **plt_args) + im = subplot.plot_surface(x, y, z) else: # heatmap - im = axis.imshow(data.values.numpy(dims.reversed), origin='lower', extent=extent, vmin=vmin, vmax=vmax, **plt_args) + im = subplot.imshow(data.values.numpy(dims.reversed), origin='lower', extent=extent, vmin=min_val, vmax=max_val) if show_color_bar: - figure_has_color_bar = any(['colorbar' in ax.get_label() for ax in axis.figure.axes]) - if vmin is None or vmax is None or not figure_has_color_bar: - axis.figure.colorbar(im, ax=axis) # adds a new Axis to the figure - elif isinstance(data, Grid) and data.spatial_rank == 2: # vector field + figure_has_color_bar = any(['colorbar' in ax.get_label() for ax in subplot.figure.axes]) + if min_val is None or max_val is None or not figure_has_color_bar: + subplot.figure.colorbar(im, ax=subplot) # adds a new Axis to the figure + return True + + +class VectorField2D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, Grid) and data.spatial_rank == 2 + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + dims = space.vector.item_names + vector = data.bounds.shape['vector'] + extra_channels = data.shape.channel.without('vector') if isinstance(data, StaggeredGrid): data = data.at_centers() x, y = math.reshaped_numpy(data.points.vector[dims], [vector, data.shape.non_channel]) u, v = math.reshaped_numpy(data.values.vector[dims], [vector, extra_channels, data.shape.non_channel], force_expand=True) - color = axis.xaxis.label.get_color() + color = subplot.xaxis.label.get_color() for ch in range(u.shape[0]): - axis.quiver(x, y, u[ch], v[ch], color=color, units='xy', scale=1) - elif isinstance(data, Grid) and channel(data).volume > 1 and data.spatial_rank == 3: # 3D vector field + subplot.quiver(x, y, u[ch], v[ch], color=color, units='xy', scale=1) + return True + + +class VectorField3D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, Grid) and channel(data).volume > 1 and data.spatial_rank == 3 + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + dims = space.vector.item_names + vector = data.bounds.shape['vector'] + extra_channels = data.shape.channel.without('vector') if isinstance(data, StaggeredGrid): data = data.at_centers() x, y, z = math.reshaped_numpy(data.points.vector[dims], [vector, data.shape.non_channel]) u, v, w = math.reshaped_numpy(data.values.vector[dims], [vector, extra_channels, data.shape.non_channel], force_expand=True) for ch in range(u.shape[0]): - axis.quiver(x, y, z, u[ch], v[ch], w[ch]) - elif isinstance(data, Grid) and channel(data).volume == 1 and data.spatial_rank == 3: # 3D heatmap + subplot.quiver(x, y, z, u[ch], v[ch], w[ch]) + + +class Heatmap3D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, Grid) and channel(data).volume == 1 and data.spatial_rank == 3 + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + dims = space.vector.item_names x, y, z = StaggeredGrid(lambda x: x, math.extrapolation.BOUNDARY, data.bounds, data.resolution).staggered_tensor().numpy(('vector',) + dims) values = data.values.numpy(dims) cmap = plt.get_cmap('viridis') norm = matplotlib.colors.Normalize(vmin=np.min(values), vmax=np.max(values)) colors = cmap(norm(values)) - axis.voxels(x, y, z, values, facecolors=colors, edgecolor='k') - elif isinstance(data, PointCloud) and data.spatial_rank == 2 and 'vector' in channel(data): # vector cloud + subplot.voxels(x, y, z, values, facecolors=colors, edgecolor='k') + + +class VectorCloud2D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, PointCloud) and data.spatial_rank == 2 and 'vector' in channel(data) + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): vector = data.points.shape['vector'] x, y = math.reshaped_numpy(data.points, [vector, data.shape.without('vector')]) u, v = math.reshaped_numpy(data.values, [vector, data.shape.without('vector')], force_expand=True) - if data.color.shape: - color = data.color.numpy(data.shape.non_channel).reshape(-1) + if color.shape: + color = color.numpy(data.shape.non_channel).reshape(-1) else: - color = data.color.native() - axis.quiver(x, y, u, v, color=color, units='xy', scale=1) - elif isinstance(data, PointCloud) and data.spatial_rank == 2: # point cloud - if channel(data.points).without('vector'): # multiple channel dimensions - channel_dim = channel(data.points).without('vector')[0] - legend_patches = [] - for name, d in zip(channel_dim.item_names[0] or (None,) * channel_dim.size, field.unstack(data, channel_dim.name)): - col = _plot_points(axis, d, dims, vector, **plt_args) - legend_patches.append(Patch(color=_rgba(col), label=name)) - if channel_dim.item_names: - axis.legend(handles=legend_patches) + color = color.native() + subplot.quiver(x, y, u, v, color=color, units='xy', scale=1) + + +class PointCloud2D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, PointCloud) and data.spatial_rank == 2 + + def plot(self, data: PointCloud, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + dims = space.vector.item_names + vector = data.bounds.shape['vector'] + channels = channel(data.points).without('vector') + legend_patches = [] + for idx in channels.meshgrid(names=True): + col = color[idx] + PointCloud2D._plot_points(subplot, data[idx], dims, vector, col) + if col.rank < color.rank: # There are multiple colors + legend_patches.append(Patch(color=_rgba(col), label=index_label(idx))) + if legend_patches: + subplot.legend(handles=legend_patches) + + @staticmethod + def _plot_points(axis, data: PointCloud, dims, vector, color): + if isinstance(data.elements, GeometryStack): + for idx in data.elements.geometries.shape[0].meshgrid(): + PointCloud2D._plot_points(axis, data[idx], dims, vector, color[idx]) + return + x, y = math.reshaped_numpy(data.points.vector[dims], [vector, non_channel(data)]) + mpl_colors = matplotlib_colors(color, non_channel(data), default=0) + if isinstance(data.elements, Point): + if spatial(data.points).is_empty: + axis.scatter(x, y, marker='x', color=mpl_colors, s=6 ** 2, alpha=0.8) else: - _plot_points(axis, data, dims, vector, **plt_args) - elif isinstance(data, PointCloud) and data.spatial_rank == 3: - if data.points.shape.non_channel.rank > 1: - data_list = field.unstack(data, data.points.shape.non_channel[0].name) - for d in data_list: - _plot(axis, d, space, show_color_bar, vmin, vmax, **plt_args) - else: - x, y, z = math.reshaped_numpy(data.points.vector[dims], [vector, data.shape.non_channel]) - color = [d.native() for d in data.color.points.unstack(len(x))] - M = axis.transData.get_matrix() + if isinstance(data.elements, Sphere): + rad = math.reshaped_numpy(data.elements.bounding_radius(), [data.shape.non_channel], force_expand=True) + shapes = [plt.Circle((xi, yi), radius=ri, linewidth=0, alpha=0.8, facecolor=ci) for xi, yi, ri, ci in zip(x, y, rad, mpl_colors)] + elif isinstance(data.elements, BaseBox): + w2, h2 = math.reshaped_numpy(data.elements.bounding_half_extent(), ['vector', data.shape.non_channel], force_expand=True) + shapes = [plt.Rectangle((xi - w2i, yi - h2i), w2i * 2, h2i * 2, linewidth=1, edgecolor='white', alpha=0.8, facecolor=ci) for xi, yi, w2i, h2i, ci in zip(x, y, w2, h2, mpl_colors)] + else: + rad = math.reshaped_numpy(data.elements.bounding_radius(), [data.shape.non_channel], force_expand=True) + shapes = [plt.Circle((xi, yi), radius=ri, linewidth=0, alpha=0.8, facecolor=ci) for xi, yi, ri, ci in zip(x, y, rad, mpl_colors)] + c = matplotlib.collections.PatchCollection(shapes, match_original=True) + axis.add_collection(c) + if spatial(data.points): # Connect by line + x, y = math.reshaped_numpy(data.points.vector[dims], [vector, instance(data), spatial(data)]) + mpl_colors = matplotlib_colors(color, instance(data)) + for i in range(instance(data).volume): + axis.plot(x[i], y[i], color=mpl_colors[i] if mpl_colors is not None else None) + if any(non_channel(data).item_names): + PointCloud2D._annotate_points(axis, data.points) + + @staticmethod + def _annotate_points(axis, points: math.Tensor): + labelled_dims = non_channel(points) + labelled_dims = math.concat_shapes(*[d for d in labelled_dims if d.item_names[0]]) + if not labelled_dims: + return + if all(dim.name in points.shape.get_item_names('vector') for dim in labelled_dims): + return # The point labels match one of the figure axes, so they are redundant + if points.shape['vector'].size == 2: + xs, ys = math.reshaped_numpy(points, ['vector', points.shape.without('vector')], force_expand=True) + labels = [index_label(idx) for idx in labelled_dims.meshgrid(names=True)] + x_view = axis.get_xlim()[1] - axis.get_xlim()[0] + y_view = axis.get_ylim()[1] - axis.get_ylim()[0] + x_c = .95 * axis.get_xlim()[1] + .1 * axis.get_xlim()[0] + y_c = .95 * axis.get_ylim()[1] + .1 * axis.get_ylim()[0] + for x, y, label in zip(xs, ys, labels): + if axis.get_xscale() == 'log': + offset_x = x * (1 + .0003 * x_view) if x < x_c else x * (1 - .0003 * x_view) + else: + offset_x = x + .01 * x_view if x < x_c else x - .01 * x_view + if axis.get_yscale() == 'log': + offset_y = y * (1 + .0003 * y_view) if y < y_c else y * (1 - .0003 * y_view) + else: + offset_y = y + .01 * y_view if y < y_c else y - .01 * y_view + axis.text(offset_x, offset_y, label) + + +class PointCloud3D(Recipe): + + def can_plot(self, data: SampledField, space: Box) -> bool: + return isinstance(data, PointCloud) and data.spatial_rank == 3 + + def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): + dims = space.vector.item_names + vector = data.bounds.shape['vector'] + channels = channel(data.points).without('vector') + for idx in channels.meshgrid(names=True): + x, y, z = math.reshaped_numpy(data[idx].points.vector[dims], [vector, non_channel(data)]) + mpl_colors = matplotlib_colors(color[idx], non_channel(data), default=0) + M = subplot.transData.get_matrix() x_scale, y_scale, z_scale = M[0, 0], M[1, 1], M[2, 2] if isinstance(data.elements, Sphere): symbol = 'o' @@ -292,78 +383,15 @@ def _plot(axis, data: SampledField, space: Box, show_color_bar, vmin, vmax, **pl else: symbol = 'X' size = data.elements.bounding_radius().numpy() - axis.scatter(x, y, z, marker=symbol, color=color, s=(size * 0.5 * (x_scale+y_scale+z_scale)/3) ** 2) - else: - raise NotImplementedError(f"No figure recipe for {data}") - - -def _plot_points(axis, data: PointCloud, dims, vector, **plt_args): - x, y = math.reshaped_numpy(data.points.vector[dims], [vector, data.shape.non_channel]) - if data.color.dtype.kind == int: - cycle = list(plt.rcParams['axes.prop_cycle'].by_key()['color']) - color = [cycle[int(d)] for d in data.color.points.unstack(len(x))] - else: - color = [d.native() for d in data.color.points.unstack(len(x))] - if isinstance(data.elements, GeometryStack): - stack_dim = data.elements.geometries.shape[0] - parts = math.unstack(data, stack_dim) - for part in parts: - _plot_points(axis, part, dims, vector, **plt_args) - return color - elif isinstance(data.elements, Point): - if spatial(data.points).is_empty: - axis.scatter(x, y, marker='x', color=color, s=6 ** 2, alpha=0.8) - else: - if isinstance(data.elements, Sphere): - rad = math.reshaped_numpy(data.elements.bounding_radius(), [data.shape.non_channel], force_expand=True) - shapes = [plt.Circle((xi, yi), radius=ri, linewidth=0, alpha=0.8, facecolor=ci) for xi, yi, ri, ci in zip(x, y, rad, color)] - elif isinstance(data.elements, BaseBox): - w2, h2 = math.reshaped_numpy(data.elements.bounding_half_extent(), ['vector', data.shape.non_channel], force_expand=True) - shapes = [plt.Rectangle((xi-w2i, yi-h2i), w2i*2, h2i*2, linewidth=1, edgecolor='white', alpha=0.8, facecolor=ci) for xi, yi, w2i, h2i, ci in zip(x, y, w2, h2, color)] - else: - rad = math.reshaped_numpy(data.elements.bounding_radius(), [data.shape.non_channel], force_expand=True) - shapes = [plt.Circle((xi, yi), radius=ri, linewidth=0, alpha=0.8, facecolor=ci) for xi, yi, ri, ci in zip(x, y, rad, color)] - c = matplotlib.collections.PatchCollection(shapes, match_original=True) - axis.add_collection(c) - if spatial(data.points): # Connect by line - x, y = math.reshaped_numpy(data.points.vector[dims], [vector, spatial(data), instance(data)]) - axis.plot(x, y, color=color[0]) - if any(non_channel(data).item_names): - _annotate_points(axis, data.points) - return color[0] - - -def _annotate_points(axis, points: math.Tensor): - labelled_dims = non_channel(points) - labelled_dims = math.concat_shapes(*[d for d in labelled_dims if d.item_names[0]]) - if not labelled_dims: - return - if all(dim.name in points.shape.get_item_names('vector') for dim in labelled_dims): - return # The point labels match one of the figure axes, so they are redundant - if points.shape['vector'].size == 2: - xs, ys = math.reshaped_numpy(points, ['vector', points.shape.without('vector')], force_expand=True) - if labelled_dims.rank == 1: - labels = labelled_dims.item_names[0] - else: - labels = labelled_dims.meshgrid(names=True) - labels = [" ".join(index_dict.values()) for index_dict in labels] - x_view = axis.get_xlim()[1] - axis.get_xlim()[0] - y_view = axis.get_ylim()[1] - axis.get_ylim()[0] - x_c = .95 * axis.get_xlim()[1] + .1 * axis.get_xlim()[0] - y_c = .95 * axis.get_ylim()[1] + .1 * axis.get_ylim()[0] - for x, y, label in zip(xs, ys, labels): - if axis.get_xscale() == 'log': - offset_x = x * (1 + .0003 * x_view) if x < x_c else x * (1 - .0003 * x_view) - else: - offset_x = x + .01 * x_view if x < x_c else x - .01 * x_view - if axis.get_yscale() == 'log': - offset_y = y * (1 + .0003 * y_view) if y < y_c else y * (1 - .0003 * y_view) - else: - offset_y = y + .01 * y_view if y < y_c else y - .01 * y_view - axis.text(offset_x, offset_y, label) + subplot.scatter(x, y, z, marker=symbol, color=mpl_colors, s=(size * 0.5 * (x_scale + y_scale + z_scale) / 3) ** 2) def _rgba(col): + if isinstance(col, Tensor): + col = next(iter(col)) + if not isinstance(col, (str, tuple, list)): + cycle = list(plt.rcParams['axes.prop_cycle'].by_key()['color']) + col = cycle[int(col)] if isinstance(col, str) and col.startswith('#'): col = tuple(int(col.lstrip('#')[i:i+2], 16) for i in (0, 2, 4)) col = np.asarray(col) @@ -372,181 +400,45 @@ def _rgba(col): return col +def matplotlib_colors(color: Tensor, dims: Shape, default=None) -> list or None: + if color.rank == 0 and color.native() is None: + if default is None: + return None + else: + color = math.wrap(default) + color = color[math.shape(color).without(dims).first_index()] # Select first color along unlisted dimensions + if color.dtype.kind == int: + cycle = list(plt.rcParams['axes.prop_cycle'].by_key()['color']) + return [cycle[int(color[idx])] for idx in dims.meshgrid()] + else: + return [color[idx].native() for idx in dims.meshgrid()] + + def _get_pixels_per_unit(fig: plt.Figure, axis: plt.Axes, dpi=90): M = axis.transData.get_matrix() x_scale, y_scale = M[0, 0], M[1, 1] # fig_size_px/unit - # subplot_width = axis.figbox.width * axis.figure.bbox_inches.width - # subplot_height = axis.figbox.height * axis.figure.bbox_inches.height - # units_x = axis.get_xlim()[1] - axis.get_xlim()[0] - # units_y = axis.get_ylim()[1] - axis.get_ylim()[0] + # subplot_width = subplot.figbox.width * subplot.figure.bbox_inches.width + # subplot_height = subplot.figbox.height * subplot.figure.bbox_inches.height + # units_x = subplot.get_xlim()[1] - subplot.get_xlim()[0] + # units_y = subplot.get_ylim()[1] - subplot.get_ylim()[0] # result_x = subplot_width * dpi / units_x # result_y = subplot_height * dpi / units_y return min(x_scale, y_scale) - -def plot_scalars(scene: str or tuple or list or Scene or math.Tensor, - names: str or tuple or list or math.Tensor = None, - reduce: str or tuple or list or math.Shape = 'names', - down='', - smooth=1, - smooth_alpha=0.2, - smooth_linewidth=2., - size=(8, 6), - transform: Callable = None, - tight_layout=True, - grid: str or dict = 'y', - log_scale='', - legend='upper right', - x='steps', - xlim=None, - ylim=None, - titles=True, - labels: math.Tensor = None, - xlabel: str = None, - ylabel: str = None, - colors: math.Tensor = 'default', - dashed: math.Tensor = False): - """ - - Args: - scene: `str` or `Tensor`. Scene paths containing the data to plot. - names: Data files to plot for each scene. The file must be located inside the scene directory and have the name `log_.txt`. - reduce: Tensor dimensions along which all curves are plotted in the same diagram. - down: Tensor dimensions along which diagrams are ordered top-to-bottom instead of left-to-right. - smooth: `int` or `Tensor`. Number of data points to average, -1 for all. - smooth_alpha: Opacity of the non-smoothed curves under the smoothed curves. - smooth_linewidth: Line width of the smoothed curves. - size: Figure size in inches. - transform: Function `T(x,y) -> (x,y)` transforming the curves. - tight_layout: - grid: - log_scale: - legend: - x: - xlim: - ylim: - titles: - labels: - xlabel: - ylabel: - colors: Line colors as `str`, `int` or `Tensor`. Integers are interpreted as indices of the default color list. - - Returns: - MatPlotLib [figure](https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure) - """ - scene = Scene.at(scene) - additional_reduce = () - if names is None: - first_path = next(iter(math.flatten(scene.paths))) - names = [_str(n) for n in os.listdir(first_path)] - names = [n[4:-4] for n in names if n.endswith('.txt') and n.startswith('log_')] - names = math.wrap(names, batch('names')) - additional_reduce = ['names'] - elif isinstance(names, str): - names = math.wrap(names) - elif isinstance(names, (tuple, list)): - names = math.wrap(names, batch('names')) - else: - assert isinstance(names, math.Tensor), f"Invalid argument 'names': {type(names)}" - colors = math.wrap(colors) - dashed = math.wrap(dashed) - if xlabel is None: - xlabel = 'Iterations' if x == 'steps' else 'Time (s)' - - shape = (scene.shape & names.shape) - batches = shape.without(reduce).without(additional_reduce) - - cycle = list(plt.rcParams['axes.prop_cycle'].by_key()['color']) - fig, axes = plt.subplots(batches.only(down).volume, batches.without(down).volume, figsize=size) - MATPLOTLIB.current_figure = fig - axes = axes if isinstance(axes, numpy.ndarray) else np.array(axes) - - for b, axis in zip(math.concat_shapes(batches.only(down), batches.without(down)).meshgrid(), axes.flatten()): - assert isinstance(axis, plt.Axes) - names_equal = names[b].rank == 0 - paths_equal = scene.paths[b].rank == 0 - if titles is not None and titles is not False: - if isinstance(titles, str): - axis.set_title(titles) - elif isinstance(titles, Tensor): - axis.set_title(titles[b].native()) - elif names_equal: - axis.set_title(display_name(names[b].native())) - elif paths_equal: - axis.set_title(os.path.basename(scene.paths[b].native())) - if labels is not None: - curve_labels = labels - elif names_equal: - curve_labels = math.map(os.path.basename, scene.paths[b]) - elif paths_equal: - curve_labels = names[b] - else: - curve_labels = math.map(lambda p, n: f"{os.path.basename(p)} - {n}", scene.paths[b], names[b]) - - def single_plot(name, path, label, i, color, dashed_, smooth): - PHI_LOGGER.debug(f"Reading {os.path.join(path, f'log_{name}.txt')}") - curve = numpy.loadtxt(os.path.join(path, f"log_{name}.txt")) - if curve.ndim == 2: - x_values, values, *_ = curve.T - else: - values = curve - x_values = np.arange(len(values)) - if x == 'steps': - pass - else: - assert x == 'time', f"x must be 'steps' or 'time' but got {x}" - PHI_LOGGER.debug(f"Reading {os.path.join(path, 'log_step_time.txt')}") - _, x_values, *_ = numpy.loadtxt(os.path.join(path, "log_step_time.txt")).T - values = values[:len(x_values+1)] - x_values = np.cumsum(x_values[:len(values)-1]) - x_values = np.concatenate([[0.], x_values]) - if transform: - x_values, values = transform(np.stack([x_values, values])) - if color == 'default': - color = cycle[i] - try: - color = int(color) - except ValueError: - pass - if isinstance(color, Number): - color = cycle[int(color)] - PHI_LOGGER.debug(f"Plotting curve {label}") - if smooth > 1: - axis.plot(x_values, values, color=color, alpha=smooth_alpha, linewidth=1) - curve = np.stack([x_values, values], -1) - axis.plot(*smooth_uniform_curve(curve, smooth).T, *(['--'] if dashed_ else []), color=color, linewidth=smooth_linewidth, label=label) - else: - axis.plot(x_values, values, *(['--'] if dashed_ else []), color=color, linewidth=1, label=label) - if grid: - if isinstance(grid, dict): - axis.grid(**grid) - else: - grid_axis = 'both' if 'x' in grid and 'y' in grid else grid - axis.grid(which='both', axis=grid_axis, linestyle='--', linewidth=size[1] * 0.3) - if 'x' in log_scale: - axis.set_xscale('log') - if 'y' in log_scale: - axis.set_yscale('log') - if xlim: - axis.set_xlim(xlim) - if ylim: - axis.set_ylim(ylim) - if xlabel: - axis.set_xlabel(xlabel) - if ylabel: - axis.set_ylabel(ylabel) - return name - - math.map(single_plot, names[b], scene.paths[b], curve_labels, math.range_tensor(shape.after_gather(b)), colors, dashed, smooth) - if legend: - axis.legend(loc=legend) - # Final touches - if tight_layout: - plt.tight_layout() - return fig - - def savefig(filename: str, transparent=True): plt.savefig(filename, transparent=transparent) + + +MATPLOTLIB = MatplotlibPlots() +MATPLOTLIB.recipes.extend([ + LinePlot(), + Heatmap2D(), + VectorField2D(), + VectorField3D(), + Heatmap3D(), + VectorCloud2D(), + PointCloud2D(), + PointCloud3D(), + ]) diff --git a/phi/vis/_matplotlib/_scalars.py b/phi/vis/_matplotlib/_scalars.py new file mode 100644 index 000000000..7c8dc1987 --- /dev/null +++ b/phi/vis/_matplotlib/_scalars.py @@ -0,0 +1,178 @@ +import os +from numbers import Number +from typing import Callable + +import matplotlib.pyplot as plt +import numpy +import numpy as np + +from phi import math +from phi.field import Scene +from phi.field._scene import _str +from phi.math import Tensor, batch +from phi.math.backend import PHI_LOGGER +from phi.vis._plot_util import smooth_uniform_curve +from phi.vis._vis_base import display_name +from ._matplotlib_plots import MATPLOTLIB + + +def plot_scalars(scene: str or tuple or list or Scene or math.Tensor, + names: str or tuple or list or math.Tensor = None, + reduce: str or tuple or list or math.Shape = 'names', + down='', + smooth=1, + smooth_alpha=0.2, + smooth_linewidth=2., + size=(8, 6), + transform: Callable = None, + tight_layout=True, + grid: str or dict = 'y', + log_scale='', + legend='upper right', + x='steps', + xlim=None, + ylim=None, + titles=True, + labels: math.Tensor = None, + xlabel: str = None, + ylabel: str = None, + colors: math.Tensor = 'default', + dashed: math.Tensor = False): + """ + + Args: + scene: `str` or `Tensor`. Scene paths containing the data to plot. + names: Data files to plot for each scene. The file must be located inside the scene directory and have the name `log_.txt`. + reduce: Tensor dimensions along which all curves are plotted in the same diagram. + down: Tensor dimensions along which diagrams are ordered top-to-bottom instead of left-to-right. + smooth: `int` or `Tensor`. Number of data points to average, -1 for all. + smooth_alpha: Opacity of the non-smoothed curves under the smoothed curves. + smooth_linewidth: Line width of the smoothed curves. + size: Figure size in inches. + transform: Function `T(x,y) -> (x,y)` transforming the curves. + tight_layout: + grid: + log_scale: + legend: + x: + xlim: + ylim: + titles: + labels: + xlabel: + ylabel: + colors: Line colors as `str`, `int` or `Tensor`. Integers are interpreted as indices of the default color list. + + Returns: + MatPlotLib [figure](https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure) + """ + scene = Scene.at(scene) + additional_reduce = () + if names is None: + first_path = next(iter(math.flatten(scene.paths))) + names = [_str(n) for n in os.listdir(first_path)] + names = [n[4:-4] for n in names if n.endswith('.txt') and n.startswith('log_')] + names = math.wrap(names, batch('names')) + additional_reduce = ['names'] + elif isinstance(names, str): + names = math.wrap(names) + elif isinstance(names, (tuple, list)): + names = math.wrap(names, batch('names')) + else: + assert isinstance(names, math.Tensor), f"Invalid argument 'names': {type(names)}" + colors = math.wrap(colors) + dashed = math.wrap(dashed) + if xlabel is None: + xlabel = 'Iterations' if x == 'steps' else 'Time (s)' + + shape = (scene.shape & names.shape) + batches = shape.without(reduce).without(additional_reduce) + + cycle = list(plt.rcParams['axes.prop_cycle'].by_key()['color']) + fig, axes = plt.subplots(batches.only(down).volume, batches.without(down).volume, figsize=size) + MATPLOTLIB.current_figure = fig + axes = axes if isinstance(axes, numpy.ndarray) else np.array(axes) + + for b, axis in zip(math.concat_shapes(batches.only(down), batches.without(down)).meshgrid(), axes.flatten()): + assert isinstance(axis, plt.Axes) + names_equal = names[b].rank == 0 + paths_equal = scene.paths[b].rank == 0 + if titles is not None and titles is not False: + if isinstance(titles, str): + axis.set_title(titles) + elif isinstance(titles, Tensor): + axis.set_title(titles[b].native()) + elif names_equal: + axis.set_title(display_name(names[b].native())) + elif paths_equal: + axis.set_title(os.path.basename(scene.paths[b].native())) + if labels is not None: + curve_labels = labels + elif names_equal: + curve_labels = math.map(os.path.basename, scene.paths[b]) + elif paths_equal: + curve_labels = names[b] + else: + curve_labels = math.map(lambda p, n: f"{os.path.basename(p)} - {n}", scene.paths[b], names[b]) + + def single_plot(name, path, label, i, color, dashed_, smooth): + PHI_LOGGER.debug(f"Reading {os.path.join(path, f'log_{name}.txt')}") + curve = numpy.loadtxt(os.path.join(path, f"log_{name}.txt")) + if curve.ndim == 2: + x_values, values, *_ = curve.T + else: + values = curve + x_values = np.arange(len(values)) + if x == 'steps': + pass + else: + assert x == 'time', f"x must be 'steps' or 'time' but got {x}" + PHI_LOGGER.debug(f"Reading {os.path.join(path, 'log_step_time.txt')}") + _, x_values, *_ = numpy.loadtxt(os.path.join(path, "log_step_time.txt")).T + values = values[:len(x_values+1)] + x_values = np.cumsum(x_values[:len(values)-1]) + x_values = np.concatenate([[0.], x_values]) + if transform: + x_values, values = transform(np.stack([x_values, values])) + if color == 'default': + color = cycle[i] + try: + color = int(color) + except ValueError: + pass + if isinstance(color, Number): + color = cycle[int(color)] + PHI_LOGGER.debug(f"Plotting curve {label}") + if smooth > 1: + axis.plot(x_values, values, color=color, alpha=smooth_alpha, linewidth=1) + curve = np.stack([x_values, values], -1) + axis.plot(*smooth_uniform_curve(curve, smooth).T, *(['--'] if dashed_ else []), color=color, linewidth=smooth_linewidth, label=label) + else: + axis.plot(x_values, values, *(['--'] if dashed_ else []), color=color, linewidth=1, label=label) + if grid: + if isinstance(grid, dict): + axis.grid(**grid) + else: + grid_axis = 'both' if 'x' in grid and 'y' in grid else grid + axis.grid(which='both', axis=grid_axis, linestyle='--', linewidth=size[1] * 0.3) + if 'x' in log_scale: + axis.set_xscale('log') + if 'y' in log_scale: + axis.set_yscale('log') + if xlim: + axis.set_xlim(xlim) + if ylim: + axis.set_ylim(ylim) + if xlabel: + axis.set_xlabel(xlabel) + if ylabel: + axis.set_ylabel(ylabel) + return name + + math.map(single_plot, names[b], scene.paths[b], curve_labels, math.range_tensor(shape.after_gather(b)), colors, dashed, smooth) + if legend: + axis.legend(loc=legend) + # Final touches + if tight_layout: + plt.tight_layout() + return fig diff --git a/phi/vis/_vis.py b/phi/vis/_vis.py index 3ea73bb88..5f32ba734 100644 --- a/phi/vis/_vis.py +++ b/phi/vis/_vis.py @@ -276,11 +276,11 @@ def plot(*fields: SampledField or Tensor or Layout, title: str or Tensor = None, size=(12, 5), same_scale=True, - log_dims: str or tuple or list or Shape='', + log_dims: str or tuple or list or Shape = '', show_color_bar=True, + color=None, frame_time=100, - repeat=True, - **plt_args): + repeat=True): """ Creates one or multiple figures and sub-figures and plots the given fields. @@ -300,6 +300,7 @@ def plot(*fields: SampledField or Tensor or Layout, Can be given as a comma-separated `str`, a sequence of dimension names or a `Shape`. Use `'_'` to scale unnamed axes logarithmically, e.g. the y-axis of scalar functions. show_color_bar: Whether to display color bars for heat maps. + color: Tensor for line / marker colors. animate: Time dimension to animate. If not present in the data, will produce a regular plot instead. frame_time: Interval between frames in the animation. @@ -316,6 +317,7 @@ def plot(*fields: SampledField or Tensor or Layout, animate = fig_shape.only(animate) fig_shape = fig_shape.without(animate) plots = default_plots() if lib is None else get_plots(lib) + # --- Process arguments --- if same_scale: if any([f.values.dtype.kind == complex for l in positioning.values() for f in l]): min_val = 0 @@ -334,6 +336,8 @@ def plot(*fields: SampledField or Tensor or Layout, assert title is None, f"title must be a str or Tensor but got {title}" title = {pos: ", ".join([i for dim, i in index.items() if isinstance(i, str)]) for pos, index in indices.items()} log_dims = parse_dim_order(log_dims) or () + color = math.wrap(color) + # --- animate or plot --- if fig_shape.volume == 1: figure, axes = plots.create_figure(size, nrows, ncols, subplots, title, log_dims) if animate: @@ -341,7 +345,8 @@ def plot_frame(frame: int): for pos, fields in positioning.items(): for f in fields: f = f[{animate.name: frame}] - plots.plot(f, figure, axes[pos], subplots[pos], min_val=min_val, max_val=max_val, show_color_bar=show_color_bar, **plt_args) + plots.plot(f, figure, axes[pos], subplots[pos], min_val, max_val, show_color_bar, color) + plots.finalize(figure) anim = plots.animate(figure, animate.size, plot_frame, frame_time, repeat) LAST_FIGURE[0] = anim plots.close(figure) @@ -349,7 +354,8 @@ def plot_frame(frame: int): else: for pos, fields in positioning.items(): for f in fields: - plots.plot(f, figure, axes[pos], subplots[pos], min_val=min_val, max_val=max_val, show_color_bar=show_color_bar, **plt_args) + plots.plot(f, figure, axes[pos], subplots[pos], min_val, max_val, show_color_bar, color) + plots.finalize(figure) LAST_FIGURE[0] = figure return layout(figure) else: diff --git a/phi/vis/_vis_base.py b/phi/vis/_vis_base.py index 6cfe925dc..2afa8a711 100644 --- a/phi/vis/_vis_base.py +++ b/phi/vis/_vis_base.py @@ -325,6 +325,7 @@ def __init__(self, name: str, figure_classes: tuple or list): self.name = name self.figure_classes = tuple(figure_classes) self.current_figure = None + self.recipes = [] def __repr__(self): return self.name @@ -355,30 +356,49 @@ def create_figure(self, figure: Native figure object subfigures: Native sub-figures by subplot location. """ - raise NotImplementedError() + raise NotImplementedError def animate(self, fig, frames: int, plot_frame_function: Callable, interval: float, repeat: bool): - raise NotImplementedError() + raise NotImplementedError - def plot(self, - data: SampledField, - figure, - subplot, - space: Box, - min_val: float = None, - max_val: float = None, - show_color_bar: bool = True, - **plt_args): - raise NotImplementedError() + def finalize(self, figure): + raise NotImplementedError def close(self, figure): - raise NotImplementedError() + raise NotImplementedError def show(self, figure): - raise NotImplementedError() + raise NotImplementedError def save(self, figure, path: str, dpi: float): - raise NotImplementedError() + raise NotImplementedError + + def plot(self, data, figure, subplot, space, *args, **kwargs): + for recipe in self.recipes: + if recipe.can_plot(data, space): + recipe.plot(data, figure, subplot, space, *args, **kwargs) + return + raise NotImplementedError(f"No {self.name} recipe found for {data}. Recipes: {self.recipes}") + + +class Recipe: + + def can_plot(self, data: SampledField, space: Box) -> bool: + raise NotImplementedError + + def plot(self, + data: SampledField, + figure, + subplot, + space: Box, + min_val: float, + max_val: float, + show_color_bar: bool, + color: Tensor): + raise NotImplementedError + + def __repr__(self): + return self.__class__.__name__ class GuiInterrupt(KeyboardInterrupt): @@ -404,6 +424,19 @@ def display_name(python_name): return text +def index_label(idx: dict) -> str or None: + if len(idx) == 0: + return None + if len(idx) == 1: + return str(next(iter(idx.values()))) + else: + number_unlabelled_dims = len([1 for k, v in idx.items() if isinstance(v, int)]) + if number_unlabelled_dims <= 1: + return " ".join(idx.values()) + else: + return ", ".join(f'{k}={v}' for k, v in idx.items()) + + def select_channel(value: SampledField or Tensor or tuple or list, channel: str or None): if isinstance(value, (tuple, list)): return [select_channel(v, channel) for v in value] diff --git a/tests/commit/vis/test__plots.py b/tests/commit/vis/test__plots.py index a4edc15a7..45166e95d 100644 --- a/tests/commit/vis/test__plots.py +++ b/tests/commit/vis/test__plots.py @@ -66,10 +66,10 @@ def test_plot_spheres_2d(self): self._test_plot(spheres) def test_plot_point_cloud_2d(self): - spheres = PointCloud(Sphere(wrap([(.2, .4), (.9, .8), (.7, .8)], instance('points'), channel(vector='x,y')), radius=.1), color='#994444') - cells = PointCloud(geom.pack_dims(CenteredGrid(0, 0, x=3, y=3, bounds=Box['x,y', .4:.6, .2:.4]).elements, 'x,y', instance('points')), color='#000000') + spheres = PointCloud(Sphere(wrap([(.2, .4), (.9, .8), (.7, .8)], instance('points'), channel(vector='x,y')), radius=.1)) + cells = PointCloud(geom.pack_dims(CenteredGrid(0, 0, x=3, y=3, bounds=Box['x,y', .4:.6, .2:.4]).elements, 'x,y', instance('points'))) cloud = field.stack([spheres, cells], instance('stack')) - self._test_plot(cloud) + self._test_plot(cloud, color=wrap(['#994444', '#000000'], instance('stack'))) def test_plot_point_cloud_2d_large(self): spheres = PointCloud(Sphere(wrap([(2, 4), (9, 8), (7, 8)], instance('points'), channel(vector='x,y')), radius=1)) @@ -129,7 +129,7 @@ def test_plot_arbitrary_lines(self): points = stack([points, points + (0, -1)], instance('disconnected')) points = stack([points, points * (1, -1)], channel('categories')) try: - self._test_plot(PointCloud(points, color=wrap([0, 1], channel('categories')))) + self._test_plot(PointCloud(points), color=wrap([0, 1], channel('categories'))) except NotImplementedError: pass From d21b03d759d215c1743e7fc4e4cc6b252f1128ab Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 4 Feb 2023 19:02:40 +0100 Subject: [PATCH 099/170] [field] Add mask() * Deprecate Hard/SoftGeometryMask * Add field.resample() * Update demos and docs * Add extrapolation.remove_constant_offset --- demos/differentiate_pressure.py | 4 +- demos/flip_liquid.py | 12 ++-- demos/fluid_logo.py | 2 +- demos/moving_obstacle.py | 4 +- demos/point_cloud.py | 10 ++-- demos/smoke_embedded_mesh.py | 2 +- demos/smoke_plume.py | 2 +- demos/smoke_plume_3d.py | 2 +- demos/smoke_plume_advanced.py | 4 +- docs/Batched_Obstacles.ipynb | 2 +- docs/Fields.md | 3 - docs/Geometry.md | 6 +- docs/Staggered_Grids.ipynb | 4 +- phi/field/__init__.py | 4 +- phi/field/_field.py | 81 +++++++++++++++++--------- phi/field/_field_math.py | 19 ++++++ phi/field/_grid.py | 5 +- phi/field/_mask.py | 9 ++- phi/field/_point_cloud.py | 52 +++++++++-------- phi/flow.py | 2 +- phi/geom/_geom.py | 6 +- phi/math/_magic_ops.py | 3 +- phi/math/extrapolation.py | 20 +++++++ phi/physics/_boundaries.py | 6 +- phi/physics/fluid.py | 6 +- tests/commit/field/test__field_math.py | 10 +++- tests/commit/physics/test_flip.py | 8 +-- tests/commit/vis/test__plots.py | 6 +- tests/release/test_flip.py | 8 +-- 29 files changed, 189 insertions(+), 113 deletions(-) diff --git a/demos/differentiate_pressure.py b/demos/differentiate_pressure.py index 63e7490ab..9398b47f9 100644 --- a/demos/differentiate_pressure.py +++ b/demos/differentiate_pressure.py @@ -10,8 +10,8 @@ DOMAIN = dict(x=80, y=64) -LEFT = StaggeredGrid(HardGeometryMask(Box(x=(-INF, 40), y=None)), 0, **DOMAIN) -RIGHT = StaggeredGrid(HardGeometryMask(Box(x=(40, INF), y=None)), extrapolation.ZERO, **DOMAIN) +LEFT = StaggeredGrid(Box(x=(-INF, 40), y=None), 0, **DOMAIN) +RIGHT = StaggeredGrid(Box(x=(40, INF), y=None), extrapolation.ZERO, **DOMAIN) TARGET = RIGHT * StaggeredGrid(lambda x: math.exp(-0.5 * math.vec_squared(x - (50, 10), 'vector') / 32**2), extrapolation.ZERO, **DOMAIN) * (0, 2) diff --git a/demos/flip_liquid.py b/demos/flip_liquid.py index 3dda354c0..2fefafb38 100644 --- a/demos/flip_liquid.py +++ b/demos/flip_liquid.py @@ -12,7 +12,7 @@ DT = .2 OBSTACLE = Box(x=(1, 25), y=(30, 33)).rotated(-20) ACCESSIBLE_CELLS = CenteredGrid(~OBSTACLE, 0, x=64, y=64) -_OBSTACLE_POINTS = PointCloud(Cuboid(field.support(1 - ACCESSIBLE_CELLS, 'points'), x=2, y=2), color='#000000', bounds=ACCESSIBLE_CELLS.bounds) +_OBSTACLE_POINTS = PointCloud(Cuboid(field.support(1 - ACCESSIBLE_CELLS, 'points'), x=2, y=2), bounds=ACCESSIBLE_CELLS.bounds) particles = distribute_points(union(Box(x=(15, 30), y=(50, 60)), Box(x=None, y=(-INF, 5))), x=64, y=64) * (0, 0) scene = vis.overlay(particles, _OBSTACLE_POINTS) # only for plotting @@ -21,13 +21,13 @@ # @jit_compile def step(particles): # --- Grid Operations --- - velocity = prev_velocity = field.finite_fill(particles.at(StaggeredGrid(0, 0, x=64, y=64), outside_handling='clamp')) - occupied = CenteredGrid(particles.mask(), velocity.extrapolation.spatial_gradient(), velocity.bounds, velocity.resolution) + velocity = prev_velocity = field.finite_fill(resample(particles, StaggeredGrid(0, 0, x=64, y=64), scatter=True, outside_handling='clamp')) + occupied = resample(field.mask(particles), CenteredGrid(0, velocity.extrapolation.spatial_gradient(), velocity.bounds, velocity.resolution), scatter=True) velocity, pressure = fluid.make_incompressible(velocity + GRAVITY * DT, [OBSTACLE], active=occupied) # --- Particle Operations --- - particles += (velocity - prev_velocity).at(particles) # FLIP update - # particles = velocity.at(particles) # PIC update - particles = advect.points(particles, velocity * ~OBSTACLE, DT, advect.finite_rk4) + particles += resample(velocity - prev_velocity, particles) # FLIP update + # particles = resample(velocity, particles) # PIC update + particles = advect.points(particles, velocity * mask(~OBSTACLE), DT, advect.finite_rk4) particles = fluid.boundary_push(particles, [OBSTACLE, ~particles.bounds]) return particles, velocity, pressure diff --git a/demos/fluid_logo.py b/demos/fluid_logo.py index c299ae48a..a6b86102b 100644 --- a/demos/fluid_logo.py +++ b/demos/fluid_logo.py @@ -10,7 +10,7 @@ OBSTACLE_GEOMETRIES = [Box(x=(15 + x * 7, 15 + (x + 1) * 7), y=(41, 83)) for x in range(1, 10, 2)] + [Box['x,y', 43:50, 41:48], Box['x,y', 15:43, 83:90], Box['x,y', 50:85, 83:90]] OBSTACLE = Obstacle(union(OBSTACLE_GEOMETRIES)) -OBSTACLE_MASK = HardGeometryMask(OBSTACLE.geometry).at(CenteredGrid(0, extrapolation.BOUNDARY, **DOMAIN)) +OBSTACLE_MASK = resample(OBSTACLE.geometry, CenteredGrid(0, extrapolation.BOUNDARY, **DOMAIN)) INFLOW = CenteredGrid(Box['x,y', 14:21, 6:10], extrapolation.BOUNDARY, **DOMAIN) + \ CenteredGrid(Box['x,y', 81:88, 6:10], extrapolation.BOUNDARY, **DOMAIN) * 0.9 + \ diff --git a/demos/moving_obstacle.py b/demos/moving_obstacle.py index 1ec33cb79..b91a6a0ba 100644 --- a/demos/moving_obstacle.py +++ b/demos/moving_obstacle.py @@ -15,11 +15,11 @@ def move_obstacle(obs: Obstacle): obstacle = Obstacle(Box(x=(5, 11), y=(10, 16)), velocity=[1., 0], angular_velocity=tensor(0,)) velocity = StaggeredGrid(0, extrapolation.ZERO, **DOMAIN) -obstacle_mask = CenteredGrid(HardGeometryMask(obstacle.geometry), extrapolation.BOUNDARY, **DOMAIN) +obstacle_mask = CenteredGrid(obstacle.geometry, extrapolation.BOUNDARY, **DOMAIN) pressure = None for _ in view(velocity, obstacle_mask, play=True, namespace=globals()).range(): obstacle = move_obstacle(obstacle) velocity = advect.mac_cormack(velocity, velocity, DT) velocity, pressure = fluid.make_incompressible(velocity, (obstacle,)) - obstacle_mask = HardGeometryMask(obstacle.geometry).at(pressure) + obstacle_mask = resample(obstacle.geometry, pressure) diff --git a/demos/point_cloud.py b/demos/point_cloud.py index bdbf6061f..c06c716f2 100644 --- a/demos/point_cloud.py +++ b/demos/point_cloud.py @@ -4,8 +4,8 @@ from phi.flow import * -points1 = PointCloud(vec(x=1, y=1), color='#ba0a04') -points2 = PointCloud(vec(x=20, y=20), color='#ba0a04') +points1 = PointCloud(vec(x=1, y=1)) +points2 = PointCloud(vec(x=20, y=20)) # points = points1 & points2 points = field.stack([points1, points2], instance('points')) @@ -15,8 +15,8 @@ points = advect.advect(points, points * (-1, 1), -5) # Euler # Grid sampling -scattered_data = field.sample(points, velocity.elements) -scattered_grid = points.at(velocity) -scattered_sgrid = points.at(StaggeredGrid(0, 0, velocity.bounds, velocity.resolution)) +scattered_data = field.sample(points, velocity.elements, scatter=True) +scattered_grid = points.at(velocity, scatter=True) +scattered_sgrid = resample(points, StaggeredGrid(0, 0, velocity.bounds, velocity.resolution), scatter=True) view(namespace=globals()) diff --git a/demos/smoke_embedded_mesh.py b/demos/smoke_embedded_mesh.py index 3958c8d66..f9395f06d 100644 --- a/demos/smoke_embedded_mesh.py +++ b/demos/smoke_embedded_mesh.py @@ -5,7 +5,7 @@ smoke = CenteredGrid(0, extrapolation.BOUNDARY, x=200, y=200, bounds=Box(x=100, y=100)) OBSTACLE = Obstacle(Sphere(x=50, y=60, radius=5)) -INFLOW = 0.2 * CenteredGrid(SoftGeometryMask(Sphere(x=50, y=9.5, radius=5)), 0, smoke.bounds, smoke.resolution) +INFLOW = 0.2 * resample(Sphere(x=50, y=9.5, radius=5), CenteredGrid(0, 0, smoke.bounds, smoke.resolution), soft=True) pressure = None diff --git a/demos/smoke_plume.py b/demos/smoke_plume.py index 17dc3f326..9d5ba6571 100644 --- a/demos/smoke_plume.py +++ b/demos/smoke_plume.py @@ -10,7 +10,7 @@ velocity = StaggeredGrid((0, 0), 0, x=64, y=64, bounds=Box(x=100, y=100)) # or CenteredGrid(...) smoke = CenteredGrid(0, extrapolation.BOUNDARY, x=200, y=200, bounds=Box(x=100, y=100)) -INFLOW = 0.2 * CenteredGrid(SoftGeometryMask(Sphere(x=50, y=9.5, radius=5)), 0, smoke.bounds, smoke.resolution) +INFLOW = 0.2 * resample(Sphere(x=50, y=9.5, radius=5), smoke, soft=True) pressure = None diff --git a/demos/smoke_plume_3d.py b/demos/smoke_plume_3d.py index 7f0a1385e..2d6d067f0 100644 --- a/demos/smoke_plume_3d.py +++ b/demos/smoke_plume_3d.py @@ -10,7 +10,7 @@ velocity = StaggeredGrid((0, 0, 0), extrapolation.ZERO, x=32, y=32, z=32, bounds=Box(x=100, y=100, z=100)) # or CenteredGrid(...) smoke = CenteredGrid(0, extrapolation.BOUNDARY, x=32, y=32, z=32, bounds=Box(x=100, y=100, z=100)) -INFLOW = 0.2 * CenteredGrid(SoftGeometryMask(Sphere(x=50, y=50, z=10, radius=5)), 0, smoke.bounds, smoke.resolution) +INFLOW = 0.2 * resample(Sphere(x=50, y=50, z=10, radius=5), smoke, soft=True) pressure = None diff --git a/demos/smoke_plume_advanced.py b/demos/smoke_plume_advanced.py index 5645fa471..8b1e15be9 100644 --- a/demos/smoke_plume_advanced.py +++ b/demos/smoke_plume_advanced.py @@ -22,8 +22,8 @@ viewer = view(smoke, velocity, namespace=globals(), play=False) for _ in viewer.range(warmup=1): # Resize grids if needed - inflow = SoftGeometryMask(INFLOW).at(CenteredGrid(0, smoke.extrapolation, x=smoke_res ** 2, y=smoke_res ** 2, bounds=BOUNDS)) - smoke = smoke.at(inflow) + inflow = resample(INFLOW, CenteredGrid(0, smoke.extrapolation, x=smoke_res ** 2, y=smoke_res ** 2, bounds=BOUNDS), soft=True) + smoke = resample(smoke, inflow) velocity = velocity.at(StaggeredGrid(0, velocity.extrapolation, x=v_res ** 2, y=v_res ** 2, bounds=BOUNDS)) # Physics step smoke = advect.mac_cormack(smoke, velocity, 1) + inflow diff --git a/docs/Batched_Obstacles.ipynb b/docs/Batched_Obstacles.ipynb index de3f77640..ba9cebe1c 100644 --- a/docs/Batched_Obstacles.ipynb +++ b/docs/Batched_Obstacles.ipynb @@ -106,7 +106,7 @@ "\n", "velocity = StaggeredGrid((0, 0), 0, x=64, y=64, bounds=Box(x=100, y=100))\n", "smoke = CenteredGrid(0, extrapolation.BOUNDARY, x=200, y=200, bounds=Box(x=100, y=100))\n", - "INFLOW = 0.2 * CenteredGrid(SoftGeometryMask(Sphere(x=50, y=9.5, radius=5)), 0, smoke.bounds, smoke.resolution)\n", + "INFLOW = 0.2 * resample(Sphere(x=50, y=9.5, radius=5), smoke, soft=True)\n", "pressure = None\n", "\n", "for _ in range(10):\n", diff --git a/docs/Fields.md b/docs/Fields.md index 3991fbf5d..5ecf60fb1 100644 --- a/docs/Fields.md +++ b/docs/Fields.md @@ -60,9 +60,6 @@ This results in the `values` having different shapes for the different vector co [`PointCloud`](phi/field/#phi.field.PointCloud) is a set of points or finite elements, each associated with a value. -[`SoftGeometryMask`](phi/field/#phi.field.SoftGeometryMask) / [`HardGeometryMask`](phi/field/#phi.field.HardGeometryMask): -1 inside the geometry, 0 outside. - [`Noise`](phi/field/#phi.field.Noise) samples random fluctuations of certain sizes. Currently, it only supports resampling to grids. diff --git a/docs/Geometry.md b/docs/Geometry.md index e45300f3e..28d16ccd3 100644 --- a/docs/Geometry.md +++ b/docs/Geometry.md @@ -60,7 +60,7 @@ Stacking: `GeometryStack(geometries, axis)` allows the type of `Geometry` to var ## Integration with fields `Geometry` objects are not [Fields](./Fields.md). -However, some sampling operations like `CenteredGrid.sample()` also accept `Geometry` objects. +To get a direct `Field` representation from a `Geometry`, use `field.mask()`. +Geometries can be resampled to existing fields using `field.resample()`. +In these cases, the field takes the value `1` inside the geometry and `0` outside. -The classes `SoftGeometryMask` and `HardGeometryMask` represent fields that take the value `1` inside the geometry and `0` outside. -The hard version always returns 0 or 1 while the soft version returns continuous values when volume-sampled. diff --git a/docs/Staggered_Grids.ipynb b/docs/Staggered_Grids.ipynb index ca8c192bd..b9c8338c7 100644 --- a/docs/Staggered_Grids.ipynb +++ b/docs/Staggered_Grids.ipynb @@ -104,8 +104,8 @@ "grid = StaggeredGrid(Noise(), **domain) # sample analytic field\n", "grid = StaggeredGrid(grid, **domain) # resample existing field\n", "grid = StaggeredGrid(lambda x: math.exp(-x), **domain) # function value(location)\n", - "grid = StaggeredGrid(Sphere(x=0, y=0, radius=1), **domain) # no anti-aliasing\n", - "grid = StaggeredGrid(SoftGeometryMask(Sphere(x=0, y=0, radius=1)), **domain) # with anti-aliasing" + "grid = resample(Sphere(x=0, y=0, radius=1), StaggeredGrid(0, **domain)) # no anti-aliasing\n", + "grid = resample(Sphere(x=0, y=0, radius=1), StaggeredGrid(0, **domain), soft=True) # with anti-aliasing" ], "metadata": { "collapsed": false, diff --git a/phi/field/__init__.py b/phi/field/__init__.py index 4bdea43b5..9ff57e8f3 100644 --- a/phi/field/__init__.py +++ b/phi/field/__init__.py @@ -17,7 +17,7 @@ See the `phi.field` module documentation at https://tum-pbs.github.io/PhiFlow/Fields.html """ -from ._field import Field, SampledField, sample, reduce_sample, as_extrapolation +from ._field import Field, SampledField, sample, reduce_sample, resample, as_extrapolation from ._mask import HardGeometryMask, SoftGeometryMask as GeometryMask, SoftGeometryMask from ._grid import Grid, CenteredGrid, StaggeredGrid from ._point_cloud import PointCloud @@ -45,7 +45,7 @@ native_call, integrate, pack_dims, - support, + support, mask, ) from ._field_io import write, read from ._scene import Scene diff --git a/phi/field/_field.py b/phi/field/_field.py index 2caf2352f..62d40114a 100644 --- a/phi/field/_field.py +++ b/phi/field/_field.py @@ -61,32 +61,15 @@ def _sample(self, geometry: Geometry, **kwargs) -> math.Tensor: def at(self, representation: 'SampledField', keep_extrapolation=False, **kwargs) -> 'SampledFieldType': """ - Samples this field at the sample points of `representation`. - The result will approximate the values of this field on the data structure of `representation`. - - Unlike `Field.sample()`, this method returns a `Field` object, not a `Tensor`. - - Operator alias: - `self @ representation`. - - See Also: - `sample()`, `reduce_sample()`, [Resampling overview](https://tum-pbs.github.io/PhiFlow/Fields.html#resampling-fields). + Short for `resample(self, representation)` - Args: - representation: Field object defining the sample points. The values of `representation` are ignored. - keep_extrapolation: Only available if `self` is a `SampledField`. - If True, the resampled field will inherit the extrapolation from `self` instead of `representation`. - This can result in non-compatible value tensors for staggered grids where the tensor size depends on the extrapolation type. - **kwargs: Sampling arguments, e.g. to specify the numerical scheme. - By default, linear interpolation is used. - Grids also support 6th order implicit sampling at mid-points. + See Also + `resample()`. Returns: Field object of same type as `representation` """ - resampled = reduce_sample(self, representation.elements, **kwargs) - extrap = self.extrapolation if isinstance(self, SampledField) and keep_extrapolation else representation.extrapolation - return representation._op1(lambda old: extrap if isinstance(old, math.extrapolation.Extrapolation) else resampled) + return resample(self, representation, keep_extrapolation, **kwargs) def __matmul__(self, other: 'SampledField'): # values @ representation """ @@ -163,7 +146,8 @@ class SampledField(Field): Base class for fields that are sampled at specific locations such as grids or point clouds. """ - def __init__(self, elements: Union[Geometry, Tensor], + def __init__(self, + elements: Union[Geometry, Tensor], values: Tensor, extrapolation: float or Extrapolation or Field or None, bounds: Box or None): @@ -273,6 +257,12 @@ def __pow__(self, power, modulo=None): def __neg__(self): return self._op1(lambda x: -x) + def __eq__(self, other): + return self._op2(other, lambda x, y: x == y) + + def __ne__(self, other): + return self._op2(other, lambda x, y: x != y) + def __gt__(self, other): return self._op2(other, lambda x, y: x > y) @@ -304,8 +294,7 @@ def _op1(self: 'SampledFieldType', operator: Callable) -> 'SampledFieldType': def _op2(self, other, operator) -> 'SampledField': if isinstance(other, Geometry): - from ._mask import HardGeometryMask - other = HardGeometryMask(other) + raise ValueError(f"Cannot combine {self.__class__.__name__} with a Geometry, got {type(other)}") if isinstance(other, Field): other_values = reduce_sample(other, self._elements) values = operator(self._values, other_values) @@ -320,7 +309,7 @@ def _op2(self, other, operator) -> 'SampledField': return self.with_values(values) -def sample(field: Field, +def sample(field: Field or Geometry, geometry: Geometry or SampledField or Tensor, **kwargs) -> math.Tensor: """ @@ -348,6 +337,9 @@ def sample(field: Field, Sampled values as a `phi.math.Tensor` """ geometry = _get_geometry(geometry) + if isinstance(field, Geometry): + from ._field_math import mask + field = mask(field) geom_ch = channel(geometry).without('vector') assert all(dim not in field.shape for dim in geom_ch) if isinstance(field, SampledField) and field.elements.shallow_equals(geometry) and not geom_ch: @@ -359,7 +351,7 @@ def sample(field: Field, return field._sample(geometry, **kwargs) -def reduce_sample(field: Field, +def reduce_sample(field: Field or Geometry, geometry: Geometry or SampledField or Tensor, dim=channel('vector'), **kwargs) -> math.Tensor: @@ -384,6 +376,9 @@ def reduce_sample(field: Field, Sampled values as a `phi.math.Tensor` """ geometry = _get_geometry(geometry) + if isinstance(field, Geometry): + from ._field_math import mask + field = mask(field) if isinstance(field, SampledField) and field.elements.shallow_equals(geometry): return field.values if channel(geometry).without('vector'): # Reduce this dimension @@ -401,6 +396,40 @@ def reduce_sample(field: Field, return field._sample(geometry, **kwargs) +def resample(obj: Union[Field, Geometry, Tensor, float], representation: SampledField, keep_extrapolation=False, **kwargs): + """ + Samples this field at the sample points of `representation`. + The result will approximate the values of this field on the data structure of `representation`. + + Unlike `Field.sample()`, this method returns a `Field` object, not a `Tensor`. + + Operator alias: + `self @ representation`. + + See Also: + `sample()`, `reduce_sample()`, [Resampling overview](https://tum-pbs.github.io/PhiFlow/Fields.html#resampling-fields). + + Args: + obj: Object containing values to resample. + This can be + representation: Field object defining the sample points. The values of `representation` are ignored. + keep_extrapolation: Only available if `self` is a `SampledField`. + If True, the resampled field will inherit the extrapolation from `self` instead of `representation`. + This can result in non-compatible value tensors for staggered grids where the tensor size depends on the extrapolation type. + **kwargs: Sampling arguments, e.g. to specify the numerical scheme. + By default, linear interpolation is used. + Grids also support 6th order implicit sampling at mid-points. + + Returns: + Field object of same type as `representation` + """ + if not isinstance(obj, (Field, Geometry)): + return representation.with_values(obj) + resampled = reduce_sample(obj, representation.elements, **kwargs) + extrap = obj.extrapolation if isinstance(obj, SampledField) and keep_extrapolation else representation.extrapolation + return representation.with_values(resampled).with_extrapolation(extrap) + + def _get_geometry(geometry): if isinstance(geometry, SampledField): return geometry.elements diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index b4125a621..500c91629 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -845,3 +845,22 @@ def support(field: SampledField, list_dim: Shape or str = instance('nonzero')) - `Tensor` with shape `(list_dim, vector)` """ return field.points[math.nonzero(field.values, list_dim=list_dim)] + + +def mask(obj: SampledFieldType or Geometry) -> SampledFieldType: + """ + Returns a `Field` that masks the inside (or non-zero values when `obj` is a grid) of a physical object. + The mask takes the value 1 inside the object and 0 outside. + For `CenteredGrid` and `StaggeredGrid`, the mask labels non-zero non-NaN entries as 1 and all other values as 0 + + Returns: + `Grid` type or `PointCloud` + """ + if isinstance(obj, PointCloud): + return PointCloud(obj.elements, 1, math.extrapolation.remove_constant_offset(obj.extrapolation), bounds=obj.bounds) + elif isinstance(obj, Geometry): + return PointCloud(obj, 1, 0) + elif isinstance(obj, CenteredGrid): + return math.cast(obj != 0, int) + else: + raise ValueError(obj) diff --git a/phi/field/_grid.py b/phi/field/_grid.py index 335f9c17e..24499df88 100644 --- a/phi/field/_grid.py +++ b/phi/field/_grid.py @@ -4,7 +4,6 @@ from phi import math, geom from phi.geom import Box, Geometry, GridCell -from . import HardGeometryMask from ._embed import FieldEmbedding from ._field import SampledField, Field, sample, reduce_sample, as_extrapolation from ..geom._stack import GeometryStack @@ -196,7 +195,7 @@ def __init__(self, if isinstance(values, math.Tensor): values = math.expand(values, resolution) elif isinstance(values, Geometry): - values = reduce_sample(HardGeometryMask(values), elements) + values = reduce_sample(values, elements) elif isinstance(values, Field): values = reduce_sample(values, elements) elif callable(values): @@ -349,7 +348,7 @@ def __init__(self, else: # Keep dim order from data and check it matches resolution assert set(resolution_from_staggered_tensor(values, extrapolation)) == set(resolution), f"Failed to create StaggeredGrid: values {values.shape} do not match given resolution {resolution} for extrapolation {extrapolation}. See https://tum-pbs.github.io/PhiFlow/Staggered_Grids.html" elif isinstance(values, Geometry): - values = reduce_sample(HardGeometryMask(values), elements) + values = reduce_sample(values, elements) elif isinstance(values, Field): values = reduce_sample(values, elements) elif callable(values): diff --git a/phi/field/_mask.py b/phi/field/_mask.py index 4610a56d0..1270c2640 100644 --- a/phi/field/_mask.py +++ b/phi/field/_mask.py @@ -1,3 +1,5 @@ +import warnings + from phi import math from phi.geom import Geometry from ._field import Field @@ -6,11 +8,11 @@ class HardGeometryMask(Field): """ - Field that takes the value 1 inside a Geometry object and 0 outside. - For volume sampling, performs sampling at the center points. + Deprecated since version 1.3. Use `phi.field.mask()` or `phi.field.resample()` instead. """ def __init__(self, geometry: Geometry): + warnings.warn("HardGeometryMask and SoftGeometryMask are deprecated. Use field.mask or field.resample instead.", DeprecationWarning, stacklevel=2) assert isinstance(geometry, Geometry) self.geometry = geometry @@ -27,9 +29,10 @@ def __getitem__(self, item: dict): class SoftGeometryMask(HardGeometryMask): """ - When sampled given another geometry, the approximate overlap between the geometries is computed, allowing for fractional values between 0 and 1. + Deprecated since version 1.3. Use `phi.field.mask()` or `phi.field.resample()` instead. """ def __init__(self, geometry: Geometry, balance: Tensor or float = 0.5): + warnings.warn("HardGeometryMask and SoftGeometryMask are deprecated. Use field.mask or field.resample instead.", DeprecationWarning, stacklevel=2) super().__init__(geometry) self.balance = balance diff --git a/phi/field/_point_cloud.py b/phi/field/_point_cloud.py index ccada61bd..d4f02f00d 100644 --- a/phi/field/_point_cloud.py +++ b/phi/field/_point_cloud.py @@ -2,8 +2,8 @@ from typing import Any, Tuple, Union from phi import math -from phi.geom import Geometry, GridCell, Box -from ._field import SampledField +from phi.geom import Geometry, GridCell, Box, Point +from ._field import SampledField, resample from ..geom._stack import GeometryStack from ..math import Tensor, instance, Shape from ..math.extrapolation import Extrapolation, ConstantExtrapolation @@ -19,8 +19,18 @@ class PointCloud(SampledField): All points belonging to one example must be listed in the 'points' dimension. - Unlike with GeometryMask, the elements of a PointCloud are assumed to be small. - When sampling this field on a grid, scatter functions may be used. + Sampling arguments: + + soft: default=False. + If `True`, interpolates smoothly from 1 to 0 between the inside and outside of elements. + If `False`, only the center position of the new representation elements is checked against the point cloud elements. + scatter: default=False. + If `True`, scattering will be used to sample the point cloud onto grids. + Then, each element of the point cloud can only affect a single cell. + This is only recommended when the points are much smaller than the cells. + outside_handling: default='discard'. One of `discard`, `clamp`, `undefined`. + balance: default=0.5. Only used when `soft=True`. + See the description in `phi.geom.Geometry.approximate_fraction_inside()`. See the `phi.field` module documentation at https://tum-pbs.github.io/PhiFlow/Fields.html """ @@ -30,8 +40,7 @@ def __init__(self, values: Any = 1., extrapolation: Union[Extrapolation, float] = 0., add_overlapping=False, - bounds: Box = None, - color: Any = None): + bounds: Box = None): """ Args: elements: `Tensor` or `Geometry` object specifying the sample points and sizes @@ -42,8 +51,6 @@ def __init__(self, """ SampledField.__init__(self, elements, math.wrap(values), extrapolation, bounds) self._add_overlapping = add_overlapping - if color is not None: - warnings.warn("PointCloud.color is no longer in use. Use plot(data, color=...) instead.", SyntaxWarning) @property def shape(self): @@ -116,16 +123,21 @@ def bounds(self) -> Box: radius = math.max(self.elements.bounding_radius()) return Box(bounds.lower - radius, bounds.upper + radius) - def _sample(self, geometry: Geometry, outside_handling="discard", **kwargs) -> Tensor: + def _sample(self, geometry: Geometry, soft=False, scatter=False, outside_handling='discard', balance=0.5) -> Tensor: if geometry == self.elements: return self.values - elif isinstance(geometry, GridCell): - return self.grid_scatter(geometry.bounds, geometry.resolution, outside_handling) - elif isinstance(geometry, GeometryStack): - sampled = [self._sample(g, **kwargs) for g in geometry.geometries] + if isinstance(geometry, GeometryStack): + sampled = [self._sample(g, soft, scatter, outside_handling, balance) for g in geometry.geometries] return math.stack(sampled, geometry.geometries.shape) + if isinstance(geometry, GridCell) and scatter: + assert not soft, "Cannot soft-sample when scatter=True" + return self.grid_scatter(geometry.bounds, geometry.resolution, outside_handling) else: - raise NotImplementedError() + assert not isinstance(self._elements, Point), "Cannot sample Point-like elements with scatter=False" + if soft: + return self.elements.approximate_fraction_inside(geometry, balance) + else: + return math.to_float(self.elements.lies_inside(geometry.center)) def grid_scatter(self, bounds: Box, resolution: math.Shape, outside_handling: str): """ @@ -138,7 +150,6 @@ def grid_scatter(self, bounds: Box, resolution: math.Shape, outside_handling: st Returns: `CenteredGrid` - """ closest_index = bounds.global_to_local(self.points) * resolution - 0.5 mode = 'add' if self._add_overlapping else 'mean' @@ -148,15 +159,6 @@ def grid_scatter(self, bounds: Box, resolution: math.Shape, outside_handling: st scattered = math.scatter(base, closest_index, self.values, mode=mode, outside_handling=outside_handling) return scattered - def mask(self): - """ - Returns an equivalent `PointCloud` with `values=1` and `extrapolation=0` - - Returns: - `PointCloud` - """ - return PointCloud(self.elements, bounds=self.bounds) - def __repr__(self): return "PointCloud[%s]" % (self.shape,) @@ -199,7 +201,7 @@ def distribute_points(geometries: tuple or list or Geometry or float, if isinstance(geometries, (tuple, list, Geometry)): from phi.geom import union geometries = union(geometries) - geometries = CenteredGrid(geometries, extrapolation, **domain) + geometries = resample(geometries, CenteredGrid(0, extrapolation, **domain), scatter=False) initial_points = _distribute_points(geometries.values, dim, points_per_cell, center=center) if radius is None: from phi.field._field_math import data_bounds diff --git a/phi/flow.py b/phi/flow.py index ce6e56a8f..d13ad93ab 100644 --- a/phi/flow.py +++ b/phi/flow.py @@ -20,7 +20,7 @@ # Classes from .math import Tensor, DType, Solve from .geom import Geometry, Sphere, Box, Cuboid -from .field import Grid, CenteredGrid, StaggeredGrid, GeometryMask, SoftGeometryMask, HardGeometryMask, Noise, PointCloud, Scene +from .field import Grid, CenteredGrid, StaggeredGrid, mask, Noise, PointCloud, Scene, resample, GeometryMask, SoftGeometryMask, HardGeometryMask from .vis import Viewer from .physics.fluid import Obstacle diff --git a/phi/geom/_geom.py b/phi/geom/_geom.py index 2175284d1..73096f35e 100644 --- a/phi/geom/_geom.py +++ b/phi/geom/_geom.py @@ -1,8 +1,8 @@ from numbers import Number from phi import math -from phi.math import Tensor, Shape, EMPTY_SHAPE, non_channel, wrap -from phi.math._magic_ops import variable_attributes +from phi.math import Tensor, Shape, EMPTY_SHAPE, non_channel, wrap, shape +from phi.math._magic_ops import variable_attributes, expand from phi.math.magic import BoundDim, slicing_dict @@ -510,7 +510,7 @@ def unstack(self, dimension: str) -> tuple: return tuple(Point(loc) for loc in self._location.unstack(dimension)) def lies_inside(self, location: Tensor) -> Tensor: - return math.wrap(False) + return expand(math.wrap(False), shape(location).without('vector')) def approximate_signed_distance(self, location: Tensor or tuple) -> Tensor: return math.vec_abs(location - self._location) diff --git a/phi/math/_magic_ops.py b/phi/math/_magic_ops.py index 41066f6d9..91cc4a1c9 100644 --- a/phi/math/_magic_ops.py +++ b/phi/math/_magic_ops.py @@ -583,10 +583,11 @@ def replace(obj: PhiTreeNodeType, **updates) -> PhiTreeNodeType: # Other Ops +MagicType = TypeVar('MagicType') OtherMagicType = TypeVar('OtherMagicType') -def cast(x: OtherMagicType, dtype: DType or type) -> OtherMagicType: +def cast(x: MagicType, dtype: DType or type) -> OtherMagicType: """ Casts `x` to a different data type. diff --git a/phi/math/extrapolation.py b/phi/math/extrapolation.py index f3ff7c23e..139424ff4 100644 --- a/phi/math/extrapolation.py +++ b/phi/math/extrapolation.py @@ -1366,3 +1366,23 @@ def map(f: Callable[[Extrapolation], Extrapolation], extrapolation): return combine_by_direction(map(f, extrapolation.normal), map(f, extrapolation.tangential)) else: return f(extrapolation) + + +def remove_constant_offset(extrapolation): + """ + Removes all constant offsets from an extrapolation. + This also includes `NaN` values in constants (unlike `ext - ext`). + + Args: + extrapolation: `Extrapolation` object. + + Returns: + `Extrapolation` that has no constant offsets + """ + def const_to_zero(extrapolation): + if isinstance(extrapolation, ConstantExtrapolation): + return ZERO + else: + return extrapolation + return map(const_to_zero, extrapolation) + diff --git a/phi/physics/_boundaries.py b/phi/physics/_boundaries.py index c3186d5ca..63f248d57 100644 --- a/phi/physics/_boundaries.py +++ b/phi/physics/_boundaries.py @@ -2,7 +2,7 @@ from numbers import Number from phi import math, field -from phi.field import CenteredGrid, StaggeredGrid, PointCloud, Field, HardGeometryMask +from phi.field import CenteredGrid, StaggeredGrid, PointCloud, Field, mask from phi.geom import Box, GridCell, Sphere, union, assert_same_rank from phi.geom import Geometry from phi.math import Tensor, channel, instance @@ -278,7 +278,7 @@ def accessible_mask(self, not_accessible: tuple or list, type: type = CenteredGr Binary mask indicating valid fields w.r.t. the boundary conditions. """ extrapolation = extrapolation if isinstance(extrapolation, math.Extrapolation) else self.boundaries[extrapolation] - accessible_mask = self.scalar_grid(HardGeometryMask(~union(not_accessible)), extrapolation=extrapolation) + accessible_mask = self.scalar_grid(mask(~union(not_accessible)), extrapolation=extrapolation) if type is CenteredGrid: return accessible_mask elif type is StaggeredGrid: @@ -339,7 +339,7 @@ def distribute_points(self, Returns: PointCloud representation of `geometries`. """ - geometries = HardGeometryMask(union(geometries)) @ self.grid() + geometries = mask(union(geometries)).at(self.grid()) initial_points = _distribute_points(geometries.values, points_per_cell, center=center) return self.points(initial_points, color=color) diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index ad92ac6de..c48595834 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -7,7 +7,7 @@ from phi import math, field from phi.math import wrap, channel, Solve -from phi.field import SoftGeometryMask, AngularVelocity, Grid, divergence, spatial_gradient, where, CenteredGrid, PointCloud, Field +from phi.field import AngularVelocity, Grid, divergence, spatial_gradient, where, CenteredGrid, PointCloud, Field, resample from phi.geom import union, Geometry from ..field._embed import FieldEmbedding from ..field._grid import GridType, StaggeredGrid @@ -143,7 +143,7 @@ def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid, active: CenteredGrid, if order == 2 and not implicit: grad = spatial_gradient(pressure, hard_bcs.extrapolation, type=type(hard_bcs)) valid_grad = grad * hard_bcs - valid_grad = valid_grad.with_extrapolation(valid_grad.extrapolation - valid_grad.extrapolation) + valid_grad = valid_grad.with_extrapolation(extrapolation.remove_constant_offset(valid_grad.extrapolation)) div = divergence(valid_grad) laplace = where(active, div, pressure) else: @@ -174,7 +174,7 @@ def apply_boundary_conditions(velocity: Grid or PointCloud, obstacles: Obstacle if isinstance(obstacle, Geometry): obstacle = Obstacle(obstacle) assert isinstance(obstacle, Obstacle) - obs_mask = SoftGeometryMask(obstacle.geometry, balance=1) @ velocity + obs_mask = resample(obstacle.geometry, velocity, soft=True, balance=1) if obstacle.is_stationary: velocity = (1 - obs_mask) * velocity else: diff --git a/tests/commit/field/test__field_math.py b/tests/commit/field/test__field_math.py index 7e3c7ae0a..4a5d20565 100644 --- a/tests/commit/field/test__field_math.py +++ b/tests/commit/field/test__field_math.py @@ -5,7 +5,7 @@ import phi from phi import math, geom -from phi.field import StaggeredGrid, CenteredGrid, HardGeometryMask, PointCloud +from phi.field import StaggeredGrid, CenteredGrid, PointCloud from phi.geom import Box, Sphere from phi import field from phi.math import extrapolation, instance, channel, spatial, batch @@ -245,4 +245,10 @@ def jxp(zeta, psi, d, i, j): val = val / 3 return val - + def test_mask(self): + mask = field.mask(Box(x=1, y=1)) + self.assertEqual(2, mask.spatial_rank) + mask = field.mask(PointCloud(math.vec(x=0, y=0))) + self.assertEqual(2, mask.spatial_rank) + mask = field.mask(CenteredGrid(0, x=4, y=3)) + self.assertEqual(2, mask.spatial_rank) diff --git a/tests/commit/physics/test_flip.py b/tests/commit/physics/test_flip.py index 86e22fde8..d90cba937 100644 --- a/tests/commit/physics/test_flip.py +++ b/tests/commit/physics/test_flip.py @@ -7,13 +7,13 @@ def step(particles: PointCloud, obstacles: list, dt: float, **grid_resolution): # --- Grid Operations --- - velocity = prev_velocity = field.finite_fill(particles.at(StaggeredGrid(0, 0, particles.bounds, **grid_resolution), outside_handling='clamp')) - occupied = CenteredGrid(particles.mask(), velocity.extrapolation.spatial_gradient(), velocity.bounds, velocity.resolution) + velocity = prev_velocity = field.finite_fill(resample(particles, StaggeredGrid(0, 0, particles.bounds, **grid_resolution), outside_handling='clamp', scatter=True)) + occupied = resample(field.mask(particles), CenteredGrid(0, velocity.extrapolation.spatial_gradient(), velocity.bounds, velocity.resolution), scatter=True) velocity, pressure = fluid.make_incompressible(velocity + (0, -9.81 * dt), obstacles, active=occupied) # --- Particle Operations --- - particles += (velocity - prev_velocity) @ particles # FLIP update + particles += resample(velocity - prev_velocity, particles) # FLIP update # particles = velocity @ particles # PIC update - particles = advect.points(particles, velocity * ~union(obstacles), dt, advect.finite_rk4) + particles = advect.points(particles, velocity * field.mask(~union(obstacles)), dt, advect.finite_rk4) particles = fluid.boundary_push(particles, obstacles + [~particles.bounds]) return particles diff --git a/tests/commit/vis/test__plots.py b/tests/commit/vis/test__plots.py index 45166e95d..698ce6724 100644 --- a/tests/commit/vis/test__plots.py +++ b/tests/commit/vis/test__plots.py @@ -3,7 +3,7 @@ import plotly from phi import geom, field, math -from phi.field import CenteredGrid, StaggeredGrid, PointCloud, Noise, SoftGeometryMask +from phi.field import CenteredGrid, StaggeredGrid, PointCloud, Noise, resample from phi.geom import Sphere, Box from phi.math import extrapolation, wrap, instance, channel, batch, spatial, vec, stack from phi.vis import show, overlay, plot, close @@ -104,12 +104,12 @@ def test_overlay(self): self._test_plot(overlay(grid, grid * (0.1, 0.02), cloud), title='Overlay') def test_plot_density_3d_batched(self): - sphere = CenteredGrid(SoftGeometryMask(Sphere(x=.5, y=.5, z=.5, radius=.4)), x=10, y=10, z=10, bounds=Box(x=1, y=1, z=1)) + sphere = resample(Sphere(x=.5, y=.5, z=.5, radius=.4), CenteredGrid(0, x=10, y=10, z=10, bounds=Box(x=1, y=1, z=1)), soft=True) cylinder = CenteredGrid(geom.infinite_cylinder(x=16, y=16, inf_dim='z', radius=10), x=32, y=32, z=32) self._test_plot(sphere, cylinder) def test_plot_vector_3d_batched(self): - sphere = CenteredGrid(SoftGeometryMask(Sphere(x=.5, y=.5, z=.5, radius=.4)), x=10, y=10, z=10, bounds=Box(x=1, y=1, z=1)) * (.1, 0, 0) + sphere = resample(Sphere(x=.5, y=.5, z=.5, radius=.4), CenteredGrid(0, x=10, y=10, z=10, bounds=Box(x=1, y=1, z=1)), soft=True) * (.1, 0, 0) cylinder = CenteredGrid(geom.infinite_cylinder(x=16, y=16, inf_dim='z', radius=10), x=32, y=32, z=32) * (0, 0, .1) self._test_plot(sphere, cylinder) diff --git a/tests/release/test_flip.py b/tests/release/test_flip.py index f663c0595..54da0a52c 100644 --- a/tests/release/test_flip.py +++ b/tests/release/test_flip.py @@ -7,13 +7,13 @@ def step(particles: PointCloud, obstacles: list, dt: float, **grid_resolution): # --- Grid Operations --- - velocity = prev_velocity = field.finite_fill(particles.at(StaggeredGrid(0, 0, particles.bounds, **grid_resolution), outside_handling='clamp')) - occupied = CenteredGrid(particles.mask(), velocity.extrapolation.spatial_gradient(), velocity.bounds, velocity.resolution) + velocity = prev_velocity = field.finite_fill(resample(particles, StaggeredGrid(0, 0, particles.bounds, **grid_resolution), outside_handling='clamp', scatter=True)) + occupied = resample(field.mask(particles), CenteredGrid(0, velocity.extrapolation.spatial_gradient(), velocity.bounds, velocity.resolution), scatter=True) velocity, pressure = fluid.make_incompressible(velocity + (0, -9.81 * dt), obstacles, active=occupied) # --- Particle Operations --- - particles += (velocity - prev_velocity) @ particles # FLIP update + particles += resample(velocity - prev_velocity, particles) # FLIP update # particles = velocity @ particles # PIC update - particles = advect.points(particles, velocity * ~union(obstacles), dt, advect.finite_rk4) + particles = advect.points(particles, velocity * field.mask(~union(obstacles)), dt, advect.finite_rk4) particles = fluid.boundary_push(particles, obstacles + [~particles.bounds]) return particles From d25aa531f217680c85543189237d5acfbc9cb012 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 5 Feb 2023 14:09:43 +0100 Subject: [PATCH 100/170] [field] Add rename resample arguments * Improve documentation --- demos/flip_liquid.py | 2 +- demos/fluid_logo.py | 4 +-- demos/point_cloud.py | 2 +- demos/smoke_plume.py | 4 +-- demos/smoke_plume_3d.py | 4 +-- phi/field/_field.py | 63 ++++++++++++++++++++-------------------- phi/field/_field_math.py | 3 +- phi/field/_grid.py | 4 +-- 8 files changed, 43 insertions(+), 43 deletions(-) diff --git a/demos/flip_liquid.py b/demos/flip_liquid.py index 2fefafb38..86e33ac1b 100644 --- a/demos/flip_liquid.py +++ b/demos/flip_liquid.py @@ -25,7 +25,7 @@ def step(particles): occupied = resample(field.mask(particles), CenteredGrid(0, velocity.extrapolation.spatial_gradient(), velocity.bounds, velocity.resolution), scatter=True) velocity, pressure = fluid.make_incompressible(velocity + GRAVITY * DT, [OBSTACLE], active=occupied) # --- Particle Operations --- - particles += resample(velocity - prev_velocity, particles) # FLIP update + particles += resample(velocity - prev_velocity, to=particles) # FLIP update # particles = resample(velocity, particles) # PIC update particles = advect.points(particles, velocity * mask(~OBSTACLE), DT, advect.finite_rk4) particles = fluid.boundary_push(particles, [OBSTACLE, ~particles.bounds]) diff --git a/demos/fluid_logo.py b/demos/fluid_logo.py index a6b86102b..5fd3d386b 100644 --- a/demos/fluid_logo.py +++ b/demos/fluid_logo.py @@ -10,7 +10,7 @@ OBSTACLE_GEOMETRIES = [Box(x=(15 + x * 7, 15 + (x + 1) * 7), y=(41, 83)) for x in range(1, 10, 2)] + [Box['x,y', 43:50, 41:48], Box['x,y', 15:43, 83:90], Box['x,y', 50:85, 83:90]] OBSTACLE = Obstacle(union(OBSTACLE_GEOMETRIES)) -OBSTACLE_MASK = resample(OBSTACLE.geometry, CenteredGrid(0, extrapolation.BOUNDARY, **DOMAIN)) +OBSTACLE_MASK = resample(OBSTACLE.geometry, to=CenteredGrid(0, extrapolation.BOUNDARY, **DOMAIN)) INFLOW = CenteredGrid(Box['x,y', 14:21, 6:10], extrapolation.BOUNDARY, **DOMAIN) + \ CenteredGrid(Box['x,y', 81:88, 6:10], extrapolation.BOUNDARY, **DOMAIN) * 0.9 + \ @@ -20,7 +20,7 @@ for _ in view('smoke, velocity, pressure, OBSTACLE_MASK', play=False, namespace=globals()).range(warmup=1): smoke = advect.semi_lagrangian(smoke, velocity, 1) + INFLOW - buoyancy_force = (smoke * (0, 0.1)).at(velocity) + buoyancy_force = resample(smoke * (0, 0.1), to=velocity) velocity = advect.semi_lagrangian(velocity, velocity, 1) + buoyancy_force velocity, pressure = fluid.make_incompressible(velocity, (OBSTACLE,), Solve('CG-adaptive', 1e-5, 0, x0=pressure)) remaining_divergence = field.divergence(velocity) diff --git a/demos/point_cloud.py b/demos/point_cloud.py index c06c716f2..e55928a1d 100644 --- a/demos/point_cloud.py +++ b/demos/point_cloud.py @@ -17,6 +17,6 @@ # Grid sampling scattered_data = field.sample(points, velocity.elements, scatter=True) scattered_grid = points.at(velocity, scatter=True) -scattered_sgrid = resample(points, StaggeredGrid(0, 0, velocity.bounds, velocity.resolution), scatter=True) +scattered_sgrid = resample(points, to=StaggeredGrid(0, 0, velocity.bounds, velocity.resolution), scatter=True) view(namespace=globals()) diff --git a/demos/smoke_plume.py b/demos/smoke_plume.py index 9d5ba6571..328481207 100644 --- a/demos/smoke_plume.py +++ b/demos/smoke_plume.py @@ -10,14 +10,14 @@ velocity = StaggeredGrid((0, 0), 0, x=64, y=64, bounds=Box(x=100, y=100)) # or CenteredGrid(...) smoke = CenteredGrid(0, extrapolation.BOUNDARY, x=200, y=200, bounds=Box(x=100, y=100)) -INFLOW = 0.2 * resample(Sphere(x=50, y=9.5, radius=5), smoke, soft=True) +INFLOW = 0.2 * resample(Sphere(x=50, y=9.5, radius=5), to=smoke, soft=True) pressure = None # @jit_compile # Only for PyTorch, TensorFlow and Jax def step(v, s, p, dt=1.): s = advect.mac_cormack(s, v, dt) + INFLOW - buoyancy = (s * (0, 0.1)).at(v) + buoyancy = resample(s * (0, 0.1), to=v) v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt v, p = fluid.make_incompressible(v, (), Solve('auto', 1e-5, 0, x0=p)) return v, s, p diff --git a/demos/smoke_plume_3d.py b/demos/smoke_plume_3d.py index 2d6d067f0..5949f21bb 100644 --- a/demos/smoke_plume_3d.py +++ b/demos/smoke_plume_3d.py @@ -10,14 +10,14 @@ velocity = StaggeredGrid((0, 0, 0), extrapolation.ZERO, x=32, y=32, z=32, bounds=Box(x=100, y=100, z=100)) # or CenteredGrid(...) smoke = CenteredGrid(0, extrapolation.BOUNDARY, x=32, y=32, z=32, bounds=Box(x=100, y=100, z=100)) -INFLOW = 0.2 * resample(Sphere(x=50, y=50, z=10, radius=5), smoke, soft=True) +INFLOW = 0.2 * resample(Sphere(x=50, y=50, z=10, radius=5), to=smoke, soft=True) pressure = None # @jit_compile # Only for PyTorch, TensorFlow and Jax def step(v, s, p, dt=1.): s = advect.mac_cormack(s, v, dt) + INFLOW - buoyancy = (s * (0, 0, 0.1)).at(v) + buoyancy = resample(s * (0, 0, 0.1), to=v) v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt v, p = fluid.make_incompressible(v, (), Solve('auto', 1e-5, 0, x0=p)) return v, s, p diff --git a/phi/field/_field.py b/phi/field/_field.py index 62d40114a..41e51f778 100644 --- a/phi/field/_field.py +++ b/phi/field/_field.py @@ -1,4 +1,5 @@ import warnings +from numbers import Number from typing import TypeVar, Callable, Union from phi import math @@ -71,22 +72,15 @@ def at(self, representation: 'SampledField', keep_extrapolation=False, **kwargs) """ return resample(self, representation, keep_extrapolation, **kwargs) - def __matmul__(self, other: 'SampledField'): # values @ representation - """ - Resampling operator with change of extrapolation. - - Args: - other: instance of SampledField - - Returns: - Copy of other with values and extrapolation from this Field. - """ + def __matmul__(self, other: 'SampledField'): # value @ representation + # Deprecated. Use `resample(value, field)` instead. + warnings.warn("value @ field is deprecated. Use resample(value, field) instead.", DeprecationWarning) return self.at(other, keep_extrapolation=False) def __rmatmul__(self, other): # values @ representation if not isinstance(self, SampledField): return NotImplemented - if isinstance(other, (Geometry, float, int, complex, tuple, list)): + if isinstance(other, (Geometry, Number, tuple, list)): return self.with_values(other) return NotImplemented @@ -257,12 +251,6 @@ def __pow__(self, power, modulo=None): def __neg__(self): return self._op1(lambda x: -x) - def __eq__(self, other): - return self._op2(other, lambda x, y: x == y) - - def __ne__(self, other): - return self._op2(other, lambda x, y: x != y) - def __gt__(self, other): return self._op2(other, lambda x, y: x > y) @@ -396,23 +384,23 @@ def reduce_sample(field: Field or Geometry, return field._sample(geometry, **kwargs) -def resample(obj: Union[Field, Geometry, Tensor, float], representation: SampledField, keep_extrapolation=False, **kwargs): +def resample(value: Union[Field, Geometry, Tensor, float], to: SampledField, keep_extrapolation=False, **kwargs): """ - Samples this field at the sample points of `representation`. - The result will approximate the values of this field on the data structure of `representation`. + Samples a `Field`, `Geometry` or value at the sample points of the field `to`. + The result will approximate `value` on the data structure of `to`. + Unlike `sample()`, this method returns a `Field` object, not a `Tensor`. - Unlike `Field.sample()`, this method returns a `Field` object, not a `Tensor`. - - Operator alias: - `self @ representation`. + Aliases: + `value.at(to)`, (and the deprecated `value @ to`). See Also: - `sample()`, `reduce_sample()`, [Resampling overview](https://tum-pbs.github.io/PhiFlow/Fields.html#resampling-fields). + `sample()`, `reduce_sample()`, `Field.at()`, [Resampling overview](https://tum-pbs.github.io/PhiFlow/Fields.html#resampling-fields). Args: - obj: Object containing values to resample. + value: Object containing values to resample. This can be - representation: Field object defining the sample points. The values of `representation` are ignored. + to: `SampledField` (`CenteredGrid`, `StaggeredGrid` or `PointCloud`) object defining the sample points. + The current values of `to` are ignored. keep_extrapolation: Only available if `self` is a `SampledField`. If True, the resampled field will inherit the extrapolation from `self` instead of `representation`. This can result in non-compatible value tensors for staggered grids where the tensor size depends on the extrapolation type. @@ -422,12 +410,23 @@ def resample(obj: Union[Field, Geometry, Tensor, float], representation: Sampled Returns: Field object of same type as `representation` + + Examples: + >>> grid = CenteredGrid(x=64, y=32) + >>> field.resample(Noise(), to=grid) + CenteredGrid[(xˢ=64, yˢ=32), size=(x=64, y=32), extrapolation=float64 0.0] + >>> field.resample(1, to=grid) + CenteredGrid[(xˢ=64, yˢ=32), size=(x=64, y=32), extrapolation=float64 0.0] + >>> field.resample(Box(x=1, y=2), to=grid) + CenteredGrid[(xˢ=64, yˢ=32), size=(x=64, y=32), extrapolation=float64 0.0] + >>> field.resample(grid, to=grid) == grid + True """ - if not isinstance(obj, (Field, Geometry)): - return representation.with_values(obj) - resampled = reduce_sample(obj, representation.elements, **kwargs) - extrap = obj.extrapolation if isinstance(obj, SampledField) and keep_extrapolation else representation.extrapolation - return representation.with_values(resampled).with_extrapolation(extrap) + if not isinstance(value, (Field, Geometry)): + return to.with_values(value) + resampled = reduce_sample(value, to.elements, **kwargs) + extrap = value.extrapolation if isinstance(value, SampledField) and keep_extrapolation else to.extrapolation + return to.with_values(resampled).with_extrapolation(extrap) def _get_geometry(geometry): diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 500c91629..2b4f32461 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -861,6 +861,7 @@ def mask(obj: SampledFieldType or Geometry) -> SampledFieldType: elif isinstance(obj, Geometry): return PointCloud(obj, 1, 0) elif isinstance(obj, CenteredGrid): - return math.cast(obj != 0, int) + values = math.cast(obj.values != 0, int) + return obj.with_values(values) else: raise ValueError(obj) diff --git a/phi/field/_grid.py b/phi/field/_grid.py index 24499df88..eb633cef7 100644 --- a/phi/field/_grid.py +++ b/phi/field/_grid.py @@ -157,7 +157,7 @@ class CenteredGrid(Grid): """ def __init__(self, - values: Any, + values: Any = 0., extrapolation: Any = 0., bounds: Box or float = None, resolution: int or Shape = None, @@ -295,7 +295,7 @@ class StaggeredGrid(Grid): """ def __init__(self, - values: Any, + values: Any = 0., extrapolation: float or Extrapolation = 0, bounds: Box or float = None, resolution: Shape or int = None, From 53b8f647e85054e80109bf20ca531f1b6c29d0e9 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 5 Feb 2023 17:47:05 +0100 Subject: [PATCH 101/170] [math] Make identity single-argument --- phi/math/_functional.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/phi/math/_functional.py b/phi/math/_functional.py index 013d39ed3..9c40ff8b2 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -1027,14 +1027,15 @@ def iterate(f: Callable, raise ValueError(f"iterations must be an int or Shape but got {type(iterations)}") -def identity(*args): +def identity(x): """ - Identity function without keyword arguments. + Identity function for one argument. + Vararg functions cannot be transformed as the argument names are unknown. Args: - *args: Positional arguments. + x: Positional argument. Returns: - `args` + `x` """ - return args + return x From 379ba4dfdf760d15475a01cef0c3d26988cd9519 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 5 Feb 2023 17:47:25 +0100 Subject: [PATCH 102/170] [math] Fix PyTorch linspace with tensor arguments --- phi/torch/_torch_backend.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index 44aa9fade..f3622c1c0 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -377,7 +377,11 @@ def meshgrid(self, *coordinates): return torch.meshgrid(*coordinates) def linspace(self, start, stop, number): - return torch.linspace(start, stop, number, dtype=to_torch_dtype(self.float_type), device=self.get_default_device().ref) + if self.is_tensor(stop, only_native=True) or self.is_tensor(start, only_native=True): + unit = torch.linspace(0, 1, number, dtype=to_torch_dtype(self.float_type), device=self.get_default_device().ref) + return unit * (stop - start) + start + else: + return torch.linspace(start, stop, number, dtype=to_torch_dtype(self.float_type), device=self.get_default_device().ref) def tensordot(self, a, a_axes: tuple or list, b, b_axes: tuple or list): a, b = self.auto_cast(a, b) From 6ee5f091967f774840273aa9f443ec4ace556bd0 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 5 Feb 2023 17:47:45 +0100 Subject: [PATCH 103/170] [Vis] Fix Matplotlib PointCloud2D --- phi/vis/_matplotlib/_matplotlib_plots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index 357e0f020..31226d92e 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -305,7 +305,7 @@ def _plot_points(axis, data: PointCloud, dims, vector, color): for idx in data.elements.geometries.shape[0].meshgrid(): PointCloud2D._plot_points(axis, data[idx], dims, vector, color[idx]) return - x, y = math.reshaped_numpy(data.points.vector[dims], [vector, non_channel(data)]) + x, y = math.reshaped_numpy(data.points.vector[dims], [vector, non_channel(data)], force_expand=True) mpl_colors = matplotlib_colors(color, non_channel(data), default=0) if isinstance(data.elements, Point): if spatial(data.points).is_empty: From e61fa393295f53b73c5b5fda1985636b4cb7b5a8 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 5 Feb 2023 18:40:35 +0100 Subject: [PATCH 104/170] [geom] Fix Geometry.at() --- phi/geom/_box.py | 4 ++-- phi/geom/_geom.py | 16 ++++++++-------- phi/geom/_sphere.py | 4 ++-- phi/geom/_stack.py | 7 +++---- phi/geom/_transform.py | 7 +++++-- phi/geom/_union.py | 4 ++++ tests/commit/physics/test_advect.py | 2 +- 7 files changed, 25 insertions(+), 19 deletions(-) diff --git a/phi/geom/_box.py b/phi/geom/_box.py index 94ba1fae4..fe66c5760 100644 --- a/phi/geom/_box.py +++ b/phi/geom/_box.py @@ -32,8 +32,8 @@ def shape(self): def center(self) -> Tensor: raise NotImplementedError() - def shifted(self, delta, **delta_by_dim) -> 'BaseBox': - raise NotImplementedError() + def at(self, center: Tensor) -> 'BaseBox': + return Cuboid(center, self._half_size) @property def size(self) -> Tensor: diff --git a/phi/geom/_geom.py b/phi/geom/_geom.py index 73096f35e..1fee704a6 100644 --- a/phi/geom/_geom.py +++ b/phi/geom/_geom.py @@ -219,9 +219,9 @@ def shifted(self, delta: Tensor) -> 'Geometry': Geometry: shifted geometry """ - raise NotImplementedError(self.__class__) + return self.at(self.center + delta) - def at(self, center: Tensor): + def at(self, center: Tensor) -> 'Geometry': """ Returns a copy of this `Geometry` with the center at `center`. This is equal to calling `self @ center`. @@ -235,7 +235,7 @@ def at(self, center: Tensor): Returns: `Geometry`. """ - return self.shifted(center - self.center) + raise NotImplementedError def __matmul__(self, other): return self.at(other) @@ -394,8 +394,8 @@ def bounding_radius(self) -> Tensor: def bounding_half_extent(self) -> Tensor: raise NotImplementedError() - def shifted(self, delta: Tensor) -> Geometry: - return _InvertedGeometry(self.geometry.shifted(delta)) + def at(self, center: Tensor) -> 'Geometry': + return _InvertedGeometry(self.geometry.at(center)) def rotated(self, angle) -> Geometry: return _InvertedGeometry(self.geometry.rotated(angle)) @@ -468,7 +468,7 @@ def lies_inside(self, location): def approximate_fraction_inside(self, other_geometry: 'Geometry', balance: Tensor or Number = 0.5) -> Tensor: return math.zeros(other_geometry.shape) - def shifted(self, delta): + def at(self, center: Tensor) -> 'Geometry': return self def rotated(self, angle): @@ -524,8 +524,8 @@ def bounding_radius(self) -> Tensor: def bounding_half_extent(self) -> Tensor: return math.zeros() - def shifted(self, delta: Tensor) -> 'Geometry': - return Point(self._location + delta) + def at(self, center: Tensor) -> 'Geometry': + return Point(center) def rotated(self, angle) -> 'Geometry': return self diff --git a/phi/geom/_sphere.py b/phi/geom/_sphere.py index ee21efcb6..db745cbe5 100644 --- a/phi/geom/_sphere.py +++ b/phi/geom/_sphere.py @@ -95,8 +95,8 @@ def bounding_radius(self): def bounding_half_extent(self): return self.radius - def shifted(self, delta): - return Sphere(self._center + delta, self._radius) + def at(self, center: Tensor) -> 'Geometry': + return Sphere(center, self._radius) def rotated(self, angle): return self diff --git a/phi/geom/_stack.py b/phi/geom/_stack.py index 7c972392b..7ffd7582d 100644 --- a/phi/geom/_stack.py +++ b/phi/geom/_stack.py @@ -5,7 +5,7 @@ from ._geom import Geometry from ..math import Tensor, expand from ..math._shape import shape_stack, Shape, INSTANCE_DIM, non_channel -from ..math._magic_ops import variable_attributes, copy_with +from ..math._magic_ops import variable_attributes, copy_with, unstack from ..math.magic import slicing_dict @@ -65,9 +65,8 @@ def bounding_half_extent(self): values = [expand(g.bounding_half_extent(), non_channel(g)) for g in self.geometries] return math.stack(values, self.geometries.shape) - def shifted(self, delta: math.Tensor): - deltas = delta.dimension(self.geometries.shape).unstack(len(self.geometries)) - geometries = [g.shifted(d) for g, d in zip(self.geometries, deltas)] + def at(self, center: Tensor) -> 'Geometry': + geometries = [self.geometries[idx].native().at(center[idx]) for idx in self.geometries.shape.meshgrid()] return GeometryStack(math.layout(geometries, self.geometries.shape)) def rotated(self, angle): diff --git a/phi/geom/_transform.py b/phi/geom/_transform.py index afa3b6b01..7827d6e6a 100644 --- a/phi/geom/_transform.py +++ b/phi/geom/_transform.py @@ -74,8 +74,8 @@ def bounding_half_extent(self): def rank(self): return self.geometry.spatial_rank - def shifted(self, delta) -> Geometry: - return RotatedGeometry(self._geometry.shifted(delta), self._angle) + def at(self, center: Tensor) -> 'Geometry': + return RotatedGeometry(self._geometry.at(center), self._angle) def rotated(self, angle) -> Geometry: return RotatedGeometry(self._geometry, self._angle + angle) @@ -164,6 +164,9 @@ def bounding_half_extent(self) -> Tensor: def shifted(self, delta: Tensor) -> 'Geometry': raise NotImplementedError() + def at(self, center: Tensor) -> 'Geometry': + raise NotImplementedError() + def rotated(self, angle: float or Tensor) -> 'Geometry': raise NotImplementedError() diff --git a/phi/geom/_union.py b/phi/geom/_union.py index 66f52d8fb..8e75f067a 100644 --- a/phi/geom/_union.py +++ b/phi/geom/_union.py @@ -3,6 +3,7 @@ from phi import math from ._geom import Geometry, NO_GEOMETRY from ._box import bounding_box, Box +from ..math import Tensor from ..math._shape import merge_shapes from ..math._magic_ops import variable_attributes, copy_with from ..math.magic import PhiTreeNode @@ -63,6 +64,9 @@ def _bounding_box(self): def shifted(self, delta) -> Geometry: return Union([geometry.shifted(delta) for geometry in self.geometries]) + def at(self, center: Tensor) -> 'Geometry': + raise AssertionError("Cannot position a union of geometries") + def rotated(self, angle) -> Geometry: from ._transform import rotate return rotate(self, angle) diff --git a/tests/commit/physics/test_advect.py b/tests/commit/physics/test_advect.py index a3726ecee..77ba6bf7d 100644 --- a/tests/commit/physics/test_advect.py +++ b/tests/commit/physics/test_advect.py @@ -8,7 +8,7 @@ def _test_advection(adv): s = CenteredGrid(Noise(), x=4, y=3) - v = CenteredGrid(Noise(vector=2), x=4, y=3) + v = CenteredGrid(Noise(vector='x,y'), x=4, y=3) field.assert_close(s, adv(s, v, 0), adv(s, v * 0, 1)) sv = StaggeredGrid(Noise(), x=4, y=3) field.assert_close(s, adv(s, sv, 0), adv(s, sv * 0, 1)) From 9ba02f8cc46392a055e3210f31d0bb3c2598fe82 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 5 Feb 2023 18:41:16 +0100 Subject: [PATCH 105/170] [math] Expand equal tree node attributes in stack() Previously, they were stacked --- phi/math/_magic_ops.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phi/math/_magic_ops.py b/phi/math/_magic_ops.py index 91cc4a1c9..da4b1667c 100644 --- a/phi/math/_magic_ops.py +++ b/phi/math/_magic_ops.py @@ -131,7 +131,10 @@ def stack(values: tuple or list or dict, dim: Shape, expand_values=False, **kwar for a in attributes: assert all(dim not in shape(getattr(v, a)) for v in values), f"Cannot stack attribute {a} because one values contains the stack dimension {dim}." a_values = [getattr(v, a) for v in values] - new_attrs[a] = stack(a_values, dim, expand_values=expand_values, **kwargs) + if all(v is a_values[0] for v in a_values[1:]): + new_attrs[a] = expand(a_values[0], dim, **kwargs) + else: + new_attrs[a] = stack(a_values, dim, expand_values=expand_values, **kwargs) return copy_with(values[0], **new_attrs) else: warnings.warn(f"Failed to concat values using value attributes because attributes differ among values {values}") From 38642a26f891f037eb0768f6455e584d969cd644 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 7 Feb 2023 11:38:00 +0100 Subject: [PATCH 106/170] [doc] Update installation instructions --- README.md | 11 +++++++++-- docs/Installation_Instructions.md | 7 +++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 795c81964..fcdcc84fd 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,17 @@ making it easy to build end-to-end differentiable functions involving both learn Installation with [pip](https://pypi.org/project/pip/) on [Python 3.6](https://www.python.org/downloads/) and above: ``` bash -$ pip install phiflow dash +$ pip install phiflow ``` Install PyTorch, TensorFlow or Jax in addition to ΦFlow to enable machine learning capabilities and GPU execution. -See the [detailed installation instructions](https://tum-pbs.github.io/PhiFlow/Installation_Instructions.html) on how to compile the custom CUDA operators and verify your installation. +To enable the web UI, also install [`dash`](https://pypi.org/project/dash/). +For optimal GPU performance, you may compile the custom CUDA operators, see the [detailed installation instructions](https://tum-pbs.github.io/PhiFlow/Installation_Instructions.html). + +You can verify your installation by running +```bash +$ python3 -c "import phi; phi.verify()" +``` +This will check for compatible PyTorch, Jax and TensorFlow installations as well. ## Documentation and Tutorials [**Documentation Overview**](https://tum-pbs.github.io/PhiFlow/) diff --git a/docs/Installation_Instructions.md b/docs/Installation_Instructions.md index 0dbe573e1..61833b446 100644 --- a/docs/Installation_Instructions.md +++ b/docs/Installation_Instructions.md @@ -65,8 +65,11 @@ $ python <Φ-Flow directory>/tests/verify.py ``` Otherwise, run the following Python code. ```python -import phi -phi.verify() +import phi; phi.verify() +``` +Or from the command line: +```bash +$ python3 -c "import phi; phi.verify()" ``` If ΦFlow and dependencies are installed correctly, you should see the text `Installation verified.`, followed by additional information on the components at the end of the console output. From ccf624dd5a3700f5c7663d15f33673bee9ca617f Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 7 Feb 2023 11:55:32 +0100 Subject: [PATCH 107/170] [field] Auto-expand PointCloud values * Add shifted() --- phi/field/_field_math.py | 4 ++-- phi/field/_point_cloud.py | 42 +++++++++++++++++++++++---------------- phi/math/magic.py | 2 +- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 2b4f32461..64cfd1fac 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -636,8 +636,8 @@ def stack(fields, dim: Shape, dim_bounds: Box = None): else: return fields[0].with_values(values) elif isinstance(fields[0], PointCloud): - elements = geom.stack([f.elements for f in fields], dim=dim) - values = math.stack([f.values for f in fields], dim=dim) + elements = geom.stack([f.elements for f in fields], dim, expand_values=True) + values = math.stack([f.values for f in fields], dim, expand_values=True) return PointCloud(elements=elements, values=values, extrapolation=fields[0].extrapolation, add_overlapping=fields[0]._add_overlapping, bounds=fields[0]._bounds) raise NotImplementedError(type(fields[0])) diff --git a/phi/field/_point_cloud.py b/phi/field/_point_cloud.py index d4f02f00d..e3fd59eaf 100644 --- a/phi/field/_point_cloud.py +++ b/phi/field/_point_cloud.py @@ -1,6 +1,8 @@ import warnings from typing import Any, Tuple, Union +from phi.math import wrap, expand, non_batch + from phi import math from phi.geom import Geometry, GridCell, Box, Point from ._field import SampledField, resample @@ -12,25 +14,25 @@ class PointCloud(SampledField): """ - A point cloud consists of elements at arbitrary locations. - A value or vector is associated with each element. + A `PointCloud` comprises: - Outside of elements, the value of the field is determined by the extrapolation. + * `elements`: a `Geometry` representing all points or volumes + * `values`: a `Tensor` representing the values corresponding to `elements` + * `extrapolation`: an `Extrapolation` defining the field value outside of `values` - All points belonging to one example must be listed in the 'points' dimension. + The points / elements of the `PointCloud` are listed along *instance* or *spatial* dimensions of `elements`. + These dimensions are automatically added to `values` if not already present. - Sampling arguments: + When sampling or resampling a `PointCloud`, the following keyword arguments can be specified. - soft: default=False. - If `True`, interpolates smoothly from 1 to 0 between the inside and outside of elements. - If `False`, only the center position of the new representation elements is checked against the point cloud elements. - scatter: default=False. - If `True`, scattering will be used to sample the point cloud onto grids. - Then, each element of the point cloud can only affect a single cell. - This is only recommended when the points are much smaller than the cells. - outside_handling: default='discard'. One of `discard`, `clamp`, `undefined`. - balance: default=0.5. Only used when `soft=True`. - See the description in `phi.geom.Geometry.approximate_fraction_inside()`. + * `soft`: default=False. + If `True`, interpolates smoothly from 1 to 0 between the inside and outside of elements. + If `False`, only the center position of the new representation elements is checked against the point cloud elements. + * `scatter`: default=False. + If `True`, scattering will be used to sample the point cloud onto grids. Then, each element of the point cloud can only affect a single cell. This is only recommended when the points are much smaller than the cells. + * `outside_handling`: default='discard'. One of `'discard'`, `'clamp'`, `'undefined'`. + * `balance`: default=0.5. Only used when `soft=True`. + See the description in `phi.geom.Geometry.approximate_fraction_inside()`. See the `phi.field` module documentation at https://tum-pbs.github.io/PhiFlow/Fields.html """ @@ -49,7 +51,7 @@ def __init__(self, add_overlapping: True: values of overlapping geometries are summed. False: values between overlapping geometries are interpolated bounds: (optional) size of the fixed domain in which the points should get visualized. None results in max and min coordinates of points. """ - SampledField.__init__(self, elements, math.wrap(values), extrapolation, bounds) + SampledField.__init__(self, elements, expand(wrap(values), non_batch(elements).non_channel), extrapolation, bounds) self._add_overlapping = add_overlapping @property @@ -68,6 +70,9 @@ def __getitem__(self, item): def with_elements(self, elements: Geometry): return PointCloud(elements=elements, values=self.values, extrapolation=self.extrapolation, add_overlapping=self._add_overlapping, bounds=self._bounds) + def shifted(self, delta): + return self.with_elements(self.elements.shifted(delta)) + def with_values(self, values): return PointCloud(elements=self.elements, values=values, extrapolation=self.extrapolation, add_overlapping=self._add_overlapping, bounds=self._bounds) @@ -160,7 +165,10 @@ def grid_scatter(self, bounds: Box, resolution: math.Shape, outside_handling: st return scattered def __repr__(self): - return "PointCloud[%s]" % (self.shape,) + try: + return "PointCloud[%s]" % (self.shape,) + except: + return "PointCloud[invalid]" def __and__(self, other): assert isinstance(other, PointCloud) diff --git a/phi/math/magic.py b/phi/math/magic.py index 615509b6f..c501ec531 100644 --- a/phi/math/magic.py +++ b/phi/math/magic.py @@ -460,7 +460,7 @@ def __init__(self, obj, name: str): if name.startswith('_') or ',' in name or ' ' in name: raise AttributeError(f"'{type(self)}' object has no attribute '{name}'") if name == 'shape': - raise AttributeError + raise AttributeError(f"{type(obj)} has no shape") assert isinstance(obj, Sliceable) and isinstance(obj, Shaped), f"Cannot create BoundDim for {type(obj).__name__}. Objects must be Sliceable and Shaped, see https://tum-pbs.github.io/PhiFlow/phi/math/magic.html" self.obj = obj self.name = name From 2952598a8e1f992034238a38b98a48eeec7b1ae4 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 7 Feb 2023 12:32:46 +0100 Subject: [PATCH 108/170] [geom] Add Geometry.bounding_box() --- phi/geom/_geom.py | 11 +++++++++++ phi/geom/_sphere.py | 6 ++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/phi/geom/_geom.py b/phi/geom/_geom.py index 1fee704a6..b1a2fe8c4 100644 --- a/phi/geom/_geom.py +++ b/phi/geom/_geom.py @@ -204,6 +204,17 @@ def bounding_half_extent(self) -> Tensor: """ raise NotImplementedError(self.__class__) + def bounding_box(self) -> 'BaseBox': + """ + Returns the approximately smallest axis-aligned box that contains this `Geometry`. + The center of the box may not be equal to `self.center`. + + Returns: + `Box` or `Cuboid` that fully contains this `Geometry`. + """ + from ._box import Cuboid + return Cuboid(self.center, half_size=self.bounding_half_extent()) + def shifted(self, delta: Tensor) -> 'Geometry': """ Returns a translated version of this geometry. diff --git a/phi/geom/_sphere.py b/phi/geom/_sphere.py index db745cbe5..e70e4bed1 100644 --- a/phi/geom/_sphere.py +++ b/phi/geom/_sphere.py @@ -1,8 +1,6 @@ -from typing import Tuple - from phi import math from ._geom import Geometry, _keep_vector -from ..math import wrap, Tensor, Shape +from ..math import wrap, Tensor, expand from ..math.magic import slicing_dict @@ -93,7 +91,7 @@ def bounding_radius(self): return self.radius def bounding_half_extent(self): - return self.radius + return expand(self.radius, self._center.shape.only('vector')) def at(self, center: Tensor) -> 'Geometry': return Sphere(center, self._radius) From 7277eb9659ce264efa4423ef6755eb6c706ca052 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 8 Feb 2023 12:08:07 +0100 Subject: [PATCH 109/170] [field] Fix stack(PointCloud) --- phi/field/_field_math.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 64cfd1fac..aa6cfbe3c 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -636,8 +636,8 @@ def stack(fields, dim: Shape, dim_bounds: Box = None): else: return fields[0].with_values(values) elif isinstance(fields[0], PointCloud): - elements = geom.stack([f.elements for f in fields], dim, expand_values=True) - values = math.stack([f.values for f in fields], dim, expand_values=True) + elements = geom.stack([f.elements for f in fields], dim) + values = math.stack([f.values for f in fields], dim) return PointCloud(elements=elements, values=values, extrapolation=fields[0].extrapolation, add_overlapping=fields[0]._add_overlapping, bounds=fields[0]._bounds) raise NotImplementedError(type(fields[0])) From 9e8b20bc2577ebadedc3819a11ac39106d4ce30f Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 8 Feb 2023 13:38:14 +0100 Subject: [PATCH 110/170] [math] Sparse ILU (experimental) * Add factor_ilu() * Add Backend.ilu_coo() --- phi/math/_sparse.py | 30 ++++++- phi/math/backend/_backend.py | 19 +++++ phi/math/backend/_precondition.py | 128 ++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 phi/math/backend/_precondition.py diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index bbaecc807..4b5f97845 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -101,7 +101,7 @@ def _native_coo_components(self, col_dims: DimFilter, matrix=False): ind_batch = batch(self._indices) channels = non_instance(self._values).without(ind_batch) if matrix: - native_indices = self.default_backend.stack([row_idx_packed, col_idx_packed], -1) + native_indices = choose_backend(row_idx_packed, col_idx_packed).stack([row_idx_packed, col_idx_packed], -1) native_shape = (row_dims.volume, col_dims.volume) else: native_indices = reshaped_native(self._indices, [ind_batch, instance, 'vector'], force_expand=True) @@ -479,7 +479,7 @@ def dense(x: Tensor) -> Tensor: from ._ops import scatter, zeros base_grid = zeros(x.shape._with_types(SPATIAL_DIM), dtype=x.dtype) result_sp = scatter(base_grid, x._indices, x._values, mode='add', outside_handling='undefined') - result = rename_dims(result_sp, shape, x.shape) + result = rename_dims(result_sp, result_sp.shape, x.shape) return result elif isinstance(x, CompressedSparseMatrix): ind_batch, channels, native_indices, native_pointers, native_values, native_shape = x._native_csr_components() @@ -546,4 +546,28 @@ def native_matrix(value: Tensor): v = pack_dims(value, rows, channel('_row')) v = pack_dims(v, cols, channel('_col')) from ._ops import reshaped_native - return reshaped_native(v, [batch, '_row', '_col']) \ No newline at end of file + return reshaped_native(v, [batch, '_row', '_col']) + + +def factor_ilu(value: Tensor): + """ + Incomplete LU factorization. + + Args: + value: Matrix to factor. Currently only supports COO matrices. + + Returns: + lower: L matrix as `Tensor` + upper: U matrix as `Tensor` + """ + assert isinstance(value, SparseCoordinateTensor), "ILU currently only supports COO matrices" + ind_batch, channels, indices, values, shape = value._native_coo_components(dual, matrix=True) + (l_idx_nat, l_val_nat), (u_idx_nat, u_val_nat) = value.default_backend.ilu_coo(indices, values, shape) # 3 is too few + from ._ops import reshaped_tensor + l_indices = reshaped_tensor(l_idx_nat, [ind_batch, instance(value._indices), channel(value._indices)], convert=False) + l_values = reshaped_tensor(l_val_nat, [ind_batch, instance(value._values), channels], convert=False) + u_indices = reshaped_tensor(u_idx_nat, [ind_batch, instance(value._indices), channel(value._indices)], convert=False) + u_values = reshaped_tensor(u_val_nat, [ind_batch, instance(value._values), channels], convert=False) + lower = SparseCoordinateTensor(l_indices, l_values, value._dense_shape, value._can_contain_double_entries, value._indices_sorted) + upper = SparseCoordinateTensor(u_indices, u_values, value._dense_shape, value._can_contain_double_entries, value._indices_sorted) + return lower, upper diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 41e507e1b..547d74d1c 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -914,6 +914,25 @@ def coo_to_dense(self, indices, values, shape, contains_duplicates: bool): result = self.scatter(base, indices, values, mode='add' if contains_duplicates else 'update') return result + def ilu_coo(self, indices, values, shape, iterations=4): + """ + values: Backend-compatible values tensor of shape (batch_size, nnz, channels) + shape: Dense shape of matrix + + Args: + indices: (batch, nnz, 2) + values: (batch_size, nnz, channels) + shape: Dense matrix shape + iterations: (Optional) Number of sweeps to perform. + + Returns: + LU indices corresponding to the sparsity pattern given by `indices`. + Since L and U don't overlap, the entries of both can be returned as a single tensor. + """ + from ._precondition import incomplete_lu_coo + assert self.dtype(values).kind in (bool, int, float) + return incomplete_lu_coo(self, indices, self.to_float(values), shape, iterations) + def csr_matrix(self, column_indices, row_pointers, values, shape: Tuple[int, int]): """ Create a sparse matrix in compressed sparse row (CSR) format. diff --git a/phi/math/backend/_precondition.py b/phi/math/backend/_precondition.py new file mode 100644 index 000000000..8c4a2accc --- /dev/null +++ b/phi/math/backend/_precondition.py @@ -0,0 +1,128 @@ +from typing import Tuple + +import numpy as np + +from ._backend import Backend +from ._dtype import DType, to_numpy_dtype + + +def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], iterations: int): + """ + Based on *Parallel Approximate LU Factorizations for Sparse Matrices* by T.K. Huckle, https://www5.in.tum.de/persons/huckle/it_ilu.pdf. + + Every matrix in the batch must explicitly store the full diagonal. + There should not be any zeros on the diagonal, else the LU initialization fails. + + Args: + b: `Backend` + indices: Row & column indices of stored entries as `numpy.ndarray` of shape (batch_size, nnz, 2). + values: Backend-compatible values tensor of shape (batch_size, nnz, channels) + shape: Dense shape of matrix + iterations: Number of sweeps to perform. + + Returns: + lower: tuple (indices, values) where indices is a NumPy array and values is backend-specific + upper: tuple (indices, values) where indices is a NumPy array and values is backend-specific + """ + assert isinstance(indices, np.ndarray), "incomplete_lu_coo indices must be a NumPy array" + row, col = indices[..., 0], indices[..., 1] + batch_size, nnz, channels = b.staticshape(values) + rows, cols = shape + assert rows == cols, "incomplete_lu_coo only implemented for square matrices" + is_lower = np.expand_dims(row > col, -1) + index_in_row = get_index_in_row(row, col) + index_in_row_ = np.stack([row, index_in_row], -1) + max_entries_per_row = np.max(index_in_row) + has_transpose, transposed_index = get_transposed_indices(row, col, shape) # The corresponding index in the transposed pattern. If non-existent, points at any valid value + transposed_index = np.expand_dims(transposed_index, -1) + has_transpose = b.cast(np.expand_dims(has_transpose, -1), b.dtype(values)) # 0 or 1 depending on whether a transposed entry exists for a value + diagonal_indices = np.expand_dims(get_lower_diagonal_indices(row, col, shape), -1) # indices of corresponding values that lie on the diagonal + l_u_compressed_zeros = b.zeros((batch_size, rows, max_entries_per_row + 1, channels)) + # --- Initialize U as the diagonal of A, then compute off-diagonal of L --- + is_diagonal = np.expand_dims(row == col, -1) + lower = values / b.batched_gather_nd(values, diagonal_indices) # Since U=diag(A), L can be computed by a simple division + lu = b.where(is_diagonal, values, b.where(is_lower, lower, 0)) # combine lower + diag(A) + 0 + # --- Fixed-point iterations --- + for sweep in range(iterations): + diag = b.batched_gather_nd(lu, diagonal_indices) # should never contain 0 + l_u = lu * b.batched_gather_nd(lu, transposed_index) * has_transpose # matches indices (like lu, values) + # --- Temporarily densify indices by row for cumsum --- + l_u_compressed = b.scatter(l_u_compressed_zeros, b.stack([row, index_in_row], -1), l_u, mode='add') + sum_l_u = b.cumsum(l_u_compressed, -2) + sum_l_u = b.batched_gather_nd(sum_l_u, index_in_row_) + # --- update L and U in one matrix --- + l = 1 / diag * (values - sum_l_u) + u = values - sum_l_u + lu = b.where(is_lower, l, u) + # --- Assemble L=lower+unit_diagonal and U. If nnz varies along batch, keep the full sparsity pattern --- + u_values = b.where(~is_lower, lu, 0) + belongs_to_lower = (is_lower | is_diagonal) + l_values = b.where(is_lower, lu, b.cast(is_diagonal, b.dtype(values))) + u_mask_indices_b, u_mask_indices = np.where(~is_lower[..., 0]) + _, u_nnz = np.unique(u_mask_indices_b, return_counts=True) + if np.all(u_nnz == u_nnz[0]): # nnz for lower/upper does not vary along batch + u_mask_indices = np.reshape(u_mask_indices, (batch_size, -1)) + u_values = b.batched_gather_nd(u_values, np.expand_dims(u_mask_indices, -1)) + u_indices = np.stack([indices[b, u_mask_indices[b], :] for b in range(batch_size)]) + _, l_mask_indices = np.where(belongs_to_lower[..., 0]) + l_mask_indices = np.reshape(l_mask_indices, (batch_size, -1)) + l_values = b.batched_gather_nd(l_values, np.expand_dims(l_mask_indices, -1)) + l_indices = np.stack([indices[b, l_mask_indices[b], :] for b in range(batch_size)]) + return (l_indices, l_values), (u_indices, u_values) + else: # Keep all indices since the number in lower/upper varies along the batch + return (indices, l_values), (indices, u_values) + + +def get_index_in_row(row: np.ndarray, col: np.ndarray): + """ How many entries are to the left of a given entry but in the same row, i.e. the how manieth index this is per row. """ + perm = np.argsort(col) + compressed_col_index = [cumcount(row[b][perm[b]])[inv_perm(perm[b])] for b in range(row.shape[0])] + return np.stack(compressed_col_index) + + +def inv_perm(perm): + """ Returns the permutation necessary to undo a sort given the argsort array. """ + u = np.empty(perm.size, dtype=np.int64) + u[perm] = np.arange(perm.size) + return u + + +def cumcount(a): + """ Based on https://stackoverflow.com/questions/40602269/how-to-use-numpy-to-get-the-cumulative-count-by-unique-values-in-linear-time """ + def dfill(a): + """ Returns the positions where the array changes and repeats that index position until the next change. """ + b = np.concatenate([[0], np.where(a[:-1] != a[1:])[0] + 1, [a.size]]) + return np.arange(a.size)[b[:-1]].repeat(np.diff(b)) + perm = a.argsort(kind='mergesort') + inv = inv_perm(perm) + return (np.arange(a.size) - dfill(a[perm]))[inv] + + +def cumcount2(l): # slightly slower than cumcount + a = np.unique(l, return_counts=True)[1] + idx = a.cumsum() + id_arr = np.ones(idx[-1], dtype=int) + id_arr[0] = 0 + id_arr[idx[:-1]] = -a[:-1] + 1 + rng = id_arr.cumsum() + return rng[inv_perm(np.argsort(l))] + + +def get_transposed_indices(row, col, shape): + linear = np.ravel_multi_index((row, col), shape) + linear_transposed = np.ravel_multi_index((col, row), shape) + has_transpose = np.stack([np.isin(linear[b], linear_transposed[b]) for b in range(row.shape[0])]) + perm = np.argsort(linear) + transposed = np.stack([np.searchsorted(linear[b], linear_transposed[b], sorter=perm[b]) for b in range(row.shape[0])]) + transposed = np.minimum(transposed, len(row) - 1) + return has_transpose, transposed + + +def get_lower_diagonal_indices(row, col, shape): + linear = np.ravel_multi_index((row, col), shape) + j = np.minimum(row, col) + diagonal_indices = np.ravel_multi_index((j, j), shape) + perm = np.argsort(linear) + result = [perm[b, np.searchsorted(linear[b], diagonal_indices[b], sorter=perm[b])] for b in range(row.shape[0])] + assert np.all([np.isin(diagonal_indices[b], linear[b]) for b in range(row.shape[0])]), "All diagonal elements must be present in sparse matrix." + return np.stack(result) From 615fe15350f996cae3e75a488cd93c3deaa9bae4 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Thu, 9 Feb 2023 23:04:01 +0100 Subject: [PATCH 111/170] [math] Shape improvements ' Improve/fix shape_stack * Improve Shape debug checks --- phi/math/_shape.py | 36 ++++++++++++++++------------ tests/commit/math/test__magic_ops.py | 2 +- tests/commit/math/test__nd.py | 2 +- tests/commit/math/test__ops.py | 2 +- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/phi/math/_shape.py b/phi/math/_shape.py index ea42afd4f..4cbcf3374 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -66,9 +66,12 @@ def __init__(self, sizes: tuple, names: tuple, types: tuple, item_names: tuple): assert isinstance(self.item_names, tuple) assert all([items is None or isinstance(items, tuple) for items in self.item_names]) assert all([items is None or all([isinstance(n, str) for n in items]) for items in self.item_names]) - for size in sizes: - if size is not None and not isinstance(size, int): + from ._tensors import Tensor + for name, size in zip(names, sizes): + if size is not None and isinstance(size, Tensor): assert size.rank > 0 + # for dim in size.shape.names: + # assert dim in self.names, f"Dimension {name} varies along {dim} but {dim} is not part of the Shape {self}" def _to_dict(self, include_sizes=True): result = dict(names=self.names, types=self.types, item_names=self.item_names) @@ -1713,10 +1716,10 @@ def concat_shapes(*shapes: Shape or Any) -> Shape: def shape_stack(stack_dim: Shape, *shapes: Shape): """ Returns the shape of a tensor created by stacking tensors with `shapes`. """ - names = list(shapes[0].names) - types = list(shapes[0].types) - item_names = list(shapes[0].item_names) - for other in shapes[1:]: + names = list(stack_dim.names) + types = list(stack_dim.types) + item_names = list(stack_dim.item_names) + for other in shapes: for size, name, type, items in other._dimensions: if name not in names: if type in types: @@ -1744,16 +1747,19 @@ def shape_stack(stack_dim: Shape, *shapes: Shape): item_names[index] = None sizes = [] for name in names: - dim_sizes = [(shape.get_size(name) if name in shape else 1) for shape in shapes] - if all([math.close(s, dim_sizes[0]) for s in dim_sizes[1:]]): - dim_sizes = dim_sizes[0] + if name == stack_dim.name: + size = len(shapes) else: - from ._magic_ops import stack - from ._tensors import wrap - dim_sizes = [wrap(d) for d in dim_sizes] - dim_sizes = stack(dim_sizes, stack_dim) - sizes.append(dim_sizes) - return Shape(tuple(sizes), tuple(names), tuple(types), tuple(item_names))._expand(stack_dim.with_sizes([len(shapes)], keep_item_names=True)) + dim_sizes = [(shape.get_size(name) if name in shape else 1) for shape in shapes] + if all([math.close(s, dim_sizes[0]) for s in dim_sizes[1:]]): + size = dim_sizes[0] + else: + from ._magic_ops import stack + from ._tensors import wrap + dim_sizes = [wrap(d) for d in dim_sizes] + size = stack(dim_sizes, stack_dim) + sizes.append(size) + return Shape(tuple(sizes), tuple(names), tuple(types), tuple(item_names)) def vector_add(*shapes: Shape): diff --git a/tests/commit/math/test__magic_ops.py b/tests/commit/math/test__magic_ops.py index 058191647..a90b3b00e 100644 --- a/tests/commit/math/test__magic_ops.py +++ b/tests/commit/math/test__magic_ops.py @@ -149,7 +149,7 @@ def test_stack(self): def test_stack_expand(self): v = stack([0, linspace(0, 1, instance(points=10))], channel(vector='x,y'), expand_values=True) - self.assertEqual(instance(points=10) & channel(vector='x,y'), shape(v)) + self.assertEqual(set(instance(points=10) & channel(vector='x,y')), set(shape(v))) def test_multi_dim_stack(self): for test_class in TEST_CLASSES: diff --git a/tests/commit/math/test__nd.py b/tests/commit/math/test__nd.py index 21065551e..f6ddf11d3 100644 --- a/tests/commit/math/test__nd.py +++ b/tests/commit/math/test__nd.py @@ -258,4 +258,4 @@ def test_dim_mask(self): def test_vec_expand(self): v = math.vec(x=0, y=math.linspace(0, 1, instance(points=10))) - self.assertEqual(instance(points=10) & channel(vector='x,y'), v.shape) + self.assertEqual(set(instance(points=10) & channel(vector='x,y')), set(v.shape)) diff --git a/tests/commit/math/test__ops.py b/tests/commit/math/test__ops.py index 13c2523c7..8938fe914 100644 --- a/tests/commit/math/test__ops.py +++ b/tests/commit/math/test__ops.py @@ -51,7 +51,7 @@ def test_stack_missing_batch(self): t = math.random_normal(instance(particles=2)) b = math.expand(t, batch(b=2)) s = math.stack([t, b], channel(c='t,b')) - self.assertEqual(batch(b=2) & instance(particles=2) & channel(c='t,b'), s.shape) + self.assertEqual(set(batch(b=2) & instance(particles=2) & channel(c='t,b')), set(s.shape)) def test_nonzero(self): c = math.concat([math.zeros(spatial(b=3, a=2)), math.ones(spatial(a=2, b=4))], spatial('b')) From 91db31b33d889f5eec434cd0f72b14c057b2232a Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 12:57:09 +0100 Subject: [PATCH 112/170] [math] Improved error in native() --- phi/math/_tensors.py | 1 + 1 file changed, 1 insertion(+) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index 73f7ecc91..c3ac575ab 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -1257,6 +1257,7 @@ def native(self, order: str or tuple or list or Shape = None): else: native = self._inner.native(order=order) multiples = [1 if name in self._inner.shape else (self.shape.get_size(name) if name in self.shape else 1) for name in order] + assert all(isinstance(m, int) for m in multiples), f"Cannot get native representation of Tensor {self.shape} because Shape is non-uniform" tiled = choose_backend(native).tile(native, multiples) return tiled From ee41311f4b43833a5c6b63ddba3bb30bab5931cf Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 13:14:53 +0100 Subject: [PATCH 113/170] [vis] Fix line colors (Matplotlib) --- phi/vis/_matplotlib/_matplotlib_plots.py | 26 +++++++++++++++--------- phi/vis/_vis_base.py | 10 +++++---- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index 31226d92e..e132adff6 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -167,16 +167,19 @@ def can_plot(self, data: SampledField, space: Box) -> bool: def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, max_val: float, show_color_bar: bool, color: Tensor): x = data.points.staggered_direction[0].vector[0].numpy() requires_legend = False - for c in channel(data).meshgrid(names=True): - label = ", ".join([i for dim, i in c.items() if isinstance(i, str)]) - values = data.values[c].numpy() - color = _default_color(len(subplot.lines)) + if (color == None).all: + color = math.range_tensor(channel(data)) + for c_idx, c_idx_n in zip(channel(data).meshgrid(), channel(data).meshgrid(names=True)): + label = index_label(c_idx_n) + values = data.values[c_idx].numpy() + col = _rgba(color[c_idx]) + # color = _default_color(len(subplot.lines)) if values.dtype in (np.complex64, np.complex128): - subplot.plot(x, values.real, label=f"real({label})" if label else "real", color=color) - subplot.plot(x, values.imag, '--', label=f"imag({label})" if label else "imag", color=color) + subplot.plot(x, values.real, label=f"{label} real" if label else "real", color=col) + subplot.plot(x, values.imag, '--', label=f"{label} imag" if label else "imag", color=col) requires_legend = True else: - subplot.plot(x, values, label=label, color=color) + subplot.plot(x, values, label=label, color=col) requires_legend = requires_legend or label if requires_legend: subplot.legend() @@ -291,11 +294,13 @@ def plot(self, data: PointCloud, figure, subplot, space: Box, min_val: float, ma vector = data.bounds.shape['vector'] channels = channel(data.points).without('vector') legend_patches = [] - for idx in channels.meshgrid(names=True): + if (color == None).all: + color = math.range_tensor(channels) + for idx, idx_n in zip(channels.meshgrid(), channels.meshgrid(names=True)): col = color[idx] PointCloud2D._plot_points(subplot, data[idx], dims, vector, col) if col.rank < color.rank: # There are multiple colors - legend_patches.append(Patch(color=_rgba(col), label=index_label(idx))) + legend_patches.append(Patch(color=_rgba(col), label=index_label(idx_n))) if legend_patches: subplot.legend(handles=legend_patches) @@ -326,7 +331,8 @@ def _plot_points(axis, data: PointCloud, dims, vector, color): x, y = math.reshaped_numpy(data.points.vector[dims], [vector, instance(data), spatial(data)]) mpl_colors = matplotlib_colors(color, instance(data)) for i in range(instance(data).volume): - axis.plot(x[i], y[i], color=mpl_colors[i] if mpl_colors is not None else None) + marker = 'o' if isinstance(data.elements, Point) and spatial(data.elements).volume > 2 else None + axis.plot(x[i], y[i], marker=marker, markersize=2.5, color=mpl_colors[i] if mpl_colors is not None else None) if any(non_channel(data).item_names): PointCloud2D._annotate_points(axis, data.points) diff --git a/phi/vis/_vis_base.py b/phi/vis/_vis_base.py index 2afa8a711..033200aa8 100644 --- a/phi/vis/_vis_base.py +++ b/phi/vis/_vis_base.py @@ -409,7 +409,9 @@ def gui_interrupt(*args, **kwargs): raise GuiInterrupt() -def display_name(python_name): +def display_name(python_name: Any): + if isinstance(python_name, (int, bool)): + return str(python_name) n = list(python_name) n[0] = n[0].upper() for i in range(1, len(n)): @@ -428,13 +430,13 @@ def index_label(idx: dict) -> str or None: if len(idx) == 0: return None if len(idx) == 1: - return str(next(iter(idx.values()))) + return display_name(next(iter(idx.values()))) else: number_unlabelled_dims = len([1 for k, v in idx.items() if isinstance(v, int)]) if number_unlabelled_dims <= 1: - return " ".join(idx.values()) + return " ".join([display_name(n) for n in idx.values()]) else: - return ", ".join(f'{k}={v}' for k, v in idx.items()) + return ", ".join(f'{k}={display_name(v)}' for k, v in idx.items()) def select_channel(value: SampledField or Tensor or tuple or list, channel: str or None): From 40643070509b74f1da2b7ad9ae17d79aa1254ab1 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 14:09:08 +0100 Subject: [PATCH 114/170] [fluid] Make Obstacle a PhiTreeNode --- phi/geom/_box.py | 2 ++ phi/physics/fluid.py | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/phi/geom/_box.py b/phi/geom/_box.py index fe66c5760..b3380bee1 100644 --- a/phi/geom/_box.py +++ b/phi/geom/_box.py @@ -232,6 +232,8 @@ def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Geometry': return Geometry.__stack__(values, dim, **kwargs) def __eq__(self, other): + if self._lower is None and self._upper is None: + return isinstance(other, BaseBox) return isinstance(other, BaseBox)\ and set(self.shape) == set(other.shape)\ and self.size.shape.get_size('vector') == other.size.shape.get_size('vector')\ diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index c48595834..e268fa463 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -3,6 +3,7 @@ The main function for incompressible fluids (Eulerian as well as FLIP / PIC) is `make_incompressible()` which removes the divergence of a velocity field. """ +import warnings from typing import Tuple, Callable from phi import math, field @@ -40,14 +41,16 @@ def is_stationary(self): return isinstance(self.velocity, (int, float)) and self.velocity == 0 and isinstance(self.angular_velocity, (int, float)) and self.angular_velocity == 0 def copied_with(self, **kwargs): - geometry, velocity, angular_velocity = self.geometry, self.velocity, self.angular_velocity - if 'geometry' in kwargs: - geometry = kwargs['geometry'] - if 'velocity' in kwargs: - velocity = kwargs['velocity'] - if 'angular_velocity' in kwargs: - angular_velocity = kwargs['angular_velocity'] - return Obstacle(geometry, velocity, angular_velocity) + warnings.warn("Obstacle.copied_with is deprecated. Use math.copy_with instead.", DeprecationWarning, stacklevel=2) + return math.copy_with(self, **kwargs) + + def __variable_attrs__(self) -> Tuple[str, ...]: + return 'geometry', 'velocity', 'angular_velocity' + + def __eq__(self, other): + if not isinstance(other, Obstacle): + return False + return self.geometry == other.geometry and self.velocity == other.velocity and self.angular_velocity == other.angular_velocity def _get_obstacles_for(obstacles, space: Field): From d45b2abfe313aa9920c36a1c0e7638d68aa7eb37 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 14:15:07 +0100 Subject: [PATCH 115/170] [math] Add trace_check() --- phi/math/__init__.py | 1 + phi/math/_functional.py | 46 +++++++++++++++++++++++++++ tests/commit/math/test__functional.py | 14 ++++++++ 3 files changed, 61 insertions(+) diff --git a/phi/math/__init__.py b/phi/math/__init__.py index 1bd1f5b26..424569bc5 100644 --- a/phi/math/__init__.py +++ b/phi/math/__init__.py @@ -63,6 +63,7 @@ map_types, map_s2b, map_i2b, iterate, identity, + trace_check, ) from ._optimize import solve_linear, solve_nonlinear, minimize, Solve, SolveInfo, ConvergenceException, NotConverged, Diverged, SolveTape from ._nd import ( diff --git a/phi/math/_functional.py b/phi/math/_functional.py index 9c40ff8b2..7aee2ab7f 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -910,6 +910,52 @@ def print_grad(params: dict, _y, dx): return identity(value) +def trace_check(f, *args, **kwargs): + """ + Tests if `f(*args, **kwargs)` has already been traced. + If true, jit-compiled functions are very fast since the Python function is not actually called anymore. + + Args: + f: Transformed Function, e.g. jit-compiled or linear function. + *args: Hypothetical arguments to be passed to `f` + **kwargs: Hypothetical keyword arugments to be passed to `f` + + Returns: + result: `True` if there is an existing trace that can be used, `False` if `f` would have to be re-traced. + reason: Message giving hints as to why `f` needs to be re-traced given `args` and `kwargs`. + """ + if isinstance(f, (JitFunction, GradientFunction, HessianFunction, CustomGradientFunction)): + keys = f.traces.keys() + elif isinstance(f, LinearFunction): + keys = f.matrices_and_biases.keys() + else: + raise ValueError(f"{f_name(f)} is not a traceable function. Only supports jit_compile, jit_compile_linear, functional_gradient, custom_gradient, jacobian, hessian") + key, *_ = key_from_args(args, kwargs, f.f_params, aux=f.auxiliary_args) + if not keys: + return False, "Function has not yet been traced" + if key in keys: + return True, "" + traced_key = next(iter(keys)) # ToDo compare against all + cond_equal = key.auxiliary_kwargs == traced_key.auxiliary_kwargs + if isinstance(cond_equal, Tensor): + cond_equal = cond_equal.all + if not cond_equal: + return False, "Auxiliary arguments do not match" + # shapes need not be compared because they are included in specs + if traced_key.tree.keys() != key.tree.keys(): + return False, f"Different primary arguments passed: {set(traced_key.tree.keys())} vs {set(key.tree.keys())}" + for name in traced_key.tree.keys(): + if traced_key.tree[name] != key.tree[name]: + return False, f"Primary argument '{name}' differs in non-traced variables: {traced_key.tree[name]} vs {key.tree[name]}. Make sure the corresponding class overrides __eq__()." + if traced_key.specs != key.specs: + return False, "Traced variables differ in shape" + if traced_key.backend != key.backend: + return False, f"Function was not traced with backend {key.backend}" + if traced_key.spatial_derivative_order != key.spatial_derivative_order: + return False, f"Different in spatial_derivative_order. This is likely an internal problem." + return True + + def map_types(f: Callable, dims: Shape or tuple or list or str or Callable, dim_type: Callable or str) -> Callable: """ Wraps a function to change the dimension types of its `Tensor` and `PhiTreeNode` arguments. diff --git a/tests/commit/math/test__functional.py b/tests/commit/math/test__functional.py index 2781a70c8..978b6ad5c 100644 --- a/tests/commit/math/test__functional.py +++ b/tests/commit/math/test__functional.py @@ -366,3 +366,17 @@ def f(x, y): self.assertTrue(f_.forget_traces) f_ = jit()(f) self.assertFalse(f_.forget_traces) + + def test_trace_check(self): + @math.jit_compile(auxiliary_args='aux') + def f(x, aux): + return x * aux + + for backend in [b for b in BACKENDS if b.supports(Backend.jit_compile)]: + with backend: + x0 = math.zeros() + aux0 = 1 + self.assertFalse(math.trace_check(f, x0, aux0)[0]) + f(x0, aux0) + self.assertTrue(math.trace_check(f, x0, aux0)[0]) + self.assertTrue(math.trace_check(f, x=x0, aux=aux0)[0]) From 367aed32731a01f86569c928184b343bc58c0003 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 15:07:42 +0100 Subject: [PATCH 116/170] [math] Make dims vararg in unpack_dim() --- phi/math/_magic_ops.py | 5 +++-- phi/math/magic.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/phi/math/_magic_ops.py b/phi/math/_magic_ops.py index da4b1667c..6bacd78aa 100644 --- a/phi/math/_magic_ops.py +++ b/phi/math/_magic_ops.py @@ -417,7 +417,7 @@ def pack_dims(value, dims: DimFilter, packed_dim: Shape, pos: int or None = None -def unpack_dim(value, dim: str or Shape, unpacked_dims: Shape, **kwargs): +def unpack_dim(value, dim: str or Shape, *unpacked_dims: Shape, **kwargs): """ Decompresses a dimension by unstacking the elements along it. This function replaces the traditional `reshape` for these cases. @@ -431,7 +431,7 @@ def unpack_dim(value, dim: str or Shape, unpacked_dims: Shape, **kwargs): Args: value: `phi.math.magic.Shapable`, such as `Tensor`, for which one dimension should be split. dim: Dimension to be decompressed. - unpacked_dims: `Shape`: Ordered dimensions to replace `dim`, fulfilling `unpacked_dims.volume == shape(self)[dim].rank`. + *unpacked_dims: Vararg `Shape`, ordered dimensions to replace `dim`, fulfilling `unpacked_dims.volume == shape(self)[dim].rank`. **kwargs: Additional keyword arguments required by specific implementations. Adding spatial dimensions to fields requires the `bounds: Box` argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments. @@ -451,6 +451,7 @@ def unpack_dim(value, dim: str or Shape, unpacked_dims: Shape, **kwargs): assert isinstance(dim, str), f"dim must be a str or Shape but got {type(dim)}" if dim not in shape(value): return value # Nothing to do, maybe expand? + unpacked_dims = concat_shapes(*unpacked_dims) if unpacked_dims.rank == 0: return value[{dim: 0}] # remove dim elif unpacked_dims.rank == 1: diff --git a/phi/math/magic.py b/phi/math/magic.py index c501ec531..7fbd9c48c 100644 --- a/phi/math/magic.py +++ b/phi/math/magic.py @@ -607,7 +607,7 @@ def replace(self, dim: Shape, **kwargs): from ._magic_ops import rename_dims return rename_dims(self.obj, self.name, dim, **kwargs) - def unpack(self, dims: Shape, **kwargs): + def unpack(self, *dims: Shape, **kwargs): """ Returns a shallow copy of the `Tensor` where this dimension has been unpacked into `dims`. @@ -615,7 +615,7 @@ def unpack(self, dims: Shape, **kwargs): `phi.math.unpack_dim()` """ from ._magic_ops import unpack_dim - return unpack_dim(self.obj, self.name, dims, **kwargs) + return unpack_dim(self.obj, self.name, *dims, **kwargs) class _BoundDims: From 1f73be4f26e81964d96f50f6648cb4ffc98ef09c Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 16:06:39 +0100 Subject: [PATCH 117/170] [math] Return Tensor in Tensor-None comparison --- phi/math/_tensors.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index c3ac575ab..cff8768e8 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -628,11 +628,15 @@ def __rmod__(self, other): def __eq__(self, other): if _EQUALITY_BY_REF: return wrap(self is other) + if other is None: + other = float('nan') return self._op2(other, lambda x, y: x == y, lambda x, y: choose_backend(x, y).equal(x, y), 'eq', '==') def __ne__(self, other): if _EQUALITY_BY_REF: return wrap(self is not other) + if other is None: + other = float('nan') return self._op2(other, lambda x, y: x != y, lambda x, y: choose_backend(x, y).not_equal(x, y), 'ne', '!=') def __lt__(self, other): From 65c4ad4c111b103d1a5246ff92dd4ce1bce0da81 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 16:30:47 +0100 Subject: [PATCH 118/170] [field] Fix PointCloud soft sampling with extrapolation / values --- phi/field/_point_cloud.py | 17 +++++++++++++---- phi/math/_tensors.py | 13 ++++++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/phi/field/_point_cloud.py b/phi/field/_point_cloud.py index e3fd59eaf..c1db75ec8 100644 --- a/phi/field/_point_cloud.py +++ b/phi/field/_point_cloud.py @@ -1,14 +1,15 @@ import warnings from typing import Any, Tuple, Union -from phi.math import wrap, expand, non_batch +from phi.math import wrap, expand, non_batch, extrapolation, spatial from phi import math from phi.geom import Geometry, GridCell, Box, Point from ._field import SampledField, resample from ..geom._stack import GeometryStack from ..math import Tensor, instance, Shape -from ..math.extrapolation import Extrapolation, ConstantExtrapolation +from ..math._tensors import may_vary_along +from ..math.extrapolation import Extrapolation, ConstantExtrapolation, PERIODIC from ..math.magic import slicing_dict @@ -52,6 +53,7 @@ def __init__(self, bounds: (optional) size of the fixed domain in which the points should get visualized. None results in max and min coordinates of points. """ SampledField.__init__(self, elements, expand(wrap(values), non_batch(elements).non_channel), extrapolation, bounds) + assert self._extrapolation is PERIODIC or isinstance(self._extrapolation, ConstantExtrapolation), f"Unsupported extrapolation for PointCloud: {self._extrapolation}" self._add_overlapping = add_overlapping @property @@ -134,15 +136,22 @@ def _sample(self, geometry: Geometry, soft=False, scatter=False, outside_handlin if isinstance(geometry, GeometryStack): sampled = [self._sample(g, soft, scatter, outside_handling, balance) for g in geometry.geometries] return math.stack(sampled, geometry.geometries.shape) + if self.extrapolation is extrapolation.PERIODIC: + raise NotImplementedError("Periodic PointClouds not yet supported") if isinstance(geometry, GridCell) and scatter: assert not soft, "Cannot soft-sample when scatter=True" return self.grid_scatter(geometry.bounds, geometry.resolution, outside_handling) else: assert not isinstance(self._elements, Point), "Cannot sample Point-like elements with scatter=False" + if may_vary_along(self._values, instance(self._values) & spatial(self._values)): + raise NotImplementedError("Non-scatter resampling not yet supported for varying values") + idx0 = (instance(self._values) & spatial(self._values)).first_index() + outside = self._extrapolation.value if isinstance(self._extrapolation, ConstantExtrapolation) else 0 if soft: - return self.elements.approximate_fraction_inside(geometry, balance) + frac_inside = self.elements.approximate_fraction_inside(geometry, balance) + return frac_inside * self._values[idx0] + (1 - frac_inside) * outside else: - return math.to_float(self.elements.lies_inside(geometry.center)) + return math.where(self.elements.lies_inside(geometry.center), self._values[idx0], outside) def grid_scatter(self, bounds: Box, resolution: math.Shape, outside_handling: str): """ diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index cff8768e8..a803b9f55 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -14,7 +14,7 @@ from ._shape import (Shape, CHANNEL_DIM, BATCH_DIM, SPATIAL_DIM, EMPTY_SHAPE, parse_dim_order, shape_stack, merge_shapes, channel, concat_shapes, - TYPE_ABBR, IncompatibleShapes, INSTANCE_DIM, batch, spatial, dual, instance) + TYPE_ABBR, IncompatibleShapes, INSTANCE_DIM, batch, spatial, dual, instance, shape, DimFilter) from .backend import NoBackendFound, choose_backend, BACKENDS, get_precision, default_backend, convert as convert_, \ Backend, ComputeDevice from .backend._dtype import DType, combine_types @@ -2526,5 +2526,12 @@ def is_scalar(value) -> bool: elif isinstance(value, numbers.Number): return True else: - shape = choose_backend(value).staticshape(value) - return len(shape) == 0 + return len(choose_backend(value).staticshape(value)) == 0 + + +def may_vary_along(value, dims: DimFilter): + if isinstance(value, CollapsedTensor) and value._inner is not None: + return may_vary_along(value._inner, dims) + s = shape(value) + dims = s.only(dims) + return dims.volume > 1 From 172aca680b77a80985766e4074cf7a76b53444b3 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 17:37:11 +0100 Subject: [PATCH 119/170] [math] Support tuple/list in Shape --- phi/math/_shape.py | 5 +++-- tests/commit/math/test__shape.py | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 4cbcf3374..8735a03c8 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -109,8 +109,9 @@ def __len__(self): return len(self.sizes) def __contains__(self, item): - if isinstance(item, str): - return item in self.names + if isinstance(item, (str, tuple, list)): + dims = parse_dim_order(item) + return all(dim in self.names for dim in dims) elif isinstance(item, Shape): return all([d in self.names for d in item.names]) else: diff --git a/tests/commit/math/test__shape.py b/tests/commit/math/test__shape.py index 53c2604bb..1b04449f7 100644 --- a/tests/commit/math/test__shape.py +++ b/tests/commit/math/test__shape.py @@ -146,3 +146,9 @@ def test_with_size_item_names(self): def test_dual_prefix(self): d = dual('~y,z', x=5) self.assertEqual(('~y', '~z', '~x'), d.names) + + def test_contains(self): + s = batch(batch=10) & spatial(x=4, y=3) & channel(vector=2) + self.assertTrue('x,y,batch' in s) + self.assertTrue(['vector', 'batch'] in s) + self.assertFalse(['other', 'batch'] in s) From 0a8d10bd749e166f7f9f7ad451feb428f75a1082 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 17:38:14 +0100 Subject: [PATCH 120/170] [math] Limit BoundDims length to 10 to avoid recursion limit --- phi/math/magic.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phi/math/magic.py b/phi/math/magic.py index 7fbd9c48c..bec4c729b 100644 --- a/phi/math/magic.py +++ b/phi/math/magic.py @@ -635,6 +635,8 @@ def __getitem__(self, item): return self.obj[{dim: i for dim, i in zip(self.dims, item)}] def __getattr__(self, item): + if len(self.dims) > 10: # to avoid recursion limit + raise RuntimeError("Maximum BoundDim length reached") return _BoundDims(self.obj, self.dims + (item,)) def __len__(self): From 795dcb9996650e4943981784d51c69da1ad9d546 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 18:18:48 +0100 Subject: [PATCH 121/170] [math] Use index item names in gather/scatter --- phi/math/_ops.py | 71 ++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index ae847f835..6d859c397 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -1983,27 +1983,35 @@ def gather(values: Tensor, indices: Tensor, dims: DimFilter or None = None): Args: values: `Tensor` containing values to gather. - indices: `int` `Tensor`. Multi-dimensional position references in `values`. - Must contain a single channel dimension for the index vector matching the number of `dims`. - dims: Dimensions indexed by `indices`. - If `None`, will default to all spatial dimensions or all instance dimensions, depending on which ones are present (but not both). + indices: `int` `Tensor`. Multidimensional position references in `values`. + Must contain a single channel dimension for the index vector matching the number of dimensons to index. + This channel dimension should list the dimension names to index as item names unless explicitly specified as `dims`. + dims: (Optional) Dimensions indexed by `indices`. + Alternatively, the dimensions can be specified as the item names of the channel dimension of `indices`. + If `None` and no index item names are specified, will default to all spatial dimensions or all instance dimensions, depending on which ones are present (but not both). Returns: `Tensor` with combined batch dimensions, channel dimensions of `values` and spatial/instance dimensions of `indices`. """ + assert channel(indices).rank < 2, f"indices can at most have one channel dimension but got {indices.shape}" if dims is None: - assert values.shape.instance.is_empty or values.shape.spatial.is_empty, f"Specify gather dimensions for values with both instance and spatial dimensions. Got {values.shape}" - dims = values.shape.instance if values.shape.spatial.is_empty else values.shape.spatial + if channel(indices) and channel(indices).item_names[0]: + dims = channel(indices).item_names[0] + else: # Fallback to spatial / instance + warnings.warn(f"Indexing without item names is not recommended. Got indices {indices.shape}", SyntaxWarning, stacklevel=2) + assert values.shape.instance.is_empty or values.shape.spatial.is_empty, f"Specify gather dimensions for values with both instance and spatial dimensions. Got {values.shape}" + dims = values.shape.instance if values.shape.spatial.is_empty else values.shape.spatial if indices.dtype.kind == bool: indices = to_int32(indices) dims = parse_dim_order(dims) - batch = (values.shape.batch & indices.shape.batch).without(dims) - channel = values.shape.without(dims).without(batch) - native_values = reshaped_native(values, [batch, *dims, channel]) - native_indices = reshaped_native(indices, [batch, *indices.shape.non_batch.non_channel, indices.shape.channel]) + assert dims in values.shape, f"Trying to index non-existant dimensions with indices {indices.shape} into values {values.shape}" + batch_ = (values.shape.batch & indices.shape.batch).without(dims) + channel_ = values.shape.without(dims).without(batch_) + native_values = reshaped_native(values, [batch_, *dims, channel_]) + native_indices = reshaped_native(indices, [batch_, *indices.shape.non_batch.non_channel, channel(indices)]) backend = choose_backend(native_values, native_indices) native_result = backend.batched_gather_nd(native_values, native_indices) - result = reshaped_tensor(native_result, [batch, *indices.shape.non_channel.non_batch, channel]) + result = reshaped_tensor(native_result, [batch_, *indices.shape.non_channel.non_batch, channel_]) return result @@ -2054,24 +2062,32 @@ def scatter(base_grid: Tensor or Shape, assert outside_handling in ('discard', 'clamp', 'undefined') assert isinstance(indices_gradient, bool) grid_shape = base_grid if isinstance(base_grid, Shape) else base_grid.shape - assert indices.shape.channel.names == ('vector',) or (grid_shape.spatial_rank + grid_shape.instance_rank == 1 and indices.shape.channel_rank == 0) - if 'vector' in indices.shape and indices.shape.get_item_names('vector') and indices.shape.get_item_names('vector') != grid_shape.names: - indices = indices.vector[grid_shape.names] + assert channel(indices).rank < 2 + if channel(indices) and channel(indices).item_names[0]: + indexed_dims = channel(indices).item_names[0] + assert indexed_dims in grid_shape, f"Scatter indices {indices.shape} point to missing dimensions in grid {grid_shape}" + if indexed_dims != grid_shape.only(indexed_dims).names: + indices = indices.vector[grid_shape.only(indexed_dims).names] + indexed_dims = grid_shape.only(indexed_dims) + else: + assert channel(indices).rank == 1 or (grid_shape.spatial_rank + grid_shape.instance_rank == 1 and indices.shape.channel_rank == 0) + indexed_dims = grid_shape.spatial + assert channel(indices).volume == indexed_dims.rank values = wrap(values) batches = values.shape.non_channel.non_instance & indices.shape.non_channel.non_instance - channels = grid_shape.channel & values.shape.channel + channels = grid_shape.without(indexed_dims).without(batches) & values.shape.channel # --- Set up grid --- if isinstance(base_grid, Shape): with choose_backend_t(indices, values): - base_grid = zeros(base_grid & batches & values.shape.channel) + base_grid = zeros(base_grid & batches & values.shape.channel, dtype=values.dtype) if mode != 'add': base_grid += math.nan # --- Handle outside indices --- if outside_handling == 'clamp': - indices = clip(indices, 0, tensor(grid_shape.spatial, channel('vector')) - 1) + indices = clip(indices, 0, tensor(indexed_dims, channel('vector')) - 1) elif outside_handling == 'discard': indices_linear = pack_dims(indices, instance, instance(_scatter_instance=1)) - indices_inside = min_((round_(indices_linear) >= 0) & (round_(indices_linear) < tensor(grid_shape.spatial, channel('vector'))), 'vector') + indices_inside = min_((round_(indices_linear) >= 0) & (round_(indices_linear) < tensor(indexed_dims, channel('vector'))), 'vector') indices_linear = boolean_mask(indices_linear, '_scatter_instance', indices_inside) if instance(values).rank > 0: values_linear = pack_dims(values, instance, instance(_scatter_instance=1)) @@ -2084,7 +2100,7 @@ def scatter(base_grid: Tensor or Shape, def scatter_forward(base_grid, indices, values): indices = to_int32(round_(indices)) - native_grid = reshaped_native(base_grid, [batches, *non_batch(base_grid).non_channel, channels], force_expand=True) + native_grid = reshaped_native(base_grid, [batches, *indexed_dims, channels], force_expand=True) native_values = reshaped_native(values, [batches, lists, channels], force_expand=True) native_indices = reshaped_native(indices, [batches, lists, 'vector'], force_expand=True) backend = choose_backend(native_indices, native_values, native_grid) @@ -2096,20 +2112,17 @@ def scatter_forward(base_grid, indices, values): count = backend.scatter(zero_grid, native_indices, backend.ones_like(native_values), mode='add') native_result = summed / backend.maximum(count, 1) native_result = backend.where(count == 0, native_grid, native_result) - return reshaped_tensor(native_result, [batches, *non_batch(base_grid).non_channel, channels], check_sizes=True) + return reshaped_tensor(native_result, [batches, *indexed_dims, channels], check_sizes=True) - def scatter_backward(shaped_base_grid_, shaped_indices_, shaped_values_, output, d_output): + def scatter_backward(args: dict, _output, d_output): from ._nd import spatial_gradient - values_grad = gather(d_output, shaped_indices_) - spatial_gradient_indices = gather(spatial_gradient(d_output), shaped_indices_) - indices_grad = mean(spatial_gradient_indices * shaped_values_, 'vector_') + values_grad = gather(d_output, args['indices']) + spatial_gradient_indices = gather(spatial_gradient(d_output, dims=indexed_dims), args['indices']) + indices_grad = mean(spatial_gradient_indices * args['values'], 'vector_') return None, indices_grad, values_grad - scatter_function = scatter_forward - if indices_gradient: - from phi.math import custom_gradient - scatter_function = custom_gradient(scatter_forward, scatter_backward) - + from ._functional import custom_gradient + scatter_function = custom_gradient(scatter_forward, scatter_backward) if indices_gradient else scatter_forward result = scatter_function(base_grid, indices, values) return result From 7c34b96f7c4f21b409f3d913c1d973a719358515 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 18:20:52 +0100 Subject: [PATCH 122/170] [math] Matrix gradient for sparse linear solves * Add grad_for_f argument to solve_linear() * Add sparse tensor functionality --- phi/math/_ops.py | 2 +- phi/math/_optimize.py | 55 +++++++++++++++++++++++++++++------------ phi/math/_sparse.py | 57 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 87 insertions(+), 27 deletions(-) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 6d859c397..8eb5f9713 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -2011,7 +2011,7 @@ def gather(values: Tensor, indices: Tensor, dims: DimFilter or None = None): native_indices = reshaped_native(indices, [batch_, *indices.shape.non_batch.non_channel, channel(indices)]) backend = choose_backend(native_values, native_indices) native_result = backend.batched_gather_nd(native_values, native_indices) - result = reshaped_tensor(native_result, [batch_, *indices.shape.non_channel.non_batch, channel_]) + result = reshaped_tensor(native_result, [batch_, *indices.shape.non_channel.non_batch, channel_], convert=False) return result diff --git a/phi/math/_optimize.py b/phi/math/_optimize.py index c4e4894a1..d0b9af699 100644 --- a/phi/math/_optimize.py +++ b/phi/math/_optimize.py @@ -6,13 +6,13 @@ import numpy from ._shape import EMPTY_SHAPE, Shape, merge_shapes, batch, non_batch, shape, dual, channel, non_dual -from ._magic_ops import stack, copy_with -from ._sparse import native_matrix, SparseCoordinateTensor -from ._tensors import Tensor, disassemble_tree, assemble_tree, wrap, cached +from ._magic_ops import stack, copy_with, rename_dims +from ._sparse import native_matrix, SparseCoordinateTensor, CompressedSparseMatrix +from ._tensors import Tensor, disassemble_tree, assemble_tree, wrap, cached, NativeTensor from . import _ops as math from ._ops import choose_backend_t, zeros_like, all_available, reshaped_native, reshaped_tensor, to_float from ._trace import matrix_from_function -from ._functional import custom_gradient, LinearFunction +from ._functional import custom_gradient, LinearFunction, f_name from .backend import Backend from .backend._backend import SolveResult, PHI_LOGGER @@ -27,7 +27,7 @@ class Solve(Generic[X, Y]): """ def __init__(self, - method: str, + method: str or None, relative_tolerance: float or Tensor, absolute_tolerance: float or Tensor, max_iterations: int or Tensor = 1000, @@ -36,6 +36,7 @@ def __init__(self, preprocess_y: Callable = None, preprocess_y_args: tuple = (), gradient_solve: 'Solve[Y, X]' or None = None): + method = method or 'auto' assert isinstance(method, str) self.method: str = method """ Optimization method to use. Available solvers depend on the solve function that is used to perform the solve. """ @@ -416,6 +417,7 @@ def solve_linear(f: Callable[[X], Y], y: Y, solve: Solve[X, Y], *f_args, + grad_for_f=False, f_kwargs: dict = None, **f_kwargs_) -> X: """ @@ -465,9 +467,10 @@ def solve_linear(f: Callable[[X], Y], # --- Get input and output tensors --- y_tree, y_tensors = disassemble_tree(y) x0_tree, x0_tensors = disassemble_tree(solve.x0) + assert solve.x0 is not None, "Please specify the initial guess as Solve(..., x0=initial_guess)" assert len(x0_tensors) == len(y_tensors) == 1, "Only single-tensor linear solves are currently supported" backend = choose_backend_t(*y_tensors, *x0_tensors) - prefer_explicit = backend.supports(Backend.sparse_coo_tensor) or backend.supports(Backend.csr_matrix) + prefer_explicit = backend.supports(Backend.sparse_coo_tensor) or backend.supports(Backend.csr_matrix) or grad_for_f if isinstance(f, LinearFunction) and prefer_explicit: # Matrix solve matrix, bias = f.sparse_matrix_and_bias(solve.x0, *f_args, **f_kwargs) @@ -479,11 +482,12 @@ def _matrix_solve_forward(y, solve: Solve, matrix: Tensor, is_backprop=False): result = _linear_solve_forward(y, solve, backend_matrix, pattern_dims_in, pattern_dims_out, backend, is_backprop) return result # must return exactly `x` so gradient isn't computed w.r.t. other quantities - _matrix_solve = attach_gradient_solve(_matrix_solve_forward, auxiliary_args='is_backprop') + _matrix_solve = attach_gradient_solve(_matrix_solve_forward, auxiliary_args='is_backprop,solve', matrix_adjoint=grad_for_f) return _matrix_solve(y - bias, solve, matrix) else: # Matrix-free solve f_args = cached(f_args) solve = cached(solve) + assert not grad_for_f, f"grad_for_f=True can only be used for math.jit_compile_linear functions but got '{f_name(f)}'. Please decorate the linear function with @jit_compile_linear" def _function_solve_forward(y, solve: Solve, f_args: tuple, f_kwargs: dict = None, is_backprop=False): y_nest, (y_tensor,) = disassemble_tree(y) @@ -505,7 +509,7 @@ def native_lin_f(native_x, batch_index=None): result = _linear_solve_forward(y, solve, native_lin_f, pattern_dims_in=non_batch(x0_tensor).names, pattern_dims_out=non_batch(y_tensor).names, backend=backend, is_backprop=is_backprop) return result # must return exactly `x` so gradient isn't computed w.r.t. other quantities - _function_solve = attach_gradient_solve(_function_solve_forward, auxiliary_args='is_backprop,f_kwargs') + _function_solve = attach_gradient_solve(_function_solve_forward, auxiliary_args='is_backprop,f_kwargs,solve', matrix_adjoint=grad_for_f) return _function_solve(y, solve, f_args, f_kwargs=f_kwargs) @@ -566,17 +570,36 @@ def _linear_solve_forward(y, return x -def attach_gradient_solve(forward_solve: Callable, auxiliary_args: str): - def implicit_gradient_solve(kwargs, x, dx): - solve = kwargs['solve'] - matrix = (kwargs['matrix'],) if 'matrix' in kwargs else () +def attach_gradient_solve(forward_solve: Callable, auxiliary_args: str, matrix_adjoint: bool): + def implicit_gradient_solve(fwd_args: dict, x, dx): + solve = fwd_args['solve'] + matrix = (fwd_args['matrix'],) if 'matrix' in fwd_args else () + if matrix_adjoint: + assert matrix, "No matrix given but matrix_gradient=True" grad_solve = solve.gradient_solve x0 = grad_solve.x0 if grad_solve.x0 is not None else zeros_like(solve.x0) grad_solve_ = copy_with(solve.gradient_solve, x0=x0) - if 'is_backprop' in kwargs: - del kwargs['is_backprop'] - dy = solve_with_grad(dx, grad_solve_, *matrix, is_backprop=True, **kwargs) # this should hopefully result in implicit gradients for higher orders as well - return {'y': dy} + if 'is_backprop' in fwd_args: + del fwd_args['is_backprop'] + dy = solve_with_grad(dx, grad_solve_, *matrix, is_backprop=True, **fwd_args) # this should hopefully result in implicit gradients for higher orders as well + if matrix_adjoint: # matrix adjoint = dy * x^T sampled at indices + matrix = matrix[0] + if isinstance(matrix, CompressedSparseMatrix): + matrix = matrix.decompress() + if isinstance(matrix, SparseCoordinateTensor): + col = matrix.dual_indices(to_primal=True) + row = matrix.primal_indices() + dm_values = dy[col] * x[row] + dm = matrix._with_values(dm_values) + elif isinstance(matrix, NativeTensor): + dy_dual = rename_dims(dy, shape(dy), dual(**shape(dy).untyped_dict)) + dm = dy_dual * x # outer product + raise NotImplementedError("Matrix adjoint not yet supported for dense matrices") + else: + raise AssertionError + return {'y': dy, 'matrix': dm} + else: + return {'y': dy} solve_with_grad = custom_gradient(forward_solve, implicit_gradient_solve, auxiliary_args=auxiliary_args) return solve_with_grad diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index 4b5f97845..6682bb5b1 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -66,6 +66,9 @@ def native(self, order: str or tuple or list or Shape = None): def _is_tracer(self) -> bool: return self._indices._is_tracer or self._values._is_tracer + def _with_values(self, new_values: Tensor): + return SparseCoordinateTensor(self._indices, new_values, self._dense_shape, self._can_contain_double_entries, self._indices_sorted) + def _natives(self) -> tuple: indices_const = self._indices.default_backend is not self._values.default_backend if indices_const: @@ -109,6 +112,19 @@ def _native_coo_components(self, col_dims: DimFilter, matrix=False): native_values = reshaped_native(self._values, [ind_batch, instance, channels]) return ind_batch, channels, native_indices, native_values, native_shape + def dual_indices(self, to_primal=False): + """ Unpacked column indices """ + idx = self._indices[self._dense_shape.dual] + if to_primal: + dual_names = idx.shape.get_item_names('vector') + primal_names = spatial(*dual_names).names + idx = rename_dims(idx, 'vector', channel(vector=primal_names)) + return idx + + def primal_indices(self): + """ Unpacked row indices """ + return self._indices[self._dense_shape.non_dual] + def _pack_indices(self, row_dims: Shape, col_dims: Shape): assert self._indices.default_backend is NUMPY, "Can only compress NumPy indices as of yet" assert row_dims in self._dense_shape, f"Can only compress sparse dims but got {row_dims} which contains non-sparse dims" @@ -136,10 +152,11 @@ def compress(self, dims: DimFilter): assert c_idx_packed.shape[1] == len(scipy_csr.data), "Failed to create CSR matrix because the CSR matrix contains fewer non-zero values than COO. This can happen when the `x` tensor is too small for the stencil." # --- Construct CompressedSparseMatrix --- entries_dim = instance(self._values).name - values = self._values[{entries_dim: wrap(scipy_csr.data - 1, instance(entries_dim))}] # Change order accordingly + perm = {entries_dim: wrap(scipy_csr.data - 1, instance(entries_dim))} + values = self._values[perm] # Change order accordingly indices = wrap(scipy_csr.indices, instance(entries_dim)) pointers = wrap(scipy_csr.indptr, instance('pointers')) - return CompressedSparseMatrix(indices, pointers, values, u_dims, c_dims) + return CompressedSparseMatrix(indices, pointers, values, u_dims, c_dims, uncompressed_indices=self._indices, uncompressed_indices_perm=perm) def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or None, **kwargs) -> 'Tensor': dims = self._shape.only(dims) @@ -165,7 +182,15 @@ def _with_shape_replaced(self, new_shape: Shape): class CompressedSparseMatrix(Tensor): - def __init__(self, indices: Tensor, pointers: Tensor, values: Tensor, uncompressed_dims: Shape, compressed_dims: Shape, uncompressed_offset: int = None): + def __init__(self, + indices: Tensor, + pointers: Tensor, + values: Tensor, + uncompressed_dims: Shape, + compressed_dims: Shape, + uncompressed_offset: int = None, + uncompressed_indices: Tensor = None, + uncompressed_indices_perm: Tensor = None): """ Args: @@ -200,6 +225,8 @@ def __init__(self, indices: Tensor, pointers: Tensor, values: Tensor, uncompress self._uncompressed_dims = uncompressed_dims self._compressed_dims = compressed_dims self._uncompressed_offset = uncompressed_offset + self._uncompressed_indices = uncompressed_indices + self._uncompressed_indices_perm = uncompressed_indices_perm @property def shape(self) -> Shape: @@ -241,7 +268,10 @@ def _spec_dict(self) -> dict: 'pointers': self._pointers if pointers_const else self._pointers._spec_dict(), 'uncompressed_dims': self._uncompressed_dims, 'compressed_dims': self._compressed_dims, - 'uncompressed_offset': self._uncompressed_offset} + 'uncompressed_offset': self._uncompressed_offset, + 'uncompressed_indices': self._uncompressed_indices, + 'uncompressed_indices_perm': self._uncompressed_indices_perm, + } @classmethod def _from_spec_and_natives(cls, spec: dict, natives: list): @@ -256,7 +286,7 @@ def _from_spec_and_natives(cls, spec: dict, natives: list): pointers = pointers_or_spec else: pointers = spec['pointers']['type']._from_spec_and_natives(spec['pointers'], natives) - return CompressedSparseMatrix(indices, pointers, values, spec['uncompressed_dims'], spec['compressed_dims'], spec['uncompressed_offset']) + return CompressedSparseMatrix(indices, pointers, values, spec['uncompressed_dims'], spec['compressed_dims'], spec['uncompressed_offset'], spec['uncompressed_indices'], spec['uncompressed_indices_perm']) def _getitem(self, selection: dict) -> 'Tensor': batch_selection = {dim: selection[dim] for dim in self._shape.only(tuple(selection)).names} @@ -369,6 +399,15 @@ def _native_csr_components(self): native_indices = choose_backend(native_indices).clip(native_indices, 0, self._uncompressed_dims.volume - 1) return ind_batch, channels, native_indices, native_pointers, native_values, native_shape + def decompress(self): + if self._uncompressed_indices is None: + self._uncompressed_indices = None + raise NotImplementedError() + if self._uncompressed_indices_perm is not None: + self._uncompressed_indices = self._uncompressed_indices[self._uncompressed_indices_perm] + self._uncompressed_indices_perm = None + return SparseCoordinateTensor(self._uncompressed_indices, self._values, self._compressed_dims & self._uncompressed_dims, False, False) + def native(self, order: str or tuple or list or Shape = None): raise RuntimeError("Sparse tensors do not have a native representation. Use math.dense(tensor).native() instead") @@ -386,6 +425,7 @@ def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or No else: raise NotImplementedError(f"Cannot pack dimensions from both columns and rows with compressed sparse matrices but got {dims}") + def sparse_dims(x: Tensor) -> Shape: """ Returns the dimensions of a `Tensor` that are explicitly stored in a sparse format. @@ -476,11 +516,8 @@ def dense(x: Tensor) -> Tensor: """ from phi.math import reshaped_tensor if isinstance(x, SparseCoordinateTensor): - from ._ops import scatter, zeros - base_grid = zeros(x.shape._with_types(SPATIAL_DIM), dtype=x.dtype) - result_sp = scatter(base_grid, x._indices, x._values, mode='add', outside_handling='undefined') - result = rename_dims(result_sp, result_sp.shape, x.shape) - return result + from ._ops import scatter + return scatter(x.shape, x._indices, x._values, mode='add', outside_handling='undefined') elif isinstance(x, CompressedSparseMatrix): ind_batch, channels, native_indices, native_pointers, native_values, native_shape = x._native_csr_components() native_dense = x.default_backend.csr_to_dense(native_indices, native_pointers, native_values, native_shape) From 812046ace111ebc72f5e14498c069b41b224f493 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 10 Feb 2023 23:05:31 +0100 Subject: [PATCH 123/170] [math] Support Jax sparse COO (non-batched) * Remove Backend.coordinates (unused and definition ambiguous) --- phi/_troubleshoot.py | 4 ++++ phi/jax/_jax_backend.py | 12 ++++++++++++ phi/math/backend/_backend.py | 14 -------------- phi/math/backend/_numpy_backend.py | 5 ----- phi/tf/_tf_backend.py | 7 ------- phi/torch/_torch_backend.py | 6 ------ tests/commit/math/backend/test__backend.py | 15 +++++++++++++-- tests/commit/math/test__ops.py | 15 --------------- 8 files changed, 29 insertions(+), 49 deletions(-) diff --git a/phi/_troubleshoot.py b/phi/_troubleshoot.py index 892e78198..29b045c60 100644 --- a/phi/_troubleshoot.py +++ b/phi/_troubleshoot.py @@ -1,6 +1,8 @@ from contextlib import contextmanager from os.path import dirname +import packaging.version + def assert_minimal_config(): # raises AssertionError import sys @@ -120,6 +122,8 @@ def troubleshoot_jax(): math.assert_close(math.ones() + math.ones(), 2) except BaseException as err: return f"Installed ({version}) but tests failed with error: {err}" + if packaging.version.parse(jax.__version__) < packaging.version.parse('0.2.20'): + return f"Installed ({version}), {gpu_count} GPUs available. This is an old version of Jax that may not support all required features, e.g. sparse matrices." return f"Installed ({version}), {gpu_count} GPUs available." diff --git a/phi/jax/_jax_backend.py b/phi/jax/_jax_backend.py index 9e4d189dd..44ee23639 100644 --- a/phi/jax/_jax_backend.py +++ b/phi/jax/_jax_backend.py @@ -2,6 +2,7 @@ import warnings from functools import wraps from typing import List, Callable, Tuple +from packaging import version import jax import jax.numpy as jnp @@ -11,6 +12,9 @@ from jax.core import Tracer from jax.interpreters.xla import DeviceArray +if version.parse(jax.__version__) >= version.parse('0.2.20'): + from jax.experimental.sparse import BCOO, COO, CSR, CSC + from phi.math import DType from phi.math.backend import Backend, ComputeDevice from phi.math.backend._backend import combined_dim, SolveResult, PHI_LOGGER, TensorType @@ -72,6 +76,8 @@ def is_tensor(self, x, only_native=False): return True if isinstance(x, jnp.bool_) and not isinstance(x, np.bool_): return True + if isinstance(x, (COO, BCOO, CSR, CSC)): + return True # --- Above considered native --- if only_native: return False @@ -307,6 +313,9 @@ def mul(self, a, b): return Backend.mul(self, a, b) def matmul(self, A, b): + from jax.experimental.sparse import BCOO + if isinstance(A, BCOO): + return(A @ b.T).T return jnp.stack([A.dot(b[i]) for i in range(b.shape[0])]) def while_loop(self, loop: Callable, values: tuple): @@ -450,5 +459,8 @@ def matrix_solve_least_squares(self, matrix: TensorType, rhs: TensorType) -> Tup solution, residuals, rank, singular_values = lstsq_batched(matrix, rhs) return solution, residuals, rank, singular_values + def sparse_coo_tensor(self, indices: tuple or list, values, shape: tuple): + return BCOO((values, indices), shape=shape) + lstsq_batched = jax.vmap(jnp.linalg.lstsq) # map first dimension, required for JaxBackend.matrix_solve_least_squares() diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 547d74d1c..6290e4ff4 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -1047,20 +1047,6 @@ def csc_matrix_batched(self, column_pointers, row_indices, values, shape: Tuple[ """ raise NotImplementedError(self) - def coordinates(self, tensor): - """ - Returns the coordinates and values of a tensor. - - Args: - tensor: Sparse tensor - - Returns: - coordinates: `tuple` of tensor holding the coordinate vectors, i.e. (row, col) for matrices. - indices: Tensor holding the corresponding values - - """ - raise NotImplementedError(self) - def pairwise_distances(self, positions, max_radius, format: str, index_dtype=DType(int, 32)) -> list: """ diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index 937178461..1b0b2ad96 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -370,11 +370,6 @@ def mul_csr_dense(self, column_indices, row_pointers, values, shape: tuple, dens def csc_matrix(self, column_pointers, row_indices, values, shape: tuple): return scipy.sparse.csc_matrix((values, row_indices, column_pointers), shape=shape) - def coordinates(self, tensor): - assert scipy.sparse.issparse(tensor) - coo = tensor.tocoo() - return (coo.row, coo.col), coo.data - def stop_gradient(self, value): return value diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index c10c8acfd..5015cd2e5 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -618,13 +618,6 @@ def mul_coo_dense(self, indices, values, shape, dense): result.append(tf.stack(b_result)) return tf.stack(result) - def coordinates(self, tensor): - assert isinstance(tensor, tf.SparseTensor) - idx = tensor.indices - with tf.device(idx.device): - idx = tuple(tf.unstack(idx, axis=-1)) - return idx, tensor.values - def not_equal(self, x, y): with self._device_for(x, y): return ~self.equal(x, y) diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index f3622c1c0..e9b083da8 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -699,12 +699,6 @@ def mul_csr_dense(self, column_indices, row_pointers, values, shape: tuple, dens # raise NotImplementedError - def coordinates(self, tensor): - assert isinstance(tensor, torch.Tensor) and tensor.is_sparse - idx = tensor._indices() - idx = self.unstack(idx, axis=0) - return idx, tensor._values() - def conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: if callable(lin) or trj: assert self.is_available(y), "Tracing conjugate_gradient with linear operator is not yet supported." diff --git a/tests/commit/math/backend/test__backend.py b/tests/commit/math/backend/test__backend.py index d2913efe4..8c48965d2 100644 --- a/tests/commit/math/backend/test__backend.py +++ b/tests/commit/math/backend/test__backend.py @@ -3,8 +3,7 @@ import numpy import phi -from phi.math.backend import ComputeDevice, convert - +from phi.math.backend import ComputeDevice, convert, Backend BACKENDS = phi.detect_backends() @@ -41,4 +40,16 @@ def test_gather(self): result = backend.gather(t, indices, axis=0) self.assertEqual((2, 3, 2), backend.staticshape(result)) + def test_sparse(self): + idx = [[0, 1, 1], + [2, 0, 2]] + v = [3, 4, 5] + shape = (2, 3) + for backend in BACKENDS: + if backend.supports(Backend.sparse_coo_tensor): + with backend: + idx_ = backend.transpose(backend.as_tensor(idx), [1, 0]) + matrix = backend.sparse_coo_tensor(idx_, v, shape) + self.assertTrue(backend.is_tensor(matrix), backend.name) + diff --git a/tests/commit/math/test__ops.py b/tests/commit/math/test__ops.py index 8938fe914..7b10c603e 100644 --- a/tests/commit/math/test__ops.py +++ b/tests/commit/math/test__ops.py @@ -644,21 +644,6 @@ def test_numpy(self): self.assertIs(math.numpy(nat), nat) assert_close(math.numpy(math.tensor(nat)), nat) - def test_sparse(self): - idx = [[0, 1, 1], - [2, 0, 2]] - v = [3, 4, 5] - shape = (2, 3) - for backend in BACKENDS: - if backend.supports(Backend.sparse_coo_tensor): - with backend: - idx_ = backend.transpose(backend.as_tensor(idx), [1, 0]) - matrix = backend.sparse_coo_tensor(idx_, v, shape) - i_, v_ = backend.coordinates(matrix) - self.assertIsInstance(i_, tuple, msg=backend.name) - assert len(i_) == 2 - assert len(v) == 3 - def test_rename_dims(self): t = math.zeros(spatial(x=5, y=4)) self.assertEqual(math.rename_dims(t, 'x', 'z').shape.get_type('z'), 'spatial') From adce0025e579a92e41b057337b927d593cea697e Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 11 Feb 2023 13:42:29 +0100 Subject: [PATCH 124/170] [math] Fix map() --- phi/math/_ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 8eb5f9713..c62e8cf5d 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -395,13 +395,13 @@ def map_(function, *values, range=range, **kwargs) -> Tensor or None: else: result.append(f_output) if results is None: - if None in result: + if any(r is None for r in result): assert all(r is None for r in result), f"map function returned None for some elements, {result}" return None return unpack_dim(wrap(result, channel('_c')), '_c', shape) else: for i, result_i in enumerate(results): - if None in result_i: + if any(r is None for r in result_i): assert all(r is None for r in result_i), f"map function returned None for some elements at output index {i}, {result_i}" results[i] = None return tuple([unpack_dim(wrap(result_i, channel('_c')), '_c', shape) for result_i in results]) From 65e16c3e7f6ca5ab67ad25ef8cb4034a60d1aa4b Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 11 Feb 2023 13:42:52 +0100 Subject: [PATCH 125/170] [io] Add create_parent parameter to Scene.subpath() --- phi/field/_scene.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phi/field/_scene.py b/phi/field/_scene.py index f316357ab..45934ae59 100644 --- a/phi/field/_scene.py +++ b/phi/field/_scene.py @@ -208,13 +208,14 @@ def at(directory: str or tuple or list or math.Tensor or 'Scene', id: int or mat raise IOError(f"There is no scene at '{path}'") return Scene(paths) - def subpath(self, name: str, create: bool = False) -> str or tuple: + def subpath(self, name: str, create=False, create_parent=False) -> str or tuple: """ Resolves the relative path `name` with this `Scene` as the root folder. Args: name: Relative path with this `Scene` as the root folder. create: Whether to create a directory of that name. + create_parent: Whether to create the parent directory. Returns: Relative path including the path to this `Scene`. @@ -222,6 +223,8 @@ def subpath(self, name: str, create: bool = False) -> str or tuple: """ def single_subpath(path): path = join(path, name) + if create_parent and not isdir(os.path.dirname(path)): + os.makedirs(os.path.dirname(path)) if create and not isdir(path): os.mkdir(path) return path From 38d35ade9c52603e9b0fb042565d094826676aca Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 11 Feb 2023 13:48:59 +0100 Subject: [PATCH 126/170] [math] Fix jit call inside linear trace --- phi/math/_functional.py | 12 +++++++++--- phi/math/_trace.py | 9 +++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/phi/math/_functional.py b/phi/math/_functional.py index 7aee2ab7f..740db79e0 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -7,7 +7,7 @@ import numpy as np from ._sparse import SparseCoordinateTensor, CompressedSparseMatrix -from ._trace import ShiftLinTracer, matrix_from_function +from ._trace import ShiftLinTracer, matrix_from_function, LinearTraceInProgress from .backend import Backend, NUMPY from .backend._backend import get_spatial_derivative_order, functional_derivative_evaluation, PHI_LOGGER from ._shape import EMPTY_SHAPE, Shape, vector_add, merge_shapes, spatial, instance, batch @@ -184,7 +184,10 @@ def jit_f_native(*natives): return in_key.backend.jit_compile(jit_f_native) def __call__(self, *args, **kwargs): - key, _, natives, _ = key_from_args(args, kwargs, self.f_params, cache=True, aux=self.auxiliary_args) + try: + key, _, natives, _ = key_from_args(args, kwargs, self.f_params, cache=True, aux=self.auxiliary_args) + except LinearTraceInProgress: + return self.f(*args, **kwargs) if isinstance(self.f, GradientFunction) and key.backend.supports(Backend.jit_compile_grad): return self.grad_jit(*args, **kwargs) if not key.backend.supports(Backend.jit_compile): @@ -309,7 +312,10 @@ def _get_or_trace(self, key: SignatureKey, args: tuple, f_kwargs: dict): return matrix, bias def __call__(self, *args: X, **kwargs) -> Y: - key, tensors, natives, x = key_from_args(args, kwargs, self.f_params, cache=False, aux=self.auxiliary_args) + try: + key, tensors, natives, x = key_from_args(args, kwargs, self.f_params, cache=False, aux=self.auxiliary_args) + except LinearTraceInProgress: + return self.f(*args, **kwargs) assert tensors, "Linear function requires at least one argument" if any(isinstance(t, ShiftLinTracer) for t in tensors): # TODO: if t is identity, use cached ShiftLinTracer, otherwise multiply two ShiftLinTracers diff --git a/phi/math/_trace.py b/phi/math/_trace.py index ef566e93e..5235375cd 100644 --- a/phi/math/_trace.py +++ b/phi/math/_trace.py @@ -206,6 +206,15 @@ def _natives(self) -> tuple: """ return sum([v._natives() for v in self.val.values()], ()) + self.bias._natives() + def _spec_dict(self) -> dict: + raise LinearTraceInProgress(self) + + +class LinearTraceInProgress(Exception): + + def __init__(self, tracer: ShiftLinTracer): + self.tracer = tracer + def simplify_add(val: dict) -> Dict[Shape, Tensor]: result = {} From bf6f99dc36b84494ccb71196ed8bce5785d54351 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 11 Feb 2023 14:22:06 +0100 Subject: [PATCH 127/170] [math] Default Solve tolerances * Sort test methods --- phi/math/_optimize.py | 42 ++++--- phi/math/backend/_backend.py | 2 + tests/commit/math/test__functional.py | 160 +----------------------- tests/commit/math/test__optimize.py | 168 ++++++++++++++++++++++++++ 4 files changed, 199 insertions(+), 173 deletions(-) create mode 100644 tests/commit/math/test__optimize.py diff --git a/phi/math/_optimize.py b/phi/math/_optimize.py index d0b9af699..04da0c495 100644 --- a/phi/math/_optimize.py +++ b/phi/math/_optimize.py @@ -5,13 +5,13 @@ import numpy +from .backend import get_precision from ._shape import EMPTY_SHAPE, Shape, merge_shapes, batch, non_batch, shape, dual, channel, non_dual from ._magic_ops import stack, copy_with, rename_dims from ._sparse import native_matrix, SparseCoordinateTensor, CompressedSparseMatrix from ._tensors import Tensor, disassemble_tree, assemble_tree, wrap, cached, NativeTensor from . import _ops as math from ._ops import choose_backend_t, zeros_like, all_available, reshaped_native, reshaped_tensor, to_float -from ._trace import matrix_from_function from ._functional import custom_gradient, LinearFunction, f_name from .backend import Backend from .backend._backend import SolveResult, PHI_LOGGER @@ -27,11 +27,11 @@ class Solve(Generic[X, Y]): """ def __init__(self, - method: str or None, - relative_tolerance: float or Tensor, - absolute_tolerance: float or Tensor, - max_iterations: int or Tensor = 1000, + method: str or None = 'auto', + relative_tolerance: float or Tensor = None, + absolute_tolerance: float or Tensor = None, x0: X or Any = None, + max_iterations: int or Tensor = 1000, suppress: tuple or list = (), preprocess_y: Callable = None, preprocess_y_args: tuple = (), @@ -40,11 +40,13 @@ def __init__(self, assert isinstance(method, str) self.method: str = method """ Optimization method to use. Available solvers depend on the solve function that is used to perform the solve. """ - self.relative_tolerance: Tensor = math.to_float(wrap(relative_tolerance)) - """ Relative tolerance for linear solves only. This must be `0` for minimization problems. + self.relative_tolerance: Tensor = math.to_float(wrap(relative_tolerance)) if relative_tolerance is not None else None + """Relative tolerance for linear solves only, defaults to 1e-5 for singe precision solves and 1e-12 for double precision solves. + This must be unset or `0` for minimization problems. For systems of equations *f(x)=y*, the final tolerance is `max(relative_tolerance * norm(y), absolute_tolerance)`. """ - self.absolute_tolerance: Tensor = math.to_float(wrap(absolute_tolerance)) + self.absolute_tolerance: Tensor = math.to_float(wrap(absolute_tolerance)) if absolute_tolerance is not None else None """ Absolut tolerance for optimization problems and linear solves. + Defaults to 1e-5 for singe precision solves and 1e-12 for double precision solves. For systems of equations *f(x)=y*, the final tolerance is `max(relative_tolerance * norm(y), absolute_tolerance)`. """ self.max_iterations: Tensor = math.to_int32(wrap(max_iterations)) """ Maximum number of iterations to perform before raising a `NotConverged` error is raised. """ @@ -70,7 +72,7 @@ def gradient_solve(self) -> 'Solve[Y, X]': In any case, the gradient solve information will be stored in `gradient_solve.result`. """ if self._gradient_solve is None: - self._gradient_solve = Solve(self.method, self.relative_tolerance, self.absolute_tolerance, self.max_iterations, None, self.suppress, self.preprocess_y, self.preprocess_y_args) + self._gradient_solve = Solve(self.method, self.relative_tolerance, self.absolute_tolerance, None, self.max_iterations, self.suppress, self.preprocess_y, self.preprocess_y_args) return self._gradient_solve def __repr__(self): @@ -92,6 +94,15 @@ def __variable_attrs__(self): return 'x0', 'preprocess_y_args' +def _default_tolerance(): + if get_precision() == 64: + return wrap(1e-12) + elif get_precision() == 32: + return wrap(1e-5) + else: + return wrap(1e-2) + + class SolveInfo(Generic[X, Y]): """ Stores information about the solution or trajectory of a solve. @@ -304,7 +315,7 @@ def minimize(f: Callable[[X], Y], solve: Solve[X, Y]) -> X: NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. Diverged: If the optimization failed prematurely. """ - assert (solve.relative_tolerance == 0).all, f"relative_tolerance must be zero for minimize() but got {solve.relative_tolerance}" + assert solve.relative_tolerance is None or (solve.relative_tolerance == 0).all, f"relative_tolerance must be zero for minimize() but got {solve.relative_tolerance}" assert solve.preprocess_y is None, "minimize() does not allow preprocess_y" x0_nest, x0_tensors = disassemble_tree(solve.x0) x0_tensors = [to_float(t) for t in x0_tensors] @@ -342,7 +353,7 @@ def native_function(x_flat): raise AssertionError(f"Failed to minimize '{f.__name__}' because its output loss {shape(y_tensors[0])} has more batch dimensions than the initial guess {batch_dims}.") return y_tensors[0].sum, (loss_native,) - atol = backend.to_float(reshaped_native(solve.absolute_tolerance, [batch_dims], force_expand=True)) + atol = backend.to_float(reshaped_native((solve.absolute_tolerance or _default_tolerance()), [batch_dims], force_expand=True)) maxi = backend.to_int32(reshaped_native(solve.max_iterations, [batch_dims], force_expand=True)) trj = _SOLVE_TAPES and any(t.record_trajectories for t in _SOLVE_TAPES) t = time.perf_counter() @@ -408,8 +419,9 @@ def min_func(x): l2 = l2_loss(diff) return l2 - rel_tol_to_abs = solve.relative_tolerance * l2_loss(y) - min_solve = copy_with(solve, absolute_tolerance=rel_tol_to_abs, relative_tolerance=0, preprocess_y=None) + rel_tol_to_abs = (_default_tolerance() if solve.relative_tolerance is None else solve.relative_tolerance) * l2_loss(y) + tol = math.maximum(rel_tol_to_abs, (_default_tolerance() if solve.absolute_tolerance is None else solve.absolute_tolerance)) + min_solve = copy_with(solve, absolute_tolerance=tol, relative_tolerance=0, preprocess_y=None) return minimize(min_func, min_solve) @@ -530,8 +542,8 @@ def _linear_solve_forward(y, batch_dims = merge_shapes(y_tensor.shape.without(pattern_dims_out), x0_tensor.shape.without(pattern_dims_in)) x0_native = backend.as_tensor(reshaped_native(x0_tensor, [batch_dims, pattern_dims_in], force_expand=True)) y_native = backend.as_tensor(reshaped_native(y_tensor, [batch_dims, y_tensor.shape.only(pattern_dims_out)], force_expand=True)) - rtol = backend.as_tensor(reshaped_native(math.to_float(solve.relative_tolerance), [batch_dims], force_expand=True)) - atol = backend.as_tensor(reshaped_native(solve.absolute_tolerance, [batch_dims], force_expand=True)) + rtol = backend.as_tensor(reshaped_native(math.to_float(_default_tolerance() if solve.relative_tolerance is None else solve.relative_tolerance), [batch_dims], force_expand=True)) + atol = backend.as_tensor(reshaped_native(_default_tolerance() if solve.absolute_tolerance is None else solve.absolute_tolerance, [batch_dims], force_expand=True)) maxi = backend.as_tensor(reshaped_native(solve.max_iterations, [batch_dims], force_expand=True)) trj = _SOLVE_TAPES and any(t.record_trajectories for t in _SOLVE_TAPES) if trj: diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 6290e4ff4..fc7a393de 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -1082,6 +1082,8 @@ def pairwise_distances(self, positions, max_radius, format: str, index_dtype=DTy return result def minimize(self, method: str, f, x0, atol, max_iter, trj: bool): + if method == 'auto': + method = 'L-BFGS-B' if method == 'GD': return self._minimize_gradient_descent(f, x0, atol, max_iter, trj) diff --git a/tests/commit/math/test__functional.py b/tests/commit/math/test__functional.py index 978b6ad5c..fdf541585 100644 --- a/tests/commit/math/test__functional.py +++ b/tests/commit/math/test__functional.py @@ -1,10 +1,10 @@ +import time from functools import partial from unittest import TestCase -import time import phi from phi import math -from phi.math import Solve, Diverged, tensor, SolveTape, extrapolation, spatial, batch, channel +from phi.math import tensor, spatial, batch, channel from phi.math.backend import Backend BACKENDS = phi.detect_backends() @@ -135,162 +135,6 @@ def loss(x): custom_loss_grad, = math.functional_gradient(loss, get_output=False)(math.ones(spatial(x=4))) math.assert_close(custom_loss_grad, 0, msg=backend.name) - def test_minimize(self): - def loss(x, y): - return math.l2_loss(x - 1) + math.l2_loss(y + 1) - - for backend in BACKENDS: - if backend.supports(Backend.jacobian): - with backend: - x0 = tensor([0, 0, 0], spatial('x')), tensor([-1, -1, -1], spatial('y')) - x, y = math.minimize(loss, math.Solve('L-BFGS-B', 0, 1e-3, x0=x0)) - math.assert_close(x, 1, abs_tolerance=1e-3, msg=backend.name) - math.assert_close(y, -1, abs_tolerance=1e-3, msg=backend.name) - - x0 = tensor([[0, 0, 0], [1, 1, 1]], batch('batch'), spatial('x')), tensor([[0, 0, 0], [-1, -1, -1]], batch('batch'), spatial('y')) - x, y = math.minimize(loss, math.Solve('L-BFGS-B', 0, 1e-3, x0=x0)) - math.assert_close(x, 1, abs_tolerance=1e-3, msg=backend.name) - math.assert_close(y, -1, abs_tolerance=1e-3, msg=backend.name) - - with math.SolveTape() as solves: - x, y = math.minimize(loss, math.Solve('L-BFGS-B', 0, 1e-3, x0=x0)) - math.assert_close(x, 1, abs_tolerance=1e-3, msg=backend.name) - math.assert_close(y, -1, abs_tolerance=1e-3, msg=backend.name) - math.assert_close(solves[0].residual, 0, abs_tolerance=1e-4) - assert (solves[0].iterations <= [4, 0]).all - assert (solves[0].function_evaluations <= [30, 1]).all - - with math.SolveTape(record_trajectories=True) as trajectories: - x, y = math.minimize(loss, math.Solve('L-BFGS-B', 0, 1e-3, x0=x0)) - math.assert_close(x, 1, abs_tolerance=1e-3, msg=backend.name) - math.assert_close(y, -1, abs_tolerance=1e-3, msg=backend.name) - math.assert_close(trajectories[0].residual.trajectory[-1], 0, abs_tolerance=1e-4) - assert (trajectories[0].iterations == solves[0].iterations).all - assert trajectories[0].residual.trajectory.size == trajectories[0].x[0].trajectory.size - assert trajectories[0].residual.trajectory.size > 1 - - def test_solve_linear_matrix(self): - for backend in BACKENDS: - with backend: - y = math.ones(spatial(x=3)) - x0 = math.zeros(spatial(x=3)) - for method in ['CG', 'CG-adaptive', 'auto']: - solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) - x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) - math.assert_close(x, [-1.5, -2, -1.5], abs_tolerance=1e-3, msg=backend) - - def test_linear_solve_matrix_batched(self): # TODO also test batched matrix - y = math.ones(spatial(x=3)) * math.vec(x=1, y=2) - x0 = math.zeros(spatial(x=3)) - for method in ['CG', 'CG-adaptive', 'auto']: - solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) - x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) - math.assert_close(x, [[-1.5, -2, -1.5], [-3, -4, -3]], abs_tolerance=1e-3) - - def test_linear_solve_matrix_jit(self): - @math.jit_compile - def solve(y, method): - solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) - return math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) - - for backend in BACKENDS: - with backend: - x0 = math.zeros(spatial(x=3)) - for method in ['CG']: - x = solve(math.zeros(spatial(x=3)), method=method) - math.assert_close(x, 0, abs_tolerance=1e-3) - x = solve(math.ones(spatial(x=3)), method=method) - math.assert_close(x, [-1.5, -2, -1.5], abs_tolerance=1e-3) - - def test_linear_solve_matrix_tape(self): - y = math.ones(spatial(x=3)) * math.vec(x=1, y=2) - x0 = math.zeros(spatial(x=3)) - for method in ['CG', 'CG-adaptive', 'auto']: - solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) - with math.SolveTape() as solves: - x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) - math.assert_close(x, [[-1.5, -2, -1.5], [-3, -4, -3]], abs_tolerance=1e-3) - assert len(solves) == 1 - assert solves[0] == solves[solve] - math.assert_close(solves[solve].residual, 0, abs_tolerance=1e-3) - assert math.close(solves[solve].iterations, 2) or math.close(solves[solve].iterations, -1) - with math.SolveTape(record_trajectories=True) as solves: - x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) - math.assert_close(x, [[-1.5, -2, -1.5], [-3, -4, -3]], abs_tolerance=1e-3) - assert solves[solve].x.trajectory.size == 3 - math.assert_close(solves[solve].residual.trajectory[-1], 0, abs_tolerance=1e-3) - # math.print(solves[solve].x.vector[1]) - - def test_solve_linear_function_batched(self): - y = math.ones(spatial(x=3)) * math.vec(x=1, y=2) - x0 = math.zeros(spatial(x=3)) - for method in ['CG', 'CG-adaptive', 'auto']: - solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) - x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) - math.assert_close(x, math.wrap([[-1.5, -2, -1.5], [-3, -4, -3]], channel('vector'), spatial('x')), abs_tolerance=1e-3) - with math.SolveTape() as solves: - x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) - math.assert_close(x, math.wrap([[-1.5, -2, -1.5], [-3, -4, -3]], channel('vector'), spatial('x')), abs_tolerance=1e-3) - assert len(solves) == 1 - assert solves[0] == solves[solve] - math.assert_close(solves[solve].residual, 0, abs_tolerance=1e-3) - - def test_solve_diverge(self): - y = math.ones(spatial(x=2)) * [1, 2] - x0 = math.zeros(spatial(x=2)) - for method in ['CG']: - solve = Solve(method, 0, 1e-3, x0=x0, max_iterations=100) - try: - math.solve_linear(math.jit_compile_linear(math.laplace), y, solve) - assert False - except Diverged: - pass - with math.SolveTape(record_trajectories=True) as solves: - try: - math.solve_linear(math.jit_compile_linear(math.laplace), y, solve) # impossible - assert False - except Diverged: - pass - - def test_solve_linear_matrix_dirichlet(self): - for backend in BACKENDS: - with backend: - y = math.ones(spatial(x=3)) - x0 = math.zeros(spatial(x=3)) - solve = math.Solve('CG', 0, 1e-3, x0=x0, max_iterations=100) - x_ref = math.solve_linear(partial(math.laplace, padding=extrapolation.ONE), y, solve) - x_jit = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ONE)), y, solve) - math.assert_close(x_ref, x_jit, [-0.5, -1, -0.5], abs_tolerance=1e-3, msg=backend) - - def test_jit_solves(self): - @math.jit_compile - def solve(y, method): - print(f"Tracing {method} with {backend}...") - solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) - with SolveTape() as solves: - x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) - return x - - for backend in BACKENDS: - with backend: - x0 = math.zeros(spatial(x=3)) - - for method in ['CG', 'CG-adaptive', 'auto']: - x = solve(math.zeros(spatial(x=3)), method=method) - math.assert_close(x, 0, abs_tolerance=1e-3) - x = solve(math.ones(spatial(x=3)), method=method) - math.assert_close(x, [-1.5, -2, -1.5], abs_tolerance=1e-3) - - def test_gradient_descent_minimize(self): - def loss(x): - return x ** 2 - - for backend in BACKENDS: - if backend.supports(Backend.jacobian): - with backend: - result = math.minimize(loss, Solve('GD', 0, 1e-5, 20, x0=3)) - math.assert_close(result, 0, abs_tolerance=1e-5, msg=backend.name) - def test_map_types(self): def f(x, y): assert x.shape.batch.names == ('batch', 'x', 'y') diff --git a/tests/commit/math/test__optimize.py b/tests/commit/math/test__optimize.py new file mode 100644 index 000000000..fc8be0bff --- /dev/null +++ b/tests/commit/math/test__optimize.py @@ -0,0 +1,168 @@ +from functools import partial +from unittest import TestCase + +import phi +from phi import math +from phi.math import Solve, Diverged, tensor, SolveTape, extrapolation, spatial, batch, channel +from phi.math.backend import Backend + +BACKENDS = phi.detect_backends() + + +class TestOptimize(TestCase): + + def test_minimize(self): + def loss(x, y): + return math.l2_loss(x - 1) + math.l2_loss(y + 1) + + for backend in BACKENDS: + if backend.supports(Backend.jacobian): + with backend: + x0 = tensor([0, 0, 0], spatial('x')), tensor([-1, -1, -1], spatial('y')) + x, y = math.minimize(loss, math.Solve('L-BFGS-B', 0, 1e-3, x0=x0)) + math.assert_close(x, 1, abs_tolerance=1e-3, msg=backend.name) + math.assert_close(y, -1, abs_tolerance=1e-3, msg=backend.name) + + x0 = tensor([[0, 0, 0], [1, 1, 1]], batch('batch'), spatial('x')), tensor([[0, 0, 0], [-1, -1, -1]], batch('batch'), spatial('y')) + x, y = math.minimize(loss, math.Solve('L-BFGS-B', 0, 1e-3, x0=x0)) + math.assert_close(x, 1, abs_tolerance=1e-3, msg=backend.name) + math.assert_close(y, -1, abs_tolerance=1e-3, msg=backend.name) + + with math.SolveTape() as solves: + x, y = math.minimize(loss, math.Solve('L-BFGS-B', 0, 1e-3, x0=x0)) + math.assert_close(x, 1, abs_tolerance=1e-3, msg=backend.name) + math.assert_close(y, -1, abs_tolerance=1e-3, msg=backend.name) + math.assert_close(solves[0].residual, 0, abs_tolerance=1e-4) + assert (solves[0].iterations <= [4, 0]).all + assert (solves[0].function_evaluations <= [30, 1]).all + + with math.SolveTape(record_trajectories=True) as trajectories: + x, y = math.minimize(loss, math.Solve('L-BFGS-B', 0, 1e-3, x0=x0)) + math.assert_close(x, 1, abs_tolerance=1e-3, msg=backend.name) + math.assert_close(y, -1, abs_tolerance=1e-3, msg=backend.name) + math.assert_close(trajectories[0].residual.trajectory[-1], 0, abs_tolerance=1e-4) + assert (trajectories[0].iterations == solves[0].iterations).all + assert trajectories[0].residual.trajectory.size == trajectories[0].x[0].trajectory.size + assert trajectories[0].residual.trajectory.size > 1 + + def test_solve_linear_matrix(self): + for backend in BACKENDS: + with backend: + y = math.ones(spatial(x=3)) + x0 = math.zeros(spatial(x=3)) + for method in ['CG', 'CG-adaptive', 'auto']: + solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) + x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) + math.assert_close(x, [-1.5, -2, -1.5], abs_tolerance=1e-3, msg=backend) + + def test_linear_solve_matrix_batched(self): # TODO also test batched matrix + y = math.ones(spatial(x=3)) * math.vec(x=1, y=2) + x0 = math.zeros(spatial(x=3)) + for method in ['CG', 'CG-adaptive', 'auto']: + solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) + x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) + math.assert_close(x, [[-1.5, -2, -1.5], [-3, -4, -3]], abs_tolerance=1e-3) + + def test_linear_solve_matrix_jit(self): + @math.jit_compile + def solve(y, method): + solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) + return math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) + + for backend in BACKENDS: + with backend: + x0 = math.zeros(spatial(x=3)) + for method in ['CG']: + x = solve(math.zeros(spatial(x=3)), method=method) + math.assert_close(x, 0, abs_tolerance=1e-3) + x = solve(math.ones(spatial(x=3)), method=method) + math.assert_close(x, [-1.5, -2, -1.5], abs_tolerance=1e-3) + + def test_linear_solve_matrix_tape(self): + y = math.ones(spatial(x=3)) * math.vec(x=1, y=2) + x0 = math.zeros(spatial(x=3)) + for method in ['CG', 'CG-adaptive', 'auto']: + solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) + with math.SolveTape() as solves: + x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) + math.assert_close(x, [[-1.5, -2, -1.5], [-3, -4, -3]], abs_tolerance=1e-3) + assert len(solves) == 1 + assert solves[0] == solves[solve] + math.assert_close(solves[solve].residual, 0, abs_tolerance=1e-3) + assert math.close(solves[solve].iterations, 2) or math.close(solves[solve].iterations, -1) + with math.SolveTape(record_trajectories=True) as solves: + x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) + math.assert_close(x, [[-1.5, -2, -1.5], [-3, -4, -3]], abs_tolerance=1e-3) + assert solves[solve].x.trajectory.size == 3 + math.assert_close(solves[solve].residual.trajectory[-1], 0, abs_tolerance=1e-3) + # math.print(solves[solve].x.vector[1]) + + def test_solve_linear_function_batched(self): + y = math.ones(spatial(x=3)) * math.vec(x=1, y=2) + x0 = math.zeros(spatial(x=3)) + for method in ['CG', 'CG-adaptive', 'auto']: + solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) + x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) + math.assert_close(x, math.wrap([[-1.5, -2, -1.5], [-3, -4, -3]], channel('vector'), spatial('x')), abs_tolerance=1e-3) + with math.SolveTape() as solves: + x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) + math.assert_close(x, math.wrap([[-1.5, -2, -1.5], [-3, -4, -3]], channel('vector'), spatial('x')), abs_tolerance=1e-3) + assert len(solves) == 1 + assert solves[0] == solves[solve] + math.assert_close(solves[solve].residual, 0, abs_tolerance=1e-3) + + def test_solve_diverge(self): + y = math.ones(spatial(x=2)) * [1, 2] + x0 = math.zeros(spatial(x=2)) + for method in ['CG']: + solve = Solve(method, 0, 1e-3, x0=x0, max_iterations=100) + try: + math.solve_linear(math.jit_compile_linear(math.laplace), y, solve) + assert False + except Diverged: + pass + with math.SolveTape(record_trajectories=True) as solves: + try: + math.solve_linear(math.jit_compile_linear(math.laplace), y, solve) # impossible + assert False + except Diverged: + pass + + def test_solve_linear_matrix_dirichlet(self): + for backend in BACKENDS: + with backend: + y = math.ones(spatial(x=3)) + x0 = math.zeros(spatial(x=3)) + solve = math.Solve('CG', 0, 1e-3, x0=x0, max_iterations=100) + x_ref = math.solve_linear(partial(math.laplace, padding=extrapolation.ONE), y, solve) + x_jit = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ONE)), y, solve) + math.assert_close(x_ref, x_jit, [-0.5, -1, -0.5], abs_tolerance=1e-3, msg=backend) + + def test_jit_solves(self): + @math.jit_compile + def solve(y, method): + print(f"Tracing {method} with {backend}...") + solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) + with SolveTape() as solves: + x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) + return x + + for backend in BACKENDS: + with backend: + x0 = math.zeros(spatial(x=3)) + + for method in ['CG', 'CG-adaptive', 'auto']: + x = solve(math.zeros(spatial(x=3)), method=method) + math.assert_close(x, 0, abs_tolerance=1e-3) + x = solve(math.ones(spatial(x=3)), method=method) + math.assert_close(x, [-1.5, -2, -1.5], abs_tolerance=1e-3) + + def test_gradient_descent_minimize(self): + def loss(x): + return x ** 2 + + for backend in BACKENDS: + if backend.supports(Backend.jacobian): + with backend: + result = math.minimize(loss, Solve('GD', 0, 1e-5, x0=3, max_iterations=20)) + math.assert_close(result, 0, abs_tolerance=1e-5, msg=backend.name) From 9bb9948122082a47c0b0bd08ca0f88887f9ed1a8 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 11 Feb 2023 14:40:32 +0100 Subject: [PATCH 128/170] [math] Set auto_compress default to False --- phi/math/_functional.py | 2 +- phi/math/_trace.py | 2 +- tests/commit/math/test__sparse.py | 2 +- tests/commit/math/test__trace.py | 4 +--- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/phi/math/_functional.py b/phi/math/_functional.py index 740db79e0..1274b2310 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -300,7 +300,7 @@ def _get_or_trace(self, key: SignatureKey, args: tuple, f_kwargs: dict): else: if self.forget_traces: self.matrices_and_biases.clear() - matrix, bias = matrix_from_function(self.f, *args, **f_kwargs) + matrix, bias = matrix_from_function(self.f, *args, **f_kwargs, auto_compress=True) if not key.tracing: self.matrices_and_biases[key] = matrix, bias if len(self.matrices_and_biases) >= 4: diff --git a/phi/math/_trace.py b/phi/math/_trace.py index 5235375cd..bd6357a8a 100644 --- a/phi/math/_trace.py +++ b/phi/math/_trace.py @@ -230,7 +230,7 @@ def simplify_add(val: dict) -> Dict[Shape, Tensor]: def matrix_from_function(f: Callable, *args, auxiliary_args=None, - auto_compress=True, + auto_compress=False, sparsify_batch=None, separate_independent=False, # not fully implemented, requires auto_compress=False **kwargs) -> Tuple[Tensor, Tensor]: diff --git a/tests/commit/math/test__sparse.py b/tests/commit/math/test__sparse.py index 9a842d0eb..b31326ef0 100644 --- a/tests/commit/math/test__sparse.py +++ b/tests/commit/math/test__sparse.py @@ -61,6 +61,6 @@ def f(x): for backend in BACKENDS: with backend: x = math.ones(spatial(x=5)) - coo, bias = math.matrix_from_function(f, x, auto_compress=False) + coo, bias = math.matrix_from_function(f, x) csr = coo.compress(non_dual) math.assert_close(f(x), coo @ x, csr @ x) diff --git a/tests/commit/math/test__trace.py b/tests/commit/math/test__trace.py index 1bc44d1b7..cfb6f1812 100644 --- a/tests/commit/math/test__trace.py +++ b/tests/commit/math/test__trace.py @@ -21,7 +21,5 @@ def diagonal(x): for f in [simple_gradient, diagonal]: x = expand(1, spatial(x=4)) matrix, bias = math.matrix_from_function(f, x) - if isinstance(matrix, SparseCoordinateTensor): - matrix = matrix.compress(non_dual) + matrix = matrix.compress(non_dual) math.assert_close(f(x), matrix @ x) - From d1fbd463037b050e799c1d38969ab8bcde172ceb Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 11 Feb 2023 18:34:42 +0100 Subject: [PATCH 129/170] [vis] Support arbitrary line plots from Tensors * Move tensor_as_field to vis_base --- phi/field/__init__.py | 2 +- phi/field/_field_math.py | 23 ---------------- phi/vis/_vis.py | 4 +-- phi/vis/_vis_base.py | 36 +++++++++++++++++++++++--- tests/commit/field/test__field_math.py | 10 ------- tests/commit/vis/test__vis_base.py | 25 ++++++++++++++++++ 6 files changed, 60 insertions(+), 40 deletions(-) create mode 100644 tests/commit/vis/test__vis_base.py diff --git a/phi/field/__init__.py b/phi/field/__init__.py index 9ff57e8f3..96079d289 100644 --- a/phi/field/__init__.py +++ b/phi/field/__init__.py @@ -32,7 +32,7 @@ unstack, stack, concat # expand, rename_dims, pack_dims, unpack_dims ) from ._field_math import ( - assert_close, tensor_as_field, + assert_close, bake_extrapolation, laplace, spatial_gradient, divergence, stagger, curl, # spatial operators fourier_poisson, fourier_laplace, diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index aa6cfbe3c..69ae83695 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -786,29 +786,6 @@ def integrate(field: Field, region: Geometry, **kwargs) -> Tensor: return field._sample(region, **kwargs) * region.volume -def tensor_as_field(t: Tensor): - """ - Interpret a `Tensor` as a `CenteredGrid` or `PointCloud` depending on its dimensions. - - Unlike the `CenteredGrid` constructor, this function will have the values sampled at integer points for each spatial dimension. - - Args: - t: `Tensor` with either `spatial` or `instance` dimensions. - - Returns: - `CenteredGrid` or `PointCloud` - """ - if instance(t): - bounds = data_bounds(t) - return PointCloud(t, bounds=Cuboid(bounds.center, bounds.half_size * 1.2).box()) - elif spatial(t): - return CenteredGrid(t, 0, bounds=Box(math.const_vec(-0.5, spatial(t)), wrap(spatial(t), channel('vector')) - 0.5)) - elif 'vector' in t.shape: - return PointCloud(math.expand(t, instance(points=1)), bounds=Cuboid(t, half_size=math.const_vec(1, t.shape['vector'])).box()) - else: - raise ValueError(f"Cannot create field from tensor with shape {t.shape}. Requires at least one spatial, instance or vector dimension.") - - def pack_dims(field: SampledFieldType, dims: Shape or tuple or list or str, packed_dim: Shape, diff --git a/phi/vis/_vis.py b/phi/vis/_vis.py index 5f32ba734..ef6806772 100644 --- a/phi/vis/_vis.py +++ b/phi/vis/_vis.py @@ -9,7 +9,7 @@ from ._user_namespace import get_user_namespace, UserNamespace, DictNamespace from ._viewer import create_viewer, Viewer from ._vis_base import Control, value_range, Action, VisModel, Gui, \ - PlottingLibrary + PlottingLibrary, tensor_as_field from .. import math, field from ..field import SampledField, Scene, Field, PointCloud, Grid from ..field._scene import _slugify_filename @@ -413,7 +413,7 @@ def layout_sub_figures(data: Tensor or Layout or SampledField, return rows, cols, non_reduced, positioning, indices else: if isinstance(data, Tensor): - data = field.tensor_as_field(data) + data = tensor_as_field(data) elif isinstance(data, Geometry): data = PointCloud(data) assert isinstance(data, Field), f"Cannot plot {type(data)}. Only tensors, geometries and fields can be plotted." diff --git a/phi/vis/_vis_base.py b/phi/vis/_vis_base.py index 033200aa8..b8b15a107 100644 --- a/phi/vis/_vis_base.py +++ b/phi/vis/_vis_base.py @@ -6,9 +6,10 @@ from typing import Tuple, Any, Optional, Dict, Callable from phi import field, math -from phi.field import SampledField, Scene -from phi.geom import Box -from phi.math import Shape, EMPTY_SHAPE, Tensor +from phi.field import SampledField, Scene, PointCloud, CenteredGrid +from phi.field._field_math import data_bounds +from phi.geom import Box, Cuboid +from phi.math import Shape, EMPTY_SHAPE, Tensor, spatial, instance, wrap, channel Control = namedtuple('Control', [ 'name', @@ -456,4 +457,31 @@ def select_channel(value: SampledField or Tensor or tuple or list, channel: str raise ValueError( f"No {channel} component present. Available dimensions: {', '.join(value.shape.spatial.names)}") else: - return value \ No newline at end of file + return value + + +def tensor_as_field(t: Tensor): + """ + Interpret a `Tensor` as a `CenteredGrid` or `PointCloud` depending on its dimensions. + + Unlike the `CenteredGrid` constructor, this function will have the values sampled at integer points for each spatial dimension. + + Args: + t: `Tensor` with either `spatial` or `instance` dimensions. + + Returns: + `CenteredGrid` or `PointCloud` + """ + arbitrary_lines_1d = spatial(t).rank == 1 and 'vector' in t.shape + if instance(t) or arbitrary_lines_1d or arbitrary_lines_1d: + bounds = data_bounds(t) + extended_bounds = Cuboid(bounds.center, bounds.half_size * 1.2).box() + lower = math.where(extended_bounds.lower * bounds.lower <= 0, bounds.lower * .9, extended_bounds.lower) + upper = math.where(extended_bounds.upper * bounds.upper <= 0, bounds.lower * .9, extended_bounds.upper) + return PointCloud(t, bounds=Box(lower, upper)) + elif spatial(t): + return CenteredGrid(t, 0, bounds=Box(math.const_vec(-0.5, spatial(t)), wrap(spatial(t), channel('vector')) - 0.5)) + elif 'vector' in t.shape: + return PointCloud(math.expand(t, instance(points=1)), bounds=Cuboid(t, half_size=math.const_vec(1, t.shape['vector'])).box()) + else: + raise ValueError(f"Cannot create field from tensor with shape {t.shape}. Requires at least one spatial, instance or vector dimension.") diff --git a/tests/commit/field/test__field_math.py b/tests/commit/field/test__field_math.py index 4a5d20565..e30c7fc7e 100644 --- a/tests/commit/field/test__field_math.py +++ b/tests/commit/field/test__field_math.py @@ -172,16 +172,6 @@ def test_integrate_all(self): grid = CenteredGrid(field.Noise(vector=2), extrapolation.ZERO, x=10, y=10, bounds=Box['x,y', 0:1, 0:1]) math.assert_close(field.integrate(grid, grid.bounds), math.sum(grid.values, 'x,y') / 100) - def test_tensor_as_field(self): - t = math.random_normal(spatial(x=4, y=3), channel(vector='x,y')) - grid = field.tensor_as_field(t) - self.assertIsInstance(grid, CenteredGrid) - math.assert_close(grid.dx, 1) - math.assert_close(grid.points.x[0].y[0], 0) - t = math.random_normal(instance(points=5), channel(vector='x,y')) - points = field.tensor_as_field(t) - self.assertIsInstance(points, PointCloud) - def test__periodic_2d_arakawa_poisson_bracket(self): """test _periodic_2d_arakawa_poisson_bracket implementation""" diff --git a/tests/commit/vis/test__vis_base.py b/tests/commit/vis/test__vis_base.py new file mode 100644 index 000000000..968b676fc --- /dev/null +++ b/tests/commit/vis/test__vis_base.py @@ -0,0 +1,25 @@ +from unittest import TestCase + +from phi import math +from phi.field import CenteredGrid, PointCloud +from phi.math import spatial, channel, instance +from phi.vis._vis_base import tensor_as_field + + +class TestVisBase(TestCase): + + def test_tensor_as_field(self): + # --- Grid --- + t = math.random_normal(spatial(x=4, y=3), channel(vector='x,y')) + grid = tensor_as_field(t) + self.assertIsInstance(grid, CenteredGrid) + math.assert_close(grid.dx, 1) + math.assert_close(grid.points.x[0].y[0], 0) + # --- PointCloud --- + t = math.random_normal(instance(points=5), channel(vector='x,y')) + points = tensor_as_field(t) + self.assertIsInstance(points, PointCloud) + # --- Arbitrary lines --- + t = math.random_normal(spatial(points=5), channel(vector='x,y')) + points = tensor_as_field(t) + self.assertIsInstance(points, PointCloud) From 66ea45d100b511202be363c960a12f57af580ef2 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 11 Feb 2023 18:56:01 +0100 Subject: [PATCH 130/170] [vis] Add overlay arg to plot() --- phi/vis/_vis.py | 55 +++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/phi/vis/_vis.py b/phi/vis/_vis.py index ef6806772..93bce37bd 100644 --- a/phi/vis/_vis.py +++ b/phi/vis/_vis.py @@ -15,7 +15,7 @@ from ..field._scene import _slugify_filename from ..geom import Geometry, Box, embed from ..math import Tensor, layout, batch, Shape, spatial, channel -from ..math._shape import parse_dim_names, parse_dim_order +from ..math._shape import parse_dim_names, parse_dim_order, DimFilter from ..math._tensors import Layout @@ -270,9 +270,10 @@ def get_current_figure(): def plot(*fields: SampledField or Tensor or Layout, lib: str or PlottingLibrary = None, - row_dims: str or Shape or tuple or list or Callable = None, - col_dims: str or Shape or tuple or list or Callable = batch, - animate: str or Shape or tuple or list or Callable = None, + row_dims: DimFilter = None, + col_dims: DimFilter = batch, + animate: DimFilter = None, + overlay: DimFilter = 'overlay', title: str or Tensor = None, size=(12, 5), same_scale=True, @@ -286,6 +287,11 @@ def plot(*fields: SampledField or Tensor or Layout, To show the figures, use `show()`. + The arguments `row_dims`, `col_dims`, `animate` and `overlay` control how data is presented. + Each accepts dimensions as a `str`, `Shape`, tuple, list or type function. + In addition to the dimensions present on the data to be plotted, the dimensions `args` is created if multiple arguments are passed, + and `tuple`, `list`, `dict` are generated for corresponding objects to be plotted. + Args: fields: Fields or Tensors to plot. lib: Plotting library name or reference. Valid names are `'matplotlib'`, `'plotly'` and `'console'`. @@ -303,6 +309,8 @@ def plot(*fields: SampledField or Tensor or Layout, color: Tensor for line / marker colors. animate: Time dimension to animate. If not present in the data, will produce a regular plot instead. + overlay: Dimensions along which elements should be overlaid in the same subplot. + The default is only the `overlay` dimension which is created by `overlay()`. frame_time: Interval between frames in the animation. repeat: Whether the animation should loop. @@ -313,7 +321,7 @@ def plot(*fields: SampledField or Tensor or Layout, In case of an animation, a displayable animation object will be returned instead of a `Tensor`. """ - nrows, ncols, fig_shape, positioning, indices = layout_sub_figures(math.layout(fields, batch('args')), row_dims, col_dims, animate, 0, 0, {}, {}) + nrows, ncols, fig_shape, positioning, indices = layout_sub_figures(math.layout(fields, batch('args')), row_dims, col_dims, animate, overlay, 0, 0, {}, {}) animate = fig_shape.only(animate) fig_shape = fig_shape.without(animate) plots = default_plots() if lib is None else get_plots(lib) @@ -363,9 +371,10 @@ def plot_frame(frame: int): def layout_sub_figures(data: Tensor or Layout or SampledField, - row_dims: str or Shape or tuple or list or Callable, - col_dims: str or Shape or tuple or list or Callable, - animate: str or Shape or tuple or list or Callable, # do not reduce these dims, has priority + row_dims: DimFilter, + col_dims: DimFilter, + animate: DimFilter, # do not reduce these dims, has priority + overlay: DimFilter, offset_row: int, offset_col: int, positioning: Dict[Tuple[int, int], List], @@ -382,30 +391,30 @@ def layout_sub_figures(data: Tensor or Layout or SampledField, rows, cols = 0, 0 non_reduced = math.EMPTY_SHAPE indices = {} - if not batch(data): # overlay + dim0 = data.shape[0] + if dim0.only(overlay): for d in data: # overlay these fields - e_rows, e_cols, d_non_reduced, positioning, indices = layout_sub_figures(d, row_dims, col_dims, animate, offset_row, offset_col, positioning, base_index) + e_rows, e_cols, d_non_reduced, positioning, indices = layout_sub_figures(d, row_dims, col_dims, animate, overlay, offset_row, offset_col, positioning, base_index) rows = max(rows, e_rows) cols = max(cols, e_cols) non_reduced &= d_non_reduced + elif dim0.only(animate): + data = math.stack(data.native(), dim0) + return layout_sub_figures(data, row_dims, col_dims, animate, overlay, offset_row, offset_col, positioning, base_index) else: - dim0 = data.shape[0] - if dim0.only(animate): - data = math.stack(data.native(), dim0) - return layout_sub_figures(data, row_dims, col_dims, animate, offset_row, offset_col, positioning, base_index) elements = data.unstack(dim0.name) for item_name, e in zip(dim0.get_item_names(dim0.name) or range(dim0.size), elements): index = dict(base_index, **{dim0.name: item_name}) if dim0.only(row_dims): - e_rows, e_cols, e_non_reduced, positioning, e_indices = layout_sub_figures(e.native(), row_dims, col_dims, animate, offset_row + rows, offset_col, positioning, index) + e_rows, e_cols, e_non_reduced, positioning, e_indices = layout_sub_figures(e.native(), row_dims, col_dims, animate, overlay, offset_row + rows, offset_col, positioning, index) rows += e_rows cols = max(cols, e_cols) elif dim0.only(col_dims): - e_rows, e_cols, e_non_reduced, positioning, e_indices = layout_sub_figures(e.native(), row_dims, col_dims, animate, offset_row, offset_col + cols, positioning, index) + e_rows, e_cols, e_non_reduced, positioning, e_indices = layout_sub_figures(e.native(), row_dims, col_dims, animate, overlay, offset_row, offset_col + cols, positioning, index) cols += e_cols rows = max(rows, e_rows) else: - e_rows, e_cols, e_non_reduced, positioning, e_indices = layout_sub_figures(e.native(), row_dims, col_dims, animate, offset_row, offset_col, positioning, index) + e_rows, e_cols, e_non_reduced, positioning, e_indices = layout_sub_figures(e.native(), row_dims, col_dims, animate, overlay, offset_row, offset_col, positioning, index) cols = max(cols, e_cols) rows = max(rows, e_rows) non_reduced &= e_non_reduced @@ -417,16 +426,18 @@ def layout_sub_figures(data: Tensor or Layout or SampledField, elif isinstance(data, Geometry): data = PointCloud(data) assert isinstance(data, Field), f"Cannot plot {type(data)}. Only tensors, geometries and fields can be plotted." - animate = data.shape.only(animate) - row_shape = batch(data).only(row_dims).without(animate) - col_shape = batch(data).only(col_dims).without(row_dims).without(animate) + overlay = data.shape.only(overlay) + animate = data.shape.only(animate).without(overlay) + row_shape = batch(data).only(row_dims).without(animate).without(overlay) + col_shape = batch(data).only(col_dims).without(row_dims).without(animate).without(overlay) non_reduced: Shape = batch(data).without(row_dims).without(col_dims) & animate indices = {} for ri, r in enumerate(row_shape.meshgrid(names=True)): for ci, c in enumerate(col_shape.meshgrid(names=True)): indices[(offset_row + ri, offset_col + ci)] = dict(base_index, **r, **c) - sub_data = data[r][c] - positioning.setdefault((offset_row + ri, offset_col + ci), []).append(sub_data) + for o in overlay.meshgrid(): + sub_data = data[r][c][o] + positioning.setdefault((offset_row + ri, offset_col + ci), []).append(sub_data) return row_shape.volume, col_shape.volume, non_reduced, positioning, indices From 5392351fd63e7feb5ab5c97981f6c5fd1cf607e7 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 11 Feb 2023 19:21:06 +0100 Subject: [PATCH 131/170] [math] Fix vec_abs for complex --- phi/field/_field_math.py | 1 + phi/math/_nd.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 69ae83695..116aa9bb2 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -718,6 +718,7 @@ def _auto_resample(*fields: Field): def vec_length(field: SampledField): """ See `phi.math.vec_abs()` """ + assert isinstance(field, SampledField), f"SampledField required but got {type(field).__name__}" if isinstance(field, StaggeredGrid): field = field.at_centers() return field.with_values(math.vec_abs(field.values)) diff --git a/phi/math/_nd.py b/phi/math/_nd.py index fe85b94a5..b979783ea 100644 --- a/phi/math/_nd.py +++ b/phi/math/_nd.py @@ -71,6 +71,8 @@ def vec_abs(vec: Tensor, vec_dim: DimFilter = channel, eps: float or Tensor = No Args: eps: Minimum vector length. Use to avoid `inf` gradients for zero-length vectors. """ + if vec.dtype.kind == complex: + vec = stack([vec.real, vec.imag], channel('_ReIm')) squared = vec_squared(vec, vec_dim) if eps is not None: squared = math.maximum(squared, eps) From 146c74a723835801e7b6502f7e96df9608e7e336 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 11 Feb 2023 19:36:49 +0100 Subject: [PATCH 132/170] [doc] Add Julia set animation --- docs/Animations.ipynb | 2642 +---------------------------------------- 1 file changed, 39 insertions(+), 2603 deletions(-) diff --git a/docs/Animations.ipynb b/docs/Animations.ipynb index 0f263ee96..f23bed0e2 100644 --- a/docs/Animations.ipynb +++ b/docs/Animations.ipynb @@ -57,11 +57,46 @@ } }, "source": [ - "from phi.flow import *" + "from phi.flow import *\n", + "np.seterr(all=\"ignore\");" ], - "execution_count": 1, + "execution_count": 6, "outputs": [] }, + { + "cell_type": "code", + "execution_count": 7, + "outputs": [ + { + "data": { + "text/plain": "", + "text/html": "" + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def julia(re, im, a=math.linspace(0, 2*PI, batch(t=100))):\n", + " r = -math.log(math.vec_abs(iterate(lambda z: z ** 2 + 0.7885 * math.exp(1j*a), 10, re + im*1j)))\n", + " return math.where(math.is_finite(r), r, math.finite_min(r, 're,im,t'))\n", + "plot(CenteredGrid(julia, re=128, im=128, bounds=Box(re=(-2, 2), im=(-2, 2))), animate='t')" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "markdown", "source": [ @@ -11836,9 +11871,9 @@ { "cell_type": "markdown", "source": [ - "## Spirals\n", + "## Spiral\n", "\n", - "For these animated spirals, we plot 200 points whose distance increases linearly from the origin and whose angle increases linearly from 0 to $\\alpha = 20 \\frac{t}{T}$ where $t$ denotes the current frame and $T$ the number of frames.\n", + "For this animated spiral, we plot 200 points whose distance increases linearly from the origin and whose angle increases linearly from 0 to $\\alpha = 20 \\frac{t}{T}$ where $t$ denotes the current frame and $T$ the number of frames.\n", "When no geometric shape is specified, `PointCloud`s are plotted as `x`." ], "metadata": { @@ -11848,2605 +11883,6 @@ } } }, - { - "cell_type": "code", - "source": [ - "dst = math.linspace(0, 1, instance(points=200))\n", - "angle = math.linspace(0, math.linspace(0, 20, batch(t=100)), dst.shape)\n", - "plot(PointCloud(dst * vec(x=math.cos(angle), y=math.sin(angle))), animate='t')" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 399 - }, - "id": "C6Nb_vJymvwt", - "outputId": "7f065bb0-a190-49c7-a318-93bf31d9e877", - "pycharm": { - "name": "#%%\n" - } - }, - "execution_count": 11, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "" - ] - }, - "metadata": {}, - "execution_count": 11 - }, - { - "output_type": "display_data", - "data": { - "text/plain": [ - "
" - ] - }, - "metadata": {} - } - ] - }, { "cell_type": "markdown", "source": [ From 6eebfb6ea1071374ebff17d7bd60fbc6ac4d68a9 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 11 Feb 2023 23:13:11 +0100 Subject: [PATCH 133/170] [doc] Update Taylor_Green_Comparison.ipynb --- .../prerendered/Taylor_Green_Comparison.ipynb | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/docs/prerendered/Taylor_Green_Comparison.ipynb b/docs/prerendered/Taylor_Green_Comparison.ipynb index ab9dbbe58..243510f6c 100644 --- a/docs/prerendered/Taylor_Green_Comparison.ipynb +++ b/docs/prerendered/Taylor_Green_Comparison.ipynb @@ -105,7 +105,7 @@ "outputs": [ { "data": { - "text/plain": "", + "text/plain": "", "text/html": "" }, "execution_count": 3, @@ -201,7 +201,7 @@ "application/vnd.jupyter.widget-view+json": { "version_major": 2, "version_minor": 0, - "model_id": "b4a16f86960f41789de3161f37bde297" + "model_id": "947c4248234f4e308d475f528d71f416" } }, "metadata": {}, @@ -209,7 +209,7 @@ }, { "data": { - "text/plain": "", + "text/plain": "", "text/html": "" }, "execution_count": 5, @@ -256,7 +256,8 @@ " '4th order': partial(rk4_step, order=4, pressure_order=4),\n", " '2nd order': partial(rk4_step, order=2, pressure_order=2),\n", " 'Semi-Lagrangian': semi_lagrangian_step,\n", - "}, batch('method'))" + "}, batch('method'))\n", + "expected_order = wrap([6, 4, 2, 1], methods.shape)" ], "metadata": { "id": "1HnN9_KZuBpq", @@ -316,7 +317,7 @@ "application/vnd.jupyter.widget-view+json": { "version_major": 2, "version_minor": 0, - "model_id": "bef013bd9552498c97a2a600cc3d78c7" + "model_id": "52b9e27964a84b9f8b15caa4ca1d7ad5" } }, "metadata": {}, @@ -353,13 +354,13 @@ "name": "#%%\n" } }, - "execution_count": 12, + "execution_count": 8, "outputs": [ { "data": { "text/plain": "
" }, - "execution_count": 12, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" }, @@ -390,20 +391,20 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 9, "outputs": [ { "data": { "text/plain": "
" }, - "execution_count": 13, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": "
", - "image/png": "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w8SJE7n66qtt4wN89NFHnHrqqUydOpV58+ZRUVEBmAju/vvvZ+bMmbz++uut7nvvvffy0EMPASYC+uUvf8mZZ57J2LFjWbduHZdffjkjR47k7rvvBiArK4sxY8Zwww03MHHiRK688kqqqqraPY991Pj8888zceJEJk2axHXXXdfqvo7+vQShHdZ288OGQXa2WfZzVQMVH2/aeLz/vikMPuMMk0ThKgoTvIIIlI84cOAAgwYN4sYbb2TKlCncfPPNVFZWcuWVV5KamspLL73E5s2bCQ8PByAmJoaNGzeSnp5uEwR7nnjiCQC2bdvGyy+/zA033GDLYFu7di3PPfccn376KRkZGURERLB161buuusuNjS3JCguLubPf/4z//vf/9i4cSOpqak8/PDDtvHDwsL48ssvueaaa5w+V0hICKtXryYtLY1LL72UJ554gu3bt7NixQpKSkoA2L17N4sWLWLr1q307duXJ5980uF4O3bsYMmSJXz66ads2bKFRx99tNVxR/9egtAhwcGmTuqMM0ybjo0bTSJF8y9jDrnwQuOq/stftiRRvPqqJFF0MiJQPqKhocEmOJs2baJPnz789a9/dXj+5ZdfDsC0adPIyspqd/zLL7+0RRdjxozBYrGwp3lz95xzzmHAgAEArF69mh//+McATJw4kYkTJwLw9ddf891333H66aczefJknnvuObKzs23jX3311W491yWXXAJASkoK48ePJz4+ntDQUIYPH86hQ4cAGDJkCKeffjoAP/7xj/nSybLJp59+ypVXXklMTAyA7TkEwSP69YOZM83+0rFjJiV91y7njQ4jI00SxbffmmjsmmvgootMNCZ0CiJQPiIpKYmkpCRmzJgBmEhg48aNDs8PDQ0FIDAwsMO9JGcF13369Gn1vqP0a60155xzDps3b2bz5s189913PPPMMw7HcDXPgIAA29fW99Z5t72/s3RwrbWkiwudg1JgsZhlv4QE2LvXWCa52j+eNs0Y0D78sDl/3DjztRf2eIXWiED5iLi4OIYMGcLu3bsB+OSTTxg3bhwAUVFRlJeXH9d4Z555Ji+99BIAe/bs4eDBg4wePdrpedu3b2fr1q0AnHLKKaxZs4Z9+/YBUFVVZYvAvM3BgwdtGYsvv/wyM2fOdHjuWWedxWuvvWZbHjzSQQHlifx7CYKN0FCTAHHaaSY54ttv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\n" + "image/png": "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t3Pd+5s5nlu7jqevuwsvXDyKN/8xlvT8Ui787xpe/GU35XVUrOgopN2GlZTTJmHOrNv50hIcAwMJW7O60fMef/xxPv74YxwdHXFwcOCNN96oVpOvJUycOJG//vqr1vFFixbh6enJv//971aN3568/vrruLu7c8UVV3T2VASh2xAW4sWymyby3tr9PPvTLqY9v4p7zo3gkpMG4OBQvaLMtKjenDTIn0XfJPLCLyn8lJTKc38bSXhv73pGbz9EoKy0pTg1dbx169axcuVKtm7diqurK5mZmZSV1d3yuTnUJU7thclkwsmp7f4zqiupWBCE1uPooLhu0hDOjgzh7mXx3PtlPF/vOMpTc2IZGFC9hJmfhwsvXjKac6L7cP+KeC54eQ23nTmc+VOG4uTYcQtvssTXiRw/fpzAwMCKunuBgYH07duXLVu2MGXKFMaOHcv06dM5ftxoAT116lTuuOMOJk+eTEREBJs2bWL27NkMHz6c+++/v2JcT0/PZs1j5syZjB07lqioKN58882K4++88w5hYWFMnTqV66+/nltuuQWAq666ijvvvJPTTz+dhQsXsnHjRiZOnMjo0aOZOHEiu3btAoz2HLNnz2bGjBkMHz6cBQsWNDr2okWLePbZZwF46623OOmkkxg5ciRz5syhqKio4v7/+te/mDhxIkOGDGHp0qXNer2C0JMZGODBx9dP4MnZMSQezWP6f1bx9up9mOto5TEjujc/3TGFGdF9ePanFGa9+he7UvM7bK4iUJ3ItGnTOHz4MGFhYdx88838+eeflJeXc+utt7J06VK2bNnCNddcw3333VdxjYuLC6tWrWL+/PlcdNFFvPLKKyQkJLBkyRKyslrWdPHdd99ly5YtbN68mZdeeomsrCyOHTvGo48+yvr16/n5559JTk6udk1KSgq//PILzz33HOHh4axatYpt27bxyCOPcO+991act3379orWH5999hmHDx9udGwbs2fPZtOmTezYsYOIiAjeeeediueOHz/OmjVrWLlyJXfffXeLXrcg9FSUUlw6PpSf7pzMqUMDeezbncx+7S9S0mqLj7+HCy9fOprXLhvDsZxiLnh5Da/8vqfOauptjSzxdSKenp5s2bKF1atX8/vvv3PxxRdz//33k5CQwNlnnw0YnW/79OlTcc2FF14IQExMDFFRURXPDRkyhMOHDxMQ0Pya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\n" }, "metadata": { "needs_background": "light" @@ -412,14 +413,10 @@ } ], "source": [ - "THEO_ORDER = wrap([6, 4, 2, 1], methods.shape)\n", - "theo_lines = vec(resolution=resolutions.resolution[(0, -1)], error=errors.time[-1].resolution[0]*.5*stack([1, (resolutions.min/resolutions.max)**THEO_ORDER], batch('resolution')))\n", - "SIM_COLOR = wrap(['#ff0000', '#00ff00', '#0000ff', '#000000'], channel('method'))\n", - "THEO_COLOR = wrap(['#ff00004d', '#00ff004d', '#0000ff4d', '#0000004d'], channel('method'))\n", - "plot(vis.overlay(\n", - " PointCloud(vec(resolution=resolutions, error=errors.time[-1]).resolution.as_spatial().method.as_channel(), color=SIM_COLOR),\n", - " PointCloud(theo_lines.resolution.as_spatial().method.as_channel(), color=THEO_COLOR)),\n", - " log_dims='resolution,error', title=\"Final Error by Resolution\", size=(6, 6))" + "expected_lines = vec(resolution=resolutions.resolution[(0, -1)], error=errors.time[-1].resolution[0]*.5*stack([1, (resolutions.min/resolutions.max)**expected_order], batch('resolution')))\n", + "plot(vec(resolution=resolutions, error=errors.time[-1]).resolution.as_spatial().method.as_channel(),\n", + " expected_lines.resolution.as_spatial().method.as_channel(),\n", + " overlay='args', log_dims='resolution,error', title=\"Final Error by Resolution\", size=(6, 6))" ], "metadata": { "collapsed": false, @@ -443,7 +440,8 @@ { "cell_type": "code", "source": [ - "plot(PointCloud(vec(resolution=resolutions, execution_time=exec_times).resolution.as_spatial().method.as_channel(), color=SIM_COLOR), log_dims='execution_time,resolution', title=\"Execution Time per Step\")" + "plot(vec(resolution=resolutions, execution_time=exec_times).resolution.as_spatial().method.as_channel(),\n", + " log_dims='execution_time,resolution', title=\"Execution Time per Step\")" ], "metadata": { "colab": { @@ -456,20 +454,20 @@ "name": "#%%\n" } }, - "execution_count": 14, + "execution_count": 10, "outputs": [ { "data": { "text/plain": "
" }, - "execution_count": 14, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": "
", - "image/png": "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IsnJyQQFBTFq1Cj69+/vfsz27dtZunQpvr6+nD17lmXLluHn58fSpUv5j//4Dz788EMA1q5dy5o1awgICKBHjx7Mnj0bX1/fco9dYMqUKdxzzz0APPvss7z77rvMnj0bcMLp8uXL2bp1KzfffLMCllRednY269at8whUBw4cAKBRo0YMHjyYp556ipiYGIYOHer+FxARERERqZmaNGlCUlIS//73v/nmm2+IjY3l2WefZePGjYwdOxaA3Nxc2rRp437MzTffDEC/fv3o06eP+74uXbpw4MCBSwpYv//97/noo48AOHDgADt27ODIkSNcd911hISEADBt2jS2b9/ufsy0adPck6RTU1O544472LFjB8YYsrOz3fuNHj3afQmf3r17s2/fPk6cOFHusQts3LiRZ599ljNnzpCWlsb111/vvu+WW27Bx8eH3r17V+nyTgWsOuzEiRMewyhWrVpFeno6AB06dHB3pmJiYujfvz8NGjTwcsUiIiIiUlm+vr6MGDGCESNG0K9fP9555x369OnDihUrSt2/YDmhj4+PxzAyHx8f90qmAs888wyLFy8GnE5Sab799luWLl3KihUrCAwMZMSIEWRkZGCtLbfuxo0bu28/99xzjBw5ko8++oi9e/cyYsSIEvUWvNacnJyLHrvArFmz+Pjjj+nfvz9z5szh22+/LfW4FT1eRShg1RF5eXls3brVozu1bds2APz8/Bg4cCD33nuve1R6hw4dvFyxiIiIiFyubdu24ePjQ7du3QAnBPXq1YslS5awYsUKhg0bRnZ2Ntu3b6dPnz6VPv7LL7/Myy+/XO4+qampBAcHExgYyNatW1m5ciUAgwcP5vHHH+f06dMEBQXx4Ycf0q9fvzKP0a5dOwDmzJlz0boqeuxz587Rpk0bsrOz+ec//+l+juqkgFVLpaWlsWrVKneYWrFiBWfOnAGgRYsWxMTEMGvWLGJiYoiOjiYwMNC7BYuIiIjUca1ataryMe0Xk5aWxuzZszlz5gx+fn6Eh4fz5z//mXvvvZdHHnmE1NRUcnJyeOyxxy4pYJXmpZde4ne/+5376127dvGnP/2JiIgIevTowdChQwFo164d//Ef/8GQIUNo27YtvXv3di/1K+4Xv/gFd9xxB2+88QajRo26aA0VPfaLL77IkCFDCAsLo1+/fpw7d+7SXnQlmKpsh9Ul0dHRNjEx0dtlAE7Lct++fR7dqXXr1pGXlwdAnz59PJb7devWTaPSRURERKrZli1b6NWrl7fLqNHS0tJo0qQJOTk5TJ48mbvuuovJkyfX+GMXV9rP2hiTZK2NLr6vOlg1UGZmJmvWrPEIVIcPHwacExmHDBnCM8884x5G0bx5c+8WLCIiIiJSihdeeIGlS5eSkZHBuHHjuOWWW2rFsS+HAlYNcPToUVasWOEOU4mJiWRmZgLQuXNnRo0a5e5O9e3bt0ovxCYiIiIiUl1ef/31Wnnsy6F36ldYbm4umzZt8uhO7dq1C3CuSRAVFcXDDz/sHkZRdKSmiIiIiNQs1lqdmlHHVfaUKgWsapaamuoelb5ixQpWrlzpPrmuVatWXH311TzwwAPExMQQGRnpMS5SRERERGquhg0bcvLkSVq0aKGQVUdZazl58iQNGzas8GMUsKqQtZadO3d6dKc2bdqEtRYfHx8iIiL46U9/6l7u16lTJ/1lFBEREaml2rdvT0pKCsePH/d2KVKNGjZsSPv27Su8vwLWZUhPTycxMdEjUJ04cQKAZs2aMWzYMKZPn05MTAyDBw8mKCjIyxWLiIiISFVp0KABnTt39nYZUsMoYFXCwYMHPcLUmjVryM7OBqB79+5MmDDB3Z3q1asXPj4+Xq5YRERERESuJAWsMlhrSUpK8ghU+/fvB5w24eDBg/n5z3/uHpUeGhrq5YpFRERERMTbdKHhMvj6+tqCC/m2a9eOq6++2t2d6t+/P/7+/l6uUEREREREvKWsCw0rYJXBGJMK7PByGc2A1Cv8nC2BE1f4OUVqIm/8/ZOqp5/j5avP38O69Npr22upifXWlJr0/rDmCLPWlljGpiWCZXNZa+/1ZgHGmD9f6RqMMYmlJXGR+sYbf/+k6unnePnq8/ewLr322vZaamK9NaUmvT+s+TSFoWwLvV0ANaMGkfpKf//qBv0cL199/h7Wpdde215LTay3ptRUU+qQMmiJoHjQv1CIiIiISFF6f1g56mBJcX/2dgEiIiIiUqPo/WElqIMlIiIiIiJSRdTBEhERERERqSIKWCIiIiIiIlVEAUtERERERKSKKGCJiIiIiIhUEQUsKZcxposx5l1jzHxv1yIiIiIi3meMucUY8xdjzL+MMeO8XU9No4BVDxlj3jPGHDPGbCy2/QZjzDZjzE5jzNMA1trd1tq7vVOpiIiIiFwJlXx/+LG19h5gFhDrhXJrNAWs+mkOcEPRDcYYX+Ad4EagNxBnjOl95UsTERERES+YQ+XfHz6bf78UoYBVD1lrlwGnim0eDOzM71hlAR8Ak654cSIiIiJyxVXm/aFx/Ab4zFqbfKVrrekUsKRAO+BAka9TgHbGmBbGmD8BA40xv/ROaSIiIiLiBaW+PwRmA2OAqcaY+71RWE3m5+0CpMYwpWyz1tqTgP7iiIiIiNQ/Zb0//D3w+ytdTG2hDpYUSAE6FPm6PXDIS7WIiIiIiPfp/eElUMCSAquBbsaYzsYYf2AG8ImXaxIRERER79H7w0uggFUPGWMSgBVAD2NMijHmbmttDvAw8AWwBZhrrd3kzTpFRERE5MrQ+8OqY6y13q5BRERERESkTlAHS0REREREpIooYImIiIiIiFQRBSwREREREZEqooAlIiIiIiJSRRSwREREREREqogCloiIiIiISBVRwBIRkVrPGJNrjFlrjNlojFlojGlehcceYYxZdJF9mhtjHizydVtjzPyqqkFERGoPBSwREakL0q21A6y1fYFTwENX+PmbA+6AZa09ZK2deoVrEBGRGkABS0RE6poVQDsAY0xXY8znxpgkY8y/jTE987dPy+92rTPGLMvf1tAY874xZoMxZo0xZmTxAxtjXjDGPFnk643GmE7Aq0DX/C7afxtjOhljNpZ3XGPMLGPMgvz6dhhjXqvub4yIiFQ/P28XICIiUlWMMb7AaODd/E1/Bu631u4wxgwB/gCMAn4FXG+tPVhkOeFDANbafvlBbIkxpnsFn/ppoK+1dkB+HZ2K3FfecQcAA4FMYJsx5i1r7YHKvWoREalJ1MESEZG6oJExZi1wEggBvjTGNAFigHn59/0v0CZ//++BOcaYewDf/G3Dgb8DWGu3AvuAigas8pR33K+stanW2gxgMxBWBc8nIiJepIAlIiJ1QXp+9ygM8MfpGvkAZ/LPzSr46AVgrb0feBboAKw1xrQATAWeJwfP350NK/CY8o6bWeR2LlpZIiJS6ylgiYhInWGtTQUeAZ4E0oE9xphpAMbRP/92V2vtj9baXwEncILWMuAn+fd3BzoC24o9xV4gMn+fSKBz/vZzQFAZZVXkuCIiUkcoYImISJ1irV0DrANm4ASbu40x64BNwKT83f47f+jERpwAtA7n/CxfY8wGwAXMstZmFjv8h0BI/pLDB4Dt+c95Evg+f+jFfxd7TEWOKyIidYSx1nq7BhERERERkTpBHSwREREREZEqooAlIiIiIiJSRRSwREREREREqogCloiIiIiISBVRwBIREREREakiClgiIiIiIiJVRAFLRERERESkivx//givyMcZp4UAAAAASUVORK5CYII=\n" + "image/png": "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/OMfs3btWubNm8e8efN48sknL3jM+9//foLBIHPnzuWrX/0qCxcu7NbG5/Pxgx/8gDvvvJM5c+bgcrl45JFHemz33e9+l7e+9a00NDQwefLk5GOf+9zniEQizJ07l6uuuorPfe5zfXzVw5+52BDgFT+BMVOAxzunCBpj7gBusda+J3H7PmCRtfZDvRz/EeAB4EXgFWvtd3pp9z7gfQCTJk1acPDgwf5+KSIiIiIiSdu3b2fmzJnZ7oYMgp6+18aYrdbabhuIZaOKYE8TNXtNedbabwDfuNhJrbXfBb4LUF9fP7CpUUREREREpAfZqCJ4BJiYdnsCcCwL/RAREREREelX2QhYLwI1xpipxphc4G7gv7PQDxERERERkX41oAHLGPNTYBMwwxhzxBjzbmttFPgQ8P8B24H/stb+cSD7ISIiIiIiMhgGdA2WtfaeXu5/ErhwiRMREREREZFhJhtTBEVEREREREakbFQRFBEREREZcXY1XEfszJl+O5+7ooLaDc9etN2Xv/xl/uM//gO3243L5eJf/uVfWLRo0RU999KlS3nuuee63f/5z38ev9/PJz/5ySs6/0D6zne+Q35+Pvfff39Wnl8BS0RERESkH/RnuOrr+TZt2sTjjz/OSy+9hNfr5cyZM3R0dFzxc/cUrgZKNBrF4+m/WNLTpsiDSVMERURERESGqePHj1NRUYHX6wWgoqKCqqoqtm7dyg033MCCBQu4+eabOX78OADLly/n4x//ONdffz0zZ87kxRdf5Pbbb6empobPfvazyfP6/f5L6sdtt93GggULmD17Nt/97neT93/ve9+jtraW5cuX8973vpcPfehDADz44IN84hOfYMWKFXzqU5/ihRdeYOnSpcyfP5+lS5eyc+dOANasWcPtt9/OLbfcQk1NDX/xF39x0XN//vOf59FHHwXgX//1X7n22mu5+uqreec730lbW1vy+T/ykY+wdOlSpk2bxi9+8YtLer0XooAlIiIiIjJM3XTTTRw+fJja2lo+8IEP8Ic//IFIJMKHP/xhfvGLX7B161YefvhhPvOZzySPyc3NZf369TzyyCPceuutfOtb3+L1119nzZo1nD179rL68f3vf5+tW7eyZcsWvvGNb3D27FmOHTvGF7/4RTZv3sz//u//smPHjoxjdu3axVNPPcXXvvY16urqWL9+PS+//DJf+MIX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\n" }, "metadata": { "needs_background": "light" @@ -492,20 +490,20 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 11, "outputs": [ { "data": { "text/plain": "
" }, - "execution_count": 15, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": "
", - "image/png": "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EOjO7H/++NPa80iSSCK72JVqgeT/8T8+5EPA6ZbYgAaXdTGsQIVsX3f69OnMnz+fJUuWYDytdp9++ik9evTA19eXihUr0qlTJyIiIpRgiYiIiIhIJrZuhZtvhsqVYcECKFPG7YhS8cabhp7HAAYAzmQa0USnaun6iZ+Yyczk86pT/bKkqwY1Lqt/wYIFjB07lh9++IESJUok769RowZLly5l8ODBxMTEsHr1ap588sk8v9/8pFkERURERERy0/790KkTxMfDjz9C3bpuR3RVjnP8j0WSPY8d7OASl6hFLToM7MCyZcs4duwYlSpVYtSoUYwZM4aLFy9Svnx5wJnoYvLkyZw7d46hQ4eydetWrLUMHTqUESNG5Mt95NcsgkqwRERERERyy9Gjznirw4fhhx+gRQu3I8oT5zlPJJGc4hQ96el2ONmiadpFRERERAqTs2edboH79jmTWxTR5AqgJCXpSEe3wyiQlGCJiIiIiFytixehb1/YsAG++sppxZJiSQmWiIiIiMjVSEyEu++GxYth2jS49Va3IxIX5e4yziIiIiIixYm1ziLCs2bB+PFw771uRyQuU4IlIiIiInKlXnkFJk+G556Dv/7V7WikAFCCJSIiIiJyJd56C/7xD7jvPhgzxu1opIDQGCwRERERkWyIjY2lc+fOXLx4kYTjx+l38CCj+vRhRJkyzAsLw8/Pj7p16zJ16lSCg4PdDldcohYsEREREZFs8Pf3Z+nSpWx6/XU2Hj7MglKlWP3kk3S/6SaioqKIjIykQYMGjFFrVrGmBEtEREREJBuMMQRt3gx33EF8kybE166NCQggPDwcHx+nY1iHDh2Ijo52OVJxkxIsEREREZHs2LKFxJtvpmViIhV37aJ7jx60b98+VZEpU6bQs2dPlwKUgkAJloiIiIhIVvbtg5tuwjswkI3btxN98CBr1qwhKioqucjo0aPx8fFh0KBBLgYqblOCJSIiIiKSmaNHITwczp+H776D2rUJDg6ma9euLFiwAIDp06czf/58PvnkE4wxLgcsblKCJSIiIiKSmb/8BXbu5OiMGZyqXh2ACxcusHjxYho1asSCBQsYO3Ysc+fOpUSJEi4HK27TNO0iIiIiIpm58Ub46it+mzmTe//+dxITE7l06RJ33XUXvXr1ol69ely8eJHu3bsDzkQXkydPdjlocYsSLBERERGR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IxqITtESPRsn4UpFnx0prpRgiUi2eCYq+BDYZq19I4MyrYD/AL2BoUA5Y8xrObjMWqC+Maa2Z9D0AGDu1UUubjPGhHharjDGBOJ049mepow+O/nMWvuCtTbUWlsL5/1aaq0dnLKMfi6SHmvtYeCAMaahZ9efgK0py+izI8WZEiwRya5OwN0433Jv9Gw3pylTArjTWvurtfYScC+wL21FxpjPgFVAQ2NMtDHmPgBrbQIwHPgOZxKNz621W/LuliSfVAG+N8ZE4vzRtMham3ZKcH12Cib9XCQjjwGfeP67bgn8M81xfXak2NI07SIiIiIiIrlELVgiIiIiIiK5RAmWiIiIiIhILlGCJSIiIiIikkuUYImIiIiIiOQSJVgiIiIiIiK5RAmWiIiIiIhILlGCJSIihZIxpnyKNdkOG2MOep6fM8a863Z8IiJSPGkdLBERKfSMMSOBc9ba8W7HIiIixZtasEREpEgxxnQ1xsz3PB9pjJlujFlojNlrjOlrjBlnjNlsjFlgjPH1lGtjjPnBGLPOGPOdMaaKu3chIiKFlRIsEREp6uoCtwC9gY+B7621zYALwC2eJGsS0M9a2waYAox2K1gRESncfNwOQEREJI99a62NN8ZsBryBBZ79m4FaQEOgKbDIGIOnzG8uxCkiIkWAEiwRESnqLgJYay8ZY+LtH4OPL+H8HjTAFmttR7cCFBGRokNdBEVEpLjbAYQYYzoCGGN8jTFNXI5JREQKKSVYIiJSrFlr44B+wFhjzCZgI3Ctq0GJiEihpWnaRUREREREcolasERERERERHKJEiwREREREZFcogRLREREREQklyjBEhERERERySVKsERERERERHKJEiwREREREZFcogRLREREREQkl/w/nOcQ8AhhzJwAAAAASUVORK5CYII=\n" + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1cAAAFgCAYAAABXIoPMAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/d3fzzAAAACXBIWXMAAAsTAAALEwEAmpwYAABvgElEQVR4nO3dd3hVVfr28e8656QHEkISKQFC7z0CiiAWGCw/EURRUQSsjDqW0VHHmVFHeRGG0VFGRUcpFmxYQFBEgooiKKFKDS1KQKQllPSTrPePHUICARJIclLuz3XtKznr7LP3s8Mw5mat/WxjrUVERERERETOjsvXBYiIiIiIiFQHClciIiIiIiJlQOFKRERERESkDChciYiIiIiIlAGFKxERERERkTKgcCUiIiIiIlIGFK5ERESOY4wZY4z53RhzxBhT19f1iIhI1aBwJSJSgxljkowxGfkh4uj2X1/XVVrGmJHGmNz8+g8ZY1YZY648w2P5Ac8BA6y1odba/WVbrYiIVFcKVyIi8n/5IeLodk9xOxljPMWMuUtzotLuX0pLrLWhQDjwBvCBMSaiNAfIv8ZzgEBgXWkLMA79t1VEpIbSfwBERKRY+bNBi40xzxtjDgBPGmOmGWNeMcZ8boxJAy4yxrQ1xnxjjEk1xqwzxlxV6Bgn7H/cOa43xiQcN/aAMWZ2/veXG2PWG2MOG2N2GmMeOl3d1to8YAoQBDQzxgQYYyYaY37NX+o32RgTlH/8fsaYZGPMI8aY3cBbwKb8Q6UaYxbm73e+MWaZMeZg/tfzC9X7jTFmrDFmMZCef05rjPmjMWZzfu1PG2OaG2OW5M+sfWCM8c//fB1jzBxjzF5jTEr+9zHHHf/p/D+Lw8aY+caYyELvX2CM+SH/57/DGDMyf/yk1y0iIuVD4UpERE6lJ7ANiAbG5o/dmP99LeBH4DNgfv4+9wLvGGNaFzpG4f2/P+74s4HWxpiWx+0/I//7N4A7rbW1gA7AwtMVnD/7dBtwBNgMjAdaAV2AFkBD4B+FPlIPiACaAKOB9vnj4dbai/Nnv+YCLwJ1cZYMzj3uXqybgTvyr/GX/LGBQHegF/AX4DVgONAo/1puyN/PBUzNP39jIAM4fmnmjcAonJ+xP/BQ/rU2Br4AJgFR+de4Kv8zp7tuEREpYwpXIiLyaf6sx9Ht9kLv7bLWTrLWeq21Gfljs6y1i/NniLoAocCz1tpsa+1CYA7HgkOR/a21mYVPbK1NB2Yd3T8/ZLXBCV0AOUA7Y0xta22KtXbFKa6jlzEmFdidf7zBwCHgduABa+0Ba+1h4P8B1xf6XB7whLU2q9A1FnYFsNla+1b+z+FdYCPwf4X2mWatXZf/fk7+2Hhr7SFr7TpgLTDfWrvNWnsQJxB1zf8Z7LfWfmStTc+vbyxw4XE1TLXWJubX9wHOzx2csLbAWvuutTYn/1irjDGmBNctIiJl7IT18yIiUuNcba1dcJL3dpxmrAGwIz9oHfULzizJqY5R2Azg38A/cWZoPs0PXQDXAH8DnjXGrAEetdYuOclxllprLyg8YIyJBoKB5U7ecIaBwvd+7T0+9B2nAcdmo44qyTX+Xuj7jGJe18uvMRh4Hmemq07++7WMMW5rbW7+692FPpuOE2jBmQXbWsy5ozj9dYuISBnTzJWIiJyKPc3YLqDRcU0cGgM7T3OMwuYDkcaYLjgzTkeXBGKtXWatHYSzHO5TnFmb0tiHE2TaW2vD87ew/MYXJa1vF86SvcJKe42n8megNdDTWlsb6Js/bk7+kQI7gObFjJfkukVEpIwpXImIyNn4EUgD/mKM8TPG9MNZLvdeSQ9grfUCM4F/4dz79BWAMcbfGDPcGBOWv9TuEJB78iMVe+w84H/A8/mzWBhjGhpj/lCKw3wOtDLG3GiM8RhjhgHtcJY/loVaOEEoNf/+ridK8dl3gEuNMdfl11bXGNOljK5bRERKSeFKapz8TmTrjDFrjTHvGmMCfV2TiI99Zoo+5+qTkn7QWpsNXAVchjNb8jIwwlq7sZQ1zAAuBT7MD1tH3QwkGWMOAXcBN5XyuACPAFuApfnHWYAzU1Qi+c+5uhJnhmk/TnOKK621+86gluL8B6ez4T5gKTCvFLX9ClyeX9sBnGYWnfPfPqvrFhGR0jPWns1KBpGqxRjTEKdbWTtrbYYx5gPgc2vtNN9WJiIiIiJVnWaupCbyAEH57ZqDce6nEBERERE5KwpXUqNYa3cCE4Ffgd+Ag9ba+b6tSkRERESqA4UrqVGMMXWAQUBTnPbKIcaYM7mHQ0RERESkCIUrqWkuBbZba/fmdx/7GDjfxzWJiIiISDVQIxpaREZG2tjYWF+XIb6Sl0dmYiLk5ZFuLb/l5dGmdWtcfn4kJSUREhJCdHS0r6ssd0lJSRw8eBCPx0P79u0Lxvfs2cOePXswxhAWFkZMTIwPqxQRERHxjeXLl++z1kadzTE8ZVVMZRYbG0tCQoKvyxAfSV+5kl9uHgFep7vzpH17mbdpE25juCAsjPHduhNSrx6eqChni4469n1UFJ66dTH+/j6+irO3aNEiQkNDGTFiRMHfh6+//pqxY8eyefNmAgIC2LNnT40ImiIiIiLHM8b8crbHqBHhSmq2wFat8NStS+6hg7iCg/l/EybwzKFDePfuLdhydu8m4+efyT1wAIqZzXWHhxcNXEcDWGRkkXFXSIgPrrBk+vbtS1JSUpGxV155hUcffZSAgAAABSsRERGRs1Alw5UxphnwOBBmrR3q63qkcnOFhND887lkJiYS2KrVKQOQ9Xrx7j+QH7r2FAlg3r378O7dS1bSdrx790FOzonnCg4uEsDcx4Wvo5s7PBxjTHledokkJiby3Xff8fjjjxMYGMjEiRM599xzfV2WiIiISJVU4eHKGDMF50n3e6y1HQqNDwReANzA69baZ092DGvtNuBWY8zM8q5XqgdXSAjBXbuedj/j8eB3TjR+50QD7U+6n7WW3NTU48LXXnL3OQHMu2cvmevW4927l7z09BPP4+eHOyoST+TRwHV8CIt2xurWxXjK76+p1+slJSWFpUuXsmzZMq677jq2bdtWKYKfiIiISFXji5mracB/gTePDhhj3MBLQH8gGVhmjJmNE7TGHff50dbaPRVTqkjxjDF46tTBU6cOtGp1yn3z0tKOBbCj4Ss/gHn37iXn11/JWL6c3NTU4k6EOyKi2NkvT2RkkfvDXIGBJz3/0Vm748XExDBkyBCMMfTo0QOXy8W+ffuIijqrezlFRESqrJycHJKTk8nMzPR1KVJOAgMDiYmJwc/Pr8yPXeHhylq7yBgTe9xwD2BL/owUxpj3gEHW2nE4s1wiVZYrJAT/kBD8T9Ox0mZn492/v+hs2J69RYJZVmIi3n37IDf3xPPUqlU0eEVF4QoL48D0adiMTFwhIeQ+/ldsTg7efftwhYQwaNAgFi5cSL9+/UhMTCQ7O5vIyMhy+kmIiIhUfsnJydSqVYvY2Fit5KiGrLXs37+f5ORkmjZtWubHryz3XDUEdhR6nQz0PNnOxpi6wFigqzHmsfwQdvw+dwB3ADRu3LhsqxUpB8bfH7/69fGrX/+U+9m8PHJTUk4MYIVmxTLWrMG7dy+20L+6PbB9Gz8NGkRqbi4x9etzT91I/q9OHf6+53da/fvf+Lk9PNu5M7+OuAVXSAiu4OBCX4NPHAsufqw6dFYUyMzMpG/fvmRlZeH1ehk6dChPPfUUDz/8MJ999hn+/v40b96cqVOnEh4e7utyRUTKTGZmpoJVNWaMoW7duuzdu7d8ju+L51zlz1zNOXrPlTHmWuAP1trb8l/fDPSw1t5bFueLi4uzasUuNY21Fu+ePWy/Zih5R47gCg4i+q9/Ba+XvPR08tLSyEtPx6ank5uWduxrWnqR949+LSnj53cscIUE4wp2vprgYNwhIUW+Fh/QCn8uBFdwEMal551XNGstaWlphIaGkpOTwwUXXMALL7zAoUOHuPjii/F4PDzyyCMAjB8/3sfVioiUnQ0bNtC2bVtflyHlrLg/Z2PMcmtt3Nkct7LMXCUDjQq9jgF2+agWkWrBGIPfOefQ4st5JeqUeCo2Lw+bkeGErWKC1wljacW8t28/WYXGbFZWya/laBA7fjYt+PgZtpPMqoUUHTMBAfoXydMwxhAaGgo49x/k5ORgjGHAgAEF+/Tq1YuZM9VXSERE5KjKEq6WAS2NMU2BncD1wI2+LUmkeihpp8RTMS4XJiSkTJ/jZXNyyMvIOHlQKy6gFfqaeyCFnOSdhfZPg7y8kp3c7S7REsdTBbTjv5ZnV0dfyc3NpXv37mzZsoW7776bnj2LrtaeMmUKw4YN81F1IiIVI+6Zr9h3JLvMjhcZ6k/C3/qfcp/U1FRuu+021q5dizGGKVOmcN555zFt2jQGDBhAgwYNAIiNjSUhIaFc7pfu168fEydOJC7uzCZyJk+eTHBwMCNGjCjTWi6//HJmzJhx0iXphc97/M+rIviiFfu7QD8g0hiTDDxhrX3DGHMP8CVOh8Ap1tp1FV2biFQc4+eH288Pd+3aZXI8ay02K+vkAe0UQS0v3dk3Z/fuIu/ZjIySX4+//1kHtCJhLTjY57NrbrebVatWkZqayuDBg1m7di0dOjhP0Bg7diwej4fhw4f7tEYRkfJWlsGqpMe77777GDhwIDNnziQ7O5v0/OX506ZNo0OHDmUeFrxeL56z/EfC3Nxc3G53weu77rrrbMsq1ueff37K9wuft7x+Xqfii26BN5xk/HPg1D8tEZGTMMZgAgOdlvQREWVyTJubS15GZn7gyg9r6WnFBrQT7l1LTyc37QjevXuKhDxbzMOnT3JBuIKCMCHBuINDinwtaUBzvoYUNCQxfn6nDWyFW/cfnakMDw+nX79+zJs3jw4dOjB9+nTmzJlDfHy8zwOgiEh1c+jQIRYtWsS0adMA8Pf3x9/fn5kzZ5KQkMDw4cMJCgpiyZIlAEyaNInPPvuMnJwcPvzwQ9q0aVPkeJmZmYwZM4aEhAQ8Hg/PPfccF110EdOmTWPu3LlkZmaSlpbG3LlzGTVqFOvXr6dt27ZkFPoHxvnz5/PEE0+QlZVV0MwoNDSU2NhYRo8ezfz587nnnnu4/vrrCz7z5JNPEhoaykMPPUS/fv3o2rUry5cvZ+/evbz55puMGzeOn3/+mWHDhvHMM8+QlJTEwIED6dmzJytXrqRVq1a8+eabBAcHF7mewrN1b775JhMnTsQYQ6dOnXjrrbcKznt0v8I/r6CgoHL6Uzum+q1jEREpI8btxh0agju0DJdDZmeffAatuKWRBaHOGcvdt5+ctF+LNhspaWMij+eUSyGNvx+H531JXnY2qW4359xxB3UbxZDldvPlJ5/w59Gjmf366zw7YQIL584l0O3GWquAJSJShrZt20ZUVBSjRo1i9erVdO/enRdeeIGhQ4fy3//+94SlepGRkaxYsYKXX36ZiRMn8vrrrxc53ksvvQTAzz//zMaNGxkwYACJiYkALFmyhDVr1hAREcFzzz1HcHAwa9asYc2aNXTr1g2Affv28cwzz7BgwQJCQkIYP348zz33HP/4xz8A55lR33///Wmvy9/fn0WLFvHCCy8waNAgli9fTkREBM2bN+eBBx4AYNOmTbzxxhv07t2b0aNH8/LLL/PQQw8Ve7x169YxduxYFi9eTGRkJAcOHCjy/sl+XuVN4UpEpAIZf3/c/v64y6h9ubUWm5l56oB2mqWROSkpzn1sqanOvWvA74cPM/rBB8izkIdlYK3atHv5Ff6wbSs51nJRx44AdA4O5p/Nmh8LbEdb8ocE4woKPmHc2YIKlj4WdIjMHyv4fDW8h01O7mT3lwBMnDiRhx9+mL179+o5fFIjeL1eVqxYwaRJk+jZsyf33Xcfzz77LE8//XSx+w8ZMgSA7t278/HHH5/w/vfff8+99zoNuNu0aUOTJk0KwlX//v2JyF/tsWjRIv70pz8B0KlTJzp16gTA0qVLWb9+Pb179wYgOzu74O8nUOJ7b6+66ioAOnbsSPv27amf/+iZZs2asWPHDsLDw2nUqFHBeW666SZefPHFk4arhQsXMnTo0IL/X4goo1UrZ0v/9RIRqcKMMZigIFxlsNQhLy2NrVdcSe6hQ7Q/5xxWff01YAuWPeZlZLA2Pf3YTNrRLSP92D757+Xu209ORnKRMFfcw69Pel3+/vkBLKhQACsU0IKCCnWNLBTUjga6kOAT9jdBautfWZ3s/pIdO3bw1Vdf6XmVUqPExMQQExNT0ERo6NChPPvssyfdPyAgAHDuk/V6vSe8f6rHLoUc16iquJUI1lr69+/Pu+++W6JjnK5Ol8tV8P3R10frPv78p1oZUVlXTihciYgI4HSWbD53zlm37i+OtdbpEJnfKKRIODsayjKOvbZFxo/tn5OaesI+pWGOn0ULKjpj5goJdsLq8YEu5MR9TXDJ72WTkzvZ/SUADzzwABMmTGDQoEE+rFCkYtWrV49GjRqxadMmWrduTXx8PO3atQOgVq1aHD58uFTH69u3L++88w4XX3wxiYmJ/Prrr7Ru3ZoVK1YUu99FF13E2rVrWbNmDeA8duPuu+9my5YttGjRgvT0dJKTk2nVqlXZXHAhv/76K0uWLOG8887j3Xff5YILLjjpvpdccgmDBw/mgQceoG7duhw4cOCE2asz+XmdLYUrEREpUBat+4tjjHFmo/z9oU6dMjuuzctzlkUWzKJlFJpZK/RctSLjRWfdco8cxrvn9yLv2+xSdAc72tr/+KWRRZY6hhQNdCHH75s/6xZSaKathiyNPNn9JfHx8TRs2JDOnTv7ukSp4SJD/cu8FfvpTJo0ieHDh5OdnU2zZs2YOnUqACNHjuSuu+4q0tDidP74xz9y11130bFjRzweD9OmTSsyc3TUmDFjGDVqFJ06daJLly706NEDgKioKKZNm8YNN9xAVv4zKp955plyCVdt27Zl+vTp3HnnnbRs2ZIxY8acdN/27dvz+OOPc+GFF+J2u+natWvBP9IcdfzPqyIaWphTTRVWF3FxcTYhIcHXZYiISBVhvd5jM2aFwlrRh2mnFw11hcNcWuHxY/uVamlkQECR5Y9F7lErZsbNFVzMrFuhsHb0/co2y5aQkECvXr1YvHhxwf0lR298nz9/PmFhYeX6LB+R423YsIG2bdv6uowaJykpiSuvvJK1a9dWyPmK+3M2xiy31p5V94ua8c9iIiIipWA8Hty1auGuVavMjmmtLegWaY+fQSvYMk6cdSsynk7OgZQiwc2WZmlkoRb/Re5RK6bpSNEllCc2HSk863YmSyOPtv1vEBFxwv0lTz75JNu3by+YtUpOTqZbt2789NNP1KtXr1TnERGpSApXIiIiFcAY48xGBQSU/dLIwjNkJ8y4HTfrdkJDkgxyDx0iZ/dvRWbdSvxMNnDa/J/sfrYis25OoMPjYf9rr2EzM3GFhNCwbl3W/fgj7eLiiI+Pp1u3bsTHxxccXjNXItVfbGxshc1alSeFKxERkSrMuFyYkJAybUACOA1IilsGmVHMzNtxjUeOzrzl7N2D/eW4pZF5eUXOk5uVxZ8zMxl2YT9ysDQKCmZCt24k3XAj7jp1cNcJJ+/QIQ68/Taexk1w1wnHU6dO/nt1cNWqVemWOopIzaVwJSIiIicwfn64/fxw165dZse01mKzsvDu20fSDTeSd/gwruAgLvrzQyxKTyc3JYXc1BS8KSnkpqSSs3MnmWvX8mXDGHInv8pvxR3U48EdHo6nTjju8GOh6/gQ5q4T4exTp06lvPesIo0ePZo5c+YQHR1dMFPw8MMP89lnn+Hv70/z5s2ZOnUq4eHh5OTkcNttt7FixQq8Xi8jRozgscce8/EViFReClciIiJSIYwxmMBA/GNiaDHvixK3/bfWkpeWTm5qihPADhwoCGC5KSlFQlnW1q35r1NPmCUrqCMgoCB0nT6U5c+Q+Z++w1tVMXLkSO655x5GjBhRMNa/f3/GjRuHx+PhkUceYdy4cYwfP54PP/yQrKwsfv75Z9LT02nXrh033HADsbGxvrsAkUpM4UpEREQqXGna/htjcIeG4A4NgZiYEn3G5uWRd+jQsRB2NJil5M+MHTj2OnvnTnJTUsk7dOjk9QYH446IKBrAThXKwsIqbTv9vn37kpSUVGRswIABBd/36tWLmTNnAs7PPi0tDa/XS0ZGBv7+/tQuw9lMkeqmcv6tFxERETkLxuXCHR6OOzwcmpbsMzYnh9yDB48FsKMzY6kpRccOpJC9dRvelJRTdmt0hYXhCXeWIjrBrFAACz8xlLlq1cK4XGXzAzgLU6ZMYdiwYYDTvXHWrFnUr1+f9PR0nn/++RMe1Frt/aslpO0pu+OFRMPDm0+7W25uLnFxcTRs2JA5c+YAMG3aNAYMGECDBg2A8m320q9fPyZOnEhc3Fl1Jq9xFK5EREREcO4z80RG4omM5MRHrBYvLzOT3NTUorNixSxXzNm1i8x168g9cODknRjdbicQ1gnHU3hWLKJO0Vmx8GNLGk1wcJnePzZ27Fg8Hg/Dhw8H4KeffsLtdrNr1y5SUlLo06cPl156Kc2aNSuzc1Z6ZRmsSnG8F154gbZt23Ko0IzqtGnT6NChQ0G4KiterxfPWc605ubm4na7y6iiqkvhSkREROQMuQIDcdWrh18Jn79lrcWmp+NNSSU35cDJQ1lKClnbt5G7ItW5f+wkD6A2/v4nLk0sFMw8Ece+NwEBePfuJah9+2KPNX36dObMmUN8fHxBYJsxYwYDBw7Ez8+P6OhoevfuTUJCQs0KVz6QnJzM3Llzefzxx3nuuecAmDlzJgkJCQwfPpygoCCWLFkCwKRJk/jss8/Iycnhww8/pE2bNkWOlZmZyZgxY0hISMDj8fDcc89x0UUXMW3aNObOnUtmZiZpaWnMnTuXUaNGsX79etq2bUtGRkbBMebPn88TTzxBVlZWQcOT0NBQYmNjGT16NPPnz+eee+7h+uuvr7gfUiWlcCUiIiJSQYwxmJAQ/ENCIKZhiT5j8/LIO3z45MsVC90/lrlrPd7UVPIOHjzp8Tz16uF++aUiY/PmzWP8+PF8++23BAcHF4w3btyYhQsXctNNN5Gens7SpUu5//77z+japeTuv/9+JkyYwOHDhwvGhg4dyn//+98TlupFRkayYsUKXn75ZSZOnMjrr79e5FgvveT8Wf/8889s3LiRAQMGkJiYCMCSJUtYs2YNERERPPfccwQHB7NmzRrWrFlDt27dANi3bx/PPPMMCxYsICQkhPHjx/Pcc8/xj3/8A4DAwEC+//77cv15VCUKVyIiUqU8//zzvP766xhj6NixI1OnTiUwMNDXZYmUG+Ny4Q4Lwx0Whn8Ju/RZr7fg/rHclBTSVqxg/0svY7OzuX/1apZfeCH7U1OJiYnhqaeeYty4cWRlZdG/f3/AaWoxefJk7r77bkaNGkWHDh2w1jJq1Cg6depUjlcrR9vkd+/enW+++ea0+w8ZMgSA7t278/HHH5/w/vfff8+9994LQJs2bWjSpElBuOrfv3/BPXSLFi3iT3/6EwCdOnUq+HNeunQp69evp3fv3gBkZ2dz3nnnFRz/6P154lC4EhGRKmPnzp28+OKLrF+/nqCgIK677jree+89Ro4c6evSRCoV4/HgqVsXT926AAS2a0fqu++Re+gQ/+ncmeZz5xRpgX/rrbcWe5zQ0FA+/PDDCqlZHIsXL2b27Nl8/vnnZGZmcujQIW666SbefvvtYvcPCHDuEHS73Xi93hPet9ae9Fwhxz0Gobj796y19O/fn3fffbdEx6jpfN+SRkREpBSOtoT2er2kp6eX+Y3dItWRKySE5nPn0PiN108IVlK5jBs3juTkZJKSknjvvfe4+OKLC4JVrVq1iiwVLIm+ffvyzjvvAJCYmMivv/5K69atT7nf2rVrWbNmDeDMYi5evJgtW7YAkJ6eXjDzJSdSuBIRkSqjYcOGPPTQQzRu3Jj69esTFhZW5Pk8InJyR58tpmBVSiHRleZ4I0eO5K677qJLly5FGk6cyh//+Edyc3Pp2LEjw4YNY9q0aQWzXYWNGTOGI0eO0KlTJyZMmECPHj0AiIqKYtq0adxwww106tSJXr16sXHjxjO+hurOnGqqsLqIi4uzCQkJvi5DRETOUkpKCtdccw3vv/8+4eHhXHvttQwdOpSbbrrJ16WJSDWxYcMG2rZt6+sypJwV9+dsjFlurT2rB3tp5kpERKqE9Jx0Xv/odRo1aURUVBR+fn4MGTKEH374wdeliYiIAGpoISIiVUB6TjoXfXARB5MPkrwgmX8f/Dd1a9clPj6+SEtiERERX9LMlYiIVHpr960lw5uBX1M/grsH07xDc2Jbx5LtzeaOO+7wdXkiIiKAZq5ERKQK6BDZgXOCzyE1K5Vm1zYjdHgov6f/TqIrkUd/eJSBTQfSN6YvQZ4gX5cqIiI1mMKViIhUesF+wcy6ehaJKYm0qtOKIE8Qq/euZl7SPL5M+pIFvy4gyBPERY0u4rKml3F+g/Pxd/v7umwREalhFK5ERKRKCPYLpkt0l4LXXaK70CW6Cw/HPczy35fzRdIXfPXLV3y+/XNq+dfi0saXMrDpQHrU64HHpf/ciYhI+dN/bUREpEpzu9z0qN+DHvV78Neef2XprqXMS5rH/F/m88mWT4gIjKB/k/5c1vQyukZ3xWV0u7GIlFy/9/uxP3N/mR2vbmBdvhn2zUnf37FjByNGjGD37t24XC7uuOMO7rvvvlKdo1+/fkycOLFMGv588803TJw4kTlz5pz1sWoChSsREak2/Fx+9InpQ5+YPvwj9x98n/w9XyR9wawts3h/0/tEB0czMHYglzW9jPZ122OM8XXJIlLJlWWwKsnxPB4P//73v+nWrRuHDx+me/fu9O/fn3bt2pVpHSeTm5uL2+0+4897vV48npobMWrulYuISLUW4A7gkiaXcEmTS0jPSeebHd/wRdIXzNg4gzfXv0lMaAwDmw5kYOxAWtVppaAlIpVC/fr1qV+/PgC1atWibdu27Ny5k3bt2tGvXz969uzJ119/TWpqKm+88QZ9+vQhIyODUaNGsX79etq2bUtGRkaxx46Pj+ehhx7C6/Vy7rnn8sorrxAQEEBsbCyjR49m/vz53HPPPYSHh3P//fcTGRlJt27dCj6flpbGvffey88//4zX6+XJJ59k0KBBTJs2jblz55KZmUlaWhoLFy6skJ9VZaRwJSIi1V6wXzCXN7ucy5tdzsGsgyz8dSHzkuYxde1UXv/5dZqFNWNg04FcFnsZsWGxvi5XRASApKQkVq5cSc+ePQvGvF4vP/30E59//jlPPfUUCxYs4JVXXiE4OJg1a9awZs2aIoHoqMzMTEaOHEl8fDytWrVixIgRvPLKK9x///0ABAYG8v3335OZmUnLli1ZuHAhLVq0YNiwYQXHGDt2LBdffDFTpkwhNTWVHj16cOmllwKwZMkS1qxZQ0RERPn+UCo5LTwXEZEaJSwgjMEtB/Nq/1eJvzaev/X8G3UC6/DKqlf4v0//j+s+u44pa6ew68iusz7X6NGjiY6OpkOHDkXGJ02aROvWrWnfvj1/+ctfzvo8IlL9HDlyhGuuuYb//Oc/1K5du2B8yJAhAHTv3p2kpCQAFi1axE033QRAp06d6NSp0wnH27RpE02bNqVVq1YA3HLLLSxatKjg/aMhauPGjTRt2pSWLVtijCk4LsD8+fN59tln6dKlC/369SMzM5Nff/0VgP79+9f4YAWauRIRkRqsblBdhrUZxrA2w9idtpv5SfOZlzSP55c/z/PLn6dzVGcua3oZA5oMICo4qtTHHzlyJPfccw8jRowoGPv666+ZNWsWa9asISAggD179pTlJYlINZCTk8M111zD8OHDC8LUUQEBAQC43W68Xm/B+OmWNltrT/l+SEjIaY9lreWjjz6idevWRcZ//PHHIp+vyTRzJSIiAtQLqceI9iOYccUMPh/yOfd1u49MbybP/vQsl3x4Cbd+eSsfJn5ISmZKiY/Zt2/fE/4l95VXXuHRRx8t+AUpOjq6TK9DRKo2ay233norbdu25cEHHyzRZ/r27cs777wDwNq1a1mzZs0J+7Rp04akpCS2bNkCwFtvvcWFF15Y7H7bt29n69atALz77rsF7/3hD39g0qRJBUFt5cqVpbu4GkDhSkRE5DiNajXito63MfOqmcwaNIs7O9/JnvQ9/HPJP7n4g4sZs2AMs7fO5nD24VIfOzExke+++46ePXty4YUXsmzZsnK4AhEpK3UD61bo8RYvXsxbb73FwoUL6dKlC126dOHzzz8/5WfGjBnDkSNH6NSpExMmTKBHjx4n7BMYGMjUqVO59tpr6dixIy6Xi7vuuqvY/V577TWuuOIKLrjgApo0aVLw3t///ndycnLo1KkTHTp04O9//3sJr7rmMKebIqwO4uLibEJCgq/LEBGRKsxay8YDG/ki6QvmbZ/Hb2m/4e/yp09MHwY2HUjfhn0J9gs+4XNJSUlceeWVrF27FoAOHTpw8cUX88ILL7Bs2TKGDRvGtm3b1K1QpJLYsGEDbdu29XUZUs6K+3M2xiy31p7Vw8F0z5WIiEgJGGNoW7ctbeu25YFuD7B672rmJc3jy6Qvif81niBPEP0a9WNg7EC6RXcj6VASreq0OuE4MTExDBkyBGMMPXr0wOVysW/fPqKiSn9Pl4iIVC4KVyIiIqVkjKFLdBe6RHfh4biHWbFnBV9s/4KvfvmKL7Z/gcHgMi6CPcHc0/gevHlerLUYY7j66qtZuHAh/fr1IzExkezsbCIjI319SSIiUgYUrkRERM6C2+Xm3Hrncm69c3ms52O8u+Fdnlv+HLk2l/UvrueWjbfgPeIlMDKQ7jd359Ihl7Jw7kLeafMOoUGhTJ8+XUsCRUSqCd1zJSIiUobSc9IZ9OkgDmUfItQvlCfOe4LkI8lsSd3C1tStbE7dXKQRRkRgBC3CW9AivAXNw5vTsk5Lmoc3p7Z/7VOcRUTKi+65qhl0z1Uhxpi2wH1AJBBvrX3FxyWJiIgAEOwXzKyrZ5GYkkirOq1OaHJhrWVvxl62pGxhS+qWgtD16ZZPSfemF+wXHRxdELoKh6/immaIiEjlUOHhyhgzBbgS2GOt7VBofCDwAuAGXrfWPnuyY1hrNwB3GWNcwP/KuWQREZFSCfYLpkt0l2LfM8YQHRxNdHA05zc8v2A8z+axO213QeA6Gr7e3/Q+WblZBfs1DG1I8/DmRUJX07CmBHoCy/uyRETkNHwxczUN+C/w5tEBY4wbeAnoDyQDy4wxs3GC1rjjPj/aWrvHGHMV8Gj+sURERKo0l3HRILQBDUIb0Demb8F4bl4uO4/sZHPqZrambnVC18Et/LDrB7x53oLPNqrViOZhzWlR51joiq0di5/bz1eXJFItJF7Qh9x9+8rseO7ISFp9/90p9xk7diwzZszA7Xbjcrl49dVX6dmz51md9/zzz+eHH344YfzJJ58kNDSUhx566KyOX54mT55McHAwI0aM8HUpp1Xh4cpau8gYE3vccA9gi7V2G4Ax5j1gkLV2HM4sV3HHmQ3MNsbMBWYc/74x5g7gDoDGjRuX3QWIiIhUILfLTePajWlcuzGXNL6kYDwnL4dfD/1aZGnh5pTNfJv8Lbk2FwCP8dCkdhNa1Mm/nyvcuZ+rUa1GeFzF/wrw/PPP8/rrr2OMoWPHjkydOpXAQM2KSc1VlsGqJMdbsmQJc+bMYcWKFQQEBLBv3z6ys7PP+rzFBavy4vV68XjKLmYU97Djyqqy3HPVENhR6HUycNJ4bozpBwwBAoBiH1ltrX0NeA2chhZlVKeIiEil4Ofyo3l4c5qHN+cP/KFgPCs3i6SDSceWF6ZuYd2+dcxPmo/F+c+hv8ufpmFNi8xyNQ9vDgfhxRdfZP369QQFBXHdddfx3nvvMXLkSB9dpUjN89tvvxEZGUlAQABAwaMali9fzoMPPsiRI0eIjIxk2rRp1K9fn379+tG1a1eWL1/O3r17efPNNxk3bhw///wzw4YN45lnngEgNDSUI0eOlLiOq6++mh07dpCZmcl9993HHXfcAcAbb7zB+PHjadCgAS1btiQgIID//ve/jBw5koiICFauXEm3bt0YNmwY999/PxkZGQQFBTF16lRat27NtGnTmD17Nunp6WzdupXBgwczYcKEUx678Oza//73P1577TWys7Np0aIFb731FsHBwYwcOZLatWuTkJDA7t27mTBhAkOHDi3LP5oSqSzhqrgetCcNRNbab4BvyqsYERGRqirAHUDriNa0jmhdZDw9J53tB7cXCV3Lf1/O3G1zC/ZxH3Lz+5Hf+dvXf6Ndg3YkH0gmMCKw4BldIlL+BgwYwD//+U9atWrFpZdeyrBhwzj//PO59957mTVrFlFRUbz//vs8/vjjTJkyBQB/f38WLVrECy+8wKBBg1i+fDkRERE0b96cBx54gLp165a6jilTphAREUFGRgbnnnsu11xzDVlZWTz99NOsWLGCWrVqcfHFF9O5c+eCzyQmJrJgwQLcbjeHDh1i0aJFeDweFixYwF//+lc++ugjAFatWsXKlSsJCAigdevW3Hvvvbjd7lMe+6ghQ4Zw++23A/C3v/2NN954g3vvvRdwgun333/Pxo0bueqqq2p0uEoGGhV6HQPs8lEtIiIi1U6wXzDtI9vTPrJ9kfHD2Yede7nylxZ+MvQT/jPkPxh/Q2j7UMamjOWFd18o2kQjf8arbmBdn4auzMxM+vbtS1ZWFl6vl6FDh/LUU0/x8MMP89lnn+Hv70/z5s2ZOnUq4eHhPqtTpDRCQ0NZvnw53333HV9//TXDhg3jb3/7G2vXrqV///4A5ObmUr9+/YLPXHXVVQB07NiR9u3bF7zXrFkzduzYcUbh6sUXX+STTz4BYMeOHWzevJndu3dz4YUXEhERAcC1115LYmJiwWeuvfZa3G43AAcPHuSWW25h8+bNGGPIyckp2O+SSy4hLCwMgHbt2vHLL7+wb9++Ux77qLVr1/K3v/2N1NRUjhw5wh/+cGzm/uqrr8blctGuXTt+//33Ul9zWags4WoZ0NIY0xTYCVwP3OjbkkRERKq/Wv616BLdhS7RXUhJSeHLTV+ye8duCHR+uWn/e3vq963PltQtLPh1AR9t/qjgs+EB4ceez5V/P1eL8BaEB4ZXSO0BAQEsXLiQ0NBQcnJyuOCCC7jsssvo378/48aNw+Px8MgjjzBu3DjGjx9fITWJlAW3202/fv3o168fHTt25KWXXqJ9+/YsWbKk2P2PLiF0uVwF3x997fV6i+z7+OOPM3euM2O9atWqYo/3zTffsGDBApYsWUJwcDD9+vUjMzOT0z0fNyQkpOD7v//971x00UV88sknJCUl0a9fvxPqPXqtXq/3tMc+auTIkXz66ad07tyZadOm8c033xR7XF89y9cXrdjfBfoBkcaYZOAJa+0bxph7gC9xOgROsdauq+jaREREaqSsI7BnPQu+20TTpk2JiooCYPSNo1m6dCl/+8vfAOeXlf2Z+4u0it+SuoW52+ZyJOfYvRyRQZEnPJ+rRXgLQv1Dy7RsYwyhoc4xc3JyyMnJwRjDgAEDCvbp1asXM2fOLNPzipSnTZs24XK5aNmyJeAEoLZt2zJ//nyWLFnCeeedR05ODomJibRv3/40RzvR2LFjGTt27Cn3OXjwIHXq1CE4OJiNGzeydOlSAHr06MEDDzxASkoKtWrV4qOPPqJjx44nPUbDhg0BmDZt2mnrKumxDx8+TP369cnJyeGdd94pOEdl4YtugTecZPxzTtKcQkRERMpJ1hF4sQtkHabx3mCWLgkkPT2doKAg4uPjiYuLK9jVGENkUCSRQZH0qt+rYNxay+/pvxeErqNt4z/a/BEZ3oyC/eqF1DvhwchNw5qe1YORc3Nz6d69O1u2bOHuu+8+oV31lClTGDZs2BkfX8QdGVnmrdhP5ciRI9x7772kpqbi8Xho0aIFr732GnfccQd/+tOfOHjwIF6vl/vvv/+MwlVxnnnmGf7zn/8UvN66dSuTJ0+mU6dOtG7dml69nL/vDRs25K9//Ss9e/akQYMGtGvXrmB53/H+8pe/cMstt/Dcc89x8cUXn7aGkh776aefpmfPnjRp0oSOHTty+PDhM7vocmJ8NWVWkeLi4mxCQoKvyxAREal8dvwEUy+DPC/4hfDE3st5f973eDweunbtyuuvv15kqU1p5Nk8dh7ZyZaULWw96LSK35q6lW0Ht5GT59x/YTA0DG1YpHPh0dDl7/Yv8blSU1MZPHgwkyZNokOHDoDzL/QJCQl8/PHHasghJbZhwwbatm3r6zIqrSNHjhAaGorX62Xw4MGMHj2awYMHV/pjH6+4P2djzHJrbdxJPlIileWeKxEREfGFyJaQlwsuDwSF89T4//DUf8pm+d7Rhxs3qtWIi7ioYNyb52XH4R3HOhemOM00vkv+ruAZXW7jPN+r8NLCluEtaVS7ETm5OSSmJNKqTquCWa/w8HD69evHvHnz6NChA9OnT2fOnDnEx8crWImUoSeffJIFCxaQmZnJgAEDuPrqq6vEsSuKZq5ERERqsq0L4a3BcOlTcO5tEFC290WVRk5uDkmHkoqEri2pW9hxeEfBM7rcxo3BYLF40j08dt5j9GzWkzruOgz8w0AeeeQRPB4PDz74IN9++23B/WMiJaWZq5pBM1ciIiJS9jbMAb9g6Hkn+AX5tBQ/tx8t67SkZZ2WRcYzvBlsP7idralbWbxrMV9s/4I8m0favjRuuuombJ7FhYsWF7YgISKBV65/Bbxw8aUX4zZuevXqxeTJk310VVIV6dlu1Vt5Ti4pXImIiNRUeXmw6XNocYnPg9WpBHmCaFe3He3qtuOSxpewfPdyDmUfIrp1NG8teYsdR3awOWUzW1K3EP9rPNFPRxd8NiwwDG8dL+N/Gu8Et/yW8WfTREOqt8DAQPbv30/dur59jpuUD2st+/fvJzAwsFyOr3AlIiJSU+1aAYd/gzb/5+tKSizYL5hZV88qcs9VL4p2LtyfuZ/ElMSCwLU5ZTMzE2eSmZtZsF9MaAwt6rSgZXjLgtDVJKwJfi4/X1yWVCIxMTEkJyezd+9eX5ci5SQwMJCYmJhyObbClYiISE214TOnkUWrAafftxIJ9gumS3SXYt8r3C7+/AbnF4zn5uWy88hONqduZnPK5oLgVbiJhsfloWlY0yKBq2WdltQPqa8ZjBrEz8+Ppk2b+roMqaIUrkRERGoia2HjHIi9AILq+Lqacud2Od0HG9duzCWNLykYz8rNIulgEokpiQWzXCv2rODz7ccevRniF1LQtbBw6KoTWP1/biJSOgpXIiIiNdG+RNi/BXre5etKfCrAHUDriNa0jmhdZPxw9uGCsLU5ZTObUzfz1S9f8dHmjwr2iQyKPCFwNQtrpvu5RGowhSsREZGaaMNnztc2V/i2jkqqln8tukZ3pWt014Ixay17M/ayJWXLseWFqZv5YNMHZOVmAc5DkWNqxdAyvKVzT9fR+7lqN8Hj0q9dItWd/paLiIjURBvnQMM4qN3A15VUGcYYooOjiQ6O5vyGRe/nSj6SXBC2jt7P9U3yN+TZPAD8XH40C2t2QhONeiH1dD+XSDWicCUiIlLTHEyGXSvh0id9XUm14Ha5aVK7CU1qN+HSJpcWjGflZrEtddux5YWpm0nYncDcbXML9gn1Cy1YWlh4iWF4YLgPrsSRmprKbbfdxtq1azHGMGXKFM477zwAJk6cyMMPP8zevXuJjIz0WY0ilZXClYiISE2zMf+X+zZX+raOai7AHUDbum1pW7dtkfFD2YecpYWFZrrmJc3jcPbhgn2igqKOha38wNUsvBlBnvJ/Htl9993HwIEDmTlzJtnZ2aSnpwOwY8cOvvrqKxo3blzuNYhUVQpXIiIiNc3GORDZGiJb+rqSGqm2f226ndONbud0Kxiz1rInfQ+bUzcXuafr/U3vF7mfq1GtRgWB62j4alyrcZndz3Xo0CEWLVrEtGnTAPD398ff3x+ABx54gAkTJjBo0KAyOZdIdaRwJSIiUpOkH4CkxXDB/b6uRAoxxnBOyDmcE3IOFzS8oGA8Ny+XHYd3FLmXa3PKZr7e8XXB/Vz+Ln+ahTc7oXPhOcHnlPp+rm3bthEVFcWoUaNYvXo13bt354UXXiA+Pp6GDRvSuXPnMr1ukepG4UpERKQmSZwHNldLAqsIt8tNbFgssWGx9G/Sv2A805vJtoPbirSL/2n3T8zZNqdgn1p+tYo00DgavsICwk56Pq/Xy4oVK5g0aRI9e/bkvvvu48knn2TRokXMnz+/XK9VpDpQuBIREalJNsyB2g2hQdfT7yuVVqAnkHZ129Gubrsi4wezDhYErqNfv9j+BR8kflCwT3RQdNEGGvnP58qzeaQGptIwpiE9e/YEYOjQoTz55JNs3769YNYqOTmZbt268dNPP1GvXr2Ku2iRKkDhSkREpKbIToetC6HbzaD239VSWEAY3c/pTvdzuheMWWv5Pf33IoFrc+pmlm1cRnZeNuDcz+UyLgBSAlOYvWQ2V/a6kvj4eLp160Z8fHzB8WJjY0lISFC3QJFiKFyJiIjUFFvjwZuhJYE1jDGGeiH1qBdSjz4xfQrGvXlefj38K1tStvDdzu/4bOtn5Npcom+MZtiNw3DluohuFM29z97LT7/9RIfIDgT7BfvwSkQqP2Ot9XUN5S4uLs4mJCT4ugwRERHf+vhO2PwlPLQF3Pr3VTkmPSedQZ8O4lD2IYL9ghnTeQwbDmxg1Z5VbEndAoDbuGkd0Zqu0V3pEtWFLtFdqBeiZYFSfRhjlltr487mGPp/VhERkZogNwcSv4DWVyhYyQmC/YKZdfUsElMSaVWnVZEZqoNZB1mzdw0r96xk1d5VfJT4Ee9seAeA+iH1C4JW1+iutKzTsszawotURfpfv4iISE3wy2LIPAhtrvB1JVJJBfsF0yW6ywnjYQFh9InpU7CkMCcvh8QDiQVha/me5XyR9IVzDE8wHaM6FsxudYrqRC3/WhV5GSI+pXAlIiJSE2yYA54gaH6xryuRKs7P5Uf7yPa0j2zPTdyEtZbf0n5j1Z5VBYHrtTWvkWfzMBha1GlB16iudIl2ZrhiQmNK/fwtkapC91yJiIhUd3l5jO4VwZxNmUQ3asHatWsL3po0aRL//e9/8Xg8XHHFFUyYMMGHhUp1kZaTxpq9a1i1dxWr9qxi9d7VpOWkARAZFEnX6K50jupM1+iutI1oi5/bz8cVi+ieKxERESmJXSsZ2T6Hex58ghHPvFMw/PXXXzNr1izWrFlDQEAAe/bs8WGRUp2E+IVwXoPzOK/BeQDk5uWyJXWLM7u1dyWr9qziq1++AiDAHUD7uu3pGt21IHSFB4b7sHqRM6dwJSIiUt1tnEPf2ACSulwOHAtXr7zyCo8++igBAQEAREdH+6hAqe7cLqfTYOuI1gxrMwyAPel7WLVnVcHs1vR103lj7RsANA1rWqQrYWztWC0llCpB4UpERKS62zgHYi+AwLAiw4mJiXz33Xc8/vjjBAYGMnHiRM4991wfFSk1TXRwNANiBzAgdgAAGd4M1u1bx6q9zr1bC35ZwMebPwYgPCC8SFfC9pHtCXAHlFtto0ePZs6cOURHRxcso3344Yf57LPP8Pf3p3nz5kydOpXw8HBycnK47bbbWLFiBV6vlxEjRvDYY4+VW21SuSlciYiIVGd7E2FfIvS444S3vF4vKSkpLF26lGXLlnHdddexbds2zRCITwR5goirF0dcPeeWlzybR9LBpIKwtWrPKr5J/gYAj8tDu7rtijTKiAyKLLNaRo4cyT333MOIESMKxvr378+4cePweDw88sgjjBs3jvHjx/Phhx+SlZXFzz//THp6Ou3ateOGG24gNja2zOqRqkPhSkREpDpb+5HztVk/OFL0rZiYGIYMGYIxhh49euByudi3bx9RUVEVXqbI8VzGRbPwZjQLb8aQlkMASMlMKbhva/We1by78V2mr58OQExojLOUMD9stQhvgcu4zujcffv2JSkpqcjYgAEDCr7v1asXM2fOBMAYQ1paGl6vl4yMDPz9/aldu/YZnVeqPoUrERGR6irrCHz/nPP9lD/A/31c5O2rr76ahQsX0q9fPxITE8nOziYysuz+9V+krNUJrMNFjS/iosYXAZCdm836/etZvXc1K/esZPGuxXy27TMAavnVolN0J7pEOUsJO0Z2LPJw5LMxZcoUhg1z7h0bOnQos2bNon79+qSnp/P8888TERFRJueRqkfhSkREpLras77g2xve2sE3T3dnXwbE1IviqafHMnr0aEaPHk2HDh3w9/dn+vTpWhIoVYq/279gpuqW9rdgrSX5cHJBR8KVe1by8s6XsVjcxk2rOq0KuhJ2ie5CvZB6pT7n2LFj8Xg8DB8+HICffvoJt9vNrl27SElJoU+fPlx66aU0a9asrC9XqgCFKxERkeoquh2EREFGKu/eXBeaXgibv4Lsw5AyHuZv5O1nxkDj88B1ZsunRCoTYwyNajeiUe1GXNX8KgAOZR9izd41BfdtfbLlE2ZsnAFAvZB6dI3qSudo55lbMaExbDu4jVZ1WhV7/OnTpzNnzhzi4+ML/iFixowZDBw4ED8/P6Kjo+nduzcJCQkKVzWUwpWIiEh1FRAKd//kzGBFt3Ne52Q4AWvdx7BqBiS8AbXqQ/vB0H4IxMSBZq+kGqntX5sLGl7ABQ0vAMCb52VTyianDfyeVazYs4Ivkr4o2D/AHUCdgDq82OXFIseZN28e48eP59tvvyU4+NjywsaNG7Nw4UJuuukm0tPTWbp0Kffff3+FXJtUPsZa6+sayl1cXJxNSEjwdRkiIiKVS9YRSJwH6z6BzfMhNxvCGkOH/KBVv7OCltQIvx35jVlbZ/Hq6lfxWi+7Ju/Ctc1F6oFUzjnnHJ566inGjRtHVlYWdevWBZymFpMnT+bIkSOMGjWK9evXY61l1KhRPPzwwz6+IjkTxpjl1tq4szqGwpWIiIiQeRA2fu50F9z2NeR5IaI5dBgCHa6B6La+rlCkXKXnpDPo00Ecyj5Ebf/azLp6Vpk1wJCqQeGqhBSuRERESiH9AGyYDWs/hqTvwOZBVFsnZHUYAnWb+7pCkXKRnpNOYkoireq0UrCqgRSuSkjhSkRE5Awd2QPrZzkzWr8uccbqd3aWDbYfDHWa+LY+ESlXzz//PK+//jrGGDp27MjUqVMJDAz0dVnlQuGqhBSuREREysDBZFj3qdMMY+dyZyzmXGdGq93VULu+L6sTkTK2c+dOLrjgAtavX09QUBDXXXcdl19+OSNHjvR1aeWiLMKVugWKiIhIyYTFwPn3ONuB7U4jjHUfw7xHYd5j0OR8Z9lg20EQGuXrakWkDHi9XjIyMvDz8yM9PZ0GDRr4uqRKrUo+1MIY088Y850xZrIxpp+v6xEREalxIppCnwfhru/hngTo9xik7YO5f4Z/t4I3r4YVbzr3b4lIldSwYUMeeughGjduTP369QkLC2PAgAG+LqtSq/BwZYyZYozZY4xZe9z4QGPMJmPMFmPMo6c5jAWOAIFAcnnVKiIiIiUQ2RL6PQJ3/whjfoALHoCUJJh9L0xsBe9cB6vfh8xDvq5UREohJSWFWbNmsX37dnbt2kVaWhpvv/22r8uq1HyxLHAa8F/gzaMDxhg38BLQHycsLTPGzAbcwLjjPj8a+M5a+60x5hzgOWB4BdQtIiIip2IMnNPe2S7+O/y2ymmEsfYT2PwluAOgZX/nHq1WfwD/EF9XLCInkZbl5bV3P6VR4yZERTnLfIcMGcIPP/zATTfd5OPqKq8KD1fW2kXGmNjjhnsAW6y12wCMMe8Bg6y144ArT3G4FCCgXAoVERGRM2cMNOjqbJf+E5KXOfdnrfsENs4Bv2BofZnTdbDFpeBXPbuPiVRFKWnZ9JnwNYd/TWXvF18zMeUQkeG1iI+PJy7urPo9VHuVpaFFQ2BHodfJQM+T7WyMGQL8AQjHmQUrbp87gDsAGjduXFZ1ioiISGm5XNC4p7P94f/BLz84M1obZjtfA2pDmyucGa1m/cDt5+uKRWq0zXsOcyTLizmnFSGte3NuXByhQf507dqVO+64w9flVWqVJVyZYsZO2iPeWvsx8PGpDmitfQ14DZxW7GdVnYiIiJQNlxua9nG2y/8F2791lg1u+AxWvwtBdaDtVU7Xwdg+zv4iUqHaNwgDwOMytLhsNAsenE5IQGWJDZVbZfkpJQONCr2OAXb5qBYRERGpCG4/Z0lgi0vhyudg60JY+7Ezm7ViOoREOc/P6jAEGvVyZsBEpNyFBHiICPbj3KYRPHddFwWrUqgsP6llQEtjTFNgJ3A9cKNvSxIREZEK4wlw7sFqfRnkZMDm+U7IWvkWLPsf1GoA7Qc7SwcbdnPu6RKRchMS6CHY36NgVUoV/tMyxrwL9AMijTHJwBPW2jeMMfcAX+J0CJxirV1X0bWJiIhIJeAXBO0GOVvWYdg0z2mG8dNrsPQlCG/izGa1HwL1OipoiZSDEH8PaVleX5dR5fiiW+ANJxn/HPi8gssRERGRyiygFnS61tkyUmHjXGdGa/GL8P3zULflsaAV3cbX1YpUGyEBHtKzc31dRpWjeT4RERGpGoLCoetwZ0vbDxtmOfdofTsBvh0P0e2hw2AnaNVt7utqRaq0kAAPhzJyfF1GlaNwJSIiIlVPSF2IG+1sh3fD+vygtfAZZ6vfxbk/q/1gCG902sOJSFEh/m52H8zwdRlVjsKViIiIVG216kHPO50tdQes/9RZOvjV352tUU9nNqv91c6+InJawf4e0rK0LLC01NNUREREqo/wRnD+vXDHN/CnlXDx3yE7DeY9Av9uA9OuhGVvQNq+MjtlZmYmPXr0oHPnzrRv354nnngCgIcffpg2bdrQqVMnBg8eTGpqapmdU6S8hQa4SctWQ4vSUrgSERGR6imiGfR9CMYshrt/ggsfcZYQzn0QJraCtwbDyredRhlnISAggIULF7J69WpWrVrFvHnzWLp0Kf3792ft2rWsWbOGVq1aMW7cuLK5LpEKEBzgIV0zV6WmcCUiIiLVX1RruOgxuGcZ3PU99L4P9m+FWXfDv1rAjOthzQdO6/dSMsYQGhoKQE5ODjk5ORhjGDBgAB6PcwdGr169SE5OLtNLEilPIf5usnPzyPbm+bqUKkX3XImIiEjNYYzzbKx6HeGSf8CuFU4jjHWfQOIX4AmElgOcZhgtB4B/cIkOm5ubS/fu3dmyZQt33303PXv2LPL+lClTGDZsWHlckUi5OPrw4PRsL/4efx9XU3UoXImIiEjNZAw07O5s/Z+GHT86Dyte9ylsmA1+IdDmcqcZRotLwBNw0kO53W5WrVpFamoqgwcPZu3atXTo0AGAsWPH4vF4GD58eAVdmMjZC/F3YkJadi7hJfs3BkHhSkRERARcLmhynrMNfBaSvneC1vpZ8POHEBAGba90glazC8HtV+xhwsPD6devH/PmzaNDhw5Mnz6dOXPmEB8fjzGmgi9K5MwFB7gBSMtSU4vSULgSERERKczldgJUswvh8omw7VuntfuGz2DVOxAUAe2ugtaXQ0Bt9rrr4Rdah/DwcDIyMliwYAGPPPII8+bNY/z48Xz77bcEB+uf/qVqObosUOGqdBSuRERERE7G7QctL3W2nOdha7xzj9bqD2D5NMDw24EAbvk6glzjR15eHtdddx1XXnklLVq0ICsri/79+wNOU4vJkyf79HJESurossD0bHUMLA2FKxEREZGS8AuENlc427ZFMGMoeLPoFJHJymt2QdMLodsIaHMlAFu2bPFxwSJnLtjfWRZ4RDNXpaJwJSIiIlJaDbtBcCRkpoJ/LehyE/z8AXx0KwSGQ6dh0O1mpyuhSBUUWqhboJScwpWIiIhIaQWEOg8m3rMeots5ry9+HLZ/CyvfguVT4adXoUFX6HozdBwKgWG+rlqkxI41tNCywNJQuBIRERE5EwGh0KjHsdcuFzS/yNnSDzgPJV75Fsx9EL58HNoNcmazmvR22sCLVGIFrdi1LLBUFK5EREREylpwBPS6C3reCbtWOiHr55mw5j2IaObMZnW5EWrV83WlIsUK8nNjjPOcKyk5l68LEBEREam2jHHuz7ryefjzJrh6MtSqD/FPwXPtYMb1sHEu5Ob4ulKRIlwuQ7Cfm3TNXJWKZq5EREREKoJ/MHS5wdn2b3Vms1bNgMQvIPQc6HyDM6MV2cLXlYoAEBzgIU0NLUpFM1ciIiIiFa1uc7j0SXhgPVz/LjTsDj9Mgv92hymXOaErO83XVUoNFxrgUUOLUtLMlYiIiIivuD3Q5nJnO7wbVr8LK96CT8fA539xugx2uxkadFMTDKlwwf5utWIvJYUrERERkcqgVj244AHofT/88oOzbHD1e05b9+j2TsjqNMxpliFSAUL8PXqIcClpWaCIiIhIZWIMxPaGwZPhoU1OMwxPAMx7FP7dGj4cBVsXQl6eryuVai4kwE26ugWWymlnrowxLqCXtfaHCqhHRERERI4KDIO40c62e60zm7XmfVj3MYQ1hq7DoctwCG/k60qlGgoO8PDrgXRfl1GlnHbmylqbB/y7AmoRERERkZOp1wEuGw8PboShU6BuM/hmHPynI7w1BNZ9At4sX1cp1UiIv1sNLUqppPdczTfGXAN8bK215VmQiIiIiJyCXyB0uMbZUn6BVe/Aynfgw5EQFAGdr3daup/TzteVShUXolbspVbScPUgEALkGmMyAANYa23tcqtMRERERE6tThO46K9w4SOw7WtY8Sb89D9Y+jI0jHOaYLQfAoH6lU1KL8TfQ3p2LtZajLpVlkiJwpW1tlZ5FyIiIiIiZ8jlhhaXOlvaPue+rBVvwWf3wbzHoP1g6DYCGvVUS3cpseAAN7l5lixvHoF+bl+XUyWUuBW7MeYqoG/+y2+stXPKpyQREREROWMhkXDe3dDrj7BzuTObtfYjZ/lg3ZbObFbnGyA02teVSiUXGuBEhbQsr8JVCZWoFbsx5lngPmB9/nZf/piIiIiIVEbGQEwcXPUi/HkTDHoJguvCV/+A59rCe8Nh0zzI1T01UrxgfydcqR17yZV05upyoEt+50CMMdOBlcCj5VWYiIiIiJSRgFDoepOz7U2ElW86DyjeOAdq1YcuNzrvRTTzdaVSiYT4O7NVepBwyZXmIcLhhb4PK+M6RERERKQiRLWCAc/Agxtg2NtQrxN8/zy82BWmXQmr34ecDF9XKZVASMDRmSuFq5Iq6czV/wNWGmO+xukU2Bd4rNyqEhEREZHy5faDtv/nbId2waoZzkOKP7kDPn8YOl3rtHRv0MXXlYqPhAQcnbnSssCSOu3MlTHGBeQBvYCP87fzrLXvlXNtIiIiIlIRajeAvg/BvSvhls+g1R9g5dvw2oUw+QL48TXISPF1lVLBCu65yvIyevRooqOj6dChQ5F9Jk2aROvWrWnfvj1/+ctffFFmpXLacJV/n9U91trfrLWzrbWzrLW7K6A2EREREalILhc07QvX/A/+vBEunwgY+OJhmNgaProNtn0LeXlndZrU1FSGDh1KmzZtaNu2LUuWLCl4b+LEiRhj2Ldv31lejJytgm6B2bmMHDmSefPmFXn/66+/ZtasWaxZs4Z169bx0EMP+aLMSqWkywK/MsY8BLwPpB0dtNYeKJeqRERERMS3gupAj9ud7bfVznOzfv4Afv4Q6sQ6DTC6DHdmvUrpvvvuY+DAgcycOZPs7GzS09MB2LFjB1999RWNGzcu44uRMxGc39AiLctL3759SUpKKvL+K6+8wqOPPkpAQAAA0dFq71/ShhajgbuBRcDy/C2hvIoSERERkUqkfme4YqLT0n3I/yCsESx8Bp5vD+9cC+tngze7RIc6dOgQixYt4tZbbwXA39+f8PBwAB544AEmTJiA0YOOK4WQgpmr4htaJCYm8t1339GzZ08uvPBCli1bVpHlVUqnnbnKv+fqUWvt+xVQj4iIiIhUVn5B0Ok6ZzuwDVa+4zyc+IObITgSOl8P3UZAVOuTHmLbtm1ERUUxatQoVq9eTffu3XnhhReIj4+nYcOGdO7cuQIvSE4lwOPC7TKkn6ShhdfrJSUlhaVLl7Js2TKuu+46tm3bVqPDcUnvubq7AmoRERERkaoiohlc8ne4fy3c+AE07gU/ToaXesAbA5xlhFlHTviY1+tlxYoVjBkzhpUrVxISEsKTTz7J2LFj+ec//+mDC5GTMcYQ5Odi274jpBXzrKuYmBiGDBmCMYYePXrgcrlq/L1yJV0W+JUx5iFjTCNjTMTRrVwrExEREZHKz+1xugte/47z7Kz+TzudBWffA/9uDbPvhR3LwFrIOkKM2UtMw4b07NkTgKFDh7JixQq2b99O586diY2NJTk5mW7durF7t3qo+VJalpf07Fzmr/udS5/7lvTjAtbVV1/NwoULAWeJYHZ2NpGRkb4otdIoaUOL0flfC89gWUCP8RYRERERR2g09P4TnH8v7PgJVrwJP890vka2gsO/Uy8vh0bug2z6eQWtO3YjPj6ebt2cr0fFxsaSkJBQ439R9yVrLc/N30SehTxrWf/O0/R9fiMHU/YTExPDU089xejRoxk9ejQdOnTA39+f6dOn1+glgQDGWuvrGkrNGNMHGI4TDttZa88/1f5xcXE2IUH9N0REREQqXNZhWPsxLH0F9m4AYNXuPG770kO2pxbNWrVh6tvvU6dOnYKPKFz5VmZOLn//dC0fLk8uuO8qLMiPBQ9eWNDkojoyxiy31sad1TFOFa6MMX+x1k7I//5aa+2Hhd77f9bav5b6hMZMAa4E9lhrOxQaHwi8ALiB1621z5bgWFcD51hrXz3VfgpXIiIiIj6WdQQmdXOWDBo3+AVDxn7nvfDGENsXmvaB2D4Q1tC3tdZgvx/K5M63lrNqRyp/urgFt/dpRuKeI7SpV6taByuomHC1wlrb7fjvi3td4hMa0xc4Arx5NFwZY9xAItAfSAaWATfgBK1xxx1itLV2T/7nPgBus9YeOtU5Fa5EREREKoGsI7BnPUS3A/8Q2LMBkr6D7Yvgl8VO8AKnWUZsftBq2gdq1fNt3TXEil9TuOut5RzJ8vLcdZ0Z2KG+r0uqUGURrk4XP81Jvi/udYlYaxcZY2KPG+4BbLHWbgMwxrwHDLLWjsOZ5TqxMGMaAwdPFqyMMXcAdwB6EJ2IiIhIZRAQCo16HHt9Tjtn63kn5OXBnnVO0Nr+Haz7FFZMd/ar2/LYrFZsHwiN8kn51dn7y37l75+uo15YIG/d2pPW9Wr5uqQq6XThyp7k++Jen42GwI5Cr5OBnqf5zK3A1JO9aa19DXgNnJmrsy1QRERERMqRywX1OjrbeXdDXi7sXuMEraTvYM0HkDDF2TeqbaGwdQEEq4n1mcrJzePpOet5c8kv9GkZyaQbuhIe7O/rsqqs04WrzsaYQzizVEH535P/OrAM6yhuFuyUgcha+0QZnl9EREREKhOXGxp0dbbef4JcL/y2ypnZSvoOVr4NP70GGDinw7Gw1eR8CAr3cfFVw/4jWfzxnRX8uP0At/dpyiMD2+Bxl/RJTVKcU4Yra627gupIBhoVeh0D7Kqgc4uIiIhIZef2QEycs/V5ELzZsGtF/szWImdWa+nLYFxQr1N+2OoLTc6DAC1xO97anQe5863l7DuSxX+GdeHqrmoiUhZ80oo9/56rOYUaWnhwGlpcAuzEaWhxo7V2XVmcTw0tRERERKq5nEzYmXBsGWHyMsjNdjoTNuh6bGarcS+nmUYNNmvVTh75aA0Rwf68enMcHWPCfF1SpVDu3QLLgzHmXaAfEAn8DjxhrX3DGHM58B+cDoFTrLVjy+qcClciIiIiNUx2OiT/dCxs7VwOeV5w+UHD7sfCVqMe4Bfk62orRG6eZcK8jby6aBs9YiN4+aZuRIYG+LqsSqNKhitfULgSERERqeGyjsCOpcfC1q6VYPPAHQAx5x4LWzFx4Kl+geNgeg73vLuC7zbv4+ZeTfj7le3w9+j+qsIUrkpI4UpEREREisg8BL8uOdYg47c1gAVPkDObdfSerYbdwO3n62rPSuLvh7n9zQR2pWbwz0EduKGHHlNUnIp4zpWIiIiISPUTWBta/cHZwHmA8S8/HJvZWviMM+4X4tyndTRs1e/sNNeoIr5ct5sH319FkL+Hd2/vRVys2taXp6rzvwwRERERkfISVAfaXOFsAGn74Zfvj4WtBU864/61nHbvR5cR1uvotI2vZPLyLC/Eb+aF+M10jgnj1ZvjqBdWlk9SkuIoXImIiIiIHC+kLrQb5GwAR/Y4Ieto2Nr8pTMeGAZNLjgWtqLbOQ9E9qHDmTk8+MFqvlr/O9d0i2Hs4A4E+lW+AFgdKVyJiIiIiJxOaDR0uMbZAA79lh+2FkHS97BprjMeFAGxF0DTvk7YimoNxlRYmdv3pXH7mwls35fGP65sx6jesZgKPH9Np3AlIiIiIlJatetDp+ucDSB1hxOyjgauDbOd8ZDo/LCVP7NVt0W5ha1vNu3h3ndX4nEZ3hrdg/NbRJbLeeTkFK5ERERERM5WeCPocoOzWQupvxxbQrj9O1j3sbNfrfpO2Irt4wSuOk3POmxZa3l10TbGz9tI63Nq8b8RcTSKCC6Di5LSUrgSERERESlLxkCdWGfrdrMTtg5sO9b2fdu38POHzr61Y47NajXtA+Gla5OekZ3LIx+tYfbqXVzRqT7/GtqJYH/9iu8r+smLiIiIiJQnY6Buc2eLG+WErX2Jx8LW5vmw+l1n3/Amx9q+N+0DtRuc9LDJKenc8eZyNuw+xF8GtmbMhc11f5WPKVyJiIiIiFQkY5xGF1GtocftkJcHezccW0a4YQ6sfNvZN6J5oZmtvk5jDWDJ1v3cPWMFObl5TLnlXC5qE+3DC5KjjLXW1zWUu7i4OJuQkODrMkRERERETi8vF35feyxs/fIDZB0CwEa2ZlNQFyZtq8+u8O78e+TFNIsK9XHB1YMxZrm1Nu6sjqFwJSIiIiJSieV6YfdqvFsXseWnL2h0eBUhJst5L7r9sZmt2N7Ow5DljJRFuNKyQBERERGRyszt4fda7bnz50xW7WvF/f1i+VPbI7h+yZ/ZWj4dfpwMGKjX8dgztpqc5zzkWCqMZq5ERERERCqxFb+mcNdbyzmS5eW56zozsEP9ojt4s2Dn8mPLCHf8BLlZYFxQv8uxBhmNe0FA+SwhHD16NHPmzCE6Opq1a9cC8PDDD/PZZ5/h7+9P8+bNmTp1KuHh4eTk5HDbbbexYsUKvF4vI0aM4LHHHiuXukpDywJLSOFKRERERKqi95f9yt8/XUe9sED+NyKO1vVqnf5DOZmQ/NOxsJWcAHk54PJAg27HlhE26gn+ZfM8rEWLFhEaGsqIESMKwtX8+fO5+OKL8Xg8PPLIIwCMHz+eGTNmMHv2bN577z3S09Np164d33zzDbGxsWVSy5nSskARERERkWooJzePp+es580lv9CnZSSTbuhKeLB/yT7sF+gsDWza13mdnQ47lh4LW9//B777N7j8IObcY2Er5lzns2egb9++JCUlFRkbMGBAwfe9evVi5syZABhjSEtLw+v1kpGRgb+/P7Vr1z6j81Y2ClciIiIiIpXI/iNZ/PGdFfy4/QC392nKIwPb4HG7zvyA/sHQ/GJnA8g6DL8udYLW9u9g0b/g2/HgDoBGPY7ds9WwO3hKGOhOY8qUKQwbNgyAoUOHMmvWLOrXr096ejrPP/88ERERZXIeX1O4EhERERGpJNbuPMidby1n35Es/jOsC1d3bVj2JwmoBS37OxtA5kH4ZUl+2FoEX/8/wIJfsLN08OjMVoOu4PYr9enGjh2Lx+Nh+PDhAPz000+43W527dpFSkoKffr04dJLL6VZs2ZleJG+oXAlIiIiIlIJzFq1k0c+WkNEsD8z7zqfjjEV1OkvMAxaD3Q2gPQDzrO1js5sxf/TGfcPdZpixPZxAlf9LpCTAXvWQ3S7Yg89ffp05syZQ3x8PMYYAGbMmMHAgQPx8/MjOjqa3r17k5CQoHAlIiIiIiJnJzfPMmHeRl5dtI0esRG8fFM3IkMDfFdQcAS0vdLZANL2QdL3x8LWgieccf9aTqMMayEkEq74sMhh5s2bx/jx4/n2228JDj7WOKNx48YsXLiQm266ifT0dJYuXcr9999fQRdXvtQtUERERETERw6m53DveytZlLiXm3s14e9XtsPfcxb3V1WEw787QWvtR7DpC8BywyfZfPNbCPtSDnLOOefw1FNPMW7cOLKysqhbty7gNLWYPHkyR44cYdSoUaxfvx5rLaNGjeLhhx/27TWhVuwlpnAlIiIiIpVN4u+HuePNBHamZvDPQR24oUdjX5dUOllH4KUekJEKQeFw90/l9hytiqBW7CIiIiIiVdCX63bz4PurCPL38O7tvYiLrYLd8gJCnUB19J6rKhysyorClYiIiIhIBcnLs7wQv5kX4jfTOSaMV2+Oo17YmT1bqlIICHXatwugcCUiIiIiUiGOZHl58P1VzF//O9d0i2Hs4A4E+rl9XZaUIYUrEREREZFylrQvjdvfTGDbvjT+cWU7RvWOLWhNLtWHwpWIiIiISDn6ZtMe/vTuStwuw1uje3B+i0hflyTlROFKREREROQsZWZm0rdvX7KysvB6vQwdOpQnn3ySATfcwbdfzSMgwJ/eXdvT7k9v+rpUKUeVvIm+iIiIiEjlFxAQwMKFC1m9ejWrVq3i8y++YNjTb7LGNubW/3zE70mb6NyhLePGjfN1qVKOFK5ERERERM6SMYbQUKcVedKegyT+lsr3W/fzxJgbePmmcwn299CrVy+Sk5N9XKmUJ4UrEREREZEykJubS6t2HWnTrBH+jbvwzmM38cd+LQoaV0yZMoXLLrvMx1VKedI9VyIiIiIiZ8lay9s/7iD36vH0Ds4je94Eorx7gGgAxo4di8fjYfjw4b4tVMqVwpWIiIiIyBlKy/Ly885U3l+2g09W7uLSttE8P6wLz+UuZ968eXTo0IHp06czZ84c4uPj1X69mlO4EhERERE5A2lZXi7597fsOZxJTtpBRl/QnCeGxpGVlcmCBQt45JFHmDdvHuPHj+fbb78lODjY1yVLOVO4EhERERE5Axt3H+ZgRjZ5FvKOHOCdJ8bxyTOGvLw8rrvuOq688kpatGhBVlYW/fv3B6BXr15MnjzZx5VLeVG4EhERERE5A23q1SI82J+8tGxMdFPChz/HlJHn0ikmvGCfLVu2+K5AqXDqFigiIiIicgZCAjwsePBCZtzei1l39ybA4+a6V5cwf91uX5cmPqJwJSIiIiJyhkICPHRvUodOjcL59O7etK5XmzvfXs4b32/HWuvr8qSCKVyJiIiIiJSBqFoBvHd7L/7Qrh5Pz1nPE7PX4c3N83VZUoGqXLgyxrQzxnxgjHnFGDPU1/WIiIiIiBwV5O/m5eHduLNvM95c8gu3v5nAkSyvr8uSClKh4coYM8UYs8cYs/a48YHGmE3GmC3GmEdPc5jLgEnW2jHAiHIrVkRERETkDLhchscub8vYwR1YtHkf105ewm8HM3xdllSAip65mgYMLDxgjHEDL+GEpnbADfmzUx2NMXOO26KBt4DrjTH/AupWcP0iIiIiIiUyvGcTpow8lx0H0rn6pcWs3XnQ1yVJOavQcGWtXQQcOG64B7DFWrvNWpsNvAcMstb+bK298rhtT/52N/AosO9k5zLG3GGMSTDGJOzdu7fcrklERERE5GQubBXFzDHn4TaG615dQvyG331dkpSjynDPVUNgR6HXyfljxTLGxBpjXgPeBP51sv2sta9Za+OstXFRUVFlVqyIiIiISGm0qVebT+7uTbOoEG5/M4HpPyT5uiQpJ5UhXJlixk7at9Jam2StvcNaO9xa+3051iUiIiIiUibOqR3IB3eex8VtzuGJ2ev452fryc1Tq/bqpjKEq2SgUaHXMcAuH9UiIiIiIlIugv09vHpzd0b3bsqUxdu5863lpGerk2B1UhnC1TKgpTGmqTHGH7gemO3jmkREREREypzbZfjH/7Xjqavas3Dj71z36hJ+P5Tp67KkjFR0K/Z3gSVAa2NMsjHmVmutF7gH+BLYAHxgrV1XkXWJiIiIiFSkW86P5fVb4ti2N43BLy1m4+5Dvi5JyoCxtvqv9YyLi7MJCQm+LkNEREREpIi1Ow9y6/RlpGXl8tLwblzYSo3YfMUYs9xaG3c2x6gMywJFRERERGqkDg3D+PTu3jSKCGb0tGW88+Mvvi5JzoLClYiIiIiID9UPC+LDu86jb8tIHv9kLf/v8w3kqZNglaRwJSIiIiLiY6EBHv43Io6bezXhtUXbuHvGCjKyc31dlpSSwpWIiIiISCXgcbv456D2/P3Kdsxbt5vr/7eUvYezfF2WlILClYiIiIhIJWGM4dYLmjL5pu5s2n2Iq19azObfD/u6LCkhhSsRERERkUrmD+3r8cGd55Gdm8eQV35g8ZZ9vi5JSkDhSkRERESkEuoUE84nfzyfBmFB3DLlJz5YtsPXJclpKFyJiIiIiFRSMXWC+XDMeZzXvC5/+WgNE+ZtVCfBSkzhSkRERESkEqsd6MeUkedyQ49GvPzNVu59byWZOeokWBl5fF2AiIiIiIicmp/bxf8b3JHYuiGM+2Ijv6Vm8L8RcdQNDfB1aVKIZq5ERERERKoAYwx3Xticl4d3Y92uQwx++Qe27j3i67KkEIUrEREREZEq5PKO9Xn3jl6kZXkZ8vIPLNm639clST6FKxERERGRKqZb4zp8endvIkP9GTHlRz5anuzrkgSFKxERERGRKqlRRDAfj+nNubER/PnD1Tz3VSLWqpOgLylciYiIiIhUUWHBfkwb1YNru8fwYvxmHnh/FVledRL0FXULFBERERGpwvw9LiYM7URsZAj/+nITu1IzefXm7tQJ8fd1aTWOZq5ERERERKo4Ywx3X9SCF2/oyqrkVIa88gNJ+9J8XVaNo3AlIiIiIlJNXNW5ATNu60lqejaDX17MsqQDvi6pRlG4EhERERGpRuJiI/jkj72pE+zP8P/9yKxVO31dUo2hcCUiIiIiUs3ERobw0Zjz6dI4nPveW8Wk+M3qJFgBFK5ERERERKqhOiH+vHVrDwZ3bci/v0rkoQ/XkO3N83VZ1Zq6BYqIiIiIVFMBHjfPXdeZJnWD+c+CzexKzWDyTd0JC/bzdWnVkmauRERERESqMWMM91/aiueHdSbhlwMMeWUxv+5P93VZ1ZLClYiIiIhIDTC4awxv3dqTfUecToIrfk055f6pqakMHTqUNm3a0LZtW5YsWVLw3sSJEzHGsG/fvvIuu0pRuBIRERERqSF6NavLx388n9BADze8tpS5a3476b733XcfAwcOZOPGjaxevZq2bdsCsGPHDr766isaN25cUWVXGQpXIiIiIiI1SPOoUD75Y286NAzj7hkreOWbrSd0Ejx06BCLFi3i1ltvBcDf35/w8HAAHnjgASZMmIAxpqJLr/QUrkREREREapiIEH/eua0nV3aqz/h5G3ns45/JyT3WSXDbtm1ERUUxatQounbtym233UZaWhqzZ8+mYcOGdO7c2YfVV17qFigiIiIiUgMF+rl58fquxNYN4b9fbyE5JYOXb+pG7UA/vF4vK1asYNKkSfTs2ZP77ruPJ598kkWLFjF//nxfl15paeZKRERERKSGcrkMD/2hNROGdmLptv0MeWkx89b+RkRUPWJiYujZsycAQ4cOZcWKFWzfvp3OnTsTGxtLcnIy3bp1Y/fu3T6+ispDM1ciIiIiIjXcdXGNiAzx59bpCdz19goiQ/1p0DCGTZs20bp1a+Lj4+nWrRvx8fEFn4mNjSUhIYHIyEgfVl65KFyJiIiIiAhhwf4E+rnIyMlj35Fsbhr1CMOHDyc7O5tmzZoxdepUX5dY6SlciYiIiIgIberVIjzYH9Jz8Obl8c4WF0++OJNbzo8tdv+kpKQKra8qULgSERERERFCAjwsePBCNu4+TOOIIB77eC1PzF7HjgPp/PXytrhcar1+OmpoISIiIiIigBOwujepQ1StQF69uTsjz4/l9e+3c/eMFWTm5Pq6vEpP4UpERERERE7gdhme+L92/O2Ktsxbt5sb/7eU/UeyfF1WpaZwJSIiIiIixTLGcFufZrx8YzfW7TrEkFd+YPu+NF+XVWkpXImIiIiIyCld1rE+M27vxeFML0NeXkxC0gFfl1QpKVyJiIiIiMhpdW9Sh4/HnE94sD83vv4jc9f85uuSKh2FKxERERERKZHYyBA+HnM+nRqGcfeMFbz67Vastb4uq9JQuBIRERERkRKrE+LP27f15IpO9Rn3xUb+9ulavLl5vi6rUqj0z7kyxjQDHgfCrLVDTzYmIiIiIiIVI9DPzaTruxJTJ4hXv93GbwczmXRDV0ICKn28KFflOnNljJlijNljjFl73PhAY8wmY8wWY8yjpzqGtXabtfbW042JiIiIiEjFcbkMj13Wlmeu7sA3m/Yw7LUl7DmU6euyfKq8lwVOAwYWHjDGuIGXgMuAdsANxph2xpiOxpg5x23R5VyfiIiIiIichZt6NeGNW85l2940Br/8A4m/H/Z1ST5TruHKWrsIOL5PYw9gS/7sUzbwHjDIWvuztfbK47Y9Z3puY8wdxpgEY0zC3r17z+IqRERERETkVC5qE80Hd55Hdm4e17zyAz9s2efrknzCFw0tGgI7Cr1Ozh8rljGmrjFmMtDVGPPYycaOZ619zVobZ62Ni4qKKsPyRURERETkeB0ahvHp3b2pHxbILVN/4qPlyb4uqcL54o4zU8zYSfs3Wmv3A3edbkxERERERHyrYXgQH951PmPeXs6fP1xNckoGf7qkBcYUFwGqH1/MXCUDjQq9jgF2+aAOEREREREpY2FBfkwb1YNrusXw/IJEHp65hmxvzWjV7ouZq2VAS2NMU2AncD1wow/qEBERERGRcuDvcTHx2k40igjiPws289vBDF65qTu1A/18XVq5Ku9W7O8CS4DWxphkY8yt1lovcA/wJbAB+MBau6486xARERERkYpljOH+S1sx8drO/LjtAENf+YGdqRm+LqtcGWtPertTtREXF2cTEhJ8XYaIiIiISI20eMs+7nprOUH+bqaMPJcODcN8XdIJjDHLrbVxZ3MMX9xzJSIiIiIiNUjvFpHMHHM+HpfhuleX8PXGM37iUqWmcCUiIiIiIuWudb1afHJ3b5pGhnDbmwm88+Mvvi6pzClciYiIiIhIhTindiAf3HkefVtG8vgna3n2i43k5VWf25QUrkREREREpMKEBHj434g4hvdszORvt3Lf+6vIzMn1dVllwhet2EVEREREpAbzuF08c3UHGkUE8+wXG9l9MIPXbo6jToi/r0s7K5q5EhERERGRCmeM4a4Lm/PfG7uyOvkg17zyA7/sT/N1WWdF4UpERERERHzmyk4NeOe2nhxIz2bIyz+w4tcUX5d0xhSuRERERETEp86NjeDjMecTEuDhhteWMm/tbl+XdEYUrkRERERExOeaRYXyyR/Pp12D2ox5ZzlvfL/d1yWVmsKViIiIiIhUCnVDA3j39l4MbF+Pp+es58nZ68itQq3aFa5ERERERKTSCPRz89KN3bjtgqZM+yGJu95eTkZ21WjVrnAlIiIiIiKVistl+NuV7XjqqvbEb/id619bwt7DWb4u67QUrkREREREpFK65fxYXr05jk2/H2bIK4vZsueIr0s6JYUrERERERGptPq3O4f37ziPjOxchry8mKXb9vu6pJNSuBIRERERkUqtc6NwPvljb6JqBTDijZ+YtWqnr0sqlsKViIiIiIhUeo0igvl4TG+6Ng7nvvdW8dLXW7C2cnUSVLgSEREREZEqISzYjzdv7cHVXRrwry838djHP5OTm+frsgp4fF2AiIiIiIhISQV43Dw/rAuNIoKZtHALuw5m8vLwboQG+D7aaOZKRERERESqFGMMfx7QmvHXdGTxln1cO3kJuw9m+roshSsREREREamahp3bmKkjz2XHgXQGv7yYDb8dOum+o0ePJjo6mg4dOhSMPfzww7Rp04ZOnToBNDfGhAMYY/yMMdONMT8bYzYYYx4rST0KVyIiIiIiUmX1bRXFB3eeh7Vw7eQlLErcW+x+I0eOZN68eUXG+vfvz9q1a1mzZg1AJnA0RF0LBFhrOwLdgTuNMbGnq0XhSkREREREqrR2DWrzyd3nE1MniFHTlvHBsh0n7NO3b18iIiKKjA0YMACPp+BerTQgJv97C4QYYzxAEJANnHxaLJ/ClYiIiIiIVHn1w4L48K7z6N0ikr98tIaJX24qbav2SOCL/O9n4oSt34BfgYnW2gOnO4DClYiIiIiIVAu1Av1445Y4rj+3Ef/9egsPvL+KLG/uaT83duxYcGar3skf6gHkAg2ApsCfjTHNTncc3/crFBERERERKSN+bhfjhnSkUUQw//pyEztTM7j3opZ0j61T7P7Tp09nzpw5ANvtsamuG4F51tocYI8xZjEQB2w71bk1cyUiIiIiItWKMYa7L2rBhGs6siwphVum/cTF//6G9Cxvkf3mzZvH+PHjmT17NkDhpxH/ClxsHCFAL2Dj6c6rcCUiIiIiItVS8+haBPq5sBY2zniGvn0uYNOmTcTExPDGG29wzz33cPjwYfr37w/QzhgzOf+jLwGhwFpgGTDVWrvmdOczpbzJq0oyxuwFfsl/GQYc9GE5hfmylkhgn4/OLVKVVab/D5HyoT/j8qGf6zHV9WdRVa+rstddmerzdS2l//3VuFx+kU3aG5fLY/PyvDn7flmHzcs7yd5NrLVRZ1NgjQhXhRljXrPW3uHrOsC3tRhjEqy1cb44t0hVVpn+P0TKh/6My4d+rsdU159FVb2uyl53ZarP17VUhd9fa+KywM98XUAhlakWESkZ/b2t/vRnXD70cz2muv4squp1Vfa6K1N9lamWSqnGzVyJoyokfxERERGRo6rC7681ceZKHK/5ugARERERkVKo9L+/auZKRERERESkDGjmSkREREREpAwoXImIiIiIiJQBhSsREREREZEyoHAlIiIiIiJSBhSu5ATGmKuNMf8zxswyxgzwdT0iIiIiIqdijGlmjHnDGDPTl3UoXFUzxpgpxpg9xpi1x40PNMZsMsZsMcY8eqpjWGs/tdbeDowEhpVjuSIiIiJSw5XR76/brLW3lm+lp6dW7NWMMaYvcAR401rbIX/MDSQC/YFkYBlwA+AGxh13iNHW2j35n/s38I61dkUFlS8iIiIiNUwZ//4601o7tKJqP57HVyeW8mGtXWSMiT1uuAewxVq7DcAY8x4wyFo7Drjy+GMYYwzwLPCFgpWIiIiIlKey+P21stCywJqhIbCj0Ovk/LGTuRe4FBhqjLmrPAsTERERESlGqX5/NcbUNcZMBroaYx4r7+JORjNXNYMpZuyk60GttS8CL5ZfOSIiIiIip1Ta31/3Az6fFNDMVc2QDDQq9DoG2OWjWkRERERETqdK/v6qcFUzLANaGmOaGmP8geuB2T6uSURERETkZKrk768KV9WMMeZdYAnQ2hiTbIy51VrrBe4BvgQ2AB9Ya9f5sk4REREREahev7+qFbuIiIiIiEgZ0MyViIiIiIhIGVC4EhERERERKQMKVyIiIiIiImVA4UpERERERKQMKFyJiIiIiIiUAYUrERERERGRMqBwJSIiVZ4xpq4xZlX+ttsYszP/+yPGmJd9XZ+IiNQMes6ViIhUK8aYJ4Ej1tqJvq5FRERqFs1ciYhItWWM6WeMmZP//ZPGmOnGmPnGmCRjzBBjzARjzM/GmHnGGL/8/bobY741xiw3xnxpjKnv26sQEZGqQuFKRERqkubAFcAg4G3ga2ttRyADuCI/YE0ChlpruwNTgLG+KlZERKoWj68LEBERqUBfWGtzjDE/A25gXv74z0As0BroAHxljCF/n998UKeIiFRBClciIlKTZAFYa/OMMTn22I3HeTj/TTTAOmvteb4qUEREqi4tCxQRETlmExBljDkPwBjjZ4xp7+OaRESkilC4EhERyWetzQaGAuONMauBVcD5Pi1KRESqDLViFxERERERKQOauRIRERERESkDClciIiIiIiJlQOFKRERERESkDChciYiIiIiIlAGFKxERERERkTKgcCUiIiIiIlIGFK5ERERERETKwP8H3a3jum4ZxaoAAAAASUVORK5CYII=\n" }, "metadata": { "needs_background": "light" @@ -514,7 +512,8 @@ } ], "source": [ - "plot(PointCloud(vec(time=exec_times, error=errors.time[-1]).resolution.as_spatial().method.as_channel(), color=SIM_COLOR), log_dims='error,time', title=\"Error vs Performance\")" + "plot(vec(time=exec_times, error=errors.time[-1]).resolution.as_spatial().method.as_channel(),\n", + " log_dims='error,time', title=\"Error vs Performance\")" ], "metadata": { "collapsed": false, From 7ccd5497670e5661f767c40d918e1bbe3610f42c Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 12 Feb 2023 15:56:51 +0100 Subject: [PATCH 134/170] [math] Add Backend.get_diagonal() --- phi/jax/_jax_backend.py | 4 ++++ phi/math/backend/_backend.py | 12 ++++++++++++ phi/math/backend/_numpy_backend.py | 3 +++ phi/tf/_tf_backend.py | 12 ++++++++++-- phi/torch/_torch_backend.py | 3 +++ tests/commit/math/backend/test__backend.py | 11 ++++++++++- 6 files changed, 42 insertions(+), 3 deletions(-) diff --git a/phi/jax/_jax_backend.py b/phi/jax/_jax_backend.py index 44ee23639..dc546fc56 100644 --- a/phi/jax/_jax_backend.py +++ b/phi/jax/_jax_backend.py @@ -318,6 +318,10 @@ def matmul(self, A, b): return(A @ b.T).T return jnp.stack([A.dot(b[i]) for i in range(b.shape[0])]) + def get_diagonal(self, matrices, offset=0): + result = jnp.diagonal(matrices, offset=offset, axis1=1, axis2=2) + return jnp.transpose(result, [0, 2, 1]) + def while_loop(self, loop: Callable, values: tuple): if all(self.is_available(t) for t in values): while jnp.any(values[0]): diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index fc7a393de..00033cd58 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -843,6 +843,18 @@ def repeat(self, x, repeats, axis: int): """ raise NotImplementedError(self) + def get_diagonal(self, matrices, offset=0): + """ + + Args: + matrices: (batch, rows, cols, channels) + offset: 0=diagonal, positive=above diagonal, negative=below diagonal + + Returns: + diagonal: (batch, max(rows,cols), channels) + """ + raise NotImplementedError(self) + def indexed_segment_sum(self, x, indices, axis: int): """ Args: diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index 1b0b2ad96..61cdf6f67 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -213,6 +213,9 @@ def mul(self, a, b): def matmul(self, A, b): return np.stack([A.dot(b[i]) for i in range(b.shape[0])]) + def get_diagonal(self, matrices, offset=0): + return np.transpose(np.diagonal(matrices, offset=offset, axis1=1, axis2=2), [0, 2, 1]) + def while_loop(self, loop: Callable, values: tuple): while np.any(values[0]): values = loop(*values) diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index 5015cd2e5..40acb491c 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -704,12 +704,20 @@ def eval_grad(*args): return eval_grad def stop_gradient(self, value): - return tf.stop_gradient(value) + with self._device_for(value): + return tf.stop_gradient(value) def matrix_solve_least_squares(self, matrix: TensorType, rhs: TensorType) -> Tuple[TensorType, TensorType, TensorType, TensorType]: - solution = tf.linalg.lstsq(matrix, rhs) + with self._device_for(matrix, rhs): + solution = tf.linalg.lstsq(matrix, rhs) return solution, None, None, None + def get_diagonal(self, matrices, offset=0): + with self._device_for(matrices): + matrices = tf.transpose(matrices, [0, 3, 1, 2]) + result = tf.linalg.diag_part(matrices, k=offset) + return tf.transpose(result, [0, 2, 1]) + _TAPES = [] diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index e9b083da8..0e8ec0333 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -394,6 +394,9 @@ def matmul(self, A, b): return torch.transpose(result, 0, 1) raise NotImplementedError(type(A), type(b)) + def get_diagonal(self, matrices, offset=0): + return torch.transpose(torch.diagonal(matrices, offset=offset, dim1=1, dim2=2), 1, 2) + def cumsum(self, x, axis: int): return torch.cumsum(x, dim=axis) diff --git a/tests/commit/math/backend/test__backend.py b/tests/commit/math/backend/test__backend.py index 8c48965d2..c9c47771b 100644 --- a/tests/commit/math/backend/test__backend.py +++ b/tests/commit/math/backend/test__backend.py @@ -52,4 +52,13 @@ def test_sparse(self): matrix = backend.sparse_coo_tensor(idx_, v, shape) self.assertTrue(backend.is_tensor(matrix), backend.name) - + def test_get_diagonal(self): + for backend in BACKENDS: + with backend: + t = backend.as_tensor([[[[1], [2]], [[0], [-1]]]]) + d = backend.numpy(backend.get_diagonal(t, offset=0)) + numpy.testing.assert_equal([[[1], [-1]]], d) + d1 = backend.numpy(backend.get_diagonal(t, offset=1)) + numpy.testing.assert_equal([[[2]]], d1) + d1 = backend.numpy(backend.get_diagonal(t, offset=-1)) + numpy.testing.assert_equal([[[0]]], d1) From 258eb7527373aa4c8ab86e97ba373561adfa171f Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 12 Feb 2023 20:10:09 +0100 Subject: [PATCH 135/170] [math] Improve matrix formatting --- phi/math/_tensors.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index a803b9f55..15416014c 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -14,7 +14,7 @@ from ._shape import (Shape, CHANNEL_DIM, BATCH_DIM, SPATIAL_DIM, EMPTY_SHAPE, parse_dim_order, shape_stack, merge_shapes, channel, concat_shapes, - TYPE_ABBR, IncompatibleShapes, INSTANCE_DIM, batch, spatial, dual, instance, shape, DimFilter) + TYPE_ABBR, IncompatibleShapes, INSTANCE_DIM, batch, spatial, dual, instance, shape, DimFilter, non_batch) from .backend import NoBackendFound, choose_backend, BACKENDS, get_precision, default_backend, convert as convert_, \ Backend, ComputeDevice from .backend._dtype import DType, combine_types @@ -2383,7 +2383,18 @@ def format_full(value: Tensor, options: PrintOptions) -> str: # multi-line cont if options.float_format: formatter['float_kind'] = ('{:' + options.float_format + '}').format with numpy.printoptions(threshold=np.inf, formatter=formatter): - if value.shape.spatial_rank == 0: + if value.shape.dual_rank > 0: # matrix + if value.shape.dual_rank > 1: + raise NotImplementedError("Multiple dual dimensions cannot currently be printed") + dual_dim = dual(value).name + primal = spatial(**dual(value).untyped_dict).name + if primal not in value.shape: + primal = non_batch(value).non_dual.name + for b in batch(value).meshgrid(names=True): + text = " " + np.array2string(value[b].numpy([primal, dual_dim]), separator=', ', max_line_width=np.inf) + text = colors.value(re.sub('[\\[\\]]', '', text).replace(',', ' ')) + lines.append(text) + elif value.shape.spatial_rank == 0: # no spatial or dual dimensions if options.include_shape is not None: lines.append(colors.shape(value.shape)) if value.shape.rank <= 1: From 32e0cb3c7bb6d7d746c50faeb8b2174f6d04f4c3 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 12 Feb 2023 20:11:55 +0100 Subject: [PATCH 136/170] [math] Add Shape.dual_rank --- phi/math/_shape.py | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 8735a03c8..94211813e 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -684,38 +684,26 @@ def rank(self) -> int: @property def batch_rank(self) -> int: """ Number of batch dimensions """ - r = 0 - for ty in self.types: - if ty == BATCH_DIM: - r += 1 - return r + return sum([1 for ty in self.types if ty == BATCH_DIM]) @property def instance_rank(self) -> int: - """ Number of instance dimensions """ - r = 0 - for ty in self.types: - if ty == INSTANCE_DIM: - r += 1 - return r + return sum([1 for ty in self.types if ty == INSTANCE_DIM]) @property def spatial_rank(self) -> int: """ Number of spatial dimensions """ - r = 0 - for ty in self.types: - if ty == SPATIAL_DIM: - r += 1 - return r + return sum([1 for ty in self.types if ty == SPATIAL_DIM]) + + @property + def dual_rank(self) -> int: + """ Number of spatial dimensions """ + return sum([1 for ty in self.types if ty == DUAL_DIM]) @property def channel_rank(self) -> int: """ Number of channel dimensions """ - r = 0 - for ty in self.types: - if ty == CHANNEL_DIM: - r += 1 - return r + return sum([1 for ty in self.types if ty == CHANNEL_DIM]) @property def well_defined(self): From 522a971237c4f3a7ffb728d8791a3e4a17972c17 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 12 Feb 2023 20:13:27 +0100 Subject: [PATCH 137/170] [math] Add ILU for dense matrices The dense algorithm produces correct results but the sparse ILU is incorrect since it does not perform the full matrix multiplication. --- phi/math/_sparse.py | 28 +++++++++----- phi/math/backend/_backend.py | 21 ++++------- phi/math/backend/_precondition.py | 63 ++++++++++++++++++++++++++----- 3 files changed, 78 insertions(+), 34 deletions(-) diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index 6682bb5b1..4a5c662f0 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -597,14 +597,22 @@ def factor_ilu(value: Tensor): lower: L matrix as `Tensor` upper: U matrix as `Tensor` """ - assert isinstance(value, SparseCoordinateTensor), "ILU currently only supports COO matrices" - ind_batch, channels, indices, values, shape = value._native_coo_components(dual, matrix=True) - (l_idx_nat, l_val_nat), (u_idx_nat, u_val_nat) = value.default_backend.ilu_coo(indices, values, shape) # 3 is too few - from ._ops import reshaped_tensor - l_indices = reshaped_tensor(l_idx_nat, [ind_batch, instance(value._indices), channel(value._indices)], convert=False) - l_values = reshaped_tensor(l_val_nat, [ind_batch, instance(value._values), channels], convert=False) - u_indices = reshaped_tensor(u_idx_nat, [ind_batch, instance(value._indices), channel(value._indices)], convert=False) - u_values = reshaped_tensor(u_val_nat, [ind_batch, instance(value._values), channels], convert=False) - lower = SparseCoordinateTensor(l_indices, l_values, value._dense_shape, value._can_contain_double_entries, value._indices_sorted) - upper = SparseCoordinateTensor(u_indices, u_values, value._dense_shape, value._can_contain_double_entries, value._indices_sorted) + if isinstance(value, CompressedSparseMatrix): + value = value.decompress() + if isinstance(value, SparseCoordinateTensor): + ind_batch, channels, indices, values, shape = value._native_coo_components(dual, matrix=True) + (l_idx_nat, l_val_nat), (u_idx_nat, u_val_nat) = value.default_backend.ilu_coo(indices, values, shape) + from ._ops import reshaped_tensor + l_indices = reshaped_tensor(l_idx_nat, [ind_batch, instance(value._indices), channel(value._indices)], convert=False) + l_values = reshaped_tensor(l_val_nat, [ind_batch, instance(value._values), channels], convert=False) + u_indices = reshaped_tensor(u_idx_nat, [ind_batch, instance(value._indices), channel(value._indices)], convert=False) + u_values = reshaped_tensor(u_val_nat, [ind_batch, instance(value._values), channels], convert=False) + lower = SparseCoordinateTensor(l_indices, l_values, value._dense_shape, value._can_contain_double_entries, value._indices_sorted) + upper = SparseCoordinateTensor(u_indices, u_values, value._dense_shape, value._can_contain_double_entries, value._indices_sorted) + else: # dense matrix + from ._ops import reshaped_native, reshaped_tensor + native_matrix = reshaped_native(value, [batch, non_batch(value).non_dual, dual, EMPTY_SHAPE]) + l_native, u_native = choose_backend(native_matrix).ilu_dense(native_matrix) + lower = reshaped_tensor(l_native, [batch(value), non_batch(value).non_dual, dual(value), EMPTY_SHAPE]) + upper = reshaped_tensor(u_native, [batch(value), non_batch(value).non_dual, dual(value), EMPTY_SHAPE]) return lower, upper diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 00033cd58..da13c2226 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -927,24 +927,17 @@ def coo_to_dense(self, indices, values, shape, contains_duplicates: bool): return result def ilu_coo(self, indices, values, shape, iterations=4): - """ - values: Backend-compatible values tensor of shape (batch_size, nnz, channels) - shape: Dense shape of matrix - - Args: - indices: (batch, nnz, 2) - values: (batch_size, nnz, channels) - shape: Dense matrix shape - iterations: (Optional) Number of sweeps to perform. - - Returns: - LU indices corresponding to the sparsity pattern given by `indices`. - Since L and U don't overlap, the entries of both can be returned as a single tensor. - """ + """ See incomplete_lu_coo() in _precondition """ from ._precondition import incomplete_lu_coo assert self.dtype(values).kind in (bool, int, float) return incomplete_lu_coo(self, indices, self.to_float(values), shape, iterations) + def ilu_dense(self, matrix, iterations=4): + """ See incomplete_lu_dense() in _precondition """ + from ._precondition import incomplete_lu_dense + assert self.dtype(matrix).kind in (bool, int, float) + return incomplete_lu_dense(self, self.to_float(matrix), iterations) + def csr_matrix(self, column_indices, row_pointers, values, shape: Tuple[int, int]): """ Create a sparse matrix in compressed sparse row (CSR) format. diff --git a/phi/math/backend/_precondition.py b/phi/math/backend/_precondition.py index 8c4a2accc..7b4ecae6d 100644 --- a/phi/math/backend/_precondition.py +++ b/phi/math/backend/_precondition.py @@ -3,7 +3,48 @@ import numpy as np from ._backend import Backend -from ._dtype import DType, to_numpy_dtype + + +# def sum_lower_product(b: Backend, matrix, row, col, is_lower, is_upper): +# return b.einsum('bikc,bkjc->bijc', matrix * is_lower, matrix * is_upper) +# --- Manual version --- +# min_i_j = np.expand_dims(np.minimum(row, col), -1) +# result = 0 +# for k in range(row.shape[0]-1): +# column_k = matrix[:, :, k:k+1, :] +# row_k = matrix[:, k:k+1, :, :] +# outer_product = column_k * row_k +# result += b.where(k < min_i_j, outer_product, 0) +# np.testing.assert_almost_equal(result, matmul) +# return result + + +def incomplete_lu_dense(b: 'Backend', matrix, iterations: int): + """ + + Args: + b: `Backend` + matrix: Square matrix of Shape (batch_size, rows, cols, channels) + iterations: Number of fixed-point iterations to perform. + + Returns: + L: lower triangular matrix with ones on the diagonal + U: upper triangular matrix + """ + row, col = np.indices(b.staticshape(matrix)[1:-1]) + is_lower = np.expand_dims(row > col, -1) + is_upper = np.expand_dims(row < col, -1) + is_diagonal = np.expand_dims(row == col, -1) + # # --- Initialize U as the diagonal of A, then compute off-diagonal of L --- + lower = matrix / b.expand_dims(b.get_diagonal(matrix), 1) # Since U=diag(A), L can be computed by a simple division + lu = matrix * is_diagonal + lower * is_lower # combine lower + diag(A) + 0 + # --- Fixed-point iterations --- + for sweep in range(iterations): + diag = b.expand_dims(b.get_diagonal(lu), 1) # should never contain 0 + sum_l_u = b.einsum('bikc,bkjc->bijc', lu * is_lower, lu * is_upper) + lu = b.where(is_lower, 1 / diag * (matrix - sum_l_u), matrix - sum_l_u) + # --- Assemble L=lower+unit_diagonal and U. --- + return lu * is_lower + is_diagonal, lu * ~is_lower def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], iterations: int): @@ -18,11 +59,11 @@ def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], ite indices: Row & column indices of stored entries as `numpy.ndarray` of shape (batch_size, nnz, 2). values: Backend-compatible values tensor of shape (batch_size, nnz, channels) shape: Dense shape of matrix - iterations: Number of sweeps to perform. + iterations: Number of fixed-point iterations to perform. Returns: - lower: tuple (indices, values) where indices is a NumPy array and values is backend-specific - upper: tuple (indices, values) where indices is a NumPy array and values is backend-specific + L: tuple `(indices, values)` where `indices` is a NumPy array and values is backend-specific + U: tuple `(indices, values)` where `indices` is a NumPy array and values is backend-specific """ assert isinstance(indices, np.ndarray), "incomplete_lu_coo indices must be a NumPy array" row, col = indices[..., 0], indices[..., 1] @@ -38,21 +79,23 @@ def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], ite has_transpose = b.cast(np.expand_dims(has_transpose, -1), b.dtype(values)) # 0 or 1 depending on whether a transposed entry exists for a value diagonal_indices = np.expand_dims(get_lower_diagonal_indices(row, col, shape), -1) # indices of corresponding values that lie on the diagonal l_u_compressed_zeros = b.zeros((batch_size, rows, max_entries_per_row + 1, channels)) - # --- Initialize U as the diagonal of A, then compute off-diagonal of L --- is_diagonal = np.expand_dims(row == col, -1) + # --- Initialize U as the diagonal of A, then compute off-diagonal of L --- lower = values / b.batched_gather_nd(values, diagonal_indices) # Since U=diag(A), L can be computed by a simple division - lu = b.where(is_diagonal, values, b.where(is_lower, lower, 0)) # combine lower + diag(A) + 0 + lu = values * is_diagonal + lower * is_lower # combine lower + diag(A) + 0 + # print(b.scatter(l_u_compressed_zeros, b.stack([row, index_in_row], -1), lu, mode='add')[0, :, :, 0]) # --- Fixed-point iterations --- for sweep in range(iterations): diag = b.batched_gather_nd(lu, diagonal_indices) # should never contain 0 l_u = lu * b.batched_gather_nd(lu, transposed_index) * has_transpose # matches indices (like lu, values) # --- Temporarily densify indices by row for cumsum --- l_u_compressed = b.scatter(l_u_compressed_zeros, b.stack([row, index_in_row], -1), l_u, mode='add') - sum_l_u = b.cumsum(l_u_compressed, -2) - sum_l_u = b.batched_gather_nd(sum_l_u, index_in_row_) + print(l_u_compressed[0, :, :, 0]) + sum_l_u_compressed = b.cumsum(l_u_compressed, -2) # we sum the rows, only the lower triangle is valid + sum_l_u_lower = b.batched_gather_nd(sum_l_u_compressed, index_in_row_) # --- update L and U in one matrix --- - l = 1 / diag * (values - sum_l_u) - u = values - sum_l_u + l = 1 / diag * (values - sum_l_u_lower) + u = values - b.batched_gather_nd(sum_l_u_lower, transposed_index) lu = b.where(is_lower, l, u) # --- Assemble L=lower+unit_diagonal and U. If nnz varies along batch, keep the full sparsity pattern --- u_values = b.where(~is_lower, lu, 0) From 4c0704b432e92e7cc7da51de7245a2f33f3690c5 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 13 Feb 2023 19:25:03 +0100 Subject: [PATCH 138/170] [math] Fix Sparse ILU, make ILU public * Add unit test for sparse / dense ILU --- phi/math/__init__.py | 2 +- phi/math/_shape.py | 2 + phi/math/_sparse.py | 75 ++++++++++++++++------- phi/math/_tensors.py | 6 +- phi/math/backend/_backend.py | 4 +- phi/math/backend/_precondition.py | 98 ++++++++++++++++++++----------- tests/commit/math/test__sparse.py | 21 +++++++ 7 files changed, 149 insertions(+), 59 deletions(-) diff --git a/phi/math/__init__.py b/phi/math/__init__.py index 424569bc5..e1d85fdc6 100644 --- a/phi/math/__init__.py +++ b/phi/math/__init__.py @@ -25,7 +25,7 @@ ) from ._magic_ops import unstack, stack, concat, expand, rename_dims, pack_dims, unpack_dim, flatten, copy_with, replace from ._tensors import wrap, tensor, layout, Tensor, Dict, to_dict, from_dict, is_scalar -from ._sparse import dense, get_sparsity +from ._sparse import dense, get_sparsity, factor_ilu from .extrapolation import Extrapolation from ._ops import ( choose_backend_t as choose_backend, all_available, convert, seed, diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 94211813e..979aa079a 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -1715,6 +1715,8 @@ def shape_stack(stack_dim: Shape, *shapes: Shape): index = len(types) - types[::-1].index(type) elif type == BATCH_DIM: index = 0 + elif type == DUAL_DIM: + index = min([len(names), *[i for i in range(len(names)) if types[i] == DUAL_DIM]]) elif type == CHANNEL_DIM: index = len(names) elif type == SPATIAL_DIM: diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index 4a5c662f0..fc6db12b6 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -136,6 +136,15 @@ def _pack_indices(self, row_dims: Shape, col_dims: Shape): col_idx_packed = np.ravel_multi_index(reshaped_native(col_idx, [channel, batch, instance]), col_dims.sizes) return row_idx_packed, col_idx_packed + def _unpack_indices(self, row_idx_packed, col_idx_packed, row_dims: Shape, col_dims: Shape, ind_batch: Shape): + row_idx = np.stack(np.unravel_index(row_idx_packed, row_dims.sizes), -1) + col_idx = np.stack(np.unravel_index(col_idx_packed, col_dims.sizes), -1) + np_indices = np.concatenate([row_idx, col_idx], -1) + from ._ops import reshaped_tensor + idx_dim = channel(**{channel(self._indices).name: row_dims.names + col_dims.names}) + indices = reshaped_tensor(np_indices, [ind_batch, instance(self._indices), idx_dim], convert=False) + return indices + def compress_rows(self): return self.compress(self._dense_shape.non_dual) @@ -586,33 +595,59 @@ def native_matrix(value: Tensor): return reshaped_native(v, [batch, '_row', '_col']) -def factor_ilu(value: Tensor): +def factor_ilu(matrix: Tensor, iterations=None): """ - Incomplete LU factorization. + Incomplete LU factorization for dense or sparse matrices. + + For sparse matrices, keeps the sparsity pattern of `matrix`. + L and U will be trimmed to the respective areas, i.e. stored upper elements in L will be dropped, + unless this would lead to varying numbers of stored elements along a batch dimension. Args: - value: Matrix to factor. Currently only supports COO matrices. + matrix: Dense or sparse matrix to factor. + Currently, compressed sparse matrices are decompressed before running the ILU algorithm. + iterations: (Optional) Number of fixed-point iterations to perform. Returns: - lower: L matrix as `Tensor` - upper: U matrix as `Tensor` + L: Lower-triangular matrix as `Tensor` with all diagonal elements equal to 1. + U: Upper-triangular matrix as `Tensor`. + + Examples: + >>> matrix = wrap([[-2, 1, 0], + >>> [1, -2, 1], + >>> [0, 1, -2]], channel('row'), dual('col')) + >>> L, U = math.factor_ilu(matrix) + >>> math.print(L) + row=0 1. 0. 0. along ~col + row=1 -0.5 1. 0. along ~col + row=2 0. -0.6666667 1. along ~col + >>> math.print(L @ U, "L @ U") + L @ U + row=0 -2. 1. 0. along ~col + row=1 1. -2. 1. along ~col + row=2 0. 1. -2. along ~col """ - if isinstance(value, CompressedSparseMatrix): - value = value.decompress() - if isinstance(value, SparseCoordinateTensor): - ind_batch, channels, indices, values, shape = value._native_coo_components(dual, matrix=True) - (l_idx_nat, l_val_nat), (u_idx_nat, u_val_nat) = value.default_backend.ilu_coo(indices, values, shape) + if iterations is None: + cols = dual(matrix).volume + iterations = min(20, int(round(1.6 * cols))) + if isinstance(matrix, CompressedSparseMatrix): + matrix = matrix.decompress() + if isinstance(matrix, SparseCoordinateTensor): + ind_batch, channels, indices, values, shape = matrix._native_coo_components(dual, matrix=True) + (l_idx_nat, l_val_nat), (u_idx_nat, u_val_nat) = matrix.default_backend.ilu_coo(indices, values, shape, iterations) + col_dims = matrix._shape.only(dual) + row_dims = matrix._dense_shape.without(col_dims) + l_indices = matrix._unpack_indices(l_idx_nat[..., 0], l_idx_nat[..., 1], row_dims, col_dims, ind_batch) + u_indices = matrix._unpack_indices(u_idx_nat[..., 0], u_idx_nat[..., 1], row_dims, col_dims, ind_batch) from ._ops import reshaped_tensor - l_indices = reshaped_tensor(l_idx_nat, [ind_batch, instance(value._indices), channel(value._indices)], convert=False) - l_values = reshaped_tensor(l_val_nat, [ind_batch, instance(value._values), channels], convert=False) - u_indices = reshaped_tensor(u_idx_nat, [ind_batch, instance(value._indices), channel(value._indices)], convert=False) - u_values = reshaped_tensor(u_val_nat, [ind_batch, instance(value._values), channels], convert=False) - lower = SparseCoordinateTensor(l_indices, l_values, value._dense_shape, value._can_contain_double_entries, value._indices_sorted) - upper = SparseCoordinateTensor(u_indices, u_values, value._dense_shape, value._can_contain_double_entries, value._indices_sorted) + l_values = reshaped_tensor(l_val_nat, [ind_batch, instance(matrix._values), channels], convert=False) + u_values = reshaped_tensor(u_val_nat, [ind_batch, instance(matrix._values), channels], convert=False) + lower = SparseCoordinateTensor(l_indices, l_values, matrix._dense_shape, matrix._can_contain_double_entries, matrix._indices_sorted) + upper = SparseCoordinateTensor(u_indices, u_values, matrix._dense_shape, matrix._can_contain_double_entries, matrix._indices_sorted) else: # dense matrix from ._ops import reshaped_native, reshaped_tensor - native_matrix = reshaped_native(value, [batch, non_batch(value).non_dual, dual, EMPTY_SHAPE]) - l_native, u_native = choose_backend(native_matrix).ilu_dense(native_matrix) - lower = reshaped_tensor(l_native, [batch(value), non_batch(value).non_dual, dual(value), EMPTY_SHAPE]) - upper = reshaped_tensor(u_native, [batch(value), non_batch(value).non_dual, dual(value), EMPTY_SHAPE]) + native_matrix = reshaped_native(matrix, [batch, non_batch(matrix).non_dual, dual, EMPTY_SHAPE]) + l_native, u_native = choose_backend(native_matrix).ilu_dense(native_matrix, iterations) + lower = reshaped_tensor(l_native, [batch(matrix), non_batch(matrix).non_dual, dual(matrix), EMPTY_SHAPE]) + upper = reshaped_tensor(u_native, [batch(matrix), non_batch(matrix).non_dual, dual(matrix), EMPTY_SHAPE]) return lower, upper diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index 15416014c..dc75ef6de 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -1821,13 +1821,15 @@ def broadcastable_native_tensors(*tensors): Expands and transposes the dimensions of the given tensors so that they all have the same dimension order. Args: - tensors: sequence of Tensors - *tensors: + *tensors: sequence of Tensors Returns: shape, native tensors) """ + from ._sparse import SparseCoordinateTensor, CompressedSparseMatrix, dense + if any(isinstance(t, (SparseCoordinateTensor, CompressedSparseMatrix)) for t in tensors) and not all(isinstance(t, (SparseCoordinateTensor, CompressedSparseMatrix)) for t in tensors): + tensors = [dense(t) for t in tensors] broadcast_shape = merge_shapes(*[t.shape for t in tensors]) natives = [t.native(order=broadcast_shape.names) if t.rank > 0 else t.native() for t in tensors] return broadcast_shape, natives diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index da13c2226..7c70e69fe 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -926,13 +926,13 @@ def coo_to_dense(self, indices, values, shape, contains_duplicates: bool): result = self.scatter(base, indices, values, mode='add' if contains_duplicates else 'update') return result - def ilu_coo(self, indices, values, shape, iterations=4): + def ilu_coo(self, indices, values, shape, iterations: int): """ See incomplete_lu_coo() in _precondition """ from ._precondition import incomplete_lu_coo assert self.dtype(values).kind in (bool, int, float) return incomplete_lu_coo(self, indices, self.to_float(values), shape, iterations) - def ilu_dense(self, matrix, iterations=4): + def ilu_dense(self, matrix, iterations: int): """ See incomplete_lu_dense() in _precondition """ from ._precondition import incomplete_lu_dense assert self.dtype(matrix).kind in (bool, int, float) diff --git a/phi/math/backend/_precondition.py b/phi/math/backend/_precondition.py index 7b4ecae6d..759375e86 100644 --- a/phi/math/backend/_precondition.py +++ b/phi/math/backend/_precondition.py @@ -5,20 +5,6 @@ from ._backend import Backend -# def sum_lower_product(b: Backend, matrix, row, col, is_lower, is_upper): -# return b.einsum('bikc,bkjc->bijc', matrix * is_lower, matrix * is_upper) -# --- Manual version --- -# min_i_j = np.expand_dims(np.minimum(row, col), -1) -# result = 0 -# for k in range(row.shape[0]-1): -# column_k = matrix[:, :, k:k+1, :] -# row_k = matrix[:, k:k+1, :, :] -# outer_product = column_k * row_k -# result += b.where(k < min_i_j, outer_product, 0) -# np.testing.assert_almost_equal(result, matmul) -# return result - - def incomplete_lu_dense(b: 'Backend', matrix, iterations: int): """ @@ -71,32 +57,20 @@ def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], ite rows, cols = shape assert rows == cols, "incomplete_lu_coo only implemented for square matrices" is_lower = np.expand_dims(row > col, -1) - index_in_row = get_index_in_row(row, col) - index_in_row_ = np.stack([row, index_in_row], -1) - max_entries_per_row = np.max(index_in_row) - has_transpose, transposed_index = get_transposed_indices(row, col, shape) # The corresponding index in the transposed pattern. If non-existent, points at any valid value - transposed_index = np.expand_dims(transposed_index, -1) - has_transpose = b.cast(np.expand_dims(has_transpose, -1), b.dtype(values)) # 0 or 1 depending on whether a transposed entry exists for a value diagonal_indices = np.expand_dims(get_lower_diagonal_indices(row, col, shape), -1) # indices of corresponding values that lie on the diagonal - l_u_compressed_zeros = b.zeros((batch_size, rows, max_entries_per_row + 1, channels)) is_diagonal = np.expand_dims(row == col, -1) + mm_above, mm_left, mm_is_valid = strict_lu_mm_pattern_coo_batched(row, col, rows, cols) + mm_above = np.expand_dims(mm_above, -1) + mm_left = np.expand_dims(mm_left, -1) + mm_is_valid = np.expand_dims(mm_is_valid, -1) # --- Initialize U as the diagonal of A, then compute off-diagonal of L --- lower = values / b.batched_gather_nd(values, diagonal_indices) # Since U=diag(A), L can be computed by a simple division lu = values * is_diagonal + lower * is_lower # combine lower + diag(A) + 0 - # print(b.scatter(l_u_compressed_zeros, b.stack([row, index_in_row], -1), lu, mode='add')[0, :, :, 0]) # --- Fixed-point iterations --- for sweep in range(iterations): diag = b.batched_gather_nd(lu, diagonal_indices) # should never contain 0 - l_u = lu * b.batched_gather_nd(lu, transposed_index) * has_transpose # matches indices (like lu, values) - # --- Temporarily densify indices by row for cumsum --- - l_u_compressed = b.scatter(l_u_compressed_zeros, b.stack([row, index_in_row], -1), l_u, mode='add') - print(l_u_compressed[0, :, :, 0]) - sum_l_u_compressed = b.cumsum(l_u_compressed, -2) # we sum the rows, only the lower triangle is valid - sum_l_u_lower = b.batched_gather_nd(sum_l_u_compressed, index_in_row_) - # --- update L and U in one matrix --- - l = 1 / diag * (values - sum_l_u_lower) - u = values - b.batched_gather_nd(sum_l_u_lower, transposed_index) - lu = b.where(is_lower, l, u) + sum_l_u = b.einsum('bnkc,bnkc->bnc', b.batched_gather_nd(lu, mm_above) * mm_is_valid, b.batched_gather_nd(lu, mm_left)) + lu = b.where(is_lower, 1 / diag * (values - sum_l_u), values - sum_l_u) # --- Assemble L=lower+unit_diagonal and U. If nnz varies along batch, keep the full sparsity pattern --- u_values = b.where(~is_lower, lu, 0) belongs_to_lower = (is_lower | is_diagonal) @@ -116,11 +90,67 @@ def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], ite return (indices, l_values), (indices, u_values) +def strict_lu_mm_pattern_coo(row: np.ndarray, col: np.ndarray, rows, cols): + """ + For each stored entry e at (row, col), finds the matching entries directly above and directly left of e, such that left.col == above.row. + + This is useful for multiplying a lower triangular and upper triangular matrix given the sparsity pattern but excluding the diagonals. + The matrix multiplication then is given by + >>> einsum('nk,nk->n', stored_lower[above_entries] * is_valid_entry, stored_upper[left_entries]) + + Returns: + above_entries: (max_num, nnz) Stored indices of matched elements above any entry. + left_entries: (max_num, nnz) Stored indices of matched elements to the left of any entry. + is_valid_entry: (max_num, nnz) Mask of valid indices. Invalid indices are undefined but lie inside the array to prevent index errors. + """ + entries, = row.shape + # --- Compress rows and cols --- + lower_entries_by_row = compress_strict_lower_triangular_rows(row, col, rows) # entry indices by row, -1 for non-existent entries + upper_entries_by_col = compress_strict_lower_triangular_rows(col, row, cols) + # --- Find above and left entries --- + same_row_entries = lower_entries_by_row[:, row] # (row entries, entries). Currently, contains valid values for invalid references + left = np.where(col[same_row_entries] < col, same_row_entries, -1) # (max_left, nnz) all entries with col_e==col, row_e < row + same_col_entries = upper_entries_by_col[:, col] + above = np.where(row[same_col_entries] < row, same_col_entries, -1) # (max_above, nnz) + # --- for each entry, match left and above where left.col == above.row --- + half_density = max(len(lower_entries_by_row), len(upper_entries_by_col)) + above_entries = np.zeros([entries, half_density], dtype=int) + left_entries = np.zeros([entries, half_density], dtype=int) + is_valid_entry = np.zeros([entries, half_density]) + k = np.zeros(entries, dtype=int) + for r in range(len(above)): + for c in range(len(left)): + match = (col[left[c]] == row[above[r]]) & (above[r] != -1) + where_match = np.where(match) + k_where_match = k[where_match] + above_entries[where_match, k_where_match] = above[r][where_match] + left_entries[where_match, k_where_match] = left[c][where_match] + is_valid_entry[where_match, k_where_match] = 1 + k += match + return above_entries, left_entries, is_valid_entry + + +def compress_strict_lower_triangular_rows(row, col, rows): + is_lower = row > col + below_diagonal = np.where(is_lower) + row_lower = row[below_diagonal] + num_in_row = get_index_in_row(row_lower, col[below_diagonal]) + lower_entries_by_row = np.zeros((np.max(num_in_row)+1, rows), dtype=row.dtype) - 1 + lower_entries_by_row[num_in_row, row_lower] = below_diagonal + return lower_entries_by_row + + +def strict_lu_mm_pattern_coo_batched(row, col, rows, cols): + results = [strict_lu_mm_pattern_coo(row[b], col[b], rows, cols) for b in range(row.shape[0])] + result = [np.stack(v) for v in zip(*results)] + return result + + def get_index_in_row(row: np.ndarray, col: np.ndarray): """ How many entries are to the left of a given entry but in the same row, i.e. the how manieth index this is per row. """ perm = np.argsort(col) - compressed_col_index = [cumcount(row[b][perm[b]])[inv_perm(perm[b])] for b in range(row.shape[0])] - return np.stack(compressed_col_index) + compressed_col_index = cumcount(row[perm])[inv_perm(perm)] + return compressed_col_index def inv_perm(perm): diff --git a/tests/commit/math/test__sparse.py b/tests/commit/math/test__sparse.py index b31326ef0..d0997c29c 100644 --- a/tests/commit/math/test__sparse.py +++ b/tests/commit/math/test__sparse.py @@ -64,3 +64,24 @@ def f(x): coo, bias = math.matrix_from_function(f, x) csr = coo.compress(non_dual) math.assert_close(f(x), coo @ x, csr @ x) + + def test_ilu(self): + def f(x): + """ + True LU for the 3x3 matrix is + + L = 1 0 0 U = -2 1 0 + -.5 1 0 0 -1.5 1 + 0 -.7 1 0 0 -1.3 + """ + return math.laplace(x, padding=math.extrapolation.ZERO) + matrix, bias = math.matrix_from_function(f, math.ones(spatial(x=5))) + # --- Sparse ILU --- + L, U = math.factor_ilu(matrix) + L, U = math.dense(L), math.dense(U) + math.assert_close(L @ U, matrix) + # --- Dense ILU --- + matrix = math.dense(matrix) + L, U = math.factor_ilu(matrix) + math.assert_close(L @ U, matrix) + From ff252df6f074b50b0256eac2feea1eb75f80ef4f Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 13 Feb 2023 20:14:42 +0100 Subject: [PATCH 139/170] [math] Improve matrix formatting --- phi/math/_tensors.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index dc75ef6de..c5a64d00c 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -687,6 +687,9 @@ def __matmul__(self, other): assert isinstance(other, Tensor), f"Matmul '@' requires two Tensor arguments but got {type(other)}" dims = batch(**self.shape.dual.untyped_dict).names match = other.shape.only(dims, reorder=True) + if not match: + assert non_batch(other).non_dual.rank == 1, f"Cannot multiply {self.shape} @ {other.shape} because arg2 does not have appropriate non-dual dimensions" + match = non_batch(other).non_dual assert len(dims) == match.rank, f"Dual dimensions {dual} do not match shape of second argument {other.shape}" left_arg = pack_dims(self, dual, dual('_reduce')) if len(dims) > 1 else self right_arg = pack_dims(other, match, channel('_reduce')) @@ -2386,6 +2389,8 @@ def format_full(value: Tensor, options: PrintOptions) -> str: # multi-line cont formatter['float_kind'] = ('{:' + options.float_format + '}').format with numpy.printoptions(threshold=np.inf, formatter=formatter): if value.shape.dual_rank > 0: # matrix + if options.include_shape is not None: + lines.append(colors.shape(value.shape)) if value.shape.dual_rank > 1: raise NotImplementedError("Multiple dual dimensions cannot currently be printed") dual_dim = dual(value).name @@ -2393,9 +2398,14 @@ def format_full(value: Tensor, options: PrintOptions) -> str: # multi-line cont if primal not in value.shape: primal = non_batch(value).non_dual.name for b in batch(value).meshgrid(names=True): - text = " " + np.array2string(value[b].numpy([primal, dual_dim]), separator=', ', max_line_width=np.inf) - text = colors.value(re.sub('[\\[\\]]', '', text).replace(',', ' ')) - lines.append(text) + text = " " + np.array2string(value[b].numpy([primal, dual_dim]), separator=', ', max_line_width=np.inf) + " " + text = re.sub('[\\[\\]]', '', text).replace(',', ' ') + prefixes = prefix_indices(non_batch(value).non_dual, colors) + if options.include_shape is not False: + for line, prefix in zip(text.split("\n"), prefixes): + lines.append(f"{prefix} {colors.value(line)} along {colors.shape(dual_dim)}") + else: + lines.append(colors.value(text)) elif value.shape.spatial_rank == 0: # no spatial or dual dimensions if options.include_shape is not None: lines.append(colors.shape(value.shape)) @@ -2425,6 +2435,13 @@ def format_full(value: Tensor, options: PrintOptions) -> str: # multi-line cont return "\n".join(lines) +def prefix_indices(index_shape, colors: ColorScheme): + prefixes = [f"{colors.shape(', '.join(f'{name}={idx}' for name, idx in index_dict.items()))}" for index_dict in index_shape.meshgrid(names=True)] + max_len = max(len(p) for p in prefixes) + prefixes = [p + " " * (max_len - len(p) + 2) for p in prefixes] + return prefixes + + def format_row(self: Tensor, options: PrintOptions) -> str: # all values in a single line """ Including shape: (x=5, y=4) along vector From d2d25f1d136178d6401732de43074c581794a672 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 13 Feb 2023 23:09:36 +0100 Subject: [PATCH 140/170] [geom] Fix slicing GridCell --- phi/geom/_box.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phi/geom/_box.py b/phi/geom/_box.py index b3380bee1..e3e798aa9 100644 --- a/phi/geom/_box.py +++ b/phi/geom/_box.py @@ -464,7 +464,7 @@ def __getitem__(self, item): bounds = Box(lower, upper) gather_dict[dim] = slice(start, stop) resolution = self._resolution.after_gather(gather_dict) - return GridCell(resolution, bounds) + return GridCell(resolution, bounds[{d: s for d, s in item.items() if d != 'vector'}]) def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or None, **kwargs) -> 'Cuboid': return math.pack_dims(self.center_representation(), dims, packed_dim, pos, **kwargs) From 8053cf94464e575bbebb72e457b7f75e091ae3c7 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 14 Feb 2023 20:20:56 +0100 Subject: [PATCH 141/170] [math] Safe ILU for rank-deficient matrices --- phi/math/_sparse.py | 11 ++++++++--- phi/math/backend/_backend.py | 8 ++++---- phi/math/backend/_precondition.py | 18 ++++++++++++++---- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index fc6db12b6..fed21846a 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -595,7 +595,7 @@ def native_matrix(value: Tensor): return reshaped_native(v, [batch, '_row', '_col']) -def factor_ilu(matrix: Tensor, iterations=None): +def factor_ilu(matrix: Tensor, iterations=None, safe=False): """ Incomplete LU factorization for dense or sparse matrices. @@ -607,6 +607,11 @@ def factor_ilu(matrix: Tensor, iterations=None): matrix: Dense or sparse matrix to factor. Currently, compressed sparse matrices are decompressed before running the ILU algorithm. iterations: (Optional) Number of fixed-point iterations to perform. + safe: If `False` (default), only matrices with a rank deficiency of up to 1 can be factored as all values of L and U are uniquely determined. + For matrices with higher rank deficiencies, the result includes `NaN` values. + If `True`, the algorithm runs slightly slower but can factor highly rank-deficient matrices as well. + However, then L is undeterdetermined and unused values of L are set to 0. + Rank deficiencies of 1 occur frequently in periodic settings but higher ones are rare. Returns: L: Lower-triangular matrix as `Tensor` with all diagonal elements equal to 1. @@ -634,7 +639,7 @@ def factor_ilu(matrix: Tensor, iterations=None): matrix = matrix.decompress() if isinstance(matrix, SparseCoordinateTensor): ind_batch, channels, indices, values, shape = matrix._native_coo_components(dual, matrix=True) - (l_idx_nat, l_val_nat), (u_idx_nat, u_val_nat) = matrix.default_backend.ilu_coo(indices, values, shape, iterations) + (l_idx_nat, l_val_nat), (u_idx_nat, u_val_nat) = matrix.default_backend.ilu_coo(indices, values, shape, iterations, safe) col_dims = matrix._shape.only(dual) row_dims = matrix._dense_shape.without(col_dims) l_indices = matrix._unpack_indices(l_idx_nat[..., 0], l_idx_nat[..., 1], row_dims, col_dims, ind_batch) @@ -647,7 +652,7 @@ def factor_ilu(matrix: Tensor, iterations=None): else: # dense matrix from ._ops import reshaped_native, reshaped_tensor native_matrix = reshaped_native(matrix, [batch, non_batch(matrix).non_dual, dual, EMPTY_SHAPE]) - l_native, u_native = choose_backend(native_matrix).ilu_dense(native_matrix, iterations) + l_native, u_native = choose_backend(native_matrix).ilu_dense(native_matrix, iterations, safe) lower = reshaped_tensor(l_native, [batch(matrix), non_batch(matrix).non_dual, dual(matrix), EMPTY_SHAPE]) upper = reshaped_tensor(u_native, [batch(matrix), non_batch(matrix).non_dual, dual(matrix), EMPTY_SHAPE]) return lower, upper diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 7c70e69fe..9ba9d257b 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -926,17 +926,17 @@ def coo_to_dense(self, indices, values, shape, contains_duplicates: bool): result = self.scatter(base, indices, values, mode='add' if contains_duplicates else 'update') return result - def ilu_coo(self, indices, values, shape, iterations: int): + def ilu_coo(self, indices, values, shape, iterations: int, safe: bool): """ See incomplete_lu_coo() in _precondition """ from ._precondition import incomplete_lu_coo assert self.dtype(values).kind in (bool, int, float) - return incomplete_lu_coo(self, indices, self.to_float(values), shape, iterations) + return incomplete_lu_coo(self, indices, self.to_float(values), shape, iterations, safe) - def ilu_dense(self, matrix, iterations: int): + def ilu_dense(self, matrix, iterations: int, safe: bool): """ See incomplete_lu_dense() in _precondition """ from ._precondition import incomplete_lu_dense assert self.dtype(matrix).kind in (bool, int, float) - return incomplete_lu_dense(self, self.to_float(matrix), iterations) + return incomplete_lu_dense(self, self.to_float(matrix), iterations, safe) def csr_matrix(self, column_indices, row_pointers, values, shape: Tuple[int, int]): """ diff --git a/phi/math/backend/_precondition.py b/phi/math/backend/_precondition.py index 759375e86..8a569db94 100644 --- a/phi/math/backend/_precondition.py +++ b/phi/math/backend/_precondition.py @@ -5,13 +5,17 @@ from ._backend import Backend -def incomplete_lu_dense(b: 'Backend', matrix, iterations: int): +def incomplete_lu_dense(b: 'Backend', matrix, iterations: int, safe: bool): """ Args: b: `Backend` matrix: Square matrix of Shape (batch_size, rows, cols, channels) iterations: Number of fixed-point iterations to perform. + safe: Avoid NaN when the rank deficiency of `matrix` is 2 or higher. + For a rank deficiency of 1, the fixed-point algorithm will still converge without NaNs and all values of L and U are uniquely determined. + If enabled, the algorithm is slightly slower. + Rank deficiencies of 1 occur frequently in periodic settings but higher ones are rare. Returns: L: lower triangular matrix with ones on the diagonal @@ -28,12 +32,13 @@ def incomplete_lu_dense(b: 'Backend', matrix, iterations: int): for sweep in range(iterations): diag = b.expand_dims(b.get_diagonal(lu), 1) # should never contain 0 sum_l_u = b.einsum('bikc,bkjc->bijc', lu * is_lower, lu * is_upper) - lu = b.where(is_lower, 1 / diag * (matrix - sum_l_u), matrix - sum_l_u) + l = (matrix - sum_l_u) / diag if not safe else b.divide_no_nan(matrix - sum_l_u, diag) + lu = b.where(is_lower, l, matrix - sum_l_u) # --- Assemble L=lower+unit_diagonal and U. --- return lu * is_lower + is_diagonal, lu * ~is_lower -def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], iterations: int): +def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], iterations: int, safe: bool): """ Based on *Parallel Approximate LU Factorizations for Sparse Matrices* by T.K. Huckle, https://www5.in.tum.de/persons/huckle/it_ilu.pdf. @@ -46,6 +51,10 @@ def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], ite values: Backend-compatible values tensor of shape (batch_size, nnz, channels) shape: Dense shape of matrix iterations: Number of fixed-point iterations to perform. + safe: Avoid NaN when the rank deficiency of `matrix` is 2 or higher. + For a rank deficiency of 1, the fixed-point algorithm will still converge without NaNs. + If enabled, the algorithm is slightly slower. + Rank deficiencies of 1 occur frequently in periodic settings but higher ones are rare. Returns: L: tuple `(indices, values)` where `indices` is a NumPy array and values is backend-specific @@ -70,7 +79,8 @@ def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], ite for sweep in range(iterations): diag = b.batched_gather_nd(lu, diagonal_indices) # should never contain 0 sum_l_u = b.einsum('bnkc,bnkc->bnc', b.batched_gather_nd(lu, mm_above) * mm_is_valid, b.batched_gather_nd(lu, mm_left)) - lu = b.where(is_lower, 1 / diag * (values - sum_l_u), values - sum_l_u) + l = (values - sum_l_u) / diag if not safe else b.divide_no_nan(values - sum_l_u, diag) + lu = b.where(is_lower, l, values - sum_l_u) # --- Assemble L=lower+unit_diagonal and U. If nnz varies along batch, keep the full sparsity pattern --- u_values = b.where(~is_lower, lu, 0) belongs_to_lower = (is_lower | is_diagonal) From 45da2a02efeaf5533e2b77cb07dae1bf190f876c Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Tue, 14 Feb 2023 20:58:11 +0100 Subject: [PATCH 142/170] [math] Refactor Solve constructor --- demos/differentiate_pressure.py | 2 +- demos/fluid_logo.py | 2 +- demos/fog.py | 2 +- demos/karman_vortex_street.py | 2 +- demos/pipe.py | 2 +- demos/smoke_embedded_mesh.py | 2 +- demos/smoke_plume.py | 2 +- demos/smoke_plume_3d.py | 2 +- demos/smoke_plume_advanced.py | 2 +- docs/Cookbook.ipynb | 5 +-- phi/math/_optimize.py | 50 +++++++++++----------- phi/physics/diffuse.py | 4 +- phi/physics/fluid.py | 6 +-- tests/commit/test_colab_fluids_tutorial.py | 2 +- tests/commit/test_poisson_solver.py | 4 +- 15 files changed, 43 insertions(+), 46 deletions(-) diff --git a/demos/differentiate_pressure.py b/demos/differentiate_pressure.py index 9398b47f9..37da7e3a9 100644 --- a/demos/differentiate_pressure.py +++ b/demos/differentiate_pressure.py @@ -16,7 +16,7 @@ def loss(v0, p0): - v1, p = fluid.make_incompressible(v0 * LEFT, solve=Solve('CG-adaptive', 1e-5, 0, x0=p0)) + v1, p = fluid.make_incompressible(v0 * LEFT, solve=Solve('CG-adaptive', 1e-5, x0=p0)) return field.l2_loss((v1 - TARGET) * RIGHT), v1, p diff --git a/demos/fluid_logo.py b/demos/fluid_logo.py index 5fd3d386b..7ae2e7aca 100644 --- a/demos/fluid_logo.py +++ b/demos/fluid_logo.py @@ -22,5 +22,5 @@ smoke = advect.semi_lagrangian(smoke, velocity, 1) + INFLOW buoyancy_force = resample(smoke * (0, 0.1), to=velocity) velocity = advect.semi_lagrangian(velocity, velocity, 1) + buoyancy_force - velocity, pressure = fluid.make_incompressible(velocity, (OBSTACLE,), Solve('CG-adaptive', 1e-5, 0, x0=pressure)) + velocity, pressure = fluid.make_incompressible(velocity, (OBSTACLE,), Solve('CG-adaptive', 1e-5, x0=pressure)) remaining_divergence = field.divergence(velocity) diff --git a/demos/fog.py b/demos/fog.py index ae55a59b5..ea5051675 100644 --- a/demos/fog.py +++ b/demos/fog.py @@ -24,6 +24,6 @@ humidity = advect.mac_cormack(humidity, velocity, dt=1) buoyancy_force = (temperature * (0, 0.1)).at(velocity) velocity = advect.semi_lagrangian(velocity, velocity, 1) + buoyancy_force - velocity, pressure = fluid.make_incompressible(velocity, (), Solve('auto', 1e-5, 0, x0=pressure)) + velocity, pressure = fluid.make_incompressible(velocity, (), Solve('auto', 1e-5, x0=pressure)) # Compute fog fog = field.maximum(humidity - temperature, 0) diff --git a/demos/karman_vortex_street.py b/demos/karman_vortex_street.py index 2df24e61e..06cb839a8 100644 --- a/demos/karman_vortex_street.py +++ b/demos/karman_vortex_street.py @@ -19,7 +19,7 @@ def step(v, p, dt=1.): v = advect.semi_lagrangian(v, v, dt) v = v * (1 - BOUNDARY_MASK) + BOUNDARY_MASK * (SPEED, 0) - return fluid.make_incompressible(v, [CYLINDER], Solve('auto', 1e-5, 0, x0=p)) + return fluid.make_incompressible(v, [CYLINDER], Solve('auto', 1e-5, x0=p)) for _ in view('vorticity,velocity,pressure', namespace=globals()).range(): diff --git a/demos/pipe.py b/demos/pipe.py index 88bb009ad..cddaadfb7 100644 --- a/demos/pipe.py +++ b/demos/pipe.py @@ -10,4 +10,4 @@ for _ in view('velocity, pressure', namespace=globals()).range(): velocity = advect.semi_lagrangian(velocity, velocity, DT) velocity = diffuse.explicit(velocity, 0.1, DT) - velocity, pressure = fluid.make_incompressible(velocity, solve=Solve('CG-adaptive', 1e-5, 0, x0=pressure)) + velocity, pressure = fluid.make_incompressible(velocity, solve=Solve('CG-adaptive', 1e-5, x0=pressure)) diff --git a/demos/smoke_embedded_mesh.py b/demos/smoke_embedded_mesh.py index f9395f06d..7dc32abc7 100644 --- a/demos/smoke_embedded_mesh.py +++ b/demos/smoke_embedded_mesh.py @@ -15,7 +15,7 @@ def step(v, v_emb, s, p, dt=1.): buoyancy = s * (0, 0.1) v_emb = advect.semi_lagrangian(v_emb, v_emb, dt) + buoyancy.at(v_emb) * dt v = advect.semi_lagrangian(v, v, dt) + buoyancy.at(v) * dt - v, p = fluid.make_incompressible(v, [OBSTACLE], Solve('auto', 1e-5, 0, x0=p)) + v, p = fluid.make_incompressible(v, [OBSTACLE], Solve('auto', 1e-5, x0=p)) # Perform the embedded pressure solve p_emb_x0 = CenteredGrid(0, p, v_emb.bounds, v_emb.resolution) v_emb = StaggeredGrid(v_emb, extrapolation.BOUNDARY, v_emb.bounds, v_emb.resolution) diff --git a/demos/smoke_plume.py b/demos/smoke_plume.py index 328481207..6ae398a96 100644 --- a/demos/smoke_plume.py +++ b/demos/smoke_plume.py @@ -19,7 +19,7 @@ def step(v, s, p, dt=1.): s = advect.mac_cormack(s, v, dt) + INFLOW buoyancy = resample(s * (0, 0.1), to=v) v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt - v, p = fluid.make_incompressible(v, (), Solve('auto', 1e-5, 0, x0=p)) + v, p = fluid.make_incompressible(v, (), Solve(x0=p)) return v, s, p diff --git a/demos/smoke_plume_3d.py b/demos/smoke_plume_3d.py index 5949f21bb..b9ff4827a 100644 --- a/demos/smoke_plume_3d.py +++ b/demos/smoke_plume_3d.py @@ -19,7 +19,7 @@ def step(v, s, p, dt=1.): s = advect.mac_cormack(s, v, dt) + INFLOW buoyancy = resample(s * (0, 0, 0.1), to=v) v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt - v, p = fluid.make_incompressible(v, (), Solve('auto', 1e-5, 0, x0=p)) + v, p = fluid.make_incompressible(v, (), Solve('auto', 1e-5, x0=p)) return v, s, p diff --git a/demos/smoke_plume_advanced.py b/demos/smoke_plume_advanced.py index 8b1e15be9..f3094a49c 100644 --- a/demos/smoke_plume_advanced.py +++ b/demos/smoke_plume_advanced.py @@ -31,7 +31,7 @@ velocity = advect.semi_lagrangian(velocity, velocity, 1) + buoyancy_force try: with math.SolveTape() as solves: - velocity, pressure = fluid.make_incompressible(velocity, (), Solve(pressure_solver, 1e-5, 0)) + velocity, pressure = fluid.make_incompressible(velocity, (), Solve(pressure_solver, 1e-5)) viewer.log_scalars(solve_time=solves[0].solve_time) viewer.info(f"Presure solve {v_res**2}x{v_res**2} with {solves[0].method}: {solves[0].solve_time * 1000:.0f} ms ({solves[0].iterations} iterations)") except ConvergenceException as err: diff --git a/docs/Cookbook.ipynb b/docs/Cookbook.ipynb index a082fccd4..d8cd27c28 100644 --- a/docs/Cookbook.ipynb +++ b/docs/Cookbook.ipynb @@ -503,7 +503,7 @@ " return math.l2_loss(math.cos(x))\n", "\n", "initial_guess = math.tensor([1, -1], math.batch('batch'))\n", - "math.minimize(loss_function, Solve('L-BFGS-B', 0, 1e-3, x0=initial_guess))\n" + "math.minimize(loss_function, Solve('L-BFGS-B', 0, 1e-3, x0=initial_guess))" ], "metadata": { "collapsed": false, @@ -532,8 +532,7 @@ "def f(x):\n", " return 2 * x\n", "\n", - "\n", - "math.solve_linear(f, 84, Solve('CG', 1e-5, 0, x0=0))\n" + "math.solve_linear(f, 84, Solve('CG', 1e-5, x0=0))" ], "metadata": { "collapsed": false, diff --git a/phi/math/_optimize.py b/phi/math/_optimize.py index 04da0c495..56c5915d2 100644 --- a/phi/math/_optimize.py +++ b/phi/math/_optimize.py @@ -1,7 +1,7 @@ import time import uuid import warnings -from typing import Callable, Generic, List, TypeVar, Any, Tuple +from typing import Callable, Generic, List, TypeVar, Any, Tuple, Union import numpy @@ -28,8 +28,8 @@ class Solve(Generic[X, Y]): def __init__(self, method: str or None = 'auto', - relative_tolerance: float or Tensor = None, - absolute_tolerance: float or Tensor = None, + rel_tol: float or Tensor = None, + abs_tol: float or Tensor = None, x0: X or Any = None, max_iterations: int or Tensor = 1000, suppress: tuple or list = (), @@ -40,14 +40,15 @@ def __init__(self, assert isinstance(method, str) self.method: str = method """ Optimization method to use. Available solvers depend on the solve function that is used to perform the solve. """ - self.relative_tolerance: Tensor = math.to_float(wrap(relative_tolerance)) if relative_tolerance is not None else None + self.rel_tol: Tensor = math.to_float(wrap(rel_tol)) if rel_tol is not None else None """Relative tolerance for linear solves only, defaults to 1e-5 for singe precision solves and 1e-12 for double precision solves. This must be unset or `0` for minimization problems. - For systems of equations *f(x)=y*, the final tolerance is `max(relative_tolerance * norm(y), absolute_tolerance)`. """ - self.absolute_tolerance: Tensor = math.to_float(wrap(absolute_tolerance)) if absolute_tolerance is not None else None + For systems of equations *f(x)=y*, the final tolerance is `max(rel_tol * norm(y), abs_tol)`. """ + self.abs_tol: Tensor = math.to_float(wrap(abs_tol)) if abs_tol is not None else None """ Absolut tolerance for optimization problems and linear solves. - Defaults to 1e-5 for singe precision solves and 1e-12 for double precision solves. - For systems of equations *f(x)=y*, the final tolerance is `max(relative_tolerance * norm(y), absolute_tolerance)`. """ + For optimization problems, defaults to 1e-5 for singe precision solves and 1e-12 for double precision solves. + For linear solves, defaults to 0. + For systems of equations *f(x)=y*, the final tolerance is `max(rel_tol * norm(y), abs_tol)`. """ self.max_iterations: Tensor = math.to_int32(wrap(max_iterations)) """ Maximum number of iterations to perform before raising a `NotConverged` error is raised. """ self.x0 = x0 @@ -72,18 +73,18 @@ def gradient_solve(self) -> 'Solve[Y, X]': In any case, the gradient solve information will be stored in `gradient_solve.result`. """ if self._gradient_solve is None: - self._gradient_solve = Solve(self.method, self.relative_tolerance, self.absolute_tolerance, None, self.max_iterations, self.suppress, self.preprocess_y, self.preprocess_y_args) + self._gradient_solve = Solve(self.method, self.rel_tol, self.abs_tol, None, self.max_iterations, self.suppress, self.preprocess_y, self.preprocess_y_args) return self._gradient_solve def __repr__(self): - return f"{self.method} with tolerance {self.relative_tolerance} (rel), {self.absolute_tolerance} (abs), max_iterations={self.max_iterations}" + return f"{self.method} with tolerance {self.rel_tol} (rel), {self.abs_tol} (abs), max_iterations={self.max_iterations}" def __eq__(self, other): if not isinstance(other, Solve): return False if self.method != other.method \ - or (self.absolute_tolerance != other.absolute_tolerance).any \ - or (self.relative_tolerance != other.relative_tolerance).any \ + or (self.abs_tol != other.abs_tol).any \ + or (self.rel_tol != other.rel_tol).any \ or (self.max_iterations != other.max_iterations).any \ or self.preprocess_y is not other.preprocess_y \ or self.suppress != other.suppress: @@ -142,7 +143,7 @@ def __init__(self, if self.diverged.any: msg = f"Solve diverged within {iterations if iterations is not None else '?'} iterations using {method}." elif not self.converged.trajectory[-1].all: - msg = f"Solve did not converge to rel={solve.relative_tolerance}, abs={solve.absolute_tolerance} within {solve.max_iterations} iterations using {method}. Max residual: {[math.max_(t.trajectory[-1]) for t in disassemble_tree(self.residual)[1]]}" + msg = f"Solve did not converge to rel={solve.rel_tol}, abs={solve.abs_tol} within {solve.max_iterations} iterations using {method}. Max residual: {[math.max_(t.trajectory[-1]) for t in disassemble_tree(self.residual)[1]]}" else: msg = f"Converged within {iterations if iterations is not None else '?'} iterations." self.msg = msg @@ -315,7 +316,7 @@ def minimize(f: Callable[[X], Y], solve: Solve[X, Y]) -> X: NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. Diverged: If the optimization failed prematurely. """ - assert solve.relative_tolerance is None or (solve.relative_tolerance == 0).all, f"relative_tolerance must be zero for minimize() but got {solve.relative_tolerance}" + assert solve.rel_tol is None or (solve.rel_tol == 0).all, f"rel_tol must be zero for minimize() but got {solve.rel_tol}" assert solve.preprocess_y is None, "minimize() does not allow preprocess_y" x0_nest, x0_tensors = disassemble_tree(solve.x0) x0_tensors = [to_float(t) for t in x0_tensors] @@ -353,7 +354,7 @@ def native_function(x_flat): raise AssertionError(f"Failed to minimize '{f.__name__}' because its output loss {shape(y_tensors[0])} has more batch dimensions than the initial guess {batch_dims}.") return y_tensors[0].sum, (loss_native,) - atol = backend.to_float(reshaped_native((solve.absolute_tolerance or _default_tolerance()), [batch_dims], force_expand=True)) + atol = backend.to_float(reshaped_native((solve.abs_tol or _default_tolerance()), [batch_dims], force_expand=True)) maxi = backend.to_int32(reshaped_native(solve.max_iterations, [batch_dims], force_expand=True)) trj = _SOLVE_TAPES and any(t.record_trajectories for t in _SOLVE_TAPES) t = time.perf_counter() @@ -409,19 +410,16 @@ def solve_nonlinear(f: Callable, y, solve: Solve) -> Tensor: NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. Diverged: If the solve failed prematurely. """ - from ._nd import l2_loss - - if solve.preprocess_y is not None: - y = solve.preprocess_y(y) - def min_func(x): diff = f(x) - y l2 = l2_loss(diff) return l2 - - rel_tol_to_abs = (_default_tolerance() if solve.relative_tolerance is None else solve.relative_tolerance) * l2_loss(y) - tol = math.maximum(rel_tol_to_abs, (_default_tolerance() if solve.absolute_tolerance is None else solve.absolute_tolerance)) - min_solve = copy_with(solve, absolute_tolerance=tol, relative_tolerance=0, preprocess_y=None) + if solve.preprocess_y is not None: + y = solve.preprocess_y(y) + from ._nd import l2_loss + rel_tol_to_abs = (_default_tolerance() if solve.rel_tol is None else solve.rel_tol) * l2_loss(y) + tol = math.maximum(rel_tol_to_abs, (_default_tolerance() if solve.abs_tol is None else solve.abs_tol)) + min_solve = copy_with(solve, abs_tol=tol, rel_tol=0, preprocess_y=None) return minimize(min_func, min_solve) @@ -542,8 +540,8 @@ def _linear_solve_forward(y, batch_dims = merge_shapes(y_tensor.shape.without(pattern_dims_out), x0_tensor.shape.without(pattern_dims_in)) x0_native = backend.as_tensor(reshaped_native(x0_tensor, [batch_dims, pattern_dims_in], force_expand=True)) y_native = backend.as_tensor(reshaped_native(y_tensor, [batch_dims, y_tensor.shape.only(pattern_dims_out)], force_expand=True)) - rtol = backend.as_tensor(reshaped_native(math.to_float(_default_tolerance() if solve.relative_tolerance is None else solve.relative_tolerance), [batch_dims], force_expand=True)) - atol = backend.as_tensor(reshaped_native(_default_tolerance() if solve.absolute_tolerance is None else solve.absolute_tolerance, [batch_dims], force_expand=True)) + rtol = backend.as_tensor(reshaped_native(math.to_float(_default_tolerance() if solve.rel_tol is None else solve.rel_tol), [batch_dims], force_expand=True)) + atol = backend.as_tensor(reshaped_native(wrap(0) if solve.abs_tol is None else solve.abs_tol, [batch_dims], force_expand=True)) maxi = backend.as_tensor(reshaped_native(solve.max_iterations, [batch_dims], force_expand=True)) trj = _SOLVE_TAPES and any(t.record_trajectories for t in _SOLVE_TAPES) if trj: diff --git a/phi/physics/diffuse.py b/phi/physics/diffuse.py index 99d95f508..b292ee3d9 100644 --- a/phi/physics/diffuse.py +++ b/phi/physics/diffuse.py @@ -5,7 +5,7 @@ from phi.field import Grid, Field, laplace, solve_linear, jit_compile_linear from phi.field._field import FieldType from phi.field._grid import GridType -from phi.math import copy_with, shape +from phi.math import copy_with, shape, Solve def explicit(field: FieldType, @@ -40,7 +40,7 @@ def implicit(field: FieldType, diffusivity: float or math.Tensor or Field, dt: float or math.Tensor, order: int = 1, - solve=math.Solve('CG', 1e-5, 0)) -> FieldType: + solve=Solve('CG')) -> FieldType: """ Diffusion by solving a linear system of equations. diff --git a/phi/physics/fluid.py b/phi/physics/fluid.py index e268fa463..3119fc321 100644 --- a/phi/physics/fluid.py +++ b/phi/physics/fluid.py @@ -64,9 +64,9 @@ def _get_obstacles_for(obstacles, space: Field): def make_incompressible(velocity: GridType, obstacles: Obstacle or Geometry or tuple or list = (), - solve=Solve('auto', 1e-5, 1e-5, gradient_solve=Solve('auto', 1e-5, 1e-5)), + solve: Solve = Solve(), active: CenteredGrid = None, - order=2) -> Tuple[GridType, CenteredGrid]: + order: int = 2) -> Tuple[GridType, CenteredGrid]: """ Projects the given velocity field by solving for the pressure and subtracting its spatial_gradient. @@ -236,7 +236,7 @@ def _accessible_extrapolation(vext: Extrapolation): raise ValueError(f"Unsupported extrapolation: {type(vext)}") -def incompressible_rk4(pde: Callable, velocity: GridType, pressure: CenteredGrid, dt, pressure_order=4, pressure_solve=Solve('CG', 1e-12, 1e-12), **pde_aux_kwargs): +def incompressible_rk4(pde: Callable, velocity: GridType, pressure: CenteredGrid, dt, pressure_order=4, pressure_solve=Solve('CG'), **pde_aux_kwargs): """ Implements the 4th-order Runge-Kutta time advancement scheme for incompressible vector fields. This approach is inspired by [Kampanis et. al., 2006](https://www.sciencedirect.com/science/article/pii/S0021999105005061) and incorporates the pressure treatment into the time step. diff --git a/tests/commit/test_colab_fluids_tutorial.py b/tests/commit/test_colab_fluids_tutorial.py index d98099629..eaae6766c 100644 --- a/tests/commit/test_colab_fluids_tutorial.py +++ b/tests/commit/test_colab_fluids_tutorial.py @@ -20,7 +20,7 @@ def simulate(velocity: StaggeredGrid, smoke: CenteredGrid): smoke = advect.mac_cormack(smoke, velocity, dt=1) + INFLOW buoyancy_force = smoke * (0, 0.5) @ velocity velocity = advect.semi_lagrangian(velocity, velocity, dt=1) + buoyancy_force - velocity, _ = fluid.make_incompressible(velocity) + velocity, _ = fluid.make_incompressible(velocity, (), Solve(abs_tol=1e-6)) loss = field.l2_loss(diffuse.explicit(smoke - field.stop_gradient(smoke.inflow_loc[-1]), 1, 1, 10)) return loss, smoke, velocity diff --git a/tests/commit/test_poisson_solver.py b/tests/commit/test_poisson_solver.py index ca1812083..f8523d9d6 100644 --- a/tests/commit/test_poisson_solver.py +++ b/tests/commit/test_poisson_solver.py @@ -172,8 +172,8 @@ def test_poisson(self): guess=CenteredGrid(0, **domain).values, dx=dx ** 2, padding=extrapolation.PERIODIC, - relative_tolerance=1, - absolute_tolerance=1e-10, + rel_tol=1, + abs_tol=1e-10, max_iterations=20000, ).numpy(order="z,y,x")[0], # "CG2_solve": lambda x: CG2_solve( From 96804343ccc808af68a9e5baa496a4d5961118b9 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 15 Feb 2023 14:07:09 +0100 Subject: [PATCH 143/170] [backend] BiCG, refactor solve/minimize --- phi/math/backend/_backend.py | 289 ++----------------- phi/math/backend/_linalg.py | 427 ++++++++++++++++++++++++++++ phi/math/backend/_minimize.py | 177 ++++++++++++ phi/math/backend/_precondition.py | 211 -------------- tests/commit/math/test__optimize.py | 2 +- 5 files changed, 629 insertions(+), 477 deletions(-) create mode 100644 phi/math/backend/_linalg.py create mode 100644 phi/math/backend/_minimize.py delete mode 100644 phi/math/backend/_precondition.py diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 9ba9d257b..c732029f4 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -2,8 +2,6 @@ import warnings from collections import namedtuple from contextlib import contextmanager -from dataclasses import dataclass -from threading import Barrier from typing import List, Callable, TypeVar, Tuple, Any import logging @@ -927,14 +925,14 @@ def coo_to_dense(self, indices, values, shape, contains_duplicates: bool): return result def ilu_coo(self, indices, values, shape, iterations: int, safe: bool): - """ See incomplete_lu_coo() in _precondition """ - from ._precondition import incomplete_lu_coo + """ See incomplete_lu_coo() in _linalg """ + from ._linalg import incomplete_lu_coo assert self.dtype(values).kind in (bool, int, float) return incomplete_lu_coo(self, indices, self.to_float(values), shape, iterations, safe) def ilu_dense(self, matrix, iterations: int, safe: bool): - """ See incomplete_lu_dense() in _precondition """ - from ._precondition import incomplete_lu_dense + """ See incomplete_lu_dense() in _linalg """ + from ._linalg import incomplete_lu_dense assert self.dtype(matrix).kind in (bool, int, float) return incomplete_lu_dense(self, self.to_float(matrix), iterations, safe) @@ -1090,177 +1088,11 @@ def minimize(self, method: str, f, x0, atol, max_iter, trj: bool): if method == 'auto': method = 'L-BFGS-B' if method == 'GD': - return self._minimize_gradient_descent(f, x0, atol, max_iter, trj) - - from scipy.optimize import OptimizeResult, minimize - from threading import Thread - - assert self.supports(Backend.jacobian) - x0 = self.numpy(x0) - assert x0.ndim == 2 # (batch, parameters) - atol = self.numpy(atol) - max_iter = self.numpy(max_iter) - batch_size = x0.shape[0] - fg = self.jacobian(f, [0], get_output=True, is_f_scalar=True) - method_description = f"SciPy {method} with {self.name}" - - iterations = [0] * batch_size - function_evaluations = [0] * batch_size - xs = [None] * batch_size - final_losses = [None] * batch_size - converged = [False] * batch_size - diverged = [False] * batch_size - messages = [""] * batch_size - - f_inputs = [None] * batch_size - f_b_losses = None - f_b_losses_np = None - f_grad_np = None - f_input_available = Barrier(batch_size + 1) - f_output_available = Barrier(batch_size + 1) - finished = [False] * batch_size - all_finished = False - trajectories = [[] for _ in range(batch_size)] if trj else None - threads = [] - - for b in range(batch_size): # Run each independent example as a scipy minimization in a new thread - - def b_thread(b=b): - recent_b_losses = [] - - def b_fun(x: numpy.ndarray): - function_evaluations[b] += 1 - f_inputs[b] = self.as_tensor(x, convert_external=True) - f_input_available.wait() - f_output_available.wait() - recent_b_losses.append(f_b_losses[b]) - if final_losses[b] is None: # first evaluation - final_losses[b] = f_b_losses[b] - if trajectories is not None: - trajectories[b].append(SolveResult(method_description, x0[b], self.numpy(f_b_losses[b]), 0, 1, False, False, "")) - return f_b_losses_np[b], f_grad_np[b] - - def callback(x, *args): # L-BFGS-B only passes x but the documentation says (x, state) - iterations[b] += 1 - loss = min(recent_b_losses) - recent_b_losses.clear() - final_losses[b] = loss - if trajectories is not None: - trajectories[b].append(SolveResult(method_description, x, self.numpy(loss), iterations[b], function_evaluations[b], False, False, "")) - - res = minimize(fun=b_fun, x0=x0[b], jac=True, method=method, tol=atol[b], options={'maxiter': max_iter[b]}, callback=callback) - assert isinstance(res, OptimizeResult) - # res.nit, res.nfev - xs[b] = res.x - converged[b] = res.success - diverged[b] = res.status not in (0, 1) # 0=success - messages[b] = res.message - finished[b] = True - while not all_finished: - f_input_available.wait() - f_output_available.wait() - - b_thread = Thread(target=b_thread) - threads.append(b_thread) - b_thread.start() - - while True: - f_input_available.wait() - if all(finished): - all_finished = True - f_output_available.wait() - break - f_b_losses, f_grad = fg(self.stack(f_inputs)) # Evaluate function and gradient - f_b_losses_np = self.numpy(f_b_losses).astype(numpy.float64) - f_grad_np = self.numpy(f_grad).astype(numpy.float64) - f_output_available.wait() - - for b_thread in threads: - b_thread.join() # make sure threads exit correctly - - if trj: - max_trajectory_length = max([len(t) for t in trajectories]) - last_points = [SolveResult(method_description, xs[b], self.numpy(final_losses[b]), iterations[b], function_evaluations[b], converged[b], diverged[b], "") for b in range(batch_size)] - trajectories = [t[:-1] + [last_point] * (max_trajectory_length - len(t) + 1) for t, last_point in zip(trajectories, last_points)] - trajectory = [] - for states in zip(*trajectories): - x = numpy.stack([state.x for state in states]) - residual = numpy.stack([state.residual for state in states]) - iterations = [state.iterations for state in states] - function_evaluations = [state.function_evaluations for state in states] - converged = [state.converged for state in states] - diverged = [state.diverged for state in states] - trajectory.append(SolveResult(method_description, x, residual, iterations, function_evaluations, converged, diverged, messages)) - return trajectory + from ._minimize import gradient_descent + return gradient_descent(self, f, x0, atol, max_iter, trj) else: - x = self.stack(xs) - residual = self.stack(final_losses) - return SolveResult(method_description, x, residual, iterations, function_evaluations, converged, diverged, messages) - - def _minimize_gradient_descent(self, f, x0, atol, max_iter, trj: bool, step_size='adaptive'): - assert self.supports(Backend.jacobian) - assert len(self.staticshape(x0)) == 2 # (batch, parameters) - batch_size = self.staticshape(x0)[0] - fg = self.jacobian(f, [0], get_output=True, is_f_scalar=True) - method = f"Gradient descent with {self.name}" - - iterations = self.zeros([batch_size], DType(int, 32)) - function_evaluations = self.ones([batch_size], DType(int, 32)) - - adaptive_step_size = step_size == 'adaptive' - if adaptive_step_size: - step_size = self.zeros([batch_size]) + 0.1 - - loss, grad = fg(x0) # Evaluate function and gradient - diverged = self.any(~self.isfinite(x0), axis=(1,)) - converged = self.zeros([batch_size], DType(bool)) - trajectory = [SolveResult(method, x0, loss, iterations, function_evaluations, converged, diverged, [""] * batch_size)] if trj else None - continue_ = ~converged & ~diverged & (iterations < max_iter) - - def gd_step(continue_, x, loss, grad, iterations, function_evaluations, step_size, converged, diverged): - prev_loss, prev_grad, prev_x = loss, grad, x - continue_1 = self.to_int32(continue_) - iterations += continue_1 - if adaptive_step_size: - for i in range(20): - dx = - grad * self.expand_dims(step_size * self.to_float(continue_1), -1) - next_x = x + dx - predicted_loss_decrease = - self.sum(grad * dx, -1) # >= 0 - next_loss, next_grad = fg(next_x); function_evaluations += continue_1 - converged = converged | (self.sum(next_grad ** 2, axis=-1) < atol ** 2) - PHI_LOGGER.debug(f"Gradient: {self.numpy(next_grad)} with step_size={self.numpy(step_size)}") - actual_loss_decrease = loss - next_loss # we want > 0 - # we want actual_loss_decrease to be at least half of predicted_loss_decrease - act_pred = self.divide_no_nan(actual_loss_decrease, predicted_loss_decrease) - PHI_LOGGER.debug(f"Actual/Predicted: {self.numpy(act_pred)}") - step_size_fac = self.clip(self.log(1 + 1.71828182845 * self.exp((act_pred - 0.5) * 2.)), 0.1, 10) - PHI_LOGGER.debug(f"step_size *= {self.numpy(step_size_fac)}") - step_size *= step_size_fac - if self.all((act_pred > 0.4) & (act_pred < 0.9) | converged | diverged): - PHI_LOGGER.debug(f"GD minimization: Finished step_size adjustment after {i + 1} tries\n") - break - else: - converged = converged | (abs(actual_loss_decrease) < predicted_loss_decrease) - PHI_LOGGER.debug("Backend._minimize_gradient_descent(): No step size found!\n") - diverged = diverged | (next_loss > loss) - x, loss, grad = next_x, next_loss, next_grad - else: - x -= grad * self.expand_dims(step_size * self.to_float(continue_1), -1) - loss, grad = fg(x); function_evaluations += continue_1 - diverged = self.any(~self.isfinite(x), axis=(1,)) | (loss > prev_loss) - converged = ~diverged & (prev_loss - loss < atol) - if trj: - trajectory.append(SolveResult(method, self.numpy(x), self.numpy(loss), self.numpy(iterations), self.numpy(function_evaluations), self.numpy(diverged), self.numpy(converged), [""] * batch_size)) - continue_ = ~converged & ~diverged & (iterations < max_iter) - return continue_, x, loss, grad, iterations, function_evaluations, step_size, converged, diverged - - not_converged, x, loss, grad, iterations, function_evaluations, step_size, converged, diverged = self.while_loop(gd_step, (continue_, x0, loss, grad, iterations, function_evaluations, step_size, converged, diverged)) - - if trj: - trajectory.append(SolveResult(method, x, loss, iterations, function_evaluations + 1, converged, diverged, [""] * batch_size)) - return trajectory - else: - return SolveResult(method, x, loss, iterations, function_evaluations, converged, diverged, [""] * batch_size) + from ._minimize import scipy_minimize + return scipy_minimize(self, method, f, x0, atol, max_iter, trj) def linear_solve(self, method: str, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: """ @@ -1289,103 +1121,30 @@ def linear_solve(self, method: str, lin, y, x0, rtol, atol, max_iter, trj: bool) return self.conjugate_gradient(lin, y, x0, rtol, atol, max_iter, trj) elif method == 'CG-adaptive': return self.conjugate_gradient_adaptive(lin, y, x0, rtol, atol, max_iter, trj) + elif method in ['biCG', 'biCG-stab(0)']: + return self.bi_conjugate_gradient_original(lin, y, x0, rtol, atol, max_iter, trj) + elif method == 'biCG-stab': + return self.bi_conjugate_gradient(lin, y, x0, rtol, atol, max_iter, trj, poly_order=1) + elif method.startswith('biCG-stab('): + order = int(method[len('biCG-stab('):-1]) + return self.bi_conjugate_gradient(lin, y, x0, rtol, atol, max_iter, trj, poly_order=order) else: raise NotImplementedError(f"Method '{method}' not supported for linear solve.") def conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: """ Standard conjugate gradient algorithm. Signature matches to `Backend.linear_solve()`. """ - # Based on "An Introduction to the Conjugate Gradient Method Without the Agonizing Pain" by Jonathan Richard Shewchuk - # symbols: dx=d, dy=q, step_size=alpha, residual_squared=delta, residual=r, y=b - method = f"Φ-Flow CG ({self.name})" - y = self.to_float(y) - x0 = self.copy(self.to_float(x0), only_mutable=True) - batch_size = self.staticshape(y)[0] - tolerance_sq = self.maximum(rtol ** 2 * self.sum(y ** 2, -1), atol ** 2) - x = x0 - dx = residual = y - self.linear(lin, x) - iterations = self.zeros([batch_size], DType(int, 32)) - function_evaluations = self.ones([batch_size], DType(int, 32)) - residual_squared = rsq0 = self.sum(residual ** 2, -1, keepdims=True) - diverged = self.any(~self.isfinite(x), axis=(1,)) - converged = self.all(residual_squared <= tolerance_sq, axis=(1,)) - trajectory = [SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")] if trj else None - continue_ = ~converged & ~diverged & (iterations < max_iter) - - def cg_loop_body(continue_, it_counter, x, dx, residual_squared, residual, iterations, function_evaluations, _converged, _diverged): - continue_1 = self.to_int32(continue_) - it_counter += 1; iterations += continue_1 - with spatial_derivative_evaluation(1): - dy = self.linear(lin, dx); function_evaluations += continue_1 - dx_dy = self.sum(dx * dy, axis=-1, keepdims=True) - step_size = self.divide_no_nan(residual_squared, dx_dy) - step_size *= self.expand_dims(self.to_float(continue_1), -1) # this is not really necessary but ensures batch-independence - x += step_size * dx - # if it_counter % 50 == 0: - # residual = y - self.linear(lin, x); function_evaluations += 1 - # else: - residual = residual - step_size * dy # in-place subtraction affects convergence - residual_squared_old = residual_squared - residual_squared = self.sum(residual ** 2, -1, keepdims=True) - dx = residual + self.divide_no_nan(residual_squared, residual_squared_old) * dx - diverged = self.any(residual_squared / rsq0 > 1e5, axis=(1,)) & (iterations >= 8) - converged = self.all(residual_squared <= tolerance_sq, axis=(1,)) - if trajectory is not None: - trajectory.append(SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")) - x = self.copy(x) - iterations = self.copy(iterations) - continue_ = ~converged & ~diverged & (iterations < max_iter) - return continue_, it_counter, x, dx, residual_squared, residual, iterations, function_evaluations, converged, diverged - - _, _, x, _, _, residual, iterations, function_evaluations, converged, diverged = self.while_loop(cg_loop_body, (continue_, 0, x, dx, residual_squared, residual, iterations, function_evaluations, converged, diverged)) - return trajectory if trj else SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "") + from ._linalg import cg + return cg(self, lin, y, x0, rtol, atol, max_iter, trj) def conjugate_gradient_adaptive(self, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: """ Conjugate gradient algorithm with adaptive step size. Signature matches to `Backend.linear_solve()`. """ - # Based on the variant described in "Methods of Conjugate Gradients for Solving Linear Systems" by Magnus R. Hestenes and Eduard Stiefel - # https://nvlpubs.nist.gov/nistpubs/jres/049/jresv49n6p409_A1b.pdf - method = f"Φ-Flow CG-adaptive ({self.name})" - y = self.to_float(y) - x0 = self.copy(self.to_float(x0), only_mutable=True) - batch_size = self.staticshape(y)[0] - tolerance_sq = self.maximum(rtol ** 2 * self.sum(y ** 2, -1), atol ** 2) - x = x0 - dx = residual = y - self.linear(lin, x) - dy = self.linear(lin, dx) - iterations = self.zeros([batch_size], DType(int, 32)) - function_evaluations = self.ones([batch_size], DType(int, 32)) - residual_squared = rsq0 = self.sum(residual ** 2, -1, keepdims=True) - diverged = self.any(~self.isfinite(x), axis=(1,)) - converged = self.all(residual_squared <= tolerance_sq, axis=(1,)) - trajectory = [SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")] if trj else None - continue_ = ~converged & ~diverged & (iterations < max_iter) - - def acg_loop_body(continue_, it_counter, x, dx, dy, residual, iterations, function_evaluations, _converged, _diverged): - continue_1 = self.to_int32(continue_) - it_counter += 1 - iterations += continue_1 - dx_dy = self.sum(dx * dy, axis=-1, keepdims=True) - step_size = self.divide_no_nan(self.sum(dx * residual, axis=-1, keepdims=True), dx_dy) - step_size *= self.expand_dims(self.to_float(continue_1), -1) # this is not really necessary but ensures batch-independence - x += step_size * dx - # if it_counter % 50 == 0: # Not traceable since Python bool - # residual = y - self.linear(lin, x); function_evaluations += 1 - # else: - residual = residual - step_size * dy # in-place subtraction affects convergence - residual_squared = self.sum(residual ** 2, -1, keepdims=True) - dx = residual - self.divide_no_nan(self.sum(residual * dy, axis=-1, keepdims=True) * dx, dx_dy) - with spatial_derivative_evaluation(1): - dy = self.linear(lin, dx); function_evaluations += continue_1 - diverged = self.any(residual_squared / rsq0 > 1e5, axis=(1,)) & (iterations >= 8) - converged = self.all(residual_squared <= tolerance_sq, axis=(1,)) - if trajectory is not None: - trajectory.append(SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")) - x = self.copy(x) - iterations = self.copy(iterations) - continue_ = ~converged & ~diverged & (iterations < max_iter) - return continue_, it_counter, x, dx, dy, residual, iterations, function_evaluations, converged, diverged - - _, _, x, _, _, residual, iterations, function_evaluations, converged, diverged = self.while_loop(acg_loop_body, (continue_, 0, x, dx, dy, residual, iterations, function_evaluations, converged, diverged)) - return trajectory if trj else SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "") + from ._linalg import cg_adaptive + return cg_adaptive(self, lin, y, x0, rtol, atol, max_iter, trj) + + def bi_conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj: bool, poly_order=2) -> SolveResult or List[SolveResult]: + """ Generalized stabilized biconjugate gradient algorithm. Signature matches to `Backend.linear_solve()`. """ + from ._linalg import bicg + return bicg(self, lin, y, x0, rtol, atol, max_iter, trj, poly_order) def linear(self, lin, vector): if callable(lin): diff --git a/phi/math/backend/_linalg.py b/phi/math/backend/_linalg.py new file mode 100644 index 000000000..426bec76c --- /dev/null +++ b/phi/math/backend/_linalg.py @@ -0,0 +1,427 @@ +from functools import partial +from typing import Tuple + +import numpy as np + +from ._backend import Backend, SolveResult, List, DType, spatial_derivative_evaluation + + +def cg(b, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: + """ + Based on "An Introduction to the Conjugate Gradient Method Without the Agonizing Pain" by Jonathan Richard Shewchuk + symbols: dx=d, dy=q, step_size=alpha, residual_squared=delta, residual=r, y=b + """ + method = f"Φ-Flow CG ({b.name})" + y = b.to_float(y) + x0 = b.copy(b.to_float(x0), only_mutable=True) + batch_size = b.staticshape(y)[0] + tolerance_sq = b.maximum(rtol ** 2 * b.sum(y ** 2, -1), atol ** 2) + x = x0 + dx = residual = y - b.linear(lin, x) + iterations = b.zeros([batch_size], DType(int, 32)) + function_evaluations = b.ones([batch_size], DType(int, 32)) + residual_squared = rsq0 = b.sum(residual ** 2, -1, keepdims=True) + diverged = b.any(~b.isfinite(x), axis=(1,)) + converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) + trajectory = [SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")] if trj else None + continue_ = ~converged & ~diverged & (iterations < max_iter) + + def cg_loop_body(continue_, it_counter, x, dx, residual_squared, residual, iterations, function_evaluations, _converged, _diverged): + continue_1 = b.to_int32(continue_) + it_counter += 1 + iterations += continue_1 + with spatial_derivative_evaluation(1): + dy = b.linear(lin, dx); function_evaluations += continue_1 + dx_dy = b.sum(dx * dy, axis=-1, keepdims=True) + step_size = b.divide_no_nan(residual_squared, dx_dy) + step_size *= b.expand_dims(b.to_float(continue_1), -1) # this is not really necessary but ensures batch-independence + x += step_size * dx + residual = residual - step_size * dy # in-place subtraction affects convergence + residual_squared_old = residual_squared + residual_squared = b.sum(residual ** 2, -1, keepdims=True) + dx = residual + b.divide_no_nan(residual_squared, residual_squared_old) * dx + diverged = b.any(residual_squared / rsq0 > 1e5, axis=(1,)) & (iterations >= 8) + converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) + if trajectory is not None: + trajectory.append(SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")) + x = b.copy(x) + iterations = b.copy(iterations) + continue_ = ~converged & ~diverged & (iterations < max_iter) + return continue_, it_counter, x, dx, residual_squared, residual, iterations, function_evaluations, converged, diverged + + _, _, x, _, _, residual, iterations, function_evaluations, converged, diverged = b.while_loop(cg_loop_body, ( + continue_, 0, x, dx, residual_squared, residual, iterations, function_evaluations, converged, diverged)) + return trajectory if trj else SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "") + + +def cg_adaptive(b, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: + """ + Based on the variant described in "Methods of Conjugate Gradients for Solving Linear Systems" by Magnus R. Hestenes and Eduard Stiefel https://nvlpubs.nist.gov/nistpubs/jres/049/jresv49n6p409_A1b.pdf + """ + method = f"Φ-Flow CG-adaptive ({b.name})" + y = b.to_float(y) + x0 = b.copy(b.to_float(x0), only_mutable=True) + batch_size = b.staticshape(y)[0] + tolerance_sq = b.maximum(rtol ** 2 * b.sum(y ** 2, -1), atol ** 2) + x = x0 + dx = residual = y - b.linear(lin, x) + dy = b.linear(lin, dx) + iterations = b.zeros([batch_size], DType(int, 32)) + function_evaluations = b.ones([batch_size], DType(int, 32)) + residual_squared = rsq0 = b.sum(residual ** 2, -1, keepdims=True) + diverged = b.any(~b.isfinite(x), axis=(1,)) + converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) + trajectory = [SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")] if trj else None + continue_ = ~converged & ~diverged & (iterations < max_iter) + + def acg_loop_body(continue_, it_counter, x, dx, dy, residual, iterations, function_evaluations, _converged, _diverged): + continue_1 = b.to_int32(continue_) + it_counter += 1 + iterations += continue_1 + dx_dy = b.sum(dx * dy, axis=-1, keepdims=True) + step_size = b.divide_no_nan(b.sum(dx * residual, axis=-1, keepdims=True), dx_dy) + step_size *= b.expand_dims(b.to_float(continue_1), -1) # this is not really necessary but ensures batch-independence + x += step_size * dx + residual = residual - step_size * dy # in-place subtraction affects convergence + residual_squared = b.sum(residual ** 2, -1, keepdims=True) + dx = residual - b.divide_no_nan(b.sum(residual * dy, axis=-1, keepdims=True) * dx, dx_dy) + with spatial_derivative_evaluation(1): + dy = b.linear(lin, dx); function_evaluations += continue_1 + diverged = b.any(residual_squared / rsq0 > 1e5, axis=(1,)) & (iterations >= 8) + converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) + if trajectory is not None: + trajectory.append(SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")) + x = b.copy(x) + iterations = b.copy(iterations) + continue_ = ~converged & ~diverged & (iterations < max_iter) + return continue_, it_counter, x, dx, dy, residual, iterations, function_evaluations, converged, diverged + + _, _, x, _, _, residual, iterations, function_evaluations, converged, diverged = b.while_loop(acg_loop_body, (continue_, 0, x, dx, dy, residual, iterations, function_evaluations, converged, diverged)) + return trajectory if trj else SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "") + + +def bicg(b: Backend, lin, y, x0, rtol, atol, max_iter, trj: bool, poly_order: int) -> SolveResult or List[SolveResult]: + """ Adapted from [BiCGstab for linear equations involving unsymmetric matrices with complex spectrum](https://dspace.library.uu.nl/bitstream/handle/1874/16827/sleijpen_93_bicgstab.pdf) """ + # Based on "BiCGstab(L) for linear equations involving unsymmetric matrices with complex spectrum" by Gerard L.G. Sleijpen + # # symbols: dx=d, dy=q, step_size=alpha, residual_squared=delta, residual=r, y=b + method = f"Φ-Flow biCG-stab({poly_order}) ({b.name})" + # tensor_size = tuple([L + 1] + [int(dim) for dim in x0.shape]) + y = b.to_float(y) + x = b.copy(b.to_float(x0), only_mutable=True) + batch_size = b.staticshape(y)[0] + tolerance_sq = b.maximum(rtol ** 2 * b.sum(y ** 2, -1), atol ** 2) + residual = y - b.linear(lin, x) + iterations = b.zeros([batch_size], DType(int, 32)) + function_evaluations = b.ones([batch_size], DType(int, 32)) + residual_squared = rsq0 = b.sum(residual ** 2, -1, keepdims=True) + diverged = b.any(~b.isfinite(x), axis=(1,)) + converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) + trajectory = [SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")] if trj else None + continue_ = ~converged & ~diverged & (iterations < max_iter) + rho_0 = b.ones([batch_size, 1]) + rho_1 = b.ones([batch_size, 1]) + omega = b.ones([batch_size, 1]) + alpha = b.zeros([batch_size, 1]) + u = b.zeros_like(x) + r0_hat = [b.zeros(x0.shape)] * (poly_order + 1) + u_hat = [b.zeros(x0.shape)] * (poly_order + 1) + loop_body = partial(_bicg_stabL_loop_body, b, poly_order, batch_size, lin, residual, trajectory, method, rsq0, tolerance_sq, max_iter) + _, _, x, residual, iterations, function_evaluations, converged, diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat = b.while_loop(loop_body, ( + continue_, 0, x, residual, iterations, function_evaluations, converged, diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat)) + return trajectory if trj else SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "") + + +def _bicg_stabL_loop_body(b: Backend, poly_order: int, batch_size: int, lin, r0_tild, trajectory, method, rsq0, tolerance_sq, max_iter, + continue_, it_counter, x, residual, iterations, function_evaluations, _converged, _diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat): + tau = [[b.zeros((batch_size,))] * (poly_order + 1)] * (poly_order + 1) + sigma = [b.zeros((batch_size,))] * (poly_order + 1) + gamma = [b.zeros((batch_size,))] * (poly_order + 1) + gamma_p = [b.zeros((batch_size,))] * (poly_order + 1) + gamma_pp = [b.zeros((batch_size,))] * (poly_order + 1) + continue_1 = b.to_int32(continue_) + it_counter += 1; iterations += continue_1 + u_hat[0] = u + r0_hat[0] = residual + rho_0 = -omega * rho_0 + # --- Bi-CG part --- + for j in range(0, poly_order): + rho_1 = b.sum(r0_hat[j] * r0_tild, axis=-1, keepdims=True) + beta = alpha * rho_1 / rho_0 + rho_0 = rho_1 + for i in range(0, j + 1): + u_hat[i] = beta * u_hat[i] + u_hat[i] = r0_hat[i] - u_hat[i] + u_hat[j + 1] = b.linear(lin, u_hat[j]); function_evaluations += continue_1 + gamma_coeff = b.sum(u_hat[j + 1] * r0_tild, axis=-1, keepdims=True) + alpha = rho_0 / gamma_coeff + for i in range(0, j + 1): + r0_hat[i] = r0_hat[i] - alpha * u_hat[i + 1] + r0_hat[j + 1] = b.linear(lin, r0_hat[j]); function_evaluations += continue_1 + x = x + alpha * u_hat[0] + for j in range(1, poly_order + 1): + for i in range(1, j): + tau[i][j] = b.sum(r0_hat[j] * r0_hat[i], axis=-1, keepdims=True) / sigma[i] + r0_hat[j] = r0_hat[j] - tau[i][j] * r0_hat[i] + sigma[j] = b.sum(r0_hat[j] * r0_hat[j], axis=-1, keepdims=True) + gamma_p[j] = b.sum(r0_hat[0] * r0_hat[j], axis=-1, keepdims=True) / sigma[j] + # --- MR part --- + omega = gamma[poly_order] = gamma_p[poly_order] + for j in range(poly_order - 1, 0, -1): + sumg = b.zeros_like(tau[0][0]) + for i in range(j + 1, poly_order + 1): + sumg = sumg + tau[j][i] * gamma[i] + gamma[j] = gamma_p[j] - sumg + for j in range(1, poly_order): + sumg = b.zeros_like(tau[0][0]) + for i in range(j + 1, poly_order): + sumg = sumg + tau[j][i] * gamma[i + 1] + gamma_pp[j] = gamma[j + 1] + sumg + # --- Update --- + x = x + gamma[1] * r0_hat[0] + r0_hat[0] = r0_hat[0] - gamma_p[poly_order] * r0_hat[poly_order] + u_hat[0] = u_hat[0] - gamma[poly_order] * u_hat[poly_order] + for j in range(1, poly_order): + u_hat[0] = u_hat[0] - gamma[j] * u_hat[j] + x = x + gamma_pp[j] * r0_hat[j] + r0_hat[0] = r0_hat[0] - gamma_p[j] * r0_hat[j] + u = u_hat[0] + residual = r0_hat[0] + residual_squared = b.sum(residual ** 2, -1, keepdims=True) + diverged = b.any(residual_squared / rsq0 > 1e5, axis=(1,)) & (iterations >= 8) + converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) + if trajectory is not None: + trajectory.append(SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")) + x = b.copy(x) + iterations = b.copy(iterations) + continue_ = ~converged & ~diverged & (iterations < max_iter) + return continue_, it_counter, x, residual, iterations, function_evaluations, converged, diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat + + +def incomplete_lu_dense(b: 'Backend', matrix, iterations: int, safe: bool): + """ + + Args: + b: `Backend` + matrix: Square matrix of Shape (batch_size, rows, cols, channels) + iterations: Number of fixed-point iterations to perform. + safe: Avoid NaN when the rank deficiency of `matrix` is 2 or higher. + For a rank deficiency of 1, the fixed-point algorithm will still converge without NaNs and all values of L and U are uniquely determined. + If enabled, the algorithm is slightly slower. + Rank deficiencies of 1 occur frequently in periodic settings but higher ones are rare. + + Returns: + L: lower triangular matrix with ones on the diagonal + U: upper triangular matrix + """ + row, col = np.indices(b.staticshape(matrix)[1:-1]) + is_lower = np.expand_dims(row > col, -1) + is_upper = np.expand_dims(row < col, -1) + is_diagonal = np.expand_dims(row == col, -1) + # # --- Initialize U as the diagonal of A, then compute off-diagonal of L --- + lower = matrix / b.expand_dims(b.get_diagonal(matrix), 1) # Since U=diag(A), L can be computed by a simple division + lu = matrix * is_diagonal + lower * is_lower # combine lower + diag(A) + 0 + # --- Fixed-point iterations --- + for sweep in range(iterations): + diag = b.expand_dims(b.get_diagonal(lu), 1) # should never contain 0 + sum_l_u = b.einsum('bikc,bkjc->bijc', lu * is_lower, lu * is_upper) + l = (matrix - sum_l_u) / diag if not safe else b.divide_no_nan(matrix - sum_l_u, diag) + lu = b.where(is_lower, l, matrix - sum_l_u) + # --- Assemble L=lower+unit_diagonal and U. --- + return lu * is_lower + is_diagonal, lu * ~is_lower + + +def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], iterations: int, safe: bool): + """ + Based on *Parallel Approximate LU Factorizations for Sparse Matrices* by T.K. Huckle, https://www5.in.tum.de/persons/huckle/it_ilu.pdf. + + Every matrix in the batch must explicitly store the full diagonal. + There should not be any zeros on the diagonal, else the LU initialization fails. + + Args: + b: `Backend` + indices: Row & column indices of stored entries as `numpy.ndarray` of shape (batch_size, nnz, 2). + values: Backend-compatible values tensor of shape (batch_size, nnz, channels) + shape: Dense shape of matrix + iterations: Number of fixed-point iterations to perform. + safe: Avoid NaN when the rank deficiency of `matrix` is 2 or higher. + For a rank deficiency of 1, the fixed-point algorithm will still converge without NaNs. + If enabled, the algorithm is slightly slower. + Rank deficiencies of 1 occur frequently in periodic settings but higher ones are rare. + + Returns: + L: tuple `(indices, values)` where `indices` is a NumPy array and values is backend-specific + U: tuple `(indices, values)` where `indices` is a NumPy array and values is backend-specific + """ + assert isinstance(indices, np.ndarray), "incomplete_lu_coo indices must be a NumPy array" + row, col = indices[..., 0], indices[..., 1] + batch_size, nnz, channels = b.staticshape(values) + rows, cols = shape + assert rows == cols, "incomplete_lu_coo only implemented for square matrices" + is_lower = np.expand_dims(row > col, -1) + diagonal_indices = np.expand_dims(get_lower_diagonal_indices(row, col, shape), -1) # indices of corresponding values that lie on the diagonal + is_diagonal = np.expand_dims(row == col, -1) + mm_above, mm_left, mm_is_valid = strict_lu_mm_pattern_coo_batched(row, col, rows, cols) + mm_above = np.expand_dims(mm_above, -1) + mm_left = np.expand_dims(mm_left, -1) + mm_is_valid = np.expand_dims(mm_is_valid, -1) + # --- Initialize U as the diagonal of A, then compute off-diagonal of L --- + lower = values / b.batched_gather_nd(values, diagonal_indices) # Since U=diag(A), L can be computed by a simple division + lu = values * is_diagonal + lower * is_lower # combine lower + diag(A) + 0 + # --- Fixed-point iterations --- + for sweep in range(iterations): + diag = b.batched_gather_nd(lu, diagonal_indices) # should never contain 0 + sum_l_u = b.einsum('bnkc,bnkc->bnc', b.batched_gather_nd(lu, mm_above) * mm_is_valid, b.batched_gather_nd(lu, mm_left)) + l = (values - sum_l_u) / diag if not safe else b.divide_no_nan(values - sum_l_u, diag) + lu = b.where(is_lower, l, values - sum_l_u) + # --- Assemble L=lower+unit_diagonal and U. If nnz varies along batch, keep the full sparsity pattern --- + u_values = b.where(~is_lower, lu, 0) + belongs_to_lower = (is_lower | is_diagonal) + l_values = b.where(is_lower, lu, b.cast(is_diagonal, b.dtype(values))) + u_mask_indices_b, u_mask_indices = np.where(~is_lower[..., 0]) + _, u_nnz = np.unique(u_mask_indices_b, return_counts=True) + if np.all(u_nnz == u_nnz[0]): # nnz for lower/upper does not vary along batch + u_mask_indices = np.reshape(u_mask_indices, (batch_size, -1)) + u_values = b.batched_gather_nd(u_values, np.expand_dims(u_mask_indices, -1)) + u_indices = np.stack([indices[b, u_mask_indices[b], :] for b in range(batch_size)]) + _, l_mask_indices = np.where(belongs_to_lower[..., 0]) + l_mask_indices = np.reshape(l_mask_indices, (batch_size, -1)) + l_values = b.batched_gather_nd(l_values, np.expand_dims(l_mask_indices, -1)) + l_indices = np.stack([indices[b, l_mask_indices[b], :] for b in range(batch_size)]) + return (l_indices, l_values), (u_indices, u_values) + else: # Keep all indices since the number in lower/upper varies along the batch + return (indices, l_values), (indices, u_values) + + +def strict_lu_mm_pattern_coo(row: np.ndarray, col: np.ndarray, rows, cols): + """ + For each stored entry e at (row, col), finds the matching entries directly above and directly left of e, such that left.col == above.row. + + This is useful for multiplying a lower triangular and upper triangular matrix given the sparsity pattern but excluding the diagonals. + The matrix multiplication then is given by + >>> einsum('nk,nk->n', stored_lower[above_entries] * is_valid_entry, stored_upper[left_entries]) + + Returns: + above_entries: (max_num, nnz) Stored indices of matched elements above any entry. + left_entries: (max_num, nnz) Stored indices of matched elements to the left of any entry. + is_valid_entry: (max_num, nnz) Mask of valid indices. Invalid indices are undefined but lie inside the array to prevent index errors. + """ + entries, = row.shape + # --- Compress rows and cols --- + lower_entries_by_row = compress_strict_lower_triangular_rows(row, col, rows) # entry indices by row, -1 for non-existent entries + upper_entries_by_col = compress_strict_lower_triangular_rows(col, row, cols) + # --- Find above and left entries --- + same_row_entries = lower_entries_by_row[:, row] # (row entries, entries). Currently, contains valid values for invalid references + left = np.where(col[same_row_entries] < col, same_row_entries, -1) # (max_left, nnz) all entries with col_e==col, row_e < row + same_col_entries = upper_entries_by_col[:, col] + above = np.where(row[same_col_entries] < row, same_col_entries, -1) # (max_above, nnz) + # --- for each entry, match left and above where left.col == above.row --- + half_density = max(len(lower_entries_by_row), len(upper_entries_by_col)) + above_entries = np.zeros([entries, half_density], dtype=int) + left_entries = np.zeros([entries, half_density], dtype=int) + is_valid_entry = np.zeros([entries, half_density]) + k = np.zeros(entries, dtype=int) + for r in range(len(above)): + for c in range(len(left)): + match = (col[left[c]] == row[above[r]]) & (above[r] != -1) + where_match = np.where(match) + k_where_match = k[where_match] + above_entries[where_match, k_where_match] = above[r][where_match] + left_entries[where_match, k_where_match] = left[c][where_match] + is_valid_entry[where_match, k_where_match] = 1 + k += match + return above_entries, left_entries, is_valid_entry + + +def compress_strict_lower_triangular_rows(row, col, rows): + is_lower = row > col + below_diagonal = np.where(is_lower) + row_lower = row[below_diagonal] + num_in_row = get_index_in_row(row_lower, col[below_diagonal]) + lower_entries_by_row = np.zeros((np.max(num_in_row)+1, rows), dtype=row.dtype) - 1 + lower_entries_by_row[num_in_row, row_lower] = below_diagonal + return lower_entries_by_row + + +def strict_lu_mm_pattern_coo_batched(row, col, rows, cols): + results = [strict_lu_mm_pattern_coo(row[b], col[b], rows, cols) for b in range(row.shape[0])] + result = [np.stack(v) for v in zip(*results)] + return result + + +def get_index_in_row(row: np.ndarray, col: np.ndarray): + """ How many entries are to the left of a given entry but in the same row, i.e. the how manieth index this is per row. """ + perm = np.argsort(col) + compressed_col_index = cumcount(row[perm])[inv_perm(perm)] + return compressed_col_index + + +def inv_perm(perm): + """ Returns the permutation necessary to undo a sort given the argsort array. """ + u = np.empty(perm.size, dtype=np.int64) + u[perm] = np.arange(perm.size) + return u + + +def cumcount(a): + """ Based on https://stackoverflow.com/questions/40602269/how-to-use-numpy-to-get-the-cumulative-count-by-unique-values-in-linear-time """ + def dfill(a): + """ Returns the positions where the array changes and repeats that index position until the next change. """ + b = np.concatenate([[0], np.where(a[:-1] != a[1:])[0] + 1, [a.size]]) + return np.arange(a.size)[b[:-1]].repeat(np.diff(b)) + perm = a.argsort(kind='mergesort') + inv = inv_perm(perm) + return (np.arange(a.size) - dfill(a[perm]))[inv] + + +def cumcount2(l): # slightly slower than cumcount + a = np.unique(l, return_counts=True)[1] + idx = a.cumsum() + id_arr = np.ones(idx[-1], dtype=int) + id_arr[0] = 0 + id_arr[idx[:-1]] = -a[:-1] + 1 + rng = id_arr.cumsum() + return rng[inv_perm(np.argsort(l))] + + +def get_transposed_indices(row, col, shape): + linear = np.ravel_multi_index((row, col), shape) + linear_transposed = np.ravel_multi_index((col, row), shape) + has_transpose = np.stack([np.isin(linear[b], linear_transposed[b]) for b in range(row.shape[0])]) + perm = np.argsort(linear) + transposed = np.stack([np.searchsorted(linear[b], linear_transposed[b], sorter=perm[b]) for b in range(row.shape[0])]) + transposed = np.minimum(transposed, len(row) - 1) + return has_transpose, transposed + + +def get_lower_diagonal_indices(row, col, shape): + linear = np.ravel_multi_index((row, col), shape) + j = np.minimum(row, col) + diagonal_indices = np.ravel_multi_index((j, j), shape) + perm = np.argsort(linear) + result = [perm[b, np.searchsorted(linear[b], diagonal_indices[b], sorter=perm[b])] for b in range(row.shape[0])] + assert np.all([np.isin(diagonal_indices[b], linear[b]) for b in range(row.shape[0])]), "All diagonal elements must be present in sparse matrix." + return np.stack(result) + + +def parallelize_dense_triangular_solve(b: Backend, matrix, lower_triangular=True): + """ + + Args: + b: + matrix: lower-triangular matrix + + Returns: + + """ + rows, cols = b.staticshape(matrix) + # batch_size, rows, cols, channels = b.staticshape(matrix) + xs = {} + for row in range(rows): + x = b.zeros((cols,)) + x[row] = 1 + for j in range(row): + x -= xs[j] * matrix[row, j] + if not lower_triangular: + x /= matrix[row, row] + xs[row] = x + print(xs) diff --git a/phi/math/backend/_minimize.py b/phi/math/backend/_minimize.py new file mode 100644 index 000000000..1c7579032 --- /dev/null +++ b/phi/math/backend/_minimize.py @@ -0,0 +1,177 @@ +from threading import Barrier + +import numpy + +from ._backend import Backend, SolveResult, DType, PHI_LOGGER + + +def scipy_minimize(self, method: str, f, x0, atol, max_iter, trj: bool): + from scipy.optimize import OptimizeResult, minimize + from threading import Thread + + assert self.supports(Backend.jacobian) + x0 = self.numpy(x0) + assert x0.ndim == 2 # (batch, parameters) + atol = self.numpy(atol) + max_iter = self.numpy(max_iter) + batch_size = x0.shape[0] + fg = self.jacobian(f, [0], get_output=True, is_f_scalar=True) + method_description = f"SciPy {method} with {self.name}" + + iterations = [0] * batch_size + function_evaluations = [0] * batch_size + xs = [None] * batch_size + final_losses = [None] * batch_size + converged = [False] * batch_size + diverged = [False] * batch_size + messages = [""] * batch_size + + f_inputs = [None] * batch_size + f_b_losses = None + f_b_losses_np = None + f_grad_np = None + f_input_available = Barrier(batch_size + 1) + f_output_available = Barrier(batch_size + 1) + finished = [False] * batch_size + all_finished = False + trajectories = [[] for _ in range(batch_size)] if trj else None + threads = [] + + for b in range(batch_size): # Run each independent example as a scipy minimization in a new thread + + def b_thread(b=b): + recent_b_losses = [] + + def b_fun(x: numpy.ndarray): + function_evaluations[b] += 1 + f_inputs[b] = self.as_tensor(x, convert_external=True) + f_input_available.wait() + f_output_available.wait() + recent_b_losses.append(f_b_losses[b]) + if final_losses[b] is None: # first evaluation + final_losses[b] = f_b_losses[b] + if trajectories is not None: + trajectories[b].append(SolveResult(method_description, x0[b], self.numpy(f_b_losses[b]), 0, 1, False, False, "")) + return f_b_losses_np[b], f_grad_np[b] + + def callback(x, *args): # L-BFGS-B only passes x but the documentation says (x, state) + iterations[b] += 1 + loss = min(recent_b_losses) + recent_b_losses.clear() + final_losses[b] = loss + if trajectories is not None: + trajectories[b].append(SolveResult(method_description, x, self.numpy(loss), iterations[b], function_evaluations[b], False, False, "")) + + res = minimize(fun=b_fun, x0=x0[b], jac=True, method=method, tol=atol[b], options={'maxiter': max_iter[b]}, callback=callback) + assert isinstance(res, OptimizeResult) + # res.nit, res.nfev + xs[b] = res.x + converged[b] = res.success + diverged[b] = res.status not in (0, 1) # 0=success + messages[b] = res.message + finished[b] = True + while not all_finished: + f_input_available.wait() + f_output_available.wait() + + b_thread = Thread(target=b_thread) + threads.append(b_thread) + b_thread.start() + + while True: + f_input_available.wait() + if all(finished): + all_finished = True + f_output_available.wait() + break + f_b_losses, f_grad = fg(self.stack(f_inputs)) # Evaluate function and gradient + f_b_losses_np = self.numpy(f_b_losses).astype(numpy.float64) + f_grad_np = self.numpy(f_grad).astype(numpy.float64) + f_output_available.wait() + + for b_thread in threads: + b_thread.join() # make sure threads exit correctly + + if trj: + max_trajectory_length = max([len(t) for t in trajectories]) + last_points = [SolveResult(method_description, xs[b], self.numpy(final_losses[b]), iterations[b], function_evaluations[b], converged[b], diverged[b], "") for b in range(batch_size)] + trajectories = [t[:-1] + [last_point] * (max_trajectory_length - len(t) + 1) for t, last_point in zip(trajectories, last_points)] + trajectory = [] + for states in zip(*trajectories): + x = numpy.stack([state.x for state in states]) + residual = numpy.stack([state.residual for state in states]) + iterations = [state.iterations for state in states] + function_evaluations = [state.function_evaluations for state in states] + converged = [state.converged for state in states] + diverged = [state.diverged for state in states] + trajectory.append(SolveResult(method_description, x, residual, iterations, function_evaluations, converged, diverged, messages)) + return trajectory + else: + x = self.stack(xs) + residual = self.stack(final_losses) + return SolveResult(method_description, x, residual, iterations, function_evaluations, converged, diverged, messages) + + +def gradient_descent(self, f, x0, atol, max_iter, trj: bool, step_size='adaptive'): + assert self.supports(Backend.jacobian) + assert len(self.staticshape(x0)) == 2 # (batch, parameters) + batch_size = self.staticshape(x0)[0] + fg = self.jacobian(f, [0], get_output=True, is_f_scalar=True) + method = f"Gradient descent with {self.name}" + + iterations = self.zeros([batch_size], DType(int, 32)) + function_evaluations = self.ones([batch_size], DType(int, 32)) + + adaptive_step_size = step_size == 'adaptive' + if adaptive_step_size: + step_size = self.zeros([batch_size]) + 0.1 + + loss, grad = fg(x0) # Evaluate function and gradient + diverged = self.any(~self.isfinite(x0), axis=(1,)) + converged = self.zeros([batch_size], DType(bool)) + trajectory = [SolveResult(method, x0, loss, iterations, function_evaluations, converged, diverged, [""] * batch_size)] if trj else None + continue_ = ~converged & ~diverged & (iterations < max_iter) + + def gd_step(continue_, x, loss, grad, iterations, function_evaluations, step_size, converged, diverged): + prev_loss, prev_grad, prev_x = loss, grad, x + continue_1 = self.to_int32(continue_) + iterations += continue_1 + if adaptive_step_size: + for i in range(20): + dx = - grad * self.expand_dims(step_size * self.to_float(continue_1), -1) + next_x = x + dx + predicted_loss_decrease = - self.sum(grad * dx, -1) # >= 0 + next_loss, next_grad = fg(next_x); function_evaluations += continue_1 + converged = converged | (self.sum(next_grad ** 2, axis=-1) < atol ** 2) + PHI_LOGGER.debug(f"Gradient: {self.numpy(next_grad)} with step_size={self.numpy(step_size)}") + actual_loss_decrease = loss - next_loss # we want > 0 + # we want actual_loss_decrease to be at least half of predicted_loss_decrease + act_pred = self.divide_no_nan(actual_loss_decrease, predicted_loss_decrease) + PHI_LOGGER.debug(f"Actual/Predicted: {self.numpy(act_pred)}") + step_size_fac = self.clip(self.log(1 + 1.71828182845 * self.exp((act_pred - 0.5) * 2.)), 0.1, 10) + PHI_LOGGER.debug(f"step_size *= {self.numpy(step_size_fac)}") + step_size *= step_size_fac + if self.all((act_pred > 0.4) & (act_pred < 0.9) | converged | diverged): + PHI_LOGGER.debug(f"GD minimization: Finished step_size adjustment after {i + 1} tries\n") + break + else: + converged = converged | (abs(actual_loss_decrease) < predicted_loss_decrease) + PHI_LOGGER.debug("Backend._minimize_gradient_descent(): No step size found!\n") + diverged = diverged | (next_loss > loss) + x, loss, grad = next_x, next_loss, next_grad + else: + x -= grad * self.expand_dims(step_size * self.to_float(continue_1), -1) + loss, grad = fg(x); function_evaluations += continue_1 + diverged = self.any(~self.isfinite(x), axis=(1,)) | (loss > prev_loss) + converged = ~diverged & (prev_loss - loss < atol) + if trj: + trajectory.append(SolveResult(method, self.numpy(x), self.numpy(loss), self.numpy(iterations), self.numpy(function_evaluations), self.numpy(diverged), self.numpy(converged), [""] * batch_size)) + continue_ = ~converged & ~diverged & (iterations < max_iter) + return continue_, x, loss, grad, iterations, function_evaluations, step_size, converged, diverged + + not_converged, x, loss, grad, iterations, function_evaluations, step_size, converged, diverged = self.while_loop(gd_step, (continue_, x0, loss, grad, iterations, function_evaluations, step_size, converged, diverged)) + if trj: + trajectory.append(SolveResult(method, x, loss, iterations, function_evaluations + 1, converged, diverged, [""] * batch_size)) + return trajectory + else: + return SolveResult(method, x, loss, iterations, function_evaluations, converged, diverged, [""] * batch_size) diff --git a/phi/math/backend/_precondition.py b/phi/math/backend/_precondition.py deleted file mode 100644 index 8a569db94..000000000 --- a/phi/math/backend/_precondition.py +++ /dev/null @@ -1,211 +0,0 @@ -from typing import Tuple - -import numpy as np - -from ._backend import Backend - - -def incomplete_lu_dense(b: 'Backend', matrix, iterations: int, safe: bool): - """ - - Args: - b: `Backend` - matrix: Square matrix of Shape (batch_size, rows, cols, channels) - iterations: Number of fixed-point iterations to perform. - safe: Avoid NaN when the rank deficiency of `matrix` is 2 or higher. - For a rank deficiency of 1, the fixed-point algorithm will still converge without NaNs and all values of L and U are uniquely determined. - If enabled, the algorithm is slightly slower. - Rank deficiencies of 1 occur frequently in periodic settings but higher ones are rare. - - Returns: - L: lower triangular matrix with ones on the diagonal - U: upper triangular matrix - """ - row, col = np.indices(b.staticshape(matrix)[1:-1]) - is_lower = np.expand_dims(row > col, -1) - is_upper = np.expand_dims(row < col, -1) - is_diagonal = np.expand_dims(row == col, -1) - # # --- Initialize U as the diagonal of A, then compute off-diagonal of L --- - lower = matrix / b.expand_dims(b.get_diagonal(matrix), 1) # Since U=diag(A), L can be computed by a simple division - lu = matrix * is_diagonal + lower * is_lower # combine lower + diag(A) + 0 - # --- Fixed-point iterations --- - for sweep in range(iterations): - diag = b.expand_dims(b.get_diagonal(lu), 1) # should never contain 0 - sum_l_u = b.einsum('bikc,bkjc->bijc', lu * is_lower, lu * is_upper) - l = (matrix - sum_l_u) / diag if not safe else b.divide_no_nan(matrix - sum_l_u, diag) - lu = b.where(is_lower, l, matrix - sum_l_u) - # --- Assemble L=lower+unit_diagonal and U. --- - return lu * is_lower + is_diagonal, lu * ~is_lower - - -def incomplete_lu_coo(b: 'Backend', indices, values, shape: Tuple[int, int], iterations: int, safe: bool): - """ - Based on *Parallel Approximate LU Factorizations for Sparse Matrices* by T.K. Huckle, https://www5.in.tum.de/persons/huckle/it_ilu.pdf. - - Every matrix in the batch must explicitly store the full diagonal. - There should not be any zeros on the diagonal, else the LU initialization fails. - - Args: - b: `Backend` - indices: Row & column indices of stored entries as `numpy.ndarray` of shape (batch_size, nnz, 2). - values: Backend-compatible values tensor of shape (batch_size, nnz, channels) - shape: Dense shape of matrix - iterations: Number of fixed-point iterations to perform. - safe: Avoid NaN when the rank deficiency of `matrix` is 2 or higher. - For a rank deficiency of 1, the fixed-point algorithm will still converge without NaNs. - If enabled, the algorithm is slightly slower. - Rank deficiencies of 1 occur frequently in periodic settings but higher ones are rare. - - Returns: - L: tuple `(indices, values)` where `indices` is a NumPy array and values is backend-specific - U: tuple `(indices, values)` where `indices` is a NumPy array and values is backend-specific - """ - assert isinstance(indices, np.ndarray), "incomplete_lu_coo indices must be a NumPy array" - row, col = indices[..., 0], indices[..., 1] - batch_size, nnz, channels = b.staticshape(values) - rows, cols = shape - assert rows == cols, "incomplete_lu_coo only implemented for square matrices" - is_lower = np.expand_dims(row > col, -1) - diagonal_indices = np.expand_dims(get_lower_diagonal_indices(row, col, shape), -1) # indices of corresponding values that lie on the diagonal - is_diagonal = np.expand_dims(row == col, -1) - mm_above, mm_left, mm_is_valid = strict_lu_mm_pattern_coo_batched(row, col, rows, cols) - mm_above = np.expand_dims(mm_above, -1) - mm_left = np.expand_dims(mm_left, -1) - mm_is_valid = np.expand_dims(mm_is_valid, -1) - # --- Initialize U as the diagonal of A, then compute off-diagonal of L --- - lower = values / b.batched_gather_nd(values, diagonal_indices) # Since U=diag(A), L can be computed by a simple division - lu = values * is_diagonal + lower * is_lower # combine lower + diag(A) + 0 - # --- Fixed-point iterations --- - for sweep in range(iterations): - diag = b.batched_gather_nd(lu, diagonal_indices) # should never contain 0 - sum_l_u = b.einsum('bnkc,bnkc->bnc', b.batched_gather_nd(lu, mm_above) * mm_is_valid, b.batched_gather_nd(lu, mm_left)) - l = (values - sum_l_u) / diag if not safe else b.divide_no_nan(values - sum_l_u, diag) - lu = b.where(is_lower, l, values - sum_l_u) - # --- Assemble L=lower+unit_diagonal and U. If nnz varies along batch, keep the full sparsity pattern --- - u_values = b.where(~is_lower, lu, 0) - belongs_to_lower = (is_lower | is_diagonal) - l_values = b.where(is_lower, lu, b.cast(is_diagonal, b.dtype(values))) - u_mask_indices_b, u_mask_indices = np.where(~is_lower[..., 0]) - _, u_nnz = np.unique(u_mask_indices_b, return_counts=True) - if np.all(u_nnz == u_nnz[0]): # nnz for lower/upper does not vary along batch - u_mask_indices = np.reshape(u_mask_indices, (batch_size, -1)) - u_values = b.batched_gather_nd(u_values, np.expand_dims(u_mask_indices, -1)) - u_indices = np.stack([indices[b, u_mask_indices[b], :] for b in range(batch_size)]) - _, l_mask_indices = np.where(belongs_to_lower[..., 0]) - l_mask_indices = np.reshape(l_mask_indices, (batch_size, -1)) - l_values = b.batched_gather_nd(l_values, np.expand_dims(l_mask_indices, -1)) - l_indices = np.stack([indices[b, l_mask_indices[b], :] for b in range(batch_size)]) - return (l_indices, l_values), (u_indices, u_values) - else: # Keep all indices since the number in lower/upper varies along the batch - return (indices, l_values), (indices, u_values) - - -def strict_lu_mm_pattern_coo(row: np.ndarray, col: np.ndarray, rows, cols): - """ - For each stored entry e at (row, col), finds the matching entries directly above and directly left of e, such that left.col == above.row. - - This is useful for multiplying a lower triangular and upper triangular matrix given the sparsity pattern but excluding the diagonals. - The matrix multiplication then is given by - >>> einsum('nk,nk->n', stored_lower[above_entries] * is_valid_entry, stored_upper[left_entries]) - - Returns: - above_entries: (max_num, nnz) Stored indices of matched elements above any entry. - left_entries: (max_num, nnz) Stored indices of matched elements to the left of any entry. - is_valid_entry: (max_num, nnz) Mask of valid indices. Invalid indices are undefined but lie inside the array to prevent index errors. - """ - entries, = row.shape - # --- Compress rows and cols --- - lower_entries_by_row = compress_strict_lower_triangular_rows(row, col, rows) # entry indices by row, -1 for non-existent entries - upper_entries_by_col = compress_strict_lower_triangular_rows(col, row, cols) - # --- Find above and left entries --- - same_row_entries = lower_entries_by_row[:, row] # (row entries, entries). Currently, contains valid values for invalid references - left = np.where(col[same_row_entries] < col, same_row_entries, -1) # (max_left, nnz) all entries with col_e==col, row_e < row - same_col_entries = upper_entries_by_col[:, col] - above = np.where(row[same_col_entries] < row, same_col_entries, -1) # (max_above, nnz) - # --- for each entry, match left and above where left.col == above.row --- - half_density = max(len(lower_entries_by_row), len(upper_entries_by_col)) - above_entries = np.zeros([entries, half_density], dtype=int) - left_entries = np.zeros([entries, half_density], dtype=int) - is_valid_entry = np.zeros([entries, half_density]) - k = np.zeros(entries, dtype=int) - for r in range(len(above)): - for c in range(len(left)): - match = (col[left[c]] == row[above[r]]) & (above[r] != -1) - where_match = np.where(match) - k_where_match = k[where_match] - above_entries[where_match, k_where_match] = above[r][where_match] - left_entries[where_match, k_where_match] = left[c][where_match] - is_valid_entry[where_match, k_where_match] = 1 - k += match - return above_entries, left_entries, is_valid_entry - - -def compress_strict_lower_triangular_rows(row, col, rows): - is_lower = row > col - below_diagonal = np.where(is_lower) - row_lower = row[below_diagonal] - num_in_row = get_index_in_row(row_lower, col[below_diagonal]) - lower_entries_by_row = np.zeros((np.max(num_in_row)+1, rows), dtype=row.dtype) - 1 - lower_entries_by_row[num_in_row, row_lower] = below_diagonal - return lower_entries_by_row - - -def strict_lu_mm_pattern_coo_batched(row, col, rows, cols): - results = [strict_lu_mm_pattern_coo(row[b], col[b], rows, cols) for b in range(row.shape[0])] - result = [np.stack(v) for v in zip(*results)] - return result - - -def get_index_in_row(row: np.ndarray, col: np.ndarray): - """ How many entries are to the left of a given entry but in the same row, i.e. the how manieth index this is per row. """ - perm = np.argsort(col) - compressed_col_index = cumcount(row[perm])[inv_perm(perm)] - return compressed_col_index - - -def inv_perm(perm): - """ Returns the permutation necessary to undo a sort given the argsort array. """ - u = np.empty(perm.size, dtype=np.int64) - u[perm] = np.arange(perm.size) - return u - - -def cumcount(a): - """ Based on https://stackoverflow.com/questions/40602269/how-to-use-numpy-to-get-the-cumulative-count-by-unique-values-in-linear-time """ - def dfill(a): - """ Returns the positions where the array changes and repeats that index position until the next change. """ - b = np.concatenate([[0], np.where(a[:-1] != a[1:])[0] + 1, [a.size]]) - return np.arange(a.size)[b[:-1]].repeat(np.diff(b)) - perm = a.argsort(kind='mergesort') - inv = inv_perm(perm) - return (np.arange(a.size) - dfill(a[perm]))[inv] - - -def cumcount2(l): # slightly slower than cumcount - a = np.unique(l, return_counts=True)[1] - idx = a.cumsum() - id_arr = np.ones(idx[-1], dtype=int) - id_arr[0] = 0 - id_arr[idx[:-1]] = -a[:-1] + 1 - rng = id_arr.cumsum() - return rng[inv_perm(np.argsort(l))] - - -def get_transposed_indices(row, col, shape): - linear = np.ravel_multi_index((row, col), shape) - linear_transposed = np.ravel_multi_index((col, row), shape) - has_transpose = np.stack([np.isin(linear[b], linear_transposed[b]) for b in range(row.shape[0])]) - perm = np.argsort(linear) - transposed = np.stack([np.searchsorted(linear[b], linear_transposed[b], sorter=perm[b]) for b in range(row.shape[0])]) - transposed = np.minimum(transposed, len(row) - 1) - return has_transpose, transposed - - -def get_lower_diagonal_indices(row, col, shape): - linear = np.ravel_multi_index((row, col), shape) - j = np.minimum(row, col) - diagonal_indices = np.ravel_multi_index((j, j), shape) - perm = np.argsort(linear) - result = [perm[b, np.searchsorted(linear[b], diagonal_indices[b], sorter=perm[b])] for b in range(row.shape[0])] - assert np.all([np.isin(diagonal_indices[b], linear[b]) for b in range(row.shape[0])]), "All diagonal elements must be present in sparse matrix." - return np.stack(result) diff --git a/tests/commit/math/test__optimize.py b/tests/commit/math/test__optimize.py index fc8be0bff..7c045aa04 100644 --- a/tests/commit/math/test__optimize.py +++ b/tests/commit/math/test__optimize.py @@ -50,7 +50,7 @@ def test_solve_linear_matrix(self): with backend: y = math.ones(spatial(x=3)) x0 = math.zeros(spatial(x=3)) - for method in ['CG', 'CG-adaptive', 'auto']: + for method in ['CG', 'CG-adaptive', 'biCG-stab(1)']: solve = math.Solve(method, 0, 1e-3, x0=x0, max_iterations=100) x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) math.assert_close(x, [-1.5, -2, -1.5], abs_tolerance=1e-3, msg=backend) From 11ee52988970115e6058a9b7fd97e107adbe6639 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 15 Feb 2023 14:39:42 +0100 Subject: [PATCH 144/170] [math] Undocument BOUNDARY in favour of ZERO_GRADIENT --- phi/math/extrapolation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phi/math/extrapolation.py b/phi/math/extrapolation.py index 139424ff4..ddbb2a795 100644 --- a/phi/math/extrapolation.py +++ b/phi/math/extrapolation.py @@ -965,8 +965,9 @@ def __rtruediv__(self, other): """ Extrapolates with the constant value 1 (Dirichlet boundary condition). """ PERIODIC = _PeriodicExtrapolation(1) """ Extends a grid by tiling it (Periodic boundary condition). """ -ZERO_GRADIENT = BOUNDARY = _BoundaryExtrapolation(2) +ZERO_GRADIENT = _BoundaryExtrapolation(2) """ Extends a grid with its edge values (Neumann boundary condition). The value of a point lying outside the grid is determined by the closest grid value(s). """ +BOUNDARY = ZERO_GRADIENT SYMMETRIC = _SymmetricExtrapolation(3) """ Extends a grid by tiling it. Every other copy of the grid is flipped. Edge values occur twice per seam. """ ANTISYMMETRIC = _AntiSymmetricExtrapolation(3) From eac1439056e785a77eb2930b8ca839bfd5f171fb Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 15 Feb 2023 16:01:38 +0100 Subject: [PATCH 145/170] [math] Rename Backend.matmul --- phi/jax/_jax_backend.py | 2 +- phi/math/backend/_backend.py | 6 +++--- phi/math/backend/_numpy_backend.py | 2 +- phi/tf/_tf_backend.py | 4 ++-- phi/torch/_torch_backend.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/phi/jax/_jax_backend.py b/phi/jax/_jax_backend.py index dc546fc56..af93c9b87 100644 --- a/phi/jax/_jax_backend.py +++ b/phi/jax/_jax_backend.py @@ -312,7 +312,7 @@ def mul(self, a, b): # else: return Backend.mul(self, a, b) - def matmul(self, A, b): + def mul_matrix_batched_vector(self, A, b): from jax.experimental.sparse import BCOO if isinstance(A, BCOO): return(A @ b.T).T diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index c732029f4..3c7246379 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -496,7 +496,7 @@ def tensordot(self, a, a_axes: tuple or list, b, b_axes: tuple or list): """ Multiply-sum-reduce a_axes of a with b_axes of b. """ raise NotImplementedError(self) - def matmul(self, A, b): + def mul_matrix_batched_vector(self, A, b): raise NotImplementedError(self) def einsum(self, equation, *tensors): @@ -1153,11 +1153,11 @@ def linear(self, lin, vector): for lin_i in lin: lin_shape = self.staticshape(lin_i) assert len(lin_shape) == 2 - return self.stack([self.matmul(m, v) for m, v in zip(lin, self.unstack(vector))]) + return self.stack([self.mul_matrix_batched_vector(m, v) for m, v in zip(lin, self.unstack(vector))]) else: lin_shape = self.staticshape(lin) assert len(lin_shape) == 2, f"A must be a matrix but got shape {lin_shape}" - return self.matmul(lin, vector) + return self.mul_matrix_batched_vector(lin, vector) def matrix_solve_least_squares(self, matrix: TensorType, rhs: TensorType) -> Tuple[TensorType, TensorType, TensorType, TensorType]: """ diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index 61cdf6f67..e704bc19a 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -210,7 +210,7 @@ def mul(self, a, b): else: return Backend.mul(self, a, b) - def matmul(self, A, b): + def mul_matrix_batched_vector(self, A, b): return np.stack([A.dot(b[i]) for i in range(b.shape[0])]) def get_diagonal(self, matrices, offset=0): diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index 40acb491c..e20b5f4aa 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -268,7 +268,7 @@ def tensordot(self, a, a_axes: tuple or list, b, b_axes: tuple or list): a, b = self.auto_cast(a, b, bool_to_int=True) return tf.tensordot(a, b, (a_axes, b_axes)) - def matmul(self, A, b): + def mul_matrix_batched_vector(self, A, b): with self._device_for(A, b): if isinstance(A, tf.SparseTensor): result_T = tf.sparse.sparse_dense_matmul(A, tf.transpose(b)) # result shape contains unknown size @@ -276,7 +276,7 @@ def matmul(self, A, b): result.set_shape(tf.TensorShape([b.shape[0], A.shape[0]])) return result else: - return tf.matmul(A, b) + return tf.transpose(tf.matmul(A, b, transpose_b=True)) def einsum(self, equation, *tensors): with self._device_for(*tensors): diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index 0e8ec0333..bc626a8f4 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -387,7 +387,7 @@ def tensordot(self, a, a_axes: tuple or list, b, b_axes: tuple or list): a, b = self.auto_cast(a, b) return torch.tensordot(a, b, (a_axes, b_axes)) - def matmul(self, A, b): + def mul_matrix_batched_vector(self, A, b): A, b = self.auto_cast(A, b) if isinstance(A, torch.Tensor) and A.is_sparse: result = torch.sparse.mm(A, torch.transpose(b, 0, 1)) From 2769a6bcd46e6be373367a3ae1ef6ebd022a9cc0 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 15 Feb 2023 17:34:30 +0100 Subject: [PATCH 146/170] [math] Dense linear solves --- phi/math/_optimize.py | 19 ++++++++++++++----- phi/math/_sparse.py | 4 +++- phi/math/backend/_numpy_backend.py | 2 +- phi/torch/_torch_backend.py | 14 +++++++++----- tests/commit/math/test__optimize.py | 17 +++++++++++++++++ 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/phi/math/_optimize.py b/phi/math/_optimize.py index 56c5915d2..e96f8ac11 100644 --- a/phi/math/_optimize.py +++ b/phi/math/_optimize.py @@ -423,7 +423,7 @@ def min_func(x): return minimize(min_func, min_solve) -def solve_linear(f: Callable[[X], Y], +def solve_linear(f: Callable[[X], Y] or Tensor, y: Y, solve: Solve[X, Y], *f_args, @@ -454,8 +454,13 @@ def solve_linear(f: Callable[[X], Y], `solve_nonlinear()`, `jit_compile_linear()`. Args: - f: Linear function with `Tensor` or `PhiTreeNode` first parameter and return value. - `f` can have additional arguments. + f: One of the following: + + * Linear function with `Tensor` or `PhiTreeNode` first parameter and return value. `f` can have additional auxiliary arguments and return auxiliary values. + * Dense matrix (`Tensor` with at least one dual dimension) + * Sparse matrix (Sparse `Tensor` with at least one dual dimension) + * Native tensor (not yet supported) + y: Desired output of `f(x)` as `Tensor` or `PhiTreeNode`. solve: `Solve` object specifying optimization method, parameters and initial guess for `x`. *f_args: Positional arguments to be passed to `f` after `solve.x0`. These arguments will not be solved for. @@ -482,8 +487,12 @@ def solve_linear(f: Callable[[X], Y], backend = choose_backend_t(*y_tensors, *x0_tensors) prefer_explicit = backend.supports(Backend.sparse_coo_tensor) or backend.supports(Backend.csr_matrix) or grad_for_f - if isinstance(f, LinearFunction) and prefer_explicit: # Matrix solve - matrix, bias = f.sparse_matrix_and_bias(solve.x0, *f_args, **f_kwargs) + if isinstance(f, Tensor) or (isinstance(f, LinearFunction) and prefer_explicit): # Matrix solve + if isinstance(f, LinearFunction): + matrix, bias = f.sparse_matrix_and_bias(solve.x0, *f_args, **f_kwargs) + else: + matrix = f + bias = 0 def _matrix_solve_forward(y, solve: Solve, matrix: Tensor, is_backprop=False): backend_matrix = native_matrix(matrix) diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index fed21846a..34c294174 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -589,10 +589,12 @@ def native_matrix(value: Tensor): else: return value.default_backend.csc_matrix(pointers[0], indices[0], values[0, :, 0], shape) else: + if batch(value): + raise NotImplementedError v = pack_dims(value, rows, channel('_row')) v = pack_dims(v, cols, channel('_col')) from ._ops import reshaped_native - return reshaped_native(v, [batch, '_row', '_col']) + return reshaped_native(v, ['_row', '_col']) def factor_ilu(matrix: Tensor, iterations=None, safe=False): diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index e704bc19a..ef770e775 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -472,7 +472,7 @@ def count_callback(x_n): # called after each step, not with x0 converged = [] diverged = [] for b in range(batch_size): - lin_b = lin[min(b, len(lin)-1)] if isinstance(lin, (tuple, list, np.ndarray)) else lin + lin_b = lin[min(b, len(lin)-1)] if isinstance(lin, (tuple, list)) or (isinstance(lin, np.ndarray) and len(lin.shape) > 2) else lin x, ret_val = scipy_function(lin_b, y[b], x0=x0[b], tol=rtol[b], atol=atol[b], maxiter=max_iter[b], callback=count_callback) # ret_val: 0=success, >0=not converged, <0=error xs.append(x) diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index bc626a8f4..d28c899c4 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -389,10 +389,11 @@ def tensordot(self, a, a_axes: tuple or list, b, b_axes: tuple or list): def mul_matrix_batched_vector(self, A, b): A, b = self.auto_cast(A, b) - if isinstance(A, torch.Tensor) and A.is_sparse: + if isinstance(A, torch.Tensor) and (A.is_sparse or A.is_sparse_csr): result = torch.sparse.mm(A, torch.transpose(b, 0, 1)) return torch.transpose(result, 0, 1) - raise NotImplementedError(type(A), type(b)) + else: + return torch.transpose(torch.matmul(A, torch.transpose(b, -1, -2)), -1, -2) def get_diagonal(self, matrices, offset=0): return torch.transpose(torch.diagonal(matrices, offset=offset, dim1=1, dim2=2), 1, 2) @@ -706,7 +707,7 @@ def conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj: bool) -> Sol if callable(lin) or trj: assert self.is_available(y), "Tracing conjugate_gradient with linear operator is not yet supported." return Backend.conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj) - assert isinstance(lin, torch.Tensor) and (lin.is_sparse or lin.is_sparse_csr), "Batched matrices are not yet supported" + assert isinstance(lin, torch.Tensor), "Batched matrices are not yet supported" y = self.to_float(y) x0 = self.copy(self.to_float(x0)) rtol = self.as_tensor(rtol) @@ -1122,8 +1123,11 @@ def torch_sparse_cg_adaptive(lin, y, x0, rtol, atol, max_iter): return x, residual, iterations, function_evaluations, converged, diverged -def sparse_matmul(matrix: torch.sparse.Tensor, b: torch.Tensor): - return torch.transpose(torch.sparse.mm(matrix, torch.transpose(b, 0, 1)), 0, 1) +def sparse_matmul(matrix: torch.Tensor, b: torch.Tensor): + if matrix.is_sparse or matrix.is_sparse_csr: + return torch.transpose(torch.sparse.mm(matrix, torch.transpose(b, 0, 1)), 0, 1) + else: + return torch.transpose(torch.matmul(matrix, torch.transpose(b, 0, 1)), 0, 1) def divide_no_nan(x: torch.Tensor, y: torch.Tensor): diff --git a/tests/commit/math/test__optimize.py b/tests/commit/math/test__optimize.py index 7c045aa04..7dcda09fa 100644 --- a/tests/commit/math/test__optimize.py +++ b/tests/commit/math/test__optimize.py @@ -166,3 +166,20 @@ def loss(x): with backend: result = math.minimize(loss, Solve('GD', 0, 1e-5, x0=3, max_iterations=20)) math.assert_close(result, 0, abs_tolerance=1e-5, msg=backend.name) + + def test_solve_dense(self): + @math.jit_compile_linear + def f(x): + return math.laplace(x, padding=extrapolation.ZERO) + + for backend in BACKENDS: + with backend: + matrix, bias = math.matrix_from_function(f, math.ones(spatial(x=3))) + dense_matrix = math.dense(matrix) + + @math.jit_compile + def solve(y): + return math.solve_linear(dense_matrix, y, Solve('CG', x0=y * 0)) + + x = solve(math.ones(spatial(x=3))) + math.assert_close([-1.5, -2, -1.5], x) From ba83b8f828744910d03908c6506e3e5e358a764b Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 17 Feb 2023 11:47:34 +0100 Subject: [PATCH 147/170] [math] Add Backend.solve_triangular_dense() --- phi/jax/_jax_backend.py | 5 +++++ phi/math/backend/_backend.py | 14 ++++++++++++++ phi/math/backend/_numpy_backend.py | 8 ++++++++ phi/tf/_tf_backend.py | 9 +++++++++ phi/torch/_torch_backend.py | 6 ++++++ tests/commit/math/backend/test__backend.py | 13 ++++++++++++- 6 files changed, 54 insertions(+), 1 deletion(-) diff --git a/phi/jax/_jax_backend.py b/phi/jax/_jax_backend.py index af93c9b87..6c940abc4 100644 --- a/phi/jax/_jax_backend.py +++ b/phi/jax/_jax_backend.py @@ -463,6 +463,11 @@ def matrix_solve_least_squares(self, matrix: TensorType, rhs: TensorType) -> Tup solution, residuals, rank, singular_values = lstsq_batched(matrix, rhs) return solution, residuals, rank, singular_values + def solve_triangular_dense(self, matrix, rhs, lower: bool, unit_diagonal: bool): + matrix, rhs = self.auto_cast(matrix, rhs, int_to_float=True, bool_to_int=True) + x = jax.lax.linalg.triangular_solve(matrix, rhs, lower=lower, unit_diagonal=unit_diagonal, left_side=True) + return x + def sparse_coo_tensor(self, indices: tuple or list, values, shape: tuple): return BCOO((values, indices), shape=shape) diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 3c7246379..ced2657ee 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -1173,6 +1173,20 @@ def matrix_solve_least_squares(self, matrix: TensorType, rhs: TensorType) -> Tup """ raise NotImplementedError(self) + def solve_triangular_dense(self, matrix, rhs, lower: bool, unit_diagonal: bool): + """ + + Args: + matrix: (batch_size, rows, cols) + rhs: (batch_size, cols) + lower: + unit_diagonal: + + Returns: + (batch_size, cols) + """ + raise NotImplementedError(self) + def stop_gradient(self, value): raise NotImplementedError(self) diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index ef770e775..63c4cec07 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -491,3 +491,11 @@ def matrix_solve_least_squares(self, matrix: TensorType, rhs: TensorType) -> Ten rank.append(rnk_b) singular_values.append(s_b) return np.stack(solution), np.stack(residuals), np.stack(rank), np.stack(singular_values) + + def solve_triangular_dense(self, matrix, rhs, lower: bool, unit_diagonal: bool): + batch_size, rows, cols = matrix.shape + result = [] + for b in range(batch_size): + x = scipy.linalg.solve_triangular(matrix[b, :, :], rhs[b, :], lower=lower, unit_diagonal=unit_diagonal) + result.append(x) + return np.stack(result) diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index e20b5f4aa..c00d4a856 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -712,6 +712,15 @@ def matrix_solve_least_squares(self, matrix: TensorType, rhs: TensorType) -> Tup solution = tf.linalg.lstsq(matrix, rhs) return solution, None, None, None + def solve_triangular_dense(self, matrix, rhs, lower: bool, unit_diagonal: bool): + matrix, rhs = self.auto_cast(matrix, rhs, int_to_float=True, bool_to_int=True) + rhs = self.expand_dims(rhs, -1) + if unit_diagonal: + diag = np.diag(np.ones((self.staticshape(matrix)[-1],))) + matrix = self.where(diag, diag, matrix) + result = tf.linalg.triangular_solve(matrix, rhs, lower=lower) + return result[..., 0] + def get_diagonal(self, matrices, offset=0): with self._device_for(matrices): matrices = tf.transpose(matrices, [0, 3, 1, 2]) diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index d28c899c4..b7acf6242 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -735,6 +735,12 @@ def matrix_solve_least_squares(self, matrix: TensorType, rhs: TensorType) -> Tup solution, residuals, rank, singular_values = torch.linalg.lstsq(matrix, rhs) return solution, residuals, rank, singular_values + def solve_triangular_dense(self, matrix, rhs, lower: bool, unit_diagonal: bool): + matrix, rhs = self.auto_cast(matrix, rhs, int_to_float=True, bool_to_int=True) + rhs = self.expand_dims(rhs, -1) + x = torch.linalg.solve_triangular(matrix, rhs, upper=not lower, unitriangular=unit_diagonal) + return x[..., 0] + def _prepare_graph_inputs(self, args: tuple, wrt: tuple or list): args = [self.as_tensor(arg, True) if i in wrt else arg for i, arg in enumerate(args)] args = [self.to_float(arg) if self.dtype(arg).kind == int else arg for arg in args] diff --git a/tests/commit/math/backend/test__backend.py b/tests/commit/math/backend/test__backend.py index c9c47771b..1cc6725c0 100644 --- a/tests/commit/math/backend/test__backend.py +++ b/tests/commit/math/backend/test__backend.py @@ -1,3 +1,4 @@ +from typing import Tuple from unittest import TestCase import numpy @@ -5,7 +6,7 @@ import phi from phi.math.backend import ComputeDevice, convert, Backend -BACKENDS = phi.detect_backends() +BACKENDS: Tuple[Backend] = phi.detect_backends() class TestBackends(TestCase): @@ -62,3 +63,13 @@ def test_get_diagonal(self): numpy.testing.assert_equal([[[2]]], d1) d1 = backend.numpy(backend.get_diagonal(t, offset=-1)) numpy.testing.assert_equal([[[0]]], d1) + + def test_solve_triangular_dense(self): + for backend in BACKENDS: + with backend: + rhs = backend.as_tensor([[1, 7, 3]]) + matrix = backend.as_tensor([[[-1, 1, 0], [0, 2, 2], [0, 1, 1]]]) + x = backend.numpy(backend.solve_triangular_dense(matrix, rhs, lower=False, unit_diagonal=True)[0, :]) + numpy.testing.assert_almost_equal([0, 1, 3], x, err_msg=backend.name) + x = backend.numpy(backend.solve_triangular_dense(matrix, rhs, lower=False, unit_diagonal=False)[0, :]) + numpy.testing.assert_almost_equal([-.5, .5, 3], x, err_msg=backend.name) From 5e63980aa0f5442f88196d68eab7ee2bd68d4cf3 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 17 Feb 2023 20:07:24 +0100 Subject: [PATCH 148/170] [backend] Pre-conditioner for cg() --- phi/math/backend/_linalg.py | 20 ++++++++++++------- tests/commit/field/test__field_math.py | 27 +++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/phi/math/backend/_linalg.py b/phi/math/backend/_linalg.py index 426bec76c..d2916eae6 100644 --- a/phi/math/backend/_linalg.py +++ b/phi/math/backend/_linalg.py @@ -1,15 +1,19 @@ from functools import partial -from typing import Tuple +from typing import Tuple, Callable import numpy as np from ._backend import Backend, SolveResult, List, DType, spatial_derivative_evaluation -def cg(b, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: +def identity(x): + return x + + +def cg(b: Backend, lin, y, x0, rtol, atol, max_iter, trj: bool, pre: Callable = identity) -> SolveResult or List[SolveResult]: """ Based on "An Introduction to the Conjugate Gradient Method Without the Agonizing Pain" by Jonathan Richard Shewchuk - symbols: dx=d, dy=q, step_size=alpha, residual_squared=delta, residual=r, y=b + symbols: dx=d, dy=q, step_size=alpha, residual_squared=delta, residual=r, y=b, pre=M """ method = f"Φ-Flow CG ({b.name})" y = b.to_float(y) @@ -17,10 +21,11 @@ def cg(b, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[So batch_size = b.staticshape(y)[0] tolerance_sq = b.maximum(rtol ** 2 * b.sum(y ** 2, -1), atol ** 2) x = x0 - dx = residual = y - b.linear(lin, x) + residual = y - b.linear(lin, x) + dx = pre(residual) iterations = b.zeros([batch_size], DType(int, 32)) function_evaluations = b.ones([batch_size], DType(int, 32)) - residual_squared = rsq0 = b.sum(residual ** 2, -1, keepdims=True) + residual_squared = rsq0 = b.sum(residual * dx, -1, keepdims=True) diverged = b.any(~b.isfinite(x), axis=(1,)) converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) trajectory = [SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")] if trj else None @@ -37,9 +42,10 @@ def cg_loop_body(continue_, it_counter, x, dx, residual_squared, residual, itera step_size *= b.expand_dims(b.to_float(continue_1), -1) # this is not really necessary but ensures batch-independence x += step_size * dx residual = residual - step_size * dy # in-place subtraction affects convergence + s = pre(residual) residual_squared_old = residual_squared - residual_squared = b.sum(residual ** 2, -1, keepdims=True) - dx = residual + b.divide_no_nan(residual_squared, residual_squared_old) * dx + residual_squared = b.sum(residual * s, -1, keepdims=True) + dx = s + b.divide_no_nan(residual_squared, residual_squared_old) * dx diverged = b.any(residual_squared / rsq0 > 1e5, axis=(1,)) & (iterations >= 8) converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) if trajectory is not None: diff --git a/tests/commit/field/test__field_math.py b/tests/commit/field/test__field_math.py index e30c7fc7e..5101c5635 100644 --- a/tests/commit/field/test__field_math.py +++ b/tests/commit/field/test__field_math.py @@ -5,11 +5,13 @@ import phi from phi import math, geom -from phi.field import StaggeredGrid, CenteredGrid, PointCloud +from phi.field import StaggeredGrid, CenteredGrid, PointCloud, Noise +from phi.field._field_math import _lhs_for_implicit_scheme, _ex_map_f, pad, shift, stack from phi.geom import Box, Sphere from phi import field from phi.math import extrapolation, instance, channel, spatial, batch from phi.math.backend import Backend +from phi.math.extrapolation import combine_by_direction, REFLECT, SYMMETRIC BACKENDS = phi.detect_backends() @@ -242,3 +244,26 @@ def test_mask(self): self.assertEqual(2, mask.spatial_rank) mask = field.mask(CenteredGrid(0, x=4, y=3)) self.assertEqual(2, mask.spatial_rank) + + def test_implicit_laplace_solve(self): + grid = CenteredGrid(Noise(), x=5, y=5) + axes_names = grid.shape.only(spatial).names + extrap_map = {} + extrap_map_rhs = {} + values, needed_shifts = [3 / 44, 12 / 11, -51 / 22, 12 / 11, 3 / 44], (-2, -1, 0, 1, 2) + extrap_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) + values_rhs, needed_shifts_rhs = [2 / 11, 1, 2 / 11], (-1, 0, 1) + extrap_map_rhs['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) + base_widths = (abs(min(needed_shifts)), max(needed_shifts)) + grid.with_extrapolation(extrapolation.map(_ex_map_f(extrap_map), grid.extrapolation)) + padded_components = [pad(grid, {dim: base_widths}) for dim in axes_names] + shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, axes_names)] + result_components = [sum([value * shift_ for value, shift_ in zip(values, shifted_component)]) / grid.dx.vector[dim] ** 2 for shifted_component, dim in zip(shifted_components, axes_names)] + result_components = stack(result_components, channel('laplacian')) + result_components.with_values(result_components.values._cache()) + result_components = result_components.with_extrapolation(extrapolation.map(_ex_map_f(extrap_map_rhs), grid.extrapolation)) + matrix, _ = math.matrix_from_function(_lhs_for_implicit_scheme, result_components, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=channel('laplacian')) + direct_result = _lhs_for_implicit_scheme(result_components, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=channel('laplacian')) + matrix_result = matrix @ result_components.values + math.assert_close(matrix_result, direct_result) + From ad253f79621432c1810d3c649ff635f8b7296986 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 20 Feb 2023 17:12:44 +0100 Subject: [PATCH 149/170] [math] Tensor bit-shift operations --- phi/math/_tensors.py | 22 ++++++++++++++++++++++ phi/math/backend/_backend.py | 8 ++++++++ tests/commit/math/test__tensors.py | 12 ++++++++++++ 3 files changed, 42 insertions(+) diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index c5a64d00c..be2f5581b 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -162,6 +162,16 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): # NumPy interface return self._op2(inputs[1], lambda x, y: x <= y, lambda x, y: choose_backend(x, y).greater_or_equal(y, x), 'less_equal', '<=') else: return self._op2(inputs[0], lambda x, y: y <= x, lambda x, y: choose_backend(x, y).greater_or_equal(x, y), 'r_less_equal', '<=') + if ufunc.__name__ == 'left_shift': + if inputs[0] is self: + return self._op2(inputs[1], lambda x, y: x << y, lambda x, y: choose_backend(x, y).shift_bits_left(x, y), 'left_shift', '<<') + else: + return self._op2(inputs[0], lambda x, y: y << x, lambda x, y: choose_backend(x, y).shift_bits_left(y, x), 'r_left_shift', '<<') + if ufunc.__name__ == 'right_shift': + if inputs[0] is self: + return self._op2(inputs[1], lambda x, y: x >> y, lambda x, y: choose_backend(x, y).shift_bits_right(x, y), 'right_shift', '>>') + else: + return self._op2(inputs[0], lambda x, y: y >> x, lambda x, y: choose_backend(x, y).shift_bits_right(y, x), 'r_right_shift', '>>') raise NotImplementedError(f"NumPy function '{ufunc.__name__}' is not compatible with Φ-Flow tensors.") @property @@ -651,6 +661,18 @@ def __gt__(self, other): def __ge__(self, other): return self._op2(other, lambda x, y: x >= y, lambda x, y: choose_backend(x, y).greater_or_equal(x, y), 'ge', '>=') + def __lshift__(self, other): + return self._op2(other, lambda x, y: x << y, lambda x, y: choose_backend(x, y).shift_bits_left(x, y), 'lshift', '<<') + + def __rlshift__(self, other): + return self._op2(other, lambda y, x: x << y, lambda y, x: choose_backend(x, y).shift_bits_left(x, y), 'lshift', '<<') + + def __rshift__(self, other): + return self._op2(other, lambda x, y: x >> y, lambda x, y: choose_backend(x, y).shift_bits_right(x, y), 'rshift', '>>') + + def __rrshift__(self, other): + return self._op2(other, lambda y, x: x >> y, lambda y, x: choose_backend(x, y).shift_bits_right(x, y), 'rshift', '>>') + def __abs__(self): return self._op1(lambda t: choose_backend(t).abs(t)) diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index ced2657ee..fd1c34bb7 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -1298,6 +1298,14 @@ def floordiv(self, a, b): a, b = self.auto_cast(a, b) return a // b + def shift_bits_left(self, a, b): + a, b = self.auto_cast(a, b) + return a << b + + def shift_bits_right(self, a, b): + a, b = self.auto_cast(a, b) + return a >> b + BACKENDS = [] """ Global list of all registered backends. Register a `Backend` by adding it to the list. """ diff --git a/tests/commit/math/test__tensors.py b/tests/commit/math/test__tensors.py index 581d8c23c..0f0f1beac 100644 --- a/tests/commit/math/test__tensors.py +++ b/tests/commit/math/test__tensors.py @@ -649,3 +649,15 @@ def test_auto_layout(self): except AssertionError: pass + def test_bit_shift(self): + ints = math.range_tensor(spatial(x=4)) + math.assert_close([0, 2, 4, 6], ints << 1) + math.assert_close(ints, (ints << 2) >> 2) + math.assert_close([1, 2, 4, 8], 1 << ints) + math.assert_close(1, (1 << ints) >> ints) + np1 = np.int32(1) + math.assert_close([0, 2, 4, 6], ints << np1) + math.assert_close(ints, (ints << np1) >> np1) + math.assert_close([1, 2, 4, 8], np1 << ints) + math.assert_close(1, (np1 << ints) >> ints) + math.assert_close([1, 0, 0, 0], np1 >> ints) From 4b8d299261f655b9695875aae9753a9b68264fe2 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 20 Feb 2023 18:06:50 +0100 Subject: [PATCH 150/170] [math] IncompatibleShapes not a ValueError --- phi/math/_shape.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 979aa079a..26a5c40e9 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -1165,12 +1165,12 @@ def __hash__(self): pass -class IncompatibleShapes(ValueError): +class IncompatibleShapes(Exception): """ Raised when the shape of a tensor does not match the other arguments. """ def __init__(self, message, *shapes: Shape): - ValueError.__init__(self, message) + Exception.__init__(self, message) self.shapes = shapes From f6d0bfc2dc01eebd129b7cb772b7c440764abb13 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 20 Feb 2023 18:07:36 +0100 Subject: [PATCH 151/170] [math] Fix shape(tuple) with incompatible entries This now returns size=None for conflicting entries --- phi/math/_shape.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/phi/math/_shape.py b/phi/math/_shape.py index 26a5c40e9..4e22a5abf 100644 --- a/phi/math/_shape.py +++ b/phi/math/_shape.py @@ -1286,13 +1286,15 @@ def shape(obj) -> Shape: return obj.shape elif isinstance(obj, (int, float, complex, bool)): return EMPTY_SHAPE - elif isinstance(obj, (tuple, list)): + elif isinstance(obj, (tuple, list)) and all(isinstance(item, (int, float, complex, bool)) for item in obj): return channel('vector') elif isinstance(obj, (Number, bool)): return EMPTY_SHAPE + elif isinstance(obj, (tuple, list)) and all(isinstance(item, PhiTreeNode) for item in obj): + return merge_shapes(*obj, allow_varying_sizes=True) elif isinstance(obj, PhiTreeNode): from phi.math._magic_ops import all_attributes - return merge_shapes(*[getattr(obj, a) for a in all_attributes(obj, assert_any=True)]) + return merge_shapes(*[getattr(obj, a) for a in all_attributes(obj, assert_any=True)], allow_varying_sizes=True) else: from .backend import choose_backend, NoBackendFound try: @@ -1527,7 +1529,7 @@ def dual(*args, **dims: int or str or tuple or list or Shape) -> Shape: raise AssertionError(f"dual() must be called either as a selector dual(Shape) or dual(Tensor) or as a constructor dual(*names, **dims). Got *args={args}, **dims={dims}") -def merge_shapes(*objs: Shape or Any, order=(batch, dual, instance, spatial, channel)): +def merge_shapes(*objs: Shape or Any, order=(batch, dual, instance, spatial, channel), allow_varying_sizes=False): """ Combines `shapes` into a single `Shape`, grouping dimensions by type. If dimensions with equal names are present in multiple shapes, their types and sizes must match. @@ -1559,18 +1561,23 @@ def merge_shapes(*objs: Shape or Any, order=(batch, dual, instance, spatial, cha if dim not in type_group: type_group = type_group._expand(dim, pos=-1) else: # check size match - if not _size_equal(dim.size, type_group.get_size(dim.name)): - raise IncompatibleShapes(f"Cannot merge shapes {shapes} because dimension '{dim.name}' exists with different sizes.", *shapes) - names1 = type_group.get_item_names(dim) - names2 = sh.get_item_names(dim) - if names1 is not None and names2 is not None and len(names1) > 1: - if names1 != names2: - if set(names1) == set(names2): - raise IncompatibleShapes(f"Inconsistent component order: '{','.join(names1)}' vs '{','.join(names2)}' in dimension '{dim.name}'. Failed to merge shapes {shapes}", *shapes) - else: - raise IncompatibleShapes(f"Cannot merge shapes {shapes} because dimension '{dim.name}' exists with different item names.", *shapes) - elif names1 is None and names2 is not None: - type_group = type_group._with_item_name(dim, tuple(names2)) + sizes_match = _size_equal(dim.size, type_group.get_size(dim.name)) + if allow_varying_sizes: + if not sizes_match: + type_group = type_group.with_dim_size(dim, None) + else: + if not sizes_match: + raise IncompatibleShapes(f"Cannot merge shapes {shapes} because dimension '{dim.name}' exists with different sizes.", *shapes) + names1 = type_group.get_item_names(dim) + names2 = sh.get_item_names(dim) + if names1 is not None and names2 is not None and len(names1) > 1: + if names1 != names2: + if set(names1) == set(names2): + raise IncompatibleShapes(f"Inconsistent component order: '{','.join(names1)}' vs '{','.join(names2)}' in dimension '{dim.name}'. Failed to merge shapes {shapes}", *shapes) + else: + raise IncompatibleShapes(f"Cannot merge shapes {shapes} because dimension '{dim.name}' exists with different item names.", *shapes) + elif names1 is None and names2 is not None: + type_group = type_group._with_item_name(dim, tuple(names2)) merged.append(type_group) return concat_shapes(*merged) From aa7dc55ffe4c19086655a7714a59d17482e3af2e Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 20 Feb 2023 18:49:36 +0100 Subject: [PATCH 152/170] [geom] Fix traced rotated geometry comparison --- phi/geom/_box.py | 9 +++++++-- phi/geom/_transform.py | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/phi/geom/_box.py b/phi/geom/_box.py index e3e798aa9..0fd11c4d8 100644 --- a/phi/geom/_box.py +++ b/phi/geom/_box.py @@ -33,7 +33,7 @@ def center(self) -> Tensor: raise NotImplementedError() def at(self, center: Tensor) -> 'BaseBox': - return Cuboid(center, self._half_size) + return Cuboid(center, self.half_size) @property def size(self) -> Tensor: @@ -233,7 +233,7 @@ def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Geometry': def __eq__(self, other): if self._lower is None and self._upper is None: - return isinstance(other, BaseBox) + return isinstance(other, Box) return isinstance(other, BaseBox)\ and set(self.shape) == set(other.shape)\ and self.size.shape.get_size('vector') == other.size.shape.get_size('vector')\ @@ -326,6 +326,8 @@ def __init__(self, def __eq__(self, other): + if self._center is None and self._half_size is None: + return isinstance(other, Cuboid) return isinstance(other, BaseBox)\ and set(self.shape) == set(other.shape)\ and math.close(self._center, other.center)\ @@ -334,6 +336,9 @@ def __eq__(self, other): def __hash__(self): return hash(self._center) + def __repr__(self): + return f"Cuboid(center={self._center}, half_size={self._half_size})" + def __getitem__(self, item): item = _keep_vector(slicing_dict(self, item)) return Cuboid(self._center[item], self._half_size[item]) diff --git a/phi/geom/_transform.py b/phi/geom/_transform.py index 7827d6e6a..a127693cf 100644 --- a/phi/geom/_transform.py +++ b/phi/geom/_transform.py @@ -20,6 +20,9 @@ def __init__(self, geometry: Geometry, angle: float or math.Tensor): def shape(self): return self._geometry.shape + def __variable_attrs__(self): + return '_geometry', '_angle' + @property def geometry(self): return self._geometry @@ -93,6 +96,9 @@ def sample_uniform(self, *shape: math.Shape) -> Tensor: def __hash__(self): return hash(self._angle) + hash(self._geometry) + def __repr__(self): + return f"rot({self._geometry}, angle={self._angle})" + def rotate(geometry: Geometry, angle: Number or Tensor) -> Geometry: """ Package-internal rotation function. Users should use Geometry.rotated() instead. """ From a75edbbfc2db0144395ce6ae55043fc63eacb0ea Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 20 Feb 2023 18:50:56 +0100 Subject: [PATCH 153/170] [math] Fix linear tracing bugs --- phi/math/_sparse.py | 2 +- phi/math/_trace.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phi/math/_sparse.py b/phi/math/_sparse.py index 34c294174..de2f48503 100644 --- a/phi/math/_sparse.py +++ b/phi/math/_sparse.py @@ -169,7 +169,7 @@ def compress(self, dims: DimFilter): def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: int or None, **kwargs) -> 'Tensor': dims = self._shape.only(dims) - assert self._dense_shape in dims, "Can only pack sparse dimensions on SparseCoordinateTensor" + assert dims in self._dense_shape, f"Can only pack sparse dimensions on SparseCoordinateTensor but got {dims} of which {dims.without(self._dense_shape)} are not sparse" assert self._indices.default_backend is NUMPY, "Can only pack NumPy indices as of yet" from ._ops import reshaped_native idx = self._indices.vector[dims.names] diff --git a/phi/math/_trace.py b/phi/math/_trace.py index bd6357a8a..bb0b61914 100644 --- a/phi/math/_trace.py +++ b/phi/math/_trace.py @@ -343,7 +343,7 @@ def tracer_to_coo(tracer: Tensor, sparsify_batch: bool, separate_independent: bo if dim in missing_dims: if not separate_independent: offset = shift_.get_size(dim, default=0) - src_idx_all.append(np.zeros(out_shape.volume, dtype=np.int32) + offset) + src_idx_all.append(np.zeros_like(src_idx[0]) + offset) else: src_idx_all.append(src_idx[out_shape.index(dim)]) src_indices.append(src_idx_all) From dcd607e951ec786869a991482761a39167ff020a Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Mon, 20 Feb 2023 18:51:52 +0100 Subject: [PATCH 154/170] [field] Avoid shadowing built-in function map --- phi/field/_field_math.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 116aa9bb2..2bf6c8b80 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -3,12 +3,12 @@ from phi import geom from phi import math -from phi.geom import Box, Geometry, Cuboid -from phi.math import Tensor, spatial, instance, tensor, channel, Shape, unstack, wrap, solve_linear, jit_compile_linear, shape, Solve +from phi.geom import Box, Geometry +from phi.math import Tensor, spatial, instance, tensor, channel, Shape, unstack, solve_linear, jit_compile_linear, shape, Solve, extrapolation from ._field import Field, SampledField, SampledFieldType, as_extrapolation from ._grid import CenteredGrid, Grid, StaggeredGrid, GridType from ._point_cloud import PointCloud -from ..math.extrapolation import Extrapolation, SYMMETRIC, REFLECT, ANTIREFLECT, ANTISYMMETRIC, combine_by_direction, map +from ..math.extrapolation import Extrapolation, SYMMETRIC, REFLECT, ANTIREFLECT, ANTISYMMETRIC, combine_by_direction def bake_extrapolation(grid: GridType) -> GridType: @@ -78,14 +78,14 @@ def laplace(field: GridType, values_rhs, needed_shifts_rhs = [2/11, 1, 2/11], (-1, 0, 1) extrap_map_rhs['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) base_widths = (abs(min(needed_shifts)), max(needed_shifts)) - field.with_extrapolation(map(_ex_map_f(extrap_map), field.extrapolation)) + field.with_extrapolation(extrapolation.map(_ex_map_f(extrap_map), field.extrapolation)) padded_components = [pad(field, {dim: base_widths}) for dim in axes_names] shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, axes_names)] result_components = [sum([value * shift_ for value, shift_ in zip(values, shifted_component)]) / field.dx.vector[dim]**2 for shifted_component, dim in zip(shifted_components, axes_names)] if implicit: result_components = stack(result_components, channel('laplacian')) result_components.with_values(result_components.values._cache()) - result_components = result_components.with_extrapolation(map(_ex_map_f(extrap_map_rhs), field.extrapolation)) + result_components = result_components.with_extrapolation(extrapolation.map(_ex_map_f(extrap_map_rhs), field.extrapolation)) implicit.x0 = result_components result_components = solve_linear(_lhs_for_implicit_scheme, result_components, solve=implicit, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=channel('laplacian')) result_components = unstack(result_components, 'laplacian') @@ -96,7 +96,7 @@ def laplace(field: GridType, assert set(channel(weights).item_names[0]) >= set(axes_names), f"the channel dim of weights must contain all laplace dims {axes_names} but only has {channel(weights).item_names}" result_components = [c * weights[ax] for c, ax in zip(result_components, axes_names)] result = sum(result_components) - result = result.with_extrapolation(map(_ex_map_f(extrap_map), field.extrapolation)) + result = result.with_extrapolation(extrapolation.map(_ex_map_f(extrap_map), field.extrapolation)) return result @@ -161,9 +161,9 @@ def spatial_gradient(field: CenteredGrid, else: raise NotImplementedError(f"implicit {order}th-order not supported") base_widths = (abs(min(needed_shifts)), max(needed_shifts)) - field.with_extrapolation(map(_ex_map_f(extrap_map), field.extrapolation)) # ToDo does this line do anything? + field.with_extrapolation(extrapolation.map(_ex_map_f(extrap_map), field.extrapolation)) # ToDo does this line do anything? if implicit: - gradient_extrapolation = map(_ex_map_f(extrap_map_rhs), gradient_extrapolation) + gradient_extrapolation = extrapolation.map(_ex_map_f(extrap_map_rhs), gradient_extrapolation) spatial_dims = field.shape.only(dims).names stack_dim = stack_dim._with_item_names((spatial_dims,)) if type == CenteredGrid: @@ -191,7 +191,6 @@ def spatial_gradient(field: CenteredGrid, result = result.with_extrapolation(gradient_extrapolation) if implicit: implicit.x0 = result - result = result result = solve_linear(_lhs_for_implicit_scheme, result, solve=implicit, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=stack_dim, staggered_output=type != CenteredGrid) if type == CenteredGrid and gradient_extrapolation == math.extrapolation.NONE: result = result.with_bounds(Box(field.bounds.lower - field.dx, field.bounds.upper + field.dx)) @@ -358,7 +357,7 @@ def divergence(field: Grid, order=2, implicit: Solve = None) -> CenteredGrid: values, needed_shifts = [-17 / 186, -63 / 62, 63 / 62, 17 / 186], (-1, 0, 1, 2) values_rhs, needed_shifts_rhs = [9 / 62, 1, 9 / 62], (-1, 0, 1) base_widths = (abs(min(needed_shifts)), max(needed_shifts)) - field.with_extrapolation(map(_ex_map_f(extrap_map), field.extrapolation)) # ToDo does this line do anything? + field.with_extrapolation(extrapolation.map(_ex_map_f(extrap_map), field.extrapolation)) # ToDo does this line do anything? spatial_dims = field.shape.spatial.names if isinstance(field, StaggeredGrid): base_widths = (base_widths[0]+1, base_widths[1]) From 17e6a433bacf126799ac82b46d88729a52fb9935 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 22 Feb 2023 15:18:27 +0100 Subject: [PATCH 155/170] [doc] Fix broken links to PhiTreeNode --- phi/math/_functional.py | 6 ++-- phi/math/_magic_ops.py | 4 +-- phi/math/_nd.py | 10 +++--- phi/math/_ops.py | 70 ++++++++++++++++++++--------------------- phi/math/_optimize.py | 20 ++++++------ phi/math/_tensors.py | 6 ++-- 6 files changed, 58 insertions(+), 58 deletions(-) diff --git a/phi/math/_functional.py b/phi/math/_functional.py index 1274b2310..3e1081ad1 100644 --- a/phi/math/_functional.py +++ b/phi/math/_functional.py @@ -235,7 +235,7 @@ def my_function(x: math.Tensor) -> math.Tensor: * it is called with a different number of arguments, * the tensor arguments have different dimension names or types (the dimension order also counts), * any `Tensor` arguments require a different backend than previous invocations, - * `PhiTreeNode` positional arguments do not match in non-variable properties. + * `phi.math.magic.PhiTreeNode` positional arguments do not match in non-variable properties. Compilation is implemented for the following backends: @@ -251,7 +251,7 @@ def my_function(x: math.Tensor) -> math.Tensor: Args: f: Function to be traced. - All positional arguments must be of type `Tensor` or `PhiTreeNode` returning a single `Tensor` or `PhiTreeNode`. + All positional arguments must be of type `Tensor` or `phi.math.magic.PhiTreeNode` returning a single `Tensor` or `phi.math.magic.PhiTreeNode`. auxiliary_args: Comma-separated parameter names of arguments that are not relevant to backpropagation. forget_traces: If `True`, only remembers the most recent compiled instance of this function. Upon tracing with new instance (due to changed shapes or auxiliary args), deletes the previous traces. @@ -964,7 +964,7 @@ def trace_check(f, *args, **kwargs): def map_types(f: Callable, dims: Shape or tuple or list or str or Callable, dim_type: Callable or str) -> Callable: """ - Wraps a function to change the dimension types of its `Tensor` and `PhiTreeNode` arguments. + Wraps a function to change the dimension types of its `Tensor` and `phi.math.magic.PhiTreeNode` arguments. Args: f: Function to wrap. diff --git a/phi/math/_magic_ops.py b/phi/math/_magic_ops.py index 6bacd78aa..b072e07da 100644 --- a/phi/math/_magic_ops.py +++ b/phi/math/_magic_ops.py @@ -557,13 +557,13 @@ def all_attributes(obj, assert_any=False) -> Set[str]: def replace(obj: PhiTreeNodeType, **updates) -> PhiTreeNodeType: """ - Creates a copy of the given `PhiTreeNode` with updated values as specified in `updates`. + Creates a copy of the given `phi.math.magic.PhiTreeNode` with updated values as specified in `updates`. If `obj` overrides `__with_attrs__`, the copy will be created via that specific implementation. Otherwise, the `copy` module and `setattr` will be used. Args: - obj: `PhiTreeNode` + obj: `phi.math.magic.PhiTreeNode` **updates: Values to be replaced. Returns: diff --git a/phi/math/_nd.py b/phi/math/_nd.py index b979783ea..4ce73d9c9 100644 --- a/phi/math/_nd.py +++ b/phi/math/_nd.py @@ -188,8 +188,8 @@ def l1_loss(x, reduce: DimFilter = math.non_batch) -> Tensor: Computes *∑i ||xi||1*, summing over all non-batch dimensions. Args: - x: `Tensor` or `PhiTreeNode` or 0D or 1D native tensor. - For `PhiTreeNode` objects, only value the sum over all value attributes is computed. + x: `Tensor` or `phi.math.magic.PhiTreeNode` or 0D or 1D native tensor. + For `phi.math.magic.PhiTreeNode` objects, only value the sum over all value attributes is computed. reduce: Dimensions to reduce as `DimFilter`. Returns: @@ -218,8 +218,8 @@ def l2_loss(x, reduce: DimFilter = math.non_batch) -> Tensor: Computes *∑i ||xi||22 / 2*, summing over all non-batch dimensions. Args: - x: `Tensor` or `PhiTreeNode` or 0D or 1D native tensor. - For `PhiTreeNode` objects, only value the sum over all value attributes is computed. + x: `Tensor` or `phi.math.magic.PhiTreeNode` or 0D or 1D native tensor. + For `phi.math.magic.PhiTreeNode` objects, only value the sum over all value attributes is computed. reduce: Dimensions to reduce as `DimFilter`. Returns: @@ -255,7 +255,7 @@ def frequency_loss(x, Lower frequencies are weighted more strongly then higher frequencies, depending on `frequency_falloff`. Args: - x: `Tensor` or `PhiTreeNode` Values to penalize, typically `actual - target`. + x: `Tensor` or `phi.math.magic.PhiTreeNode` Values to penalize, typically `actual - target`. frequency_falloff: Large values put more emphasis on lower frequencies, 1.0 weights all frequencies equally. *Note*: The total loss is not normalized. Varying the value will result in losses of different magnitudes. threshold: Frequency amplitudes below this value are ignored. diff --git a/phi/math/_ops.py b/phi/math/_ops.py index c62e8cf5d..81ff74ddd 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -39,7 +39,7 @@ def choose_backend_t(*values, prefer_default=False) -> Backend: def convert(x, backend: Backend = None, use_dlpack=True): """ - Convert the native representation of a `Tensor` or `PhiTreeNode` to the native format of `backend`. + Convert the native representation of a `Tensor` or `phi.math.magic.PhiTreeNode` to the native format of `backend`. *Warning*: This operation breaks the automatic differentiation chain. @@ -47,7 +47,7 @@ def convert(x, backend: Backend = None, use_dlpack=True): `phi.math.backend.convert()`. Args: - x: `Tensor` to convert. If `x` is a `PhiTreeNode`, its variable attributes are converted. + x: `Tensor` to convert. If `x` is a `phi.math.magic.PhiTreeNode`, its variable attributes are converted. backend: Target backend. If `None`, uses the current default backend, see `phi.math.backend.default_backend()`. Returns: @@ -1602,7 +1602,7 @@ def abs_(x) -> Tensor or PhiTreeNode: TensorFlow and PyTorch return 0 while Jax returns 1. Args: - x: `Tensor` or `PhiTreeNode` + x: `Tensor` or `phi.math.magic.PhiTreeNode` Returns: Absolute value of `x` of same type as `x`. @@ -1616,36 +1616,36 @@ def sign(x) -> Tensor or PhiTreeNode: The sign of 0 is undefined. Args: - x: `Tensor` or `PhiTreeNode` + x: `Tensor` or `phi.math.magic.PhiTreeNode` Returns: - `Tensor` or `PhiTreeNode` matching `x`. + `Tensor` or `phi.math.magic.PhiTreeNode` matching `x`. """ return _backend_op1(x, Backend.sign) def round_(x) -> Tensor or PhiTreeNode: - """ Rounds the `Tensor` or `PhiTreeNode` `x` to the closest integer. """ + """ Rounds the `Tensor` or `phi.math.magic.PhiTreeNode` `x` to the closest integer. """ return _backend_op1(x, Backend.round) def ceil(x) -> Tensor or PhiTreeNode: - """ Computes *⌈x⌉* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes *⌈x⌉* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.ceil) def floor(x) -> Tensor or PhiTreeNode: - """ Computes *⌊x⌋* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes *⌊x⌋* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.floor) def sqrt(x) -> Tensor or PhiTreeNode: - """ Computes *sqrt(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes *sqrt(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.sqrt) def exp(x) -> Tensor or PhiTreeNode: - """ Computes *exp(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes *exp(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.exp) @@ -1661,21 +1661,21 @@ def to_float(x) -> Tensor or PhiTreeNode: `cast()`. Args: - x: `Tensor` or `PhiTreeNode` to convert + x: `Tensor` or `phi.math.magic.PhiTreeNode` to convert Returns: - `Tensor` or `PhiTreeNode` matching `x`. + `Tensor` or `phi.math.magic.PhiTreeNode` matching `x`. """ return _backend_op1(x, Backend.to_float) def to_int32(x) -> Tensor or PhiTreeNode: - """ Converts the `Tensor` or `PhiTreeNode` `x` to 32-bit integer. """ + """ Converts the `Tensor` or `phi.math.magic.PhiTreeNode` `x` to 32-bit integer. """ return _backend_op1(x, Backend.to_int32) def to_int64(x) -> Tensor or PhiTreeNode: - """ Converts the `Tensor` or `PhiTreeNode` `x` to 64-bit integer. """ + """ Converts the `Tensor` or `phi.math.magic.PhiTreeNode` `x` to 64-bit integer. """ return _backend_op1(x, Backend.to_int64) @@ -1700,7 +1700,7 @@ def to_complex(x) -> Tensor or PhiTreeNode: def is_finite(x) -> Tensor or PhiTreeNode: - """ Returns a `Tensor` or `PhiTreeNode` matching `x` with values `True` where `x` has a finite value and `False` otherwise. """ + """ Returns a `Tensor` or `phi.math.magic.PhiTreeNode` matching `x` with values `True` where `x` has a finite value and `False` otherwise. """ return _backend_op1(x, Backend.isfinite) @@ -1710,7 +1710,7 @@ def real(x) -> Tensor or PhiTreeNode: `imag()`, `conjugate()`. Args: - x: `Tensor` or `PhiTreeNode` or native tensor. + x: `Tensor` or `phi.math.magic.PhiTreeNode` or native tensor. Returns: Real component of `x`. @@ -1727,7 +1727,7 @@ def imag(x) -> Tensor or PhiTreeNode: `real()`, `conjugate()`. Args: - x: `Tensor` or `PhiTreeNode` or native tensor. + x: `Tensor` or `phi.math.magic.PhiTreeNode` or native tensor. Returns: Imaginary component of `x` if `x` is complex, zeros otherwise. @@ -1741,7 +1741,7 @@ def conjugate(x) -> Tensor or PhiTreeNode: `imag()`, `real()`. Args: - x: Real or complex `Tensor` or `PhiTreeNode` or native tensor. + x: Real or complex `Tensor` or `phi.math.magic.PhiTreeNode` or native tensor. Returns: Complex conjugate of `x` if `x` is complex, else `x`. @@ -1755,37 +1755,37 @@ def degrees(deg): def sin(x) -> Tensor or PhiTreeNode: - """ Computes *sin(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes *sin(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.sin) def arcsin(x) -> Tensor or PhiTreeNode: - """ Computes the inverse of *sin(x)* of the `Tensor` or `PhiTreeNode` `x`. + """ Computes the inverse of *sin(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. For real arguments, the result lies in the range [-π/2, π/2]. """ return _backend_op1(x, Backend.arcsin) def cos(x) -> Tensor or PhiTreeNode: - """ Computes *cos(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes *cos(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.cos) def arccos(x) -> Tensor or PhiTreeNode: - """ Computes the inverse of *cos(x)* of the `Tensor` or `PhiTreeNode` `x`. + """ Computes the inverse of *cos(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. For real arguments, the result lies in the range [0, π]. """ return _backend_op1(x, Backend.cos) def tan(x) -> Tensor or PhiTreeNode: - """ Computes *tan(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes *tan(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.tan) def arctan(x, divide_by=None) -> Tensor or PhiTreeNode: """ - Computes the inverse of *tan(x)* of the `Tensor` or `PhiTreeNode` `x`. + Computes the inverse of *tan(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. Args: x: Input. The single-argument `arctan` function cannot output π/2 or -π/2 since tan(π/2) is infinite. @@ -1800,52 +1800,52 @@ def arctan(x, divide_by=None) -> Tensor or PhiTreeNode: def sinh(x) -> Tensor or PhiTreeNode: - """ Computes *sinh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes *sinh(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.sinh) def arcsinh(x) -> Tensor or PhiTreeNode: - """ Computes the inverse of *sinh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes the inverse of *sinh(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.arcsinh) def cosh(x) -> Tensor or PhiTreeNode: - """ Computes *cosh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes *cosh(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.cosh) def arccosh(x) -> Tensor or PhiTreeNode: - """ Computes the inverse of *cosh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes the inverse of *cosh(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.arccosh) def tanh(x) -> Tensor or PhiTreeNode: - """ Computes *tanh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes *tanh(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.tanh) def arctanh(x) -> Tensor or PhiTreeNode: - """ Computes the inverse of *tanh(x)* of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes the inverse of *tanh(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.arctanh) def log(x) -> Tensor or PhiTreeNode: - """ Computes the natural logarithm of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes the natural logarithm of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.log) def log2(x) -> Tensor or PhiTreeNode: - """ Computes *log(x)* of the `Tensor` or `PhiTreeNode` `x` with base 2. """ + """ Computes *log(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x` with base 2. """ return _backend_op1(x, Backend.log2) def log10(x) -> Tensor or PhiTreeNode: - """ Computes *log(x)* of the `Tensor` or `PhiTreeNode` `x` with base 10. """ + """ Computes *log(x)* of the `Tensor` or `phi.math.magic.PhiTreeNode` `x` with base 10. """ return _backend_op1(x, Backend.log10) def sigmoid(x) -> Tensor or PhiTreeNode: - """ Computes the sigmoid function of the `Tensor` or `PhiTreeNode` `x`. """ + """ Computes the sigmoid function of the `Tensor` or `phi.math.magic.PhiTreeNode` `x`. """ return _backend_op1(x, Backend.sigmoid) @@ -2363,7 +2363,7 @@ def stop_gradient(x): * Jax: [`jax.lax.stop_gradient`](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.stop_gradient.html) Args: - x: `Tensor` or `PhiTreeNode` for which gradients should be disabled. + x: `Tensor` or `phi.math.magic.PhiTreeNode` for which gradients should be disabled. Returns: Copy of `x`. diff --git a/phi/math/_optimize.py b/phi/math/_optimize.py index e96f8ac11..143367628 100644 --- a/phi/math/_optimize.py +++ b/phi/math/_optimize.py @@ -126,9 +126,9 @@ def __init__(self, self.solve: Solve[X, Y] = solve """ `Solve`, Parameters specified for the solve. """ self.x: X = x - """ `Tensor` or `PhiTreeNode`, solution estimate. """ + """ `Tensor` or `phi.math.magic.PhiTreeNode`, solution estimate. """ self.residual: Y = residual - """ `Tensor` or `PhiTreeNode`, residual vector for systems of equations or function value for minimization problems. """ + """ `Tensor` or `phi.math.magic.PhiTreeNode`, residual vector for systems of equations or function value for minimization problems. """ self.iterations: Tensor = iterations """ `Tensor`, number of performed iterations to reach this state. """ self.function_evaluations: Tensor = function_evaluations @@ -303,10 +303,10 @@ def minimize(f: Callable[[X], Y], solve: Solve[X, Y]) -> X: Args: f: Function whose output is subject to minimization. - All positional arguments of `f` are optimized and must be `Tensor` or `PhiTreeNode`. + All positional arguments of `f` are optimized and must be `Tensor` or `phi.math.magic.PhiTreeNode`. If `solve.x0` is a `tuple` or `list`, it will be passed to *f* as varargs, `f(*x0)`. To minimize a subset of the positional arguments, define a new (lambda) function depending only on those. - The first return value of `f` must be a scalar float `Tensor` or `PhiTreeNode`. + The first return value of `f` must be a scalar float `Tensor` or `phi.math.magic.PhiTreeNode`. solve: `Solve` object to specify method type, parameters and initial guess for `x`. Returns: @@ -398,13 +398,13 @@ def solve_nonlinear(f: Callable, y, solve: Solve) -> Tensor: Args: f: Function whose output is optimized to match `y`. - All positional arguments of `f` are optimized and must be `Tensor` or `PhiTreeNode`. + All positional arguments of `f` are optimized and must be `Tensor` or `phi.math.magic.PhiTreeNode`. The output of `f` must match `y`. - y: Desired output of `f(x)` as `Tensor` or `PhiTreeNode`. + y: Desired output of `f(x)` as `Tensor` or `phi.math.magic.PhiTreeNode`. solve: `Solve` object specifying optimization method, parameters and initial guess for `x`. Returns: - x: Solution fulfilling `f(x) = y` within specified tolerance as `Tensor` or `PhiTreeNode`. + x: Solution fulfilling `f(x) = y` within specified tolerance as `Tensor` or `phi.math.magic.PhiTreeNode`. Raises: NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. @@ -456,12 +456,12 @@ def solve_linear(f: Callable[[X], Y] or Tensor, Args: f: One of the following: - * Linear function with `Tensor` or `PhiTreeNode` first parameter and return value. `f` can have additional auxiliary arguments and return auxiliary values. + * Linear function with `Tensor` or `phi.math.magic.PhiTreeNode` first parameter and return value. `f` can have additional auxiliary arguments and return auxiliary values. * Dense matrix (`Tensor` with at least one dual dimension) * Sparse matrix (Sparse `Tensor` with at least one dual dimension) * Native tensor (not yet supported) - y: Desired output of `f(x)` as `Tensor` or `PhiTreeNode`. + y: Desired output of `f(x)` as `Tensor` or `phi.math.magic.PhiTreeNode`. solve: `Solve` object specifying optimization method, parameters and initial guess for `x`. *f_args: Positional arguments to be passed to `f` after `solve.x0`. These arguments will not be solved for. Supports vararg mode or pass all arguments as a `tuple`. @@ -469,7 +469,7 @@ def solve_linear(f: Callable[[X], Y] or Tensor, These arguments are treated as auxiliary arguments and can be of any type. Returns: - x: solution of the linear system of equations `f(x) = y` as `Tensor` or `PhiTreeNode`. + x: solution of the linear system of equations `f(x) = y` as `Tensor` or `phi.math.magic.PhiTreeNode`. Raises: NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. diff --git a/phi/math/_tensors.py b/phi/math/_tensors.py index be2f5581b..274cf952b 100644 --- a/phi/math/_tensors.py +++ b/phi/math/_tensors.py @@ -1943,7 +1943,7 @@ def disassemble_tree(obj: PhiTreeNodeType) -> Tuple[PhiTreeNodeType, List[Tensor Args: obj: Nested structure of `Tensor` objects. - Nested structures include: `tuple`, `list`, `dict`, `PhiTreeNode`. + Nested structures include: `tuple`, `list`, `dict`, `phi.math.magic.PhiTreeNode`. Returns: empty structure: Same structure as `obj` but with the tensors replaced by `None`. @@ -2053,12 +2053,12 @@ def cached(t: Tensor or 'PhiTreeNode') -> Tensor or 'PhiTreeNode': class Dict(dict): """ - Dictionary of `Tensor` or `PhiTreeNode` values. + Dictionary of `Tensor` or `phi.math.magic.PhiTreeNode` values. Dicts are not themselves tensors and do not have a shape. Use `layout()` to treat `dict` instances like tensors. In addition to dictionary functions, supports mathematical operators with other `Dict`s and lookup via `.key` syntax. - `Dict` implements `PhiTreeNode` so instances can be passed to math operations like `sin`. + `Dict` implements `phi.math.magic.PhiTreeNode` so instances can be passed to math operations like `sin`. """ def __value_attrs__(self): From 2a214208812ec76b43508adf3003581028a0a39d Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 22 Feb 2023 15:21:24 +0100 Subject: [PATCH 156/170] [vis] Don't force aspect for heatmaps --- phi/vis/_matplotlib/_matplotlib_plots.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index e132adff6..4ddceb5fb 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -34,6 +34,7 @@ def create_figure(self, self.current_figure = figure axes = np.reshape(axes, (rows, cols)) axes_by_pos = {} + subplot_aspect = (size[0] / cols) / (size[1] / rows) # x / y for row in range(rows): for col in range(cols): axis = axes[row, col] @@ -62,7 +63,7 @@ def create_figure(self, if bounds.vector.item_names[1] in log_dims: axis.set_yscale('log') any_log = True - if not any_log and x_size > 0 and y_size > 0 and max(x_size/y_size, y_size/x_size) < 5: + if not any_log and x_size > 0 and y_size > 0 and max(x_size/y_size/subplot_aspect, y_size/x_size*subplot_aspect) < 4: axis.set_aspect('equal', adjustable='box') elif bounds.spatial_rank == 3: axis.remove() @@ -209,7 +210,8 @@ def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, x, y = math.reshaped_numpy(data.points, [vector, *spatial(data)]) im = subplot.plot_surface(x, y, z) else: # heatmap - im = subplot.imshow(data.values.numpy(dims.reversed), origin='lower', extent=extent, vmin=min_val, vmax=max_val) + aspect = subplot.get_aspect() + im = subplot.imshow(data.values.numpy(dims.reversed), origin='lower', extent=extent, vmin=min_val, vmax=max_val, aspect=aspect) if show_color_bar: figure_has_color_bar = any(['colorbar' in ax.get_label() for ax in subplot.figure.axes]) if min_val is None or max_val is None or not figure_has_color_bar: From a091a0d36ad053bd30245f59f479bfa7da8e4b94 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Wed, 22 Feb 2023 15:43:58 +0100 Subject: [PATCH 157/170] [learning] Remove unused norm in PyTorch dense_net * Add unit test for conv_classifier --- phi/jax/stax/nets.py | 2 +- phi/torch/nets.py | 10 ++++++---- tests/commit/test_networks.py | 6 ++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/phi/jax/stax/nets.py b/phi/jax/stax/nets.py index 14adfde9b..53ba0a971 100644 --- a/phi/jax/stax/nets.py +++ b/phi/jax/stax/nets.py @@ -491,7 +491,7 @@ def conv_classifier(in_features: int, init_fn, apply_fn = {}, {} net_list = [] - for i, (prev, next) in enumerate(zip((in_features,) + blocks[:-1], blocks)): + for i, (prev, next) in enumerate(zip((in_features,) + tuple(blocks[:-1]), blocks)): if i in (0, 1): net_list.append(f'conv{i+1}') init_fn[net_list[-1]], apply_fn[net_list[-1]] = create_double_conv(d, next, next, batch_norm, activation, periodic) diff --git a/phi/torch/nets.py b/phi/torch/nets.py index 2f353c680..5e285e816 100644 --- a/phi/torch/nets.py +++ b/phi/torch/nets.py @@ -194,10 +194,11 @@ def __init__(self, self._layers = layers self._activation = activation self._batch_norm = batch_norm - for i, (s1, s2) in enumerate(zip(layers[:-1], layers[1:])): + for i, (s1, s2) in enumerate(zip(layers[:-2], layers[1:-1])): self.add_module(f'linear{i}', _bias0(nn.Linear)(s1, s2, bias=True)) if batch_norm: self.add_module(f'norm{i}', nn.BatchNorm1d(s2)) + self.add_module(f'linear_out', _bias0(nn.Linear)(layers[-2], layers[-1], bias=True)) self.softmax = nn.Softmax() if use_softmax else None def forward(self, x): @@ -207,7 +208,7 @@ def forward(self, x): x = self._activation()(getattr(self, f'linear{i}')(x)) if self._batch_norm: x = getattr(self, f'norm{i}')(x) - x = getattr(self, f'linear{len(self._layers) - 2}')(x) + x = getattr(self, f'linear_out')(x) if self.softmax: x = self.softmax(x) return x @@ -573,8 +574,9 @@ def __init__(self, in_features, in_spatial: list, num_classes: int, batch_norm: super(ConvClassifier, self).__init__() d = len(in_spatial) self.in_spatial = in_spatial + self._blocks = blocks self.add_module('maxpool', MAX_POOL[d](2)) - for i, (prev, next) in enumerate(zip((in_features,) + blocks[:-1], blocks)): + for i, (prev, next) in enumerate(zip((in_features,) + tuple(blocks[:-1]), blocks)): if i in (0, 1): conv = DoubleConv(d, prev, next, next, batch_norm, activation, periodic) else: @@ -588,7 +590,7 @@ def __init__(self, in_features, in_spatial: list, num_classes: int, batch_norm: self.flatten = nn.Flatten() def forward(self, x): - for i in range(5): + for i in range(len(self._blocks)): x = getattr(self, f'conv{i+1}')(x) x = self.maxpool(x) x = self.flatten(x) diff --git a/tests/commit/test_networks.py b/tests/commit/test_networks.py index 771a31694..3a479fa99 100644 --- a/tests/commit/test_networks.py +++ b/tests/commit/test_networks.py @@ -91,6 +91,8 @@ def test_dense_net_network_params(self): self.assertTrue(all(isinstance(p, math.Tensor) for p in params.values())) params = lib.get_parameters(net, wrap=False) self.assertEqual(6, len(params)) + net = lib.dense_net(2, 3, layers=[10], batch_norm=True, activation='ReLU') + self.assertEqual(83, lib.parameter_count(net), str(lib)) def test_optimize_dense_net(self): for lib in LIBRARIES: @@ -177,6 +179,10 @@ def test_invertible_net_network_sizes(self): net_dense = lib.invertible_net(2, 3, True, activation='ReLU', in_spatial=0) self.assertEqual(240, lib.parameter_count(net_dense)) + def test_conv_classifier(self): + for lib in LIBRARIES: + net = lib.conv_classifier(1, (2,), 1, blocks=[10], dense_layers=[], batch_norm=True, softmax=False, periodic=False) + self.assertEqual(401, lib.parameter_count(net)) From 58e8e9123b9b846b94a619365c9dc55612af0228 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Fri, 24 Feb 2023 18:38:01 +0100 Subject: [PATCH 158/170] [backend] Advanced while loop --- phi/jax/_jax_backend.py | 23 +++-- phi/math/backend/_backend.py | 60 ++++++++++- phi/math/backend/_numpy_backend.py | 5 - phi/tf/_tf_backend.py | 22 +++- phi/torch/_torch_backend.py | 54 ++++++---- tests/commit/math/backend/test__backend.py | 113 ++++++++++++++------- 6 files changed, 201 insertions(+), 76 deletions(-) diff --git a/phi/jax/_jax_backend.py b/phi/jax/_jax_backend.py index 6c940abc4..c18f4f9e2 100644 --- a/phi/jax/_jax_backend.py +++ b/phi/jax/_jax_backend.py @@ -322,15 +322,26 @@ def get_diagonal(self, matrices, offset=0): result = jnp.diagonal(matrices, offset=offset, axis1=1, axis2=2) return jnp.transpose(result, [0, 2, 1]) - def while_loop(self, loop: Callable, values: tuple): + def while_loop(self, loop: Callable, values: tuple, max_iter=None): if all(self.is_available(t) for t in values): - while jnp.any(values[0]): + return self.stop_gradient_tree(Backend.while_loop(self, loop, values, max_iter)) + if isinstance(max_iter, (tuple, list)): # stack traced trajectory, unroll until max_iter + values = self.stop_gradient_tree(values) + trj = [values] if 0 in max_iter else [] + for i in range(1, max(max_iter) + 1): values = loop(*values) - return values + if i in max_iter: + trj.append(values) # values are not mutable so no need to copy + return self.stop_gradient_tree(self.stack_leaves(trj)) else: - cond = lambda vals: jnp.any(vals[0]) - body = lambda vals: loop(*vals) - return jax.lax.while_loop(cond, body, values) + if max_iter is None: + cond = lambda vals: jnp.any(vals[0]) + body = lambda vals: loop(*vals) + return jax.lax.while_loop(cond, body, values) + else: + cond = lambda vals: jnp.any(vals[1][0]) & (vals[0] < max_iter) + body = lambda vals: (vals[0] + 1, loop(*vals[1])) + return jax.lax.while_loop(cond, body, (self.as_tensor(0), values))[1] def max(self, x, axis=None, keepdims=False): return jnp.max(x, axis, keepdims=keepdims) diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index fd1c34bb7..50bc82864 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -332,6 +332,16 @@ def from_dlpack(self, capsule): def copy(self, tensor, only_mutable=False): raise NotImplementedError() + def copy_leaves(self, tree, only_mutable=False): + if isinstance(tree, tuple): + return tuple([self.copy_leaves(e, only_mutable) for e in tree]) + elif isinstance(tree, list): + return [self.copy_leaves(e, only_mutable) for e in tree] + elif isinstance(tree, dict): + return {k: self.copy_leaves(e, only_mutable) for k, e in tree.items()} + else: + return self.copy(tree, only_mutable=only_mutable) + def call(self, f: Callable, *args, name=None): """ Calls `f(*args)` and returns the result. @@ -416,6 +426,17 @@ def random_normal(self, shape, dtype: DType): def stack(self, values, axis=0): raise NotImplementedError(self) + def stack_leaves(self, trees: tuple or list, axis=0): + tree0 = trees[0] + if isinstance(tree0, tuple): + return tuple([self.stack_leaves([tree[i] for tree in trees], axis=axis) for i in range(len(tree0))]) + elif isinstance(tree0, list): + return [self.stack_leaves([tree[i] for tree in trees], axis=axis) for i in range(len(tree0))] + elif isinstance(tree0, dict): + return {k: self.stack_leaves([tree[k] for tree in trees], axis=axis) for k in tree0} + else: + return self.stack(trees, axis=axis) + def concat(self, values, axis): raise NotImplementedError(self) @@ -505,8 +526,10 @@ def einsum(self, equation, *tensors): def cumsum(self, x, axis: int): raise NotImplementedError(self) - def while_loop(self, loop: Callable, values: tuple): + def while_loop(self, loop: Callable, values: tuple, max_iter: int or Tuple[int, ...] or List[int]): """ + If `max_iter is None`, runs + ```python while any(values[0]): values = loop(*values) @@ -518,10 +541,30 @@ def while_loop(self, loop: Callable, values: tuple): Args: loop: Loop function, must return a `tuple` with entries equal to `values` in shape and data type. values: Initial values of loop variables. + max_iter: Maximum number of iterations to run, single `int` or sequence of integers. Returns: - Loop variables upon loop completion. - """ - raise NotImplementedError(self) + Loop variables upon loop completion if `max_iter` is a single integer. + If `max_iter` is a sequence, stacks the variables after each entry in `max_iter`, adding an outer dimension of size `<= len(max_iter)`. + If the condition is fulfilled before the maximum max_iter is reached, the loop may be broken or not, depending on the implementation. + If the loop is broken, the values returned by the last loop are expected to be constant and filled. + """ + values = self.stop_gradient_tree(values) + if isinstance(max_iter, (tuple, list)): + trj = [values] if 0 in max_iter else [] + for i in range(1, max(max_iter) + 1): + values = loop(*values) + if i in max_iter: + trj.append(self.copy_leaves(values, only_mutable=True)) + if not self.any(values[0]): + break + trj.extend([trj[-1]] * (len(max_iter) - len(trj))) # fill trj with final values + return self.stop_gradient_tree(self.stack_leaves(trj)) + else: + for i in range(1, max_iter + 1): + if not self.any(values[0]): + break + values = loop(*values) + return self.stop_gradient_tree(values) def abs(self, x): raise NotImplementedError(self) @@ -1190,6 +1233,15 @@ def solve_triangular_dense(self, matrix, rhs, lower: bool, unit_diagonal: bool): def stop_gradient(self, value): raise NotImplementedError(self) + def stop_gradient_tree(self, tree): + if isinstance(tree, tuple): + return tuple([self.stop_gradient_tree(v) for v in tree]) + if isinstance(tree, list): + return [self.stop_gradient_tree(v) for v in tree] + if isinstance(tree, dict): + return {k: self.stop_gradient_tree(v) for k, v in tree.items()} + return self.stop_gradient(tree) + def grid_sample(self, grid, coordinates, extrapolation: str): """ Interpolates a regular grid at the specified coordinates. diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index 63c4cec07..abd37f4af 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -216,11 +216,6 @@ def mul_matrix_batched_vector(self, A, b): def get_diagonal(self, matrices, offset=0): return np.transpose(np.diagonal(matrices, offset=offset, axis1=1, axis2=2), [0, 2, 1]) - def while_loop(self, loop: Callable, values: tuple): - while np.any(values[0]): - values = loop(*values) - return values - def max(self, x, axis=None, keepdims=False): return np.max(x, axis, keepdims=keepdims) diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index c00d4a856..8e807cbf6 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -286,10 +286,26 @@ def cumsum(self, x, axis: int): with tf.device(x.device): return tf.cumsum(x, axis=axis, exclusive=False) - def while_loop(self, loop: Callable, values: tuple): - cond = lambda c, *vals: tf.reduce_any(c) + def while_loop(self, loop: Callable, values: tuple, max_iter=None): with self._device_for(*values): - return tf.nest.map_structure(tf.stop_gradient, tf.while_loop(cond, loop, values)) + if isinstance(max_iter, (tuple, list)): # stack traced trajectory, unroll until max_iter + values = self.stop_gradient_tree(values) + trj = [values] if 0 in max_iter else [] + for i in range(1, max(max_iter) + 1): + values = loop(*values) + if i in max_iter: + trj.append(values) # values are not mutable so no need to copy + condition = values[0] + if self.is_available(condition) and not self.any(values[0]): + break + trj.extend([trj[-1]] * (len(max_iter) - len(trj))) # fill trj with final values + return self.stop_gradient_tree(self.stack_leaves(trj)) + else: + cond = lambda c, *vals: tf.reduce_any(tf.cast(c, tf.bool)) + return tf.while_loop(cond, loop, values, maximum_iterations=max_iter, back_prop=False) + + def stop_gradient_tree(self, tree): + return tf.nest.map_structure(tf.stop_gradient, tree) def abs(self, x): with tf.device(x.device): diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index b7acf6242..89e5b81e2 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -401,28 +401,42 @@ def get_diagonal(self, matrices, offset=0): def cumsum(self, x, axis: int): return torch.cumsum(x, dim=axis) - def while_loop(self, loop: Callable, values: tuple): - if torch._C._get_tracing_state() is not None: - if isinstance(loop, torch.ScriptFunction): - jit_loop = loop - while torch.any(values[0]): - values = jit_loop(*values) - return values - else: - warnings.warn("Tracing a PyTorch while loop requires an additional tracing pass. You can avoid this by passing a torch.ScriptFunction.", RuntimeWarning) - raise NotImplementedError() - # def trace_later(): - # jit_loop = torch.jit.trace(loop, check_trace=False) - # @torch.jit.script - # def loop_script(values: Tuple[torch.Tensor], loop_script: Callable): - # while torch.any(values[0]): - # values = loop_script(*values) - # return values - # CURRENT_JIT_CALLS[-1].post_trace.append(trace_later) + def while_loop(self, loop: Callable, values: tuple, max_iter=None): + tracing = torch._C._get_tracing_state() is not None + if not tracing: + return Backend.while_loop(self, loop, values, max_iter) + # --- We are tracing --- + warnings.warn("PyTorch while_loop always iterates until max_iter. Please put a while loop into a torch.ScriptFunction instead.", RuntimeWarning) + values = self.stop_gradient_tree(values) + if isinstance(max_iter, (tuple, list)): + trj = [values] if 0 in max_iter else [] + for i in range(1, max(max_iter) + 1): + values = loop(*values) + if i in max_iter: + trj.append(self.copy_leaves(values, only_mutable=True)) + trj.extend([trj[-1]] * (len(max_iter) - len(trj))) # fill trj with final values + return self.stop_gradient_tree(self.stack_leaves(trj)) else: - while torch.any(values[0]): + for i in range(1, max_iter + 1): values = loop(*values) - return values + return self.stop_gradient_tree(values) + # if isinstance(loop, torch.ScriptFunction): + # jit_loop = loop + # i = 0 + # while torch.any(values[0]): + # values = jit_loop(*values) + # i += 1 + # if max_iter is not None and i >= max_iter: + # break + # return values + # def trace_later(): + # jit_loop = torch.jit.trace(loop, check_trace=False) + # @torch.jit.script + # def loop_script(values: Tuple[torch.Tensor], loop_script: Callable): + # while torch.any(values[0]): + # values = loop_script(*values) + # return values + # CURRENT_JIT_CALLS[-1].post_trace.append(trace_later) def max(self, x, axis=None, keepdims=False): if axis is None: diff --git a/tests/commit/math/backend/test__backend.py b/tests/commit/math/backend/test__backend.py index 1cc6725c0..903b980cb 100644 --- a/tests/commit/math/backend/test__backend.py +++ b/tests/commit/math/backend/test__backend.py @@ -12,64 +12,101 @@ class TestBackends(TestCase): def test_list_devices(self): - for backend in BACKENDS: - devices = backend.list_devices() + for b in BACKENDS: + devices = b.list_devices() self.assertGreater(len(devices), 0) self.assertTrue(all(isinstance(d, ComputeDevice) for d in devices)) def test_convert(self): # TODO this causes RuntimeError when GPU capsule is given to Jax in CPU mode - for source_backend in BACKENDS: - for target_backend in BACKENDS: - data = source_backend.random_normal([4], None) # may be deleted in conversion - print(f"{source_backend} -> {target_backend} {data}") - converted = convert(data, target_backend) - np1 = source_backend.numpy(data) - np2 = target_backend.numpy(converted) + for b_src in BACKENDS: + for b_target in BACKENDS: + data = b_src.random_normal([4], None) # may be deleted in conversion + print(f"{b_src} -> {b_target} {data}") + converted = convert(data, b_target) + np1 = b_src.numpy(data) + np2 = b_target.numpy(converted) numpy.testing.assert_equal(np1, np2) def test_allocate_on_device(self): - for backend in BACKENDS: - t = backend.zeros(()) - assert backend.get_device(t) == backend.get_default_device() - t_ = backend.allocate_on_device(t, backend.get_default_device()) - assert backend.get_device(t_) == backend.get_default_device() + for b in BACKENDS: + t = b.zeros(()) + assert b.get_device(t) == b.get_default_device() + t_ = b.allocate_on_device(t, b.get_default_device()) + assert b.get_device(t_) == b.get_default_device() def test_gather(self): - for backend in BACKENDS: - t = backend.zeros((4, 3, 2)) + for b in BACKENDS: + t = b.zeros((4, 3, 2)) indices = [0, 1] - result = backend.gather(t, indices, axis=0) - self.assertEqual((2, 3, 2), backend.staticshape(result)) + result = b.gather(t, indices, axis=0) + self.assertEqual((2, 3, 2), b.staticshape(result)) def test_sparse(self): idx = [[0, 1, 1], [2, 0, 2]] v = [3, 4, 5] shape = (2, 3) - for backend in BACKENDS: - if backend.supports(Backend.sparse_coo_tensor): - with backend: - idx_ = backend.transpose(backend.as_tensor(idx), [1, 0]) - matrix = backend.sparse_coo_tensor(idx_, v, shape) - self.assertTrue(backend.is_tensor(matrix), backend.name) + for b in BACKENDS: + if b.supports(Backend.sparse_coo_tensor): + with b: + idx_ = b.transpose(b.as_tensor(idx), [1, 0]) + matrix = b.sparse_coo_tensor(idx_, v, shape) + self.assertTrue(b.is_tensor(matrix), b.name) def test_get_diagonal(self): - for backend in BACKENDS: - with backend: - t = backend.as_tensor([[[[1], [2]], [[0], [-1]]]]) - d = backend.numpy(backend.get_diagonal(t, offset=0)) + for b in BACKENDS: + with b: + t = b.as_tensor([[[[1], [2]], [[0], [-1]]]]) + d = b.numpy(b.get_diagonal(t, offset=0)) numpy.testing.assert_equal([[[1], [-1]]], d) - d1 = backend.numpy(backend.get_diagonal(t, offset=1)) + d1 = b.numpy(b.get_diagonal(t, offset=1)) numpy.testing.assert_equal([[[2]]], d1) - d1 = backend.numpy(backend.get_diagonal(t, offset=-1)) + d1 = b.numpy(b.get_diagonal(t, offset=-1)) numpy.testing.assert_equal([[[0]]], d1) def test_solve_triangular_dense(self): - for backend in BACKENDS: - with backend: - rhs = backend.as_tensor([[1, 7, 3]]) - matrix = backend.as_tensor([[[-1, 1, 0], [0, 2, 2], [0, 1, 1]]]) - x = backend.numpy(backend.solve_triangular_dense(matrix, rhs, lower=False, unit_diagonal=True)[0, :]) - numpy.testing.assert_almost_equal([0, 1, 3], x, err_msg=backend.name) - x = backend.numpy(backend.solve_triangular_dense(matrix, rhs, lower=False, unit_diagonal=False)[0, :]) - numpy.testing.assert_almost_equal([-.5, .5, 3], x, err_msg=backend.name) + for b in BACKENDS: + with b: + rhs = b.as_tensor([[1, 7, 3]]) + matrix = b.as_tensor([[[-1, 1, 0], [0, 2, 2], [0, 1, 1]]]) + x = b.numpy(b.solve_triangular_dense(matrix, rhs, lower=False, unit_diagonal=True)[0, :]) + numpy.testing.assert_almost_equal([0, 1, 3], x, err_msg=b.name) + x = b.numpy(b.solve_triangular_dense(matrix, rhs, lower=False, unit_diagonal=False)[0, :]) + numpy.testing.assert_almost_equal([-.5, .5, 3], x, err_msg=b.name) + + def test_while_loop_direct(self): + for b in BACKENDS: + with b: + # --- while loop with max_iter --- + count, = b.while_loop(lambda i: (i - 1,), (b.as_tensor(10),), max_iter=3) + numpy.testing.assert_almost_equal(7, b.numpy(count), err_msg=b.name) + # --- while loop with multiple max_iter --- + count, = b.while_loop(lambda i: (i - 1,), (b.as_tensor(10),), max_iter=[0, 3, 6]) + numpy.testing.assert_almost_equal([10, 7, 4], b.numpy(count), err_msg=b.name) + # --- while loop with fill --- + count, = b.while_loop(lambda i: (i - 1,), (b.as_tensor(2),), max_iter=(1, 5)) + self.assertEqual((2,), b.staticshape(count)) + numpy.testing.assert_almost_equal([1, 1], b.numpy(count), err_msg=b.name) + + def test_while_loop_jit(self): + for b in BACKENDS: + if b.supports(Backend.jit_compile): + with b: + # --- while loop with max_iter --- + def max_iter_int(start): + print("max_iter_int") + return b.while_loop(lambda i: (i - 1,), (start,), max_iter=3) + count, = b.jit_compile(max_iter_int)(b.as_tensor(10)) + numpy.testing.assert_almost_equal(7, b.numpy(count), err_msg=b.name) + # --- while loop with multiple max_iter --- + def max_iter_sequence(start): + print("max_iter_sequence") + return b.while_loop(lambda i: (i - 1,), (start,), max_iter=[0, 3, 6]) + count, = b.jit_compile(max_iter_sequence)(b.as_tensor(10)) + numpy.testing.assert_almost_equal([10, 7, 4], b.numpy(count), err_msg=b.name) + # --- while loop with fill --- + def max_iter_none(start): + print("max_iter_none") + return b.while_loop(lambda i: (i - 1,), (start,), max_iter=(1, 5)) + count, = b.jit_compile(max_iter_none)(b.as_tensor(2)) + numpy.testing.assert_almost_equal(1, b.numpy(count)[0], err_msg=b.name) From f43972c8bf0c8d51ec0d607881fbfcb1adaf5f22 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 25 Feb 2023 14:17:31 +0100 Subject: [PATCH 159/170] [math] Fix sum(scalar bool) --- phi/math/_magic_ops.py | 34 ++++++++++++++++++++++++++++ phi/math/_ops.py | 4 ++-- tests/commit/math/test__magic_ops.py | 15 ++++++++++-- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/phi/math/_magic_ops.py b/phi/math/_magic_ops.py index b072e07da..adb3f0b3a 100644 --- a/phi/math/_magic_ops.py +++ b/phi/math/_magic_ops.py @@ -629,3 +629,37 @@ def cast(x: MagicType, dtype: DType or type) -> OtherMagicType: if dtype.kind == bool: return bool(x) raise ValueError(f"Cannot cast object of type '{type(x).__name__}'") + + +def bool_to_int(x: MagicType, bits=32): + if isinstance(x, bool): + return int(x) + if isinstance(x, Number): + return x + if hasattr(x, 'dtype') and isinstance(x.dtype, DType): + return cast(x, DType(int, bits)) if x.dtype.kind == bool else x + elif isinstance(x, PhiTreeNode): + return tree_map(bool_to_int, x, bits=32) + try: + backend = choose_backend(x) + return backend.cast(x, DType(int, bits)) if backend.dtype(x).kind == bool else x + except NoBackendFound: + raise ValueError(f"Cannot cast object of type '{type(x).__name__}'") + + +def tree_map(f, tree, **f_kwargs): + from ._tensors import Tensor + if isinstance(tree, Tensor): + return f(tree, **f_kwargs) + if isinstance(tree, list): + return [tree_map(f, e, **f_kwargs) for e in tree] + elif isinstance(tree, tuple): + return tuple([tree_map(f, e, **f_kwargs) for e in tree]) + elif isinstance(tree, dict): + return {k: tree_map(f, e, **f_kwargs) for k, e in tree.items()} + elif isinstance(tree, PhiTreeNode): + attrs = {key: getattr(tree, key) for key in value_attributes(tree)} + new_attrs = {k: tree_map(f, v, **f_kwargs) for k, v in attrs.items()} + return copy_with(tree, **new_attrs) + else: + return f(tree, **f_kwargs) # try anyway diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 81ff74ddd..11c63afb3 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -7,7 +7,7 @@ import numpy as np from . import extrapolation as e_ -from ._magic_ops import expand, pack_dims, flatten, unpack_dim, cast, copy_with, value_attributes +from ._magic_ops import expand, pack_dims, flatten, unpack_dim, cast, copy_with, value_attributes, bool_to_int from ._shape import (Shape, EMPTY_SHAPE, spatial, batch, channel, instance, merge_shapes, parse_dim_order, concat_shapes, IncompatibleShapes, DimFilter, non_batch, non_channel) @@ -1055,7 +1055,7 @@ def sum_(value: Tensor or list or tuple, dim: DimFilter = non_batch) -> Tensor: Returns: `Tensor` without the reduced dimensions. """ - return reduce_(_sum, value, dim, require_all_dims_present=True) + return reduce_(_sum, bool_to_int(value), dim, require_all_dims_present=True) def _sum(value: Tensor, dims: Shape) -> Tensor: diff --git a/tests/commit/math/test__magic_ops.py b/tests/commit/math/test__magic_ops.py index a90b3b00e..f9df6001c 100644 --- a/tests/commit/math/test__magic_ops.py +++ b/tests/commit/math/test__magic_ops.py @@ -4,7 +4,8 @@ import dataclasses from phi.math import batch, unstack, Shape, merge_shapes, stack, concat, expand, spatial, shape, instance, rename_dims, \ - pack_dims, random_normal, flatten, unpack_dim, EMPTY_SHAPE, Tensor, Dict, channel, linspace, zeros, meshgrid, assert_close + pack_dims, random_normal, flatten, unpack_dim, EMPTY_SHAPE, Tensor, Dict, channel, linspace, zeros, meshgrid, assert_close, wrap +from phi.math._magic_ops import bool_to_int from phi.math.magic import BoundDim, Shaped, Sliceable, Shapable, PhiTreeNode, slicing_dict @@ -63,7 +64,13 @@ def __getitem__(self, item): return MyPoint(self.x[item], self.y[item], is_normalized=self.is_normalized) -TEST_CLASSES = [Stackable, ConcatExpandable, random_normal, ValuedPhiTreeNode, lambda shape: MyPoint(zeros(shape), zeros(shape), is_normalized=False)] +TEST_CLASSES = [ + Stackable, + ConcatExpandable, + random_normal, + ValuedPhiTreeNode, + lambda shape: MyPoint(zeros(shape), zeros(shape), is_normalized=False), +] class TestMagicOps(TestCase): @@ -222,3 +229,7 @@ def test_bound_dims(self): self.fail() except SyntaxError: pass + + def test_bool_to_int(self): + a = [wrap(True), wrap(1.), {'a': wrap(False)}] + self.assertEqual([1, 1., {'a': 0}], bool_to_int(a)) From 2dc1cb5c5ccf702e8448040c2af372586bdd1408 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 25 Feb 2023 14:19:00 +0100 Subject: [PATCH 160/170] [math] Refactor linear solves --- phi/jax/_jax_backend.py | 8 +- phi/math/_ops.py | 2 +- phi/math/_optimize.py | 121 ++++++++------ phi/math/backend/_backend.py | 43 +++-- phi/math/backend/_linalg.py | 235 +++++++++++++--------------- phi/math/backend/_minimize.py | 11 +- phi/math/backend/_numpy_backend.py | 40 ++--- phi/tf/_tf_backend.py | 4 +- phi/torch/_torch_backend.py | 48 +++--- tests/commit/math/test__optimize.py | 2 +- 10 files changed, 258 insertions(+), 256 deletions(-) diff --git a/phi/jax/_jax_backend.py b/phi/jax/_jax_backend.py index c18f4f9e2..b9f7d9b7a 100644 --- a/phi/jax/_jax_backend.py +++ b/phi/jax/_jax_backend.py @@ -322,7 +322,7 @@ def get_diagonal(self, matrices, offset=0): result = jnp.diagonal(matrices, offset=offset, axis1=1, axis2=2) return jnp.transpose(result, [0, 2, 1]) - def while_loop(self, loop: Callable, values: tuple, max_iter=None): + def while_loop(self, loop: Callable, values: tuple, max_iter: int or Tuple[int, ...] or List[int]): if all(self.is_available(t) for t in values): return self.stop_gradient_tree(Backend.while_loop(self, loop, values, max_iter)) if isinstance(max_iter, (tuple, list)): # stack traced trajectory, unroll until max_iter @@ -464,12 +464,6 @@ def dtype(self, array) -> DType: array = jnp.array(array) return from_numpy_dtype(array.dtype) - def linear_solve(self, method: str, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: - if method == 'auto' and not trj and not self.is_available(y): - return self.conjugate_gradient(lin, y, x0, rtol, atol, max_iter, trj) - else: - return Backend.linear_solve(self, method, lin, y, x0, rtol, atol, max_iter, trj) - def matrix_solve_least_squares(self, matrix: TensorType, rhs: TensorType) -> Tuple[TensorType, TensorType, TensorType, TensorType]: solution, residuals, rank, singular_values = lstsq_batched(matrix, rhs) return solution, residuals, rank, singular_values diff --git a/phi/math/_ops.py b/phi/math/_ops.py index 11c63afb3..7d3e04612 100644 --- a/phi/math/_ops.py +++ b/phi/math/_ops.py @@ -239,7 +239,7 @@ def reshaped_tensor(value: Any, try: value = tensor(value, *dims, convert=convert) except IncompatibleShapes: - raise IncompatibleShapes(f"Cannot reshape native tensor with sizes {value.shape} given groups {groups}") + raise IncompatibleShapes(f"Cannot reshape native tensor {type(value)} with sizes {value.shape} given groups {groups}") for i, group in enumerate(groups): if value.shape.get_size(f'group{i}') == group.volume: value = unpack_dim(value, f'group{i}', group) diff --git a/phi/math/_optimize.py b/phi/math/_optimize.py index 143367628..fd9de457b 100644 --- a/phi/math/_optimize.py +++ b/phi/math/_optimize.py @@ -4,14 +4,15 @@ from typing import Callable, Generic, List, TypeVar, Any, Tuple, Union import numpy +import numpy as np from .backend import get_precision from ._shape import EMPTY_SHAPE, Shape, merge_shapes, batch, non_batch, shape, dual, channel, non_dual -from ._magic_ops import stack, copy_with, rename_dims +from ._magic_ops import stack, copy_with, rename_dims, unpack_dim from ._sparse import native_matrix, SparseCoordinateTensor, CompressedSparseMatrix -from ._tensors import Tensor, disassemble_tree, assemble_tree, wrap, cached, NativeTensor +from ._tensors import Tensor, disassemble_tree, assemble_tree, wrap, cached, NativeTensor, layout from . import _ops as math -from ._ops import choose_backend_t, zeros_like, all_available, reshaped_native, reshaped_tensor, to_float +from ._ops import choose_backend_t, zeros_like, all_available, reshaped_native, reshaped_tensor, to_float, reshaped_numpy from ._functional import custom_gradient, LinearFunction, f_name from .backend import Backend from .backend._backend import SolveResult, PHI_LOGGER @@ -46,8 +47,7 @@ def __init__(self, For systems of equations *f(x)=y*, the final tolerance is `max(rel_tol * norm(y), abs_tol)`. """ self.abs_tol: Tensor = math.to_float(wrap(abs_tol)) if abs_tol is not None else None """ Absolut tolerance for optimization problems and linear solves. - For optimization problems, defaults to 1e-5 for singe precision solves and 1e-12 for double precision solves. - For linear solves, defaults to 0. + Defaults to 1e-5 for singe precision solves and 1e-12 for double precision solves. For systems of equations *f(x)=y*, the final tolerance is `max(rel_tol * norm(y), abs_tol)`. """ self.max_iterations: Tensor = math.to_int32(wrap(max_iterations)) """ Maximum number of iterations to perform before raising a `NotConverged` error is raised. """ @@ -62,7 +62,7 @@ def __init__(self, self.suppress: tuple = tuple(suppress) """ Error types to suppress; `tuple` of `ConvergenceException` types. For these errors, the solve function will instead return the partial result without raising the error. """ self._gradient_solve: Solve[Y, X] = gradient_solve - self.id = str(uuid.uuid4()) + self.id = str(uuid.uuid4()) # not altered by copy_with(), so that the lookup SolveTape[Solve] works after solve has been copied @property def gradient_solve(self) -> 'Solve[Y, X]': @@ -94,6 +94,15 @@ def __eq__(self, other): def __variable_attrs__(self): return 'x0', 'preprocess_y_args' + def with_defaults(self, mode: str): + assert mode in ('solve', 'optimization') + result = self + if result.rel_tol is None: + result = copy_with(result, rel_tol=_default_tolerance() if mode == 'solve' else wrap(0.)) + if result.abs_tol is None: + result = copy_with(result, abs_tol=_default_tolerance()) + return result + def _default_tolerance(): if get_precision() == 64: @@ -120,7 +129,7 @@ def __init__(self, converged: Tensor, diverged: Tensor, method: str, - msg: str, + msg: Tensor, solve_time: float): # tuple.__new__(SolveInfo, (x, residual, iterations, function_evaluations, converged, diverged)) self.solve: Solve[X, Y] = solve @@ -139,20 +148,15 @@ def __init__(self, """ `Tensor`, whether the solve has diverged at this point. """ self.method = method """ `str`, which method and implementation that was used. """ - if not msg and all_available(diverged, converged): - if self.diverged.any: - msg = f"Solve diverged within {iterations if iterations is not None else '?'} iterations using {method}." - elif not self.converged.trajectory[-1].all: - msg = f"Solve did not converge to rel={solve.rel_tol}, abs={solve.abs_tol} within {solve.max_iterations} iterations using {method}. Max residual: {[math.max_(t.trajectory[-1]) for t in disassemble_tree(self.residual)[1]]}" - else: - msg = f"Converged within {iterations if iterations is not None else '?'} iterations." + if all_available(diverged, converged, iterations): + msg = math.map_(_default_solve_info_msg, msg, converged.trajectory[-1], diverged.trajectory[-1], iterations.trajectory[-1], solve=solve, method=method, residual=residual) self.msg = msg """ `str`, termination message """ self.solve_time = solve_time """ Time spent in Backend solve function (in seconds) """ def __repr__(self): - return self.msg + return f"{self.method}: {self.converged.trajectory[-1].sum} converged, {self.diverged.trajectory[-1].sum} diverged" def snapshot(self, index): return SolveInfo(self.solve, self.x.trajectory[index], self.residual.trajectory[index], self.iterations.trajectory[index], self.function_evaluations.trajectory[index], @@ -175,6 +179,18 @@ def convergence_check(self, only_warn: bool): raise NotConverged(self) +def _default_solve_info_msg(msg, converged, diverged, iterations, solve: Solve, method, residual): + if msg: + return msg + if diverged: + return f"Solve diverged within {iterations if iterations is not None else '?'} iterations using {method}." + elif not converged: + max_res = [f"{math.max_(t.trajectory[-1]):no-color:no-dtype}" for t in disassemble_tree(residual)[1]] + return f"{method} did not converge to rel_tol={float(solve.rel_tol):.0e}, abs_tol={float(solve.abs_tol):.0e} within {int(solve.max_iterations)} iterations. Max residual: {', '.join(max_res)}" + else: + return f"Converged within {iterations if iterations is not None else '?'} iterations." + + class ConvergenceException(RuntimeError): """ Base class for exceptions raised when a solve does not converge. @@ -316,7 +332,8 @@ def minimize(f: Callable[[X], Y], solve: Solve[X, Y]) -> X: NotConverged: If the desired accuracy was not be reached within the maximum number of iterations. Diverged: If the optimization failed prematurely. """ - assert solve.rel_tol is None or (solve.rel_tol == 0).all, f"rel_tol must be zero for minimize() but got {solve.rel_tol}" + solve = solve.with_defaults('optimization') + assert (solve.rel_tol == 0).all, f"rel_tol must be zero for minimize() but got {solve.rel_tol}" assert solve.preprocess_y is None, "minimize() does not allow preprocess_y" x0_nest, x0_tensors = disassemble_tree(solve.x0) x0_tensors = [to_float(t) for t in x0_tensors] @@ -354,8 +371,8 @@ def native_function(x_flat): raise AssertionError(f"Failed to minimize '{f.__name__}' because its output loss {shape(y_tensors[0])} has more batch dimensions than the initial guess {batch_dims}.") return y_tensors[0].sum, (loss_native,) - atol = backend.to_float(reshaped_native((solve.abs_tol or _default_tolerance()), [batch_dims], force_expand=True)) - maxi = backend.to_int32(reshaped_native(solve.max_iterations, [batch_dims], force_expand=True)) + atol = backend.to_float(reshaped_native(solve.abs_tol, [batch_dims], force_expand=True)) + maxi = reshaped_numpy(solve.max_iterations, [batch_dims], force_expand=True) trj = _SOLVE_TAPES and any(t.record_trajectories for t in _SOLVE_TAPES) t = time.perf_counter() ret = backend.minimize(solve.method, native_function, x0_flat, atol, maxi, trj) @@ -417,8 +434,8 @@ def min_func(x): if solve.preprocess_y is not None: y = solve.preprocess_y(y) from ._nd import l2_loss - rel_tol_to_abs = (_default_tolerance() if solve.rel_tol is None else solve.rel_tol) * l2_loss(y) - tol = math.maximum(rel_tol_to_abs, (_default_tolerance() if solve.abs_tol is None else solve.abs_tol)) + solve = solve.with_defaults('solve') + tol = math.maximum(solve.rel_tol * l2_loss(y), solve.abs_tol) min_solve = copy_with(solve, abs_tol=tol, rel_tol=0, preprocess_y=None) return minimize(min_func, min_solve) @@ -480,6 +497,7 @@ def solve_linear(f: Callable[[X], Y] or Tensor, f_kwargs.update(f_kwargs_) f_args = f_args[0] if len(f_args) == 1 and isinstance(f_args[0], tuple) else f_args # --- Get input and output tensors --- + solve = solve.with_defaults('solve') y_tree, y_tensors = disassemble_tree(y) x0_tree, x0_tensors = disassemble_tree(solve.x0) assert solve.x0 is not None, "Please specify the initial guess as Solve(..., x0=initial_guess)" @@ -549,44 +567,47 @@ def _linear_solve_forward(y, batch_dims = merge_shapes(y_tensor.shape.without(pattern_dims_out), x0_tensor.shape.without(pattern_dims_in)) x0_native = backend.as_tensor(reshaped_native(x0_tensor, [batch_dims, pattern_dims_in], force_expand=True)) y_native = backend.as_tensor(reshaped_native(y_tensor, [batch_dims, y_tensor.shape.only(pattern_dims_out)], force_expand=True)) - rtol = backend.as_tensor(reshaped_native(math.to_float(_default_tolerance() if solve.rel_tol is None else solve.rel_tol), [batch_dims], force_expand=True)) - atol = backend.as_tensor(reshaped_native(wrap(0) if solve.abs_tol is None else solve.abs_tol, [batch_dims], force_expand=True)) - maxi = backend.as_tensor(reshaped_native(solve.max_iterations, [batch_dims], force_expand=True)) + rtol = backend.as_tensor(reshaped_native(math.to_float(solve.rel_tol), [batch_dims], force_expand=True)) + atol = backend.as_tensor(reshaped_native(solve.abs_tol, [batch_dims], force_expand=True)) + tol_sq = backend.maximum(rtol ** 2 * backend.sum(y_native ** 2, -1), atol ** 2) trj = _SOLVE_TAPES and any(t.record_trajectories for t in _SOLVE_TAPES) if trj: assert all_available(y_tensor, x0_tensor), "Cannot record linear solve in jit mode" + max_iter = np.expand_dims(np.arange(int(solve.max_iterations)+1), -1) + else: + max_iter = reshaped_numpy(solve.max_iterations, [shape(solve.max_iterations).without(batch_dims), batch_dims], force_expand=True) t = time.perf_counter() - ret = backend.linear_solve(solve.method, native_lin_op, y_native, x0_native, rtol, atol, maxi, trj) + ret = backend.linear_solve(solve.method, native_lin_op, y_native, x0_native, tol_sq, max_iter) t = time.perf_counter() - t - if not trj: - assert isinstance(ret, SolveResult) - converged = reshaped_tensor(ret.converged, [batch_dims]) - diverged = reshaped_tensor(ret.diverged, [batch_dims]) - x = assemble_tree(x0_nest, [reshaped_tensor(ret.x, [batch_dims, pattern_dims_in])]) - iterations = reshaped_tensor(ret.iterations, [batch_dims]) - function_evaluations = reshaped_tensor(ret.function_evaluations, [batch_dims]) - if ret.residual is not None: - residual = assemble_tree(y_nest, [reshaped_tensor(ret.residual, [batch_dims, pattern_dims_out])]) - elif _SOLVE_TAPES: - residual = backend.linear(native_lin_op, ret.x) - y_native - residual = assemble_tree(y_nest, [reshaped_tensor(residual, [batch_dims, pattern_dims_out])]) - else: - residual = None - result = SolveInfo(solve, x, residual, iterations, function_evaluations, converged, diverged, ret.method, ret.message, t) - else: # trajectory - assert isinstance(ret, (tuple, list)) and all(isinstance(r, SolveResult) for r in ret), f"Trajectory recording failed: got {type(ret)}" - converged = reshaped_tensor(ret[-1].converged, [batch_dims]) - diverged = reshaped_tensor(ret[-1].diverged, [batch_dims]) - x = assemble_tree(x0_nest, [reshaped_tensor(ret[-1].x, [batch_dims, pattern_dims_in])]) - x_ = assemble_tree(x0_nest, [stack([reshaped_tensor(r.x, [batch_dims, pattern_dims_in]) for r in ret], batch('trajectory'))]) - residual = assemble_tree(y_nest, [stack([reshaped_tensor(r.residual, [batch_dims, pattern_dims_out]) for r in ret], batch('trajectory'))]) - iterations = reshaped_tensor(ret[-1].iterations, [batch_dims]) - function_evaluations = stack([reshaped_tensor(r.function_evaluations, [batch_dims]) for r in ret], batch('trajectory')) - result = SolveInfo(solve, x_, residual, iterations, function_evaluations, converged, diverged, ret[-1].method, ret[-1].message, t) + trj_dims = [batch(trajectory=len(max_iter))] if trj else [] + assert isinstance(ret, SolveResult) + converged = reshaped_tensor(ret.converged, [*trj_dims, batch_dims]) + diverged = reshaped_tensor(ret.diverged, [*trj_dims, batch_dims]) + x = assemble_tree(x0_nest, [reshaped_tensor(ret.x, [*trj_dims, batch_dims, pattern_dims_in])]) + iterations = reshaped_tensor(ret.iterations, [*trj_dims, batch_dims]) + function_evaluations = reshaped_tensor(ret.function_evaluations, [*trj_dims, batch_dims]) + if ret.residual is not None: + residual = assemble_tree(y_nest, [reshaped_tensor(ret.residual, [*trj_dims, batch_dims, pattern_dims_out])]) + elif _SOLVE_TAPES: + residual = backend.linear(native_lin_op, ret.x) - y_native + residual = assemble_tree(y_nest, [reshaped_tensor(residual, [*trj_dims, batch_dims, pattern_dims_out])]) + else: + residual = None + msg = unpack_dim(layout(ret.message, batch('_all')), '_all', batch_dims) + result = SolveInfo(solve, x, residual, iterations, function_evaluations, converged, diverged, ret.method, msg, t) + # else: # trajectory + # converged = reshaped_tensor(ret[-1].converged, [batch_dims]) + # diverged = reshaped_tensor(ret[-1].diverged, [batch_dims]) + # x = assemble_tree(x0_nest, [reshaped_tensor(ret[-1].x, [batch_dims, pattern_dims_in])]) + # x_ = assemble_tree(x0_nest, [stack([reshaped_tensor(r.x, [batch_dims, pattern_dims_in]) for r in ret], )]) + # residual = assemble_tree(y_nest, [stack([reshaped_tensor(r.residual, [batch_dims, pattern_dims_out]) for r in ret], batch('trajectory'))]) + # iterations = reshaped_tensor(ret[-1].iterations, [batch_dims]) + # function_evaluations = stack([reshaped_tensor(r.function_evaluations, [batch_dims]) for r in ret], batch('trajectory')) + # result = SolveInfo(solve, x_, residual, iterations, function_evaluations, converged, diverged, ret[-1].method, ret[-1].message, t) for tape in _SOLVE_TAPES: tape._add(solve, trj, result) result.convergence_check(is_backprop and 'TensorFlow' in backend.name) # raises ConvergenceException - return x + return x[{'trajectory': -1}] if isinstance(x, Tensor) else x def attach_gradient_solve(forward_solve: Callable, auxiliary_args: str, matrix_adjoint: bool): diff --git a/phi/math/backend/_backend.py b/phi/math/backend/_backend.py index 50bc82864..6925f06e3 100644 --- a/phi/math/backend/_backend.py +++ b/phi/math/backend/_backend.py @@ -550,7 +550,7 @@ def while_loop(self, loop: Callable, values: tuple, max_iter: int or Tuple[int, """ values = self.stop_gradient_tree(values) if isinstance(max_iter, (tuple, list)): - trj = [values] if 0 in max_iter else [] + trj = [self.copy_leaves(values, only_mutable=True)] if 0 in max_iter else [] for i in range(1, max(max_iter) + 1): values = loop(*values) if i in max_iter: @@ -1137,7 +1137,7 @@ def minimize(self, method: str, f, x0, atol, max_iter, trj: bool): from ._minimize import scipy_minimize return scipy_minimize(self, method, f, x0, atol, max_iter, trj) - def linear_solve(self, method: str, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: + def linear_solve(self, method: str, lin, y, x0, tol_sq, max_iter) -> SolveResult: """ Solve the system of linear equations A · x = y. This method need not provide a gradient for the operation. @@ -1150,44 +1150,43 @@ def linear_solve(self, method: str, lin, y, x0, rtol, atol, max_iter, trj: bool) * linear function A(x), must be called on all instances in parallel y: target result of A * x. 2nd order tensor (batch, vector) or list of vectors. x0: Initial guess of size (batch, parameters) - rtol: Relative tolerance of size (batch,) - atol: Absolute tolerance of size (batch,) - max_iter: Maximum number of iterations of size (batch,) - trj: Whether to record and return the optimization trajectory as a `List[SolveResult]`. + tol_sq: Squared absolute tolerance of size (batch,) + max_iter: Maximum number of iterations of size (batch,) or a sequence of maximum iterations to obtain a trajectory. Returns: - result: `SolveResult` or `List[SolveResult]`, depending on `trj`. + `SolveResult` """ if method == 'auto': - return self.conjugate_gradient_adaptive(lin, y, x0, rtol, atol, max_iter, trj) + return self.conjugate_gradient_adaptive(lin, y, x0, tol_sq, max_iter) elif method == 'CG': - return self.conjugate_gradient(lin, y, x0, rtol, atol, max_iter, trj) + return self.conjugate_gradient(lin, y, x0, tol_sq, max_iter) elif method == 'CG-adaptive': - return self.conjugate_gradient_adaptive(lin, y, x0, rtol, atol, max_iter, trj) + return self.conjugate_gradient_adaptive(lin, y, x0, tol_sq, max_iter) elif method in ['biCG', 'biCG-stab(0)']: - return self.bi_conjugate_gradient_original(lin, y, x0, rtol, atol, max_iter, trj) + raise NotImplementedError("Unstabilized Bi-CG not yet supported") + # return self.bi_conjugate_gradient_original(lin, y, x0, tol_sq, max_iter) elif method == 'biCG-stab': - return self.bi_conjugate_gradient(lin, y, x0, rtol, atol, max_iter, trj, poly_order=1) + return self.bi_conjugate_gradient(lin, y, x0, tol_sq, max_iter, poly_order=1) elif method.startswith('biCG-stab('): order = int(method[len('biCG-stab('):-1]) - return self.bi_conjugate_gradient(lin, y, x0, rtol, atol, max_iter, trj, poly_order=order) + return self.bi_conjugate_gradient(lin, y, x0, tol_sq, max_iter, poly_order=order) else: raise NotImplementedError(f"Method '{method}' not supported for linear solve.") - def conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: + def conjugate_gradient(self, lin, y, x0, tol_sq, max_iter) -> SolveResult: """ Standard conjugate gradient algorithm. Signature matches to `Backend.linear_solve()`. """ - from ._linalg import cg - return cg(self, lin, y, x0, rtol, atol, max_iter, trj) + from ._linalg import cg, stop_on_l2 + return cg(self, lin, y, x0, stop_on_l2(self, tol_sq, max_iter), max_iter) - def conjugate_gradient_adaptive(self, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: + def conjugate_gradient_adaptive(self, lin, y, x0, tol_sq, max_iter) -> SolveResult: """ Conjugate gradient algorithm with adaptive step size. Signature matches to `Backend.linear_solve()`. """ - from ._linalg import cg_adaptive - return cg_adaptive(self, lin, y, x0, rtol, atol, max_iter, trj) + from ._linalg import cg_adaptive, stop_on_l2 + return cg_adaptive(self, lin, y, x0, stop_on_l2(self, tol_sq, max_iter), max_iter) - def bi_conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj: bool, poly_order=2) -> SolveResult or List[SolveResult]: + def bi_conjugate_gradient(self, lin, y, x0, tol_sq, max_iter, poly_order=2) -> SolveResult: """ Generalized stabilized biconjugate gradient algorithm. Signature matches to `Backend.linear_solve()`. """ - from ._linalg import bicg - return bicg(self, lin, y, x0, rtol, atol, max_iter, trj, poly_order) + from ._linalg import bicg, stop_on_l2 + return bicg(self, lin, y, x0, stop_on_l2(self, tol_sq, max_iter), max_iter, poly_order) def linear(self, lin, vector): if callable(lin): diff --git a/phi/math/backend/_linalg.py b/phi/math/backend/_linalg.py index d2916eae6..23dc973f3 100644 --- a/phi/math/backend/_linalg.py +++ b/phi/math/backend/_linalg.py @@ -10,30 +10,49 @@ def identity(x): return x -def cg(b: Backend, lin, y, x0, rtol, atol, max_iter, trj: bool, pre: Callable = identity) -> SolveResult or List[SolveResult]: +def stop_on_l2(b: Backend, tolerance_squared, max_iter: np.ndarray, on_diverged: Exception or None = None): + max_iter = b.as_tensor(max_iter[-1, :]) + rsq0 = [] + def check_progress(iterations, residual_squared): + converged = b.all(residual_squared <= tolerance_squared, axis=(1,)) + if not rsq0: + diverged = b.any(~b.isfinite(residual_squared), axis=(1,)) + rsq0.append(residual_squared) + else: + diverged = b.any(residual_squared / rsq0[0] > 1e5, axis=(1,)) & (iterations >= 8) + continue_ = ~converged & ~diverged & (iterations < max_iter) + if on_diverged is not None and b.any(diverged): + on_diverged(iterations) + return continue_, converged, diverged + return check_progress + + +def _max_iter(max_iter: np.ndarray) -> int or list: + trj_size, batch_size = max_iter.shape + if trj_size == 1: + return int(np.max(max_iter)) + else: + assert np.all(max_iter == max_iter[:, :1]), "When recording a trajectory, max_iter must be equal for all batch entries" + return max_iter[:, 0].tolist() + + +def cg(b: Backend, lin, y, x0, check_progress: Callable, max_iter, pre: Callable = identity) -> SolveResult or List[SolveResult]: """ Based on "An Introduction to the Conjugate Gradient Method Without the Agonizing Pain" by Jonathan Richard Shewchuk symbols: dx=d, dy=q, step_size=alpha, residual_squared=delta, residual=r, y=b, pre=M """ - method = f"Φ-Flow CG ({b.name})" - y = b.to_float(y) - x0 = b.copy(b.to_float(x0), only_mutable=True) batch_size = b.staticshape(y)[0] - tolerance_sq = b.maximum(rtol ** 2 * b.sum(y ** 2, -1), atol ** 2) - x = x0 + y = b.to_float(y) + x = b.copy(b.to_float(x0), only_mutable=True) residual = y - b.linear(lin, x) dx = pre(residual) iterations = b.zeros([batch_size], DType(int, 32)) function_evaluations = b.ones([batch_size], DType(int, 32)) - residual_squared = rsq0 = b.sum(residual * dx, -1, keepdims=True) - diverged = b.any(~b.isfinite(x), axis=(1,)) - converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) - trajectory = [SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")] if trj else None - continue_ = ~converged & ~diverged & (iterations < max_iter) + residual_squared = b.sum(residual * dx, -1, keepdims=True) + continue_, converged, diverged = check_progress(iterations, residual_squared) - def cg_loop_body(continue_, it_counter, x, dx, residual_squared, residual, iterations, function_evaluations, _converged, _diverged): + def cg_loop_body(continue_, x, dx, residual_squared, residual, iterations, function_evaluations, _converged, _diverged): continue_1 = b.to_int32(continue_) - it_counter += 1 iterations += continue_1 with spatial_derivative_evaluation(1): dy = b.linear(lin, dx); function_evaluations += continue_1 @@ -46,43 +65,30 @@ def cg_loop_body(continue_, it_counter, x, dx, residual_squared, residual, itera residual_squared_old = residual_squared residual_squared = b.sum(residual * s, -1, keepdims=True) dx = s + b.divide_no_nan(residual_squared, residual_squared_old) * dx - diverged = b.any(residual_squared / rsq0 > 1e5, axis=(1,)) & (iterations >= 8) - converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) - if trajectory is not None: - trajectory.append(SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")) - x = b.copy(x) - iterations = b.copy(iterations) - continue_ = ~converged & ~diverged & (iterations < max_iter) - return continue_, it_counter, x, dx, residual_squared, residual, iterations, function_evaluations, converged, diverged + continue_, converged, diverged = check_progress(iterations, residual_squared) + return continue_, x, dx, residual_squared, residual, iterations, function_evaluations, converged, diverged - _, _, x, _, _, residual, iterations, function_evaluations, converged, diverged = b.while_loop(cg_loop_body, ( - continue_, 0, x, dx, residual_squared, residual, iterations, function_evaluations, converged, diverged)) - return trajectory if trj else SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "") + _, x, _, _, residual, iterations, function_evaluations, converged, diverged = b.while_loop(cg_loop_body, (continue_, x, dx, residual_squared, residual, iterations, function_evaluations, converged, diverged), _max_iter(max_iter)) + return SolveResult(f"Φ-Flow CG ({b.name})", x, residual, iterations, function_evaluations, converged, diverged, [""] * batch_size) -def cg_adaptive(b, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: +def cg_adaptive(b, lin, y, x0, check_progress: Callable, max_iter) -> SolveResult or List[SolveResult]: """ Based on the variant described in "Methods of Conjugate Gradients for Solving Linear Systems" by Magnus R. Hestenes and Eduard Stiefel https://nvlpubs.nist.gov/nistpubs/jres/049/jresv49n6p409_A1b.pdf """ - method = f"Φ-Flow CG-adaptive ({b.name})" y = b.to_float(y) x0 = b.copy(b.to_float(x0), only_mutable=True) batch_size = b.staticshape(y)[0] - tolerance_sq = b.maximum(rtol ** 2 * b.sum(y ** 2, -1), atol ** 2) x = x0 dx = residual = y - b.linear(lin, x) dy = b.linear(lin, dx) iterations = b.zeros([batch_size], DType(int, 32)) function_evaluations = b.ones([batch_size], DType(int, 32)) - residual_squared = rsq0 = b.sum(residual ** 2, -1, keepdims=True) - diverged = b.any(~b.isfinite(x), axis=(1,)) - converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) - trajectory = [SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")] if trj else None - continue_ = ~converged & ~diverged & (iterations < max_iter) + residual_squared = b.sum(residual ** 2, -1, keepdims=True) + continue_, converged, diverged = check_progress(iterations, residual_squared) - def acg_loop_body(continue_, it_counter, x, dx, dy, residual, iterations, function_evaluations, _converged, _diverged): + def acg_loop_body(continue_, x, dx, dy, residual, iterations, function_evaluations, _converged, _diverged): continue_1 = b.to_int32(continue_) - it_counter += 1 iterations += continue_1 dx_dy = b.sum(dx * dy, axis=-1, keepdims=True) step_size = b.divide_no_nan(b.sum(dx * residual, axis=-1, keepdims=True), dx_dy) @@ -93,37 +99,24 @@ def acg_loop_body(continue_, it_counter, x, dx, dy, residual, iterations, functi dx = residual - b.divide_no_nan(b.sum(residual * dy, axis=-1, keepdims=True) * dx, dx_dy) with spatial_derivative_evaluation(1): dy = b.linear(lin, dx); function_evaluations += continue_1 - diverged = b.any(residual_squared / rsq0 > 1e5, axis=(1,)) & (iterations >= 8) - converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) - if trajectory is not None: - trajectory.append(SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")) - x = b.copy(x) - iterations = b.copy(iterations) - continue_ = ~converged & ~diverged & (iterations < max_iter) - return continue_, it_counter, x, dx, dy, residual, iterations, function_evaluations, converged, diverged + continue_, converged, diverged = check_progress(iterations, residual_squared) + return continue_, x, dx, dy, residual, iterations, function_evaluations, converged, diverged - _, _, x, _, _, residual, iterations, function_evaluations, converged, diverged = b.while_loop(acg_loop_body, (continue_, 0, x, dx, dy, residual, iterations, function_evaluations, converged, diverged)) - return trajectory if trj else SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "") + _, x, _, _, residual, iterations, function_evaluations, converged, diverged = b.while_loop(acg_loop_body, (continue_, x, dx, dy, residual, iterations, function_evaluations, converged, diverged), _max_iter(max_iter)) + return SolveResult(f"Φ-Flow CG-adaptive ({b.name})", x, residual, iterations, function_evaluations, converged, diverged, [""] * batch_size) -def bicg(b: Backend, lin, y, x0, rtol, atol, max_iter, trj: bool, poly_order: int) -> SolveResult or List[SolveResult]: +def bicg(b: Backend, lin, y, x0, check_progress: Callable, max_iter, poly_order: int) -> SolveResult or List[SolveResult]: """ Adapted from [BiCGstab for linear equations involving unsymmetric matrices with complex spectrum](https://dspace.library.uu.nl/bitstream/handle/1874/16827/sleijpen_93_bicgstab.pdf) """ # Based on "BiCGstab(L) for linear equations involving unsymmetric matrices with complex spectrum" by Gerard L.G. Sleijpen - # # symbols: dx=d, dy=q, step_size=alpha, residual_squared=delta, residual=r, y=b - method = f"Φ-Flow biCG-stab({poly_order}) ({b.name})" - # tensor_size = tuple([L + 1] + [int(dim) for dim in x0.shape]) y = b.to_float(y) x = b.copy(b.to_float(x0), only_mutable=True) batch_size = b.staticshape(y)[0] - tolerance_sq = b.maximum(rtol ** 2 * b.sum(y ** 2, -1), atol ** 2) - residual = y - b.linear(lin, x) + r0_tild = residual = y - b.linear(lin, x) iterations = b.zeros([batch_size], DType(int, 32)) function_evaluations = b.ones([batch_size], DType(int, 32)) - residual_squared = rsq0 = b.sum(residual ** 2, -1, keepdims=True) - diverged = b.any(~b.isfinite(x), axis=(1,)) - converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) - trajectory = [SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")] if trj else None - continue_ = ~converged & ~diverged & (iterations < max_iter) + residual_squared = b.sum(residual ** 2, -1, keepdims=True) + continue_, converged, diverged = check_progress(iterations, residual_squared) rho_0 = b.ones([batch_size, 1]) rho_1 = b.ones([batch_size, 1]) omega = b.ones([batch_size, 1]) @@ -131,76 +124,68 @@ def bicg(b: Backend, lin, y, x0, rtol, atol, max_iter, trj: bool, poly_order: in u = b.zeros_like(x) r0_hat = [b.zeros(x0.shape)] * (poly_order + 1) u_hat = [b.zeros(x0.shape)] * (poly_order + 1) - loop_body = partial(_bicg_stabL_loop_body, b, poly_order, batch_size, lin, residual, trajectory, method, rsq0, tolerance_sq, max_iter) - _, _, x, residual, iterations, function_evaluations, converged, diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat = b.while_loop(loop_body, ( - continue_, 0, x, residual, iterations, function_evaluations, converged, diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat)) - return trajectory if trj else SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "") - - -def _bicg_stabL_loop_body(b: Backend, poly_order: int, batch_size: int, lin, r0_tild, trajectory, method, rsq0, tolerance_sq, max_iter, - continue_, it_counter, x, residual, iterations, function_evaluations, _converged, _diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat): - tau = [[b.zeros((batch_size,))] * (poly_order + 1)] * (poly_order + 1) - sigma = [b.zeros((batch_size,))] * (poly_order + 1) - gamma = [b.zeros((batch_size,))] * (poly_order + 1) - gamma_p = [b.zeros((batch_size,))] * (poly_order + 1) - gamma_pp = [b.zeros((batch_size,))] * (poly_order + 1) - continue_1 = b.to_int32(continue_) - it_counter += 1; iterations += continue_1 - u_hat[0] = u - r0_hat[0] = residual - rho_0 = -omega * rho_0 - # --- Bi-CG part --- - for j in range(0, poly_order): - rho_1 = b.sum(r0_hat[j] * r0_tild, axis=-1, keepdims=True) - beta = alpha * rho_1 / rho_0 - rho_0 = rho_1 - for i in range(0, j + 1): - u_hat[i] = beta * u_hat[i] - u_hat[i] = r0_hat[i] - u_hat[i] - u_hat[j + 1] = b.linear(lin, u_hat[j]); function_evaluations += continue_1 - gamma_coeff = b.sum(u_hat[j + 1] * r0_tild, axis=-1, keepdims=True) - alpha = rho_0 / gamma_coeff - for i in range(0, j + 1): - r0_hat[i] = r0_hat[i] - alpha * u_hat[i + 1] - r0_hat[j + 1] = b.linear(lin, r0_hat[j]); function_evaluations += continue_1 - x = x + alpha * u_hat[0] - for j in range(1, poly_order + 1): - for i in range(1, j): - tau[i][j] = b.sum(r0_hat[j] * r0_hat[i], axis=-1, keepdims=True) / sigma[i] - r0_hat[j] = r0_hat[j] - tau[i][j] * r0_hat[i] - sigma[j] = b.sum(r0_hat[j] * r0_hat[j], axis=-1, keepdims=True) - gamma_p[j] = b.sum(r0_hat[0] * r0_hat[j], axis=-1, keepdims=True) / sigma[j] - # --- MR part --- - omega = gamma[poly_order] = gamma_p[poly_order] - for j in range(poly_order - 1, 0, -1): - sumg = b.zeros_like(tau[0][0]) - for i in range(j + 1, poly_order + 1): - sumg = sumg + tau[j][i] * gamma[i] - gamma[j] = gamma_p[j] - sumg - for j in range(1, poly_order): - sumg = b.zeros_like(tau[0][0]) - for i in range(j + 1, poly_order): - sumg = sumg + tau[j][i] * gamma[i + 1] - gamma_pp[j] = gamma[j + 1] + sumg - # --- Update --- - x = x + gamma[1] * r0_hat[0] - r0_hat[0] = r0_hat[0] - gamma_p[poly_order] * r0_hat[poly_order] - u_hat[0] = u_hat[0] - gamma[poly_order] * u_hat[poly_order] - for j in range(1, poly_order): - u_hat[0] = u_hat[0] - gamma[j] * u_hat[j] - x = x + gamma_pp[j] * r0_hat[j] - r0_hat[0] = r0_hat[0] - gamma_p[j] * r0_hat[j] - u = u_hat[0] - residual = r0_hat[0] - residual_squared = b.sum(residual ** 2, -1, keepdims=True) - diverged = b.any(residual_squared / rsq0 > 1e5, axis=(1,)) & (iterations >= 8) - converged = b.all(residual_squared <= tolerance_sq, axis=(1,)) - if trajectory is not None: - trajectory.append(SolveResult(method, x, residual, iterations, function_evaluations, converged, diverged, "")) - x = b.copy(x) - iterations = b.copy(iterations) - continue_ = ~converged & ~diverged & (iterations < max_iter) - return continue_, it_counter, x, residual, iterations, function_evaluations, converged, diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat + + def loop_body(continue_, x, residual, iterations, function_evaluations, _converged, _diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat): + tau = [[b.zeros((batch_size,))] * (poly_order + 1)] * (poly_order + 1) + sigma = [b.zeros((batch_size,))] * (poly_order + 1) + gamma = [b.zeros((batch_size,))] * (poly_order + 1) + gamma_p = [b.zeros((batch_size,))] * (poly_order + 1) + gamma_pp = [b.zeros((batch_size,))] * (poly_order + 1) + continue_1 = b.to_int32(continue_) + iterations += continue_1 + u_hat[0] = u + r0_hat[0] = residual + rho_0 = -omega * rho_0 + # --- Bi-CG part --- + for j in range(0, poly_order): + rho_1 = b.sum(r0_hat[j] * r0_tild, axis=-1, keepdims=True) + beta = alpha * rho_1 / rho_0 + rho_0 = rho_1 + for i in range(0, j + 1): + u_hat[i] = beta * u_hat[i] + u_hat[i] = r0_hat[i] - u_hat[i] + u_hat[j + 1] = b.linear(lin, u_hat[j]); function_evaluations += continue_1 + gamma_coeff = b.sum(u_hat[j + 1] * r0_tild, axis=-1, keepdims=True) + alpha = rho_0 / gamma_coeff + for i in range(0, j + 1): + r0_hat[i] = r0_hat[i] - alpha * u_hat[i + 1] + r0_hat[j + 1] = b.linear(lin, r0_hat[j]); function_evaluations += continue_1 + x = x + alpha * u_hat[0] + for j in range(1, poly_order + 1): + for i in range(1, j): + tau[i][j] = b.sum(r0_hat[j] * r0_hat[i], axis=-1, keepdims=True) / sigma[i] + r0_hat[j] = r0_hat[j] - tau[i][j] * r0_hat[i] + sigma[j] = b.sum(r0_hat[j] * r0_hat[j], axis=-1, keepdims=True) + gamma_p[j] = b.sum(r0_hat[0] * r0_hat[j], axis=-1, keepdims=True) / sigma[j] + # --- MR part --- + omega = gamma[poly_order] = gamma_p[poly_order] + for j in range(poly_order - 1, 0, -1): + sumg = b.zeros_like(tau[0][0]) + for i in range(j + 1, poly_order + 1): + sumg = sumg + tau[j][i] * gamma[i] + gamma[j] = gamma_p[j] - sumg + for j in range(1, poly_order): + sumg = b.zeros_like(tau[0][0]) + for i in range(j + 1, poly_order): + sumg = sumg + tau[j][i] * gamma[i + 1] + gamma_pp[j] = gamma[j + 1] + sumg + # --- Update --- + x = x + gamma[1] * r0_hat[0] + r0_hat[0] = r0_hat[0] - gamma_p[poly_order] * r0_hat[poly_order] + u_hat[0] = u_hat[0] - gamma[poly_order] * u_hat[poly_order] + for j in range(1, poly_order): + u_hat[0] = u_hat[0] - gamma[j] * u_hat[j] + x = x + gamma_pp[j] * r0_hat[j] + r0_hat[0] = r0_hat[0] - gamma_p[j] * r0_hat[j] + u = u_hat[0] + residual = r0_hat[0] + residual_squared = b.sum(residual ** 2, -1, keepdims=True) + continue_, converged, diverged = check_progress(iterations, residual_squared) + # ToDo multiply step_size by continue_1 to avoid NaN when iterating after convergence + return continue_, x, residual, iterations, function_evaluations, converged, diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat + + _, x, residual, iterations, function_evaluations, converged, diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat = b.while_loop(loop_body, (continue_, x, residual, iterations, function_evaluations, converged, diverged, rho_0, rho_1, omega, alpha, u, u_hat, r0_hat), _max_iter(max_iter)) + return SolveResult(f"Φ-Flow biCG-stab({poly_order}) ({b.name})", x, residual, iterations, function_evaluations, converged, diverged, [""] * batch_size) def incomplete_lu_dense(b: 'Backend', matrix, iterations: int, safe: bool): diff --git a/phi/math/backend/_minimize.py b/phi/math/backend/_minimize.py index 1c7579032..d1de6bfe5 100644 --- a/phi/math/backend/_minimize.py +++ b/phi/math/backend/_minimize.py @@ -3,6 +3,7 @@ import numpy from ._backend import Backend, SolveResult, DType, PHI_LOGGER +from ._linalg import _max_iter def scipy_minimize(self, method: str, f, x0, atol, max_iter, trj: bool): @@ -13,7 +14,6 @@ def scipy_minimize(self, method: str, f, x0, atol, max_iter, trj: bool): x0 = self.numpy(x0) assert x0.ndim == 2 # (batch, parameters) atol = self.numpy(atol) - max_iter = self.numpy(max_iter) batch_size = x0.shape[0] fg = self.jacobian(f, [0], get_output=True, is_f_scalar=True) method_description = f"SciPy {method} with {self.name}" @@ -112,7 +112,7 @@ def callback(x, *args): # L-BFGS-B only passes x but the documentation says (x, return SolveResult(method_description, x, residual, iterations, function_evaluations, converged, diverged, messages) -def gradient_descent(self, f, x0, atol, max_iter, trj: bool, step_size='adaptive'): +def gradient_descent(self: Backend, f, x0, atol, max_iter, trj: bool, step_size='adaptive'): assert self.supports(Backend.jacobian) assert len(self.staticshape(x0)) == 2 # (batch, parameters) batch_size = self.staticshape(x0)[0] @@ -130,7 +130,8 @@ def gradient_descent(self, f, x0, atol, max_iter, trj: bool, step_size='adaptive diverged = self.any(~self.isfinite(x0), axis=(1,)) converged = self.zeros([batch_size], DType(bool)) trajectory = [SolveResult(method, x0, loss, iterations, function_evaluations, converged, diverged, [""] * batch_size)] if trj else None - continue_ = ~converged & ~diverged & (iterations < max_iter) + max_iter_ = self.to_int32(max_iter) + continue_ = ~converged & ~diverged & (iterations < max_iter_) def gd_step(continue_, x, loss, grad, iterations, function_evaluations, step_size, converged, diverged): prev_loss, prev_grad, prev_x = loss, grad, x @@ -166,10 +167,10 @@ def gd_step(continue_, x, loss, grad, iterations, function_evaluations, step_siz converged = ~diverged & (prev_loss - loss < atol) if trj: trajectory.append(SolveResult(method, self.numpy(x), self.numpy(loss), self.numpy(iterations), self.numpy(function_evaluations), self.numpy(diverged), self.numpy(converged), [""] * batch_size)) - continue_ = ~converged & ~diverged & (iterations < max_iter) + continue_ = ~converged & ~diverged & (iterations < max_iter_) return continue_, x, loss, grad, iterations, function_evaluations, step_size, converged, diverged - not_converged, x, loss, grad, iterations, function_evaluations, step_size, converged, diverged = self.while_loop(gd_step, (continue_, x0, loss, grad, iterations, function_evaluations, step_size, converged, diverged)) + not_converged, x, loss, grad, iterations, function_evaluations, step_size, converged, diverged = self.while_loop(gd_step, (continue_, x0, loss, grad, iterations, function_evaluations, step_size, converged, diverged), int(max(max_iter))) if trj: trajectory.append(SolveResult(method, x, loss, iterations, function_evaluations + 1, converged, diverged, [""] * batch_size)) return trajectory diff --git a/phi/math/backend/_numpy_backend.py b/phi/math/backend/_numpy_backend.py index abd37f4af..4d593d99f 100644 --- a/phi/math/backend/_numpy_backend.py +++ b/phi/math/backend/_numpy_backend.py @@ -400,30 +400,30 @@ def stop_gradient(self, value): # return grads # return gradient - def linear_solve(self, method: str, lin, y, x0, rtol, atol, max_iter, trj: bool) -> Any: + def linear_solve(self, method: str, lin, y, x0, tol_sq, max_iter) -> SolveResult: if method == 'direct': return self.direct_linear_solve(lin, y) elif method == 'CG-native': - return self.scipy_iterative_sparse_solve(lin, y, x0, rtol, atol, max_iter, scipy_function=scipy.sparse.linalg.cg) + return self.scipy_iterative_sparse_solve(lin, y, x0, tol_sq, max_iter, scipy_function=scipy.sparse.linalg.cg) elif method == 'GMres': - return self.scipy_iterative_sparse_solve(lin, y, x0, rtol, atol, max_iter, scipy_function=scipy.sparse.linalg.gmres) + return self.scipy_iterative_sparse_solve(lin, y, x0, tol_sq, max_iter, scipy_function=scipy.sparse.linalg.gmres) elif method == 'biCG': - return self.scipy_iterative_sparse_solve(lin, y, x0, rtol, atol, max_iter, scipy_function=scipy.sparse.linalg.bicg) + return self.scipy_iterative_sparse_solve(lin, y, x0, tol_sq, max_iter, scipy_function=scipy.sparse.linalg.bicg) elif method == 'CGS': - return self.scipy_iterative_sparse_solve(lin, y, x0, rtol, atol, max_iter, scipy_function=scipy.sparse.linalg.cgs) + return self.scipy_iterative_sparse_solve(lin, y, x0, tol_sq, max_iter, scipy_function=scipy.sparse.linalg.cgs) elif method == 'lGMres': - return self.scipy_iterative_sparse_solve(lin, y, x0, rtol, atol, max_iter, scipy_function=scipy.sparse.linalg.lgmres) + return self.scipy_iterative_sparse_solve(lin, y, x0, tol_sq, max_iter, scipy_function=scipy.sparse.linalg.lgmres) # elif method == 'minres': - # return self.scipy_iterative_sparse_solve(lin, y, x0, rtol, atol, max_iter, scipy_function=scipy.sparse.linalg.minres) + # return self.scipy_iterative_sparse_solve(lin, y, x0, tol_sq, max_iter, scipy_function=scipy.sparse.linalg.minres) elif method == 'QMR': - return self.scipy_iterative_sparse_solve(lin, y, x0, rtol, atol, max_iter, scipy_function=scipy.sparse.linalg.qmr) + return self.scipy_iterative_sparse_solve(lin, y, x0, tol_sq, max_iter, scipy_function=scipy.sparse.linalg.qmr) elif method == 'GCrotMK': - return self.scipy_iterative_sparse_solve(lin, y, x0, rtol, atol, max_iter, scipy_function=scipy.sparse.linalg.gcrotmk) + return self.scipy_iterative_sparse_solve(lin, y, x0, tol_sq, max_iter, scipy_function=scipy.sparse.linalg.gcrotmk) elif method == 'auto': - return self.conjugate_gradient_adaptive(lin, y, x0, rtol, atol, max_iter, trj) - # return self.conjugate_gradient(lin, y, x0, rtol, atol, max_iter, trj) + return self.conjugate_gradient_adaptive(lin, y, x0, tol_sq, max_iter) + # return self.conjugate_gradient(lin, y, x0, tol_sq, max_iter, trj) else: - return Backend.linear_solve(self, method, lin, y, x0, rtol, atol, max_iter, trj) + return Backend.linear_solve(self, method, lin, y, x0, tol_sq, max_iter) def direct_linear_solve(self, lin, y) -> Any: batch_size = self.staticshape(y)[0] @@ -444,15 +444,15 @@ def direct_linear_solve(self, lin, y) -> Any: converged = np.stack(converged) diverged = ~converged iterations = [-1] * batch_size # spsolve does not perform iterations - return SolveResult('scipy.sparse.linalg.spsolve', x, None, iterations, iterations, converged, diverged, "") + return SolveResult('scipy.sparse.linalg.spsolve', x, None, iterations, iterations, converged, diverged, [""] * batch_size) - def conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj: bool) -> Any: - if trj or callable(lin): - return Backend.conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj) # generic implementation + def conjugate_gradient(self, lin, y, x0, tol_sq, max_iter) -> SolveResult: + if len(max_iter) > 1 or callable(lin): + return Backend.conjugate_gradient(self, lin, y, x0, tol_sq, max_iter) # generic implementation else: - return self.scipy_iterative_sparse_solve(lin, y, x0, rtol, atol, max_iter, scipy_function=scipy.sparse.linalg.bicg) # more stable than cg + return self.scipy_iterative_sparse_solve(lin, y, x0, tol_sq, max_iter, scipy_function=scipy.sparse.linalg.bicg) # more stable than cg - def scipy_iterative_sparse_solve(self, lin, y, x0, rtol, atol, max_iter, scipy_function=cg) -> Any: + def scipy_iterative_sparse_solve(self, lin, y, x0, tol_sq, max_iter, scipy_function=cg) -> SolveResult: bs_y = self.staticshape(y)[0] bs_x0 = self.staticshape(x0)[0] batch_size = combined_dim(bs_y, bs_x0) @@ -468,14 +468,14 @@ def count_callback(x_n): # called after each step, not with x0 diverged = [] for b in range(batch_size): lin_b = lin[min(b, len(lin)-1)] if isinstance(lin, (tuple, list)) or (isinstance(lin, np.ndarray) and len(lin.shape) > 2) else lin - x, ret_val = scipy_function(lin_b, y[b], x0=x0[b], tol=rtol[b], atol=atol[b], maxiter=max_iter[b], callback=count_callback) + x, ret_val = scipy_function(lin_b, y[b], x0=x0[b], tol=0, atol=np.sqrt(tol_sq[b]), maxiter=max_iter[-1, b], callback=count_callback) # ret_val: 0=success, >0=not converged, <0=error xs.append(x) converged.append(ret_val == 0) diverged.append(ret_val < 0 or np.any(~np.isfinite(x))) x = np.stack(xs) f_eval = [i + 1 for i in iterations] - return SolveResult(f'scipy.sparse.linalg.{scipy_function.__name__}', x, None, iterations, f_eval, converged, diverged, "") + return SolveResult(f'scipy.sparse.linalg.{scipy_function.__name__}', x, None, iterations, f_eval, converged, diverged, [""] * batch_size) def matrix_solve_least_squares(self, matrix: TensorType, rhs: TensorType) -> TensorType: solution, residuals, rank, singular_values = [], [], [], [] diff --git a/phi/tf/_tf_backend.py b/phi/tf/_tf_backend.py index 8e807cbf6..64486b1ff 100644 --- a/phi/tf/_tf_backend.py +++ b/phi/tf/_tf_backend.py @@ -286,7 +286,7 @@ def cumsum(self, x, axis: int): with tf.device(x.device): return tf.cumsum(x, axis=axis, exclusive=False) - def while_loop(self, loop: Callable, values: tuple, max_iter=None): + def while_loop(self, loop: Callable, values: tuple, max_iter: int or Tuple[int, ...] or List[int]): with self._device_for(*values): if isinstance(max_iter, (tuple, list)): # stack traced trajectory, unroll until max_iter values = self.stop_gradient_tree(values) @@ -302,7 +302,7 @@ def while_loop(self, loop: Callable, values: tuple, max_iter=None): return self.stop_gradient_tree(self.stack_leaves(trj)) else: cond = lambda c, *vals: tf.reduce_any(tf.cast(c, tf.bool)) - return tf.while_loop(cond, loop, values, maximum_iterations=max_iter, back_prop=False) + return self.stop_gradient_tree(tf.while_loop(cond, loop, values, maximum_iterations=max_iter)) def stop_gradient_tree(self, tree): return tf.nest.map_structure(tf.stop_gradient, tree) diff --git a/phi/torch/_torch_backend.py b/phi/torch/_torch_backend.py index 89e5b81e2..8b5b33380 100644 --- a/phi/torch/_torch_backend.py +++ b/phi/torch/_torch_backend.py @@ -401,7 +401,7 @@ def get_diagonal(self, matrices, offset=0): def cumsum(self, x, axis: int): return torch.cumsum(x, dim=axis) - def while_loop(self, loop: Callable, values: tuple, max_iter=None): + def while_loop(self, loop: Callable, values: tuple, max_iter: int or Tuple[int, ...] or List[int]): tracing = torch._C._get_tracing_state() is not None if not tracing: return Backend.while_loop(self, loop, values, max_iter) @@ -716,32 +716,36 @@ def mul_csr_dense(self, column_indices, row_pointers, values, shape: tuple, dens # # tile # raise NotImplementedError - - def conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: - if callable(lin) or trj: + def conjugate_gradient(self, lin, y, x0, tol_sq, max_iter) -> SolveResult: + if callable(lin) or len(max_iter) > 1: assert self.is_available(y), "Tracing conjugate_gradient with linear operator is not yet supported." - return Backend.conjugate_gradient(self, lin, y, x0, rtol, atol, max_iter, trj) + return Backend.conjugate_gradient(self, lin, y, x0, tol_sq, max_iter) assert isinstance(lin, torch.Tensor), "Batched matrices are not yet supported" + batch_size = self.staticshape(y)[0] y = self.to_float(y) x0 = self.copy(self.to_float(x0)) - rtol = self.as_tensor(rtol) - atol = self.as_tensor(atol) - max_iter = self.as_tensor(max_iter) - x, residual, iterations, function_evaluations, converged, diverged = torch_sparse_cg(lin, y, x0, rtol, atol, max_iter) - return SolveResult(f"Φ-Flow CG ({'PyTorch*' if self.is_available(y) else 'TorchScript'})", x, residual, iterations, function_evaluations, converged, diverged, "") - - def conjugate_gradient_adaptive(self, lin, y, x0, rtol, atol, max_iter, trj: bool) -> SolveResult or List[SolveResult]: - if callable(lin) or trj: + tol_sq = self.as_tensor(tol_sq) + max_iter = self.as_tensor(max_iter[0]) + x, residual, iterations, function_evaluations, converged, diverged = torch_sparse_cg(lin, y, x0, tol_sq, max_iter) + return SolveResult(f"Φ-Flow CG ({'PyTorch*' if self.is_available(y) else 'TorchScript'})", x, residual, iterations, function_evaluations, converged, diverged, [""] * batch_size) + + def conjugate_gradient_adaptive(self, lin, y, x0, tol_sq, max_iter) -> SolveResult: + if callable(lin) or len(max_iter) > 1: assert self.is_available(y), "Tracing conjugate_gradient with linear operator is not yet supported." - return Backend.conjugate_gradient_adaptive(self, lin, y, x0, rtol, atol, max_iter, trj) + return Backend.conjugate_gradient_adaptive(self, lin, y, x0, tol_sq, max_iter) assert isinstance(lin, torch.Tensor), "Batched matrices are not yet supported" + batch_size = self.staticshape(y)[0] y = self.to_float(y) x0 = self.copy(self.to_float(x0)) - rtol = self.as_tensor(rtol) - atol = self.as_tensor(atol) - max_iter = self.as_tensor(max_iter) - x, residual, iterations, function_evaluations, converged, diverged = torch_sparse_cg_adaptive(lin, y, x0, rtol, atol, max_iter) - return SolveResult(f"Φ-Flow CG ({'PyTorch*' if self.is_available(y) else 'TorchScript'})", x, residual, iterations, function_evaluations, converged, diverged, "") + tol_sq = self.as_tensor(tol_sq) + max_iter = self.as_tensor(max_iter[0]) + x, residual, iterations, function_evaluations, converged, diverged = torch_sparse_cg_adaptive(lin, y, x0, tol_sq, max_iter) + return SolveResult(f"Φ-Flow CG ({'PyTorch*' if self.is_available(y) else 'TorchScript'})", x, residual, iterations, function_evaluations, converged, diverged, [""] * batch_size) + + def bi_conjugate_gradient(self, lin, y, x0, tol_sq, max_iter, poly_order=2) -> SolveResult: + if not self.is_available(y): + warnings.warn("Bi-CG is not optimized for PyTorch and will always run the maximum number of iterations.", RuntimeWarning) + return Backend.bi_conjugate_gradient(self, lin, y, x0, tol_sq, max_iter, poly_order) def matrix_solve_least_squares(self, matrix: TensorType, rhs: TensorType) -> Tuple[TensorType, TensorType, TensorType, TensorType]: assert version.parse(torch.__version__) >= version.parse('1.9.0'), "least squares requires PyTorch >= 1.9.0" @@ -1079,9 +1083,8 @@ def from_torch_dtype(torch_dtype): @torch.jit._script_if_tracing -def torch_sparse_cg(lin, y, x0, rtol, atol, max_iter): +def torch_sparse_cg(lin, y, x0, tolerance_sq, max_iter): batch_size = y.shape[0] - tolerance_sq = torch.maximum(rtol ** 2 * torch.sum(y ** 2, -1), atol ** 2) x = x0 dx = residual = y - sparse_matmul(lin, x) it_counter = torch.tensor(0, dtype=torch.int32, device=x.device) @@ -1112,9 +1115,8 @@ def torch_sparse_cg(lin, y, x0, rtol, atol, max_iter): @torch.jit._script_if_tracing -def torch_sparse_cg_adaptive(lin, y, x0, rtol, atol, max_iter): +def torch_sparse_cg_adaptive(lin, y, x0, tolerance_sq, max_iter): batch_size = y.shape[0] - tolerance_sq = torch.maximum(rtol ** 2 * torch.sum(y ** 2, -1), atol ** 2) x = x0 dx = residual = y - sparse_matmul(lin, x) it_counter = torch.tensor(0, dtype=torch.int32, device=x.device) diff --git a/tests/commit/math/test__optimize.py b/tests/commit/math/test__optimize.py index 7dcda09fa..bf91cb868 100644 --- a/tests/commit/math/test__optimize.py +++ b/tests/commit/math/test__optimize.py @@ -93,7 +93,7 @@ def test_linear_solve_matrix_tape(self): with math.SolveTape(record_trajectories=True) as solves: x = math.solve_linear(math.jit_compile_linear(partial(math.laplace, padding=extrapolation.ZERO)), y, solve) math.assert_close(x, [[-1.5, -2, -1.5], [-3, -4, -3]], abs_tolerance=1e-3) - assert solves[solve].x.trajectory.size == 3 + assert solves[solve].x.trajectory.size >= 3 math.assert_close(solves[solve].residual.trajectory[-1], 0, abs_tolerance=1e-3) # math.print(solves[solve].x.vector[1]) From 631d5897a18a7f8d21657274c1c201fccdf1f392 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 25 Feb 2023 15:17:08 +0100 Subject: [PATCH 161/170] [doc] Put prerendered notebook output into docs/ --- .github/workflows/update-gh-pages.yml | 2 +- docs/index.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-gh-pages.yml b/.github/workflows/update-gh-pages.yml index 43b624cac..a3be60064 100644 --- a/.github/workflows/update-gh-pages.yml +++ b/.github/workflows/update-gh-pages.yml @@ -35,7 +35,7 @@ jobs: - name: Build static HTML for Jupyter Notebooks run: | jupyter nbconvert --to html --execute --allow-errors docs/*.ipynb - jupyter nbconvert --to html docs/prerendered/*.ipynb + jupyter nbconvert --to html --output-dir docs/ docs/prerendered/*.ipynb - name: Deploy 🚀 uses: JamesIves/github-pages-deploy-action@4.1.4 # See https://github.com/marketplace/actions/deploy-to-github-pages diff --git a/docs/index.md b/docs/index.md index 2158db02f..0e0b76755 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,7 +28,7 @@ * [▶️ Introduction Video](https://youtu.be/YRi_c0v3HKs) * [Differentiable fluid simulations](Fluids_Tutorial.html) -* [Higher-order incompressible fluids](prerendered/HigherOrder_Demo.html) +* [Higher-order incompressible fluids](HigherOrder_Demo.html) * [Batched Obstacles](Batched_Obstacles.html) #### I/O @@ -56,7 +56,7 @@ | Module API | Documentation | |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [phi.vis](phi/vis) | [Visualization](Visualization.md): Plotting, interactive user interfaces
[Dash](Web_Interface.md): Web interface
[Console](ConsoleUI.md): Command line interface | -| [phi.physics](phi/physics)
[phi.physics.advect](phi/physics/advect.html)
[phi.physics.fluid](phi/physics/fluid.html)
[phi.physics.diffuse](phi/physics/diffuse.html)
[phi.physics.flip](phi/physics/flip.html) | [Fluids Tutorial](Fluids_Tutorial.html): Introduction to core classes and fluid-related functions.
[Higher-order schemes](prerendered/Taylor_Green_Comparison.html): Compares the accuracy of various numerial schemes.
[Overview](Physics.md): Domains, built-in physics functions
[Functions for Fluid Simulations](Fluid_Simulation.md): Advection, projection, diffusion | +| [phi.physics](phi/physics)
[phi.physics.advect](phi/physics/advect.html)
[phi.physics.fluid](phi/physics/fluid.html)
[phi.physics.diffuse](phi/physics/diffuse.html)
[phi.physics.flip](phi/physics/flip.html) | [Fluids Tutorial](Fluids_Tutorial.html): Introduction to core classes and fluid-related functions.
[Higher-order schemes](Taylor_Green_Comparison.html): Compares the accuracy of various numerial schemes.
[Overview](Physics.md): Domains, built-in physics functions
[Functions for Fluid Simulations](Fluid_Simulation.md): Advection, projection, diffusion | | [phi.field](phi/field) | [Overview](Fields.md): Grids, particles
[Staggered Grids](Staggered_Grids.html): Data layout, usage
[Reading and Writing Simulation Data](Reading_and_Writing_Data.md)
[Scene Format Specification](Scene_Format_Specification.md): Directory layout, file format | | [phi.geom](phi/geom) | [Overview](Geometry.md): Differentiable Geometry | | [phi.math](phi/math)
[phi.math.backend](phi/math/backend)
[phi.math.extrapolation](phi/math/extrapolation.html)
[phi.math.magic](phi/math/magic.html) | [Overview](Math.html): Named dimensions, backends, indexing, non-uniform tensors, precision
[Optimization and Training](Optimization.md): Automatic differentiation, neural network training
[Performance](GPU_Execution.md): GPU, JIT compilation, profiler | From e892221c177938120edac14aa29e73a50c4ac320 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 25 Feb 2023 15:41:05 +0100 Subject: [PATCH 162/170] [doc] Fix broken links --- README.md | 2 +- docs/Animations.ipynb | 6 +++--- docs/Fields.md | 2 +- docs/Learn_to_Throw_Tutorial.ipynb | 2 +- docs/Optimization.md | 29 +---------------------------- docs/Package_Info.md | 2 +- docs/Physics.md | 8 ++++---- docs/Planets_Tutorial.ipynb | 2 +- docs/index.md | 6 +++--- 9 files changed, 16 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index fcdcc84fd..f405b1ca1 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ This will check for compatible PyTorch, Jax and TensorFlow installations as well [**Documentation Overview**](https://tum-pbs.github.io/PhiFlow/)   •   [**▶ YouTube Tutorials**](https://www.youtube.com/playlist?list=PLYLhRkuWBmZ5R6hYzusA2JBIUPFEE755O)   •   [**API**](https://tum-pbs.github.io/PhiFlow/phi/) -  •   [**Demos**](https://github.com/tum-pbs/PhiFlow/tree/develop/demos) +  •   [**Demos**](https://github.com/tum-pbs/PhiFlow/tree/master/demos)   •   [ **Playground**](https://colab.research.google.com/drive/1zBlQbmNguRt-Vt332YvdTqlV4DBcus2S#offline=true&sandboxMode=true) To get started, check out our YouTube tutorial series and the following Jupyter notebooks: diff --git a/docs/Animations.ipynb b/docs/Animations.ipynb index f23bed0e2..af61aa053 100644 --- a/docs/Animations.ipynb +++ b/docs/Animations.ipynb @@ -24,7 +24,7 @@ "source": [ "# ΦFlow Animation Gallery\n", "\n", - "[GitHub](https://github.com/tum-pbs/PhiFlow)   •   [Documentation](https://tum-pbs.github.io/PhiFlow/)   •   [API](https://tum-pbs.github.io/PhiFlow/phi)   •   [Demos](https://github.com/tum-pbs/PhiFlow/tree/develop/demos)\n", + "[GitHub](https://github.com/tum-pbs/PhiFlow)   •   [Documentation](https://tum-pbs.github.io/PhiFlow/)   •   [API](https://tum-pbs.github.io/PhiFlow/phi)   •   [Demos](https://github.com/tum-pbs/PhiFlow/tree/master/demos)\n", "\n", "[![Google Collab Book](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tum-pbs/PhiFlow/blob/master/docs/Animations.ipynb)\n", "\n", @@ -29103,7 +29103,7 @@ "\n", "$$\\frac{\\partial v}{\\partial t} = \\nu \\frac{\\partial^2 v}{\\partial x^2} - v \\frac{\\partial v}{\\partial x}.$$\n", "\n", - "Here, we simulate Burgers' equation on a $64^2$ grid for 100 time steps with $\\Delta t = 0.5$, starting with a randomly generated initial condition. The evolution is plotted as a vector field. A standalone demo of Burgers' equation is also available [here](https://github.com/tum-pbs/PhiFlow/blob/develop/demos/burgers.py)." + "Here, we simulate Burgers' equation on a $64^2$ grid for 100 time steps with $\\Delta t = 0.5$, starting with a randomly generated initial condition. The evolution is plotted as a vector field. A standalone demo of Burgers' equation is also available [here](https://github.com/tum-pbs/PhiFlow/blob/master/demos/burgers.py)." ], "metadata": { "id": "Sc1rmNXLbg5l", @@ -40887,7 +40887,7 @@ "\n", "into advection, diffusion and pressure projection but will rely purely on numerical diffusion in this example.\n", "Starting from a random initial conditions, the fluid is simulated for 40 time steps and the vorticity $w = \\nabla \\times v$ and the pressure $p$ are shown.\n", - "Also check out the [tutorial notebook](https://tum-pbs.github.io/PhiFlow/Fluids_Tutorial.html) or the [standalone](https://github.com/tum-pbs/PhiFlow/blob/develop/demos/smoke_plume.py) [Python](https://github.com/tum-pbs/PhiFlow/blob/develop/demos/karman_vortex_street.py) [scripts](https://github.com/tum-pbs/PhiFlow/blob/develop/demos/fluid_logo.py).\n" + "Also check out the [tutorial notebook](https://tum-pbs.github.io/PhiFlow/Fluids_Tutorial.html) or the [standalone](https://github.com/tum-pbs/PhiFlow/blob/master/demos/smoke_plume.py) [Python](https://github.com/tum-pbs/PhiFlow/blob/master/demos/karman_vortex_street.py) [scripts](https://github.com/tum-pbs/PhiFlow/blob/master/demos/fluid_logo.py).\n" ], "metadata": { "id": "_sS4zf3Wa-9F", diff --git a/docs/Fields.md b/docs/Fields.md index 5ecf60fb1..61fc35590 100644 --- a/docs/Fields.md +++ b/docs/Fields.md @@ -134,4 +134,4 @@ The following example uses 0 for the upper face along `y` and 1 everywhere else. ```python zero_top = extrapolation.combine_sides(x=extrapolation.ONE, y=(extrapolation.ONE, extrapolation.ZERO)) ``` -For a full example, see the [pipe demo](https://github.com/tum-pbs/PhiFlow/blob/develop/demos/pipe.py). \ No newline at end of file +For a full example, see the [pipe demo](https://github.com/tum-pbs/PhiFlow/blob/master/demos/pipe.py). \ No newline at end of file diff --git a/docs/Learn_to_Throw_Tutorial.ipynb b/docs/Learn_to_Throw_Tutorial.ipynb index d2f5c8883..60ba1cd4a 100644 --- a/docs/Learn_to_Throw_Tutorial.ipynb +++ b/docs/Learn_to_Throw_Tutorial.ipynb @@ -18,7 +18,7 @@ "[**Φ-Flow**](https://github.com/tum-pbs/PhiFlow)\n", "    [**Documentation**](https://tum-pbs.github.io/PhiFlow/)\n", "    [**API**](https://tum-pbs.github.io/PhiFlow/phi)\n", - "    [**Demos**](https://github.com/tum-pbs/PhiFlow/tree/develop/demos)" + "    [**Demos**](https://github.com/tum-pbs/PhiFlow/tree/master/demos)" ] }, { diff --git a/docs/Optimization.md b/docs/Optimization.md index cbb75f111..c8adaa529 100644 --- a/docs/Optimization.md +++ b/docs/Optimization.md @@ -1,4 +1,4 @@ -# Optimization and Training +# Optimization and Linear Systems of Equations The backends PyTorch, TensorFlow and Jax have built-in automatic differentiation functionality. However, the respective APIs vary widely in how the gradients are computed. ΦFlow seeks to unify optimization and gradient computation so that code written against the ΦFlow API will work with all backends. @@ -129,30 +129,3 @@ Unfortunately all supported backends have a different approach to computing grad TensorFlow and PyTorch include various optimizers for neural network (NN) training. Additionally, NN variables created through the respective layer functions are typically marked as variables by default, meaning the computational graph for derived tensors is created automatically. - -### PyTorch Neural Network -The following script shows how a PyTorch neural network can be trained. -See the demo [network_training_pytorch.py](https://github.com/tum-pbs/PhiFlow/blob/master/demos/network_training_pytorch.py) -for a full example. -```python -net = u_net(2, 2) -optimizer = optim.Adam(net.parameters(), lr=1e-3) - -for training_step in range(100): - data: Grid = load_training_data(training_step) - optimizer.zero_grad() - prediction: Grid = field.native_call(net, data) - simulation_output: Grid = simulate(prediction) - loss = field.l2_loss(simulation_output) - loss.native().backward() - optimizer.step() -``` -In the above example, [`field.native_call()`](phi/field/#phi.field.native_call) -extracts the field values as PyTorch tensors with shape `(batch_size, channels, spatial...)`, -then calls the network and returs the result again as a `Field`. - -Since `loss` is a `phi.math.Tensor`, we need to invoke `native()` to call PyTorch's `backward()` function. - -### TensorFlow Neural Network -For TensorFlow, a `GradientTape` context is required around network evaluation, physics and loss definition. - diff --git a/docs/Package_Info.md b/docs/Package_Info.md index 4658fc537..143b88294 100644 --- a/docs/Package_Info.md +++ b/docs/Package_Info.md @@ -3,7 +3,7 @@ [**Homepage**](https://github.com/tum-pbs/PhiFlow)     [**Documentation**](https://tum-pbs.github.io/PhiFlow/)     [**API**](https://tum-pbs.github.io/PhiFlow/phi) -    [**Demos**](https://github.com/tum-pbs/PhiFlow/tree/develop/demos) +    [**Demos**](https://github.com/tum-pbs/PhiFlow/tree/master/demos)     [ **Fluids Tutorial**](https://colab.research.google.com/github/tum-pbs/PhiFlow/blob/develop/docs/Fluids_Tutorial.ipynb#offline=true&sandboxMode=true)     [ **Playground**](https://colab.research.google.com/drive/1zBlQbmNguRt-Vt332YvdTqlV4DBcus2S#offline=true&sandboxMode=true) diff --git a/docs/Physics.md b/docs/Physics.md index 3d4fdca18..5d11aea4a 100644 --- a/docs/Physics.md +++ b/docs/Physics.md @@ -34,10 +34,10 @@ for _ in ModuleViewer().range(100): This launches a web interface displaying the velocity and pressure fields and allows you to step through the simulation step by step. Slightly more complex examples can be found in -[marker.py](../demos/marker.py) which passively advects an additional marker field, -[smoke_plume.py](../demos/smoke_plume.py) which additionally introduces a buoyancy force, -[fluid_logo.py](../demos/fluid_logo.py) which adds obstacles to the scene and -[rotating_bar.py](../demos/rotating_bar.py) which adds geometry movement. +[marker.py](https://github.com/tum-pbs/PhiFlow/blob/master/demos/marker.py) which passively advects an additional marker field, +[smoke_plume.py](https://github.com/tum-pbs/PhiFlow/blob/master/demos/smoke_plume.py) which additionally introduces a buoyancy force, +[fluid_logo.py](https://github.com/tum-pbs/PhiFlow/blob/master/demos/fluid_logo.py) which adds obstacles to the scene and +[rotating_bar.py](https://github.com/tum-pbs/PhiFlow/blob/master/demos/rotating_bar.py) which adds geometry movement. ## Differences to MantaFlow diff --git a/docs/Planets_Tutorial.ipynb b/docs/Planets_Tutorial.ipynb index b69eaeb9b..d1b087f77 100644 --- a/docs/Planets_Tutorial.ipynb +++ b/docs/Planets_Tutorial.ipynb @@ -19,7 +19,7 @@ "[**Φ-Flow**](https://github.com/tum-pbs/PhiFlow)\n", "    [**Documentation**](https://tum-pbs.github.io/PhiFlow/)\n", "    [**API**](https://tum-pbs.github.io/PhiFlow/phi)\n", - "    [**Demos**](https://github.com/tum-pbs/PhiFlow/tree/develop/demos)" + "    [**Demos**](https://github.com/tum-pbs/PhiFlow/tree/master/demos)" ] }, { diff --git a/docs/index.md b/docs/index.md index 0e0b76755..0f9218527 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,7 @@ [**Homepage**](https://github.com/tum-pbs/PhiFlow)     [**API**](phi) -    [**Demos**](https://github.com/tum-pbs/PhiFlow/tree/develop/demos) +    [**Demos**](https://github.com/tum-pbs/PhiFlow/tree/master/demos)        [ **Playground**](https://colab.research.google.com/drive/1zBlQbmNguRt-Vt332YvdTqlV4DBcus2S#offline=true&sandboxMode=true) @@ -56,7 +56,7 @@ | Module API | Documentation | |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [phi.vis](phi/vis) | [Visualization](Visualization.md): Plotting, interactive user interfaces
[Dash](Web_Interface.md): Web interface
[Console](ConsoleUI.md): Command line interface | -| [phi.physics](phi/physics)
[phi.physics.advect](phi/physics/advect.html)
[phi.physics.fluid](phi/physics/fluid.html)
[phi.physics.diffuse](phi/physics/diffuse.html)
[phi.physics.flip](phi/physics/flip.html) | [Fluids Tutorial](Fluids_Tutorial.html): Introduction to core classes and fluid-related functions.
[Higher-order schemes](Taylor_Green_Comparison.html): Compares the accuracy of various numerial schemes.
[Overview](Physics.md): Domains, built-in physics functions
[Functions for Fluid Simulations](Fluid_Simulation.md): Advection, projection, diffusion | +| [phi.physics](phi/physics)
[phi.physics.advect](phi/physics/advect.html)
[phi.physics.fluid](phi/physics/fluid.html)
[phi.physics.diffuse](phi/physics/diffuse.html) | [Fluids Tutorial](Fluids_Tutorial.html): Introduction to core classes and fluid-related functions.
[Higher-order schemes](Taylor_Green_Comparison.html): Compares the accuracy of various numerial schemes.
[Overview](Physics.md): Domains, built-in physics functions
[Functions for Fluid Simulations](Fluid_Simulation.md): Advection, projection, diffusion | | [phi.field](phi/field) | [Overview](Fields.md): Grids, particles
[Staggered Grids](Staggered_Grids.html): Data layout, usage
[Reading and Writing Simulation Data](Reading_and_Writing_Data.md)
[Scene Format Specification](Scene_Format_Specification.md): Directory layout, file format | | [phi.geom](phi/geom) | [Overview](Geometry.md): Differentiable Geometry | | [phi.math](phi/math)
[phi.math.backend](phi/math/backend)
[phi.math.extrapolation](phi/math/extrapolation.html)
[phi.math.magic](phi/math/magic.html) | [Overview](Math.html): Named dimensions, backends, indexing, non-uniform tensors, precision
[Optimization and Training](Optimization.md): Automatic differentiation, neural network training
[Performance](GPU_Execution.md): GPU, JIT compilation, profiler | @@ -81,4 +81,4 @@ This requires PyTorch, TensorFlow and Jax to be installed, in addition to the st Contributions are welcome! -If you have changes to merge, check out our [style guide](https://github.com/tum-pbs/PhiFlow/blob/develop/CONTRIBUTING.md) before opening a pull request. +If you have changes to merge, check out our [style guide](https://github.com/tum-pbs/PhiFlow/blob/master/CONTRIBUTING.md) before opening a pull request. From 3367344d7b87bb5058b9eec008e8d00c1d73fbb0 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sat, 25 Feb 2023 15:41:49 +0100 Subject: [PATCH 163/170] [field] Disable sparse matrix generation for implicit field ops * Add implicit field operation disclaimer * Update notebooks * Disable unit test --- docs/prerendered/HigherOrder_Demo.ipynb | 4 +- .../prerendered/Taylor_Green_Comparison.ipynb | 7 ++-- phi/field/_field_math.py | 10 ++++- tests/commit/field/test__field_math.py | 42 +++++++++---------- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/docs/prerendered/HigherOrder_Demo.ipynb b/docs/prerendered/HigherOrder_Demo.ipynb index e7ecdbb6a..cdde36e21 100644 --- a/docs/prerendered/HigherOrder_Demo.ipynb +++ b/docs/prerendered/HigherOrder_Demo.ipynb @@ -14,9 +14,9 @@ "[![Google Collab Book](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/eloasdjo/PhiFlow/blob/higher_order_demo.ipynb)\n", "\n", "This notebook shows how to write a higher-order incompressible fluid simulation, simulating a Kolmogorov flow.\n", - "Higher-order finite difference schemes are available in ΦFlow 2.3 and newer (only with periodic boundary conditions as of 2.3.0).\n", + "Higher-order finite difference schemes are available since ΦFlow 2.3.\n", "\n", - "\n" + "**Warning:** Higher-order solvers are experimental in version 2.3. Only periodic boundary conditions are supported and the 6th-order implicit schemes does not yet support automatic matrix generation.\n" ] }, { diff --git a/docs/prerendered/Taylor_Green_Comparison.ipynb b/docs/prerendered/Taylor_Green_Comparison.ipynb index 243510f6c..e7b9e5e3c 100644 --- a/docs/prerendered/Taylor_Green_Comparison.ipynb +++ b/docs/prerendered/Taylor_Green_Comparison.ipynb @@ -16,6 +16,8 @@ "This notebook compares the accuracy of various numerical schemes on the [Taylor-Green Vortex](https://en.wikipedia.org/wiki/Taylor_Green_vortex),\n", "from semi-Lagrangian advection up to 6th order compact schemes.\n", "\n", + "**Warning:** Higher-order solvers are experimental in version 2.3. Only periodic boundary conditions are supported and the 6th-order implicit schemes does not yet support automatic matrix generation.\n", + "\n", "If [ΦFlow](https://github.com/tum-pbs/PhiFlow) 2.3 or newer is not already installed, uncomment the first line in the cell below." ] }, @@ -36,7 +38,7 @@ "from tqdm.notebook import trange\n", "from phi.jax.flow import *\n", "\n", - "math.set_global_precision(64) # double precision for all operations" + "math.set_global_precision(64) # double precision for all following operations" ] }, { @@ -294,11 +296,10 @@ " analytic_v = StaggeredGrid(partial(taylor_green_velocity, t=times), **domain)\n", " analytic_p = CenteredGrid(partial(taylor_green_pressure, t=times), **domain)\n", " v, p = analytic_v.time[0], analytic_p.time[0]\n", - " step_function(v, p, dt) # jit-compile function before measuring the execution time\n", " (sim_v, _), exec_times = iterate(step_function, times.shape - 1, v, p, dt=dt, measure=time.perf_counter)\n", " rmse = math.sqrt(math.mean((analytic_v - sim_v).values**2))\n", " relative_err = rmse / math.mean(abs(analytic_v.values))\n", - " return relative_err, exec_times.mean\n", + " return relative_err, exec_times.time[1:].mean # ignore jit compilation time\n", "\n", "resolutions = wrap([8, 16, 32, 64, 128], batch(resolution='8, 16, 32, 64, 128'))\n", "errors, exec_times = math.map(eval_error, resolutions, methods, range=trange)" diff --git a/phi/field/_field_math.py b/phi/field/_field_math.py index 2bf6c8b80..289bd4c54 100644 --- a/phi/field/_field_math.py +++ b/phi/field/_field_math.py @@ -1,10 +1,11 @@ +import warnings from numbers import Number from typing import Callable, List, Tuple, Optional from phi import geom from phi import math from phi.geom import Box, Geometry -from phi.math import Tensor, spatial, instance, tensor, channel, Shape, unstack, solve_linear, jit_compile_linear, shape, Solve, extrapolation +from phi.math import Tensor, spatial, instance, tensor, channel, Shape, unstack, solve_linear, jit_compile_linear, shape, Solve, extrapolation, jit_compile from ._field import Field, SampledField, SampledFieldType, as_extrapolation from ._grid import CenteredGrid, Grid, StaggeredGrid, GridType from ._point_cloud import PointCloud @@ -60,6 +61,8 @@ def laplace(field: GridType, Returns: laplacian field as `CenteredGrid` """ + if implicit: + warnings.warn("Implicit operators currently do not support sparse matrix generation and may be slow.", RuntimeWarning, stacklevel=2) if isinstance(weights, Field): weights = weights.at(field).values axes_names = field.shape.only(axes).names @@ -131,6 +134,8 @@ def spatial_gradient(field: CenteredGrid, Returns: spatial_gradient field of type `type`. """ + if implicit: + warnings.warn("Implicit operators currently do not support sparse matrix generation and may be slow.", RuntimeWarning, stacklevel=2) if gradient_extrapolation is None: gradient_extrapolation = field.extrapolation.spatial_gradient() extrap_map = {} @@ -205,7 +210,8 @@ def f(ext: Extrapolation): return f -@jit_compile_linear(auxiliary_args="values_rhs, needed_shifts_rhs, stack_dim, staggered_output") +# @jit_compile_linear(auxiliary_args="values_rhs, needed_shifts_rhs, stack_dim, staggered_output") +@jit_compile(auxiliary_args="values_rhs, needed_shifts_rhs, stack_dim, staggered_output") # ToDo the matrix generation gives incorrect results in 2.3.0 def _lhs_for_implicit_scheme(x, values_rhs, needed_shifts_rhs, stack_dim, staggered_output=False): result = [] for dim, component in zip(x.shape.only(math.spatial).names, unstack(x, stack_dim.name)): diff --git a/tests/commit/field/test__field_math.py b/tests/commit/field/test__field_math.py index 5101c5635..f16f05789 100644 --- a/tests/commit/field/test__field_math.py +++ b/tests/commit/field/test__field_math.py @@ -245,25 +245,25 @@ def test_mask(self): mask = field.mask(CenteredGrid(0, x=4, y=3)) self.assertEqual(2, mask.spatial_rank) - def test_implicit_laplace_solve(self): - grid = CenteredGrid(Noise(), x=5, y=5) - axes_names = grid.shape.only(spatial).names - extrap_map = {} - extrap_map_rhs = {} - values, needed_shifts = [3 / 44, 12 / 11, -51 / 22, 12 / 11, 3 / 44], (-2, -1, 0, 1, 2) - extrap_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) - values_rhs, needed_shifts_rhs = [2 / 11, 1, 2 / 11], (-1, 0, 1) - extrap_map_rhs['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) - base_widths = (abs(min(needed_shifts)), max(needed_shifts)) - grid.with_extrapolation(extrapolation.map(_ex_map_f(extrap_map), grid.extrapolation)) - padded_components = [pad(grid, {dim: base_widths}) for dim in axes_names] - shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, axes_names)] - result_components = [sum([value * shift_ for value, shift_ in zip(values, shifted_component)]) / grid.dx.vector[dim] ** 2 for shifted_component, dim in zip(shifted_components, axes_names)] - result_components = stack(result_components, channel('laplacian')) - result_components.with_values(result_components.values._cache()) - result_components = result_components.with_extrapolation(extrapolation.map(_ex_map_f(extrap_map_rhs), grid.extrapolation)) - matrix, _ = math.matrix_from_function(_lhs_for_implicit_scheme, result_components, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=channel('laplacian')) - direct_result = _lhs_for_implicit_scheme(result_components, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=channel('laplacian')) - matrix_result = matrix @ result_components.values - math.assert_close(matrix_result, direct_result) + # def test_implicit_laplace_solve(self): + # grid = CenteredGrid(Noise(), x=5, y=5) + # axes_names = grid.shape.only(spatial).names + # extrap_map = {} + # extrap_map_rhs = {} + # values, needed_shifts = [3 / 44, 12 / 11, -51 / 22, 12 / 11, 3 / 44], (-2, -1, 0, 1, 2) + # extrap_map['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) + # values_rhs, needed_shifts_rhs = [2 / 11, 1, 2 / 11], (-1, 0, 1) + # extrap_map_rhs['symmetric'] = combine_by_direction(REFLECT, SYMMETRIC) + # base_widths = (abs(min(needed_shifts)), max(needed_shifts)) + # grid.with_extrapolation(extrapolation.map(_ex_map_f(extrap_map), grid.extrapolation)) + # padded_components = [pad(grid, {dim: base_widths}) for dim in axes_names] + # shifted_components = [shift(padded_component, needed_shifts, None, pad=False, dims=dim) for padded_component, dim in zip(padded_components, axes_names)] + # result_components = [sum([value * shift_ for value, shift_ in zip(values, shifted_component)]) / grid.dx.vector[dim] ** 2 for shifted_component, dim in zip(shifted_components, axes_names)] + # result_components = stack(result_components, channel('laplacian')) + # result_components.with_values(result_components.values._cache()) + # result_components = result_components.with_extrapolation(extrapolation.map(_ex_map_f(extrap_map_rhs), grid.extrapolation)) + # matrix, _ = math.matrix_from_function(_lhs_for_implicit_scheme, result_components, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=channel('laplacian')) + # direct_result = _lhs_for_implicit_scheme(result_components, values_rhs=values_rhs, needed_shifts_rhs=needed_shifts_rhs, stack_dim=channel('laplacian')) + # matrix_result = matrix @ result_components.values + # math.assert_close(matrix_result, direct_result) From df2838a4484765bf9dfcfc25071ef01f9ec7c02f Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 26 Feb 2023 13:42:01 +0100 Subject: [PATCH 164/170] [doc] Clear widget state in prerendered notebooks, update links --- docs/prerendered/HigherOrder_Demo.ipynb | 2843 +- .../prerendered/Taylor_Green_Comparison.ipynb | 23129 +++++++++++++++- 2 files changed, 25417 insertions(+), 555 deletions(-) diff --git a/docs/prerendered/HigherOrder_Demo.ipynb b/docs/prerendered/HigherOrder_Demo.ipynb index cdde36e21..1fd363465 100644 --- a/docs/prerendered/HigherOrder_Demo.ipynb +++ b/docs/prerendered/HigherOrder_Demo.ipynb @@ -11,7 +11,7 @@ "source": [ "# Higher-order Fluid Simulations on Periodic Boundaries with ΦFlow\n", "\n", - "[![Google Collab Book](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/eloasdjo/PhiFlow/blob/higher_order_demo.ipynb)\n", + "[![Google Collab Book](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tum-pbs/PhiFlow/blob/develop/docs/prerendered/HigherOrder_Demo.ipynb)\n", "\n", "This notebook shows how to write a higher-order incompressible fluid simulation, simulating a Kolmogorov flow.\n", "Higher-order finite difference schemes are available since ΦFlow 2.3.\n", @@ -21,6 +21,12 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "aZ7ayjSaccOh", + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "## Higher-order Schemes in ΦFlow\n", "\n", @@ -57,34 +63,28 @@ " \n", "\n", "" - ], - "metadata": { - "id": "aZ7ayjSaccOh", - "pycharm": { - "name": "#%% md\n" - } - } + ] }, { "cell_type": "markdown", - "source": [ - "If [ΦFlow](https://github.com/tum-pbs/PhiFlow) 2.3 or newer is not already installed, uncomment the first line in the cell below." - ], "metadata": { "id": "dwjWUYgxcaMZ", "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "If [ΦFlow](https://github.com/tum-pbs/PhiFlow) 2.3 or newer is not already installed, uncomment the first line in the cell below." + ] }, { "cell_type": "code", "execution_count": 1, "metadata": { - "id": "-NuGPniRlX3u", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "-NuGPniRlX3u", "outputId": "f204a947-7a92-47b8-cbe4-40f41bafaca0", "pycharm": { "name": "#%%\n" @@ -118,27 +118,24 @@ }, { "cell_type": "code", - "source": [ - "DOMAIN = dict(extrapolation=extrapolation.PERIODIC, bounds=Box(x=2*PI, y=2*PI), x=100, y=100)\n", - "FORCING = StaggeredGrid(lambda x, y: vec(x=math.sin(4 * y), y=0), **DOMAIN) + StaggeredGrid(Noise(), **DOMAIN) * 0.01\n", - "plot({'Force along X': FORCING['x'], 'Force along Y': FORCING['y']}, same_scale=False)" - ], + "execution_count": 2, "metadata": { - "id": "L95qdtYBfbk1", "colab": { "base_uri": "https://localhost:8080/", "height": 386 }, + "id": "L95qdtYBfbk1", "outputId": "b755086c-dc72-4b6b-de10-8d8bd5d79d88", "pycharm": { "name": "#%%\n" } }, - "execution_count": 2, "outputs": [ { "data": { - "text/plain": "
" + "text/plain": [ + "
" + ] }, "execution_count": 2, "metadata": {}, @@ -146,34 +143,49 @@ }, { "data": { - "text/plain": "
", - "image/png": "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\n" + "image/png": "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\n", + "text/plain": [ + "
" + ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } + ], + "source": [ + "DOMAIN = dict(extrapolation=extrapolation.PERIODIC, bounds=Box(x=2*PI, y=2*PI), x=100, y=100)\n", + "FORCING = StaggeredGrid(lambda x, y: vec(x=math.sin(4 * y), y=0), **DOMAIN) + StaggeredGrid(Noise(), **DOMAIN) * 0.01\n", + "plot({'Force along X': FORCING['x'], 'Force along Y': FORCING['y']}, same_scale=False)" ] }, { "cell_type": "markdown", + "metadata": { + "id": "lsr6Ri_9ZPXy", + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "## Simulation\n", "Next we define the momentum equation (PDE) for the incompressible flow.\n", "We use 6th-order implicit advection and diffusion.\n", "The pressure solve is integrated into ΦFlow's 4th-order Runge-Kutta integrator `fluid.incompressible_rk4`. It uses a 4th-order direct scheme to avoid nested linear solves.\n", "For all implicit operations, we use the conjugate gradient method `'CG'` since the periodic boundaries result in symmetric linear equation systems for which CG is fastest." - ], - "metadata": { - "id": "lsr6Ri_9ZPXy", - "pycharm": { - "name": "#%% md\n" - } - } + ] }, { "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "R-e3yX3cZ95Z", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], "source": [ "def momentum_equation(v, viscosity=0.001):\n", " advection = advect.finite_difference(v, v, order=6, implicit=Solve('CG', 1e-5, 1e-5))\n", @@ -183,37 +195,22 @@ "@jit_compile\n", "def rk4_step(v, p, dt):\n", " return fluid.incompressible_rk4(momentum_equation, v, p, dt, pressure_order=4, pressure_solve=Solve('CG', 1e-5, 1e-5))" - ], - "metadata": { - "id": "R-e3yX3cZ95Z", - "pycharm": { - "name": "#%%\n" - } - }, - "execution_count": 3, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "Let's run the simulation!" - ], "metadata": { - "collapsed": false, "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "Let's run the simulation!" + ] }, { "cell_type": "code", - "source": [ - "v0 = StaggeredGrid(0, **DOMAIN)\n", - "p0 = CenteredGrid(0, **DOMAIN)\n", - "multi_step = lambda *x, **kwargs: iterate(rk4_step, 25, *x, **kwargs)\n", - "v_trj, p_trj = iterate(multi_step, batch(time=100), v0, p0, dt=0.005, range=trange)\n", - "vis.plot(field.curl(v_trj.with_extrapolation(0)), animate='time')" - ], + "execution_count": 5, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -225,12 +222,2322 @@ "name": "#%%\n" } }, - "execution_count": 5, "outputs": [ { "data": { - "text/plain": "", - "text/html": "" + "text/html": [ + "" + ], + "text/plain": [ + "" + ] }, "execution_count": 5, "metadata": {}, @@ -238,31 +2545,44 @@ }, { "data": { - "text/plain": "
" + "text/plain": [ + "
" + ] }, "metadata": {}, "output_type": "display_data" } + ], + "source": [ + "v0 = StaggeredGrid(0, **DOMAIN)\n", + "p0 = CenteredGrid(0, **DOMAIN)\n", + "multi_step = lambda *x, **kwargs: iterate(rk4_step, 25, *x, **kwargs)\n", + "v_trj, p_trj = iterate(multi_step, batch(time=100), v0, p0, dt=0.005, range=trange)\n", + "vis.plot(field.curl(v_trj.with_extrapolation(0)), animate='time')" ] }, { "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "## Saving the Simulation Data\n", "\n", "We can store the data in one of two ways: Either we use ΦFlow's built-in field I/O functions, or we store the data using NumPy.\n", "Let's view the NumPy data first." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } + ] }, { "cell_type": "code", "execution_count": 6, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [ { "name": "stdout", @@ -275,32 +2595,30 @@ "source": [ "np_velocity = v_trj.uniform_values().numpy('time,x,y,vector')\n", "print(np_velocity.dtype, np_velocity.shape)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + ] }, { "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "We can write this array using any of NumPy's save functions, such as `np.save, np.savez, np.savez_compressed`.\n", "Note that we called `.uniform_values()` instead of `.values` to get an array that is guaranteed to be NumPy-compatible for all possible boundary conditions.\n", "\n", "Alternatively, we can create a ΦFlow `Scene` object and write the data to it." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } + ] }, { "cell_type": "code", "execution_count": 7, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ "scene = Scene.create('data/')\n", @@ -309,27 +2627,20 @@ "\n", "for i, v_frame in enumerate(v_trj.time): # write each frame into one file\n", " scene.write(velocity=v_frame, frame=i)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + ] }, { "cell_type": "markdown", - "source": [ - "## Comparison to Lower-Order Schemes\n", - "\n", - "An evaluation of accuracy and performance can be found [here](Taylor_Green_Comparison.html)." - ], "metadata": { - "collapsed": false, "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "## Comparison to Lower-Order Schemes\n", + "\n", + "An evaluation of accuracy and performance can be found [here](Taylor_Green_Comparison.html)." + ] } ], "metadata": { @@ -337,7 +2648,7 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -351,355 +2662,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "daa8a904b6824715890d965e9e9a1b08": { - "model_module": "@jupyter-widgets/controls", - "model_name": "HBoxModel", - "model_module_version": "1.5.0", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_f6898b402c62403e8c689a5e18c299d4", - "IPY_MODEL_176186b2c10b4df0a6158c9a23d8758d", - "IPY_MODEL_d91c4f13076b434f82f59fe6a08a6bb4" - ], - "layout": "IPY_MODEL_60db88e473014d83a4767272051961b3" - } - }, - "f6898b402c62403e8c689a5e18c299d4": { - "model_module": "@jupyter-widgets/controls", - "model_name": "HTMLModel", - "model_module_version": "1.5.0", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_c8fa3abfaf4d41acbe520d983d28575c", - "placeholder": "​", - "style": "IPY_MODEL_9975b313c00e4d998768f2513f386c14", - "value": "100%" - } - }, - "176186b2c10b4df0a6158c9a23d8758d": { - "model_module": "@jupyter-widgets/controls", - "model_name": "FloatProgressModel", - "model_module_version": "1.5.0", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_02503916033d47b7b436dce40d019948", - "max": 5000, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_baf769c8c81a43989aa60693a0b65adf", - "value": 5000 - } - }, - "d91c4f13076b434f82f59fe6a08a6bb4": { - "model_module": "@jupyter-widgets/controls", - "model_name": "HTMLModel", - "model_module_version": "1.5.0", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_379e91b0499c46b8a7da1f3e23d68c72", - "placeholder": "​", - "style": "IPY_MODEL_720685a440694d179f972eb9459eb73a", - "value": " 5000/5000 [10:59<00:00, 8.17it/s]" - } - }, - "60db88e473014d83a4767272051961b3": { - "model_module": "@jupyter-widgets/base", - "model_name": "LayoutModel", - "model_module_version": "1.2.0", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "c8fa3abfaf4d41acbe520d983d28575c": { - "model_module": "@jupyter-widgets/base", - "model_name": "LayoutModel", - "model_module_version": "1.2.0", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "9975b313c00e4d998768f2513f386c14": { - "model_module": "@jupyter-widgets/controls", - "model_name": "DescriptionStyleModel", - "model_module_version": "1.5.0", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "02503916033d47b7b436dce40d019948": { - "model_module": "@jupyter-widgets/base", - "model_name": "LayoutModel", - "model_module_version": "1.2.0", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "baf769c8c81a43989aa60693a0b65adf": { - "model_module": "@jupyter-widgets/controls", - "model_name": "ProgressStyleModel", - "model_module_version": "1.5.0", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "379e91b0499c46b8a7da1f3e23d68c72": { - "model_module": "@jupyter-widgets/base", - "model_name": "LayoutModel", - "model_module_version": "1.2.0", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "720685a440694d179f972eb9459eb73a": { - "model_module": "@jupyter-widgets/controls", - "model_name": "DescriptionStyleModel", - "model_module_version": "1.5.0", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - } - } + "version": "3.8.5" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } \ No newline at end of file diff --git a/docs/prerendered/Taylor_Green_Comparison.ipynb b/docs/prerendered/Taylor_Green_Comparison.ipynb index e7b9e5e3c..6373c0b32 100644 --- a/docs/prerendered/Taylor_Green_Comparison.ipynb +++ b/docs/prerendered/Taylor_Green_Comparison.ipynb @@ -11,7 +11,7 @@ "source": [ "# Evaluating Higher-order Fluid Simulations on the Taylor-Green Vortex\n", "\n", - "[![Google Collab Book](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/eloasdjo/PhiFlow/blob/Higher_order_Tutorial.ipynb)\n", + "[![Google Collab Book](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tum-pbs/PhiFlow/blob/develop/docs/prerendered/Taylor_Green_Comparison.ipynb)\n", "\n", "This notebook compares the accuracy of various numerical schemes on the [Taylor-Green Vortex](https://en.wikipedia.org/wiki/Taylor_Green_vortex),\n", "from semi-Lagrangian advection up to 6th order compact schemes.\n", @@ -107,8 +107,12416 @@ "outputs": [ { "data": { - "text/plain": "", - "text/html": "" + "text/html": [ + "" + ], + "text/plain": [ + "" + ] }, "execution_count": 3, "metadata": {}, @@ -116,7 +12524,9 @@ }, { "data": { - "text/plain": "
" + "text/plain": [ + "
" + ] }, "metadata": {}, "output_type": "display_data" @@ -187,10 +12597,10 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "id": "g0WD9yntEQzB", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "g0WD9yntEQzB", "outputId": "f18db300-434a-4bdc-e5f2-011afc6c4d05", "pycharm": { "name": "#%%\n" @@ -199,20 +12609,10491 @@ "outputs": [ { "data": { - "text/plain": " 0%| | 0/200 [00:00", - "text/html": "" + "text/html": [ + "" + ], + "text/plain": [ + "" + ] }, "execution_count": 5, "metadata": {}, @@ -220,7 +23101,9 @@ }, { "data": { - "text/plain": "
" + "text/plain": [ + "
" + ] }, "metadata": {}, "output_type": "display_data" @@ -233,19 +23116,27 @@ }, { "cell_type": "markdown", - "source": [ - "## Benchmark and Comparison\n", - "To compare the higher-order approach to different lower-order methods, we define two additional finite-difference schemes with lower orders and a semi-Lagrangian scheme that uses operator splitting instead of Runge-Kutta 4." - ], "metadata": { "id": "LI3LQpso_DvQ", "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "## Benchmark and Comparison\n", + "To compare the higher-order approach to different lower-order methods, we define two additional finite-difference schemes with lower orders and a semi-Lagrangian scheme that uses operator splitting instead of Runge-Kutta 4." + ] }, { "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "1HnN9_KZuBpq", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], "source": [ "@jit_compile(forget_traces=True)\n", "def semi_lagrangian_step(velocity, pressure, dt, viscosity=0.1):\n", @@ -260,18 +23151,16 @@ " 'Semi-Lagrangian': semi_lagrangian_step,\n", "}, batch('method'))\n", "expected_order = wrap([6, 4, 2, 1], methods.shape)" - ], + ] + }, + { + "cell_type": "markdown", "metadata": { - "id": "1HnN9_KZuBpq", + "id": "ThzE3Q3SE8kl", "pycharm": { - "name": "#%%\n" + "name": "#%% md\n" } }, - "execution_count": 6, - "outputs": [] - }, - { - "cell_type": "markdown", "source": [ "We will use a small step size of Δt = 0.001 to factor out the time advancement and put emphasis on the spatial discretization.\n", "We target a simulation time of 0.5 seconds resulting in 500 frames.\n", @@ -279,87 +23168,82 @@ "We define the function `eval_error` to compute the error for a given method and `resolution` at every time step.\n", "We run it on the four methods defined above with five resolutions each.\n", "Note that this can take 15-20 min to run." - ], - "metadata": { - "id": "ThzE3Q3SE8kl", - "pycharm": { - "name": "#%% md\n" - } - } + ] }, { "cell_type": "code", - "source": [ - "def eval_error(resolution, step_function, dt=0.001, time_sec=.5):\n", - " domain = dict(x=resolution, y=resolution, extrapolation=extrapolation.PERIODIC, bounds=Box(x=2*PI, y=2*PI))\n", - " times = math.linspace(0, time_sec, batch(time=math.round(time_sec/dt)+1))\n", - " analytic_v = StaggeredGrid(partial(taylor_green_velocity, t=times), **domain)\n", - " analytic_p = CenteredGrid(partial(taylor_green_pressure, t=times), **domain)\n", - " v, p = analytic_v.time[0], analytic_p.time[0]\n", - " (sim_v, _), exec_times = iterate(step_function, times.shape - 1, v, p, dt=dt, measure=time.perf_counter)\n", - " rmse = math.sqrt(math.mean((analytic_v - sim_v).values**2))\n", - " relative_err = rmse / math.mean(abs(analytic_v.values))\n", - " return relative_err, exec_times.time[1:].mean # ignore jit compilation time\n", - "\n", - "resolutions = wrap([8, 16, 32, 64, 128], batch(resolution='8, 16, 32, 64, 128'))\n", - "errors, exec_times = math.map(eval_error, resolutions, methods, range=trange)" - ], + "execution_count": 7, "metadata": { "id": "KOKuyn7FU-4c", "pycharm": { "name": "#%%\n" } }, - "execution_count": 7, "outputs": [ { "data": { - "text/plain": " 0%| | 0/20 [00:00" + "text/plain": [ + "
" + ] }, "execution_count": 8, "metadata": {}, @@ -367,36 +23251,48 @@ }, { "data": { - "text/plain": "
", - "image/png": "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\n" + "image/png": "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\n", + "text/plain": [ + "
" + ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } + ], + "source": [ + "plot(errors.resolution.as_channel().time.as_spatial(), log_dims='_')" ] }, { "cell_type": "markdown", - "source": [ - "Next we visualise the final error with respect to the resolution in a log-log plot. This allows us to easily see the order of accuracy.\n", - "The dark lines correspond to the errors of our four different simulation schemes and each one is accompanied by a theoretical convergence line (orders 6, 4, 2 and 1 for the four methods) in a lighter color." - ], "metadata": { "id": "fTrKTdDTcB3g", "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "Next we visualise the final error with respect to the resolution in a log-log plot. This allows us to easily see the order of accuracy.\n", + "The dark lines correspond to the errors of our four different simulation schemes and each one is accompanied by a theoretical convergence line (orders 6, 4, 2 and 1 for the four methods) in a lighter color." + ] }, { "cell_type": "code", "execution_count": 9, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [ { "data": { - "text/plain": "
" + "text/plain": [ + "
" + ] }, "execution_count": 9, "metadata": {}, @@ -404,8 +23300,10 @@ }, { "data": { - "text/plain": "
", - "image/png": "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t3Pd+5s5nlu7jqevuwsvXDyKN/8xlvT8Ui787xpe/GU35XVUrOgopN2GlZTTJmHOrNv50hIcAwMJW7O60fMef/xxPv74YxwdHXFwcOCNN96oVpOvJUycOJG//vqr1vFFixbh6enJv//971aN3568/vrruLu7c8UVV3T2VASh2xAW4sWymyby3tr9PPvTLqY9v4p7zo3gkpMG4OBQvaLMtKjenDTIn0XfJPLCLyn8lJTKc38bSXhv73pGbz9EoKy0pTg1dbx169axcuVKtm7diqurK5mZmZSV1d3yuTnUJU7thclkwsmp7f4zqiupWBCE1uPooLhu0hDOjgzh7mXx3PtlPF/vOMpTc2IZGFC9hJmfhwsvXjKac6L7cP+KeC54eQ23nTmc+VOG4uTYcQtvssTXiRw/fpzAwMCKunuBgYH07duXLVu2MGXKFMaOHcv06dM5ftxoAT116lTuuOMOJk+eTEREBJs2bWL27NkMHz6c+++/v2JcT0/PZs1j5syZjB07lqioKN58882K4++88w5hYWFMnTqV66+/nltuuQWAq666ijvvvJPTTz+dhQsXsnHjRiZOnMjo0aOZOHEiu3btAoz2HLNnz2bGjBkMHz6cBQsWNDr2okWLePbZZwF46623OOmkkxg5ciRz5syhqKio4v7/+te/mDhxIkOGDGHp0qXNer2C0JMZGODBx9dP4MnZMSQezWP6f1bx9up9mOto5TEjujc/3TGFGdF9ePanFGa9+he7UvM7bK4iUJ3ItGnTOHz4MGFhYdx88838+eeflJeXc+utt7J06VK2bNnCNddcw3333VdxjYuLC6tWrWL+/PlcdNFFvPLKKyQkJLBkyRKyslrWdPHdd99ly5YtbN68mZdeeomsrCyOHTvGo48+yvr16/n5559JTk6udk1KSgq//PILzz33HOHh4axatYpt27bxyCOPcO+991act3379orWH5999hmHDx9udGwbs2fPZtOmTezYsYOIiAjeeeediueOHz/OmjVrWLlyJXfffXeLXrcg9FSUUlw6PpSf7pzMqUMDeezbncx+7S9S0mqLj7+HCy9fOprXLhvDsZxiLnh5Da/8vqfOauptjSzxdSKenp5s2bKF1atX8/vvv3PxxRdz//33k5CQwNlnnw0YnW/79OlTcc2FF14IQExMDFFRURXPDRkyhMOHDxMQ0Pya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\n" + "image/png": "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t3Pd+5s5nlu7jqevuwsvXDyKN/8xlvT8Ui787xpe/GU35XVUrOgopN2GlZTTJmHOrNv50hIcAwMJW7O60fMef/xxPv74YxwdHXFwcOCNN96oVpOvJUycOJG//vqr1vFFixbh6enJv//971aN3568/vrruLu7c8UVV3T2VASh2xAW4sWymyby3tr9PPvTLqY9v4p7zo3gkpMG4OBQvaLMtKjenDTIn0XfJPLCLyn8lJTKc38bSXhv73pGbz9EoKy0pTg1dbx169axcuVKtm7diqurK5mZmZSV1d3yuTnUJU7thclkwsmp7f4zqiupWBCE1uPooLhu0hDOjgzh7mXx3PtlPF/vOMpTc2IZGFC9hJmfhwsvXjKac6L7cP+KeC54eQ23nTmc+VOG4uTYcQtvssTXiRw/fpzAwMCKunuBgYH07duXLVu2MGXKFMaOHcv06dM5ftxoAT116lTuuOMOJk+eTEREBJs2bWL27NkMHz6c+++/v2JcT0/PZs1j5syZjB07lqioKN58882K4++88w5hYWFMnTqV66+/nltuuQWAq666ijvvvJPTTz+dhQsXsnHjRiZOnMjo0aOZOHEiu3btAoz2HLNnz2bGjBkMHz6cBQsWNDr2okWLePbZZwF46623OOmkkxg5ciRz5syhqKio4v7/+te/mDhxIkOGDGHp0qXNer2C0JMZGODBx9dP4MnZMSQezWP6f1bx9up9mOto5TEjujc/3TGFGdF9ePanFGa9+he7UvM7bK4iUJ3ItGnTOHz4MGFhYdx88838+eeflJeXc+utt7J06VK2bNnCNddcw3333VdxjYuLC6tWrWL+/PlcdNFFvPLKKyQkJLBkyRKyslrWdPHdd99ly5YtbN68mZdeeomsrCyOHTvGo48+yvr16/n5559JTk6udk1KSgq//PILzz33HOHh4axatYpt27bxyCOPcO+991act3379orWH5999hmHDx9udGwbs2fPZtOmTezYsYOIiAjeeeediueOHz/OmjVrWLlyJXfffXeLXrcg9FSUUlw6PpSf7pzMqUMDeezbncx+7S9S0mqLj7+HCy9fOprXLhvDsZxiLnh5Da/8vqfOauptjSzxdSKenp5s2bKF1atX8/vvv3PxxRdz//33k5CQwNlnnw0YnW/79OlTcc2FF14IQExMDFFRURXPDRkyhMOHDxMQ0Pya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\n", + "text/plain": [ + "
" + ] }, "metadata": { "needs_background": "light" @@ -418,32 +23316,22 @@ "plot(vec(resolution=resolutions, error=errors.time[-1]).resolution.as_spatial().method.as_channel(),\n", " expected_lines.resolution.as_spatial().method.as_channel(),\n", " overlay='args', log_dims='resolution,error', title=\"Final Error by Resolution\", size=(6, 6))" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + ] }, { "cell_type": "markdown", - "source": [ - "Next, we plot the execution times per resolution and method." - ], "metadata": { - "collapsed": false, "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "Next, we plot the execution times per resolution and method." + ] }, { "cell_type": "code", - "source": [ - "plot(vec(resolution=resolutions, execution_time=exec_times).resolution.as_spatial().method.as_channel(),\n", - " log_dims='execution_time,resolution', title=\"Execution Time per Step\")" - ], + "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -455,11 +23343,12 @@ "name": "#%%\n" } }, - "execution_count": 10, "outputs": [ { "data": { - "text/plain": "
" + "text/plain": [ + "
" + ] }, "execution_count": 10, "metadata": {}, @@ -467,35 +23356,48 @@ }, { "data": { - "text/plain": "
", - "image/png": "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/OMfs3btWubNm8e8efN48sknL3jM+9//foLBIHPnzuWrX/0qCxcu7NbG5/Pxgx/8gDvvvJM5c+bgcrl45JFHemz33e9+l7e+9a00NDQwefLk5GOf+9zniEQizJ07l6uuuorPfe5zfXzVw5+52BDgFT+BMVOAxzunCBpj7gBusda+J3H7PmCRtfZDvRz/EeAB4EXgFWvtd3pp9z7gfQCTJk1acPDgwf5+KSIiIiIiSdu3b2fmzJnZ7oYMgp6+18aYrdbabhuIZaOKYE8TNXtNedbabwDfuNhJrbXfBb4LUF9fP7CpUUREREREpAfZqCJ4BJiYdnsCcCwL/RAREREREelX2QhYLwI1xpipxphc4G7gv7PQDxERERERkX41oAHLGPNTYBMwwxhzxBjzbmttFPgQ8P8B24H/stb+cSD7ISIiIiIiMhgGdA2WtfaeXu5/ErhwiRMREREREZFhJhtTBEVEREREREakbFQRFBEREREZcXY1XEfszJl+O5+7ooLaDc9etN2Xv/xl/uM//gO3243L5eJf/uVfWLRo0RU999KlS3nuuee63f/5z38ev9/PJz/5ySs6/0D6zne+Q35+Pvfff39Wnl8BS0RERESkH/RnuOrr+TZt2sTjjz/OSy+9hNfr5cyZM3R0dFzxc/cUrgZKNBrF4+m/WNLTpsiDSVMERURERESGqePHj1NRUYHX6wWgoqKCqqoqtm7dyg033MCCBQu4+eabOX78OADLly/n4x//ONdffz0zZ87kxRdf5Pbbb6empobPfvazyfP6/f5L6sdtt93GggULmD17Nt/97neT93/ve9+jtraW5cuX8973vpcPfehDADz44IN84hOfYMWKFXzqU5/ihRdeYOnSpcyfP5+lS5eyc+dOANasWcPtt9/OLbfcQk1NDX/xF39x0XN//vOf59FHHwXgX//1X7n22mu5+uqreec730lbW1vy+T/ykY+wdOlSpk2bxi9+8YtLer0XooAlIiIiIjJM3XTTTRw+fJja2lo+8IEP8Ic//IFIJMKHP/xhfvGLX7B161YefvhhPvOZzySPyc3NZf369TzyyCPceuutfOtb3+L1119nzZo1nD179rL68f3vf5+tW7eyZcsWvvGNb3D27FmOHTvGF7/4RTZv3sz//u//smPHjoxjdu3axVNPPcXXvvY16urqWL9+PS+//DJf+MIX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\n" + "image/png": "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/OMfs3btWubNm8e8efN48sknL3jM+9//foLBIHPnzuWrX/0qCxcu7NbG5/Pxgx/8gDvvvJM5c+bgcrl45JFHemz33e9+l7e+9a00NDQwefLk5GOf+9zniEQizJ07l6uuuorPfe5zfXzVw5+52BDgFT+BMVOAxzunCBpj7gBusda+J3H7PmCRtfZDvRz/EeAB4EXgFWvtd3pp9z7gfQCTJk1acPDgwf5+KSIiIiIiSdu3b2fmzJnZ7oYMgp6+18aYrdbabhuIZaOKYE8TNXtNedbabwDfuNhJrbXfBb4LUF9fP7CpUUREREREpAfZqCJ4BJiYdnsCcCwL/RAREREREelX2QhYLwI1xpipxphc4G7gv7PQDxERERERkX41oAHLGPNTYBMwwxhzxBjzbmttFPgQ8P8B24H/stb+cSD7ISIiIiIiMhgGdA2WtfaeXu5/ErhwiRMREREREZFhJhtTBEVEREREREakbFQRFBEREREZcXY1XEfszJl+O5+7ooLaDc9etN2Xv/xl/uM//gO3243L5eJf/uVfWLRo0RU999KlS3nuuee63f/5z38ev9/PJz/5ySs6/0D6zne+Q35+Pvfff39Wnl8BS0RERESkH/RnuOrr+TZt2sTjjz/OSy+9hNfr5cyZM3R0dFzxc/cUrgZKNBrF4+m/WNLTpsiDSVMERURERESGqePHj1NRUYHX6wWgoqKCqqoqtm7dyg033MCCBQu4+eabOX78OADLly/n4x//ONdffz0zZ87kxRdf5Pbbb6empobPfvazyfP6/f5L6sdtt93GggULmD17Nt/97neT93/ve9+jtraW5cuX8973vpcPfehDADz44IN84hOfYMWKFXzqU5/ihRdeYOnSpcyfP5+lS5eyc+dOANasWcPtt9/OLbfcQk1NDX/xF39x0XN//vOf59FHHwXgX//1X7n22mu5+uqreec730lbW1vy+T/ykY+wdOlSpk2bxi9+8YtLer0XooAlIiIiIjJM3XTTTRw+fJja2lo+8IEP8Ic//IFIJMKHP/xhfvGLX7B161YefvhhPvOZzySPyc3NZf369TzyyCPceuutfOtb3+L1119nzZo1nD179rL68f3vf5+tW7eyZcsWvvGNb3D27FmOHTvGF7/4RTZv3sz//u//smPHjoxjdu3axVNPPcXXvvY16urqWL9+PS+//DJf+MIX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\n", + "text/plain": [ + "
" + ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } + ], + "source": [ + "plot(vec(resolution=resolutions, execution_time=exec_times).resolution.as_spatial().method.as_channel(),\n", + " log_dims='execution_time,resolution', title=\"Execution Time per Step\")" ] }, { "cell_type": "markdown", - "source": [ - "Combining the previous two figures into one, we can plot the error against the execution time." - ], "metadata": { "id": "kffIKZI0zhdI", "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "Combining the previous two figures into one, we can plot the error against the execution time." + ] }, { "cell_type": "code", "execution_count": 11, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [ { "data": { - "text/plain": "
" + "text/plain": [ + "
" + ] }, "execution_count": 11, "metadata": {}, @@ -503,8 +23405,10 @@ }, { "data": { - "text/plain": "
", - "image/png": "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KsPyRURERETkeB0ahvHp3b2pHxbILVN/4qPlyb4uqcL54o4zU8zYSfs3Wmv3A3edbkxERERERHyrYXgQH951PmPeXs6fP1xNckoGf7qkBcYUFwGqH1/MXCUDjQq9jgF2+aAOEREREREpY2FBfkwb1YNrusXw/IJEHp65hmxvzWjV7ouZq2VAS2NMU2AncD1wow/qEBERERGRcuDvcTHx2k40igjiPws289vBDF65qTu1A/18XVq5Ku9W7O8CS4DWxphkY8yt1lovcA/wJbAB+MBau6486xARERERkYpljOH+S1sx8drO/LjtAENf+YGdqRm+LqtcGWtPertTtREXF2cTEhJ8XYaIiIiISI20eMs+7nprOUH+bqaMPJcODcN8XdIJjDHLrbVxZ3MMX9xzJSIiIiIiNUjvFpHMHHM+HpfhuleX8PXGM37iUqWmcCUiIiIiIuWudb1afHJ3b5pGhnDbmwm88+Mvvi6pzClciYiIiIhIhTindiAf3HkefVtG8vgna3n2i43k5VWf25QUrkREREREpMKEBHj434g4hvdszORvt3Lf+6vIzMn1dVllwhet2EVEREREpAbzuF08c3UHGkUE8+wXG9l9MIPXbo6jToi/r0s7K5q5EhERERGRCmeM4a4Lm/PfG7uyOvkg17zyA7/sT/N1WWdF4UpERERERHzmyk4NeOe2nhxIz2bIyz+w4tcUX5d0xhSuRERERETEp86NjeDjMecTEuDhhteWMm/tbl+XdEYUrkRERERExOeaRYXyyR/Pp12D2ox5ZzlvfL/d1yWVmsKViIiIiIhUCnVDA3j39l4MbF+Pp+es58nZ68itQq3aFa5ERERERKTSCPRz89KN3bjtgqZM+yGJu95eTkZ21WjVrnAlIiIiIiKVistl+NuV7XjqqvbEb/id619bwt7DWb4u67QUrkREREREpFK65fxYXr05jk2/H2bIK4vZsueIr0s6JYUrERERERGptPq3O4f37ziPjOxchry8mKXb9vu6pJNSuBIRERERkUqtc6NwPvljb6JqBTDijZ+YtWqnr0sqlsKViIiIiIhUeo0igvl4TG+6Ng7nvvdW8dLXW7C2cnUSVLgSEREREZEqISzYjzdv7cHVXRrwry838djHP5OTm+frsgp4fF2AiIiIiIhISQV43Dw/rAuNIoKZtHALuw5m8vLwboQG+D7aaOZKRERERESqFGMMfx7QmvHXdGTxln1cO3kJuw9m+roshSsREREREamahp3bmKkjz2XHgXQGv7yYDb8dOum+o0ePJjo6mg4dOhSMPfzww7Rp04ZOnToBNDfGhAMYY/yMMdONMT8bYzYYYx4rST0KVyIiIiIiUmX1bRXFB3eeh7Vw7eQlLErcW+x+I0eOZN68eUXG+vfvz9q1a1mzZg1AJnA0RF0LBFhrOwLdgTuNMbGnq0XhSkREREREqrR2DWrzyd3nE1MniFHTlvHBsh0n7NO3b18iIiKKjA0YMACPp+BerTQgJv97C4QYYzxAEJANnHxaLJ/ClYiIiIiIVHn1w4L48K7z6N0ikr98tIaJX24qbav2SOCL/O9n4oSt34BfgYnW2gOnO4DClYiIiIiIVAu1Av1445Y4rj+3Ef/9egsPvL+KLG/uaT83duxYcGar3skf6gHkAg2ApsCfjTHNTncc3/crFBERERERKSN+bhfjhnSkUUQw//pyEztTM7j3opZ0j61T7P7Tp09nzpw5ANvtsamuG4F51tocYI8xZjEQB2w71bk1cyUiIiIiItWKMYa7L2rBhGs6siwphVum/cTF//6G9Cxvkf3mzZvH+PHjmT17NkDhpxH/ClxsHCFAL2Dj6c6rcCUiIiIiItVS8+haBPq5sBY2zniGvn0uYNOmTcTExPDGG29wzz33cPjwYfr37w/QzhgzOf+jLwGhwFpgGTDVWrvmdOczpbzJq0oyxuwFfsl/GQYc9GE5hfmylkhgn4/OLVKVVab/D5HyoT/j8qGf6zHV9WdRVa+rstddmerzdS2l//3VuFx+kU3aG5fLY/PyvDn7flmHzcs7yd5NrLVRZ1NgjQhXhRljXrPW3uHrOsC3tRhjEqy1cb44t0hVVpn+P0TKh/6My4d+rsdU159FVb2uyl53ZarP17VUhd9fa+KywM98XUAhlakWESkZ/b2t/vRnXD70cz2muv4squp1Vfa6K1N9lamWSqnGzVyJoyokfxERERGRo6rC7681ceZKHK/5ugARERERkVKo9L+/auZKRERERESkDGjmSkREREREpAwoXImIiIiIiJQBhSsREREREZEyoHAlIiIiIiJSBhSu5ATGmKuNMf8zxswyxgzwdT0iIiIiIqdijGlmjHnDGDPTl3UoXFUzxpgpxpg9xpi1x40PNMZsMsZsMcY8eqpjWGs/tdbeDowEhpVjuSIiIiJSw5XR76/brLW3lm+lp6dW7NWMMaYvcAR401rbIX/MDSQC/YFkYBlwA+AGxh13iNHW2j35n/s38I61dkUFlS8iIiIiNUwZ//4601o7tKJqP57HVyeW8mGtXWSMiT1uuAewxVq7DcAY8x4wyFo7Drjy+GMYYwzwLPCFgpWIiIiIlKey+P21stCywJqhIbCj0Ovk/LGTuRe4FBhqjLmrPAsTERERESlGqX5/NcbUNcZMBroaYx4r7+JORjNXNYMpZuyk60GttS8CL5ZfOSIiIiIip1Ta31/3Az6fFNDMVc2QDDQq9DoG2OWjWkRERERETqdK/v6qcFUzLANaGmOaGmP8geuB2T6uSURERETkZKrk768KV9WMMeZdYAnQ2hiTbIy51VrrBe4BvgQ2AB9Ya9f5sk4REREREahev7+qFbuIiIiIiEgZ0MyViIiIiIhIGVC4EhERERERKQMKVyIiIiIiImVA4UpERERERKQMKFyJiIiIiIiUAYUrERERERGRMqBwJSIiVZ4xpq4xZlX+ttsYszP/+yPGmJd9XZ+IiNQMes6ViIhUK8aYJ4Ej1tqJvq5FRERqFs1ciYhItWWM6WeMmZP//ZPGmOnGmPnGmCRjzBBjzARjzM/GmHnGGL/8/bobY741xiw3xnxpjKnv26sQEZGqQuFKRERqkubAFcAg4G3ga2ttRyADuCI/YE0ChlpruwNTgLG+KlZERKoWj68LEBERqUBfWGtzjDE/A25gXv74z0As0BroAHxljCF/n998UKeIiFRBClciIlKTZAFYa/OMMTn22I3HeTj/TTTAOmvteb4qUEREqi4tCxQRETlmExBljDkPwBjjZ4xp7+OaRESkilC4EhERyWetzQaGAuONMauBVcD5Pi1KRESqDLViFxERERERKQOauRIRERERESkDClciIiIiIiJlQOFKRERERESkDChciYiIiIiIlAGFKxERERERkTKgcCUiIiIiIlIGFK5ERERERETKwP8H3a3jum4ZxaoAAAAASUVORK5CYII=\n" + "image/png": "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KsPyRURERETkeB0ahvHp3b2pHxbILVN/4qPlyb4uqcL54o4zU8zYSfs3Wmv3A3edbkxERERERHyrYXgQH951PmPeXs6fP1xNckoGf7qkBcYUFwGqH1/MXCUDjQq9jgF2+aAOEREREREpY2FBfkwb1YNrusXw/IJEHp65hmxvzWjV7ouZq2VAS2NMU2AncD1wow/qEBERERGRcuDvcTHx2k40igjiPws289vBDF65qTu1A/18XVq5Ku9W7O8CS4DWxphkY8yt1lovcA/wJbAB+MBau6486xARERERkYpljOH+S1sx8drO/LjtAENf+YGdqRm+LqtcGWtPertTtREXF2cTEhJ8XYaIiIiISI20eMs+7nprOUH+bqaMPJcODcN8XdIJjDHLrbVxZ3MMX9xzJSIiIiIiNUjvFpHMHHM+HpfhuleX8PXGM37iUqWmcCUiIiIiIuWudb1afHJ3b5pGhnDbmwm88+Mvvi6pzClciYiIiIhIhTindiAf3HkefVtG8vgna3n2i43k5VWf25QUrkREREREpMKEBHj434g4hvdszORvt3Lf+6vIzMn1dVllwhet2EVEREREpAbzuF08c3UHGkUE8+wXG9l9MIPXbo6jToi/r0s7K5q5EhERERGRCmeM4a4Lm/PfG7uyOvkg17zyA7/sT/N1WWdF4UpERERERHzmyk4NeOe2nhxIz2bIyz+w4tcUX5d0xhSuRERERETEp86NjeDjMecTEuDhhteWMm/tbl+XdEYUrkRERERExOeaRYXyyR/Pp12D2ox5ZzlvfL/d1yWVmsKViIiIiIhUCnVDA3j39l4MbF+Pp+es58nZ68itQq3aFa5ERERERKTSCPRz89KN3bjtgqZM+yGJu95eTkZ21WjVrnAlIiIiIiKVistl+NuV7XjqqvbEb/id619bwt7DWb4u67QUrkREREREpFK65fxYXr05jk2/H2bIK4vZsueIr0s6JYUrERERERGptPq3O4f37ziPjOxchry8mKXb9vu6pJNSuBIRERERkUqtc6NwPvljb6JqBTDijZ+YtWqnr0sqlsKViIiIiIhUeo0igvl4TG+6Ng7nvvdW8dLXW7C2cnUSVLgSEREREZEqISzYjzdv7cHVXRrwry838djHP5OTm+frsgp4fF2AiIiIiIhISQV43Dw/rAuNIoKZtHALuw5m8vLwboQG+D7aaOZKRERERESqFGMMfx7QmvHXdGTxln1cO3kJuw9m+roshSsREREREamahp3bmKkjz2XHgXQGv7yYDb8dOum+o0ePJjo6mg4dOhSMPfzww7Rp04ZOnToBNDfGhAMYY/yMMdONMT8bYzYYYx4rST0KVyIiIiIiUmX1bRXFB3eeh7Vw7eQlLErcW+x+I0eOZN68eUXG+vfvz9q1a1mzZg1AJnA0RF0LBFhrOwLdgTuNMbGnq0XhSkREREREqrR2DWrzyd3nE1MniFHTlvHBsh0n7NO3b18iIiKKjA0YMACPp+BerTQgJv97C4QYYzxAEJANnHxaLJ/ClYiIiIiIVHn1w4L48K7z6N0ikr98tIaJX24qbav2SOCL/O9n4oSt34BfgYnW2gOnO4DClYiIiIiIVAu1Av1445Y4rj+3Ef/9egsPvL+KLG/uaT83duxYcGar3skf6gHkAg2ApsCfjTHNTncc3/crFBERERERKSN+bhfjhnSkUUQw//pyEztTM7j3opZ0j61T7P7Tp09nzpw5ANvtsamuG4F51tocYI8xZjEQB2w71bk1cyUiIiIiItWKMYa7L2rBhGs6siwphVum/cTF//6G9Cxvkf3mzZvH+PHjmT17NkDhpxH/ClxsHCFAL2Dj6c6rcCUiIiIiItVS8+haBPq5sBY2zniGvn0uYNOmTcTExPDGG29wzz33cPjwYfr37w/QzhgzOf+jLwGhwFpgGTDVWrvmdOczpbzJq0oyxuwFfsl/GQYc9GE5hfmylkhgn4/OLVKVVab/D5HyoT/j8qGf6zHV9WdRVa+rstddmerzdS2l//3VuFx+kU3aG5fLY/PyvDn7flmHzcs7yd5NrLVRZ1NgjQhXhRljXrPW3uHrOsC3tRhjEqy1cb44t0hVVpn+P0TKh/6My4d+rsdU159FVb2uyl53ZarP17VUhd9fa+KywM98XUAhlakWESkZ/b2t/vRnXD70cz2muv4squp1Vfa6K1N9lamWSqnGzVyJoyokfxERERGRo6rC7681ceZKHK/5ugARERERkVKo9L+/auZKRERERESkDGjmSkREREREpAwoXImIiIiIiJQBhSsREREREZEyoHAlIiIiIiJSBhSu5ATGmKuNMf8zxswyxgzwdT0iIiIiIqdijGlmjHnDGDPTl3UoXFUzxpgpxpg9xpi1x40PNMZsMsZsMcY8eqpjWGs/tdbeDowEhpVjuSIiIiJSw5XR76/brLW3lm+lp6dW7NWMMaYvcAR401rbIX/MDSQC/YFkYBlwA+AGxh13iNHW2j35n/s38I61dkUFlS8iIiIiNUwZ//4601o7tKJqP57HVyeW8mGtXWSMiT1uuAewxVq7DcAY8x4wyFo7Drjy+GMYYwzwLPCFgpWIiIiIlKey+P21stCywJqhIbCj0Ovk/LGTuRe4FBhqjLmrPAsTERERESlGqX5/NcbUNcZMBroaYx4r7+JORjNXNYMpZuyk60GttS8CL5ZfOSIiIiIip1Ta31/3Az6fFNDMVc2QDDQq9DoG2OWjWkRERERETqdK/v6qcFUzLANaGmOaGmP8geuB2T6uSURERETkZKrk768KV9WMMeZdYAnQ2hiTbIy51VrrBe4BvgQ2AB9Ya9f5sk4REREREahev7+qFbuIiIiIiEgZ0MyViIiIiIhIGVC4EhERERERKQMKVyIiIiIiImVA4UpERERERKQMKFyJiIiIiIiUAYUrERERERGRMqBwJSIiVZ4xpq4xZlX+ttsYszP/+yPGmJd9XZ+IiNQMes6ViIhUK8aYJ4Ej1tqJvq5FRERqFs1ciYhItWWM6WeMmZP//ZPGmOnGmPnGmCRjzBBjzARjzM/GmHnGGL/8/bobY741xiw3xnxpjKnv26sQEZGqQuFKRERqkubAFcAg4G3ga2ttRyADuCI/YE0ChlpruwNTgLG+KlZERKoWj68LEBERqUBfWGtzjDE/A25gXv74z0As0BroAHxljCF/n998UKeIiFRBClciIlKTZAFYa/OMMTn22I3HeTj/TTTAOmvteb4qUEREqi4tCxQRETlmExBljDkPwBjjZ4xp7+OaRESkilC4EhERyWetzQaGAuONMauBVcD5Pi1KRESqDLViFxERERERKQOauRIRERERESkDClciIiIiIiJlQOFKRERERESkDChciYiIiIiIlAGFKxERERERkTKgcCUiIiIiIlIGFK5ERERERETKwP8H3a3jum4ZxaoAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] }, "metadata": { "needs_background": "light" @@ -515,26 +23419,19 @@ "source": [ "plot(vec(time=exec_times, error=errors.time[-1]).resolution.as_spatial().method.as_channel(),\n", " log_dims='error,time', title=\"Error vs Performance\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + ] }, { "cell_type": "markdown", - "source": [ - "As expected, higher orders are more expensive but yield better accuracy.\n", - "When computation time is limited, the highest order of accuracy is not always preferable, especially since the computational cost of the implicit 6th-order scheme is approximately constant below a resolution of 32." - ], "metadata": { - "collapsed": false, "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "As expected, higher orders are more expensive but yield better accuracy.\n", + "When computation time is limited, the highest order of accuracy is not always preferable, especially since the computational cost of the implicit 6th-order scheme is approximately constant below a resolution of 32." + ] } ], "metadata": { @@ -542,7 +23439,7 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -556,9 +23453,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.8.5" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } \ No newline at end of file From 5f40ff2035f7b1f50e4ebd496c0c3f742a4176d3 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 26 Feb 2023 16:27:15 +0100 Subject: [PATCH 165/170] [vis] Fix animations for newer Matplotlib versions --- phi/vis/_matplotlib/_matplotlib_plots.py | 26 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index 4ddceb5fb..68ca32309 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -102,14 +102,22 @@ def clear_and_plot(frame: int): axis.remove() else: # subplot.cla() # this also clears titles and subplot labels - axis.lines.clear() - axis.patches.clear() - axis.texts.clear() - axis.tables.clear() - axis.artists.clear() - axis.images.clear() - axis.collections.clear() - + try: + raise AttributeError + axis.lines.clear() + axis.patches.clear() + axis.texts.clear() + axis.tables.clear() + axis.artists.clear() + axis.images.clear() + axis.collections.clear() + except AttributeError: # newer Matplotlib versions don't support clear() anymore + for artist_list in [axis.lines, axis.patches, axis.texts, axis.tables, axis.artists, axis.images, axis.collections]: + try: + while artist_list: + artist_list[0].remove() + except AttributeError: + warnings.warn(f"Failed to remove Matplotlib list '{artist_list}'", RuntimeWarning) box = Bbox(positions[axis]) axis.set_position(box, which='active') axis.set_subplotspec(specs[axis]) @@ -399,7 +407,7 @@ def _rgba(col): col = next(iter(col)) if not isinstance(col, (str, tuple, list)): cycle = list(plt.rcParams['axes.prop_cycle'].by_key()['color']) - col = cycle[int(col)] + col = cycle[int(col) % len(cycle)] if isinstance(col, str) and col.startswith('#'): col = tuple(int(col.lstrip('#')[i:i+2], 16) for i in (0, 2, 4)) col = np.asarray(col) From d4e5c2f81acdd0757cf8ec70c2f8d300ea7b7367 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 26 Feb 2023 16:27:27 +0100 Subject: [PATCH 166/170] [doc] Update cookbook --- docs/Cookbook.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Cookbook.ipynb b/docs/Cookbook.ipynb index d8cd27c28..3a5de9c8a 100644 --- a/docs/Cookbook.ipynb +++ b/docs/Cookbook.ipynb @@ -655,8 +655,8 @@ "segments = []\n", "for start, end in zip(trajectories.trajectory[:-1].trajectory, trajectories.trajectory[1:].trajectory):\n", " segments.append(PointCloud(start, end - start, bounds=Box(x=2*PI, y=2*PI)))\n", - "anim_segments = field.stack(segments, batch('time')).with_color('#FFFFFF')\n", - "vis.plot(vis.overlay(f_grid, *segments, anim_segments), animate='time', frame_time=500)" + "anim_segments = field.stack(segments, batch('time'))\n", + "vis.plot(f_grid, anim_segments, overlay='args', animate='time', color='#FFFFFF', frame_time=500)" ], "metadata": { "collapsed": false, From 9c3916180d4aa3ef22c16c6b5cc6caac66d96923 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 26 Feb 2023 17:41:48 +0100 Subject: [PATCH 167/170] [vis] Allow color per subplot --- phi/vis/_matplotlib/_matplotlib_plots.py | 9 ++++++--- phi/vis/_vis.py | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/phi/vis/_matplotlib/_matplotlib_plots.py b/phi/vis/_matplotlib/_matplotlib_plots.py index 68ca32309..f89e3c614 100644 --- a/phi/vis/_matplotlib/_matplotlib_plots.py +++ b/phi/vis/_matplotlib/_matplotlib_plots.py @@ -288,10 +288,10 @@ def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, x, y = math.reshaped_numpy(data.points, [vector, data.shape.without('vector')]) u, v = math.reshaped_numpy(data.values, [vector, data.shape.without('vector')], force_expand=True) if color.shape: - color = color.numpy(data.shape.non_channel).reshape(-1) + col = [_rgba(c) for c in color.numpy(data.shape.non_channel).reshape(-1)] else: - color = color.native() - subplot.quiver(x, y, u, v, color=color, units='xy', scale=1) + col = _rgba(color) + subplot.quiver(x, y, u, v, color=col, units='xy', scale=1) class PointCloud2D(Recipe): @@ -405,6 +405,9 @@ def plot(self, data: SampledField, figure, subplot, space: Box, min_val: float, def _rgba(col): if isinstance(col, Tensor): col = next(iter(col)) + if col is None: + cycle = list(plt.rcParams['axes.prop_cycle'].by_key()['color']) + return cycle[0] if not isinstance(col, (str, tuple, list)): cycle = list(plt.rcParams['axes.prop_cycle'].by_key()['color']) col = cycle[int(col) % len(cycle)] diff --git a/phi/vis/_vis.py b/phi/vis/_vis.py index 93bce37bd..bbd8e539e 100644 --- a/phi/vis/_vis.py +++ b/phi/vis/_vis.py @@ -351,9 +351,10 @@ def plot(*fields: SampledField or Tensor or Layout, if animate: def plot_frame(frame: int): for pos, fields in positioning.items(): + idx = indices[pos] for f in fields: f = f[{animate.name: frame}] - plots.plot(f, figure, axes[pos], subplots[pos], min_val, max_val, show_color_bar, color) + plots.plot(f, figure, axes[pos], subplots[pos], min_val, max_val, show_color_bar, color[idx]) plots.finalize(figure) anim = plots.animate(figure, animate.size, plot_frame, frame_time, repeat) LAST_FIGURE[0] = anim @@ -361,8 +362,9 @@ def plot_frame(frame: int): return anim else: for pos, fields in positioning.items(): + idx = indices[pos] for f in fields: - plots.plot(f, figure, axes[pos], subplots[pos], min_val, max_val, show_color_bar, color) + plots.plot(f, figure, axes[pos], subplots[pos], min_val, max_val, show_color_bar, color[idx]) plots.finalize(figure) LAST_FIGURE[0] = figure return layout(figure) From 3bf43c12d002fe28c973da0e796b47ee336b1b17 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 26 Feb 2023 17:56:40 +0100 Subject: [PATCH 168/170] [doc] Update tutorial notebooks to new plotting --- docs/Animations.ipynb | 56 ++++++++++++++++++------------------- docs/Planets_Tutorial.ipynb | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/Animations.ipynb b/docs/Animations.ipynb index af61aa053..9bb5efd64 100644 --- a/docs/Animations.ipynb +++ b/docs/Animations.ipynb @@ -64,36 +64,36 @@ "outputs": [] }, { - "cell_type": "code", - "execution_count": 7, - "outputs": [ - { - "data": { - "text/plain": "", - "text/html": "" - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": "
" - }, - "metadata": {}, - "output_type": "display_data" - } + "cell_type": "markdown", + "source": [ + "## Julia Set\n", + "\n", + "The [Julia set](https://en.wikipedia.org/wiki/Julia_set) for the function *f(z) = z² + c* where we animate *c = 0.7885 exp(2πi t)* over time.\n", + "A point *z'* belongs to the Julia set if the sequence obtained by iterating *f(z)* starting with *z'* does not diverge.\n", + "In the below animation, the plot shows number of iterations until the sequence went beyond |*z*| > 2.\n", + "For a more detailed explanation, see the related [video showcase](https://youtu.be/t6nAbxHCW5s) of the Mandelbrot set." ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], "source": [ "def julia(re, im, a=math.linspace(0, 2*PI, batch(t=100))):\n", - " r = -math.log(math.vec_abs(iterate(lambda z: z ** 2 + 0.7885 * math.exp(1j*a), 10, re + im*1j)))\n", - " return math.where(math.is_finite(r), r, math.finite_min(r, 're,im,t'))\n", - "plot(CenteredGrid(julia, re=128, im=128, bounds=Box(re=(-2, 2), im=(-2, 2))), animate='t')" + " return iterate(lambda z, k: (z ** 2 + 0.7885 * math.exp(1j*a), k + (abs(z)<2)), 50, re + im*1j, 0)[1]\n", + "plot(CenteredGrid(julia, re=256, im=256, bounds=Box(re=(-2, 2), im=(-2, 2))), animate='t')" ], "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" + "name": "#%%\n", + "is_executing": true } } }, @@ -3096,9 +3096,9 @@ { "cell_type": "code", "source": [ - "x = math.rotate_vector(vec(x=1, y=0), angle=math.linspace(0, 2*PI, spatial(points=50)))\n", - "dx, x = x.points[1:] - x.points[:-1], x.points[:-1]\n", - "plot(vis.overlay(PointCloud(x, dx), rename_dims(PointCloud(x, dx, color='#40FFFF'), 'points', 'time')), animate='time')" + "grid = flatten(CenteredGrid(x=12, y=10).elements).center\n", + "direction = math.rotate_vector(vec(x=0, y=1), angle=math.linspace(0, 2*PI, batch(time=50)))\n", + "plot(PointCloud(grid, direction), animate='time')" ], "metadata": { "id": "p9usuMqNXWZD", @@ -26131,11 +26131,11 @@ "cell_type": "code", "source": [ "x0 = math.random_uniform(instance(balls=30), channel(vector='x,y,z')) + 5\n", - "balls = PointCloud(Sphere(x0, radius=.1), math.random_normal(x0.shape) * (1, 1, 2))\n", + "balls = PointCloud(Sphere(x0, radius=.02), math.random_normal(x0.shape) * (1, 1, 2))\n", "def step(balls, dt=.1):\n", " balls *= math.where(balls.points.vector['z'] < 0, (1, 1, -1), 1) * 0.7 ** dt\n", " return advect.points(balls, balls, dt) + (0, 0, -9.81 * dt)\n", - "plot(iterate(step, batch(t=100), balls).mask(), animate='t')" + "plot(field.mask(iterate(step, batch(t=100), balls)), animate='t')" ], "metadata": { "colab": { diff --git a/docs/Planets_Tutorial.ipynb b/docs/Planets_Tutorial.ipynb index d1b087f77..d1e6bcf97 100644 --- a/docs/Planets_Tutorial.ipynb +++ b/docs/Planets_Tutorial.ipynb @@ -266,7 +266,7 @@ "source": [ "def simulate(x, v, dt=.5):\n", " dx = math.pairwise_distances(x)\n", - " a = - .01 * math.sum(math.divide_no_nan(math.rename_dims(masses, 'planets', 'others') * dx, math.vec_squared(dx) ** 1.5), 'others')\n", + " a = .01 * math.sum(math.divide_no_nan(math.rename_dims(masses, 'planets', 'others') * dx, math.vec_squared(dx) ** 1.5), 'others')\n", " return x + v * dt, v + a * dt\n", "\n", "xs, vs = iterate(simulate, batch(time=100), x, v)\n", From 94c12bf9e0ee7e8a9c41a63cc31b46631557ee63 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 26 Feb 2023 19:40:41 +0100 Subject: [PATCH 169/170] [doc] Update installation instructions --- README.md | 4 ++-- docs/Installation_Instructions.md | 10 +++++++++- docs/Package_Info.md | 7 +------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f405b1ca1..569d3d65d 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ Installation with [pip](https://pypi.org/project/pip/) on [Python 3.6](https://w ``` bash $ pip install phiflow ``` -Install PyTorch, TensorFlow or Jax in addition to ΦFlow to enable machine learning capabilities and GPU execution. -To enable the web UI, also install [`dash`](https://pypi.org/project/dash/). +Install [PyTorch](https://pytorch.org/), [TensorFlow](https://www.tensorflow.org/install) or [Jax](https://github.com/google/jax#installation) in addition to ΦFlow to enable machine learning capabilities and GPU execution. +To enable the web UI, also install [Dash](https://pypi.org/project/dash/). For optimal GPU performance, you may compile the custom CUDA operators, see the [detailed installation instructions](https://tum-pbs.github.io/PhiFlow/Installation_Instructions.html). You can verify your installation by running diff --git a/docs/Installation_Instructions.md b/docs/Installation_Instructions.md index 61833b446..7de5def13 100644 --- a/docs/Installation_Instructions.md +++ b/docs/Installation_Instructions.md @@ -19,13 +19,21 @@ We recommend CUDA 11.0 with cuDNN 8. The following command installs the latest stable version of ΦFlow with GUI support using pip. ```bash -$ pip install phiflow dash +$ pip install phiflow ``` To install the latest developer version of ΦFlow, run ```bash $ pip install --upgrade git+https://github.com/tum-pbs/PhiFlow@develop ``` +Install [PyTorch](https://pytorch.org/), [TensorFlow](https://www.tensorflow.org/install) or [Jax](https://github.com/google/jax#installation) in addition to ΦFlow to enable machine learning capabilities and GPU execution. + +To enable the web UI, also install [`dash`](https://pypi.org/project/dash/). +```bash +$ pip install dash +``` +If you only run ΦFlow inside of Jupyter notebooks, dash is not required as Matplotlib will be used for plots and animations by default. + ## Installing ΦFlow from Source The ΦFlow source additionally contains demo scripts and tests. In particular, it includes [`tests/verify.py`](https://github.com/tum-pbs/PhiFlow/blob/develop/tests/verify.py), diff --git a/docs/Package_Info.md b/docs/Package_Info.md index 143b88294..a60520502 100644 --- a/docs/Package_Info.md +++ b/docs/Package_Info.md @@ -12,9 +12,4 @@ It is written mostly in Python and can be used with NumPy, TensorFlow, Jax or Py The close integration with machine learning frameworks allows it to leverage their automatic differentiation functionality, making it easy to build end-to-end differentiable functions involving both learning models and physics simulations. -## Installation -See the [installation Instructions](https://tum-pbs.github.io/PhiFlow/Installation_Instructions.html). -To install the latest stable version of PhiFlow: -```bash -$ pip install phiflow dash -``` +See the [installation Instructions](https://tum-pbs.github.io/PhiFlow/Installation_Instructions.html) on how to compile the optional custom CUDA operations. From cd6aaf0e13004b6c15800ebcbfba14a2557b2d60 Mon Sep 17 00:00:00 2001 From: Philipp Holl Date: Sun, 26 Feb 2023 20:00:15 +0100 Subject: [PATCH 170/170] [doc] Update Animations.gif --- docs/figures/Animations.gif | Bin 139268 -> 910580 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/figures/Animations.gif b/docs/figures/Animations.gif index 754ad3fb9dd095cae7db876c0454d3a6c52034c9..abaeeeaefcb29c9d1e686d10d6c66a2091fc0abb 100644 GIT binary patch literal 910580 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 z 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
  • Ls2~kwS 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

    Fhz)42%v)5^*(&`q}!}Df?;Y% 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%0AmenXR zOq3D@5rUaB=oGs=mFXr0XU}Y;{k)}!u5E>!et;ek1Hz9>I*O^Nx{0# ze^OTWrZ#py&SL9{HTckA-MpBfqs#|iu@3sx1XJd%kfURy0P1lnC{mFZ3&lX+^o^6K zl3JsHEMv|P%;mwf8g0%UL9pw~zn_p^7`QLOTGu6{CO(cY+YoesexDiPap|%#hbol~hpI{5bh#9sS9?8R4q)GHP zD{GeYhPeC!KxV6)ZddO*R4Aa>EalfGye2Nb?X=*l`zG$YWgGYSMlCFQ&G)RXxD$b$ zAXuSP)*)Up@Mel%U|i0lk8tF(nyRDKm%la?M=Ok4^YT15KUMLe046CM&(U`#nyT~t znEl+HUKpBN7vR%%TW3v-(s$NtaA|Y$DaVUmKq4{`oYd`X_F+J6m~bls;_i#we8OTf zCD&pcCWkS%(SjG_S+@RrNnzN{lStl%`tdlYjlri@XwDzJVP0*C6d15OuuJY1>bC(m*b2Lc%(jm z`#;@Wx^M@28QN~ti{vl^RX440he<;8qFm${-LX`H8`^rQr>`BW&&jv4LlrjaL5+b^ z`_nT_!20htS3_f2r%Rc!CU$6)Ia8F}=$3EXjS!{W!MQt{C})K`N

    1(e>ka4mG=I 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 z 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

    Sw5i~!Y{k(#HjwF(>=;}Q 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%0DA>l# zK?k)LysGEsdn;>+0woND)(#m}n>&C-nm-knL$u2->H|8y_x^xk@*fS;ys3~l*=^LJ zC)=rrg21T<2FnLxKjL-YDm;1*JJ2Ir+9>#lg;YW;6p-yVzp)V#Bic6vIt%kzj?)6b|jPW+9$h(s)e&;4b!YGfz)(dY3tpT9WthjWtN zg>NU9g0jdG%7eUD9`m~kLB5-xu_?+>bNIHeMU;X)FZuUO5`-5w} zyi;)$RPQu3OTNG{BIxRz;K0{J>>h%52C>>R=QH>3A9w{A|y3k|2`|k zk!Vr9;+*GtPJ=Iu*mGfRTDUF2hJGvZNgk1cfFKp!3|9Fn=hCh2+zPmGusyooeJ?iY zm%;*paacCUTfW(cOM?+x*IRY(54~vF78rb@f!yc%wO8Y6-Bu8M$(3|za)=q~8x6&KF4{oG$EC#4vD52o+9JYtrNpgrSiZRe1@L3#&X!6?1yAt93hkRTkE7pZmMnc~;LUzW(Exz`dpu`NzKx zVO+kZ67D51x1gj`2P#VRy1!ojM7}jOZ;~^7isI*dL9+7-$@cB*->N^NIyxUezJ1*I z>DBbNSjxX?0X3aM-?_#TD;S@Pdemf%sooBMwZGQoH+yP4NAt}@d|ou@^%7U?(3L`Z z@{GV6zIWSCp(z~cRE4)e?J`#}1gL%Wh=cG^M|{ASs8Q5uv> zi8+{-dL)*ni^(|AT1f@qzYH_hNz6ZJMjhvd_r$$b;iJPIdPE}wu?S|%O*GcFFJ%Gq zMFgvlVT$&POkElfQo1u#K5!!7o@hN$j(k8wj`_zqhy1buy28KTYH;tMcOz)e(Zn&% z>=PCihAYvSt%JQYI^9QV+gw`pb?b?SW3el|>T1m`DSje8hM7|5hD@bvPip7yJJDfx z$56>nE6G6he0EDfi;U{l;H?^>{36slfHE`Iu=awc&8KD>KkO7#A%oC=P@8QR*SaaG zrx`^t$buP7W;cHV@C*#Haai+Umla~`JTNB~Oudyj!Qu}!Eq6L$G|y()t# z9tl%Ln|lAodF(QaSQsPHgMCDpxL=L9w0V;E!RM}qsl#KtnI^e%sj_BAlxo_(U<)+B zgIr+))n!b)5lj#QU8pX0=8_*msgfJ9SNd*DsZH)v7##MVoK`9r@*nik$hsA3E-nYl z(yM?#ShaoTGZj-Is#E)|L<;4IYagYSU722%eS3vQRuAi^mi0)lCa@}W>nYmQOD%Zr zy13dRRIRH`rxf$9!s6}RBa`PamDNXub5pO}Gc*0`n!QarVEOf^dIM&hB@hR zu-`Kfnpt0lt#jzs@014fYG8Y99AYkRB}jULO(!`_j~Gt#pPz;nJZNo67rHzxGKJ^W zyeWLlX5YJMt>69i>(k`(>J#}eGXD(tw<2jgBO^u?J2NBu7cU~lBbzXz@D5L$!cY99 zDYeh2;Hi->@yf?0CBM(8eb10L6CKO9kybxr2djlCvQ$)a?Nk%&wBF4ed@%d6hI#ni z?2&hNQd4%)cr=8vi{QdSpb(|A5C=#CwC?`~N17M;KybviVCMe@M@Uk+3Uap*&1IF9 zmdcLEvI^(cs;Qe=AUHC1r`kl3=F$_iV9VxLIFhY!a=fi}wi;XACo0Sff+Nm%0upqL zYpe@h!Pb*yhmwA>L+Y0~&yEIA@N(9=c3HFW6)1Q)!d2(k^ad2XMAQd9N#z{?!4af( zUhu*Y2#%DvQ5)ViJFskDPwpNVzgKLSCVny-yV~kN&OS9&SUWdy)~>VUnC^$bG}n&M z>8a=+whDED+!BwbHG<+@dqOds2Wj6vJjR4?HrO59Tw4&l&`{W%_G6WQAbRg~$ZXWk zw-0A8qP|Y=_HRl}mESt;dUxRE7kG?z9P(J9h97#{*bKZ{ejy+{X8ML%e*XMRn(K;h zqVV5Z;z)u!`ds3%$KMm(4Y6u+%iaX>oTB)@m8By>mSClc=v1T164cVJ7QMt>tFe47 zD>w3V1l(rzjfe+2(-43v>4Oa?WOJ$Ym-Cw*1_PUZ?@sR!`6wAMoBse41UZpU4+rHE z_Np&6$%f_+yfZz$Eh{VH!?*ojKk;O3^(hyF{99N2>chASy;#HqZr^JZlYIZ=?F3d3 ziBH~;^2J}?<%mF=3}}7<1vJMIv=LrgTG#_2FK?m#Le#5nPC2V?kFr+ZS|vg&=Q`X) za?ab)aXhgfX3oiT#TYB=S8N=uQM9*kOLVM|78)fL3~SbE95 z8@KuSU;4|h(|lv(M|cg`00ggX78cL}Ukczd+EX2wFi_%tut+x}atZkPOw`?+ z5i?bJ&dRGg_=0n=d!$AMJ75Udy2oD3hZndJ^I@eT{k*RPN|i_k2$o;}X{osfVg1G- zy=GMnPM5|!(m}nB27}knHyc`*74t3TiWFfhRE6va9&+rBP6YM9E5YD4SC;jE&sv7(X zcFT)Bu3-)p1<9K}Cr7FD7xj-azfm%a#i#a36GB%*Eu%@wN-s1ZRdXJ@JgJ zQzZvQeN|7cCrBdI{tz(kN`pWY3$C_9_P1?zf%AACh7VL-uu6YxAwVjz=yp79)IcB$r9G;Ty8lGuA3mP@>|}}P*@vWrxt2(q$z|G#ss486lItiO zE}-cG)cfNj)foB^`|2LbPa0AWe0%3?j~(~<>>j*Kk`_}l=PjdB^1Pk)zqv?NTg|k$ zq%mQrGY^>Y9yx0f?oWkG4tz6TD&$hPCB!i9>3irG+!7h&Z_P#en_~`ItXF>S8DzPC z`e42sEg@9@Q*938fi_^r0zr;@517eI2G3^%goep=iZuO*BYw_&Xu$*-kTS7cb;srP z!@IweAp+Y2BK4gKL<`&E!?#xqo<6A(2(ZO%^1c>|rj7JqR^rJetbK0=GQ?YPAlM4~ z^Q8zMVk_QmhP{W8MVn!~R*d^; zlloedPRqCTU(cyXp7ik1bC!un$g6kiVW(5I2C!y}Fku!1NtW_9IQ%}Y!niy3IZ1;- zH(7)569+t5FGR*aCPXaDH?d53qLL<5Vx61OYBVU`js4<}%2M+HH{^*MJwRl1amv#1 zJirz_<9DR_Db_zLy`P4pR>V%Cl`u!ZyMBOy_y3dc#C2guo+iZ_3*C?&FNyq)?=TVR1LeBMSj*z>Q9AJ-y2DHPG zai~QN_?~&!pcLvcHM!L4RlIzX>=zHU{kz>3X^k6MwJ$bOJ#ygpJxnYfiRbJ)^Gfl+ zd^XIu!XV+v4gUI0kITsc-JkjTN_FjIMQDb|55$@ext$$dmGt2(#!3(Wi^vC(`tAB*^5JrQGI@yM^3e|Y|u zsm|U8ru7<~UiPzaby#bArStXxtNp#JOk}ND%^FJMcl6hJ!9O9HCskH{ygdZG>c@?T z;$cwCM?py4B4Mc1fGBPE)l}g?>xrN3w5Lz3oTcTT`vO-!TyFI`$Fu(m^CG^EjVYjG zXQ+e*Hl~n`eZ`I$Tw=glfzQ=?)!9}Nyjtfjy6t?RNuiXBoxnC96WaA(!Q?+=f`b9) zfG-^Ut7gI$H9<9*sOfz1`wiD-`$Mnd-43XDHo#Y?043FX__m{AH1)Gx)H?w@KW}UCagsA$(6O>FN?<5=A=i2mH#? z+qz>I@y)`w&F=W3GfnVZwn4J6N3xIeLFg)Bb%TH@WtR5GBZVOLk@8o{-CX2+wL@X{ z*&4Ux9ADpvE+WPAXNCyGCcpnq80TOL0PH*@j*G&x3R)S2cv2}TqMk{}r9&6I4%JDS zQ8%D59WXsk8d)$r3Uy@AnRq5LS+d4y2BaN8Cj=t~xtSc6A9ctkMav%Xh}Spsp{*R< zCVV#S=*6r33o?BdiB+T5-U@Pma|SUF^d$ZI4mt(gX1dteE(T%FPcXZNc!C_KUKS}6 zN3~@i^Nsh_Uxd}2KXTHJg_IA^G)NVmw#hPy@85I6G68HQ3)I=*P&S!xaJ1Y&MQxx8 z`!mC)uc3)q4J6fFNnj-ramHK8Rs9e*oNFoJB0(cC$RJzDBU{)b{ZkIl?gqYt#hurW zilL)Hr^=dXX1E#9zkvd|4KSh88y{s0W8ta+$To-gtr^#`f&T&UfSAuG8sQB{Kro2?pk4-Y{VIn$6k32P z<1QxAO#aaMMVKP zF?}4ER07Q?qW;%~C_G<6aV-$s7xzpbj|{yfE;=SLqxHhKZD*>mU)00>d?GgtM}bg; zb{+)rUmlEuEG6IC=K(fgFf+B713#1HQ_Dgq;O3z^wKK+N+`D2|U5KuQ z#9D3Da)mJSA7Qh|sttZ#H#Q0z22;58v}<)s zt8YEy6N-{p@@V8Ys5!8|bYJpK0b&E1c>4wkKiQA%r2?CUw?%Du*2ut*XT05<+FQTM zj<+<;dl4VSW_V$#wRMf?kFgvq34L3(kBpAkC<()m-mtM?jVn16zq7Z<3%0HX1Gq;R z_+Ou@T|Tuu8Yn#NRHN2?5itR*EfD1cW!{p}V38%54bLKz{!3y}+nF1!5=#WJ0wBDG z0EWHE!3u$TZpI0GWArR0JJ;34(~C^N_>1<7o&}VNlz^ zeQ1q1xq%8}qoOe#3Q_p^4Z>D`s+DS;aArq%_(j9BHvbIOIuu6$HFw8Ro4lc zM1DX>JpiOu5eVjRUwJ{Cr=pbEaCHuHu@IcDF`#@Yhe|kolh2xyNA51>JJ=SaD{Y^s z_Sd!wL+RP)(VZ6#R(_Dd5Cj(8BGG~g1_6vcHav<-xHCbl1VI%Lr=Y^Knrq>kgd8f$ zL>pB=>$VuMc8RKrwJ0xiZL@#c{rGo17v=6z{hozPo_B(ftIg<5a+A0RzL0(IDm7C; zpQpb+yG0Ht1>p=%sX7P!MG-izTb%*0-CpoIe)WdS&cqrHd?7AgCoB4B=P*3;?N1=PYzlJBojEuN%oK!1@EkT zM~8t%42(^_1inDVu1=1VFHN9-jZ^kbw6340p+_!glDcuU763*9U?L1CI0=*WdFB;? z3$%fY(9yh`qQaY^8yd21N)vaguD-0lKRYpc&lcVP5_TVlsQp9Sq4G4)FvX$xcfW~m zS>aVx62+D=SlsOrDo*WvY z^ET#t{}_(Q^NNnWuZVfo+=JL8yy4&k*SM$2gssr7aW-6r3YTHSGkX66EnIGL$v`j{ z(s>u2kq+IxA8_|Udgn=%59cn*p27Z_oqjQWMs|vvz_SyIAE(_@Cxk zJGo8950LS*?m!{9$Fp9_uqSb)nEY4BLS;;86z?su=TG`NC~1nwgU6kR4FBwTUnYCY ziktO|EIoc_!1LSuNoNKyL!To7P$2*nIf08;2EH=T?s$Zi6%bE-HYNs(;edI$`K)7z z-nD1@!k+pSvyb|@pN!`^66&GyQTwlSDbmL2bf(u9mFLsl*Qn|L;S(T(0Q!iis4q%D zF$Z>945mZI*T;zi-o&-eI86xgRacs{vKTc663&_+GY^tFeh0J>>Tt108i;Pf;J| zQ%`aq;qFhq69Q{sq3;o!C<+8&1bJG=;eh=yPAGPoLwG|6mf0A)+wceu_Ye9J+w3>?3}M!lln+h${30LTS0{^%9%Wyn{!4NpIdD+Jh5 zT0~k>z8WO=33^OXq64A1IJ@U0TduX^?Gxf)rLsHp5uo3B;n1}o^oDqrE=(zQO9NmT}RYuu47THtfI;aeq62Z9acf@bN<5*W11B<%ygr%4a*-5G92g{M7` zc#RxC8<=5fw!A}YQ53(=T!E%MD8at-A@`Pk9zxF3emooE+(knb zOe$$4kxZ&{?TZrHo1PM`jVmykIj@>kH7^&MrrrC~-7iyg=+>Z0duU6~&k5n{nzMRf zph``6Jh$9To&+&xOcgg#gMVp!kJ|bC>i$>1rhR3?Pz&@mj^vzP&-YK$y(Wd{*Rv)L zy_zsEY;tk7D{KjFa4A5V`&);2qdBxVVC_hS#wcyi}zXc+FV z3$T?M)+~{1G5Q*Bq1yN8Q3X0(Q)kAuQ9dAE-THRVv{9Dx^H~p32~C5Hf}_b~Ny0I| zZviEA1G(6}1lE>WlDI$k$l^&=D>dUd4;?AH+g*JaSyhui>lEHGC4*#gDwd2@IynA? za+CatZtdcG>{y||NEVwoyDn^+b(AbzeXz$nCqDToHf4z5|0y%DNJ#^zO|bKRCL8i-echOeL(m(RD4WEX88GpUH1|&U{j>7V+@wK2s3XIQGNR`0IvMa%C{q);ULhgx*rV4m!`;uGJVT;=lV@xN}7>@FSW z;L)*1dsBY``QlB-j5N3IKR?6kfo8K0@*`sceQ%x#@egqgf%k{|hic0Rs;Q1f!cxSq zWi3-KjENecC00H|??~Qo_FPpIs9sN@L@o##br)9^g-hZ_`ts`Tsx>S4NsBq&4g*r8 z)*UU>qnDCwPo=o#YppB1bXq$FPgiYp1_7Hyts=b(nR1$Fi5N6F2@=2fcVjqrP!cQ6 z+9*XmmgIKd8gJs!62OsL-;xt{>#^IMb^}zNb46*jM#IDEbtyR;#>Xae4NTug8LvX4>XQy*$IZp>a)w0ZRN0L4jeGv$ z^~t}1Z$02*jLQ_BUq2|>%af6Ip}qGsUp#m#g;=srj3eW{*@l8$>`0yzCjgMk5$|Fk ze56K^O^*MhwZa4Uw)fB`6m?+<%cRElZ!TtNFy8G8 zUNw;*5BLBsgY-mqGoz}~W7d5Z>Ehj!x7}3TAA*Ny7W#5N2Hx1bJsiZc3zDtjrjON5 z9^^Oh%C*0tE_;|__|3vNj(?PlQtPMKAZ6jg+l%%_rLln4P~XkkWTcspp`<)>GC+XO zZ2hLgfn{o(FrZKRsO~r3_A$Zsnm$^OW>91AX??APw|FYr7ZSSz44>H_8eKGRs1}h^ zQG`1A(!bpvzugBsdb z>O83_R6N}|G)VHJD@aH#o&Bs}u$cVe=v2^%dKSgTF%v)<+ae5g5(3@e)a%--{e%mC zh62}Gw0%hYA(byzc*~lHzr|#oeupy@I&$7&pRG3CkCx2kKtq7y2G zYRC}BX_)mpo#*54^ije2L%3y5Mycnlt;xwDg>O6kvN0yOln23{`+8kp59VB=QLbB{ ze$ckov*oSFltau@kdL3|v^_rQqiMaADBT?!pmUjbe;Rhd!GeO;rhht~I^w!@<45If zHKI`>z=+&!I;*S}^tUlC@vL^IR7QNrl{4qBL)^Uhvb&7jirKB7#WjmL(PYa!&$e&> zZd^+EoMJ3;Q)Tf;{Pl<25MPbowGYub`%C`TUol=ci{eO$!}f0&_JL#x8j8{Hm)YFe zf2uDGi8-WBoMK#jTXxt8*UoFmQdBFTzf(aSrfpGm_u>u^-V7&fi>*+#AV7NDiE|g zVpq0=Y3p1n(Zkqhcf-s0Pl_1%ely_NT+VCk*Bub>kk8$WRsMTuc(d8k#|07E(U&{AuTiMfn{}vC2_t9Q%sD-OO&h3Gnj&*Qbm4EyE#hVK$yhgu|oci(WxlZ>R-sN{b@QxSu z-_IR%X!ZK=TVCc-ag+_D4Sy))Z2b5lQmgAh!>hg69-Qv`9s{~XyJ_OLFPDPA*XFwD zxG#M7k%hTnx4Uwu%bU)pP}FW`Q;Iq6nTAhbmjs`hoz|^QcXV(#zG3M`{D|GyB6lWZ!wbE9Z%0AfjKT;i?-SO*Q?kEan!F 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

    25k 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 literal 139268 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

    g#%cKF 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�bJGN2Tf?q2$NQ6PC5NtPM*G|Wz%ay!d^e#mA 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 Hoc@0RSX%kL

    BWxyj7Wat!XvzHXm$%Vm3n#Couf;qF@ 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%0Aonp$R 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�}$@fPW`Q^G&W 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