Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Miscellaneous changes to pyat #103

Merged
merged 23 commits into from
Apr 2, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9a0c2f2
Ohmi_envelope and test_matlab changes.
T-Nicholls Mar 7, 2019
7ed8cd8
Rearrange pyat README.
T-Nicholls Mar 12, 2019
7268a9b
Add check that PassMethod exists.
T-Nicholls Mar 12, 2019
8f706d5
Fix typos in test_matlab/README.rst
T-Nicholls Mar 12, 2019
6c38f8f
Update tests to support new PassMethod existence check.
T-Nicholls Mar 14, 2019
6e5442a
Fix Matlab comparison test failures and simplify pyAT install instruc…
T-Nicholls Mar 14, 2019
5a311b0
Ensure radiation state for lattice call of get_mcf.
T-Nicholls Mar 15, 2019
6fe7b64
Add another pass method check and remove last multi_dot useage.
T-Nicholls Mar 22, 2019
5b5318a
Add tests for new element methods.
T-Nicholls Mar 22, 2019
83b933d
Add tests for lattice object creation.
T-Nicholls Mar 22, 2019
6ea36fe
Add tests for the rest of the public lattice object methods.
T-Nicholls Mar 25, 2019
a8bc5dc
Changes to str and repr methods and add some more tests.
T-Nicholls Mar 25, 2019
94f2f16
Move all test fixtures to conftest and set their scope to session.
T-Nicholls Mar 26, 2019
239ec10
Add new get elements method to get elements by family or class.
T-Nicholls Mar 28, 2019
8c2f8e0
Fix failing tests for python 3 versions and numpy < 1.13 versions.
T-Nicholls Mar 28, 2019
7a08a3f
Various suggested ammendments.
T-Nicholls Apr 1, 2019
461ed53
Move updated get_elements to lattice.utils and add a test for it.
T-Nicholls Apr 1, 2019
c7ba30f
Revert energy guessing change to lattice object to keep support for i…
T-Nicholls Apr 1, 2019
94a355e
Update tests for get_ring_energy.
T-Nicholls Apr 1, 2019
010f072
Add quiet parameter to get_elements to suppress the print statement.
T-Nicholls Apr 1, 2019
1b2e255
Merge changes from #102 and resolve conflicts.
T-Nicholls Apr 1, 2019
33e5dfc
Add final few tests for lattice object and other minor changes.
T-Nicholls Apr 2, 2019
ecacabb
Split up lattice obect tests and make pass method checking more robust.
T-Nicholls Apr 2, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 deletions pyat/at/lattice/lattice_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from scipy.constants import physical_constants as cst
from warnings import warn
from at.lattice import elements, get_s_pos, checktype, uint32_refpts
from at.lattice import AtWarning, AtError
from at.lattice import AtWarning, AtError, get_ring_energy
from at.physics import find_orbit4, find_orbit6, find_sync_orbit, find_m44
from at.physics import find_m66, linopt, ohmi_envelope, get_mcf

