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

Add type hints to all remaining top-level files #1150

Merged
merged 7 commits into from
Jan 8, 2020

Conversation

karalekas
Copy link
Contributor

@karalekas karalekas commented Jan 2, 2020

Description

Some typing odds and ends.

  • Ignore conftest.py, pyquil/magic.py, and all the files in pyquil/_parser
  • Add typing to all remaining top-level files other than pyquil/paulis.py
  • Finish off @rht's work and complete the typing for pyquil/paulis.py
  • Address all feedback (specifically the division issue in pyQVM)

Next PR will be to deal with the API module, which is the only thing left.

Checklist

  • The above description motivates these changes.
  • All new and existing tests pass locally and on Travis CI.
  • Parameters and return values have type hints with PEP 484 syntax.
  • All code follows Black style and obeys flake8 conventions.
  • The changelog is updated, including author and PR number (@username, gh-xxx).

@karalekas karalekas added devops 🚀 An issue related to CI/CD. quality 🎨 Improve code quality. labels Jan 2, 2020
@karalekas karalekas added this to the v2.16 milestone Jan 2, 2020
@karalekas karalekas requested a review from a team as a code owner January 2, 2020 21:05
@karalekas karalekas requested a review from a team January 2, 2020 21:09
@@ -147,15 +147,14 @@ def pretty_print_probabilities(self, decimal_digits=2):
outcome_dict[outcome] = prob
return outcome_dict

def pretty_print(self, decimal_digits=2):
def pretty_print(self, decimal_digits: int = 2) -> str:
Copy link
Contributor

Choose a reason for hiding this comment

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

wicked smart type inference

@stylewarning
Copy link
Contributor

but who type checks the types?

@stylewarning
Copy link
Contributor

but who type checks the types?

sorry that was a bad "who watches the watchmen" joke

pyquil/operator_estimation.py Outdated Show resolved Hide resolved
pyquil/pyqvm.py Outdated Show resolved Hide resolved
pyquil/pyqvm.py Outdated Show resolved Hide resolved
pyquil/pyqvm.py Outdated Show resolved Hide resolved
return len(self.amplitudes).bit_length() - 1

def __iter__(self):
def __iter__(self) -> Any:
Copy link
Contributor

Choose a reason for hiding this comment

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

Iterator[complex]?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, this is because numpy is a big ball of untyped anarchy, isn't it?

Copy link
Contributor

Choose a reason for hiding this comment

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

could we instead cast the returned value to Iterator[complex]?

Copy link
Contributor

Choose a reason for hiding this comment

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

tried it: mypy doesn't complain, but we also don't actually check that all values in amplitude_vector are complex in __init__, but that is the documented type in the docstring. otoh, Wavefunction.zeros appears to create an amplitude_vector of floats so maybe complex is too restrictive...

pyquil/wavefunction.py Outdated Show resolved Hide resolved
pyquil/wavefunction.py Outdated Show resolved Hide resolved
@appleby
Copy link
Contributor

appleby commented Jan 2, 2020

but who type checks the types?

mymypy? yourpy?

@karalekas karalekas changed the base branch from device-dataclasses to master January 2, 2020 23:34
@rht
Copy link
Contributor

rht commented Jan 3, 2020

but who type checks the types?

mymypy? yourpy?

In fact, mypy is type-checked by itself, e.g. https://github.com/python/mypy/blob/master/mypy/checker.py.

