Coverage for install/scipp/core/variable.py: 62%

95 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-11-17 01:51 +0000

1# SPDX-License-Identifier: BSD-3-Clause 

2# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) 

3# @author Matthew Andrew 

4# ruff: noqa: E501 

5 

6from __future__ import annotations 

7 

8import warnings 

9from collections.abc import Iterable, Sequence 

10from contextlib import contextmanager 

11from typing import Any, Generator, SupportsIndex, TypeVar 

12 

13import numpy as _np 

14from numpy.typing import ArrayLike 

15 

16from .._scipp import core as _cpp 

17from ..typing import DTypeLike 

18from ..units import DefaultUnit, default_unit 

19from ._sizes import _parse_dims_shape_sizes 

20from .cpp_classes import DType, Unit, Variable 

21 

22NumberOrVar = TypeVar('NumberOrVar', int, float, Variable) 

23 

24 

25def scalar( 

26 value: Any, 

27 *, 

28 variance: Any = None, 

29 unit: Unit | str | DefaultUnit | None = default_unit, 

30 dtype: DTypeLike | None = None, 

31) -> Variable: 

32 """Constructs a zero dimensional :class:`Variable` with a unit and optional 

33 variance. 

34 

35 Parameters 

36 ---------- 

37 value: 

38 Initial value. 

39 variance: 

40 Optional, initial variance. 

41 unit: 

42 Optional, unit. 

43 dtype: scipp.typing.DTypeLike 

44 Optional, type of underlying data. By default, 

45 the dtype is inferred from the `value` argument. 

46 Cannot be specified for value types of 

47 Dataset or DataArray. 

48 

49 Returns 

50 ------- 

51 : 

52 A scalar (zero-dimensional) Variable. 

53 

54 See Also 

55 -------- 

56 scipp.array, scipp.empty, scipp.index, scipp.ones, scipp.zeros 

57 

58 Examples 

59 -------- 

60 With deduced dtype and default unit: 

61 

62 >>> sc.scalar(3.14) 

63 <scipp.Variable> () float64 [dimensionless] 3.14 

64 

65 >>> sc.scalar('a string') 

66 <scipp.Variable> () string <no unit> "a string" 

67 

68 Or specifying a unit and dtype: 

69 

70 >>> sc.scalar(3.14, unit='m', dtype=int) 

71 <scipp.Variable> () int64 [m] 3 

72 

73 Calling ``scalar`` with a list (or similar array-like object) will store that 

74 object in a scalar variable and *not* create an array variable: 

75 

76 >>> sc.scalar([1, 2, 3]) 

77 <scipp.Variable> () PyObject <no unit> [1, 2, 3] 

78 """ 

79 return _cpp.Variable( # type: ignore[no-any-return] 

80 dims=(), values=value, variances=variance, unit=unit, dtype=dtype 

81 ) 

82 

83 

84def index(value: Any, *, dtype: DTypeLike | None = None) -> Variable: 

85 """Constructs a zero dimensional :class:`Variable` representing an index. 

86 

87 This is equivalent to calling :py:func:`scipp.scalar` with unit=None. 

88 

89 Parameters 

90 ---------- 

91 value: 

92 Initial value. 

93 dtype: scipp.typing.DTypeLike 

94 Optional, type of underlying data. By default, 

95 the dtype is inferred from the `value` argument. 

96 

97 Returns 

98 ------- 

99 : 

100 A scalar (zero-dimensional) variable without unit. 

101 

102 See Also 

103 -------- 

104 scipp.scalar, scipp.array 

105 

106 Examples 

107 -------- 

108 

109 >>> sc.index(123) 

110 <scipp.Variable> () int64 <no unit> 123 

111 """ 

112 return scalar(value=value, dtype=dtype, unit=None) 

113 

114 

115def zeros( 

116 *, 

117 dims: Sequence[str] | None = None, 

118 shape: Sequence[int] | None = None, 

119 sizes: dict[str, int] | None = None, 

120 unit: Unit | str | DefaultUnit | None = default_unit, 

121 dtype: DTypeLike = DType.float64, 

122 with_variances: bool = False, 

123) -> Variable: 

