Notas de versão do Qiskit 0.14
0.14.0
Terra 0.11.0
Prelúdio
A versão 0.11.0 inclui vários novos recursos e correções de bugs. A maior mudança desta versão é a adição do agendador de pulsos. Isso permite que os usuários definam seu programa quântico como um QuantumCircuit e, em seguida, mapeiem-no para as instruções de pulso subjacentes que controlarão o hardware quântico para implementar o circuito.
Novos Recurso
-
Foram adicionados 5 novos comandos para recuperar facilmente dados específicos do usuário em
BackendProperties:gate_property,gate_error,gate_length,qubit_property,t1,t2,readout_errorefrequency. Eles retornam os valores específicos das propriedades de backend. Por exemplo:from qiskit.test.mock import FakeOurense backend = FakeOurense() properties = backend.properties() gate_property = properties.gate_property('u1') gate_error = properties.gate_error('u1', 0) gate_length = properties.gate_length('u1', 0) qubit_0_property = properties.qubit_property(0) t1_time_0 = properties.t1(0) t2_time_0 = properties.t2(0) readout_error_0 = properties.readout_error(0) frequency_0 = properties.frequency(0) -
Adição do método
Instruction.is_parameterized()para verificar se um objeto de instrução é parametrizado. Esse método retornaTruese e somente se a instrução tiver um objetoParameterExpressionouParametercomo um de seus parâmetros. -
Adicionado um novo passe de análise
Layout2qDistance. Essa passagem permite "pontuar" uma seleção de layout, uma vez queproperty_set['layout']esteja definido. A pontuação será a soma das distâncias de cada porta de dois qubits no circuito, quando elas não estiverem diretamente conectadas. Essa pontuação não considera a direção no mapa de acoplamento. Quanto menor o número, melhor é a seleção do layout.Por exemplo, considere um mapa de acoplamento linear
[0]--[2]--[1]e o seguinte circuito:qr = QuantumRegister(2, 'qr') circuit = QuantumCircuit(qr) circuit.cx(qr[0], qr[1])Se o layout for
{qr[0]:0, qr[1]:1},Layout2qDistancedefiniráproperty_set['layout_score'] = 1. Se o layout for{qr[0]:0, qr[1]:2}, o resultado seráproperty_set['layout_score'] = 0. Quanto menor a pontuação, melhor. -
Adicionado
qiskit.QuantumCircuit.cnotcomo um alias para o métodocxdeQuantumCircuit. Os nomescnotecxsão frequentemente usados de forma intercambiável, agora o método cx pode ser chamado com qualquer um dos nomes. -
Adicionado
qiskit.QuantumCircuit.toffolicomo um alias para o métodoccxdeQuantumCircuit. Os nomestoffolieccxsão frequentemente usados de forma intercambiável, agora o método ccx pode ser chamado com qualquer um dos nomes. -
Adicionado
qiskit.QuantumCircuit.fredkincomo um alias para o métodocswapdeQuantumCircuit. Os nomesfredkinecswapsão frequentemente usados de forma intercambiável, agora o método cswap pode ser chamado com qualquer um dos nomes. -
O modo de saída
latexparaqiskit.visualization.circuit_drawer()e o métodoqiskit.circuit.QuantumCircuit.draw()agora têm um modo de passagem de látex bruto de rótulos de porta e parâmetros. A sintaxe para fazer isso reflete a sintaxe do modo mathtext do matplotlib. Qualquer parte de uma cadeia de rótulos entre um par de caracteres "$" será tratada como látex bruto e passada diretamente para o látex de saída gerado. Isso pode ser aproveitado para adicionar formatação mais avançada aos diagramas de circuito gerados com a gaveta de látex.Antes desta versão, todos os rótulos de porta eram executados por meio de uma conversão utf8 -> latex para garantir que o latex de saída compilasse a cadeia de caracteres conforme esperado. Isso ainda é o que acontece com todas as partes de um rótulo fora do par '$'. Além disso, se quiser usar um cifrão no rótulo, certifique-se de escapar dele na cadeia de caracteres do rótulo (ou seja,
'\$').Você pode misturar e combinar essa passagem com a conversão utf8 -> latex para criar o rótulo exato que deseja, por exemplo:
from qiskit import circuit circ = circuit.QuantumCircuit(2) circ.h([0, 1]) circ.append(circuit.Gate(name='α_gate', num_qubits=1, params=[0]), [0]) circ.append(circuit.Gate(name='α_gate$_2$', num_qubits=1, params=[0]), [1]) circ.append(circuit.Gate(name='\$α\$_gate', num_qubits=1, params=[0]), [1]) circ.draw(output='latex')agora, o rótulo do primeiro portão personalizado será
α_gate, o segundo seráα_gatecom um subscrito 2 e o rótulo do último portão personalizado será$α$_gate. -
Adicionar a classe
ControlledGatepara representar portas controladas. As instâncias de porta controlada são criadas com o métodocontrol(n)dos objetosGate, em quenrepresenta o número de controles. Os qubits de controle vêm antes dos qubits controlados na nova porta. Por exemplo:from qiskit import QuantumCircuit from qiskit.extensions import HGate hgate = HGate() circ = QuantumCircuit(4) circ.append(hgate.control(3), [0, 1, 2, 3]) print(circ)gera:
q_0: |0>──■── │ q_1: |0>──■── │ q_2: |0>──■── ┌─┴─┐ q_3: |0>┤ H ├ └───┘ -
Os valores permitidos dos parâmetros e campos do site
meas_levelagora podem ser um membro da classe IntEnumqiskit.qobj.utils.MeasLevel. Pode ser usado ao chamarexecute(ou em qualquer outro lugar em quemeas_levelseja especificado) com um experimento de pulso. Por exemplo:from qiskit import QuantumCircuit, transpile, schedule, execute from qiskit.test.mock import FakeOpenPulse2Q from qiskit.qobj.utils import MeasLevel, MeasReturnType backend = FakeOpenPulse2Q() qc = QuantumCircuit(2, 2) qc.h(0) qc.cx(0,1) qc_transpiled = transpile(qc, backend) sched = schedule(qc_transpiled, backend) execute(sched, backend, meas_level=MeasLevel.CLASSIFIED)Neste exemplo acima,
meas_level=MeasLevel.CLASSIFIEDemeas_level=2podem ser usados de forma intercambiável agora. -
Um novo seletor de layout baseado na solução de restrições está incluído. O CSPLayout modela o problema de encontrar um layout como um problema de restrição e usa o backtracking recursivo para resolvê-lo.
cmap16 = CouplingMap(FakeRueschlikon().configuration().coupling_map) qr = QuantumRegister(5, 'q') circuit = QuantumCircuit(qr) circuit.cx(qr[0], qr[1]) circuit.cx(qr[0], qr[2]) circuit.cx(qr[0], qr[3]) pm = PassManager(CSPLayout(cmap16)) circuit_after = pm.run(circuit) print(pm.property_set['layout'])Layout({ 1: Qubit(QuantumRegister(5, 'q'), 1), 2: Qubit(QuantumRegister(5, 'q'), 0), 3: Qubit(QuantumRegister(5, 'q'), 3), 4: Qubit(QuantumRegister(5, 'q'), 4), 15: Qubit(QuantumRegister(5, 'q'), 2) })O parâmetro
CSPLayout(...,strict_direction=True)é mais restritivo, mas garantirá que não seja necessário executar oCXDirectiondepois.pm = PassManager(CSPLayout(cmap16, strict_direction=True)) circuit_after = pm.run(circuit) print(pm.property_set['layout'])Layout({ 8: Qubit(QuantumRegister(5, 'q'), 4), 11: Qubit(QuantumRegister(5, 'q'), 3), 5: Qubit(QuantumRegister(5, 'q'), 1), 6: Qubit(QuantumRegister(5, 'q'), 0), 7: Qubit(QuantumRegister(5, 'q'), 2) })Se o sistema de restrição não for solucionável, a propriedade de layout não será definida.
circuit.cx(qr[0], qr[4]) pm = PassManager(CSPLayout(cmap16)) circuit_after = pm.run(circuit) print(pm.property_set['layout'])None -
PulseBackendConfiguration (acessado normalmente como backend.configuration ()) foi estendido com métodos úteis para explorar seus dados e a funcionalidade que existe em PulseChannelSpec. PulseChannelSpec será descontinuado no futuro. Por exemplo:
backend = provider.get_backend(backend_name) config = backend.configuration() q0_drive = config.drive(0) # or, DriveChannel(0) q0_meas = config.measure(0) # MeasureChannel(0) q0_acquire = config.acquire(0) # AcquireChannel(0) config.hamiltonian # Returns a dictionary with hamiltonian info config.sample_rate() # New method which returns 1 / dt -
PulseDefaults(acessado normalmente comobackend.defaults()) tem um atributo,circuit_instruction_map, que tem os métodos de CmdDef. O novo circuit_instruction_map é um objetoInstructionScheduleMapcom três novas funções além das que o CmdDef tinha:- qubit_instructions(qubits) retorna as operações definidas para os qubits
- assert_has(instruction, qubits) gera um erro se a operação não estiver definida
- remove(instruction, qubits) como pop, mas não requer parâmetros
Há algumas diferenças em relação ao CmdDef:
__init__não recebe argumentoscmdsecmd_qubitsestão obsoletos e foram substituídos porinstructionsequbits_with_instruction
Exemplo:
backend = provider.get_backend(backend_name) inst_map = backend.defaults().circuit_instruction_map qubit = inst_map.qubits_with_instruction('u3')[0] x_gate = inst_map.get('u3', qubit, P0=np.pi, P1=0, P2=np.pi) pulse_schedule = x_gate(DriveChannel(qubit)) -
Um novo parâmetro kwarg,
show_framechange_channels, para desativar opcionalmente a exibição de canais com apenas instruções de mudança de quadro em visualizações de pulso, foi adicionado à funçãoqiskit.visualization.pulse_drawer()e ao métodoqiskit.pulse.Schedule.draw(). Quando esse novo kwarg estiver definido comoFalse, a visualização do cronograma de pulso de saída não incluirá nenhum canal que inclua apenas alterações de quadro.Por exemplo:
from qiskit.pulse import * from qiskit.pulse import library as pulse_lib gp0 = pulse_lib.gaussian(duration=20, amp=1.0, sigma=1.0) sched = Schedule() channel_a = DriveChannel(0) channel_b = DriveChannel(1) sched += Play(gp0, channel_a) sched = sched.insert(60, ShiftPhase(-1.57, channel_a)) sched = sched.insert(30, ShiftPhase(-1.50, channel_b)) sched = sched.insert(70, ShiftPhase(1.50, channel_b)) sched.draw(show_framechange_channels=False) -
Uma nova função de utilidade
qiskit.result.marginal_counts()foi adicionada para permitir a marginalização das contagens em alguns índices de interesse. Isso é útil quando mais qubits são medidos do que o necessário e se deseja obter as contagens de observação apenas para algum subconjunto deles. -
Quando o
passmanager.run(...)for invocado com mais de um circuito, a transpilação desses circuitos será executada em paralelo. -
PassManagers pode agora ser cortado para criar um novo PassManager contendo um subconjunto de passes usando o operador de colchetes. Isso permite executar ou desenhar uma parte do site PassManager para facilitar o teste e a visualização. Por exemplo, vamos tentar desenhar as três primeiras passagens de um PassManager pm, ou executar apenas a segunda passagem em nosso circuito:
pm[0:4].draw() circuit2 = pm[1].run(circuit)Além disso, agora PassManagers pode ser criado adicionando dois PassManagers ou adicionando diretamente um passe/lista de passes a um PassManager.
pm = pm1[0] + pm2[1:3] pm += [setLayout, unroller] -
Um módulo básico do
schedulerfoi adicionado ao Qiskit. O agendador programa uma entrada transpiladaQuantumCircuitem um pulsoSchedule. O agendador aceita como entrada umSchedulee um pulsoBackend, ou umCmdDefque relaciona objetos de circuitoInstructionem qubits específicos a agendamentos de pulso e ummeas_mapque determina quais medições devem ocorrer juntas.Exemplo de agendamento:
from qiskit import QuantumCircuit, transpile, schedule from qiskit.test.mock import FakeOpenPulse2Q backend = FakeOpenPulse2Q() qc = QuantumCircuit(2, 2) qc.h(0) qc.cx(0,1) qc_transpiled = transpile(qc, backend) schedule(qc_transpiled, backend)Atualmente, o agendador é compatível com duas políticas de agendamento, as_late_as_possible (
alap) e as_soon_as_possible (asap), que respectivamente agendam as instruções de pulso para que ocorram o mais tarde possível ou o mais rápido possível nos qubits de um circuito. A política de agendamento pode ser selecionada com o argumento de entradamethod, por exemplo:schedule(qc_transpiled, backend, method='alap')É fácil usar um pulso
Scheduleem umQuantumCircuitmapeando-o para uma instrução de circuito personalizado, como uma porta que pode ser usada em umQuantumCircuit. Para fazer isso, primeiro defina a porta personalizada e, em seguida, adicione uma entrada no siteCmdDefpara a porta, para cada qubit ao qual a porta será aplicada. A porta pode então ser usada no siteQuantumCircuit. No momento do agendamento, a porta será mapeada para o agendamento de pulso subjacente. O uso dessa técnica permite a fácil integração com módulos qiskit preexistentes, como o Ignis.Por exemplo:
from qiskit import pulse, circuit, schedule from qiskit.pulse import pulse_lib custom_cmd_def = pulse.CmdDef() # create custom gate custom_gate = circuit.Gate(name='custom_gate', num_qubits=1, params=[]) # define schedule for custom gate custom_schedule = pulse.Schedule() custom_schedule += pulse_lib.gaussian(20, 1.0, 10)(pulse.DriveChannel) # add schedule to custom gate with same name custom_cmd_def.add('custom_gate', (0,), custom_schedule) # use custom gate in a circuit custom_qc = circuit.QuantumCircuit(1) custom_qc.append(custom_gate, qargs=[0]) # schedule the custom gate schedule(custom_qc, cmd_def=custom_cmd_def, meas_map=[[0]])
Problemas Conhecidos
- O recurso de transpilação em paralelo quando o site
passmanager.run(...)é chamado com mais de um circuito não é compatível com o Windows. Consulte #2988 para obter mais detalhes.
Notas da Atualização
- A classe
qiskit.pulse.channels.SystemTopologyfoi usada como uma classe auxiliar paraPulseChannelSpec. Ele foi removido com a descontinuidade doPulseChannelSpece as alterações noBackendConfigurationo tornaram desnecessário. - A representação anteriormente depreciada de qubits e bits clássicos como tuplas, que foi depreciada na versão 0.9, foi removida. O uso dos objetos
QubiteClbité a nova maneira de representar qubits e bits clássicos. - A representação anteriormente obsoleta do conjunto de bases como uma única cadeia de caracteres foi removida. Uma lista de cadeias de caracteres é a nova forma preferida.
- O método
BaseModel.as_dict, que foi descontinuado na versão 0.9, foi removido em favor do métodoBaseModel.to_dict. - Em PulseDefaults (acessado normalmente como backend.defaults ()),
qubit_freq_estemeas_freq_estagora são retornados em Hz em vez de GHz. Isso significa que os novos valores de retorno são 1e9 * seu valor anterior. - o endro foi adicionado como requisito. Isso é necessário para permitir a execução do
passmanager.run()em paralelo para mais de um circuito. - O portão
UBase, que foi descontinuado na versão 0.9, foi removido. O portãoU3Gatedeve ser usado em seu lugar. - O portão
CXBase, que foi descontinuado na versão 0.9, foi removido. O portãoCnotGatedeve ser usado em seu lugar. - A instrução
snapshotusada para converter implicitamente o parâmetrolabelem string. Essa conversão foi removida e um erro será gerado se uma cadeia de caracteres não for fornecida. - O portão
U0Gate, que foi descontinuado na versão 0.9, foi removido. Em vez disso, o gateIdGatedeve ser usado para inserir atrasos.
Notas de descontinuação
-
A classe
qiskit.pulse.CmdDeffoi descontinuada. Em vez disso, você deve usar o endereçoqiskit.pulse.InstructionScheduleMap. O objetoInstructionScheduleMappara um sistema ativado por pulso pode ser acessado embackend.defaults().instruction_schedules. -
PulseChannelSpecestá sendo descontinuado. Em vez disso, useBackendConfiguration. A configuração do backend é acessada normalmente comobackend.configuration(). A configuração foi ampliada com a maior parte da funcionalidade do site PulseChannelSpec, com algumas modificações, como segue, em que 0 é um índice de qubit exemplar:pulse_spec.drives[0] -> config.drive(0) pulse_spec.measures[0] -> config.measure(0) pulse_spec.acquires[0] -> config.acquire(0) pulse_spec.controls[0] -> config.control(0)Agora, se houver uma tentativa de obter um canal para um qubit que não exista para o dispositivo, um
BackendConfigurationErrorserá levantado com uma explicação útil.Os métodos
memoryslotseregisterslotsdo PulseChannelSpec não foram migrados para a configuração de back-end. Esses recursos clássicos não são limitados pela configuração física de um sistema de back-end. Instancie-os diretamente:pulse_spec.memoryslots[0] -> MemorySlot(0) pulse_spec.registerslots[0] -> RegisterSlot(0)O método
qubitsnão é migrado para a configuração de back-end. O resultado dequbitspode ser construído dessa forma:[q for q in range(backend.configuration().n_qubits)] -
Qubitempulse.channelsfoi descontinuado. Eles não devem ser usados. É possível obter mapeamentos de qubit do canal <=> por meio do BackendConfiguration (ou backend.configuration ()). -
A função
qiskit.visualization.circuit_drawer.qx_color_scheme()foi descontinuada. Essa função não é mais usada internamente e não reflete o estilo atual do IBM QX. Se você estiver usando essa função para gerar um dicionário de estilos localmente, deverá salvar a saída dessa função e usar esse dicionário diretamente. -
A Exception
TranspilerAccessErrorfoi descontinuada. Uma função alternativaTranspilerErrorpode ser usada para fornecer a mesma funcionalidade. Essa função alternativa oferece exatamente a mesma funcionalidade, mas com maior generalidade. -
Os buffers no Pulse estão obsoletos. Se for fornecido um buffer diferente de zero, será emitido um aviso com um lembrete para usar um Delay em vez disso. Outras opções incluem adicionar amostras a uma instrução de pulso que são ( 0.+0.j ) ou definir a hora de início do próximo pulso como
schedule.duration + buffer. -
A passagem dos tipos
sympy.Basic,sympy.Expresympy.Matrixcomo parâmetros de instrução está obsoleta e será removida em uma versão futura. Você precisará converter a entrada em um dos tipos compatíveis, que são:intfloatcomplexstrnp.ndarray
Correções de bugs
- As passagens Collect2qBlocks e CommutationAnalysis no transpilador não conseguiram processar circuitos que continham portas parametrizadas, impedindo que os circuitos parametrizados fossem transpilados no optimization_level 2 ou superior. Essas passagens foram corrigidas para tratar as portas parametrizadas como opacas.
- A função align_measures apresentava um problema em que os pulsos de estímulo Measure não eram alinhados corretamente com os pulsos Acquire, resultando em um erro. Isso foi corrigido.
- Os usos de
numpy.random.seedforam removidos para que as chamadas das funções do qiskit não afetem os resultados de futuras chamadas paranumpy.random - Foi corrigida a condição de corrida que ocorria no monitor de trabalho quando
job.queue_position()retornavaNone.Noneé um retorno válido dejob.queue_position(). - Suporte de backend para
memory=Trueagora é verificado quando esse kwarg é passado.QiskitErrorresultados se não for compatível. - Ao fazer a transpilação sem um mapa de acoplamento, não houve verificação da quantidade de qubits do circuito a ser transpilado. Agora, o processo de transpilação verifica se o backend tem qubits suficientes para alocar o circuito.
Outras observações
- A função
qiskit.result.marginal_counts()substitui uma função utilitária semelhante no qiskit-ignisqiskit.ignis.verification.tomography.marginal_counts(), que será descontinuada em uma versão futura do qiskit-ignis. - Todo o suporte ao tipo de saída de parâmetro sympy foi removido (ou obsoleto, conforme indicado) do qiskit-terra. Isso inclui parâmetros do tipo sympy em objetos
QuantumCircuit, nós qasm ast ou objetosQobj.
Aer 0.3
Nenhuma mudança
Ignis 0.2
Nenhuma mudança
Aqua 0.6
Nenhuma mudança
IBM 0.4
Prelúdio
A versão 0.4.0 é a primeira versão que faz uso de todos os recursos da nova API IBM Q. Em particular, a classe IBMQJob foi reformulada para poder recuperar mais informações do IBM Q, e uma classe Job Manager foi adicionada para permitir um uso de nível mais alto e mais contínuo de trabalhos grandes ou complexos. Se você ainda não fez o upgrade do antigo IBM Q Experience ou QConsole, consulte as notas de versão do IBM Q Provider 0.3 (Qiskit 0.11 ) para obter mais detalhes sobre como fazer a transição. As contas legadas não serão mais suportadas a partir desta versão.
Novos Recurso
Modificações no trabalho
A classe IBMQJob foi revisada e agora imita mais de perto o conteúdo de um trabalho remoto, além de novos recursos:
- Agora você pode atribuir um nome a um trabalho, especificando
IBMQBackend.run(..., job_name='...')ao enviar um trabalho. Esse nome pode ser recuperado pelo siteIBMQJob.name()e pode ser usado para filtragem. - Os trabalhos agora podem ser compartilhados com outros usuários em diferentes níveis (global, por hub, grupo ou projeto) por meio de um parâmetro opcional
job_share_levelao enviar o trabalho. IBMQJobagora têm mais atributos, refletindo o conteúdo dos trabalhos remotos do IBM Q. Isso implica que os novos atributos introduzidos pela API IBM Q estarão automática e imediatamente disponíveis para uso (por exemplo,job.new_api_attribute). Os novos atributos serão promovidos a métodos quando forem considerados estáveis (por exemplo,job.name())..error_message()retorna mais informações sobre o motivo da falha de um trabalho..queue_position()aceita um parâmetrorefreshpara forçar uma atualização..result()aceita um parâmetro opcionalpartial, para retornar resultados parciais, se houver, de trabalhos que falharam. Esteja ciente de que os métodosResult, comoget_counts(), gerarão uma exceção se forem aplicados em experimentos que falharam.
Observe que as alterações incluem algumas modificações de baixo nível na classe. Se você estava criando as instâncias manualmente, observe isso:
- a assinatura do construtor foi alterada para levar em conta os novos recursos.
- o método
.submit()não pode mais ser chamado diretamente, e espera-se que os trabalhos sejam enviados por meio doIBMQBackend.run()síncrono ou do Job Manager.
Gerenciador de Tarefa
Um novo Job Manager (IBMQJobManager) foi introduzido como um mecanismo de nível superior para lidar com trabalhos compostos de vários circuitos ou programações de pulso. O Job Manager tem como objetivo fornecer uma interface transparente, dividindo de forma inteligente a entrada em unidades de trabalho eficientes e aproveitando ao máximo os diferentes componentes. Ele será expandido nas próximas versões e se tornará o ponto de entrada recomendado para o envio de trabalhos.
Seu método .run() recebe uma lista de circuitos ou programações de pulso e retorna um ManagedJobSet instance, que pode ser usado para rastrear os status e os resultados desses trabalhos. Por exemplo:
from qiskit.providers.ibmq.managed import IBMQJobManager
from qiskit.circuit.random import random_circuit
from qiskit import IBMQ
from qiskit.compiler import transpile
provider = IBMQ.load_account()
backend = provider.backends.ibmq_ourense
circs = []
for _ in range(1000000):
circs.append(random_circuit(2, 2))
transpile(circs, backend=backend)
# Farm out the jobs.
jm = IBMQJobManager()
job_set = jm.run(circs, backend=backend, name='foo')
job_set.statuses() # Gives a list of job statuses
job_set.report() # Prints detailed job information
results = job_set.results()
counts = results.get_counts(5) # Returns data for experiment 5provider.backends modificações
O membro provider.backends , que anteriormente era uma função que retornava uma lista de backends, foi promovido a um serviço. Isso significa que ele pode ser usado da maneira anterior, como um método .backends() , e também como um atributo .backends com recursos expandidos:
-
ele contém os backends existentes desse provedor como atributos, que podem ser usados para preenchimento automático. Por exemplo:
my_backend = provider.get_backend('ibmq_qasm_simulator')é equivalente a:
my_backend = provider.backends.ibmq_qasm_simulator -
os métodos
provider.backends.jobs()eprovider.backends.retrieve_job()podem ser usados para recuperar trabalhos em todo o provedor.
Outras alterações
- A função
backend.properties()agora aceita um parâmetro opcionaldatetime. Se especificado, a função retorna as propriedades de backend mais próximas, mas mais antigas, do filtro de data e hora especificado. - Algumas mensagens do site
warningsforam reduzidas paralogger.warning.