Expand Down Expand Up @@ -53,7 +53,6 @@ def full_scan(elems, keep_all=False, **kwargs):
"""
params = []
valid_elems = []
energies = []
thetas = []
attributes = dict(_radiation=False)

Expand All @@ -68,8 +67,6 @@ def full_scan(elems, keep_all=False, **kwargs):
warn(AtWarning('item {0} ({1}) is not an AT element: '
'ignored'.format(idx, elem)))
continue
if hasattr(elem, 'Energy'):
energies.append(elem.Energy)
if isinstance(elem, elements.Dipole):
thetas.append(elem.BendingAngle)
if elem.PassMethod.endswith(
Expand All @@ -90,13 +87,7 @@ def full_scan(elems, keep_all=False, **kwargs):
attributes['name'] = ''
if 'energy' not in kwargs:
# Guess energy from the Energy attribute of the elems
if not energies:
raise AtError('Lattice energy is not defined')
energy = max(energies)
if min(energies) < energy:
warn(AtWarning('Inconsistent energy values, '
'"energy" set to {0}'.format(energy)))
attributes['energy'] = energy
attributes['energy'] = get_ring_energy(elems)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a problem here:
I made significant modifications to lattice_object in the plot_preparation branch. To avoid unnecessary list copies (build a list first, and then give it to super(Lattice, self).__init__, the list constructor, which will itself build a copy of this list), full_scan now accepts any iterable as input and returns an iterator over the elements. So it is executed within super(Lattice, self).__init__. The gain is most effective when the input to full_scan is itself an iterator. In such a case, after the first loop over elements, get_ring_energy will get an empty iterator, and fail.

Example: we have a Lattice ring, without RingParam element but with an RFCavity giving the energy. Let's assume we want to filter this lattice with some condition:

ring2=Lattice([elem for elem in ring if <some condition>])
Here we build a list first, which will be copied by the list constructor. get_ring_energy will work.

ring2=Lattice(elem for elem in ring if <some condition>)
Here we have no intermediate list, <some condition> will be tested within the list constructor and get_ring_energy will fail.

I think we should keep the possibility of accepting any iterable (sequence or iterator) as input, not only a sequence.

For the time being, I suggest to keep lattice_object unchanged to avoid merging problems. Keep all your other modifications as they are, let's merge both branches and start over then with this code factorisation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will revert the modifications to lattice_object for now. Yes, I am learning that working on multiple branches at once is a recipe for problems, the test on #102 is failing because it relies on changes made here.

if 'periodicity' not in kwargs:
# Guess periodicity from the bending angles of the lattice
try:
Expand Down Expand Up @@ -155,8 +146,7 @@ def __getitem__(self, key):
return super(Lattice, self).__getitem__(key)
except TypeError:
key = uint32_refpts(key, len(self))
items = [super(Lattice, self).__getitem__(i) for i in key]
return items[0] if len(items) == 1 else items
return [super(Lattice, self).__getitem__(i) for i in key]

def __setitem__(self, key, values):
try:
Expand Down
37 changes: 37 additions & 0 deletions pyat/at/lattice/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"""
import numpy
import itertools
from warnings import warn
from at.lattice import elements


class AtError(Exception):
Expand Down Expand Up @@ -185,6 +187,41 @@ def get_s_pos(ring, refpts=None):
return s_pos[refpts]


def get_ring_energy(ring):
"""Establish the energy of the ring from the Energy attribute of the
elements. Energies of RingParam elements are most prioritised, if none are
found then the energies from RFCavity elements will be used, if none are
found then the energies from all elements will be used. An error will be
raised if no elements have a 'Energy' attribute or if inconsistent values
for energy are found.

Args:
ring: sequence of elements of which you wish to establish the energy.
"""
rp_energies = []
rf_energies = []
energies = []
for elem in ring:
if hasattr(elem, 'Energy'):
energies.append(elem.Energy)
if isinstance(elem, elements.RingParam):
rp_energies.append(elem.Energy)
elif isinstance(elem, elements.RFCavity):
rf_energies.append(elem.Energy)
if not energies:
raise AtError('Lattice energy is not defined.')
elif rp_energies:
energy = max(rp_energies)
elif rf_energies:
energy = max(rf_energies)
else:
energy = max(energies)
if len(set(energies)) > 1:
warn(AtWarning('Inconsistent energy values in ring, {0} has been '
'used.'.format(energy)))
return energy


def tilt_elem(elem, rots):
"""
set a new tilt angle to an element. The rotation matrices are stored in the R1 and R2 attributes
Expand Down
25 changes: 13 additions & 12 deletions pyat/at/load/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,10 @@ def sanitise_class(index, class_name, kwargs):
AttributeError: if the PassMethod and Class are incompatible.
"""
def err_message(message, *args):
location = '' if index is None else 'Error in element {0}: '.format(
index)
return ''.join(
(location,
'PassMethod {0} is not compatible with '.format(pass_method),
message.format(*args), '\n{0}'.format(kwargs)))
location = ': ' if index is None else ' {0}: '.format(index)
return ''.join(('Error in element', location,
'PassMethod {0} '.format(pass_method),
message.format(*args), '\n{0}'.format(kwargs)))

pass_method = kwargs.get('PassMethod')
if pass_method is not None:
Expand All @@ -164,19 +162,22 @@ def err_message(message, *args):
'../../integrators',
pass_method + extension))
if not os.path.exists(file_path):
raise AttributeError('PassMethod {0} does not exist.'
'\n{1}'.format(pass_method, kwargs))
raise AttributeError(err_message("does not exist."))
elif (pass_method == 'IdentityPass') and (length != 0.0):
raise AttributeError(err_message("length {0}.", length))
raise AttributeError(err_message("is not compatible with "
"length {0}.", length))
elif pass_to_class is not None:
if pass_to_class != class_name:
raise AttributeError(err_message("Class {0}.", class_name))
raise AttributeError(err_message("is not compatible with "
"Class {0}.", class_name))
elif class_name in ['Marker', 'Monitor', 'RingParam']:
if pass_method != 'IdentityPass':
raise AttributeError(err_message("Class {0}.", class_name))
raise AttributeError(err_message("is not compatible with "
"Class {0}.", class_name))
elif class_name == 'Drift':
if pass_method != 'DriftPass':
raise AttributeError(err_message("Class {0}.", class_name))
raise AttributeError(err_message("is not compatible with "
"Class {0}.", class_name))

class_name = find_class_name(elem_dict, quiet=quiet)
if check:
Expand Down
4 changes: 2 additions & 2 deletions pyat/at/physics/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def get_twiss(ring, dp=0.0, refpts=None, get_chrom=False, orbit=None,
l_down['closed_orbit'])[:, :4] / ddp
disp0 = (d0_up['closed_orbit'] - d0_down['closed_orbit'])[:4] / ddp
else:
chrom = None
chrom = numpy.array([numpy.NaN, numpy.NaN])
dispersion = numpy.NaN
disp0 = numpy.NaN

Expand Down Expand Up @@ -314,7 +314,7 @@ def analyze(r44):
l_down['closed_orbit'])[:, :4] / ddp
disp0 = (d0_up['closed_orbit'] - d0_down['closed_orbit'])[:4] / ddp
else:
chrom = None
chrom = numpy.array([numpy.NaN, numpy.NaN])
dispersion = numpy.NaN
disp0 = numpy.NaN

Expand Down
28 changes: 8 additions & 20 deletions pyat/at/physics/radiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
"""
import numpy
from scipy.linalg import inv, det, solve_sylvester
from warnings import warn
import at
from at.lattice import uint32_refpts, RFCavity, AtError, AtWarning
from at.lattice import uint32_refpts, get_ring_energy
from at.tracking import lattice_pass
from at.physics import find_orbit6, find_m66, find_elem_m66, get_tunes_damp
# noinspection PyUnresolvedReferences
Expand Down Expand Up @@ -120,21 +119,7 @@ def propag(m, cumb, orbit6):
if isinstance(ring, at.lattice.Lattice):
energy = ring.energy
else:
rf_energies = []
energies = []
for elem in ring:
if hasattr(elem, 'Energy'):
energies.append(elem.Energy)
if isinstance(elem, RFCavity):
rf_energies.append(elem.Energy)
if len(energies) is 0:
raise AtError('Lattice energy is not defined')
else:
energy = max(energies if len(rf_energies) is 0
else rf_energies)
if len(set(energies)) > 1:
warn(AtWarning('Inconsistent energy values in ring, {0} '
'has been used'.format(energy)))
energy = get_ring_energy(ring)

if orbit is None:
orbit, _ = find_orbit6(ring, keep_lattice=keep_lattice)
Expand Down Expand Up @@ -171,8 +156,11 @@ def propag(m, cumb, orbit6):
data0 = numpy.rec.fromarrays(
(rr, rr4, mring, orbit, emitxy, emitxyz),
dtype=ENVELOPE_DTYPE)
data = numpy.rec.fromrecords(
list(map(propag, ms, bbcum[uint32refs], orbs[uint32refs, :])),
dtype=ENVELOPE_DTYPE)
if uint32refs.shape == (0,):
data = numpy.recarray((0,), dtype=ENVELOPE_DTYPE)
else:
data = numpy.rec.fromrecords(
list(map(propag, ms, bbcum[uint32refs], orbs[uint32refs, :])),
dtype=ENVELOPE_DTYPE)

return data0, r66data, data
2 changes: 1 addition & 1 deletion pyat/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pytest>=2.9
numpy>=1.13
numpy>=1.10
scipy>=0.16
pytest-lazy-fixture
2 changes: 1 addition & 1 deletion pyat/test/test_lattice_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def test_lattice_creation_warnings_and_errors():
def test_lattice_string_ordering():
l = Lattice([elements.Drift('D0', 1.0, attr1=numpy.array(0))], name='lat',
energy=5, periodicity=1, attr2=3)
# Dictionary ordering is only in Python >= 3.6
# Default dictionary ordering is only in Python >= 3.6
if sys.version_info < (3, 6):
assert l.__str__().startswith("Lattice(<1 elements>, ")
assert l.__str__().endswith(", attr2=3)")
Expand Down