Eu sei que existe algo chamado correção parcial, mas eu queria saber se havia uma maneira de saber quão próximo um programa semi-correto está de um programa totalmente correto.
Por exemplo, se você tivesse um programa de classificação que classifique quase completamente uma matriz, poderia usar a lógica Hoare para determinar o quão perto está de obter a resposta correta? Uma maneira de fazer isso seria tornar a pré-condição uma série de instruções e ver quantas dessas instruções a pré-condição mais fraca, resultante da propagação da pós-condição pelo programa, poderia implicar.
No entanto, este método parece ser muito bruto. Existe alguma outra maneira de fazer algo assim?
Respostas:
A correção parcial não significa que nem todas as instruções de uma especificação sejam atendidas por um algoritmo. Dê uma olhada no artigo da Wikipedia sobre correção :
A correção parcial de um algoritmo significa que ele retornará a resposta correta se terminar.
A correção total significa que é adicionalmente garantido que o algoritmo termina.
Essa prova de terminação pode, por exemplo, ser feita por uma variante de loop : Para provar que um loop termina, mostramos que uma expressão inteira é diminuída no corpo do loop e que a expressão permanece sempre não-negativa. Em seguida, o loop pode ser iterado apenas um número finito de vezes. O método B usa essas variantes inteiras em seus loops while. Uma alternativa para uma expressão inteira seria um conjunto finito onde, em cada iteração, um elemento é removido.
Exemplo : Um algoritmo simples para inicializar uma matriz de tamanho n com 0:
A correção parcial pode ser comprovada usando um loop invariável ("todos os elementos de x in
0..i
são 0"0<=i
,,i<=n
). É preciso mostrar que a invariante é cumprida ao entrar no loop e após cada iteração. Após o loop, sabemos que a invariante é cumprida e a condição do loop não (i>=n
junto com o invariante do loop implica quei=n
, isso implica novamente "todos os elementos de x in0..n
são 0"). Mesmo se esquecermos a linhai := i+1
, poderíamos provar a correção parcial do algoritmo, ou seja, a matriz será preenchida com 0 após o término. O problema seria que ele não termina.A rescisão pode ser mostrada escolhendo
n-i
como uma variante. Com a invariantei<=n
, pode-se provar que a variante é sempre não-negativa en-i
diminui (aumentando i) em cada iteração. Assim, o loop deve terminar. Juntamente com a correção parcial, a correção total é mostrada.fonte
Em poucas palavras:
A correção parcial é uma questão de rescisão, não a correção do que é calculado. Uma função está parcialmente correta com relação a uma especificação, se o que ela calcula estiver correto, quando terminar. Essa idéia pode ser estendida ao cálculo de respostas incompletas (parciais). O que quer que seja calculado da resposta está correto, mas o programa pode, em algum momento, entrar em um loop sem fim, possivelmente sem ter computado toda a resposta. Respostas parciais são aproximações de respostas completas.
Essa estrutura de aproximação é uma ordem parcial, que é o conceito básico dos domínios semânticos de Scott, e pode realmente ser usada para responder a outra interpretação da questão. Podemos medir a distância entre uma resposta correta e uma não muito correta, como ter um elemento de uma matriz que está errado (em vez de desconhecido). Uma maneira de definir essa distância é considerar a ordem das aproximações e relacionar as duas respostas incompatíveis (a correta e a incorreta) com a melhor resposta parcial que é uma aproximação de ambas. Esse problema é analisado rapidamente do ponto de vista da análise numérica, onde a análise de precisão é essencial e de algumas outras áreas.
Este segundo ponto é realmente explorado em uma segunda resposta à pergunta, pois não percebi a princípio que as duas respostas poderiam ter uma conexão. Mas as duas respostas são bastante longas e não me senti sábio em mesclá-las quando percebi a conexão.
Uma primeira visão simples da correção parcial
Não existe algo (parcialmente) correto em um sentido absoluto . Um programa está correto se atender a uma especificação, de qualquer forma. A especificação pode ser outro programa, uma declaração lógica ou qualquer outra coisa que possa ser formalizada. A especificação deve de alguma forma incluir informações sobre quando o programa termina, possivelmente sempre (o que é realmente assumido na maioria das definições, para que nada mais complexo precise ser dito). Na verdade, o domínio pode estar restrito na especificação à parte em que a rescisão é esperada, de modo que a rescisão seja sempre esperada , o que pode justificar a suposição de rescisão em todo o domínio na definição usual ( wikipedia e wikipedia) Por sua vez, isso impõe uma suposição implícita a qualquer especificação, que pode ser ou não ser tão conveniente.
Um programaP está correto com relação a uma especificação S
iff termina sempre que a especificação diz que deve, e com um resultado que atenda à especificação. Está parcialmente correto se, às vezes, não terminar onde a especificação diz que deveria, mas sempre fornecer um resultado correto ao finalizar.
Como conseqüência, um programa que nunca termina é parcialmente correto com relação a qualquer especificação .
Escolhi uma definição um pouco extensa também porque corresponde precisamente à noção de aproximação nos domínios semânticos de Scott , como a usada na semântica denotacional. Um domínio Scott inclui uma ordem parcial correspondente precisamente à idéia de correção parcial (os dois usos da palavra "parcial" não são relacionados). Uma funçãoF é e aproximação de uma função G é G termina sempre que F
termina e ambos dão o mesmo resultado. assimG pode dar um resultado quando F não. E podemos dizer queF está parcialmente correto com relação a G , ou aquilo F aproximado G ou F⊑ G .
Essas idéias são essenciais para definir a semântica de funções com loop (ou recursão) como o limite de um conjunto infinito de funções sem loop ou recursão. Veja, por exemplo , a Wikipedia , ou uma apresentação muito informal sobre SE .
A lógica padrão do Hoare funcionará apenas para provar a correção parcial e precisa ser estendida para tratar das propriedades de terminação, portanto, para corrigir a correção total (consulte a Wikipedia ). Existem exemplos implementados dessas extensões específicas.
Provar correção total equivale a comprovação de correção parcial e rescisão. A lógica de Hoare é bastante apropriada para correção parcial. Provar a terminação requer geralmente uma prova por indução (recorrência), que é a abordagem natural para provar as coisas na semântica de Scott (como a própria semântica é definida dessa maneira, indutivamente). A resposta de danielp mostra como tal indução pode complementar uma prova na lógica de Hoare.
Quanto à quantificação da correção parcial, supondo que você ainda queira fazê-lo, pode ser de alguma forma identificando as partes do domínio em que o programa termina ou não termina ou algumas propriedades dessas partes.
Extensão para resultados complexos, aplicada ao exemplo de classificação.
Na verdade, o problema pode ser um pouco mais complexo quando você considera respostas complexas, como estruturas de dados (que é o caso ao classificar matrizes). A especificação pode exigir o cálculo de duas respostas (ou seja, um par) e, para algumas partes do domínio de entrada, um programa real pode encontrar um elemento do par, mas não terminar enquanto calcula o outro, em outros casos, encontrar apenas o outro elemento, ou encontre ambos, ou não encontre nenhum. Ainda é uma aproximação no sentido de Scott, e esse programa está parcialmente correto.
De maneira mais geral, a idéia de aproximação no sentido de Scott se aplica aos dados e também ao programa. Para isso, informalmente, você precisa do conceito de uma resposta desconhecida (ainda não calculada, possivelmente nunca conhecida se o seu cálculo não terminar). Geralmente é representado pelo símbolo⊥ . O par( ⊥ , 36 ) aproxima ( 25 , 36 ) . O que você obtém de um programa que entrega a parte 36 e depois não termina pode ser representado por( ⊥ , 36 ) .
Como isso pode ser aplicado a um programa que classifica matrizes de cinco números inteiros? Suponha que você escreva um programa SORT5 que seja executado paralelamente ao seu aplicativo principal (estou tentando tornar as coisas realistas) e que deve classificar essa matriz para o aplicativo. O programa SORT5 deve armazenar seu resultado em alguma matriz fornecida pelo aplicativo e pode ser feito separadamente para cada elemento, assim que souber onde colocá-lo. Primeiro, ele procura o maior e o menor, e os armazena nas duas extremidades; depois, tenta fazer uma recursão (ou qualquer outra coisa), mas tem um bug que o envia a um loop infinito sem mais resultados. O aplicativo principal ainda recebe uma resposta parcial. Se a matriz a ser classificada fosse[ 25 , 36 , 3 , 9 , 12 ] , a resposta fornecida é [3,⊥,⊥,⊥,36] ao invés de
[3,9,12,25,36] . Tudo o que é fornecido está correto, e o restante não é calculado, de modo que você tem apenas parte da resposta . Assim, você tem uma aproximação do resultado desejado. Se você puder provar que esse é sempre o caso, seu programa de buggy SORT5 que não termina ainda estará parcialmente correto com relação à especificação de um programa de classificação.
Um programa parcialmente correto pode ser útil. Pode ser que você realmente não precise de classificação, mas apenas o maior e menor elemento. Nesse caso, o fato de o seu programa de classificação SORT5 não terminar e estar apenas parcialmente correto não importa, e seu aplicativo funcionará e, com sorte, será finalizado com uma resposta correta.
Mas quem interromperá o seu algoritmo de classificação não autorizado que continuará desperdiçando o poder da computação? Existem estratégias de computação (avaliação lenta), que não executam um subprograma quando mais informações sobre seu resultado não são necessárias no momento. Então, depois que você obtiver o maior e o menor elemento, o programa SORT5 ficará em espera até que outros elementos sejam solicitados.
Nesse caso, é claro, poderia haver uma maneira de quantificar a correção parcial. No entanto, não tenho certeza de que seria muito útil.
Nesse contexto, é necessário revisar um pouco a definição, que estou fazendo de maneira informal:
Um programa P está parcialmente correto em relação a uma especificação S, se der uma resposta completa que atenda à especificação antes de terminar ou forneça parte de uma resposta que atenda à especificação antes de entrar em um cálculo não final que não forneça mais parte da resposta .
Então, um programa que nunca termina e não produz parte do resultado está parcialmente correto com relação a qualquer especificação.
Observe que essa definição deixa de fora um programa que mantém a computação, sempre produzindo novas partes da resposta. Mas, como não produz números infinitesimais (não sei se isso poderia fazer sentido computacional), na verdade, ele está computando uma resposta infinita.
Essas técnicas podem realmente ser muito proveitosas para formalizar a semântica da computação de objetos infinitos (apenas para usuários muito pacientes), como a representação decimal exata (ou binária) do valor deπ ou listas infinitas. Existem outras aplicações interessantes. Mas isso está longe de ser a pergunta inicial, e é por isso que estou deixando de fora.
fonte
Quantificar a correção dos programas é atualmente um tópico bastante popular no contexto de métodos formais atualmente. Em vez de postar uma lista de referências, você pode começar aqui ¹ (versão completa aqui ) e continuar a partir das referências. Divulgação: este trabalho é um trabalho meu.
Um breve resumo deste trabalho: introduzimos um formalismo de especificação que aumenta a lógica temporal linear por um conjunto de "funções de qualidade". Essas funções são escolhidas pelo projetista, dando ao projetista a capacidade de definir a qualidade como desejar.
Mostramos que a verificação de modelo para essa lógica está no PSPACE. Usando as funções de qualidade apropriadas, você pode medir, por exemplo, a distância de uma matriz de uma ordenada.
fonte
Em princípio, é possível expressar essa condição usando algo como a lógica Hoare, mas não está claro que será muito útil ou prático fazê-lo.
Considere uma funçãof no seu programa, com um argumento. Suponha que tenhamos um predicadoP(x,y) , expressando a condição de que y é a resposta correta para a entrada x , ou seja, se f produz saída y na entrada x então esta saída está correta. Suponha também que tenhamos um predicadoQ(y,y′) expressando que as respostas y e y′ estão próximos um do outro. Definir o predicadoR(x,y′) de
EntãoR(x,y′) expressa a condição que você deseja, ou seja, que y′ está perto da resposta correta para a entrada x .
No seu exemplo,P(x,y) poderia expressar a afirmação de que y é uma versão classificada de x e Q(y,y′) pode expressar alguma métrica de distância nas listas (por exemplo, y′ pode ser obtido de y por um pequeno número de transposições).
Agora, esse é apenas o problema da especificação. Existe um problema separado de verificação, ou seja, verificar se uma funçãof atende às especificações R . O problema de verificação pode ser feio e difícil na prática. E, verificar se uma implementação de uma função atende a uma especificação específica é indecidível em geral, como jmite afirma. Portanto, como sempre na verificação, você está sempre lidando com indecidibilidade (por exemplo, incompletude).
fonte
Incorrecção
Escrevi uma primeira resposta sobre correção parcial, que tem um significado técnico preciso. Achei melhor separar essa outra resposta, que inicialmente pensei ser tecnicamente muito diferente. Acontece que não é bem verdade, mas ambas as respostas são longas o suficiente, então achei melhor não mesclá-las
Aparentemente, parece que o OP está mais interessado em uma idéia de programas que estão parcialmente incorretos, em encontrar respostas que estejam incorretas em algum aspecto, embora, eu acho, espero que não muito longe de estar correto.
Na verdade, existem duas maneiras de considerar a proximidade para corrigir uma função:
se as respostas computadas têm partes corretas e incorretas ou
se às vezes estão corretas e às vezes incorretas.
Mas esses dois aspectos podem ser combinados. Se você conseguir definir algo como uma distância entre valores no conjunto de respostas, tente estendê-lo para uma distância entre funções que são, extremamente informalmente, algum tipo de integral da distância do resultado para cada ponto do domínio. , ou alguma outra função da incorreta para todos os pontos do domínio.
A questão pode ser determinar se a distância entre a função completamente correta e a programada não excede algum limite fixo ou se o erro no resultado da aplicação da função não excede, para cada ponto do domínio, um limite que possa estar relacionado para este ponto de domínio.
Essas técnicas também podem ser úteis para fazer a computação o mais correta possível, com dados que, em certo sentido, não estão corretos, como resultados experimentais. Quando o grau de incorreto pode ser avaliado ou levantado a hipótese, isso pode ajudar a acompanhar seu efeito no cálculo.
Provavelmente, isso depende muito do tipo de dados em que você está computando.
Acredito que já exista essa teoria para a computação numérica e é frequentemente aplicada a trabalhos técnicos, mas sei pouco sobre isso. Aspectos elementares são frequentemente ensinados em cursos de física.
Muita computação numérica lida com números reais. Não pode ser exato (correto) porque o computador usa apenas aproximações de números reais (existe um conceito de computação com aritmética real exata , mas é um tópico muito diferente, muito relacionado teoricamente à correção parcial ). Aproximações na computação numérica causam pequenos erros ( erros de arredondamento) que podem se propagar e, às vezes, ficar fora de controle. Portanto, os numericistas desenvolveram técnicas para analisar seus programas e avaliar a proximidade da resposta ao resultado correto. Na verdade, eles projetam seus algoritmos para minimizar os erros computacionais, além de outros critérios usuais, porque a ordem de algumas operações pode ter uma influência profunda no tamanho do erro que está sendo propagado.
Essas técnicas também são importantes porque muitas vezes precisam lidar com dados físicos que são apenas "próximos ao correto", isto é, dados com alguma aproximação. Manipular os erros na entrada juntamente com os erros computacionais, e sua propagação é, acredito, o objeto de uma pesquisa significativa no campo da Análise Numérica . Mas eu não sou especialista. Algum programa calculará o resultado aproximado e um intervalo de erro em torno dele, onde a resposta correta deve ser encontrada. Isso combina os erros de medição física e os erros de cálculo numérico.
Entretanto, embora isso fosse essencialmente inevitável na matemática numérica que lida com reais (um conjunto contínuo de valores), não há uma limitação interna semelhante na computação simbólica , portanto, não há incentivo óbvio, a priori, para o desenvolvimento de técnicas semelhantes. Além disso, pode não ser óbvio fazê-lo.
Ainda assim, uma análise cuidadosa das técnicas de tratamento de erros na análise e no processamento de linguagem natural mostra que elas realmente usam uma visão conceitual semelhante, mesmo em um contexto puramente simbólico.
A resposta de Shaull parece indicar que alguém pode estar interessado nessas idéias de aproximação na engenharia de software , mas não tenho certeza de que lide com os mesmos conceitos. Não li o artigo dele e li pouco da literatura sobre esse assunto, e a resposta não dá nenhuma dica das técnicas que ele pode estar considerando.
Pode ser uma idéia muito diferente, uma vez que a engenharia de software está muito preocupada em medir quão buggy o software pode ser, mas inadvertidamente buggy. Eu sei que algumas análises estatísticas mostram que vários parâmetros que podem ser medidos em um programa estão estatisticamente relacionados à qualidade do programa, manutenção e probabilidade de erros.
Os ides das respostas aproximadas na análise numérica (por exemplo) não são uma questão de bugs, mas de lidar com as limitações das medidas físicas, bem como as limitações da computação (que é inerentemente contável) quando usada para lidar com reais ( que são incontáveis). Se é um bug, a culpa é do nosso universo, não dos programadores.
Tentativa de unificar os problemas: medição parcial da correção e da correção
O que se segue é puramente especulativo e uma indicação do trabalho que poderia ser feito. Eu suspeitaria que pelo menos parte disso já tenha sido feita (não pesquisei completamente). Mas não me lembro de ler sobre o assunto e não posso dar referências apropriadas. A descrição é apenas um esboço, e é provável que muito disso deva ser refinado ou tornado mais preciso, incluindo a escolha de definições. Não posso garantir nada que não tenha sido totalmente elaborado matematicamente (e mesmo assim ... :).
Existe literatura publicada sobre o cálculo de números reais com base em definições de aproximações de números reais que os organizam em um domínio Scott. Aproximar reais com intervalos é certamente uma maneira de fazê-lo, e essa é uma maneira adequada de desenvolver uma teoria da computabilidade sobre os reais. Meu palpite é que já deve ter sido feito, e oferece uma boa base para uma teoria semântica e para analisar programas de trituração de números reais, juntamente com uma avaliação da precisão do resultado, conforme descrito acima. (Não tive a oportunidade de perguntar a um especialista).
Agora, isso pode ser uma dica sobre o que fazer com a computação simbólica, ou com a computação sobre números inteiros, para obter uma noção de computação aproximadamente correta, especialmente na presença de dados complexos, ou seja, o uso de estruturas de dados.
A idéia básica é a mesma que na real, use um conceito de aproximação e organize seu domínio de computação (os valores com os quais você calcula) como um domínio Scoot. No entanto, precisará ser algo como uma treliça , onde dois elementos devem ter um maior limite inferior (glb ou meet), bem como um limite inferior (superior ou lub). No caso numérico, o glb corresponde ao menor intervalo que contém 2 outros intervalos, e o lub à interseção do intervalo.
Tomando nosso exemplo de classificação da primeira resposta , classificando uma matriz de 5 números[25,36,3,9,12] , poderíamos considerar todas as matrizes parciais como uma treliça e ter:
Agora, se você definir uma noção de distância na estrutura da ordem, poderá definir a distância entre duas respostas possíveis como a soma de suas distâncias em relação à glb (ou alguma outra função simétrica e monotonicamente crescente dessas duas distâncias).
Se o domínio não possuir glb , você poderá percorrer as distâncias de acordo com cada um dos limites inferiores (na verdade, apenas os elementos máximos do conjunto de limites inferiores) e considerar a menor distância possível (ou possivelmente alguma outra função das distâncias de elementos máximos, com propriedades adequadas).
O ponto importante é ter uma definição tratável da distância de correção sobre os dados que você manipula.
Então, essa noção de distância pode ser estendida para medições de distâncias entre funções, o que deve ser uma resposta à pergunta. Não sei quanto é necessário um aparato matemático extra, pois pode ser necessária alguma forma de integração (no sentido de cálculo).
Uma pesquisa superficial da Web sobre esses problemas resultou no seguinte artigo: " Em direção à distância de computação entre programas via domínios de Scott ", que já tem 15 anos. Deve fornecer um melhor fundo matemático. Mas eu encontrei depois de escrever esta resposta.
Esse problema pode ser resolvido com outra lógica, mas suspeito que seja muito mais um problema para o conceito de aproximação nos domínios de valores. Existem outras maneiras de construí-las além da descrita acima para matrizes. Definir aproximações aos dados pode fazer parte da definição de um tipo de dados abstrato ou de uma classe na programação OO.
Nota: eu não esperava essa conexão com a minha resposta anterior. daqui as duas respostas separadas.
fonte