124 """Constructs a :class:`Variable` with default initialized values with 

125 given dimension labels and shape. 

126 

127 The dims and shape can also be specified using a `sizes` dict. 

128 Optionally can add default initialized variances. 

129 Only keyword arguments accepted. 

130 

131 Parameters 

132 ---------- 

133 dims: 

134 Optional (if sizes is specified), dimension labels. 

135 shape: 

136 Optional (if sizes is specified), dimension sizes. 

137 sizes: 

138 Optional, dimension label to size map. 

139 unit: 

140 Unit of contents. 

141 dtype: scipp.typing.DTypeLike 

142 Type of underlying data. 

143 with_variances: 

144 If True includes variances initialized to 0. 

145 

146 Returns 

147 ------- 

148 : 

149 A variable filled with 0's. 

150 

151 See Also 

152 -------- 

153 scipp.array, scipp.empty, scipp.ones, scipp.scalar, scipp.zeros_like 

154 

155 Examples 

156 -------- 

157 

158 >>> sc.zeros(dims=['x'], shape=[4]) 

159 <scipp.Variable> (x: 4) float64 [dimensionless] [0, 0, 0, 0] 

160 

161 >>> sc.zeros(sizes={'x': 4}) 

162 <scipp.Variable> (x: 4) float64 [dimensionless] [0, 0, 0, 0] 

163 

164 >>> sc.zeros(sizes={'y': 3}, with_variances=True) 

165 <scipp.Variable> (y: 3) float64 [dimensionless] [0, 0, 0] [0, 0, 0] 

166 

167 >>> sc.zeros(sizes={'z': 3}, unit='kg', dtype=int) 

168 <scipp.Variable> (z: 3) int64 [kg] [0, 0, 0] 

169 """ 

170 

171 return _cpp.zeros( # type: ignore[no-any-return] 

172 **_parse_dims_shape_sizes(dims, shape, sizes), 

173 unit=unit, 

174 dtype=dtype, 

175 with_variances=with_variances, 

176 ) 

177 

178 

179def ones( 

180 *, 

181 dims: Sequence[str] | None = None, 

182 shape: Sequence[int] | None = None, 

183 sizes: dict[str, int] | None = None, 

184 unit: Unit | str | DefaultUnit | None = default_unit, 

185 dtype: DTypeLike = DType.float64, 

186 with_variances: bool = False, 

187) -> Variable: 

188 """Constructs a :class:`Variable` with values initialized to 1 with 

189 given dimension labels and shape. 

190 

191 The dims and shape can also be specified using a `sizes` dict. 

192 

193 Parameters 

194 ---------- 

195 dims: 

196 Optional (if sizes is specified), dimension labels. 

197 shape: 

198 Optional (if sizes is specified), dimension sizes. 

199 sizes: 

200 Optional, dimension label to size map. 

201 unit: 

202 Unit of contents. 

203 dtype: scipp.typing.DTypeLike 

204 Type of underlying data. 

205 with_variances: 

206 If True includes variances initialized to 1. 

207 

208 Returns 

209 ------- 

210 : 

211 A variable filled with 1's. 

212 

213 See Also 

214 -------- 

215 scipp.array, scipp.empty, scipp.ones_like, scipp.scalar, scipp.zeros 

216 

217 Examples 

218 -------- 

219 

220 >>> sc.ones(dims=['x'], shape=[4]) 

221 <scipp.Variable> (x: 4) float64 [dimensionless] [1, 1, 1, 1] 

222 

223 >>> sc.ones(sizes={'x': 4}) 

224 <scipp.Variable> (x: 4) float64 [dimensionless] [1, 1, 1, 1] 

225 

226 >>> sc.ones(sizes={'y': 3}, with_variances=True) 

227 <scipp.Variable> (y: 3) float64 [dimensionless] [1, 1, 1] [1, 1, 1] 

228 

229 >>> sc.ones(sizes={'z': 3}, unit='kg', dtype=int) 

230 <scipp.Variable> (z: 3) int64 [kg] [1, 1, 1] 

231 """ 

232 return _cpp.ones( # type: ignore[no-any-return] 

233 **_parse_dims_shape_sizes(dims, shape, sizes), 

234 unit=unit, 

235 dtype=dtype, 

236 with_variances=with_variances, 

237 ) 

238 

239 

240def empty( 

241 *, 

242 dims: Sequence[str] | None = None, 

243 shape: Sequence[int] | None = None, 

244 sizes: dict[str, int] | None = None, 

245 unit: Unit | str | DefaultUnit | None = default_unit, 

246 dtype: DTypeLike = DType.float64, 

247 with_variances: bool = False, 

248 aligned: bool = True, 

249) -> Variable: 

250 """Constructs a :class:`Variable` with uninitialized values with given 

251 dimension labels and shape. 

252 

253 The dims and shape can also be specified using a `sizes` dict. 

254 

255 Warning 

256 ------- 

257 'Uninitialized' means that values have undetermined values. 

258 Reading elements before writing to them is undefined behavior. 

259 Consider using :py:func:`scipp.zeros` unless you 

260 know what you are doing and require maximum performance. 

261 

262 Parameters 

263 ---------- 

264 dims: 

265 Optional (if sizes is specified), dimension labels. 

266 shape: 

267 Optional (if sizes is specified), dimension sizes. 

268 sizes: 

269 Optional, dimension label to size map. 

270 unit: 

271 Unit of contents. 

272 dtype: scipp.typing.DTypeLike 

273 Type of underlying data. 

274 with_variances: 

275 If True includes uninitialized variances. 

276 aligned: 

277 Initial value for the alignment flag. 

278 

279 Returns 

280 ------- 

281 : 

282 A variable with uninitialized elements. 

283 

284 See Also 

285 -------- 

286 scipp.array, scipp.empty_like, scipp.ones, scipp.scalar, scipp.zeros 

287 

288 Examples 

289 -------- 

290 

291 >>> var = sc.empty(dims=['x'], shape=[4]) 

292 >>> var[:] = sc.scalar(2.0) # initialize values before printing 

293 >>> var 

294 <scipp.Variable> (x: 4) float64 [dimensionless] [2, 2, 2, 2] 

295 """ 

296 return _cpp.empty( # type: ignore[no-any-return] 

297 **_parse_dims_shape_sizes(dims, shape, sizes), 

298 unit=unit, 

299 dtype=dtype, 

300 with_variances=with_variances, 

301 aligned=aligned, 

302 ) 

303 

304 

305def full( 

306 *, 

307 value: Any, 

308 variance: Any = None, 

309 dims: Sequence[str] | None = None, 

310 shape: Sequence[int] | None = None, 

311 sizes: dict[str, int] | None = None, 

312 unit: Unit | str | DefaultUnit | None = default_unit, 

313 dtype: DTypeLike | None = None, 

314) -> Variable: 

315 """Constructs a :class:`Variable` with values initialized to the specified 

316 value with given dimension labels and shape. 

317 

318 The dims and shape can also be specified using a `sizes` dict. 

319 

320 Parameters 

321 ---------- 

322 value: 

323 The value to fill the Variable with. 

324 variance: 

325 The variance to fill the Variable with. 

326 dims: 

327 Optional (if sizes is specified), dimension labels. 

328 shape: 

329 Optional (if sizes is specified), dimension sizes. 

330 sizes: 

331 Optional, dimension label to size map. 

332 unit: 

333 Unit of contents. 

334 dtype: scipp.typing.DTypeLike 

335 Type of underlying data. 

336 

337 Returns 

338 ------- 

339 : 

340 A variable filled with given value. 

341 

342 See Also 

343 -------- 

344 scipp.empty, scipp.full_like, scipp.ones, scipp.zeros 

345 

346 Examples 

347 -------- 

348 

349 >>> sc.full(value=2, dims=['x'], shape=[2]) 

350 <scipp.Variable> (x: 2) int64 [dimensionless] [2, 2] 

351 

352 >>> sc.full(value=2, sizes={'x': 2}) 

353 <scipp.Variable> (x: 2) int64 [dimensionless] [2, 2] 

354 

355 >>> sc.full(value=5, sizes={'y': 3, 'x': 2}) 

356 <scipp.Variable> (y: 3, x: 2) int64 [dimensionless] [5, 5, ..., 5, 5] 

357 

358 >>> sc.full(value=2.0, variance=0.1, sizes={'x': 3}, unit='s') 

359 <scipp.Variable> (x: 3) float64 [s] [2, 2, 2] [0.1, 0.1, 0.1] 

360 """ 

361 return ( 

362 scalar(value=value, variance=variance, unit=unit, dtype=dtype) 

363 .broadcast(**_parse_dims_shape_sizes(dims, shape, sizes)) 

364 .copy() 

365 ) 

366 

367 

368def vector( 

369 value: ArrayLike, *, unit: Unit | str | DefaultUnit | None = default_unit 

370) -> Variable: 

371 """Constructs a zero dimensional :class:`Variable` holding a single length-3 

372 vector. 

373 

374 Parameters 

375 ---------- 

376 value: 

377 Initial value, a list or 1-D numpy array. 

378 unit: 

379 Unit of contents. 

380 

381 Returns 

382 ------- 

383 : 

384 A scalar (zero-dimensional) variable of a vector. 

385 

386 See Also 

387 -------- 

388 scipp.vectors: 

389 Construct an array of vectors. 

390 scipp.spatial.as_vectors: 

391 Construct vectors from Scipp Variables 

392 

393 Examples 

394 -------- 

395 

396 >>> sc.vector(value=[1, 2, 3]) 

397 <scipp.Variable> () vector3 [dimensionless] (1, 2, 3) 

398 

399 >>> sc.vector(value=[4, 5, 6], unit='m') 

400 <scipp.Variable> () vector3 [m] (4, 5, 6) 

401 """ 

402 return vectors(dims=(), values=value, unit=unit) 

403 

404 

405def vectors( 

406 *, 

407 dims: Sequence[str], 

408 values: ArrayLike, 

409 unit: Unit | str | DefaultUnit | None = default_unit, 

410) -> Variable: 

411 """Constructs a :class:`Variable` with given dimensions holding an array 

412 of length-3 vectors. 

413 

414 Parameters 

415 ---------- 

416 dims: 

417 Dimension labels. 

418 values: 

419 Initial values. 

420 unit: 

421 Unit of contents. 

422 

423 Returns 

424 ------- 

425 : 

426 A variable of vectors with given values. 

427 

428 See Also 

429 -------- 

430 scipp.vector: 

431 Construct a single vector. 

432 scipp.spatial.as_vectors: 

433 Construct vectors from Scipp Variables 

434 

435 Examples 

436 -------- 

437 

438 >>> sc.vectors(dims=['x'], values=[[1, 2, 3]]) 

439 <scipp.Variable> (x: 1) vector3 [dimensionless] [(1, 2, 3)] 

440 

441 >>> var = sc.vectors(dims=['x'], values=[[1, 2, 3], [4, 5, 6]]) 

442 >>> var 

443 <scipp.Variable> (x: 2) vector3 [dimensionless] [(1, 2, 3), (4, 5, 6)] 

444 >>> var.values 

445 array([[1., 2., 3.], 

446 [4., 5., 6.]]) 

447 

448 >>> sc.vectors(dims=['x'], values=[[1, 2, 3], [4, 5, 6]], unit='mm') 

449 <scipp.Variable> (x: 2) vector3 [mm] [(1, 2, 3), (4, 5, 6)] 

450 """ 

451 return _cpp.Variable( # type: ignore[no-any-return] 

452 dims=dims, values=values, unit=unit, dtype=DType.vector3 

453 ) 

454 

455 

456def array( 

457 *, 

458 dims: Iterable[str], 

459 values: ArrayLike, 

460 variances: ArrayLike | None = None, 

461 unit: Unit | str | DefaultUnit | None = default_unit, 

462 dtype: DTypeLike | None = None, 

463) -> Variable: 

464 """Constructs a :class:`Variable` with given dimensions, containing given 

465 values and optional variances. 

466 

467 Dimension and value shape must match. 

468 Only keyword arguments accepted. 

469 

470 Parameters 

471 ---------- 

472 dims: 

473 Dimension labels 

474 values: numpy.typing.ArrayLike 

475 Initial values. 

476 variances: numpy.typing.ArrayLike 

477 Initial variances, must be same shape and size as values. 

478 unit: 

479 Unit of contents. 

480 dtype: scipp.typing.DTypeLike 

481 Type of underlying data. By default, inferred from `values` argument. 

482 

483 Returns 

484 ------- 

485 : 

486 A variable with the given values. 

487 

488 See Also 

489 -------- 

490 scipp.empty, scipp.ones, scipp.scalar, scipp.zeros 

491 

492 Examples 

493 -------- 

494 

495 >>> sc.array(dims=['x'], values=[1, 2, 3]) 

496 <scipp.Variable> (x: 3) int64 [dimensionless] [1, 2, 3] 

497 

498 Multiple dimensions: 

499 

500 >>> sc.array(dims=['x', 'y'], values=[[1, 2, 3], [4, 5, 6]]) 

501 <scipp.Variable> (x: 2, y: 3) int64 [dimensionless] [1, 2, ..., 5, 6] 

502 

503 DType upcasting: 

504 

505 >>> sc.array(dims=['x'], values=[1, 2, 3.0]) 

506 <scipp.Variable> (x: 3) float64 [dimensionless] [1, 2, 3] 

507 

508 Manually specified dtype: 

509 

510 >>> sc.array(dims=['x'], values=[1, 2, 3], dtype=float) 

511 <scipp.Variable> (x: 3) float64 [dimensionless] [1, 2, 3] 

512 

513 Set unit: 

514 

515 >>> sc.array(dims=['x'], values=[1, 2, 3], unit='m') 

516 <scipp.Variable> (x: 3) int64 [m] [1, 2, 3] 

517 

518 Setting variances: 

519 

520 >>> sc.array(dims=['x'], values=[1.0, 2.0, 3.0], variances=[0.1, 0.2, 0.3]) 

521 <scipp.Variable> (x: 3) float64 [dimensionless] [1, 2, 3] [0.1, 0.2, 0.3] 

522 """ 

523 return _cpp.Variable( # type: ignore[no-any-return] 

524 dims=dims, values=values, variances=variances, unit=unit, dtype=dtype 

525 ) 

526 

527 

528def _expect_no_variances(args: dict[str, Variable | None]) -> None: 

529 has_variances = [ 

530 key 

531 for key, val in args.items() 

532 if val is not None and val.variances is not None 

533 ] 

534 if has_variances: 

535 raise _cpp.VariancesError( 

536 'Arguments cannot have variances. ' f'Passed variances in {has_variances}' 

537 ) 

538 

539 

540# Assumes that all arguments are Variable or None. 

541def _ensure_same_unit( 

542 *, unit: Unit | str | DefaultUnit | None, args: dict[str, Variable | None] 

543) -> tuple[dict[str, Variable | None], Unit | str | DefaultUnit | None]: 

544 if unit == default_unit: 

545 units = {key: val.unit for key, val in args.items() if val is not None} 

546 if len(set(units.values())) != 1: 

547 raise _cpp.UnitError( 

548 f'All units of the following arguments must be equal: {units}. ' 

549 'You can specify a unit explicitly with the `unit` argument.' 

550 ) 

551 unit = next(iter(units.values())) 

552 return { 

553 key: _cpp.to_unit(val, unit, copy=False).value if val is not None else None 

554 for key, val in args.items() 

555 }, unit 

556 

557 

558# Process arguments of arange, linspace, etc and return them as plain numbers or None. 

559def _normalize_range_args( 

560 *, unit: Unit | str | DefaultUnit | None, **kwargs: Any 

561) -> tuple[dict[str, Any], Unit | str | DefaultUnit | None]: 

562 is_var = { 

563 key: isinstance(val, _cpp.Variable) 

564 for key, val in kwargs.items() 

565 if val is not None 

566 } 

567 if any(is_var.values()): 

568 if not all(is_var.values()): 

569 arg_types = {key: type(val) for key, val in kwargs.items()} 

570 raise TypeError( 

571 'Either all of the following arguments or none have to ' 

572 f'be variables: {arg_types}' 

573 ) 

574 _expect_no_variances(kwargs) 

575 return _ensure_same_unit(unit=unit, args=kwargs) 

576 return kwargs, unit 

577 

578 

579def linspace( 

580 dim: str, 

581 start: NumberOrVar, 

582 stop: NumberOrVar, 

583 num: SupportsIndex, 

584 *, 

585 endpoint: bool = True, 

586 unit: Unit | str | DefaultUnit | None = default_unit, 

587 dtype: DTypeLike | None = None, 

588) -> Variable: 

589 """Constructs a :class:`Variable` with `num` evenly spaced samples, 

590 calculated over the interval `[start, stop]`. 

591 

592 Parameters 

593 ---------- 

594 dim: 

595 Dimension label. 

596 start: 

597 The starting value of the sequence. 

598 stop: 

599 The end value of the sequence. 

600 num: 

601 Number of samples to generate. 

602 endpoint: 

603 If True, `step` is the last returned value. 

604 Otherwise, it is not included. 

605 unit: 

606 Unit of contents. 

607 dtype: scipp.typing.DTypeLike 

608 Type of underlying data. By default, inferred from `value` argument. 

609 

610 Returns 

611 ------- 

612 : 

613 A variable of evenly spaced values. 

614 

615 See Also 

616 -------- 

617 scipp.arange, scipp.geomspace, scipp.logspace 

618 

619 Examples 

620 -------- 

621 

622 >>> sc.linspace('x', 2.0, 4.0, num=4) 

623 <scipp.Variable> (x: 4) float64 [dimensionless] [2, 2.66667, 3.33333, 4] 

624 >>> sc.linspace('x', 2.0, 4.0, num=4, endpoint=False) 

625 <scipp.Variable> (x: 4) float64 [dimensionless] [2, 2.5, 3, 3.5] 

626 

627 >>> sc.linspace('x', 1.5, 3.0, num=4, unit='m') 

628 <scipp.Variable> (x: 4) float64 [m] [1.5, 2, 2.5, 3] 

629 """ 

630 range_args, unit = _normalize_range_args(unit=unit, start=start, stop=stop) 

631 return array( 

632 dims=[dim], 

633 values=_np.linspace(**range_args, num=num, endpoint=endpoint), 

634 unit=unit, 

635 dtype=dtype, 

636 ) 

637 

638 

639def geomspace( 

640 dim: str, 

641 start: NumberOrVar, 

642 stop: NumberOrVar, 

643 num: int, 

644 *, 

645 endpoint: bool = True, 

646 unit: Unit | str | DefaultUnit | None = default_unit, 

647 dtype: DTypeLike | None = None, 

648) -> Variable: 

649 """Constructs a :class:`Variable` with values spaced evenly on a log scale 

650 (a geometric progression). 

651 

652 This is similar to :py:func:`scipp.logspace`, but with endpoints specified 

653 directly instead of as exponents. 

654 Each output sample is a constant multiple of the previous. 

655 

656 Parameters 

657 ---------- 

658 dim: 

659 Dimension label. 

660 start: 

661 The starting value of the sequence. 

662 stop: 

663 The end value of the sequence. 

664 num: 

665 Number of samples to generate. 

666 endpoint: 

667 If True, `step` is the last returned value. 

668 Otherwise, it is not included. 

669 unit: 

670 Unit of contents. 

671 dtype: scipp.typing.DTypeLike 

672 Type of underlying data. By default, inferred from `value` argument. 

673 

674 Returns 

675 ------- 

676 : 

677 A variable of evenly spaced values on a logscale. 

678 

679 See Also 

680 -------- 

681 scipp.arange, scipp.linspace, scipp.logspace 

682 

683 Examples 

684 -------- 

685 

686 >>> sc.geomspace('x', 1.0, 1000.0, num=4) 

687 <scipp.Variable> (x: 4) float64 [dimensionless] [1, 10, 100, 1000] 

688 >>> sc.geomspace('x', 1.0, 1000.0, num=4, endpoint=False) 

689 <scipp.Variable> (x: 4) float64 [dimensionless] [1, 5.62341, 31.6228, 177.828] 

690 

691 >>> sc.geomspace('x', 1.0, 100.0, num=3, unit='s') 

692 <scipp.Variable> (x: 3) float64 [s] [1, 10, 100] 

693 """ 

694 range_args, unit = _normalize_range_args(unit=unit, start=start, stop=stop) 

695 return array( 

696 dims=[dim], 

697 values=_np.geomspace(**range_args, num=num, endpoint=endpoint), 

698 unit=unit, 

699 dtype=dtype, 

700 ) 

701 

702 

703def logspace( 

704 dim: str, 

705 start: NumberOrVar, 

706 stop: NumberOrVar, 

707 num: int, 

708 *, 

709 endpoint: bool = True, 

710 base: float = 10.0, 

711 unit: Unit | str | DefaultUnit | None = default_unit, 

712 dtype: DTypeLike | None = None, 

713) -> Variable: 

714 """Constructs a :class:`Variable` with values spaced evenly on a log scale. 

715 

716 This is similar to :py:func:`scipp.geomspace`, but with endpoints specified 

717 as exponents. 

718 

719 Parameters 

720 ---------- 

721 dim: 

722 Dimension label. 

723 start: 

724 The starting value of the sequence. 

725 stop: 

726 The end value of the sequence. 

727 num: 

728 Number of samples to generate. 

729 base: 

730 The base of the log space. 

731 endpoint: 

732 If True, `step` is the last returned value. 

733 Otherwise, it is not included. 

734 unit: 

735 Unit of contents. 

736 dtype: scipp.typing.DTypeLike 

737 Type of underlying data. By default, inferred from `value` argument. 

738 

739 Returns 

740 ------- 

741 : 

742 A variable of evenly spaced values on a logscale. 

743 

744 See Also 

745 -------- 

746 scipp.arange, scipp.geomspace, scipp.linspace 

747 

748 Examples 

749 -------- 

750 

751 >>> sc.logspace('x', 1, 4, num=4) 

752 <scipp.Variable> (x: 4) float64 [dimensionless] [10, 100, 1000, 10000] 

753 >>> sc.logspace('x', 1, 4, num=4, endpoint=False) 

754 <scipp.Variable> (x: 4) float64 [dimensionless] [10, 56.2341, 316.228, 1778.28] 

755 

756 Set a different base: 

757 

758 >>> sc.logspace('x', 1, 4, num=4, base=2) 

759 <scipp.Variable> (x: 4) float64 [dimensionless] [2, 4, 8, 16] 

760 

761 Set a unit: 

762 

763 >>> sc.logspace('x', 1, 3, num=3, unit='m') 

764 <scipp.Variable> (x: 3) float64 [m] [10, 100, 1000] 

765 """ 

766 # Passing unit='one' enforces that start and stop are dimensionless. 

767 range_args, _ = _normalize_range_args(unit='one', start=start, stop=stop) 

768 return array( 

769 dims=[dim], 

770 values=_np.logspace(**range_args, num=num, base=base, endpoint=endpoint), 

771 unit=unit, 

772 dtype=dtype, 

773 ) 

774 

775 

776def arange( 

777 dim: str, 

778 start: NumberOrVar | _np.datetime64 | str, 

779 stop: NumberOrVar | _np.datetime64 | str | None = None, 

780 step: NumberOrVar | None = None, 

781 *, 

782 unit: Unit | str | DefaultUnit | None = default_unit, 

783 dtype: DTypeLike | None = None, 

784) -> Variable: 

785 """Creates a :class:`Variable` with evenly spaced values within a given interval. 

786 

787 Values are generated within the half-open interval [start, stop). 

788 In other words, the interval including start but excluding stop. 

789 

790 ``start``, ``stop``, and ``step`` may be given as plain values or as 0-D variables. 

791 In the latter case this then implies the unit (the units of all arguments must be 

792 identical), but a different unit-scale can still be requested with the ``unit`` 

793 argument. 

794 

795 When all the types or dtypes of the input arguments are the same, the output will 

796 also have this dtype. This is different to :func:`numpy.arange` which will always 

797 produce 64-bit (int64 or float64) outputs. 

798 

799 Warning 

800 ------- 

801 The length of the output might not be numerically stable. 

802 See :func:`numpy.arange`. 

803 

804 Parameters 

805 ---------- 

806 dim: 

807 Dimension label. 

808 start: 

809 Optional, the starting value of the sequence. Default=0. 

810 stop: 

811 End of interval. The interval does not include this value, 

812 except in some (rare) cases where step is not an integer and 

813 floating-point round-off can come into play. 

814 step: 

815 Spacing between values. 

816 unit: 

817 Unit of contents. 

818 dtype: scipp.typing.DTypeLike 

819 Type of underlying data. By default, inferred from `value` argument. 

820 

821 Returns 

822 ------- 

823 : 

824 A variable of evenly spaced values. 

825 

826 See Also 

827 -------- 

828 scipp.geomspace, scipp.linspace, scipp.logspace 

829 

830 Examples 

831 -------- 

832 

833 >>> sc.arange('x', 4) 

834 <scipp.Variable> (x: 4) int64 [dimensionless] [0, 1, 2, 3] 

835 >>> sc.arange('x', 1, 5) 

836 <scipp.Variable> (x: 4) int64 [dimensionless] [1, 2, 3, 4] 

837 >>> sc.arange('x', 1, 5, 0.5) 

838 <scipp.Variable> (x: 8) float64 [dimensionless] [1, 1.5, ..., 4, 4.5] 

839 

840 >>> sc.arange('x', 1, 5, unit='m') 

841 <scipp.Variable> (x: 4) int64 [m] [1, 2, 3, 4] 

842 

843 Datetimes are also supported: 

844 

845 >>> sc.arange('t', '2000-01-01T01:00:00', '2000-01-01T01:01:30', 30 * sc.Unit('s'), dtype='datetime64') 

846 <scipp.Variable> (t: 3) datetime64 [s] [2000-01-01T01:00:00, 2000-01-01T01:00:30, 2000-01-01T01:01:00] 

847 

848 Note that in this case the datetime ``start`` and ``stop`` strings implicitly 

849 define the unit. The ``step`` must have the same unit. 

850 """ 

851 if dtype == 'datetime64' and isinstance(start, str): 

852 start = datetime(start) # type: ignore[assignment] 

853 if not isinstance(stop, str) and stop is not None: 

854 raise TypeError( 

855 'Argument `stop` must be a string or `None` if `start` is a string.' 

856 ) 

857 stop = stop if stop is None else datetime(stop) # type: ignore[assignment] 

858 range_args, unit = _normalize_range_args( 

859 unit=unit, start=start, stop=stop, step=step 

860 ) 

861 types = [ 

862 x.values.dtype if isinstance(x, _cpp.Variable) else _np.asarray(x).dtype 

863 for x in (start, stop, step) 

864 if x is not None 

865 ] 

866 if dtype is None: 

867 candidates = set(types) 

868 if len(candidates) == 1: 

869 dtype = next(iter(candidates)) 

870 if dtype is not None and dtype != 'datetime64': 

871 numpy_dtype = str(dtype) if isinstance(dtype, DType) else dtype 

872 else: 

873 numpy_dtype = None 

874 return array( 

875 dims=[dim], 

876 values=_np.arange(**range_args, dtype=numpy_dtype), 

877 unit=unit, 

878 dtype=dtype, 

879 ) 

880 

881 

882@contextmanager 

883def _timezone_warning_as_error() -> Generator[None, None, None]: 

884 with warnings.catch_warnings(): 

885 warnings.filterwarnings( 

886 'error', category=DeprecationWarning, message='parsing timezone' 

887 ) 

888 try: 

889 yield 

890 except DeprecationWarning: 

891 raise ValueError( 

892 'Parsing timezone aware datetimes is not supported' 

893 ) from None 

894 

895 

896def datetime( 

897 value: str | int | _np.datetime64, 

898 *, 

899 unit: Unit | str | DefaultUnit | None = default_unit, 

900) -> Variable: 

901 """Constructs a zero dimensional :class:`Variable` with a dtype of datetime64. 

902 

903 Parameters 

904 ---------- 

905 value: 

906 

907 - `str`: Interpret the string according to the ISO 8601 date time format. 

908 - `int`: Number of time units (see argument ``unit``) 

909 since Scipp's epoch (see :py:func:`scipp.epoch`). 

910 - `np.datetime64`: Construct equivalent datetime of Scipp. 

911 

912 unit: Unit of the resulting datetime. 

913 Can be deduced if ``value`` is a str or np.datetime64 but 

914 is required if it is an int. 

915 

916 Returns 

917 ------- 

918 : 

919 A scalar variable containing a datetime. 

920 

921 See Also 

922 -------- 

923 scipp.datetimes: 

924 scipp.epoch: 

925 Details in: 

926 'Dates and Times' section in `Data Types <../../reference/dtype.rst>`_ 

927 

928 Examples 

929 -------- 

930 

931 >>> sc.datetime('2021-01-10T14:16:15') 

932 <scipp.Variable> () datetime64 [s] 2021-01-10T14:16:15 

933 >>> sc.datetime('2021-01-10T14:16:15', unit='ns') 

934 <scipp.Variable> () datetime64 [ns] 2021-01-10T14:16:15.000000000 

935 >>> sc.datetime(1610288175, unit='s') 

936 <scipp.Variable> () datetime64 [s] 2021-01-10T14:16:15 

937 

938 Get the current time: 

939 

940 >>> now = sc.datetime('now', unit='s') 

941 """ 

942 if isinstance(value, str): 

943 with _timezone_warning_as_error(): 

944 return scalar(_np.datetime64(value), unit=unit) 

945 return scalar(value, unit=unit, dtype=_cpp.DType.datetime64) 

946 

947 

948def datetimes( 

949 *, 

950 dims: Sequence[str], 

951 values: ArrayLike, 

952 unit: Unit | str | DefaultUnit | None = default_unit, 

953) -> Variable: 

954 """Constructs an array :class:`Variable` with a dtype of datetime64. 

955 

956 Parameters 

957 ---------- 

958 dims: 

959 Dimension labels 

960 values: numpy.typing.ArrayLike 

961 Numpy array or something that can be converted to a 

962 Numpy array of datetimes. 

963 unit: 

964 Unit for the resulting Variable. 

965 Can be deduced if ``values`` contains strings or np.datetime64's. 

966 

967 Returns 

968 ------- 

969 : 

970 An array variable containing a datetime. 

971 

972 See Also 

973 -------- 

974 scipp.datetime: 

975 scipp.epoch: 

976 Details in: 

977 'Dates and Times' section in `Data Types <../../reference/dtype.rst>`_ 

978 

979 Examples 

980 -------- 

981 

982 >>> sc.datetimes(dims=['t'], values=['2021-01-10T01:23:45', 

983 ... '2021-01-11T01:23:45']) 

984 <scipp.Variable> (t: 2) datetime64 [s] [2021-01-10T01:23:45, 2021-01-11T01:23:45] 

985 >>> sc.datetimes(dims=['t'], values=['2021-01-10T01:23:45', 

986 ... '2021-01-11T01:23:45'], unit='h') 

987 <scipp.Variable> (t: 2) datetime64 [h] [2021-01-10T01:00:00, 2021-01-11T01:00:00] 

988 >>> sc.datetimes(dims=['t'], values=[0, 1610288175], unit='s') 

989 <scipp.Variable> (t: 2) datetime64 [s] [1970-01-01T00:00:00, 2021-01-10T14:16:15] 

990 """ 

991 np_unit_str = f'[{u}]' if (u := _cpp.to_numpy_time_string(unit)) else '' 

992 with _timezone_warning_as_error(): 

993 return array( 

994 dims=dims, values=_np.asarray(values, dtype=f'datetime64{np_unit_str}') 

995 ) 

996 

997 

998def epoch(*, unit: Unit | str | DefaultUnit | None) -> Variable: 

999 """Constructs a zero dimensional :class:`Variable` with a dtype of 

1000 datetime64 that contains Scipp's epoch. 

1001 

1002 Currently, the epoch of datetimes in Scipp is the Unix epoch 1970-01-01T00:00:00. 

1003 

1004 Parameters 

1005 ---------- 

1006 unit: 

1007 Unit of the resulting Variable. 

1008 

1009 Returns 

1010 ------- 

1011 : 

1012 A scalar variable containing the datetime of the epoch. 

1013 

1014 See Also 

1015 -------- 

1016 scipp.datetime: 

1017 scipp.datetimes: 

1018 Details in: 

1019 'Dates and Times' section in `Data Types <../../reference/dtype.rst>`_ 

1020 

1021 Examples 

1022 -------- 

1023 

1024 >>> sc.epoch(unit='s') 

1025 <scipp.Variable> () datetime64 [s] 1970-01-01T00:00:00 

1026 """ 

1027 return scalar(0, unit=unit, dtype=_cpp.DType.datetime64)