or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

area-detectors.mdcore-framework.mdepics-integration.mdflyers-continuous-scanning.mdindex.mdmotors-positioners.mdsimulation-testing.mdspecialized-devices.md

motors-positioners.mddocs/

0

# Motors and Positioners

1

2

Positioning devices including EPICS motors, software positioners, and pseudo positioners for coordinate transformations and multi-axis control. These classes provide standardized interfaces for moving hardware to specific positions and tracking movement status.

3

4

## Capabilities

5

6

### EPICS Motors

7

8

Interface to EPICS motor records providing comprehensive motor control functionality.

9

10

```python { .api }

11

class EpicsMotor(Device):

12

"""

13

EPICS motor record interface with comprehensive positioning capabilities.

14

15

Parameters:

16

- prefix (str): EPICS motor record PV prefix

17

- name (str): Motor name for identification

18

- settle_time (float): Time to wait after moves complete

19

- timeout (float): Default timeout for operations

20

"""

21

def __init__(self, prefix='', *, name, settle_time=0.0, timeout=None, **kwargs): ...

22

23

def move(self, position, *, wait=False, timeout=None, **kwargs):

24

"""

25

Move motor to absolute position.

26

27

Parameters:

28

- position (float): Target position in user units

29

- wait (bool): Whether to wait for move completion

30

- timeout (float): Move timeout

31

32

Returns:

33

MoveStatus: Status object tracking move progress

34

"""

35

36

def set(self, position, **kwargs):

37

"""

38

Set motor position (alias for move).

39

40

Parameters:

41

- position (float): Target position

42

43

Returns:

44

MoveStatus: Move status object

45

"""

46

47

def home(self, direction, *, wait=False, timeout=None):

48

"""

49

Home motor in specified direction.

50

51

Parameters:

52

- direction (str or HomeEnum): 'forward', 'reverse', or HomeEnum value

53

- wait (bool): Wait for homing completion

54

- timeout (float): Homing timeout

55

56

Returns:

57

StatusBase: Homing status

58

"""

59

60

def stop(self, *, success=False):

61

"""

62

Stop motor motion.

63

64

Parameters:

65

- success (bool): Whether stop should be considered successful

66

"""

67

68

@property

69

def position(self):

70

"""

71

Current motor position.

72

73

Returns:

74

float: Current position in user units

75

"""

76

77

@property

78

def user_readback(self):

79

"""

80

User readback position.

81

82

Returns:

83

EpicsSignalRO: Readback position signal

84

"""

85

86

@property

87

def user_setpoint(self):

88

"""

89

User setpoint position.

90

91

Returns:

92

EpicsSignal: Setpoint position signal

93

"""

94

95

@property

96

def limits(self):

97

"""

98

Motor soft limits.

99

100

Returns:

101

tuple: (low_limit, high_limit)

102

"""

103

104

@property

105

def moving(self):

106

"""

107

Whether motor is currently moving.

108

109

Returns:

110

bool: True if motor is in motion

111

"""

112

113

@property

114

def settle_time(self):

115

"""

116

Time to wait after moves complete.

117

118

Returns:

119

float: Settle time in seconds

120

"""

121

122

class MotorBundle(Device):

123

"""

124

Collection of motors that can be moved together.

125

"""

126

def __init__(self, *args, **kwargs): ...

127

128

def move(self, position_dict, **kwargs):

129

"""

130

Move multiple motors simultaneously.

131

132

Parameters:

133

- position_dict (dict): Mapping of motor names to positions

134

135

Returns:

136

AndStatus: Combined status for all motor moves

137

"""

138

139

# Motor-related enums

140

class HomeEnum(IntEnum):

141

"""Enumeration for motor homing directions."""

142

forward = 1

143

reverse = 0

144

```

145

146

### Base Positioners

147

148

Abstract base classes for positioning devices.

149

150

```python { .api }

151

class PositionerBase(Device):

152

"""

153

Base class for all positioner devices.

154

"""

155

def __init__(self, *, name=None, **kwargs): ...

156

157

def move(self, position, **kwargs):

158

"""

159

Move to target position.

160

161

Parameters:

162

- position: Target position

163

164

Returns:

165

StatusBase: Move status

166

"""

167

168

def set(self, position, **kwargs):

169

"""

170

Set position (alias for move).

171

172

Parameters:

173

- position: Target position

174

175

Returns:

176

StatusBase: Move status

177

"""

178

179

@property

180

def position(self):

181

"""

182

Current position.

183

184

Returns:

185

Current position value

186

"""

187

188

def stop(self):

189

"""Stop positioner motion."""

190

191

class SoftPositioner(PositionerBase):

192

"""

193

Software-based positioner without hardware backing.

194

195

Parameters:

196

- name (str): Positioner name

197

- init_pos (float): Initial position value

198

"""

199

def __init__(self, *, name=None, init_pos=0, **kwargs): ...

200

201

def move(self, position, **kwargs):

202

"""

203

Move to position (instantaneous for software positioner).

204

205

Parameters:

206

- position (float): Target position

207

208

Returns:

209

Status: Completed status

210

"""

211

212

@property

213

def position(self):

214

"""

215

Current software position.

216

217

Returns:

218

float: Current position

219

"""

220

```

221

222

### Pseudo Positioners

223

224

Positioners that transform between different coordinate systems.

225

226

```python { .api }

227

class PseudoPositioner(Device):

228

"""

229

Multi-axis pseudo positioner with coordinate transformations.

230

231

Transforms between pseudo (user) coordinates and real (motor) coordinates

232

using forward() and inverse() transformation methods.

233

"""

234

def __init__(self, prefix='', *, configuration_attrs=None, read_attrs=None, **kwargs): ...

235

236

def move(self, position, **kwargs):

237

"""

238

Move to pseudo position.

239

240

Parameters:

241

- position: Target pseudo position (single value or dict/tuple for multi-axis)

242

243

Returns:

244

StatusBase: Combined move status for all real motors

245

"""

246

247

def set(self, position, **kwargs):

248

"""

249

Set pseudo position.

250

251

Parameters:

252

- position: Target pseudo position

253

254

Returns:

255

StatusBase: Move status

256

"""

257

258

def forward(self, pseudo_pos):

259

"""

260

Transform pseudo coordinates to real coordinates.

261

262

Parameters:

263

- pseudo_pos: Pseudo position(s)

264

265

Returns:

266

Real position(s) for underlying motors

267

268

Note: Must be implemented by subclasses

269

"""

270

raise NotImplementedError()

271

272

def inverse(self, real_pos):

273

"""

274

Transform real coordinates to pseudo coordinates.

275

276

Parameters:

277

- real_pos: Real motor position(s)

278

279

Returns:

280

Pseudo position(s)

281

282

Note: Must be implemented by subclasses

283

"""

284

raise NotImplementedError()

285

286

@property

287

def position(self):

288

"""

289

Current pseudo position.

290

291

Returns:

292

Current pseudo coordinates

293

"""

294

295

@property

296

def real_position(self):

297

"""

298

Current real motor positions.

299

300

Returns:

301

tuple: Current real motor coordinates

302

"""

303

304

class PseudoSingle(PseudoPositioner):

305

"""

306

Single-axis pseudo positioner.

307

308

Simplified pseudo positioner for single pseudo axis transformations.

309

"""

310

def __init__(self, prefix='', *, name=None, **kwargs): ...

311

312

@property

313

def position(self):

314

"""

315

Current pseudo position.

316

317

Returns:

318

float: Single pseudo position value

319

"""

320

```

321

322

### PV-Based Positioners

323

324

Positioners controlled through individual EPICS process variables.

325

326

```python { .api }

327

class PVPositioner(Device):

328

"""

329

Positioner controlled via separate setpoint and readback PVs.

330

331

Parameters:

332

- prefix (str): PV prefix

333

- name (str): Positioner name

334

- limits (tuple): Software limits (low, high)

335

- egu (str): Engineering units

336

"""

337

def __init__(self, prefix='', *, limits=None, name=None, egu='', **kwargs): ...

338

339

def move(self, position, **kwargs):

340

"""

341

Move to target position.

342

343

Parameters:

344

- position (float): Target position

345

346

Returns:

347

MoveStatus: Move completion status

348

"""

349

350

def stop(self):

351

"""Stop positioner movement."""

352

353

@property

354

def position(self):

355

"""

356

Current position from readback PV.

357

358

Returns:

359

float: Current position

360

"""

361

362

class PVPositionerPC(PVPositioner):

363

"""

364

PV positioner with position compare functionality.

365

366

Uses EPICS position compare feature for precise positioning.

367

"""

368

def __init__(self, prefix='', **kwargs): ...

369

370

class PVPositionerIsClose(PVPositioner):

371

"""

372

PV positioner that uses proximity checking for move completion.

373

374

Parameters:

375

- atol (float): Absolute tolerance for position comparison

376

- rtol (float): Relative tolerance for position comparison

377

"""

378

def __init__(self, prefix='', *, atol=1e-6, rtol=1e-6, **kwargs): ...

379

380

class PVPositionerDone(PVPositioner):

381

"""

382

PV positioner that uses a separate "done" PV for move completion.

383

"""

384

def __init__(self, prefix='', **kwargs): ...

385

```

386

387

## Usage Examples

388

389

### Basic EPICS Motor Usage

390

391

```python

392

from ophyd import EpicsMotor

393

from ophyd.status import wait

394

395

# Create motor instance

396

motor_x = EpicsMotor('XF:28IDC:MOT:01', name='motor_x')

397

motor_x.wait_for_connection()

398

399

# Check current position

400

print(f"Current position: {motor_x.position}")

401

print(f"Limits: {motor_x.limits}")

402

print(f"Moving: {motor_x.moving}")

403

404

# Move to absolute position

405

status = motor_x.move(10.5)

406

wait(status) # Wait for move to complete

407

408

# Move relative to current position

409

current_pos = motor_x.position

410

status = motor_x.move(current_pos + 2.0)

411

wait(status)

412

413

# Home motor

414

status = motor_x.home('forward')

415

wait(status)

416

417

# Stop motor if needed

418

if motor_x.moving:

419

motor_x.stop()

420

```

421

422

### Motor Bundle for Coordinated Movement

423

424

```python

425

from ophyd import EpicsMotor, MotorBundle

426

427

# Create individual motors

428

motor_x = EpicsMotor('XF:28IDC:MOT:01', name='motor_x')

429

motor_y = EpicsMotor('XF:28IDC:MOT:02', name='motor_y')

430

motor_z = EpicsMotor('XF:28IDC:MOT:03', name='motor_z')

431

432

# Create motor bundle

433

xyz_stage = MotorBundle(motor_x, motor_y, motor_z, name='xyz_stage')

434

xyz_stage.wait_for_connection()

435

436

# Move all motors simultaneously

437

target_positions = {

438

'motor_x': 5.0,

439

'motor_y': -2.5,

440

'motor_z': 10.0

441

}

442

443

status = xyz_stage.move(target_positions)

444

wait(status) # All motors will move simultaneously

445

446

print("All motors have reached target positions")

447

```

448

449

### Software Positioner

450

451

```python

452

from ophyd import SoftPositioner

453

454

# Create software positioner

455

virtual_motor = SoftPositioner(name='virtual_motor', init_pos=0.0)

456

457

# Move instantly (no real hardware)

458

status = virtual_motor.move(25.0)

459

print(f"Status done: {status.done}") # True immediately

460

print(f"Position: {virtual_motor.position}") # 25.0

461

462

# Useful for testing or simulation

463

```

464

465

### Pseudo Positioner Example

466

467

```python

468

from ophyd import PseudoPositioner, EpicsMotor, Component

469

import numpy as np

470

471

class TwoThetaStage(PseudoPositioner):

472

"""Pseudo positioner for 2-theta diffractometer geometry."""

473

474

# Real motors

475

theta = Component(EpicsMotor, 'THETA')

476

two_theta = Component(EpicsMotor, 'TWO_THETA')

477

478

# Pseudo axes

479

h = Cpt(PseudoSingle, '')

480

k = Cpt(PseudoSingle, '')

481

482

def forward(self, pseudo_pos):

483

"""Convert (h,k) to (theta, two_theta)."""

484

h, k = pseudo_pos

485

486

# Simple example: assume direct relationship

487

theta_angle = np.arctan2(k, h) * 180 / np.pi

488

two_theta_angle = 2 * theta_angle

489

490

return (theta_angle, two_theta_angle)

491

492

def inverse(self, real_pos):

493

"""Convert (theta, two_theta) to (h,k)."""

494

theta_angle, two_theta_angle = real_pos

495

496

# Convert back to reciprocal space coordinates

497

theta_rad = theta_angle * np.pi / 180

498

h = np.cos(theta_rad)

499

k = np.sin(theta_rad)

500

501

return (h, k)

502

503

# Use pseudo positioner

504

diffractometer = TwoThetaStage('XF:28IDC:DIFF:', name='diffractometer')

505

diffractometer.wait_for_connection()

506

507

# Move in reciprocal space coordinates

508

status = diffractometer.move((1.0, 0.5)) # Move to h=1.0, k=0.5

509

wait(status)

510

511

print(f"Pseudo position (h,k): {diffractometer.position}")

512

print(f"Real positions (theta, 2theta): {diffractometer.real_position}")

513

```

514

515

### PV Positioner

516

517

```python

518

from ophyd import PVPositioner, Component, EpicsSignal, EpicsSignalRO

519

520

class SlitPositioner(PVPositioner):

521

"""Custom slit positioner with separate setpoint and readback."""

522

523

# Define required PVs

524

setpoint = Component(EpicsSignal, 'SP')

525

readback = Component(EpicsSignalRO, 'RBV')

526

actuate = Component(EpicsSignal, 'GO')

527

528

# Optional: done signal

529

done = Component(EpicsSignalRO, 'DONE')

530

531

# Create slit positioner

532

slit_gap = SlitPositioner('XF:28IDC:SLIT:GAP:', name='slit_gap', limits=(0, 20))

533

slit_gap.wait_for_connection()

534

535

# Move slit gap

536

status = slit_gap.move(5.5)

537

wait(status)

538

539

print(f"Slit gap: {slit_gap.position} mm")

540

```

541

542

### Position Monitoring

543

544

```python

545

from ophyd import EpicsMotor

546

547

motor = EpicsMotor('XF:28IDC:MOT:01', name='motor')

548

motor.wait_for_connection()

549

550

# Subscribe to position changes

551

def position_changed(value=None, **kwargs):

552

print(f"Motor position changed to: {value}")

553

554

motor.user_readback.subscribe(position_changed)

555

556

# Move motor - will trigger callbacks

557

status = motor.move(15.0)

558

559

# Monitor move progress

560

def move_progress(fraction=None, **kwargs):

561

print(f"Move progress: {fraction * 100:.1f}%")

562

563

status.add_callback(move_progress)

564

wait(status)

565

566

# Clear subscription

567

motor.user_readback.clear_sub(position_changed)

568

```