@@ -305,7 +306,7 @@ def measure_observables(
# either the first column, second column, or both and multiplying along the row.
for setting in settings:
# Get the term's coefficient so we can multiply it in later.
coeff = complex(setting.out_operator.coefficient)
coeff = complex(cast(complex, setting.out_operator.coefficient))
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not obvious to me -- why is this needed?

pyquil/pyqvm.py Show resolved Hide resolved
@karalekas karalekas changed the title Add type annotations to nearly all of the top-level files Add type hints to all remaining top-level files Jan 7, 2020
@karalekas karalekas requested a review from appleby January 7, 2020 05:45
Copy link
Contributor

@appleby appleby left a comment

Choose a reason for hiding this comment

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

lgtm. all comments can be of # type: ignore

pyquil/paulis.py Outdated
Comment on lines 324 to 326
pow = result * self
assert isinstance(pow, PauliTerm)
result = pow
Copy link
Contributor

Choose a reason for hiding this comment

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

mypy is weird.

normally I prefer isinstance checks, but maybe a cast would be better inside this loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah this one threw me for a l00p (pun intended). I'll try the cast idea

pyquil/paulis.py Outdated
@@ -330,24 +340,23 @@ def __add__(self, other: Union[PauliDesignator, ExpressionDesignator]) -> "Pauli
else: # is a Number
return self + PauliTerm("I", 0, other)

def __radd__(self, other: ExpressionDesignator) -> "PauliTerm":
def __radd__(self, other: ExpressionDesignator) -> "PauliSum":
"""Adds this PauliTerm with a Number.

:param other: A Number
:returns: A new PauliTerm
Copy link
Contributor

Choose a reason for hiding this comment

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

PauliSum

pyquil/paulis.py Outdated
return PauliTerm("I", 0, other) + self

def __sub__(self, other: Union["PauliTerm", Number]) -> "PauliSum":
def __sub__(self, other: Union["PauliTerm", ExpressionDesignator]) -> "PauliSum":
"""Subtracts a PauliTerm from this one.

:param other: A PauliTerm object or a Number
Copy link
Contributor

Choose a reason for hiding this comment

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

or an Expression ?

@@ -899,6 +917,8 @@ def exponential_map(term: PauliTerm) -> Callable[[float], Program]:
:param term: A pauli term to exponentiate
:returns: A function that takes an angle parameter and returns a program.
"""
assert isinstance(term.coefficient, (float, complex))
Copy link
Contributor

Choose a reason for hiding this comment

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

Is a float possible here? The type annotation on PauliTerm.coefficient above says it's Union[Expression, complex] and the __init__ normalizes any Number to complex.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought so too, but it happens in the unit tests. Unclear to me how, but I think that's a problem for another time

pyquil/paulis.py Outdated
@@ -964,14 +984,13 @@ def reverse_hack(p: Program) -> Program:
highest_target_index = None

for index, op in pauli_term:
assert isinstance(index, int) or isinstance(index, QubitPlaceholder)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
assert isinstance(index, int) or isinstance(index, QubitPlaceholder)
assert isinstance(index, (int, QubitPlaceholder))

@@ -984,6 +1003,7 @@ def reverse_hack(p: Program) -> Program:
# building rotation circuit
quil_prog += change_to_z_basis
quil_prog += cnot_seq
assert isinstance(pauli_term.coefficient, (float, complex)) and highest_target_index is not None
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto here re: float

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ditto

return len(self.amplitudes).bit_length() - 1

def __iter__(self):
def __iter__(self) -> Any:
Copy link
Contributor

Choose a reason for hiding this comment

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

could we instead cast the returned value to Iterator[complex]?

return self.amplitudes.__iter__()

def __getitem__(self, index):
def __getitem__(self, index: int) -> Any:
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto here regarding cast

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ill give it a shot

self, op: str, index: PauliTargetDesignator, coefficient: ExpressionDesignator = 1.0
self,
op: str,
index: Optional[PauliTargetDesignator],
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is there an extra Optional[] here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

because for sI() the qubit argument is optional, so this can actually be None

Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't it be index: Optional[PauliTargetDesignator] = None then?

Copy link
Contributor

Choose a reason for hiding this comment

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

Not necessarily. Despite the name, it's valid (but rare) to have an Optional required argument. Adding the = None makes it a keyword arg, which means it gets a default value if the caller doesn't explicitly provide it. Whereas Optional without = None means you must explicitly pass None when you want index to be None.

Making index a keyword arg here should be backwards compatible, but since it was a required arg before adding the type hints, it seems reasonable for it to remain one after.

@karalekas karalekas merged commit 70659d1 into master Jan 8, 2020
@karalekas karalekas deleted the pyquil-top-level-typing branch January 8, 2020 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
devops 🚀 An issue related to CI/CD. quality 🎨 Improve code quality.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants